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;
541 Returns a "cleaned up" URL for display (to strip login data)
544 static const char *CleanURL(const char *url)
546 static char urlbuf[1024];
547 const char *p, *q, *r;
549 // if URL is of form anything://foo-without-slash@rest, replace by anything://rest
550 p = strstr(url, "://");
553 q = strchr(p + 3, '@');
556 r = strchr(p + 3, '/');
559 dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s%s", (int)(p - url + 3), url, q + 1);
570 CheckPendingDownloads
572 checks if there are free download slots to start new downloads in.
573 To not start too many downloads at once, only one download is added at a time,
574 up to a maximum number of cl_curl_maxdownloads are running.
577 static void CheckPendingDownloads(void)
581 if(numdownloads < cl_curl_maxdownloads.integer)
584 for(di = downloads; di; di = di->next)
590 Con_Printf("Downloading %s -> %s", CleanURL(di->url), di->filename);
592 di->stream = FS_OpenRealFile(di->filename, "ab", false);
595 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
596 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
599 FS_Seek(di->stream, 0, SEEK_END);
600 di->startpos = FS_Tell(di->stream);
603 Con_Printf(", resuming from position %ld", (long) di->startpos);
608 Con_DPrintf("Downloading %s -> memory\n", CleanURL(di->url));
612 di->curle = qcurl_easy_init();
613 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
614 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
615 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
616 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
617 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
618 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
619 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
620 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
621 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
622 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
623 qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP);
624 if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK)
626 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");
627 //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0);
630 qcurl_multi_add_handle(curlm, di->curle);
633 if(numdownloads >= cl_curl_maxdownloads.integer)
644 this function MUST be called before using anything else in this file.
645 On Win32, this must be called AFTER WSAStartup has been done!
653 qcurl_global_init(CURL_GLOBAL_NOTHING);
654 curlm = qcurl_multi_init();
661 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
664 void Curl_ClearRequirements(void);
665 void Curl_Shutdown(void)
669 Curl_ClearRequirements();
679 Finds the internal information block for a download given by file name.
682 static downloadinfo *Curl_Find(const char *filename)
687 for(di = downloads; di; di = di->next)
688 if(!strcasecmp(di->filename, filename))
693 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
698 for(di = downloads; di; )
700 if(di->callback == callback && di->callback_data == cbdata)
702 di->callback = curl_quiet_callback; // do NOT call the callback
703 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
715 Starts a download of a given URL to the file name portion of this URL (or name
716 if given) in the "dlcache/" folder.
719 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)
733 // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server
734 p = strchr(URL, ':');
737 if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4))
739 char addressstring[128];
741 InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring));
742 q = strchr(addressstring, ':');
744 q = addressstring + strlen(addressstring);
747 dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3);
753 // Note: This extraction of the file name portion is NOT entirely correct.
755 // It does the following:
757 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
758 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
759 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
761 // However, I'd like to keep this "buggy" behavior so that PHP script
762 // authors can write download scripts without having to enable
763 // AcceptPathInfo on Apache. They just have to ensure that their script
764 // can be called with such a "fake" path name like
765 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
767 // By the way, such PHP scripts should either send the file or a
768 // "Location:" redirect; PHP code example:
770 // header("Location: http://www.example.com/");
772 // By the way, this will set User-Agent to something like
773 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
774 // dp://serverhost:serverport/ so you can filter on this; an example
775 // httpd log file line might be:
777 // 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"
780 name = CleanURL(URL);
784 p = strrchr(name, '/');
785 p = p ? (p+1) : name;
787 length = q ? (size_t)(q - p) : strlen(p);
788 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
790 name = fn; // make it point back
792 // already downloading the file?
794 downloadinfo *di = Curl_Find(fn);
797 Con_Printf("Can't download %s, already getting it from %s!\n", fn, CleanURL(di->url));
799 // however, if it was not for this map yet...
800 if(forthismap && !di->forthismap)
802 di->forthismap = true;
803 // this "fakes" a download attempt so the client will wait for
804 // the download to finish and then reconnect
805 ++numdownloads_added;
812 if(ispak && FS_FileExists(fn))
814 qboolean already_loaded;
815 if(FS_AddPack(fn, &already_loaded, true))
817 Con_DPrintf("%s already exists, not downloading!\n", fn);
819 Con_DPrintf("(pak was already loaded)\n");
824 ++numdownloads_added;
825 ++numdownloads_success;
833 qfile_t *f = FS_OpenRealFile(fn, "rb", false);
837 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
839 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
841 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
843 f = FS_OpenRealFile(fn, "wb", false);
857 // if we get here, we actually want to download... so first verify the
858 // URL scheme (so one can't read local files using file://)
859 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
861 Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
866 ++numdownloads_added;
867 di = (downloadinfo *) Z_Malloc(sizeof(*di));
868 strlcpy(di->filename, name, sizeof(di->filename));
869 strlcpy(di->url, URL, sizeof(di->url));
870 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
871 di->forthismap = forthismap;
876 di->ispak = (ispak && !buf);
877 di->maxspeed = maxspeed;
878 di->bytes_received = 0;
879 di->next = downloads;
885 di->buffersize = bufsize;
888 di->callback = curl_default_callback;
889 di->callback_data = di;
893 di->callback = callback;
894 di->callback_data = cbdata;
902 qboolean Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, qboolean ispak, qboolean forthismap)
904 return Curl_Begin(URL, maxspeed, name, ispak, forthismap, NULL, 0, NULL, NULL);
906 qboolean Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
908 return Curl_Begin(URL, maxspeed, NULL, false, false, buf, bufsize, callback, cbdata);
915 call this regularily as this will always download as much as possible without
926 if(!cl_curl_enabled.integer)
932 Curl_CheckCommandWhenDone();
937 if(realtime < curltime) // throttle
946 mc = qcurl_multi_perform(curlm, &remaining);
948 while(mc == CURLM_CALL_MULTI_PERFORM);
952 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
955 if(msg->msg == CURLMSG_DONE)
957 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
959 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
960 result = msg->data.result;
963 failed = CURL_DOWNLOAD_FAILED;
968 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
973 failed = CURL_DOWNLOAD_SERVERERROR;
974 result = (CURLcode) code;
979 Curl_EndDownload(di, failed, result);
984 CheckPendingDownloads();
986 // when will we curl the next time?
987 // we will wait a bit to ensure our download rate is kept.
988 // we now know that realtime >= curltime... so set up a new curltime
990 // use the slowest allowing download to derive the maxspeed... this CAN
991 // be done better, but maybe later
992 maxspeed = cl_curl_maxspeed.value;
993 for(di = downloads; di; di = di->next)
995 if(di->maxspeed < maxspeed || maxspeed <= 0)
996 maxspeed = di->maxspeed;
1000 unsigned long bytes = bytes_received; // maybe smoothen a bit?
1001 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
1002 bytes_received -= bytes;
1005 curltime = realtime;
1009 ====================
1012 Stops ALL downloads.
1013 ====================
1015 void Curl_CancelAll(void)
1022 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1023 // INVARIANT: downloads will point to the next download after that!
1028 ====================
1031 returns true iff there is a download running.
1032 ====================
1034 qboolean Curl_Running(void)
1039 return downloads != NULL;
1043 ====================
1044 Curl_GetDownloadAmount
1046 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
1047 for the given download.
1048 ====================
1050 static double Curl_GetDownloadAmount(downloadinfo *di)
1057 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1059 return (di->startpos + di->bytes_received) / (di->startpos + length);
1068 ====================
1069 Curl_GetDownloadSpeed
1071 returns the speed of the given download in bytes per second
1072 ====================
1074 static double Curl_GetDownloadSpeed(downloadinfo *di)
1081 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1089 ====================
1092 prints the download list
1093 ====================
1095 // TODO rewrite using Curl_GetDownloadInfo?
1096 static void Curl_Info_f(void)
1103 Con_Print("Currently running downloads:\n");
1104 for(di = downloads; di; di = di->next)
1106 double speed, percent;
1107 Con_Printf(" %s -> %s ", CleanURL(di->url), di->filename);
1108 percent = 100.0 * Curl_GetDownloadAmount(di);
1109 speed = Curl_GetDownloadSpeed(di);
1111 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1113 Con_Print("(queued)\n");
1118 Con_Print("No downloads running.\n");
1123 ====================
1126 implements the "curl" console command
1130 curl --cancel filename
1135 curl [--pak] [--forthismap] [--for filename filename...] url
1136 --pak: after downloading, load the package into the virtual file system
1137 --for filename...: only download of at least one of the named files is missing
1138 --forthismap: don't reconnect on failure
1140 curl --clear_autodownload
1141 clears the download success/failure counters
1143 curl --finish_autodownload
1144 if at least one download has been started, disconnect and drop to the menu
1145 once the last download completes successfully, reconnect to the current server
1146 ====================
1148 void Curl_Curl_f(void)
1150 double maxspeed = 0;
1153 qboolean pak = false;
1154 qboolean forthismap = false;
1156 const char *name = 0;
1160 Con_Print("libcurl DLL not found, this command is inactive.\n");
1164 if(!cl_curl_enabled.integer)
1166 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1172 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1176 url = Cmd_Argv(Cmd_Argc() - 1);
1179 for(i = 1; i != end; ++i)
1181 const char *a = Cmd_Argv(i);
1182 if(!strcmp(a, "--info"))
1187 else if(!strcmp(a, "--cancel"))
1189 if(i == end - 1) // last argument
1193 downloadinfo *di = Curl_Find(url);
1195 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1197 Con_Print("download not found\n");
1201 else if(!strcmp(a, "--pak"))
1205 else if(!strcmp(a, "--for")) // must be last option
1207 for(i = i + 1; i != end - 1; ++i)
1209 if(!FS_FileExists(Cmd_Argv(i)))
1210 goto needthefile; // why can't I have a "double break"?
1212 // if we get here, we have all the files...
1215 else if(!strcmp(a, "--forthismap"))
1219 else if(!strcmp(a, "--as"))
1227 else if(!strcmp(a, "--clear_autodownload"))
1229 // mark all running downloads as "not for this map", so if they
1230 // fail, it does not matter
1231 Curl_Clear_forthismap();
1234 else if(!strcmp(a, "--finish_autodownload"))
1236 if(numdownloads_added)
1238 char donecommand[256];
1241 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1243 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1244 Curl_CommandWhenDone(donecommand);
1248 Curl_CheckCommandWhenDone();
1251 Curl_Register_predownload();
1256 else if(!strncmp(a, "--maxspeed=", 11))
1258 maxspeed = atof(a + 11);
1262 Con_Printf("curl: invalid option %s\n", a);
1263 // but we ignore the option
1268 Curl_Begin_ToFile(url, maxspeed, name, pak, forthismap);
1272 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1274 Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1278 void Curl_CurlCat_f(void)
1281 const char *url = Cmd_Argv(1);
1282 buf = Z_Malloc(16384);
1283 Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1288 ====================
1291 loads the commands and cvars this library uses
1292 ====================
1294 void Curl_Init_Commands(void)
1296 Cvar_RegisterVariable (&cl_curl_enabled);
1297 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1298 Cvar_RegisterVariable (&cl_curl_maxspeed);
1299 Cvar_RegisterVariable (&sv_curl_defaulturl);
1300 Cvar_RegisterVariable (&sv_curl_serverpackages);
1301 Cvar_RegisterVariable (&sv_curl_maxspeed);
1302 Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1303 //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1307 ====================
1308 Curl_GetDownloadInfo
1310 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1311 The number of elements in the array is returned in int *nDownloads.
1312 const char **additional_info may be set to a string of additional user
1313 information, or to NULL if no such display shall occur. The returned
1314 array must be freed later using Z_Free.
1315 ====================
1317 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1321 Curl_downloadinfo_t *downinfo;
1322 static char addinfo[128];
1328 *additional_info = NULL;
1333 for(di = downloads; di; di = di->next)
1336 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1338 for(di = downloads; di; di = di->next)
1340 // do not show infobars for background downloads
1341 if(developer.integer <= 0)
1344 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1347 downinfo[i].progress = Curl_GetDownloadAmount(di);
1348 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1349 downinfo[i].queued = false;
1353 downinfo[i].queued = true;
1360 // TODO: can I clear command_when_done as soon as the first download fails?
1361 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1363 if(!strncmp(command_when_done, "connect ", 8))
1364 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1365 else if(!strcmp(command_when_done, "cl_begindownloads"))
1366 dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
1368 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1369 *additional_info = addinfo;
1372 *additional_info = NULL;
1381 ====================
1384 finds the URL where to find a given package.
1386 For this, it reads a file "curl_urls.txt" of the following format:
1389 revdm*.pk3 http://revdm/downloads/are/here/
1390 * http://any/other/stuff/is/here/
1392 The URLs should end in /. If not, downloads will still work, but the cached files
1393 can't be just put into the data directory with the same download configuration
1394 (you might want to do this if you want to tag downloaded files from your
1395 server, but you should not). "-" means "don't download".
1397 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1400 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1401 this file for obvious reasons.
1402 ====================
1404 static const char *Curl_FindPackURL(const char *filename)
1406 static char foundurl[1024];
1407 fs_offset_t filesize;
1408 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1411 // read lines of format "pattern url"
1413 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1414 qboolean eof = false;
1426 if(pattern && url && patternend)
1432 if(matchpattern(filename, pattern, true))
1434 strlcpy(foundurl, url, sizeof(foundurl));
1446 if(pattern && !patternend)
1448 else if(url && !urlend)
1454 else if(pattern && patternend && !url)
1463 return sv_curl_defaulturl.string;
1466 typedef struct requirement_s
1468 struct requirement_s *next;
1469 char filename[MAX_OSPATH];
1472 static requirement *requirements = NULL;
1476 ====================
1479 Adds the given file to the list of requirements.
1480 ====================
1482 void Curl_RequireFile(const char *filename)
1484 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1485 req->next = requirements;
1486 strlcpy(req->filename, filename, sizeof(req->filename));
1491 ====================
1492 Curl_ClearRequirements
1494 Clears the list of required files for playing on the current map.
1495 This should be called at every map change.
1496 ====================
1498 void Curl_ClearRequirements(void)
1503 requirement *req = requirements;
1504 requirements = requirements->next;
1507 p = sv_curl_serverpackages.string;
1508 Con_DPrintf("Require all of: %s\n", p);
1509 while(COM_ParseToken_Simple(&p, false, false))
1511 Con_DPrintf("Require: %s\n", com_token);
1512 Curl_RequireFile(com_token);
1517 ====================
1518 Curl_SendRequirements
1520 Makes the current host_clients download all files he needs.
1521 This is done by sending him the following console commands:
1523 curl --clear_autodownload
1524 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1525 curl --finish_autodownload
1526 ====================
1528 void Curl_SendRequirements(void)
1530 // for each requirement, find the pack name
1531 char sendbuffer[4096] = "";
1533 qboolean foundone = false;
1535 for(req = requirements; req; req = req->next)
1538 const char *thispack = FS_WhichPack(req->filename);
1539 const char *packurl;
1544 p = strrchr(thispack, '/');
1548 packurl = Curl_FindPackURL(thispack);
1550 if(packurl && *packurl && strcmp(packurl, "-"))
1553 strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
1555 strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
1556 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1557 if(sv_curl_maxspeed.value > 0)
1558 dpsnprintf(sendbuffer + strlen(sendbuffer), sizeof(sendbuffer) - strlen(sendbuffer), " --maxspeed=%.1f", sv_curl_maxspeed.value);
1559 strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
1560 strlcat(sendbuffer, req->filename, sizeof(sendbuffer));
1561 strlcat(sendbuffer, " ", sizeof(sendbuffer));
1562 strlcat(sendbuffer, packurl, sizeof(sendbuffer));
1563 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1564 strlcat(sendbuffer, "\n", sizeof(sendbuffer));
1571 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1573 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1574 Host_ClientCommands("%s", sendbuffer);
1576 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");