10 static cvar_t cl_curl_maxdownloads = {CF_CLIENT | CF_ARCHIVE, "cl_curl_maxdownloads","1", "maximum number of concurrent HTTP/FTP downloads"};
11 static cvar_t cl_curl_maxspeed = {CF_CLIENT | CF_ARCHIVE, "cl_curl_maxspeed","300", "maximum download speed (KiB/s)"};
12 static cvar_t sv_curl_defaulturl = {CF_SERVER | CF_ARCHIVE, "sv_curl_defaulturl","", "default autodownload source URL"};
13 static cvar_t sv_curl_serverpackages = {CF_SERVER | CF_ARCHIVE, "sv_curl_serverpackages","", "list of required files for the clients, separated by spaces"};
14 static cvar_t sv_curl_maxspeed = {CF_SERVER | CF_ARCHIVE, "sv_curl_maxspeed","0", "maximum download speed for clients downloading from sv_curl_defaulturl (KiB/s)"};
15 static cvar_t cl_curl_enabled = {CF_CLIENT | CF_ARCHIVE, "cl_curl_enabled","1", "whether client's download support is enabled"};
16 static cvar_t cl_curl_useragent = {CF_CLIENT, "cl_curl_useragent","1", "send the User-Agent string (note: turning this off may break stuff)"};
17 static cvar_t cl_curl_useragent_append = {CF_CLIENT, "cl_curl_useragent_append","", "a string to append to the User-Agent string (useful for name and version number of your mod)"};
18 static cvar_t developer_curl = {CF_CLIENT | CF_SERVER, "developer_curl","0", "whether verbose curl output should be printed to stderr"};
21 =================================================================
23 Minimal set of definitions from libcurl
25 WARNING: for a matter of simplicity, several pointer types are
26 casted to "void*", and most enumerated values are not included
28 =================================================================
31 typedef struct CURL_s CURL;
32 typedef struct CURLM_s CURLM;
33 typedef struct curl_slist curl_slist;
41 CURLM_CALL_MULTI_PERFORM=-1, /* please call curl_multi_perform() soon */
45 #define CURL_GLOBAL_NOTHING 0
46 #define CURL_GLOBAL_SSL 1
47 #define CURL_GLOBAL_WIN32 2
48 #define CURLOPTTYPE_LONG 0
49 #define CURLOPTTYPE_OBJECTPOINT 10000
50 #define CURLOPTTYPE_FUNCTIONPOINT 20000
51 #define CURLOPTTYPE_OFF_T 30000
52 #define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number
55 CINIT(WRITEDATA, OBJECTPOINT, 1),
56 CINIT(URL, OBJECTPOINT, 2),
57 CINIT(ERRORBUFFER, OBJECTPOINT, 10),
58 CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11),
59 CINIT(POSTFIELDS, OBJECTPOINT, 15),
60 CINIT(REFERER, OBJECTPOINT, 16),
61 CINIT(USERAGENT, OBJECTPOINT, 18),
62 CINIT(LOW_SPEED_LIMIT, LONG , 19),
63 CINIT(LOW_SPEED_TIME, LONG, 20),
64 CINIT(RESUME_FROM, LONG, 21),
65 CINIT(HTTPHEADER, OBJECTPOINT, 23),
66 CINIT(VERBOSE, LONG, 41),
67 CINIT(POST, LONG, 47), /* HTTP POST method */
68 CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */
69 CINIT(POSTFIELDSIZE, LONG, 60),
70 CINIT(PRIVATE, OBJECTPOINT, 103),
71 CINIT(PROTOCOLS, LONG, 181),
72 CINIT(REDIR_PROTOCOLS, LONG, 182)
75 #define CURLPROTO_HTTP (1<<0)
76 #define CURLPROTO_HTTPS (1<<1)
77 #define CURLPROTO_FTP (1<<2)
81 CURLINFO_HEADER_IN, /* 1 */
82 CURLINFO_HEADER_OUT, /* 2 */
83 CURLINFO_DATA_IN, /* 3 */
84 CURLINFO_DATA_OUT, /* 4 */
85 CURLINFO_SSL_DATA_IN, /* 5 */
86 CURLINFO_SSL_DATA_OUT, /* 6 */
90 #define CURLINFO_STRING 0x100000
91 #define CURLINFO_LONG 0x200000
92 #define CURLINFO_DOUBLE 0x300000
93 #define CURLINFO_SLIST 0x400000
94 #define CURLINFO_MASK 0x0fffff
95 #define CURLINFO_TYPEMASK 0xf00000
98 CURLINFO_NONE, /* first, never use this */
99 CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1,
100 CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2,
101 CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3,
102 CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4,
103 CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5,
104 CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6,
105 CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7,
106 CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8,
107 CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9,
108 CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10,
109 CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11,
110 CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12,
111 CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13,
112 CURLINFO_FILETIME = CURLINFO_LONG + 14,
113 CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15,
114 CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16,
115 CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17,
116 CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18,
117 CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19,
118 CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20,
119 CURLINFO_PRIVATE = CURLINFO_STRING + 21,
120 CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22,
121 CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23,
122 CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24,
123 CURLINFO_OS_ERRNO = CURLINFO_LONG + 25,
124 CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26,
125 CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27
131 CURLMSG_NONE, /* first, not used */
132 CURLMSG_DONE, /* This easy handle has completed. 'result' contains
133 the CURLcode of the transfer */
139 CURLMSG msg; /* what this message means */
140 CURL *easy_handle; /* the handle it concerns */
143 void *whatever; /* message-specific data */
144 CURLcode result; /* return code for transfer */
150 static void (*qcurl_global_init) (long flags);
151 static void (*qcurl_global_cleanup) (void);
153 static CURL * (*qcurl_easy_init) (void);
154 static void (*qcurl_easy_cleanup) (CURL *handle);
155 static CURLcode (*qcurl_easy_setopt) (CURL *handle, CURLoption option, ...);
156 static CURLcode (*qcurl_easy_getinfo) (CURL *handle, CURLINFO info, ...);
157 static const char * (*qcurl_easy_strerror) (CURLcode);
159 static CURLM * (*qcurl_multi_init) (void);
160 static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles);
161 static CURLMcode (*qcurl_multi_poll) (CURLM *multi_handle, void*, unsigned int extra_nfds, int timeout_ms, int *ret);
162 static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle);
163 static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle);
164 static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue);
165 static void (*qcurl_multi_cleanup) (CURLM *);
166 static const char * (*qcurl_multi_strerror) (CURLcode);
167 static curl_slist * (*qcurl_slist_append) (curl_slist *list, const char *string);
168 static void (*qcurl_slist_free_all) (curl_slist *list);
170 static dllfunction_t curlfuncs[] =
172 {"curl_global_init", (void **) &qcurl_global_init},
173 {"curl_global_cleanup", (void **) &qcurl_global_cleanup},
174 {"curl_easy_init", (void **) &qcurl_easy_init},
175 {"curl_easy_cleanup", (void **) &qcurl_easy_cleanup},
176 {"curl_easy_setopt", (void **) &qcurl_easy_setopt},
177 {"curl_easy_strerror", (void **) &qcurl_easy_strerror},
178 {"curl_easy_getinfo", (void **) &qcurl_easy_getinfo},
179 {"curl_multi_init", (void **) &qcurl_multi_init},
180 {"curl_multi_perform", (void **) &qcurl_multi_perform},
181 {"curl_multi_poll", (void **) &qcurl_multi_poll},
182 {"curl_multi_add_handle", (void **) &qcurl_multi_add_handle},
183 {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle},
184 {"curl_multi_info_read", (void **) &qcurl_multi_info_read},
185 {"curl_multi_cleanup", (void **) &qcurl_multi_cleanup},
186 {"curl_multi_strerror", (void **) &qcurl_multi_strerror},
187 {"curl_slist_append", (void **) &qcurl_slist_append},
188 {"curl_slist_free_all", (void **) &qcurl_slist_free_all},
192 // Handle for CURL DLL
193 static dllhandle_t curl_dll = NULL;
194 // will be checked at many places to find out if qcurl calls are allowed
196 #define LOADTYPE_NONE 0
197 #define LOADTYPE_PAK 1
198 #define LOADTYPE_CACHEPIC 2
199 #define LOADTYPE_SKINFRAME 3
201 void *curl_mutex = NULL;
203 typedef struct downloadinfo_s
205 char filename[MAX_OSPATH];
209 fs_offset_t startpos;
213 size_t bytes_received; // for buffer
214 double bytes_received_curl; // for throttling
215 double bytes_sent_curl; // for throttling
219 curl_slist *slist; // http headers
221 unsigned char *buffer;
223 curl_callback_t callback;
226 const unsigned char *postbuf;
228 const char *post_content_type;
229 const char *extraheaders;
232 LIST_HEAD(downloads);
233 static int numdownloads = 0;
235 static qbool noclear = false;
237 static int numdownloads_fail = 0;
238 static int numdownloads_success = 0;
239 static int numdownloads_added = 0;
240 static char command_when_done[256] = "";
241 static char command_when_error[256] = "";
247 Sets the command which is to be executed when the last download completes AND
248 all downloads since last server connect ended with a successful status.
249 Setting the command to NULL clears it.
252 static void Curl_CommandWhenDone(const char *cmd)
257 strlcpy(command_when_done, cmd, sizeof(command_when_done));
259 *command_when_done = 0;
264 Do not use yet. Not complete.
265 Problem: what counts as an error?
268 static void Curl_CommandWhenError(const char *cmd)
273 strlcpy(command_when_error, cmd, sizeof(command_when_error));
275 *command_when_error = 0;
280 Curl_Clear_forthismap
282 Clears the "will disconnect on failure" flags.
285 void Curl_Clear_forthismap(void)
290 if (curl_mutex) Thread_LockMutex(curl_mutex);
291 List_For_Each_Entry(di, &downloads, downloadinfo, list)
292 di->forthismap = false;
293 Curl_CommandWhenError(NULL);
294 Curl_CommandWhenDone(NULL);
295 numdownloads_fail = 0;
296 numdownloads_success = 0;
297 numdownloads_added = 0;
298 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
305 Returns true if a download needed for the current game is running.
308 qbool Curl_Have_forthismap(void)
310 return numdownloads_added != 0;
313 void Curl_Register_predownload(void)
315 if (curl_mutex) Thread_LockMutex(curl_mutex);
316 Curl_CommandWhenDone("cl_begindownloads");
317 Curl_CommandWhenError("cl_begindownloads");
318 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
323 Curl_CheckCommandWhenDone
325 Checks if a "done command" is to be executed.
326 All downloads finished, at least one success since connect, no single failure
327 -> execute the command.
329 static void Curl_CheckCommandWhenDone(void)
333 if(numdownloads_added && ((numdownloads_success + numdownloads_fail) == numdownloads_added))
335 if(numdownloads_fail == 0)
337 Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done);
338 Cbuf_AddText(cmd_local, "\n");
339 Cbuf_AddText(cmd_local, command_when_done);
340 Cbuf_AddText(cmd_local, "\n");
344 Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error);
345 Cbuf_AddText(cmd_local, "\n");
346 Cbuf_AddText(cmd_local, command_when_error);
347 Cbuf_AddText(cmd_local, "\n");
349 Curl_Clear_forthismap();
360 static qbool CURL_OpenLibrary (void)
362 const char* dllnames [] =
367 #elif defined(MACOSX)
368 "libcurl.4.dylib", // Mac OS X Notyetreleased
369 "libcurl.3.dylib", // Mac OS X Tiger
370 "libcurl.2.dylib", // Mac OS X Panther
374 "libcurl.so", // FreeBSD
384 return Sys_LoadDependency (dllnames, &curl_dll, curlfuncs);
395 static void CURL_CloseLibrary (void)
397 Sys_FreeLibrary (&curl_dll);
401 static CURLM *curlm = NULL;
402 static double bytes_received = 0; // used for bandwidth throttling
403 static double bytes_sent = 0; // used for bandwidth throttling
404 static double curltime = 0;
410 fwrite-compatible function that writes the data to a file. libcurl can call
414 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
416 fs_offset_t ret = -1;
417 size_t bytes = size * nmemb;
418 downloadinfo *di = (downloadinfo *) vdi;
422 if(di->bytes_received + bytes <= di->buffersize)
424 memcpy(di->buffer + di->bytes_received, data, bytes);
427 // otherwise: buffer overrun, ret stays -1
432 ret = FS_Write(di->stream, data, bytes);
435 di->bytes_received += bytes;
437 //Con_Printf("CURL_fwrite callback timestamp: %f bytes: %ld\n", host.realtime, ret);
440 // Why not ret / nmemb?
441 // Because CURLOPT_WRITEFUNCTION docs say to return the number of bytes.
442 // Yes, this is incompatible to fwrite(2).
447 CURL_DOWNLOAD_SUCCESS = 0,
448 CURL_DOWNLOAD_FAILED,
449 CURL_DOWNLOAD_ABORTED,
450 CURL_DOWNLOAD_SERVERERROR
454 static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
456 downloadinfo *di = (downloadinfo *) cbdata;
459 case CURLCBSTATUS_OK:
460 Con_DPrintf("Download of %s: OK\n", di->filename);
462 case CURLCBSTATUS_FAILED:
463 Con_DPrintf("Download of %s: FAILED\n", di->filename);
465 case CURLCBSTATUS_ABORTED:
466 Con_DPrintf("Download of %s: ABORTED\n", di->filename);
468 case CURLCBSTATUS_SERVERERROR:
469 Con_DPrintf("Download of %s: (unknown server error)\n", di->filename);
471 case CURLCBSTATUS_UNKNOWN:
472 Con_DPrintf("Download of %s: (unknown client error)\n", di->filename);
475 Con_DPrintf("Download of %s: %d\n", di->filename, status);
480 static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
482 curl_default_callback(status, length_received, buffer, cbdata);
485 static unsigned char *decode_image(downloadinfo *di, const char *content_type)
487 unsigned char *pixels = NULL;
488 fs_offset_t filesize = 0;
489 unsigned char *data = FS_LoadFile(di->filename, tempmempool, true, &filesize);
493 if(!strcmp(content_type, "image/jpeg"))
494 pixels = JPEG_LoadImage_BGRA(data, filesize, &mip);
495 else if(!strcmp(content_type, "image/png"))
496 pixels = PNG_LoadImage_BGRA(data, filesize, &mip);
497 else if(filesize >= 7 && !strncmp((char *) data, "\xFF\xD8", 7))
498 pixels = JPEG_LoadImage_BGRA(data, filesize, &mip);
499 else if(filesize >= 7 && !strncmp((char *) data, "\x89PNG\x0D\x0A\x1A\x0A", 7))
500 pixels = PNG_LoadImage_BGRA(data, filesize, &mip);
502 Con_Printf("Did not detect content type: %s\n", content_type);
505 // do we call Image_MakeLinearColorsFromsRGB or not?
513 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
514 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
515 code from libcurl, or 0, if another error has occurred.
518 static qbool Curl_Begin(const char *URL, const char *extraheaders, double maxspeed, const char *name, int loadtype, qbool forthismap, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata);
519 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error, const char *content_type_)
521 char content_type[64];
527 case CURL_DOWNLOAD_SUCCESS:
529 di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
531 case CURL_DOWNLOAD_FAILED:
532 di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
534 case CURL_DOWNLOAD_ABORTED:
535 di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
537 case CURL_DOWNLOAD_SERVERERROR:
538 // reopen to enforce it to have zero bytes again
541 FS_Close(di->stream);
542 di->stream = FS_OpenRealFile(di->filename, "wb", false);
546 di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
550 di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
554 strlcpy(content_type, content_type_, sizeof(content_type));
560 qcurl_multi_remove_handle(curlm, di->curle);
561 qcurl_easy_cleanup(di->curle);
563 qcurl_slist_free_all(di->slist);
566 if(!di->callback && ok && !di->bytes_received)
568 Con_Printf("ERROR: empty file\n");
573 FS_Close(di->stream);
575 #define CLEAR_AND_RETRY() \
578 di->stream = FS_OpenRealFile(di->filename, "wb", false); \
579 FS_Close(di->stream); \
580 if(di->startpos && !di->callback) \
582 Curl_Begin(di->url, di->extraheaders, di->maxspeed, di->filename, di->loadtype, di->forthismap, di->post_content_type, di->postbuf, di->postbufsize, NULL, 0, NULL, NULL); \
583 di->forthismap = false; \
588 if(ok && di->loadtype == LOADTYPE_PAK)
590 ok = FS_AddPack(di->filename, NULL, true, true);
594 else if(ok && di->loadtype == LOADTYPE_CACHEPIC)
597 unsigned char *pixels = NULL;
601 if(!strncmp(p, "dlcache/", 8))
605 pixels = decode_image(di, content_type);
607 Draw_NewPic(p, image_width, image_height, pixels, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_CLAMP);
611 else if(ok && di->loadtype == LOADTYPE_SKINFRAME)
614 unsigned char *pixels = NULL;
618 if(!strncmp(p, "dlcache/", 8))
622 pixels = decode_image(di, content_type);
624 R_SkinFrame_LoadInternalBGRA(p, TEXF_FORCE_RELOAD | TEXF_MIPMAP | TEXF_ALPHA, pixels, image_width, image_height, 0, 0, 0, false); // TODO what sRGB argument to put here?
629 List_Delete(&di->list);
635 ++numdownloads_success;
646 Returns a "cleaned up" URL for display (to strip login data)
649 static const char *CleanURL(const char *url, char *urlbuf, size_t urlbuflength)
651 const char *p, *q, *r;
653 // if URL is of form anything://foo-without-slash@rest, replace by anything://rest
654 p = strstr(url, "://");
657 q = strchr(p + 3, '@');
660 r = strchr(p + 3, '/');
663 dpsnprintf(urlbuf, urlbuflength, "%.*s%s", (int)(p - url + 3), url, q + 1);
674 CheckPendingDownloads
676 checks if there are free download slots to start new downloads in.
677 To not start too many downloads at once, only one download is added at a time,
678 up to a maximum number of cl_curl_maxdownloads are running.
681 static void CheckPendingDownloads(void)
688 if(numdownloads < cl_curl_maxdownloads.integer)
691 List_For_Each_Entry(di, &downloads, downloadinfo, list)
697 Con_Printf("Downloading %s -> %s", CleanURL(di->url, urlbuf, sizeof(urlbuf)), di->filename);
699 di->stream = FS_OpenRealFile(di->filename, "ab", false);
702 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
703 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK, NULL);
706 FS_Seek(di->stream, 0, SEEK_END);
707 di->startpos = FS_Tell(di->stream);
710 Con_Printf(", resuming from position %ld", (long) di->startpos);
715 Con_DPrintf("Downloading %s -> memory\n", CleanURL(di->url, urlbuf, sizeof(urlbuf)));
719 di->curle = qcurl_easy_init();
721 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
722 if(cl_curl_useragent.integer)
725 #ifdef HTTP_USER_AGENT
732 if(*cl_curl_useragent_append.string)
733 ua = va(vabuf, sizeof(vabuf), "%s%s%s",
735 (ua[0] && ua[strlen(ua)-1] != ' ')
738 cl_curl_useragent_append.string);
739 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, ua);
742 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, "");
743 if(developer_curl.integer)
744 qcurl_easy_setopt(di->curle, CURLOPT_VERBOSE, (long) 1);
745 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
746 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
747 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
748 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
749 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
750 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
751 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
752 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
753 qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP);
754 if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK)
756 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");
757 //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0);
759 if(di->post_content_type)
761 qcurl_easy_setopt(di->curle, CURLOPT_POST, 1);
762 qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDS, di->postbuf);
763 qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDSIZE, di->postbufsize);
764 di->slist = qcurl_slist_append(di->slist, va(vabuf, sizeof(vabuf), "Content-Type: %s", di->post_content_type));
767 // parse extra headers into slist
768 // \n separated list!
769 h = di->extraheaders;
772 const char *hh = strchr(h, '\n');
775 char *buf = (char *) Mem_Alloc(tempmempool, hh - h + 1);
776 memcpy(buf, h, hh - h);
778 di->slist = qcurl_slist_append(di->slist, buf);
783 di->slist = qcurl_slist_append(di->slist, h);
788 qcurl_easy_setopt(di->curle, CURLOPT_HTTPHEADER, di->slist);
790 qcurl_multi_add_handle(curlm, di->curle);
793 if(numdownloads >= cl_curl_maxdownloads.integer)
804 this function MUST be called before using anything else in this file.
805 On Win32, this must be called AFTER WSAStartup has been done!
813 if (Thread_HasThreads()) curl_mutex = Thread_CreateMutex();
814 qcurl_global_init(CURL_GLOBAL_SSL);
815 curlm = qcurl_multi_init();
822 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
825 void Curl_ClearRequirements(void);
826 void Curl_Shutdown(void)
830 Curl_ClearRequirements();
832 if (curl_mutex) Thread_DestroyMutex(curl_mutex);
837 // for VM_checkextension()
838 qbool Curl_Available(void)
840 return curl_dll ? true : false;
847 Finds the internal information block for a download given by file name.
850 static downloadinfo *Curl_Find(const char *filename)
855 List_For_Each_Entry(di, &downloads, downloadinfo, list)
856 if(!strcasecmp(di->filename, filename))
861 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
863 downloadinfo *di, *ndi;
866 List_For_Each_Entry_Safe(di, ndi, &downloads, downloadinfo, list)
868 if(di->callback == callback && di->callback_data == cbdata)
870 di->callback = curl_quiet_callback; // do NOT call the callback
871 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL);
880 Starts a download of a given URL to the file name portion of this URL (or name
881 if given) in the "dlcache/" folder.
884 static qbool Curl_Begin(const char *URL, const char *extraheaders, double maxspeed, const char *name, int loadtype, qbool forthismap, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
887 if(loadtype != LOADTYPE_NONE)
888 Host_Error("Curl_Begin: loadtype and buffer are both set");
890 if(!curl_dll || !cl_curl_enabled.integer)
902 // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server
903 p = strchr(URL, ':');
906 if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4))
908 char addressstring[128];
910 InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring));
911 q = strchr(addressstring, ':');
913 q = addressstring + strlen(addressstring);
916 dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3);
922 // Note: This extraction of the file name portion is NOT entirely correct.
924 // It does the following:
926 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
927 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
928 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
930 // However, I'd like to keep this "buggy" behavior so that PHP script
931 // authors can write download scripts without having to enable
932 // AcceptPathInfo on Apache. They just have to ensure that their script
933 // can be called with such a "fake" path name like
934 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
936 // By the way, such PHP scripts should either send the file or a
937 // "Location:" redirect; PHP code example:
939 // header("Location: http://www.example.com/");
941 // By the way, this will set User-Agent to something like
942 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
943 // dp://serverhost:serverport/ so you can filter on this; an example
944 // httpd log file line might be:
946 // 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"
948 if (curl_mutex) Thread_LockMutex(curl_mutex);
953 name = CleanURL(URL, urlbuf, sizeof(urlbuf));
959 name = CleanURL(URL, urlbuf, sizeof(urlbuf));
960 p = strrchr(name, '/');
961 p = p ? (p+1) : name;
963 length = q ? (size_t)(q - p) : strlen(p);
964 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
968 dpsnprintf(fn, sizeof(fn), "dlcache/%s", name);
971 name = fn; // make it point back
973 // already downloading the file?
975 downloadinfo *existingdownloadinfo = Curl_Find(fn);
976 if(existingdownloadinfo)
978 Con_Printf("Can't download %s, already getting it from %s!\n", fn, CleanURL(existingdownloadinfo->url, urlbuf, sizeof(urlbuf)));
980 // however, if it was not for this map yet...
981 if(forthismap && !existingdownloadinfo->forthismap)
983 existingdownloadinfo->forthismap = true;
984 // this "fakes" a download attempt so the client will wait for
985 // the download to finish and then reconnect
986 ++numdownloads_added;
989 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
994 if(FS_FileExists(fn))
996 if(loadtype == LOADTYPE_PAK)
998 qbool already_loaded;
999 if(FS_AddPack(fn, &already_loaded, true, true))
1001 Con_DPrintf("%s already exists, not downloading!\n", fn);
1003 Con_DPrintf("(pak was already loaded)\n");
1008 ++numdownloads_added;
1009 ++numdownloads_success;
1013 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1018 qfile_t *f = FS_OpenRealFile(fn, "rb", false);
1022 FS_Read(f, b, sizeof(b)); // no "-1", I will use memcmp
1024 if(memcmp(b, "PK\x03\x04", 4) && memcmp(b, "PACK", 4))
1026 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
1028 f = FS_OpenRealFile(fn, "wb", false);
1042 // never resume these
1043 qfile_t *f = FS_OpenRealFile(fn, "wb", false);
1050 // if we get here, we actually want to download... so first verify the
1051 // URL scheme (so one can't read local files using file://)
1052 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
1054 Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
1055 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1060 ++numdownloads_added;
1061 di = (downloadinfo *) Z_Malloc(sizeof(*di));
1062 strlcpy(di->filename, name, sizeof(di->filename));
1063 strlcpy(di->url, URL, sizeof(di->url));
1064 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
1065 di->forthismap = forthismap;
1069 di->started = false;
1070 di->loadtype = loadtype;
1071 di->maxspeed = maxspeed;
1072 di->bytes_received = 0;
1073 di->bytes_received_curl = 0;
1074 di->bytes_sent_curl = 0;
1075 di->extraheaders = extraheaders;
1077 di->buffersize = bufsize;
1078 if(callback == NULL)
1080 di->callback = curl_default_callback;
1081 di->callback_data = di;
1085 di->callback = callback;
1086 di->callback_data = cbdata;
1089 if(post_content_type)
1091 di->post_content_type = post_content_type;
1092 di->postbuf = postbuf;
1093 di->postbufsize = postbufsize;
1097 di->post_content_type = NULL;
1099 di->postbufsize = 0;
1102 List_Add(&di->list, &downloads);
1105 Thread_UnlockMutex(curl_mutex);
1111 qbool Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, int loadtype, qbool forthismap)
1113 return Curl_Begin(URL, NULL, maxspeed, name, loadtype, forthismap, NULL, NULL, 0, NULL, 0, NULL, NULL);
1115 qbool Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
1117 return Curl_Begin(URL, NULL, maxspeed, NULL, false, false, NULL, NULL, 0, buf, bufsize, callback, cbdata);
1119 qbool Curl_Begin_ToMemory_POST(const char *URL, const char *extraheaders, double maxspeed, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
1121 return Curl_Begin(URL, extraheaders, maxspeed, NULL, false, false, post_content_type, postbuf, postbufsize, buf, bufsize, callback, cbdata);
1125 ====================
1128 call this regularily as this will always download as much as possible without
1130 ====================
1132 void Curl_Frame(void)
1139 if(!cl_curl_enabled.integer)
1145 if (curl_mutex) Thread_LockMutex(curl_mutex);
1147 Curl_CheckCommandWhenDone();
1149 if(List_Is_Empty(&downloads))
1151 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1155 if(host.realtime < curltime) // throttle
1157 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1167 mc = qcurl_multi_perform(curlm, &remaining);
1169 while(mc == CURLM_CALL_MULTI_PERFORM);
1171 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1176 qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_UPLOAD, &b);
1177 bytes_sent += (b - di->bytes_sent_curl);
1178 di->bytes_sent_curl = b;
1179 qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_DOWNLOAD, &b);
1180 bytes_sent += (b - di->bytes_received_curl);
1181 di->bytes_received_curl = b;
1187 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
1190 if(msg->msg == CURLMSG_DONE)
1192 const char *ct = NULL;
1193 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
1195 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
1196 result = msg->data.result;
1199 failed = CURL_DOWNLOAD_FAILED;
1204 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
1207 case 4: // e.g. 404?
1208 case 5: // e.g. 500?
1209 failed = CURL_DOWNLOAD_SERVERERROR;
1210 result = (CURLcode) code;
1213 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_CONTENT_TYPE, &ct);
1216 Curl_EndDownload(di, failed, result, ct);
1221 CheckPendingDownloads();
1223 // when will we curl the next time?
1224 // we will wait a bit to ensure our download rate is kept.
1225 // we now know that realtime >= curltime... so set up a new curltime
1227 // use the slowest allowing download to derive the maxspeed... this CAN
1228 // be done better, but maybe later
1229 maxspeed = cl_curl_maxspeed.value;
1230 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1231 if(di->maxspeed > 0)
1232 if(di->maxspeed < maxspeed || maxspeed <= 0)
1233 maxspeed = di->maxspeed;
1237 double bytes = bytes_sent + bytes_received; // maybe smoothen a bit?
1238 curltime = host.realtime + bytes / (maxspeed * 1024.0);
1243 curltime = host.realtime;
1245 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1249 ====================
1252 Sleeps until there's some transfer progress or a timeout is reached,
1253 unfortunately the timeout is only in milliseconds.
1254 This allows good throughput even at very low FPS.
1255 ====================
1257 void Curl_Select(double *microseconds)
1259 if (List_Is_Empty(&downloads))
1261 if (qcurl_multi_poll(curlm, NULL, 0, *microseconds / 1000, NULL) == CURLM_OK)
1262 *microseconds = 0; // either we finished waiting or a transfer progressed
1264 Con_Print("There's an emergency going on!\nIt's still going on!\nMaybe you need to upgrade libcurl?\n");
1268 ====================
1271 Stops ALL downloads.
1272 ====================
1274 void Curl_CancelAll(void)
1279 if (curl_mutex) Thread_LockMutex(curl_mutex);
1281 while(!List_Is_Empty(&downloads))
1283 Curl_EndDownload(List_First_Entry(&downloads, downloadinfo, list), CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL);
1284 // INVARIANT: downloads will point to the next download after that!
1287 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1291 ====================
1294 returns true if there is a download running.
1295 ====================
1297 qbool Curl_Running(void)
1302 return !List_Is_Empty(&downloads);
1306 ====================
1307 Curl_GetDownloadAmount
1309 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
1310 for the given download.
1311 ====================
1313 static double Curl_GetDownloadAmount(downloadinfo *di)
1320 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1322 return (di->startpos + di->bytes_received) / (di->startpos + length);
1331 ====================
1332 Curl_GetDownloadSpeed
1334 returns the speed of the given download in bytes per second
1335 ====================
1337 static double Curl_GetDownloadSpeed(downloadinfo *di)
1344 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1352 ====================
1355 prints the download list
1356 ====================
1358 // TODO rewrite using Curl_GetDownloadInfo?
1359 static void Curl_Info_f(cmd_state_t *cmd)
1367 if (curl_mutex) Thread_LockMutex(curl_mutex);
1368 Con_Print("Currently running downloads:\n");
1369 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1371 double speed, percent;
1372 Con_Printf(" %s -> %s ", CleanURL(di->url, urlbuf, sizeof(urlbuf)), di->filename);
1373 percent = 100.0 * Curl_GetDownloadAmount(di);
1374 speed = Curl_GetDownloadSpeed(di);
1376 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1378 Con_Print("(queued)\n");
1380 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1384 Con_Print("No downloads running.\n");
1389 ====================
1392 implements the "curl" console command
1396 curl --cancel filename
1401 curl [--pak] [--forthismap] [--for filename filename...] url
1402 --pak: after downloading, load the package into the virtual file system
1403 --for filename...: only download of at least one of the named files is missing
1404 --forthismap: don't reconnect on failure
1406 curl --clear_autodownload
1407 clears the download success/failure counters
1409 curl --finish_autodownload
1410 if at least one download has been started, disconnect and drop to the menu
1411 once the last download completes successfully, reconnect to the current server
1412 ====================
1414 static void Curl_Curl_f(cmd_state_t *cmd)
1416 double maxspeed = 0;
1419 int loadtype = LOADTYPE_NONE;
1420 qbool forthismap = false;
1422 const char *name = 0;
1426 Con_Print("libcurl DLL not found, this command is inactive.\n");
1430 if(!cl_curl_enabled.integer)
1432 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1436 if(Cmd_Argc(cmd) < 2)
1438 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1442 url = Cmd_Argv(cmd, Cmd_Argc(cmd) - 1);
1443 end = Cmd_Argc(cmd);
1445 for(i = 1; i != end; ++i)
1447 const char *a = Cmd_Argv(cmd, i);
1448 if(!strcmp(a, "--info"))
1453 else if(!strcmp(a, "--cancel"))
1455 if(i == end - 1) // last argument
1459 downloadinfo *di = Curl_Find(url);
1461 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL);
1463 Con_Print("download not found\n");
1467 else if(!strcmp(a, "--pak"))
1469 loadtype = LOADTYPE_PAK;
1471 else if(!strcmp(a, "--cachepic"))
1473 loadtype = LOADTYPE_CACHEPIC;
1475 else if(!strcmp(a, "--skinframe"))
1477 loadtype = LOADTYPE_SKINFRAME;
1479 else if(!strcmp(a, "--for")) // must be last option
1481 for(i = i + 1; i != end - 1; ++i)
1483 if(!FS_FileExists(Cmd_Argv(cmd, i)))
1484 goto needthefile; // why can't I have a "double break"?
1486 // if we get here, we have all the files...
1489 else if(!strcmp(a, "--forthismap"))
1493 else if(!strcmp(a, "--as"))
1498 name = Cmd_Argv(cmd, i);
1501 else if(!strcmp(a, "--clear_autodownload"))
1503 // mark all running downloads as "not for this map", so if they
1504 // fail, it does not matter
1505 Curl_Clear_forthismap();
1508 else if(!strcmp(a, "--finish_autodownload"))
1510 if(numdownloads_added)
1512 char donecommand[256];
1515 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1517 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1518 Curl_CommandWhenDone(donecommand);
1522 Curl_CheckCommandWhenDone();
1525 Curl_Register_predownload();
1530 else if(!strncmp(a, "--maxspeed=", 11))
1532 maxspeed = atof(a + 11);
1536 Con_Printf("curl: invalid option %s\n", a);
1537 // but we ignore the option
1542 Curl_Begin_ToFile(url, maxspeed, name, loadtype, forthismap);
1546 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1548 Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1552 void Curl_CurlCat_f(cmd_state_t *cmd)
1555 const char *url = Cmd_Argv(cmd, 1);
1556 buf = Z_Malloc(16384);
1557 Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1562 ====================
1565 loads the commands and cvars this library uses
1566 ====================
1568 void Curl_Init_Commands(void)
1570 Cvar_RegisterVariable (&cl_curl_enabled);
1571 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1572 Cvar_RegisterVariable (&cl_curl_maxspeed);
1573 Cvar_RegisterVariable (&sv_curl_defaulturl);
1574 Cvar_RegisterVariable (&sv_curl_serverpackages);
1575 Cvar_RegisterVariable (&sv_curl_maxspeed);
1576 Cvar_RegisterVariable (&cl_curl_useragent);
1577 Cvar_RegisterVariable (&cl_curl_useragent_append);
1578 Cvar_RegisterVariable (&developer_curl);
1579 Cmd_AddCommand(CF_CLIENT | CF_CLIENT_FROM_SERVER, "curl", Curl_Curl_f, "download data from an URL and add to search path");
1580 //Cmd_AddCommand(cmd_local, "curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1584 ====================
1585 Curl_GetDownloadInfo
1587 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1588 The number of elements in the array is returned in int *nDownloads.
1589 const char **additional_info may be set to a string of additional user
1590 information, or to NULL if no such display shall occur. The returned
1591 array must be freed later using Z_Free.
1592 ====================
1594 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info, char *addinfo, size_t addinfolength)
1598 Curl_downloadinfo_t *downinfo;
1604 *additional_info = NULL;
1608 if (curl_mutex) Thread_LockMutex(curl_mutex);
1611 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1614 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1616 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1618 // do not show infobars for background downloads
1619 if(developer.integer <= 0)
1622 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1625 downinfo[i].progress = Curl_GetDownloadAmount(di);
1626 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1627 downinfo[i].queued = false;
1631 downinfo[i].queued = true;
1638 // TODO: can I clear command_when_done as soon as the first download fails?
1639 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1641 if(!strncmp(command_when_done, "connect ", 8))
1642 dpsnprintf(addinfo, addinfolength, "(will join %s when done)", command_when_done + 8);
1643 else if(!strcmp(command_when_done, "cl_begindownloads"))
1644 dpsnprintf(addinfo, addinfolength, "(will enter the game when done)");
1646 dpsnprintf(addinfo, addinfolength, "(will do '%s' when done)", command_when_done);
1647 *additional_info = addinfo;
1650 *additional_info = NULL;
1654 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1660 ====================
1663 finds the URL where to find a given package.
1665 For this, it reads a file "curl_urls.txt" of the following format:
1668 revdm*.pk3 http://revdm/downloads/are/here/
1669 * http://any/other/stuff/is/here/
1671 The URLs should end in /. If not, downloads will still work, but the cached files
1672 can't be just put into the data directory with the same download configuration
1673 (you might want to do this if you want to tag downloaded files from your
1674 server, but you should not). "-" means "don't download".
1676 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1679 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1680 this file for obvious reasons.
1681 ====================
1683 static const char *Curl_FindPackURL(const char *filename)
1685 static char foundurl[1024]; // invoked only by server
1686 fs_offset_t filesize;
1687 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1690 // read lines of format "pattern url"
1692 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1705 if(pattern && url && patternend)
1711 if(matchpattern(filename, pattern, true))
1713 strlcpy(foundurl, url, sizeof(foundurl));
1725 if(pattern && !patternend)
1727 else if(url && !urlend)
1733 else if(pattern && patternend && !url)
1742 return sv_curl_defaulturl.string;
1745 typedef struct requirement_s
1747 struct requirement_s *next;
1748 char filename[MAX_OSPATH];
1751 static requirement *requirements = NULL;
1755 ====================
1758 Adds the given file to the list of requirements.
1759 ====================
1761 void Curl_RequireFile(const char *filename)
1763 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1764 req->next = requirements;
1765 strlcpy(req->filename, filename, sizeof(req->filename));
1770 ====================
1771 Curl_ClearRequirements
1773 Clears the list of required files for playing on the current map.
1774 This should be called at every map change.
1775 ====================
1777 void Curl_ClearRequirements(void)
1781 requirement *req = requirements;
1782 requirements = requirements->next;
1788 ====================
1789 Curl_SendRequirements
1791 Makes the current host_clients download all files he needs.
1792 This is done by sending him the following console commands:
1794 curl --clear_autodownload
1795 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1796 curl --finish_autodownload
1797 ====================
1799 static qbool Curl_SendRequirement(const char *filename, qbool foundone, char *sendbuffer, size_t sendbuffer_len)
1802 const char *thispack = FS_WhichPack(filename);
1803 const char *packurl;
1805 if(!thispack || !*thispack)
1808 p = strrchr(thispack, '/');
1812 packurl = Curl_FindPackURL(thispack);
1814 if(packurl && *packurl && strcmp(packurl, "-"))
1817 strlcat(sendbuffer, "curl --clear_autodownload\n", sendbuffer_len);
1819 strlcat(sendbuffer, "curl --pak --forthismap --as ", sendbuffer_len);
1820 strlcat(sendbuffer, thispack, sendbuffer_len);
1821 if(sv_curl_maxspeed.value > 0)
1822 dpsnprintf(sendbuffer + strlen(sendbuffer), sendbuffer_len - strlen(sendbuffer), " --maxspeed=%.1f", sv_curl_maxspeed.value);
1823 strlcat(sendbuffer, " --for ", sendbuffer_len);
1824 strlcat(sendbuffer, filename, sendbuffer_len);
1825 strlcat(sendbuffer, " ", sendbuffer_len);
1826 strlcat(sendbuffer, packurl, sendbuffer_len);
1827 strlcat(sendbuffer, thispack, sendbuffer_len);
1828 strlcat(sendbuffer, "\n", sendbuffer_len);
1835 void Curl_SendRequirements(void)
1837 // for each requirement, find the pack name
1838 char sendbuffer[4096] = "";
1840 qbool foundone = false;
1843 for(req = requirements; req; req = req->next)
1844 foundone = Curl_SendRequirement(req->filename, foundone, sendbuffer, sizeof(sendbuffer)) || foundone;
1846 p = sv_curl_serverpackages.string;
1847 while(COM_ParseToken_Simple(&p, false, false, true))
1848 foundone = Curl_SendRequirement(com_token, foundone, sendbuffer, sizeof(sendbuffer)) || foundone;
1851 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1853 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1854 SV_ClientCommands("%s", sendbuffer);
1856 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");