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 [] =
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_Printf("Download of %s: OK\n", di->filename);
416 case CURLCBSTATUS_FAILED:
417 Con_Printf("Download of %s: FAILED\n", di->filename);
419 case CURLCBSTATUS_ABORTED:
420 Con_Printf("Download of %s: ABORTED\n", di->filename);
422 case CURLCBSTATUS_SERVERERROR:
423 Con_Printf("Download of %s: (unknown server error)\n", di->filename);
425 case CURLCBSTATUS_UNKNOWN:
426 Con_Printf("Download of %s: (unknown client error)\n", di->filename);
429 Con_Printf("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 if(developer.integer)
437 curl_default_callback(status, length_received, buffer, cbdata);
444 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
445 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
446 code from libcurl, or 0, if another error has occurred.
449 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);
450 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error)
457 case CURL_DOWNLOAD_SUCCESS:
459 di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
461 case CURL_DOWNLOAD_FAILED:
462 di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
464 case CURL_DOWNLOAD_ABORTED:
465 di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
467 case CURL_DOWNLOAD_SERVERERROR:
468 // reopen to enforce it to have zero bytes again
471 FS_Close(di->stream);
472 di->stream = FS_OpenRealFile(di->filename, "wb", false);
476 di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
480 di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
486 qcurl_multi_remove_handle(curlm, di->curle);
487 qcurl_easy_cleanup(di->curle);
490 if(!di->callback && ok && !di->bytes_received)
492 Con_Printf("ERROR: empty file\n");
497 FS_Close(di->stream);
501 ok = FS_AddPack(di->filename, NULL, true);
504 // pack loading failed?
506 // better clear the file again...
507 di->stream = FS_OpenRealFile(di->filename, "wb", false);
508 FS_Close(di->stream);
510 if(di->startpos && !di->callback)
512 // this was a resume?
513 // then try to redownload it without reporting the error
514 Curl_Begin(di->url, di->filename, di->ispak, di->forthismap, NULL, 0, NULL, NULL);
515 di->forthismap = false; // don't count the error
521 di->prev->next = di->next;
523 downloads = di->next;
525 di->next->prev = di->prev;
531 ++numdownloads_success;
540 CheckPendingDownloads
542 checks if there are free download slots to start new downloads in.
543 To not start too many downloads at once, only one download is added at a time,
544 up to a maximum number of cl_curl_maxdownloads are running.
547 static void CheckPendingDownloads(void)
551 if(numdownloads < cl_curl_maxdownloads.integer)
554 for(di = downloads; di; di = di->next)
560 Con_Printf("Downloading %s -> %s", di->url, di->filename);
562 di->stream = FS_OpenRealFile(di->filename, "ab", false);
565 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
566 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
569 FS_Seek(di->stream, 0, SEEK_END);
570 di->startpos = FS_Tell(di->stream);
573 Con_Printf(", resuming from position %ld", (long) di->startpos);
578 Con_DPrintf("Downloading %s -> memory\n", di->url);
582 di->curle = qcurl_easy_init();
583 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
584 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
585 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
586 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
587 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
588 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
589 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
590 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
591 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
592 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
593 qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP);
594 if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK)
596 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");
597 //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0);
600 qcurl_multi_add_handle(curlm, di->curle);
603 if(numdownloads >= cl_curl_maxdownloads.integer)
614 this function MUST be called before using anything else in this file.
615 On Win32, this must be called AFTER WSAStartup has been done!
623 qcurl_global_init(CURL_GLOBAL_NOTHING);
624 curlm = qcurl_multi_init();
631 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
634 void Curl_ClearRequirements(void);
635 void Curl_Shutdown(void)
639 Curl_ClearRequirements();
649 Finds the internal information block for a download given by file name.
652 static downloadinfo *Curl_Find(const char *filename)
657 for(di = downloads; di; di = di->next)
658 if(!strcasecmp(di->filename, filename))
663 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
668 for(di = downloads; di; )
670 if(di->callback == callback && di->callback_data == cbdata)
672 di->callback = curl_quiet_callback; // do NOT call the callback
673 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
685 Starts a download of a given URL to the file name portion of this URL (or name
686 if given) in the "dlcache/" folder.
689 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)
703 // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server
704 p = strchr(URL, ':');
707 if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4))
709 char addressstring[128];
711 InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring));
712 q = strchr(addressstring, ':');
714 q = addressstring + strlen(addressstring);
717 dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3);
723 // Note: This extraction of the file name portion is NOT entirely correct.
725 // It does the following:
727 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
728 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
729 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
731 // However, I'd like to keep this "buggy" behavior so that PHP script
732 // authors can write download scripts without having to enable
733 // AcceptPathInfo on Apache. They just have to ensure that their script
734 // can be called with such a "fake" path name like
735 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
737 // By the way, such PHP scripts should either send the file or a
738 // "Location:" redirect; PHP code example:
740 // header("Location: http://www.example.com/");
742 // By the way, this will set User-Agent to something like
743 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
744 // dp://serverhost:serverport/ so you can filter on this; an example
745 // httpd log file line might be:
747 // 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"
754 p = strrchr(name, '/');
755 p = p ? (p+1) : name;
757 length = q ? (size_t)(q - p) : strlen(p);
758 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
760 name = fn; // make it point back
762 // already downloading the file?
764 downloadinfo *di = Curl_Find(fn);
767 Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
769 // however, if it was not for this map yet...
770 if(forthismap && !di->forthismap)
772 di->forthismap = true;
773 // this "fakes" a download attempt so the client will wait for
774 // the download to finish and then reconnect
775 ++numdownloads_added;
782 if(ispak && FS_FileExists(fn))
784 qboolean already_loaded;
785 if(FS_AddPack(fn, &already_loaded, true))
787 Con_DPrintf("%s already exists, not downloading!\n", fn);
789 Con_DPrintf("(pak was already loaded)\n");
794 ++numdownloads_added;
795 ++numdownloads_success;
803 qfile_t *f = FS_OpenRealFile(fn, "rb", false);
807 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
809 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
811 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
813 f = FS_OpenRealFile(fn, "wb", false);
827 // if we get here, we actually want to download... so first verify the
828 // URL scheme (so one can't read local files using file://)
829 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
831 Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
836 ++numdownloads_added;
837 di = (downloadinfo *) Z_Malloc(sizeof(*di));
838 strlcpy(di->filename, name, sizeof(di->filename));
839 strlcpy(di->url, URL, sizeof(di->url));
840 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
841 di->forthismap = forthismap;
846 di->ispak = (ispak && !buf);
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, const char *name, qboolean ispak, qboolean forthismap)
873 return Curl_Begin(URL, name, ispak, forthismap, NULL, 0, NULL, NULL);
875 qboolean Curl_Begin_ToMemory(const char *URL, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
877 return Curl_Begin(URL, NULL, false, false, buf, bufsize, callback, cbdata);
884 call this regularily as this will always download as much as possible without
892 if(!cl_curl_enabled.integer)
898 Curl_CheckCommandWhenDone();
903 if(realtime < curltime) // throttle
912 mc = qcurl_multi_perform(curlm, &remaining);
914 while(mc == CURLM_CALL_MULTI_PERFORM);
918 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
921 if(msg->msg == CURLMSG_DONE)
924 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
926 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
927 result = msg->data.result;
930 failed = CURL_DOWNLOAD_FAILED;
935 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
940 failed = CURL_DOWNLOAD_SERVERERROR;
941 result = (CURLcode) code;
946 Curl_EndDownload(di, failed, result);
951 CheckPendingDownloads();
953 // when will we curl the next time?
954 // we will wait a bit to ensure our download rate is kept.
955 // we now know that realtime >= curltime... so set up a new curltime
956 if(cl_curl_maxspeed.value > 0)
958 unsigned long bytes = bytes_received; // maybe smoothen a bit?
959 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
960 bytes_received -= bytes;
973 void Curl_CancelAll(void)
980 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
981 // INVARIANT: downloads will point to the next download after that!
989 returns true iff there is a download running.
992 qboolean Curl_Running(void)
997 return downloads != NULL;
1001 ====================
1002 Curl_GetDownloadAmount
1004 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
1005 for the given download.
1006 ====================
1008 static double Curl_GetDownloadAmount(downloadinfo *di)
1015 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1017 return (di->startpos + di->bytes_received) / (di->startpos + length);
1026 ====================
1027 Curl_GetDownloadSpeed
1029 returns the speed of the given download in bytes per second
1030 ====================
1032 static double Curl_GetDownloadSpeed(downloadinfo *di)
1039 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1047 ====================
1050 prints the download list
1051 ====================
1053 // TODO rewrite using Curl_GetDownloadInfo?
1054 static void Curl_Info_f(void)
1061 Con_Print("Currently running downloads:\n");
1062 for(di = downloads; di; di = di->next)
1064 double speed, percent;
1065 Con_Printf(" %s -> %s ", di->url, di->filename);
1066 percent = 100.0 * Curl_GetDownloadAmount(di);
1067 speed = Curl_GetDownloadSpeed(di);
1069 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1071 Con_Print("(queued)\n");
1076 Con_Print("No downloads running.\n");
1081 ====================
1084 implements the "curl" console command
1088 curl --cancel filename
1093 curl [--pak] [--forthismap] [--for filename filename...] url
1094 --pak: after downloading, load the package into the virtual file system
1095 --for filename...: only download of at least one of the named files is missing
1096 --forthismap: don't reconnect on failure
1098 curl --clear_autodownload
1099 clears the download success/failure counters
1101 curl --finish_autodownload
1102 if at least one download has been started, disconnect and drop to the menu
1103 once the last download completes successfully, reconnect to the current server
1104 ====================
1106 void Curl_Curl_f(void)
1110 qboolean pak = false;
1111 qboolean forthismap = false;
1113 const char *name = 0;
1117 Con_Print("libcurl DLL not found, this command is inactive.\n");
1121 if(!cl_curl_enabled.integer)
1123 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1127 for(i = 0; i != Cmd_Argc(); ++i)
1128 Con_DPrintf("%s ", Cmd_Argv(i));
1133 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1137 url = Cmd_Argv(Cmd_Argc() - 1);
1140 for(i = 1; i != end; ++i)
1142 const char *a = Cmd_Argv(i);
1143 if(!strcmp(a, "--info"))
1148 else if(!strcmp(a, "--cancel"))
1150 if(i == end - 1) // last argument
1154 downloadinfo *di = Curl_Find(url);
1156 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1158 Con_Print("download not found\n");
1162 else if(!strcmp(a, "--pak"))
1166 else if(!strcmp(a, "--for"))
1168 for(i = i + 1; i != end - 1; ++i)
1170 if(!FS_FileExists(Cmd_Argv(i)))
1171 goto needthefile; // why can't I have a "double break"?
1173 // if we get here, we have all the files...
1176 else if(!strcmp(a, "--forthismap"))
1180 else if(!strcmp(a, "--as"))
1188 else if(!strcmp(a, "--clear_autodownload"))
1190 // mark all running downloads as "not for this map", so if they
1191 // fail, it does not matter
1192 Curl_Clear_forthismap();
1195 else if(!strcmp(a, "--finish_autodownload"))
1197 if(numdownloads_added)
1199 char donecommand[256];
1202 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1204 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1205 Curl_CommandWhenDone(donecommand);
1209 Curl_CheckCommandWhenDone();
1212 Curl_Register_predownload();
1219 Con_Printf("invalid option %s\n", a);
1225 Curl_Begin_ToFile(url, name, pak, forthismap);
1229 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1231 Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1235 void Curl_CurlCat_f(void)
1238 const char *url = Cmd_Argv(1);
1239 buf = Z_Malloc(16384);
1240 Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1245 ====================
1248 loads the commands and cvars this library uses
1249 ====================
1251 void Curl_Init_Commands(void)
1253 Cvar_RegisterVariable (&cl_curl_enabled);
1254 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1255 Cvar_RegisterVariable (&cl_curl_maxspeed);
1256 Cvar_RegisterVariable (&sv_curl_defaulturl);
1257 Cvar_RegisterVariable (&sv_curl_serverpackages);
1258 Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1259 //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1263 ====================
1264 Curl_GetDownloadInfo
1266 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1267 The number of elements in the array is returned in int *nDownloads.
1268 const char **additional_info may be set to a string of additional user
1269 information, or to NULL if no such display shall occur. The returned
1270 array must be freed later using Z_Free.
1271 ====================
1273 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1277 Curl_downloadinfo_t *downinfo;
1278 static char addinfo[128];
1284 *additional_info = NULL;
1289 for(di = downloads; di; di = di->next)
1292 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1294 for(di = downloads; di; di = di->next)
1296 // do not show infobars for background downloads
1297 if(!developer.integer)
1300 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1303 downinfo[i].progress = Curl_GetDownloadAmount(di);
1304 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1305 downinfo[i].queued = false;
1309 downinfo[i].queued = true;
1316 // TODO: can I clear command_when_done as soon as the first download fails?
1317 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1319 if(!strncmp(command_when_done, "connect ", 8))
1320 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1321 else if(!strcmp(command_when_done, "cl_begindownloads"))
1322 dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
1324 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1325 *additional_info = addinfo;
1328 *additional_info = NULL;
1337 ====================
1340 finds the URL where to find a given package.
1342 For this, it reads a file "curl_urls.txt" of the following format:
1345 revdm*.pk3 http://revdm/downloads/are/here/
1346 * http://any/other/stuff/is/here/
1348 The URLs should end in /. If not, downloads will still work, but the cached files
1349 can't be just put into the data directory with the same download configuration
1350 (you might want to do this if you want to tag downloaded files from your
1351 server, but you should not). "-" means "don't download".
1353 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1356 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1357 this file for obvious reasons.
1358 ====================
1360 static const char *Curl_FindPackURL(const char *filename)
1362 static char foundurl[1024];
1363 fs_offset_t filesize;
1364 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1367 // read lines of format "pattern url"
1369 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1370 qboolean eof = false;
1382 if(pattern && url && patternend)
1388 if(matchpattern(filename, pattern, true))
1390 strlcpy(foundurl, url, sizeof(foundurl));
1402 if(pattern && !patternend)
1404 else if(url && !urlend)
1410 else if(pattern && patternend && !url)
1419 return sv_curl_defaulturl.string;
1422 typedef struct requirement_s
1424 struct requirement_s *next;
1425 char filename[MAX_OSPATH];
1428 static requirement *requirements = NULL;
1432 ====================
1435 Adds the given file to the list of requirements.
1436 ====================
1438 void Curl_RequireFile(const char *filename)
1440 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1441 req->next = requirements;
1442 strlcpy(req->filename, filename, sizeof(req->filename));
1447 ====================
1448 Curl_ClearRequirements
1450 Clears the list of required files for playing on the current map.
1451 This should be called at every map change.
1452 ====================
1454 void Curl_ClearRequirements(void)
1459 requirement *req = requirements;
1460 requirements = requirements->next;
1463 p = sv_curl_serverpackages.string;
1464 Con_DPrintf("Require all of: %s\n", p);
1465 while(COM_ParseToken_Simple(&p, false, false))
1467 Con_DPrintf("Require: %s\n", com_token);
1468 Curl_RequireFile(com_token);
1473 ====================
1474 Curl_SendRequirements
1476 Makes the current host_clients download all files he needs.
1477 This is done by sending him the following console commands:
1479 curl --clear_autodownload
1480 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1481 curl --finish_autodownload
1482 ====================
1484 void Curl_SendRequirements(void)
1486 // for each requirement, find the pack name
1487 char sendbuffer[4096] = "";
1489 qboolean foundone = false;
1491 for(req = requirements; req; req = req->next)
1494 const char *thispack = FS_WhichPack(req->filename);
1495 const char *packurl;
1500 p = strrchr(thispack, '/');
1504 packurl = Curl_FindPackURL(thispack);
1506 if(packurl && *packurl && strcmp(packurl, "-"))
1509 strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
1511 strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
1512 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1513 strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
1514 strlcat(sendbuffer, req->filename, sizeof(sendbuffer));
1515 strlcat(sendbuffer, " ", sizeof(sendbuffer));
1516 strlcat(sendbuffer, packurl, sizeof(sendbuffer));
1517 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1518 strlcat(sendbuffer, "\n", sizeof(sendbuffer));
1525 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1527 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1528 Host_ClientCommands("%s", sendbuffer);
1530 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");