5 static cvar_t cl_curl_maxdownloads = {CVAR_SAVE, "cl_curl_maxdownloads","1", "maximum number of concurrent HTTP/FTP downloads"};
6 static cvar_t cl_curl_maxspeed = {CVAR_SAVE, "cl_curl_maxspeed","100", "maximum download speed (KiB/s)"};
7 static cvar_t sv_curl_defaulturl = {CVAR_SAVE, "sv_curl_defaulturl","", "default autodownload source URL"};
8 static cvar_t sv_curl_serverpackages = {CVAR_SAVE, "sv_curl_serverpackages","", "list of required files for the clients, separated by spaces"};
9 static cvar_t cl_curl_enabled = {CVAR_SAVE, "cl_curl_enabled","1", "whether client's download support is enabled"};
12 =================================================================
14 Minimal set of definitions from libcurl
16 WARNING: for a matter of simplicity, several pointer types are
17 casted to "void*", and most enumerated values are not included
19 =================================================================
22 typedef struct CURL_s CURL;
23 typedef struct CURLM_s CURLM;
31 CURLM_CALL_MULTI_PERFORM=-1, /* please call curl_multi_perform() soon */
35 #define CURL_GLOBAL_NOTHING 0
36 #define CURL_GLOBAL_SSL 1
37 #define CURL_GLOBAL_WIN32 2
38 #define CURLOPTTYPE_LONG 0
39 #define CURLOPTTYPE_OBJECTPOINT 10000
40 #define CURLOPTTYPE_FUNCTIONPOINT 20000
41 #define CURLOPTTYPE_OFF_T 30000
42 #define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number
45 CINIT(WRITEDATA, OBJECTPOINT, 1),
46 CINIT(URL, OBJECTPOINT, 2),
47 CINIT(ERRORBUFFER, OBJECTPOINT, 10),
48 CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11),
49 CINIT(REFERER, OBJECTPOINT, 16),
50 CINIT(USERAGENT, OBJECTPOINT, 18),
51 CINIT(RESUME_FROM, LONG, 21),
52 CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */
53 CINIT(PRIVATE, OBJECTPOINT, 103),
54 CINIT(LOW_SPEED_LIMIT, LONG , 19),
55 CINIT(LOW_SPEED_TIME, LONG, 20),
56 CINIT(PROTOCOLS, LONG, 181),
57 CINIT(REDIR_PROTOCOLS, LONG, 182),
60 #define CURLPROTO_HTTP (1<<0)
61 #define CURLPROTO_HTTPS (1<<1)
62 #define CURLPROTO_FTP (1<<2)
66 CURLINFO_HEADER_IN, /* 1 */
67 CURLINFO_HEADER_OUT, /* 2 */
68 CURLINFO_DATA_IN, /* 3 */
69 CURLINFO_DATA_OUT, /* 4 */
70 CURLINFO_SSL_DATA_IN, /* 5 */
71 CURLINFO_SSL_DATA_OUT, /* 6 */
75 #define CURLINFO_STRING 0x100000
76 #define CURLINFO_LONG 0x200000
77 #define CURLINFO_DOUBLE 0x300000
78 #define CURLINFO_SLIST 0x400000
79 #define CURLINFO_MASK 0x0fffff
80 #define CURLINFO_TYPEMASK 0xf00000
83 CURLINFO_NONE, /* first, never use this */
84 CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1,
85 CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2,
86 CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3,
87 CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4,
88 CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5,
89 CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6,
90 CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7,
91 CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8,
92 CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9,
93 CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10,
94 CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11,
95 CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12,
96 CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13,
97 CURLINFO_FILETIME = CURLINFO_LONG + 14,
98 CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15,
99 CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16,
100 CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17,
101 CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18,
102 CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19,
103 CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20,
104 CURLINFO_PRIVATE = CURLINFO_STRING + 21,
105 CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22,
106 CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23,
107 CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24,
108 CURLINFO_OS_ERRNO = CURLINFO_LONG + 25,
109 CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26,
110 CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27,
116 CURLMSG_NONE, /* first, not used */
117 CURLMSG_DONE, /* This easy handle has completed. 'result' contains
118 the CURLcode of the transfer */
124 CURLMSG msg; /* what this message means */
125 CURL *easy_handle; /* the handle it concerns */
128 void *whatever; /* message-specific data */
129 CURLcode result; /* return code for transfer */
135 static void (*qcurl_global_init) (long flags);
136 static void (*qcurl_global_cleanup) (void);
138 static CURL * (*qcurl_easy_init) (void);
139 static void (*qcurl_easy_cleanup) (CURL *handle);
140 static CURLcode (*qcurl_easy_setopt) (CURL *handle, CURLoption option, ...);
141 static CURLcode (*qcurl_easy_getinfo) (CURL *handle, CURLINFO info, ...);
142 static const char * (*qcurl_easy_strerror) (CURLcode);
144 static CURLM * (*qcurl_multi_init) (void);
145 static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles);
146 static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle);
147 static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle);
148 static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue);
149 static void (*qcurl_multi_cleanup) (CURLM *);
150 static const char * (*qcurl_multi_strerror) (CURLcode);
152 static dllfunction_t curlfuncs[] =
154 {"curl_global_init", (void **) &qcurl_global_init},
155 {"curl_global_cleanup", (void **) &qcurl_global_cleanup},
156 {"curl_easy_init", (void **) &qcurl_easy_init},
157 {"curl_easy_cleanup", (void **) &qcurl_easy_cleanup},
158 {"curl_easy_setopt", (void **) &qcurl_easy_setopt},
159 {"curl_easy_strerror", (void **) &qcurl_easy_strerror},
160 {"curl_easy_getinfo", (void **) &qcurl_easy_getinfo},
161 {"curl_multi_init", (void **) &qcurl_multi_init},
162 {"curl_multi_perform", (void **) &qcurl_multi_perform},
163 {"curl_multi_add_handle", (void **) &qcurl_multi_add_handle},
164 {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle},
165 {"curl_multi_info_read", (void **) &qcurl_multi_info_read},
166 {"curl_multi_cleanup", (void **) &qcurl_multi_cleanup},
167 {"curl_multi_strerror", (void **) &qcurl_multi_strerror},
171 // Handle for CURL DLL
172 static dllhandle_t curl_dll = NULL;
173 // will be checked at many places to find out if qcurl calls are allowed
175 typedef struct downloadinfo_s
177 char filename[MAX_OSPATH];
181 fs_offset_t startpos;
185 unsigned long bytes_received;
186 struct downloadinfo_s *next, *prev;
189 unsigned char *buffer;
191 curl_callback_t callback;
195 static downloadinfo *downloads = NULL;
196 static int numdownloads = 0;
198 static qboolean noclear = FALSE;
200 static int numdownloads_fail = 0;
201 static int numdownloads_success = 0;
202 static int numdownloads_added = 0;
203 static char command_when_done[256] = "";
204 static char command_when_error[256] = "";
210 Sets the command which is to be executed when the last download completes AND
211 all downloads since last server connect ended with a successful status.
212 Setting the command to NULL clears it.
215 void Curl_CommandWhenDone(const char *cmd)
220 strlcpy(command_when_done, cmd, sizeof(command_when_done));
222 *command_when_done = 0;
227 Do not use yet. Not complete.
228 Problem: what counts as an error?
231 void Curl_CommandWhenError(const char *cmd)
236 strlcpy(command_when_error, cmd, sizeof(command_when_error));
238 *command_when_error = 0;
243 Curl_Clear_forthismap
245 Clears the "will disconnect on failure" flags.
248 void Curl_Clear_forthismap(void)
253 for(di = downloads; di; di = di->next)
254 di->forthismap = false;
255 Curl_CommandWhenError(NULL);
256 Curl_CommandWhenDone(NULL);
257 numdownloads_fail = 0;
258 numdownloads_success = 0;
259 numdownloads_added = 0;
266 Returns true if a download needed for the current game is running.
269 qboolean Curl_Have_forthismap(void)
271 return numdownloads_added != 0;
274 void Curl_Register_predownload(void)
276 Curl_CommandWhenDone("cl_begindownloads");
277 Curl_CommandWhenError("cl_begindownloads");
282 Curl_CheckCommandWhenDone
284 Checks if a "done command" is to be executed.
285 All downloads finished, at least one success since connect, no single failure
286 -> execute the command.
288 static void Curl_CheckCommandWhenDone(void)
292 if(numdownloads_added && (numdownloads_success == numdownloads_added) && *command_when_done)
294 Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done);
296 Cbuf_AddText(command_when_done);
298 Curl_Clear_forthismap();
300 else if(numdownloads_added && numdownloads_fail && *command_when_error)
302 Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error);
304 Cbuf_AddText(command_when_error);
306 Curl_Clear_forthismap();
317 static qboolean CURL_OpenLibrary (void)
319 const char* dllnames [] =
324 #elif defined(MACOSX)
325 "libcurl.4.dylib", // Mac OS X Notyetreleased
326 "libcurl.3.dylib", // Mac OS X Tiger
327 "libcurl.2.dylib", // Mac OS X Panther
331 "libcurl.so", // FreeBSD
341 return Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs);
352 static void CURL_CloseLibrary (void)
354 Sys_UnloadLibrary (&curl_dll);
358 static CURLM *curlm = NULL;
359 static unsigned long bytes_received = 0; // used for bandwidth throttling
360 static double curltime = 0;
366 fwrite-compatible function that writes the data to a file. libcurl can call
370 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
372 fs_offset_t ret = -1;
373 size_t bytes = size * nmemb;
374 downloadinfo *di = (downloadinfo *) vdi;
378 if(di->bytes_received + bytes <= di->buffersize)
380 memcpy(di->buffer + di->bytes_received, data, bytes);
383 // otherwise: buffer overrun, ret stays -1
388 ret = FS_Write(di->stream, data, bytes);
391 bytes_received += bytes;
392 di->bytes_received += bytes;
394 return ret; // why not ret / nmemb?
399 CURL_DOWNLOAD_SUCCESS = 0,
400 CURL_DOWNLOAD_FAILED,
401 CURL_DOWNLOAD_ABORTED,
402 CURL_DOWNLOAD_SERVERERROR
406 static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
408 downloadinfo *di = (downloadinfo *) cbdata;
411 case CURLCBSTATUS_OK:
412 Con_Printf("Download of %s: OK\n", di->filename);
414 case CURLCBSTATUS_FAILED:
415 Con_Printf("Download of %s: FAILED\n", di->filename);
417 case CURLCBSTATUS_ABORTED:
418 Con_Printf("Download of %s: ABORTED\n", di->filename);
420 case CURLCBSTATUS_SERVERERROR:
421 Con_Printf("Download of %s: (unknown server error)\n", di->filename);
423 case CURLCBSTATUS_UNKNOWN:
424 Con_Printf("Download of %s: (unknown client error)\n", di->filename);
427 Con_Printf("Download of %s: %d\n", di->filename, status);
432 static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
434 if(developer.integer)
435 curl_default_callback(status, length_received, buffer, cbdata);
442 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
443 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
444 code from libcurl, or 0, if another error has occurred.
447 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);
448 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error)
455 case CURL_DOWNLOAD_SUCCESS:
457 di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
459 case CURL_DOWNLOAD_FAILED:
460 di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
462 case CURL_DOWNLOAD_ABORTED:
463 di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
465 case CURL_DOWNLOAD_SERVERERROR:
466 // reopen to enforce it to have zero bytes again
469 FS_Close(di->stream);
470 di->stream = FS_OpenRealFile(di->filename, "wb", false);
474 di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
478 di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
484 qcurl_multi_remove_handle(curlm, di->curle);
485 qcurl_easy_cleanup(di->curle);
488 if(!di->callback && ok && !di->bytes_received)
490 Con_Printf("ERROR: empty file\n");
495 FS_Close(di->stream);
499 ok = FS_AddPack(di->filename, NULL, true);
502 // pack loading failed?
504 // better clear the file again...
505 di->stream = FS_OpenRealFile(di->filename, "wb", false);
506 FS_Close(di->stream);
508 if(di->startpos && !di->callback)
510 // this was a resume?
511 // then try to redownload it without reporting the error
512 Curl_Begin(di->url, di->filename, di->ispak, di->forthismap, NULL, 0, NULL, NULL);
513 di->forthismap = false; // don't count the error
519 di->prev->next = di->next;
521 downloads = di->next;
523 di->next->prev = di->prev;
529 ++numdownloads_success;
538 CheckPendingDownloads
540 checks if there are free download slots to start new downloads in.
541 To not start too many downloads at once, only one download is added at a time,
542 up to a maximum number of cl_curl_maxdownloads are running.
545 static void CheckPendingDownloads(void)
549 if(numdownloads < cl_curl_maxdownloads.integer)
552 for(di = downloads; di; di = di->next)
558 Con_Printf("Downloading %s -> %s", di->url, di->filename);
560 di->stream = FS_OpenRealFile(di->filename, "ab", false);
563 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
564 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
567 FS_Seek(di->stream, 0, SEEK_END);
568 di->startpos = FS_Tell(di->stream);
571 Con_Printf(", resuming from position %ld", (long) di->startpos);
576 Con_DPrintf("Downloading %s -> memory\n", di->url);
580 di->curle = qcurl_easy_init();
581 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
582 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
583 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
584 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
585 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
586 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
587 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
588 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
589 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
590 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
591 qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP);
592 if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK)
594 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");
595 //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0);
598 qcurl_multi_add_handle(curlm, di->curle);
601 if(numdownloads >= cl_curl_maxdownloads.integer)
612 this function MUST be called before using anything else in this file.
613 On Win32, this must be called AFTER WSAStartup has been done!
621 qcurl_global_init(CURL_GLOBAL_NOTHING);
622 curlm = qcurl_multi_init();
629 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
632 void Curl_ClearRequirements(void);
633 void Curl_Shutdown(void)
637 Curl_ClearRequirements();
647 Finds the internal information block for a download given by file name.
650 static downloadinfo *Curl_Find(const char *filename)
655 for(di = downloads; di; di = di->next)
656 if(!strcasecmp(di->filename, filename))
661 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
666 for(di = downloads; di; )
668 if(di->callback == callback && di->callback_data == cbdata)
670 di->callback = curl_quiet_callback; // do NOT call the callback
671 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
683 Starts a download of a given URL to the file name portion of this URL (or name
684 if given) in the "dlcache/" folder.
687 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)
701 // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server
702 p = strchr(URL, ':');
705 if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4))
707 char addressstring[128];
709 InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring));
710 q = strchr(addressstring, ':');
712 q = addressstring + strlen(addressstring);
715 dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3);
721 // Note: This extraction of the file name portion is NOT entirely correct.
723 // It does the following:
725 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
726 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
727 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
729 // However, I'd like to keep this "buggy" behavior so that PHP script
730 // authors can write download scripts without having to enable
731 // AcceptPathInfo on Apache. They just have to ensure that their script
732 // can be called with such a "fake" path name like
733 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
735 // By the way, such PHP scripts should either send the file or a
736 // "Location:" redirect; PHP code example:
738 // header("Location: http://www.example.com/");
740 // By the way, this will set User-Agent to something like
741 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
742 // dp://serverhost:serverport/ so you can filter on this; an example
743 // httpd log file line might be:
745 // 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"
752 p = strrchr(name, '/');
753 p = p ? (p+1) : name;
755 length = q ? (size_t)(q - p) : strlen(p);
756 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
758 name = fn; // make it point back
760 // already downloading the file?
762 downloadinfo *di = Curl_Find(fn);
765 Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
767 // however, if it was not for this map yet...
768 if(forthismap && !di->forthismap)
770 di->forthismap = true;
771 // this "fakes" a download attempt so the client will wait for
772 // the download to finish and then reconnect
773 ++numdownloads_added;
780 if(ispak && FS_FileExists(fn))
782 qboolean already_loaded;
783 if(FS_AddPack(fn, &already_loaded, true))
785 Con_DPrintf("%s already exists, not downloading!\n", fn);
787 Con_DPrintf("(pak was already loaded)\n");
792 ++numdownloads_added;
793 ++numdownloads_success;
801 qfile_t *f = FS_OpenRealFile(fn, "rb", false);
805 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
807 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
809 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
811 f = FS_OpenRealFile(fn, "wb", false);
825 // if we get here, we actually want to download... so first verify the
826 // URL scheme (so one can't read local files using file://)
827 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
829 Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
834 ++numdownloads_added;
835 di = (downloadinfo *) Z_Malloc(sizeof(*di));
836 strlcpy(di->filename, name, sizeof(di->filename));
837 strlcpy(di->url, URL, sizeof(di->url));
838 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
839 di->forthismap = forthismap;
844 di->ispak = (ispak && !buf);
845 di->bytes_received = 0;
846 di->next = downloads;
852 di->buffersize = bufsize;
855 di->callback = curl_default_callback;
856 di->callback_data = di;
860 di->callback = callback;
861 di->callback_data = cbdata;
869 qboolean Curl_Begin_ToFile(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
871 return Curl_Begin(URL, name, ispak, forthismap, NULL, 0, NULL, NULL);
873 qboolean Curl_Begin_ToMemory(const char *URL, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
875 return Curl_Begin(URL, NULL, false, false, buf, bufsize, callback, cbdata);
882 call this regularily as this will always download as much as possible without
890 if(!cl_curl_enabled.integer)
896 Curl_CheckCommandWhenDone();
901 if(realtime < curltime) // throttle
910 mc = qcurl_multi_perform(curlm, &remaining);
912 while(mc == CURLM_CALL_MULTI_PERFORM);
916 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
919 if(msg->msg == CURLMSG_DONE)
922 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
924 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
925 result = msg->data.result;
928 failed = CURL_DOWNLOAD_FAILED;
933 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
938 failed = CURL_DOWNLOAD_SERVERERROR;
939 result = (CURLcode) code;
944 Curl_EndDownload(di, failed, result);
949 CheckPendingDownloads();
951 // when will we curl the next time?
952 // we will wait a bit to ensure our download rate is kept.
953 // we now know that realtime >= curltime... so set up a new curltime
954 if(cl_curl_maxspeed.value > 0)
956 unsigned long bytes = bytes_received; // maybe smoothen a bit?
957 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
958 bytes_received -= bytes;
971 void Curl_CancelAll(void)
978 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
979 // INVARIANT: downloads will point to the next download after that!
987 returns true iff there is a download running.
990 qboolean Curl_Running(void)
995 return downloads != NULL;
1000 Curl_GetDownloadAmount
1002 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
1003 for the given download.
1004 ====================
1006 static double Curl_GetDownloadAmount(downloadinfo *di)
1013 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1015 return (di->startpos + di->bytes_received) / (di->startpos + length);
1024 ====================
1025 Curl_GetDownloadSpeed
1027 returns the speed of the given download in bytes per second
1028 ====================
1030 static double Curl_GetDownloadSpeed(downloadinfo *di)
1037 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1045 ====================
1048 prints the download list
1049 ====================
1051 // TODO rewrite using Curl_GetDownloadInfo?
1052 static void Curl_Info_f(void)
1059 Con_Print("Currently running downloads:\n");
1060 for(di = downloads; di; di = di->next)
1062 double speed, percent;
1063 Con_Printf(" %s -> %s ", di->url, di->filename);
1064 percent = 100.0 * Curl_GetDownloadAmount(di);
1065 speed = Curl_GetDownloadSpeed(di);
1067 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1069 Con_Print("(queued)\n");
1074 Con_Print("No downloads running.\n");
1079 ====================
1082 implements the "curl" console command
1086 curl --cancel filename
1091 curl [--pak] [--forthismap] [--for filename filename...] url
1092 --pak: after downloading, load the package into the virtual file system
1093 --for filename...: only download of at least one of the named files is missing
1094 --forthismap: don't reconnect on failure
1096 curl --clear_autodownload
1097 clears the download success/failure counters
1099 curl --finish_autodownload
1100 if at least one download has been started, disconnect and drop to the menu
1101 once the last download completes successfully, reconnect to the current server
1102 ====================
1104 void Curl_Curl_f(void)
1108 qboolean pak = false;
1109 qboolean forthismap = false;
1111 const char *name = 0;
1115 Con_Print("libcurl DLL not found, this command is inactive.\n");
1119 if(!cl_curl_enabled.integer)
1121 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1125 for(i = 0; i != Cmd_Argc(); ++i)
1126 Con_DPrintf("%s ", Cmd_Argv(i));
1131 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1135 url = Cmd_Argv(Cmd_Argc() - 1);
1138 for(i = 1; i != end; ++i)
1140 const char *a = Cmd_Argv(i);
1141 if(!strcmp(a, "--info"))
1146 else if(!strcmp(a, "--cancel"))
1148 if(i == end - 1) // last argument
1152 downloadinfo *di = Curl_Find(url);
1154 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1156 Con_Print("download not found\n");
1160 else if(!strcmp(a, "--pak"))
1164 else if(!strcmp(a, "--for"))
1166 for(i = i + 1; i != end - 1; ++i)
1168 if(!FS_FileExists(Cmd_Argv(i)))
1169 goto needthefile; // why can't I have a "double break"?
1171 // if we get here, we have all the files...
1174 else if(!strcmp(a, "--forthismap"))
1178 else if(!strcmp(a, "--as"))
1186 else if(!strcmp(a, "--clear_autodownload"))
1188 // mark all running downloads as "not for this map", so if they
1189 // fail, it does not matter
1190 Curl_Clear_forthismap();
1193 else if(!strcmp(a, "--finish_autodownload"))
1195 if(numdownloads_added)
1197 char donecommand[256];
1200 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1202 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1203 Curl_CommandWhenDone(donecommand);
1207 Curl_CheckCommandWhenDone();
1210 Curl_Register_predownload();
1217 Con_Printf("invalid option %s\n", a);
1223 Curl_Begin_ToFile(url, name, pak, forthismap);
1227 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1229 Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1233 void Curl_CurlCat_f(void)
1236 const char *url = Cmd_Argv(1);
1237 buf = Z_Malloc(16384);
1238 Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1243 ====================
1246 loads the commands and cvars this library uses
1247 ====================
1249 void Curl_Init_Commands(void)
1251 Cvar_RegisterVariable (&cl_curl_enabled);
1252 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1253 Cvar_RegisterVariable (&cl_curl_maxspeed);
1254 Cvar_RegisterVariable (&sv_curl_defaulturl);
1255 Cvar_RegisterVariable (&sv_curl_serverpackages);
1256 Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1257 //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1261 ====================
1262 Curl_GetDownloadInfo
1264 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1265 The number of elements in the array is returned in int *nDownloads.
1266 const char **additional_info may be set to a string of additional user
1267 information, or to NULL if no such display shall occur. The returned
1268 array must be freed later using Z_Free.
1269 ====================
1271 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1275 Curl_downloadinfo_t *downinfo;
1276 static char addinfo[128];
1282 *additional_info = NULL;
1287 for(di = downloads; di; di = di->next)
1290 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1292 for(di = downloads; di; di = di->next)
1294 // do not show infobars for background downloads
1295 if(!developer.integer)
1298 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1301 downinfo[i].progress = Curl_GetDownloadAmount(di);
1302 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1303 downinfo[i].queued = false;
1307 downinfo[i].queued = true;
1314 // TODO: can I clear command_when_done as soon as the first download fails?
1315 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1317 if(!strncmp(command_when_done, "connect ", 8))
1318 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1319 else if(!strcmp(command_when_done, "cl_begindownloads"))
1320 dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
1322 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1323 *additional_info = addinfo;
1326 *additional_info = NULL;
1335 ====================
1338 finds the URL where to find a given package.
1340 For this, it reads a file "curl_urls.txt" of the following format:
1343 revdm*.pk3 http://revdm/downloads/are/here/
1344 * http://any/other/stuff/is/here/
1346 The URLs should end in /. If not, downloads will still work, but the cached files
1347 can't be just put into the data directory with the same download configuration
1348 (you might want to do this if you want to tag downloaded files from your
1349 server, but you should not). "-" means "don't download".
1351 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1354 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1355 this file for obvious reasons.
1356 ====================
1358 static const char *Curl_FindPackURL(const char *filename)
1360 static char foundurl[1024];
1361 fs_offset_t filesize;
1362 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1365 // read lines of format "pattern url"
1367 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1368 qboolean eof = false;
1380 if(pattern && url && patternend)
1386 if(matchpattern(filename, pattern, true))
1388 strlcpy(foundurl, url, sizeof(foundurl));
1400 if(pattern && !patternend)
1402 else if(url && !urlend)
1408 else if(pattern && patternend && !url)
1417 return sv_curl_defaulturl.string;
1420 typedef struct requirement_s
1422 struct requirement_s *next;
1423 char filename[MAX_OSPATH];
1426 static requirement *requirements = NULL;
1430 ====================
1433 Adds the given file to the list of requirements.
1434 ====================
1436 void Curl_RequireFile(const char *filename)
1438 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1439 req->next = requirements;
1440 strlcpy(req->filename, filename, sizeof(req->filename));
1445 ====================
1446 Curl_ClearRequirements
1448 Clears the list of required files for playing on the current map.
1449 This should be called at every map change.
1450 ====================
1452 void Curl_ClearRequirements(void)
1457 requirement *req = requirements;
1458 requirements = requirements->next;
1461 p = sv_curl_serverpackages.string;
1462 Con_DPrintf("Require all of: %s\n", p);
1463 while(COM_ParseToken_Simple(&p, false, false))
1465 Con_DPrintf("Require: %s\n", com_token);
1466 Curl_RequireFile(com_token);
1471 ====================
1472 Curl_SendRequirements
1474 Makes the current host_clients download all files he needs.
1475 This is done by sending him the following console commands:
1477 curl --clear_autodownload
1478 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1479 curl --finish_autodownload
1480 ====================
1482 void Curl_SendRequirements(void)
1484 // for each requirement, find the pack name
1485 char sendbuffer[4096] = "";
1487 qboolean foundone = false;
1489 for(req = requirements; req; req = req->next)
1492 const char *thispack = FS_WhichPack(req->filename);
1493 const char *packurl;
1498 p = strrchr(thispack, '/');
1502 packurl = Curl_FindPackURL(thispack);
1504 if(packurl && *packurl && strcmp(packurl, "-"))
1507 strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
1509 strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
1510 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1511 strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
1512 strlcat(sendbuffer, req->filename, sizeof(sendbuffer));
1513 strlcat(sendbuffer, " ", sizeof(sendbuffer));
1514 strlcat(sendbuffer, packurl, sizeof(sendbuffer));
1515 strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1516 strlcat(sendbuffer, "\n", sizeof(sendbuffer));
1523 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1525 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1526 Host_ClientCommands("%s", sendbuffer);
1528 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");