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","300", "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 sv_curl_maxspeed = {CVAR_SAVE, "sv_curl_maxspeed","0", "maximum download speed for clients downloading from sv_curl_defaulturl (KiB/s)"};
10 static cvar_t cl_curl_enabled = {CVAR_SAVE, "cl_curl_enabled","1", "whether client's download support is enabled"};
13 =================================================================
15 Minimal set of definitions from libcurl
17 WARNING: for a matter of simplicity, several pointer types are
18 casted to "void*", and most enumerated values are not included
20 =================================================================
23 typedef struct CURL_s CURL;
24 typedef struct CURLM_s CURLM;
32 CURLM_CALL_MULTI_PERFORM=-1, /* please call curl_multi_perform() soon */
36 #define CURL_GLOBAL_NOTHING 0
37 #define CURL_GLOBAL_SSL 1
38 #define CURL_GLOBAL_WIN32 2
39 #define CURLOPTTYPE_LONG 0
40 #define CURLOPTTYPE_OBJECTPOINT 10000
41 #define CURLOPTTYPE_FUNCTIONPOINT 20000
42 #define CURLOPTTYPE_OFF_T 30000
43 #define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number
46 CINIT(WRITEDATA, OBJECTPOINT, 1),
47 CINIT(URL, OBJECTPOINT, 2),
48 CINIT(ERRORBUFFER, OBJECTPOINT, 10),
49 CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11),
50 CINIT(REFERER, OBJECTPOINT, 16),
51 CINIT(USERAGENT, OBJECTPOINT, 18),
52 CINIT(RESUME_FROM, LONG, 21),
53 CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */
54 CINIT(PRIVATE, OBJECTPOINT, 103),
55 CINIT(LOW_SPEED_LIMIT, LONG , 19),
56 CINIT(LOW_SPEED_TIME, LONG, 20),
57 CINIT(PROTOCOLS, LONG, 181),
58 CINIT(REDIR_PROTOCOLS, LONG, 182),
61 #define CURLPROTO_HTTP (1<<0)
62 #define CURLPROTO_HTTPS (1<<1)
63 #define CURLPROTO_FTP (1<<2)
67 CURLINFO_HEADER_IN, /* 1 */
68 CURLINFO_HEADER_OUT, /* 2 */
69 CURLINFO_DATA_IN, /* 3 */
70 CURLINFO_DATA_OUT, /* 4 */
71 CURLINFO_SSL_DATA_IN, /* 5 */
72 CURLINFO_SSL_DATA_OUT, /* 6 */
76 #define CURLINFO_STRING 0x100000
77 #define CURLINFO_LONG 0x200000
78 #define CURLINFO_DOUBLE 0x300000
79 #define CURLINFO_SLIST 0x400000
80 #define CURLINFO_MASK 0x0fffff
81 #define CURLINFO_TYPEMASK 0xf00000
84 CURLINFO_NONE, /* first, never use this */
85 CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1,
86 CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2,
87 CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3,
88 CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4,
89 CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5,
90 CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6,
91 CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7,
92 CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8,
93 CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9,
94 CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10,
95 CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11,
96 CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12,
97 CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13,
98 CURLINFO_FILETIME = CURLINFO_LONG + 14,
99 CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15,
100 CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16,
101 CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17,
102 CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18,
103 CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19,
104 CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20,
105 CURLINFO_PRIVATE = CURLINFO_STRING + 21,
106 CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22,
107 CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23,
108 CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24,
109 CURLINFO_OS_ERRNO = CURLINFO_LONG + 25,
110 CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26,
111 CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27,
117 CURLMSG_NONE, /* first, not used */
118 CURLMSG_DONE, /* This easy handle has completed. 'result' contains
119 the CURLcode of the transfer */
125 CURLMSG msg; /* what this message means */
126 CURL *easy_handle; /* the handle it concerns */
129 void *whatever; /* message-specific data */
130 CURLcode result; /* return code for transfer */
136 static void (*qcurl_global_init) (long flags);
137 static void (*qcurl_global_cleanup) (void);
139 static CURL * (*qcurl_easy_init) (void);
140 static void (*qcurl_easy_cleanup) (CURL *handle);
141 static CURLcode (*qcurl_easy_setopt) (CURL *handle, CURLoption option, ...);
142 static CURLcode (*qcurl_easy_getinfo) (CURL *handle, CURLINFO info, ...);
143 static const char * (*qcurl_easy_strerror) (CURLcode);
145 static CURLM * (*qcurl_multi_init) (void);
146 static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles);
147 static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle);
148 static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle);
149 static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue);
150 static void (*qcurl_multi_cleanup) (CURLM *);
151 static const char * (*qcurl_multi_strerror) (CURLcode);
153 static dllfunction_t curlfuncs[] =
155 {"curl_global_init", (void **) &qcurl_global_init},
156 {"curl_global_cleanup", (void **) &qcurl_global_cleanup},
157 {"curl_easy_init", (void **) &qcurl_easy_init},
158 {"curl_easy_cleanup", (void **) &qcurl_easy_cleanup},
159 {"curl_easy_setopt", (void **) &qcurl_easy_setopt},
160 {"curl_easy_strerror", (void **) &qcurl_easy_strerror},
161 {"curl_easy_getinfo", (void **) &qcurl_easy_getinfo},
162 {"curl_multi_init", (void **) &qcurl_multi_init},
163 {"curl_multi_perform", (void **) &qcurl_multi_perform},
164 {"curl_multi_add_handle", (void **) &qcurl_multi_add_handle},
165 {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle},
166 {"curl_multi_info_read", (void **) &qcurl_multi_info_read},
167 {"curl_multi_cleanup", (void **) &qcurl_multi_cleanup},
168 {"curl_multi_strerror", (void **) &qcurl_multi_strerror},
172 // Handle for CURL DLL
173 static dllhandle_t curl_dll = NULL;
174 // will be checked at many places to find out if qcurl calls are allowed
176 typedef struct downloadinfo_s
178 char filename[MAX_OSPATH];
182 fs_offset_t startpos;
186 unsigned long bytes_received;
187 struct downloadinfo_s *next, *prev;
191 unsigned char *buffer;
193 curl_callback_t callback;
197 static downloadinfo *downloads = NULL;
198 static int numdownloads = 0;
200 static qboolean noclear = FALSE;
202 static int numdownloads_fail = 0;
203 static int numdownloads_success = 0;
204 static int numdownloads_added = 0;
205 static char command_when_done[256] = "";
206 static char command_when_error[256] = "";
212 Sets the command which is to be executed when the last download completes AND
213 all downloads since last server connect ended with a successful status.
214 Setting the command to NULL clears it.
217 void Curl_CommandWhenDone(const char *cmd)
222 strlcpy(command_when_done, cmd, sizeof(command_when_done));
224 *command_when_done = 0;
229 Do not use yet. Not complete.
230 Problem: what counts as an error?
233 void Curl_CommandWhenError(const char *cmd)
238 strlcpy(command_when_error, cmd, sizeof(command_when_error));
240 *command_when_error = 0;
245 Curl_Clear_forthismap
247 Clears the "will disconnect on failure" flags.
250 void Curl_Clear_forthismap(void)
255 for(di = downloads; di; di = di->next)
256 di->forthismap = false;
257 Curl_CommandWhenError(NULL);
258 Curl_CommandWhenDone(NULL);
259 numdownloads_fail = 0;
260 numdownloads_success = 0;
261 numdownloads_added = 0;
268 Returns true if a download needed for the current game is running.
271 qboolean Curl_Have_forthismap(void)
273 return numdownloads_added != 0;
276 void Curl_Register_predownload(void)
278 Curl_CommandWhenDone("cl_begindownloads");
279 Curl_CommandWhenError("cl_begindownloads");
284 Curl_CheckCommandWhenDone
286 Checks if a "done command" is to be executed.
287 All downloads finished, at least one success since connect, no single failure
288 -> execute the command.
290 static void Curl_CheckCommandWhenDone(void)
294 if(numdownloads_added && (numdownloads_success == numdownloads_added) && *command_when_done)
296 Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done);
298 Cbuf_AddText(command_when_done);
300 Curl_Clear_forthismap();
302 else if(numdownloads_added && numdownloads_fail && *command_when_error)
304 Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error);
306 Cbuf_AddText(command_when_error);
308 Curl_Clear_forthismap();
319 static qboolean CURL_OpenLibrary (void)
321 const char* dllnames [] =
326 #elif defined(MACOSX)
327 "libcurl.4.dylib", // Mac OS X Notyetreleased
328 "libcurl.3.dylib", // Mac OS X Tiger
329 "libcurl.2.dylib", // Mac OS X Panther
333 "libcurl.so", // FreeBSD
343 return Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs);
354 static void CURL_CloseLibrary (void)
356 Sys_UnloadLibrary (&curl_dll);
360 static CURLM *curlm = NULL;
361 static unsigned long bytes_received = 0; // used for bandwidth throttling
362 static double curltime = 0;
368 fwrite-compatible function that writes the data to a file. libcurl can call
372 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
374 fs_offset_t ret = -1;
375 size_t bytes = size * nmemb;
376 downloadinfo *di = (downloadinfo *) vdi;
380 if(di->bytes_received + bytes <= di->buffersize)
382 memcpy(di->buffer + di->bytes_received, data, bytes);
385 // otherwise: buffer overrun, ret stays -1
390 ret = FS_Write(di->stream, data, bytes);
393 bytes_received += bytes;
394 di->bytes_received += bytes;
396 return ret; // why not ret / nmemb?
401 CURL_DOWNLOAD_SUCCESS = 0,
402 CURL_DOWNLOAD_FAILED,
403 CURL_DOWNLOAD_ABORTED,
404 CURL_DOWNLOAD_SERVERERROR
408 static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
410 downloadinfo *di = (downloadinfo *) cbdata;
413 case CURLCBSTATUS_OK:
414 Con_DPrintf("Download of %s: OK\n", di->filename);
416 case CURLCBSTATUS_FAILED:
417 Con_DPrintf("Download of %s: FAILED\n", di->filename);
419 case CURLCBSTATUS_ABORTED:
420 Con_DPrintf("Download of %s: ABORTED\n", di->filename);
422 case CURLCBSTATUS_SERVERERROR:
423 Con_DPrintf("Download of %s: (unknown server error)\n", di->filename);
425 case CURLCBSTATUS_UNKNOWN:
426 Con_DPrintf("Download of %s: (unknown client error)\n", di->filename);
429 Con_DPrintf("Download of %s: %d\n", di->filename, status);
434 static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
436 curl_default_callback(status, length_received, buffer, cbdata);
443 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
444 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
445 code from libcurl, or 0, if another error has occurred.
448 static qboolean Curl_Begin(const char *URL, double maxspeed, const char *name, qboolean ispak, qboolean forthismap, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata);
449 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error)
456 case CURL_DOWNLOAD_SUCCESS:
458 di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
460 case CURL_DOWNLOAD_FAILED:
461 di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
463 case CURL_DOWNLOAD_ABORTED:
464 di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
466 case CURL_DOWNLOAD_SERVERERROR:
467 // reopen to enforce it to have zero bytes again
470 FS_Close(di->stream);
471 di->stream = FS_OpenRealFile(di->filename, "wb", false);
475 di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
479 di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
485 qcurl_multi_remove_handle(curlm, di->curle);
486 qcurl_easy_cleanup(di->curle);
489 if(!di->callback && ok && !di->bytes_received)
491 Con_Printf("ERROR: empty file\n");
496 FS_Close(di->stream);
500 ok = FS_AddPack(di->filename, NULL, true);
503 // pack loading failed?
505 // better clear the file again...
506 di->stream = FS_OpenRealFile(di->filename, "wb", false);
507 FS_Close(di->stream);
509 if(di->startpos && !di->callback)
511 // this was a resume?
512 // then try to redownload it without reporting the error
513 Curl_Begin(di->url, di->maxspeed, di->filename, di->ispak, di->forthismap, NULL, 0, NULL, NULL);
514 di->forthismap = false; // don't count the error
520 di->prev->next = di->next;
522 downloads = di->next;
524 di->next->prev = di->prev;
530 ++numdownloads_success;
539 CheckPendingDownloads
541 checks if there are free download slots to start new downloads in.
542 To not start too many downloads at once, only one download is added at a time,
543 up to a maximum number of cl_curl_maxdownloads are running.
546 static void CheckPendingDownloads(void)
550 if(numdownloads < cl_curl_maxdownloads.integer)
553 for(di = downloads; di; di = di->next)
559 Con_Printf("Downloading %s -> %s", di->url, di->filename);
561 di->stream = FS_OpenRealFile(di->filename, "ab", false);
564 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
565 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
568 FS_Seek(di->stream, 0, SEEK_END);
569 di->startpos = FS_Tell(di->stream);
572 Con_Printf(", resuming from position %ld", (long) di->startpos);
577 Con_DPrintf("Downloading %s -> memory\n", di->url);
581 di->curle = qcurl_easy_init();
582 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
583 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
584 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
585 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
586 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
587 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
588 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
589 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
590 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
591 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
592 qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP);
593 if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK)
595 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");
596 //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0);
599 qcurl_multi_add_handle(curlm, di->curle);
602 if(numdownloads >= cl_curl_maxdownloads.integer)
613 this function MUST be called before using anything else in this file.
614 On Win32, this must be called AFTER WSAStartup has been done!
622 qcurl_global_init(CURL_GLOBAL_NOTHING);
623 curlm = qcurl_multi_init();
630 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
633 void Curl_ClearRequirements(void);
634 void Curl_Shutdown(void)
638 Curl_ClearRequirements();
648 Finds the internal information block for a download given by file name.
651 static downloadinfo *Curl_Find(const char *filename)
656 for(di = downloads; di; di = di->next)
657 if(!strcasecmp(di->filename, filename))
662 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
667 for(di = downloads; di; )
669 if(di->callback == callback && di->callback_data == cbdata)
671 di->callback = curl_quiet_callback; // do NOT call the callback
672 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
684 Starts a download of a given URL to the file name portion of this URL (or name
685 if given) in the "dlcache/" folder.
688 static qboolean Curl_Begin(const char *URL, double maxspeed, const char *name, qboolean ispak, qboolean forthismap, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
702 // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server
703 p = strchr(URL, ':');
706 if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4))
708 char addressstring[128];
710 InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring));
711 q = strchr(addressstring, ':');
713 q = addressstring + strlen(addressstring);
716 dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3);
722 // Note: This extraction of the file name portion is NOT entirely correct.
724 // It does the following:
726 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
727 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
728 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
730 // However, I'd like to keep this "buggy" behavior so that PHP script
731 // authors can write download scripts without having to enable
732 // AcceptPathInfo on Apache. They just have to ensure that their script
733 // can be called with such a "fake" path name like
734 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
736 // By the way, such PHP scripts should either send the file or a
737 // "Location:" redirect; PHP code example:
739 // header("Location: http://www.example.com/");
741 // By the way, this will set User-Agent to something like
742 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
743 // dp://serverhost:serverport/ so you can filter on this; an example
744 // httpd log file line might be:
746 // 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"
753 p = strrchr(name, '/');
754 p = p ? (p+1) : name;
756 length = q ? (size_t)(q - p) : strlen(p);
757 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
759 name = fn; // make it point back
761 // already downloading the file?
763 downloadinfo *di = Curl_Find(fn);
766 Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
768 // however, if it was not for this map yet...
769 if(forthismap && !di->forthismap)
771 di->forthismap = true;
772 // this "fakes" a download attempt so the client will wait for
773 // the download to finish and then reconnect
774 ++numdownloads_added;
781 if(ispak && FS_FileExists(fn))
783 qboolean already_loaded;
784 if(FS_AddPack(fn, &already_loaded, true))
786 Con_DPrintf("%s already exists, not downloading!\n", fn);
788 Con_DPrintf("(pak was already loaded)\n");
793 ++numdownloads_added;
794 ++numdownloads_success;
802 qfile_t *f = FS_OpenRealFile(fn, "rb", false);
806 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
808 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
810 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
812 f = FS_OpenRealFile(fn, "wb", false);
826 // if we get here, we actually want to download... so first verify the
827 // URL scheme (so one can't read local files using file://)
828 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
830 Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
835 ++numdownloads_added;
836 di = (downloadinfo *) Z_Malloc(sizeof(*di));
837 strlcpy(di->filename, name, sizeof(di->filename));
838 strlcpy(di->url, URL, sizeof(di->url));
839 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
840 di->forthismap = forthismap;
845 di->ispak = (ispak && !buf);
846 di->maxspeed = maxspeed;
847 di->bytes_received = 0;
848 di->next = downloads;
854 di->buffersize = bufsize;
857 di->callback = curl_default_callback;
858 di->callback_data = di;
862 di->callback = callback;
863 di->callback_data = cbdata;
871 qboolean Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, qboolean ispak, qboolean forthismap)
873 return Curl_Begin(URL, maxspeed, name, ispak, forthismap, NULL, 0, NULL, NULL);
875 qboolean Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
877 return Curl_Begin(URL, maxspeed, NULL, false, false, buf, bufsize, callback, cbdata);
884 call this regularily as this will always download as much as possible without
895 if(!cl_curl_enabled.integer)
901 Curl_CheckCommandWhenDone();
906 if(realtime < curltime) // throttle
915 mc = qcurl_multi_perform(curlm, &remaining);
917 while(mc == CURLM_CALL_MULTI_PERFORM);
921 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
924 if(msg->msg == CURLMSG_DONE)
926 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
928 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
929 result = msg->data.result;
932 failed = CURL_DOWNLOAD_FAILED;
937 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
942 failed = CURL_DOWNLOAD_SERVERERROR;
943 result = (CURLcode) code;
948 Curl_EndDownload(di, failed, result);
953 CheckPendingDownloads();
955 // when will we curl the next time?
956 // we will wait a bit to ensure our download rate is kept.
957 // we now know that realtime >= curltime... so set up a new curltime
959 // use the slowest allowing download to derive the maxspeed... this CAN
960 // be done better, but maybe later
961 maxspeed = cl_curl_maxspeed.value;
962 for(di = downloads; di; di = di->next)
964 if(di->maxspeed < maxspeed || maxspeed <= 0)
965 maxspeed = di->maxspeed;
969 unsigned long bytes = bytes_received; // maybe smoothen a bit?
970 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
971 bytes_received -= bytes;
984 void Curl_CancelAll(void)
991 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
992 // INVARIANT: downloads will point to the next download after that!
1000 returns true iff there is a download running.
1001 ====================
1003 qboolean Curl_Running(void)
1008 return downloads != NULL;
1012 ====================
1013 Curl_GetDownloadAmount
1015 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
1016 for the given download.
1017 ====================
1019 static double Curl_GetDownloadAmount(downloadinfo *di)
1026 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1028 return (di->startpos + di->bytes_received) / (di->startpos + length);
1037 ====================
1038 Curl_GetDownloadSpeed
1040 returns the speed of the given download in bytes per second
1041 ====================
1043 static double Curl_GetDownloadSpeed(downloadinfo *di)
1050 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1058 ====================
1061 prints the download list
1062 ====================
1064 // TODO rewrite using Curl_GetDownloadInfo?
1065 static void Curl_Info_f(void)
1072 Con_Print("Currently running downloads:\n");
1073 for(di = downloads; di; di = di->next)
1075 double speed, percent;
1076 Con_Printf(" %s -> %s ", di->url, di->filename);
1077 percent = 100.0 * Curl_GetDownloadAmount(di);
1078 speed = Curl_GetDownloadSpeed(di);
1080 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1082 Con_Print("(queued)\n");
1087 Con_Print("No downloads running.\n");
1092 ====================
1095 implements the "curl" console command
1099 curl --cancel filename
1104 curl [--pak] [--forthismap] [--for filename filename...] url
1105 --pak: after downloading, load the package into the virtual file system
1106 --for filename...: only download of at least one of the named files is missing
1107 --forthismap: don't reconnect on failure
1109 curl --clear_autodownload
1110 clears the download success/failure counters
1112 curl --finish_autodownload
1113 if at least one download has been started, disconnect and drop to the menu
1114 once the last download completes successfully, reconnect to the current server
1115 ====================
1117 void Curl_Curl_f(void)
1119 double maxspeed = 0;
1122 qboolean pak = false;
1123 qboolean forthismap = false;
1125 const char *name = 0;
1129 Con_Print("libcurl DLL not found, this command is inactive.\n");
1133 if(!cl_curl_enabled.integer)
1135 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1139 for(i = 0; i != Cmd_Argc(); ++i)
1140 Con_DPrintf("%s ", Cmd_Argv(i));
1145 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1149 url = Cmd_Argv(Cmd_Argc() - 1);
1152 for(i = 1; i != end; ++i)
1154 const char *a = Cmd_Argv(i);
1155 if(!strcmp(a, "--info"))
1160 else if(!strcmp(a, "--cancel"))
1162 if(i == end - 1) // last argument
1166 downloadinfo *di = Curl_Find(url);
1168 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1170 Con_Print("download not found\n");
1174 else if(!strcmp(a, "--pak"))
1178 else if(!strcmp(a, "--for")) // must be last option
1180 for(i = i + 1; i != end - 1; ++i)
1182 if(!FS_FileExists(Cmd_Argv(i)))
1183 goto needthefile; // why can't I have a "double break"?
1185 // if we get here, we have all the files...
1188 else if(!strcmp(a, "--forthismap"))
1192 else if(!strcmp(a, "--as"))
1200 else if(!strcmp(a, "--clear_autodownload"))
1202 // mark all running downloads as "not for this map", so if they
1203 // fail, it does not matter
1204 Curl_Clear_forthismap();
1207 else if(!strcmp(a, "--finish_autodownload"))
1209 if(numdownloads_added)
1211 char donecommand[256];
1214 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1216 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1217 Curl_CommandWhenDone(donecommand);
1221 Curl_CheckCommandWhenDone();
1224 Curl_Register_predownload();
1229 else if(!strncmp(a, "--maxspeed=", 11))
1231 maxspeed = atof(a + 11);
1235 Con_Printf("curl: invalid option %s\n", a);
1236 // but we ignore the option
1241 Curl_Begin_ToFile(url, maxspeed, name, pak, forthismap);
1245 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1247 Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1251 void Curl_CurlCat_f(void)
1254 const char *url = Cmd_Argv(1);
1255 buf = Z_Malloc(16384);
1256 Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1261 ====================
1264 loads the commands and cvars this library uses
1265 ====================
1267 void Curl_Init_Commands(void)
1269 Cvar_RegisterVariable (&cl_curl_enabled);
1270 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1271 Cvar_RegisterVariable (&cl_curl_maxspeed);
1272 Cvar_RegisterVariable (&sv_curl_defaulturl);
1273 Cvar_RegisterVariable (&sv_curl_serverpackages);
1274 Cvar_RegisterVariable (&sv_curl_maxspeed);
1275 Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1276 //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1280 ====================
1281 Curl_GetDownloadInfo
1283 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1284 The number of elements in the array is returned in int *nDownloads.
1285 const char **additional_info may be set to a string of additional user
1286 information, or to NULL if no such display shall occur. The returned
1287 array must be freed later using Z_Free.
1288 ====================
1290 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1294 Curl_downloadinfo_t *downinfo;
1295 static char addinfo[128];
1301 *additional_info = NULL;
1306 for(di = downloads; di; di = di->next)
1309 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1311 for(di = downloads; di; di = di->next)
1313 // do not show infobars for background downloads
1314 if(!developer.integer)
1317 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1320 downinfo[i].progress = Curl_GetDownloadAmount(di);
1321 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1322 downinfo[i].queued = false;
1326 downinfo[i].queued = true;
1333 // TODO: can I clear command_when_done as soon as the first download fails?
1334 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1336 if(!strncmp(command_when_done, "connect ", 8))
1337 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1338 else if(!strcmp(command_when_done, "cl_begindownloads"))
1339 dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
1341 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1342 *additional_info = addinfo;
1345 *additional_info = NULL;
1354 ====================
1357 finds the URL where to find a given package.
1359 For this, it reads a file "curl_urls.txt" of the following format:
1362 revdm*.pk3 http://revdm/downloads/are/here/
1363 * http://any/other/stuff/is/here/
1365 The URLs should end in /. If not, downloads will still work, but the cached files
1366 can't be just put into the data directory with the same download configuration
1367 (you might want to do this if you want to tag downloaded files from your
1368 server, but you should not). "-" means "don't download".
1370 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1373 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1374 this file for obvious reasons.
1375 ====================
1377 static const char *Curl_FindPackURL(const char *filename)
1379 static char foundurl[1024];
1380 fs_offset_t filesize;
1381 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1384 // read lines of format "pattern url"
1386 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1387 qboolean eof = false;
1399 if(pattern && url && patternend)
1405 if(matchpattern(filename, pattern, true))
1407 strlcpy(foundurl, url, sizeof(foundurl));
1419 if(pattern && !patternend)
1421 else if(url && !urlend)
1427 else if(pattern && patternend && !url)
1436 return sv_curl_defaulturl.string;
1439 typedef struct requirement_s
1441 struct requirement_s *next;
1442 char filename[MAX_OSPATH];
1445 static requirement *requirements = NULL;
1449 ====================
1452 Adds the given file to the list of requirements.
1453 ====================
1455 void Curl_RequireFile(const char *filename)
1457 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1458 req->next = requirements;
1459 strlcpy(req->filename, filename, sizeof(req->filename));
1464 ====================
1465 Curl_ClearRequirements
1467 Clears the list of required files for playing on the current map.
1468 This should be called at every map change.
1469 ====================
1471 void Curl_ClearRequirements(void)
1476 requirement *req = requirements;
1477 requirements = requirements->next;
1480 p = sv_curl_serverpackages.string;
1481 Con_DPrintf("Require all of: %s\n", p);
1482 while(COM_ParseToken_Simple(&p, false, false))
1484 Con_DPrintf("Require: %s\n", com_token);
1485 Curl_RequireFile(com_token);
1490 ====================
1491 Curl_SendRequirements
1493 Makes the current host_clients download all files he needs.
1494 This is done by sending him the following console commands:
1496 curl --clear_autodownload
1497 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1498 curl --finish_autodownload
1499 ====================
1501 void Curl_SendRequirements(void)
1503 // for each requirement, find the pack name
1504 char sendbuffer[4096] = "";
1506 qboolean foundone = false;
1508 for(req = requirements; req; req = req->next)
1511 const char *thispack = FS_WhichPack(req->filename);
1512 const char *packurl;
1517 p = strrchr(thispack, '/');
1521 packurl = Curl_FindPackURL(thispack);
1523 if(packurl && *packurl && strcmp(packurl, "-"))
1526 strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
1528 strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
1529 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1530 if(sv_curl_maxspeed.value > 0)
1531 dpsnprintf(sendbuffer + strlen(sendbuffer), sizeof(sendbuffer) - strlen(sendbuffer), " --maxspeed=%.1f", sv_curl_maxspeed.value);
1532 strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
1533 strlcat(sendbuffer, req->filename, sizeof(sendbuffer));
1534 strlcat(sendbuffer, " ", sizeof(sendbuffer));
1535 strlcat(sendbuffer, packurl, sizeof(sendbuffer));
1536 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1537 strlcat(sendbuffer, "\n", sizeof(sendbuffer));
1544 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1546 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1547 Host_ClientCommands("%s", sendbuffer);
1549 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");