5 static cvar_t cl_curl_maxdownloads = {CVAR_SAVE, "cl_curl_maxdownloads","1", "maximum number of concurrent HTTP/FTP downloads"};
6 static cvar_t cl_curl_maxspeed = {CVAR_SAVE, "cl_curl_maxspeed","100", "maximum download speed (KiB/s)"};
7 static cvar_t sv_curl_defaulturl = {CVAR_SAVE, "sv_curl_defaulturl","", "default autodownload source URL"};
8 static cvar_t sv_curl_serverpackages = {CVAR_SAVE, "sv_curl_serverpackages","", "list of required files for the clients, separated by spaces"};
9 static cvar_t cl_curl_enabled = {CVAR_SAVE, "cl_curl_enabled","1", "whether client's download support is enabled"};
12 =================================================================
14 Minimal set of definitions from libcurl
16 WARNING: for a matter of simplicity, several pointer types are
17 casted to "void*", and most enumerated values are not included
19 =================================================================
22 typedef struct CURL_s CURL;
23 typedef struct CURLM_s CURLM;
31 CURLM_CALL_MULTI_PERFORM=-1, /* please call curl_multi_perform() soon */
35 #define CURL_GLOBAL_NOTHING 0
36 #define CURL_GLOBAL_SSL 1
37 #define CURL_GLOBAL_WIN32 2
38 #define CURLOPTTYPE_LONG 0
39 #define CURLOPTTYPE_OBJECTPOINT 10000
40 #define CURLOPTTYPE_FUNCTIONPOINT 20000
41 #define CURLOPTTYPE_OFF_T 30000
42 #define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number
45 CINIT(WRITEDATA, OBJECTPOINT, 1),
46 CINIT(URL, OBJECTPOINT, 2),
47 CINIT(ERRORBUFFER, OBJECTPOINT, 10),
48 CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11),
49 CINIT(REFERER, OBJECTPOINT, 16),
50 CINIT(USERAGENT, OBJECTPOINT, 18),
51 CINIT(RESUME_FROM, LONG, 21),
52 CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */
53 CINIT(PRIVATE, OBJECTPOINT, 103),
54 CINIT(LOW_SPEED_LIMIT, LONG , 19),
55 CINIT(LOW_SPEED_TIME, LONG, 20),
56 CINIT(PROTOCOLS, LONG, 181),
57 CINIT(REDIR_PROTOCOLS, LONG, 182),
60 #define CURLPROTO_HTTP (1<<0)
61 #define CURLPROTO_HTTPS (1<<1)
62 #define CURLPROTO_FTP (1<<2)
66 CURLINFO_HEADER_IN, /* 1 */
67 CURLINFO_HEADER_OUT, /* 2 */
68 CURLINFO_DATA_IN, /* 3 */
69 CURLINFO_DATA_OUT, /* 4 */
70 CURLINFO_SSL_DATA_IN, /* 5 */
71 CURLINFO_SSL_DATA_OUT, /* 6 */
75 #define CURLINFO_STRING 0x100000
76 #define CURLINFO_LONG 0x200000
77 #define CURLINFO_DOUBLE 0x300000
78 #define CURLINFO_SLIST 0x400000
79 #define CURLINFO_MASK 0x0fffff
80 #define CURLINFO_TYPEMASK 0xf00000
83 CURLINFO_NONE, /* first, never use this */
84 CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1,
85 CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2,
86 CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3,
87 CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4,
88 CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5,
89 CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6,
90 CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7,
91 CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8,
92 CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9,
93 CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10,
94 CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11,
95 CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12,
96 CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13,
97 CURLINFO_FILETIME = CURLINFO_LONG + 14,
98 CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15,
99 CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16,
100 CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17,
101 CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18,
102 CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19,
103 CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20,
104 CURLINFO_PRIVATE = CURLINFO_STRING + 21,
105 CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22,
106 CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23,
107 CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24,
108 CURLINFO_OS_ERRNO = CURLINFO_LONG + 25,
109 CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26,
110 CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27,
116 CURLMSG_NONE, /* first, not used */
117 CURLMSG_DONE, /* This easy handle has completed. 'result' contains
118 the CURLcode of the transfer */
124 CURLMSG msg; /* what this message means */
125 CURL *easy_handle; /* the handle it concerns */
128 void *whatever; /* message-specific data */
129 CURLcode result; /* return code for transfer */
135 static void (*qcurl_global_init) (long flags);
136 static void (*qcurl_global_cleanup) (void);
138 static CURL * (*qcurl_easy_init) (void);
139 static void (*qcurl_easy_cleanup) (CURL *handle);
140 static CURLcode (*qcurl_easy_setopt) (CURL *handle, CURLoption option, ...);
141 static CURLcode (*qcurl_easy_getinfo) (CURL *handle, CURLINFO info, ...);
142 static const char * (*qcurl_easy_strerror) (CURLcode);
144 static CURLM * (*qcurl_multi_init) (void);
145 static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles);
146 static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle);
147 static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle);
148 static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue);
149 static void (*qcurl_multi_cleanup) (CURLM *);
150 static const char * (*qcurl_multi_strerror) (CURLcode);
152 static dllfunction_t curlfuncs[] =
154 {"curl_global_init", (void **) &qcurl_global_init},
155 {"curl_global_cleanup", (void **) &qcurl_global_cleanup},
156 {"curl_easy_init", (void **) &qcurl_easy_init},
157 {"curl_easy_cleanup", (void **) &qcurl_easy_cleanup},
158 {"curl_easy_setopt", (void **) &qcurl_easy_setopt},
159 {"curl_easy_strerror", (void **) &qcurl_easy_strerror},
160 {"curl_easy_getinfo", (void **) &qcurl_easy_getinfo},
161 {"curl_multi_init", (void **) &qcurl_multi_init},
162 {"curl_multi_perform", (void **) &qcurl_multi_perform},
163 {"curl_multi_add_handle", (void **) &qcurl_multi_add_handle},
164 {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle},
165 {"curl_multi_info_read", (void **) &qcurl_multi_info_read},
166 {"curl_multi_cleanup", (void **) &qcurl_multi_cleanup},
167 {"curl_multi_strerror", (void **) &qcurl_multi_strerror},
171 // Handle for CURL DLL
172 static dllhandle_t curl_dll = NULL;
173 // will be checked at many places to find out if qcurl calls are allowed
175 typedef struct downloadinfo_s
177 char filename[MAX_OSPATH];
181 fs_offset_t startpos;
185 unsigned long bytes_received;
186 struct downloadinfo_s *next, *prev;
189 unsigned char *buffer;
191 curl_callback_t callback;
195 static downloadinfo *downloads = NULL;
196 static int numdownloads = 0;
198 static qboolean noclear = FALSE;
200 static int numdownloads_fail = 0;
201 static int numdownloads_success = 0;
202 static int numdownloads_added = 0;
203 static char command_when_done[256] = "";
204 static char command_when_error[256] = "";
210 Sets the command which is to be executed when the last download completes AND
211 all downloads since last server connect ended with a successful status.
212 Setting the command to NULL clears it.
215 void Curl_CommandWhenDone(const char *cmd)
220 strlcpy(command_when_done, cmd, sizeof(command_when_done));
222 *command_when_done = 0;
227 Do not use yet. Not complete.
228 Problem: what counts as an error?
231 void Curl_CommandWhenError(const char *cmd)
236 strlcpy(command_when_error, cmd, sizeof(command_when_error));
238 *command_when_error = 0;
243 Curl_Clear_forthismap
245 Clears the "will disconnect on failure" flags.
248 void Curl_Clear_forthismap(void)
253 for(di = downloads; di; di = di->next)
254 di->forthismap = false;
255 Curl_CommandWhenError(NULL);
256 Curl_CommandWhenDone(NULL);
257 numdownloads_fail = 0;
258 numdownloads_success = 0;
259 numdownloads_added = 0;
266 Returns true if a download needed for the current game is running.
269 qboolean Curl_Have_forthismap(void)
271 return numdownloads_added != 0;
274 void Curl_Register_predownload(void)
276 Curl_CommandWhenDone("cl_begindownloads");
277 Curl_CommandWhenError("cl_begindownloads");
282 Curl_CheckCommandWhenDone
284 Checks if a "done command" is to be executed.
285 All downloads finished, at least one success since connect, no single failure
286 -> execute the command.
288 static void Curl_CheckCommandWhenDone(void)
292 if(numdownloads_added && (numdownloads_success == numdownloads_added) && *command_when_done)
294 Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done);
296 Cbuf_AddText(command_when_done);
298 Curl_Clear_forthismap();
300 else if(numdownloads_added && numdownloads_fail && *command_when_error)
302 Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error);
304 Cbuf_AddText(command_when_error);
306 Curl_Clear_forthismap();
317 static qboolean CURL_OpenLibrary (void)
319 const char* dllnames [] =
324 #elif defined(MACOSX)
325 "libcurl.4.dylib", // Mac OS X Notyetreleased
326 "libcurl.3.dylib", // Mac OS X Tiger
327 "libcurl.2.dylib", // Mac OS X Panther
331 "libcurl.so", // FreeBSD
341 return Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs);
352 static void CURL_CloseLibrary (void)
354 Sys_UnloadLibrary (&curl_dll);
358 static CURLM *curlm = NULL;
359 static unsigned long bytes_received = 0; // used for bandwidth throttling
360 static double curltime = 0;
366 fwrite-compatible function that writes the data to a file. libcurl can call
370 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
372 fs_offset_t ret = -1;
373 size_t bytes = size * nmemb;
374 downloadinfo *di = (downloadinfo *) vdi;
378 if(di->bytes_received + bytes <= di->buffersize)
380 memcpy(di->buffer + di->bytes_received, data, bytes);
383 // otherwise: buffer overrun, ret stays -1
388 ret = FS_Write(di->stream, data, bytes);
391 bytes_received += bytes;
392 di->bytes_received += bytes;
394 return ret; // why not ret / nmemb?
399 CURL_DOWNLOAD_SUCCESS = 0,
400 CURL_DOWNLOAD_FAILED,
401 CURL_DOWNLOAD_ABORTED,
402 CURL_DOWNLOAD_SERVERERROR
406 static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
408 downloadinfo *di = (downloadinfo *) cbdata;
411 case CURLCBSTATUS_OK:
412 Con_DPrintf("Download of %s: OK\n", di->filename);
414 case CURLCBSTATUS_FAILED:
415 Con_DPrintf("Download of %s: FAILED\n", di->filename);
417 case CURLCBSTATUS_ABORTED:
418 Con_DPrintf("Download of %s: ABORTED\n", di->filename);
420 case CURLCBSTATUS_SERVERERROR:
421 Con_DPrintf("Download of %s: (unknown server error)\n", di->filename);
423 case CURLCBSTATUS_UNKNOWN:
424 Con_DPrintf("Download of %s: (unknown client error)\n", di->filename);
427 Con_DPrintf("Download of %s: %d\n", di->filename, status);
432 static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
434 curl_default_callback(status, length_received, buffer, cbdata);
441 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
442 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
443 code from libcurl, or 0, if another error has occurred.
446 static qboolean Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean forthismap, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata);
447 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error)
454 case CURL_DOWNLOAD_SUCCESS:
456 di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
458 case CURL_DOWNLOAD_FAILED:
459 di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
461 case CURL_DOWNLOAD_ABORTED:
462 di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
464 case CURL_DOWNLOAD_SERVERERROR:
465 // reopen to enforce it to have zero bytes again
468 FS_Close(di->stream);
469 di->stream = FS_OpenRealFile(di->filename, "wb", false);
473 di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
477 di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
483 qcurl_multi_remove_handle(curlm, di->curle);
484 qcurl_easy_cleanup(di->curle);
487 if(!di->callback && ok && !di->bytes_received)
489 Con_Printf("ERROR: empty file\n");
494 FS_Close(di->stream);
498 ok = FS_AddPack(di->filename, NULL, true);
501 // pack loading failed?
503 // better clear the file again...
504 di->stream = FS_OpenRealFile(di->filename, "wb", false);
505 FS_Close(di->stream);
507 if(di->startpos && !di->callback)
509 // this was a resume?
510 // then try to redownload it without reporting the error
511 Curl_Begin(di->url, di->filename, di->ispak, di->forthismap, NULL, 0, NULL, NULL);
512 di->forthismap = false; // don't count the error
518 di->prev->next = di->next;
520 downloads = di->next;
522 di->next->prev = di->prev;
528 ++numdownloads_success;
537 CheckPendingDownloads
539 checks if there are free download slots to start new downloads in.
540 To not start too many downloads at once, only one download is added at a time,
541 up to a maximum number of cl_curl_maxdownloads are running.
544 static void CheckPendingDownloads(void)
548 if(numdownloads < cl_curl_maxdownloads.integer)
551 for(di = downloads; di; di = di->next)
557 Con_Printf("Downloading %s -> %s", di->url, di->filename);
559 di->stream = FS_OpenRealFile(di->filename, "ab", false);
562 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
563 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
566 FS_Seek(di->stream, 0, SEEK_END);
567 di->startpos = FS_Tell(di->stream);
570 Con_Printf(", resuming from position %ld", (long) di->startpos);
575 Con_DPrintf("Downloading %s -> memory\n", di->url);
579 di->curle = qcurl_easy_init();
580 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
581 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
582 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
583 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
584 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
585 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
586 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
587 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
588 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
589 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
590 qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP);
591 if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK)
593 Con_Printf("^1WARNING:^7 for security reasons, please upgrade to libcurl 7.19.4 or above. In a later version of DarkPlaces, HTTP redirect support will be disabled for this libcurl version.\n");
594 //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0);
597 qcurl_multi_add_handle(curlm, di->curle);
600 if(numdownloads >= cl_curl_maxdownloads.integer)
611 this function MUST be called before using anything else in this file.
612 On Win32, this must be called AFTER WSAStartup has been done!
620 qcurl_global_init(CURL_GLOBAL_NOTHING);
621 curlm = qcurl_multi_init();
628 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
631 void Curl_ClearRequirements(void);
632 void Curl_Shutdown(void)
636 Curl_ClearRequirements();
646 Finds the internal information block for a download given by file name.
649 static downloadinfo *Curl_Find(const char *filename)
654 for(di = downloads; di; di = di->next)
655 if(!strcasecmp(di->filename, filename))
660 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
665 for(di = downloads; di; )
667 if(di->callback == callback && di->callback_data == cbdata)
669 di->callback = curl_quiet_callback; // do NOT call the callback
670 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
682 Starts a download of a given URL to the file name portion of this URL (or name
683 if given) in the "dlcache/" folder.
686 static qboolean Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean forthismap, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
700 // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server
701 p = strchr(URL, ':');
704 if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4))
706 char addressstring[128];
708 InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring));
709 q = strchr(addressstring, ':');
711 q = addressstring + strlen(addressstring);
714 dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3);
720 // Note: This extraction of the file name portion is NOT entirely correct.
722 // It does the following:
724 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
725 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
726 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
728 // However, I'd like to keep this "buggy" behavior so that PHP script
729 // authors can write download scripts without having to enable
730 // AcceptPathInfo on Apache. They just have to ensure that their script
731 // can be called with such a "fake" path name like
732 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
734 // By the way, such PHP scripts should either send the file or a
735 // "Location:" redirect; PHP code example:
737 // header("Location: http://www.example.com/");
739 // By the way, this will set User-Agent to something like
740 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
741 // dp://serverhost:serverport/ so you can filter on this; an example
742 // httpd log file line might be:
744 // 141.2.16.3 - - [17/Mar/2006:22:32:43 +0100] "GET /maps/tznex07.pk3 HTTP/1.1" 200 1077455 "dp://141.2.16.7:26000/" "Nexuiz Linux 22:07:43 Mar 17 2006"
751 p = strrchr(name, '/');
752 p = p ? (p+1) : name;
754 length = q ? (size_t)(q - p) : strlen(p);
755 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
757 name = fn; // make it point back
759 // already downloading the file?
761 downloadinfo *di = Curl_Find(fn);
764 Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
766 // however, if it was not for this map yet...
767 if(forthismap && !di->forthismap)
769 di->forthismap = true;
770 // this "fakes" a download attempt so the client will wait for
771 // the download to finish and then reconnect
772 ++numdownloads_added;
779 if(ispak && FS_FileExists(fn))
781 qboolean already_loaded;
782 if(FS_AddPack(fn, &already_loaded, true))
784 Con_DPrintf("%s already exists, not downloading!\n", fn);
786 Con_DPrintf("(pak was already loaded)\n");
791 ++numdownloads_added;
792 ++numdownloads_success;
800 qfile_t *f = FS_OpenRealFile(fn, "rb", false);
804 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
806 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
808 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
810 f = FS_OpenRealFile(fn, "wb", false);
824 // if we get here, we actually want to download... so first verify the
825 // URL scheme (so one can't read local files using file://)
826 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
828 Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
833 ++numdownloads_added;
834 di = (downloadinfo *) Z_Malloc(sizeof(*di));
835 strlcpy(di->filename, name, sizeof(di->filename));
836 strlcpy(di->url, URL, sizeof(di->url));
837 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
838 di->forthismap = forthismap;
843 di->ispak = (ispak && !buf);
844 di->bytes_received = 0;
845 di->next = downloads;
851 di->buffersize = bufsize;
854 di->callback = curl_default_callback;
855 di->callback_data = di;
859 di->callback = callback;
860 di->callback_data = cbdata;
868 qboolean Curl_Begin_ToFile(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
870 return Curl_Begin(URL, name, ispak, forthismap, NULL, 0, NULL, NULL);
872 qboolean Curl_Begin_ToMemory(const char *URL, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
874 return Curl_Begin(URL, NULL, false, false, buf, bufsize, callback, cbdata);
881 call this regularily as this will always download as much as possible without
889 if(!cl_curl_enabled.integer)
895 Curl_CheckCommandWhenDone();
900 if(realtime < curltime) // throttle
909 mc = qcurl_multi_perform(curlm, &remaining);
911 while(mc == CURLM_CALL_MULTI_PERFORM);
915 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
918 if(msg->msg == CURLMSG_DONE)
921 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
923 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
924 result = msg->data.result;
927 failed = CURL_DOWNLOAD_FAILED;
932 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
937 failed = CURL_DOWNLOAD_SERVERERROR;
938 result = (CURLcode) code;
943 Curl_EndDownload(di, failed, result);
948 CheckPendingDownloads();
950 // when will we curl the next time?
951 // we will wait a bit to ensure our download rate is kept.
952 // we now know that realtime >= curltime... so set up a new curltime
953 if(cl_curl_maxspeed.value > 0)
955 unsigned long bytes = bytes_received; // maybe smoothen a bit?
956 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
957 bytes_received -= bytes;
970 void Curl_CancelAll(void)
977 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
978 // INVARIANT: downloads will point to the next download after that!
986 returns true iff there is a download running.
989 qboolean Curl_Running(void)
994 return downloads != NULL;
999 Curl_GetDownloadAmount
1001 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
1002 for the given download.
1003 ====================
1005 static double Curl_GetDownloadAmount(downloadinfo *di)
1012 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1014 return (di->startpos + di->bytes_received) / (di->startpos + length);
1023 ====================
1024 Curl_GetDownloadSpeed
1026 returns the speed of the given download in bytes per second
1027 ====================
1029 static double Curl_GetDownloadSpeed(downloadinfo *di)
1036 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1044 ====================
1047 prints the download list
1048 ====================
1050 // TODO rewrite using Curl_GetDownloadInfo?
1051 static void Curl_Info_f(void)
1058 Con_Print("Currently running downloads:\n");
1059 for(di = downloads; di; di = di->next)
1061 double speed, percent;
1062 Con_Printf(" %s -> %s ", di->url, di->filename);
1063 percent = 100.0 * Curl_GetDownloadAmount(di);
1064 speed = Curl_GetDownloadSpeed(di);
1066 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1068 Con_Print("(queued)\n");
1073 Con_Print("No downloads running.\n");
1078 ====================
1081 implements the "curl" console command
1085 curl --cancel filename
1090 curl [--pak] [--forthismap] [--for filename filename...] url
1091 --pak: after downloading, load the package into the virtual file system
1092 --for filename...: only download of at least one of the named files is missing
1093 --forthismap: don't reconnect on failure
1095 curl --clear_autodownload
1096 clears the download success/failure counters
1098 curl --finish_autodownload
1099 if at least one download has been started, disconnect and drop to the menu
1100 once the last download completes successfully, reconnect to the current server
1101 ====================
1103 void Curl_Curl_f(void)
1107 qboolean pak = false;
1108 qboolean forthismap = false;
1110 const char *name = 0;
1114 Con_Print("libcurl DLL not found, this command is inactive.\n");
1118 if(!cl_curl_enabled.integer)
1120 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1124 for(i = 0; i != Cmd_Argc(); ++i)
1125 Con_DPrintf("%s ", Cmd_Argv(i));
1130 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1134 url = Cmd_Argv(Cmd_Argc() - 1);
1137 for(i = 1; i != end; ++i)
1139 const char *a = Cmd_Argv(i);
1140 if(!strcmp(a, "--info"))
1145 else if(!strcmp(a, "--cancel"))
1147 if(i == end - 1) // last argument
1151 downloadinfo *di = Curl_Find(url);
1153 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1155 Con_Print("download not found\n");
1159 else if(!strcmp(a, "--pak"))
1163 else if(!strcmp(a, "--for"))
1165 for(i = i + 1; i != end - 1; ++i)
1167 if(!FS_FileExists(Cmd_Argv(i)))
1168 goto needthefile; // why can't I have a "double break"?
1170 // if we get here, we have all the files...
1173 else if(!strcmp(a, "--forthismap"))
1177 else if(!strcmp(a, "--as"))
1185 else if(!strcmp(a, "--clear_autodownload"))
1187 // mark all running downloads as "not for this map", so if they
1188 // fail, it does not matter
1189 Curl_Clear_forthismap();
1192 else if(!strcmp(a, "--finish_autodownload"))
1194 if(numdownloads_added)
1196 char donecommand[256];
1199 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1201 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1202 Curl_CommandWhenDone(donecommand);
1206 Curl_CheckCommandWhenDone();
1209 Curl_Register_predownload();
1216 Con_Printf("invalid option %s\n", a);
1222 Curl_Begin_ToFile(url, name, pak, forthismap);
1226 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1228 Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1232 void Curl_CurlCat_f(void)
1235 const char *url = Cmd_Argv(1);
1236 buf = Z_Malloc(16384);
1237 Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1242 ====================
1245 loads the commands and cvars this library uses
1246 ====================
1248 void Curl_Init_Commands(void)
1250 Cvar_RegisterVariable (&cl_curl_enabled);
1251 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1252 Cvar_RegisterVariable (&cl_curl_maxspeed);
1253 Cvar_RegisterVariable (&sv_curl_defaulturl);
1254 Cvar_RegisterVariable (&sv_curl_serverpackages);
1255 Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1256 //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1260 ====================
1261 Curl_GetDownloadInfo
1263 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1264 The number of elements in the array is returned in int *nDownloads.
1265 const char **additional_info may be set to a string of additional user
1266 information, or to NULL if no such display shall occur. The returned
1267 array must be freed later using Z_Free.
1268 ====================
1270 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1274 Curl_downloadinfo_t *downinfo;
1275 static char addinfo[128];
1281 *additional_info = NULL;
1286 for(di = downloads; di; di = di->next)
1289 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1291 for(di = downloads; di; di = di->next)
1293 // do not show infobars for background downloads
1294 if(!developer.integer)
1297 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1300 downinfo[i].progress = Curl_GetDownloadAmount(di);
1301 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1302 downinfo[i].queued = false;
1306 downinfo[i].queued = true;
1313 // TODO: can I clear command_when_done as soon as the first download fails?
1314 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1316 if(!strncmp(command_when_done, "connect ", 8))
1317 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1318 else if(!strcmp(command_when_done, "cl_begindownloads"))
1319 dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
1321 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1322 *additional_info = addinfo;
1325 *additional_info = NULL;
1334 ====================
1337 finds the URL where to find a given package.
1339 For this, it reads a file "curl_urls.txt" of the following format:
1342 revdm*.pk3 http://revdm/downloads/are/here/
1343 * http://any/other/stuff/is/here/
1345 The URLs should end in /. If not, downloads will still work, but the cached files
1346 can't be just put into the data directory with the same download configuration
1347 (you might want to do this if you want to tag downloaded files from your
1348 server, but you should not). "-" means "don't download".
1350 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1353 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1354 this file for obvious reasons.
1355 ====================
1357 static const char *Curl_FindPackURL(const char *filename)
1359 static char foundurl[1024];
1360 fs_offset_t filesize;
1361 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1364 // read lines of format "pattern url"
1366 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1367 qboolean eof = false;
1379 if(pattern && url && patternend)
1385 if(matchpattern(filename, pattern, true))
1387 strlcpy(foundurl, url, sizeof(foundurl));
1399 if(pattern && !patternend)
1401 else if(url && !urlend)
1407 else if(pattern && patternend && !url)
1416 return sv_curl_defaulturl.string;
1419 typedef struct requirement_s
1421 struct requirement_s *next;
1422 char filename[MAX_OSPATH];
1425 static requirement *requirements = NULL;
1429 ====================
1432 Adds the given file to the list of requirements.
1433 ====================
1435 void Curl_RequireFile(const char *filename)
1437 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1438 req->next = requirements;
1439 strlcpy(req->filename, filename, sizeof(req->filename));
1444 ====================
1445 Curl_ClearRequirements
1447 Clears the list of required files for playing on the current map.
1448 This should be called at every map change.
1449 ====================
1451 void Curl_ClearRequirements(void)
1456 requirement *req = requirements;
1457 requirements = requirements->next;
1460 p = sv_curl_serverpackages.string;
1461 Con_DPrintf("Require all of: %s\n", p);
1462 while(COM_ParseToken_Simple(&p, false, false))
1464 Con_DPrintf("Require: %s\n", com_token);
1465 Curl_RequireFile(com_token);
1470 ====================
1471 Curl_SendRequirements
1473 Makes the current host_clients download all files he needs.
1474 This is done by sending him the following console commands:
1476 curl --clear_autodownload
1477 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1478 curl --finish_autodownload
1479 ====================
1481 void Curl_SendRequirements(void)
1483 // for each requirement, find the pack name
1484 char sendbuffer[4096] = "";
1486 qboolean foundone = false;
1488 for(req = requirements; req; req = req->next)
1491 const char *thispack = FS_WhichPack(req->filename);
1492 const char *packurl;
1497 p = strrchr(thispack, '/');
1501 packurl = Curl_FindPackURL(thispack);
1503 if(packurl && *packurl && strcmp(packurl, "-"))
1506 strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
1508 strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
1509 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1510 strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
1511 strlcat(sendbuffer, req->filename, sizeof(sendbuffer));
1512 strlcat(sendbuffer, " ", sizeof(sendbuffer));
1513 strlcat(sendbuffer, packurl, sizeof(sendbuffer));
1514 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1515 strlcat(sendbuffer, "\n", sizeof(sendbuffer));
1522 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1524 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1525 Host_ClientCommands("%s", sendbuffer);
1527 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");