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);
841 Finds the internal information block for a download given by file name.
844 static downloadinfo *Curl_Find(const char *filename)
849 List_For_Each_Entry(di, &downloads, downloadinfo, list)
850 if(!strcasecmp(di->filename, filename))
855 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
857 downloadinfo *di, *ndi;
860 List_For_Each_Entry_Safe(di, ndi, &downloads, downloadinfo, list)
862 if(di->callback == callback && di->callback_data == cbdata)
864 di->callback = curl_quiet_callback; // do NOT call the callback
865 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL);
874 Starts a download of a given URL to the file name portion of this URL (or name
875 if given) in the "dlcache/" folder.
878 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)
881 if(loadtype != LOADTYPE_NONE)
882 Host_Error("Curl_Begin: loadtype and buffer are both set");
884 if(!curl_dll || !cl_curl_enabled.integer)
896 // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server
897 p = strchr(URL, ':');
900 if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4))
902 char addressstring[128];
904 InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring));
905 q = strchr(addressstring, ':');
907 q = addressstring + strlen(addressstring);
910 dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3);
916 // Note: This extraction of the file name portion is NOT entirely correct.
918 // It does the following:
920 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
921 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
922 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
924 // However, I'd like to keep this "buggy" behavior so that PHP script
925 // authors can write download scripts without having to enable
926 // AcceptPathInfo on Apache. They just have to ensure that their script
927 // can be called with such a "fake" path name like
928 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
930 // By the way, such PHP scripts should either send the file or a
931 // "Location:" redirect; PHP code example:
933 // header("Location: http://www.example.com/");
935 // By the way, this will set User-Agent to something like
936 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
937 // dp://serverhost:serverport/ so you can filter on this; an example
938 // httpd log file line might be:
940 // 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"
942 if (curl_mutex) Thread_LockMutex(curl_mutex);
947 name = CleanURL(URL, urlbuf, sizeof(urlbuf));
953 name = CleanURL(URL, urlbuf, sizeof(urlbuf));
954 p = strrchr(name, '/');
955 p = p ? (p+1) : name;
957 length = q ? (size_t)(q - p) : strlen(p);
958 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
962 dpsnprintf(fn, sizeof(fn), "dlcache/%s", name);
965 name = fn; // make it point back
967 // already downloading the file?
969 downloadinfo *existingdownloadinfo = Curl_Find(fn);
970 if(existingdownloadinfo)
972 Con_Printf("Can't download %s, already getting it from %s!\n", fn, CleanURL(existingdownloadinfo->url, urlbuf, sizeof(urlbuf)));
974 // however, if it was not for this map yet...
975 if(forthismap && !existingdownloadinfo->forthismap)
977 existingdownloadinfo->forthismap = true;
978 // this "fakes" a download attempt so the client will wait for
979 // the download to finish and then reconnect
980 ++numdownloads_added;
983 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
988 if(FS_FileExists(fn))
990 if(loadtype == LOADTYPE_PAK)
992 qbool already_loaded;
993 if(FS_AddPack(fn, &already_loaded, true, true))
995 Con_DPrintf("%s already exists, not downloading!\n", fn);
997 Con_DPrintf("(pak was already loaded)\n");
1002 ++numdownloads_added;
1003 ++numdownloads_success;
1007 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1012 qfile_t *f = FS_OpenRealFile(fn, "rb", false);
1016 FS_Read(f, b, sizeof(b)); // no "-1", I will use memcmp
1018 if(memcmp(b, "PK\x03\x04", 4) && memcmp(b, "PACK", 4))
1020 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
1022 f = FS_OpenRealFile(fn, "wb", false);
1036 // never resume these
1037 qfile_t *f = FS_OpenRealFile(fn, "wb", false);
1044 // if we get here, we actually want to download... so first verify the
1045 // URL scheme (so one can't read local files using file://)
1046 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
1048 Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
1049 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1054 ++numdownloads_added;
1055 di = (downloadinfo *) Z_Malloc(sizeof(*di));
1056 strlcpy(di->filename, name, sizeof(di->filename));
1057 strlcpy(di->url, URL, sizeof(di->url));
1058 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
1059 di->forthismap = forthismap;
1063 di->started = false;
1064 di->loadtype = loadtype;
1065 di->maxspeed = maxspeed;
1066 di->bytes_received = 0;
1067 di->bytes_received_curl = 0;
1068 di->bytes_sent_curl = 0;
1069 di->extraheaders = extraheaders;
1071 di->buffersize = bufsize;
1072 if(callback == NULL)
1074 di->callback = curl_default_callback;
1075 di->callback_data = di;
1079 di->callback = callback;
1080 di->callback_data = cbdata;
1083 if(post_content_type)
1085 di->post_content_type = post_content_type;
1086 di->postbuf = postbuf;
1087 di->postbufsize = postbufsize;
1091 di->post_content_type = NULL;
1093 di->postbufsize = 0;
1096 List_Add(&di->list, &downloads);
1099 Thread_UnlockMutex(curl_mutex);
1105 qbool Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, int loadtype, qbool forthismap)
1107 return Curl_Begin(URL, NULL, maxspeed, name, loadtype, forthismap, NULL, NULL, 0, NULL, 0, NULL, NULL);
1109 qbool Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
1111 return Curl_Begin(URL, NULL, maxspeed, NULL, false, false, NULL, NULL, 0, buf, bufsize, callback, cbdata);
1113 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)
1115 return Curl_Begin(URL, extraheaders, maxspeed, NULL, false, false, post_content_type, postbuf, postbufsize, buf, bufsize, callback, cbdata);
1119 ====================
1122 call this regularily as this will always download as much as possible without
1124 ====================
1126 void Curl_Frame(void)
1133 if(!cl_curl_enabled.integer)
1139 if (curl_mutex) Thread_LockMutex(curl_mutex);
1141 Curl_CheckCommandWhenDone();
1143 if(List_Is_Empty(&downloads))
1145 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1149 if(host.realtime < curltime) // throttle
1151 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1161 mc = qcurl_multi_perform(curlm, &remaining);
1163 while(mc == CURLM_CALL_MULTI_PERFORM);
1165 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1170 qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_UPLOAD, &b);
1171 bytes_sent += (b - di->bytes_sent_curl);
1172 di->bytes_sent_curl = b;
1173 qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_DOWNLOAD, &b);
1174 bytes_sent += (b - di->bytes_received_curl);
1175 di->bytes_received_curl = b;
1181 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
1184 if(msg->msg == CURLMSG_DONE)
1186 const char *ct = NULL;
1187 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
1189 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
1190 result = msg->data.result;
1193 failed = CURL_DOWNLOAD_FAILED;
1198 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
1201 case 4: // e.g. 404?
1202 case 5: // e.g. 500?
1203 failed = CURL_DOWNLOAD_SERVERERROR;
1204 result = (CURLcode) code;
1207 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_CONTENT_TYPE, &ct);
1210 Curl_EndDownload(di, failed, result, ct);
1215 CheckPendingDownloads();
1217 // when will we curl the next time?
1218 // we will wait a bit to ensure our download rate is kept.
1219 // we now know that realtime >= curltime... so set up a new curltime
1221 // use the slowest allowing download to derive the maxspeed... this CAN
1222 // be done better, but maybe later
1223 maxspeed = cl_curl_maxspeed.value;
1224 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1225 if(di->maxspeed > 0)
1226 if(di->maxspeed < maxspeed || maxspeed <= 0)
1227 maxspeed = di->maxspeed;
1231 double bytes = bytes_sent + bytes_received; // maybe smoothen a bit?
1232 curltime = host.realtime + bytes / (maxspeed * 1024.0);
1237 curltime = host.realtime;
1239 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1243 ====================
1246 Sleeps until there's some transfer progress or a timeout is reached,
1247 unfortunately the timeout is only in milliseconds.
1248 This allows good throughput even at very low FPS.
1249 ====================
1251 void Curl_Select(double *microseconds)
1253 if (List_Is_Empty(&downloads))
1255 if (qcurl_multi_poll(curlm, NULL, 0, *microseconds / 1000, NULL) == CURLM_OK)
1256 *microseconds = 0; // either we finished waiting or a transfer progressed
1258 Con_Print("There's an emergency going on!\nIt's still going on!\nMaybe you need to upgrade libcurl?\n");
1262 ====================
1265 Stops ALL downloads.
1266 ====================
1268 void Curl_CancelAll(void)
1273 if (curl_mutex) Thread_LockMutex(curl_mutex);
1275 while(!List_Is_Empty(&downloads))
1277 Curl_EndDownload(List_First_Entry(&downloads, downloadinfo, list), CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL);
1278 // INVARIANT: downloads will point to the next download after that!
1281 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1285 ====================
1288 returns true if there is a download running.
1289 ====================
1291 qbool Curl_Running(void)
1296 return !List_Is_Empty(&downloads);
1300 ====================
1301 Curl_GetDownloadAmount
1303 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
1304 for the given download.
1305 ====================
1307 static double Curl_GetDownloadAmount(downloadinfo *di)
1314 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1316 return (di->startpos + di->bytes_received) / (di->startpos + length);
1325 ====================
1326 Curl_GetDownloadSpeed
1328 returns the speed of the given download in bytes per second
1329 ====================
1331 static double Curl_GetDownloadSpeed(downloadinfo *di)
1338 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1346 ====================
1349 prints the download list
1350 ====================
1352 // TODO rewrite using Curl_GetDownloadInfo?
1353 static void Curl_Info_f(cmd_state_t *cmd)
1361 if (curl_mutex) Thread_LockMutex(curl_mutex);
1362 Con_Print("Currently running downloads:\n");
1363 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1365 double speed, percent;
1366 Con_Printf(" %s -> %s ", CleanURL(di->url, urlbuf, sizeof(urlbuf)), di->filename);
1367 percent = 100.0 * Curl_GetDownloadAmount(di);
1368 speed = Curl_GetDownloadSpeed(di);
1370 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1372 Con_Print("(queued)\n");
1374 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1378 Con_Print("No downloads running.\n");
1383 ====================
1386 implements the "curl" console command
1390 curl --cancel filename
1395 curl [--pak] [--forthismap] [--for filename filename...] url
1396 --pak: after downloading, load the package into the virtual file system
1397 --for filename...: only download of at least one of the named files is missing
1398 --forthismap: don't reconnect on failure
1400 curl --clear_autodownload
1401 clears the download success/failure counters
1403 curl --finish_autodownload
1404 if at least one download has been started, disconnect and drop to the menu
1405 once the last download completes successfully, reconnect to the current server
1406 ====================
1408 static void Curl_Curl_f(cmd_state_t *cmd)
1410 double maxspeed = 0;
1413 int loadtype = LOADTYPE_NONE;
1414 qbool forthismap = false;
1416 const char *name = 0;
1420 Con_Print("libcurl DLL not found, this command is inactive.\n");
1424 if(!cl_curl_enabled.integer)
1426 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1430 if(Cmd_Argc(cmd) < 2)
1432 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1436 url = Cmd_Argv(cmd, Cmd_Argc(cmd) - 1);
1437 end = Cmd_Argc(cmd);
1439 for(i = 1; i != end; ++i)
1441 const char *a = Cmd_Argv(cmd, i);
1442 if(!strcmp(a, "--info"))
1447 else if(!strcmp(a, "--cancel"))
1449 if(i == end - 1) // last argument
1453 downloadinfo *di = Curl_Find(url);
1455 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL);
1457 Con_Print("download not found\n");
1461 else if(!strcmp(a, "--pak"))
1463 loadtype = LOADTYPE_PAK;
1465 else if(!strcmp(a, "--cachepic"))
1467 loadtype = LOADTYPE_CACHEPIC;
1469 else if(!strcmp(a, "--skinframe"))
1471 loadtype = LOADTYPE_SKINFRAME;
1473 else if(!strcmp(a, "--for")) // must be last option
1475 for(i = i + 1; i != end - 1; ++i)
1477 if(!FS_FileExists(Cmd_Argv(cmd, i)))
1478 goto needthefile; // why can't I have a "double break"?
1480 // if we get here, we have all the files...
1483 else if(!strcmp(a, "--forthismap"))
1487 else if(!strcmp(a, "--as"))
1492 name = Cmd_Argv(cmd, i);
1495 else if(!strcmp(a, "--clear_autodownload"))
1497 // mark all running downloads as "not for this map", so if they
1498 // fail, it does not matter
1499 Curl_Clear_forthismap();
1502 else if(!strcmp(a, "--finish_autodownload"))
1504 if(numdownloads_added)
1506 char donecommand[256];
1509 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1511 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1512 Curl_CommandWhenDone(donecommand);
1516 Curl_CheckCommandWhenDone();
1519 Curl_Register_predownload();
1524 else if(!strncmp(a, "--maxspeed=", 11))
1526 maxspeed = atof(a + 11);
1530 Con_Printf("curl: invalid option %s\n", a);
1531 // but we ignore the option
1536 Curl_Begin_ToFile(url, maxspeed, name, loadtype, forthismap);
1540 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1542 Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1546 void Curl_CurlCat_f(cmd_state_t *cmd)
1549 const char *url = Cmd_Argv(cmd, 1);
1550 buf = Z_Malloc(16384);
1551 Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1556 ====================
1559 loads the commands and cvars this library uses
1560 ====================
1562 void Curl_Init_Commands(void)
1564 Cvar_RegisterVariable (&cl_curl_enabled);
1565 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1566 Cvar_RegisterVariable (&cl_curl_maxspeed);
1567 Cvar_RegisterVariable (&sv_curl_defaulturl);
1568 Cvar_RegisterVariable (&sv_curl_serverpackages);
1569 Cvar_RegisterVariable (&sv_curl_maxspeed);
1570 Cvar_RegisterVariable (&cl_curl_useragent);
1571 Cvar_RegisterVariable (&cl_curl_useragent_append);
1572 Cvar_RegisterVariable (&developer_curl);
1573 Cmd_AddCommand(CF_CLIENT | CF_CLIENT_FROM_SERVER, "curl", Curl_Curl_f, "download data from an URL and add to search path");
1574 //Cmd_AddCommand(cmd_local, "curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1578 ====================
1579 Curl_GetDownloadInfo
1581 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1582 The number of elements in the array is returned in int *nDownloads.
1583 const char **additional_info may be set to a string of additional user
1584 information, or to NULL if no such display shall occur. The returned
1585 array must be freed later using Z_Free.
1586 ====================
1588 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info, char *addinfo, size_t addinfolength)
1592 Curl_downloadinfo_t *downinfo;
1598 *additional_info = NULL;
1602 if (curl_mutex) Thread_LockMutex(curl_mutex);
1605 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1608 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1610 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1612 // do not show infobars for background downloads
1613 if(developer.integer <= 0)
1616 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1619 downinfo[i].progress = Curl_GetDownloadAmount(di);
1620 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1621 downinfo[i].queued = false;
1625 downinfo[i].queued = true;
1632 // TODO: can I clear command_when_done as soon as the first download fails?
1633 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1635 if(!strncmp(command_when_done, "connect ", 8))
1636 dpsnprintf(addinfo, addinfolength, "(will join %s when done)", command_when_done + 8);
1637 else if(!strcmp(command_when_done, "cl_begindownloads"))
1638 dpsnprintf(addinfo, addinfolength, "(will enter the game when done)");
1640 dpsnprintf(addinfo, addinfolength, "(will do '%s' when done)", command_when_done);
1641 *additional_info = addinfo;
1644 *additional_info = NULL;
1648 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1654 ====================
1657 finds the URL where to find a given package.
1659 For this, it reads a file "curl_urls.txt" of the following format:
1662 revdm*.pk3 http://revdm/downloads/are/here/
1663 * http://any/other/stuff/is/here/
1665 The URLs should end in /. If not, downloads will still work, but the cached files
1666 can't be just put into the data directory with the same download configuration
1667 (you might want to do this if you want to tag downloaded files from your
1668 server, but you should not). "-" means "don't download".
1670 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1673 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1674 this file for obvious reasons.
1675 ====================
1677 static const char *Curl_FindPackURL(const char *filename)
1679 static char foundurl[1024]; // invoked only by server
1680 fs_offset_t filesize;
1681 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1684 // read lines of format "pattern url"
1686 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1699 if(pattern && url && patternend)
1705 if(matchpattern(filename, pattern, true))
1707 strlcpy(foundurl, url, sizeof(foundurl));
1719 if(pattern && !patternend)
1721 else if(url && !urlend)
1727 else if(pattern && patternend && !url)
1736 return sv_curl_defaulturl.string;
1739 typedef struct requirement_s
1741 struct requirement_s *next;
1742 char filename[MAX_OSPATH];
1745 static requirement *requirements = NULL;
1749 ====================
1752 Adds the given file to the list of requirements.
1753 ====================
1755 void Curl_RequireFile(const char *filename)
1757 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1758 req->next = requirements;
1759 strlcpy(req->filename, filename, sizeof(req->filename));
1764 ====================
1765 Curl_ClearRequirements
1767 Clears the list of required files for playing on the current map.
1768 This should be called at every map change.
1769 ====================
1771 void Curl_ClearRequirements(void)
1775 requirement *req = requirements;
1776 requirements = requirements->next;
1782 ====================
1783 Curl_SendRequirements
1785 Makes the current host_clients download all files he needs.
1786 This is done by sending him the following console commands:
1788 curl --clear_autodownload
1789 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1790 curl --finish_autodownload
1791 ====================
1793 static qbool Curl_SendRequirement(const char *filename, qbool foundone, char *sendbuffer, size_t sendbuffer_len)
1796 const char *thispack = FS_WhichPack(filename);
1797 const char *packurl;
1799 if(!thispack || !*thispack)
1802 p = strrchr(thispack, '/');
1806 packurl = Curl_FindPackURL(thispack);
1808 if(packurl && *packurl && strcmp(packurl, "-"))
1811 strlcat(sendbuffer, "curl --clear_autodownload\n", sendbuffer_len);
1813 strlcat(sendbuffer, "curl --pak --forthismap --as ", sendbuffer_len);
1814 strlcat(sendbuffer, thispack, sendbuffer_len);
1815 if(sv_curl_maxspeed.value > 0)
1816 dpsnprintf(sendbuffer + strlen(sendbuffer), sendbuffer_len - strlen(sendbuffer), " --maxspeed=%.1f", sv_curl_maxspeed.value);
1817 strlcat(sendbuffer, " --for ", sendbuffer_len);
1818 strlcat(sendbuffer, filename, sendbuffer_len);
1819 strlcat(sendbuffer, " ", sendbuffer_len);
1820 strlcat(sendbuffer, packurl, sendbuffer_len);
1821 strlcat(sendbuffer, thispack, sendbuffer_len);
1822 strlcat(sendbuffer, "\n", sendbuffer_len);
1829 void Curl_SendRequirements(void)
1831 // for each requirement, find the pack name
1832 char sendbuffer[4096] = "";
1834 qbool foundone = false;
1837 for(req = requirements; req; req = req->next)
1838 foundone = Curl_SendRequirement(req->filename, foundone, sendbuffer, sizeof(sendbuffer)) || foundone;
1840 p = sv_curl_serverpackages.string;
1841 while(COM_ParseToken_Simple(&p, false, false, true))
1842 foundone = Curl_SendRequirement(com_token, foundone, sendbuffer, sizeof(sendbuffer)) || foundone;
1845 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1847 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1848 SV_ClientCommands("%s", sendbuffer);
1850 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");