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)"};
20 =================================================================
22 Minimal set of definitions from libcurl
24 WARNING: for a matter of simplicity, several pointer types are
25 casted to "void*", and most enumerated values are not included
27 =================================================================
30 typedef struct CURL_s CURL;
31 typedef struct CURLM_s CURLM;
32 typedef struct curl_slist curl_slist;
40 CURLM_CALL_MULTI_PERFORM=-1, /* please call curl_multi_perform() soon */
44 #define CURL_GLOBAL_NOTHING 0
45 #define CURL_GLOBAL_SSL 1
46 #define CURL_GLOBAL_WIN32 2
47 #define CURLOPTTYPE_LONG 0
48 #define CURLOPTTYPE_OBJECTPOINT 10000
49 #define CURLOPTTYPE_FUNCTIONPOINT 20000
50 #define CURLOPTTYPE_OFF_T 30000
51 #define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number
54 CINIT(WRITEDATA, OBJECTPOINT, 1),
55 CINIT(URL, OBJECTPOINT, 2),
56 CINIT(ERRORBUFFER, OBJECTPOINT, 10),
57 CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11),
58 CINIT(POSTFIELDS, OBJECTPOINT, 15),
59 CINIT(REFERER, OBJECTPOINT, 16),
60 CINIT(USERAGENT, OBJECTPOINT, 18),
61 CINIT(LOW_SPEED_LIMIT, LONG , 19),
62 CINIT(LOW_SPEED_TIME, LONG, 20),
63 CINIT(RESUME_FROM, LONG, 21),
64 CINIT(HTTPHEADER, OBJECTPOINT, 23),
65 CINIT(POST, LONG, 47), /* HTTP POST method */
66 CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */
67 CINIT(POSTFIELDSIZE, LONG, 60),
68 CINIT(PRIVATE, OBJECTPOINT, 103),
69 CINIT(PROTOCOLS, LONG, 181),
70 CINIT(REDIR_PROTOCOLS, LONG, 182)
73 #define CURLPROTO_HTTP (1<<0)
74 #define CURLPROTO_HTTPS (1<<1)
75 #define CURLPROTO_FTP (1<<2)
79 CURLINFO_HEADER_IN, /* 1 */
80 CURLINFO_HEADER_OUT, /* 2 */
81 CURLINFO_DATA_IN, /* 3 */
82 CURLINFO_DATA_OUT, /* 4 */
83 CURLINFO_SSL_DATA_IN, /* 5 */
84 CURLINFO_SSL_DATA_OUT, /* 6 */
88 #define CURLINFO_STRING 0x100000
89 #define CURLINFO_LONG 0x200000
90 #define CURLINFO_DOUBLE 0x300000
91 #define CURLINFO_SLIST 0x400000
92 #define CURLINFO_MASK 0x0fffff
93 #define CURLINFO_TYPEMASK 0xf00000
96 CURLINFO_NONE, /* first, never use this */
97 CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1,
98 CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2,
99 CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3,
100 CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4,
101 CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5,
102 CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6,
103 CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7,
104 CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8,
105 CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9,
106 CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10,
107 CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11,
108 CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12,
109 CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13,
110 CURLINFO_FILETIME = CURLINFO_LONG + 14,
111 CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15,
112 CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16,
113 CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17,
114 CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18,
115 CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19,
116 CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20,
117 CURLINFO_PRIVATE = CURLINFO_STRING + 21,
118 CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22,
119 CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23,
120 CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24,
121 CURLINFO_OS_ERRNO = CURLINFO_LONG + 25,
122 CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26,
123 CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27
129 CURLMSG_NONE, /* first, not used */
130 CURLMSG_DONE, /* This easy handle has completed. 'result' contains
131 the CURLcode of the transfer */
137 CURLMSG msg; /* what this message means */
138 CURL *easy_handle; /* the handle it concerns */
141 void *whatever; /* message-specific data */
142 CURLcode result; /* return code for transfer */
148 static void (*qcurl_global_init) (long flags);
149 static void (*qcurl_global_cleanup) (void);
151 static CURL * (*qcurl_easy_init) (void);
152 static void (*qcurl_easy_cleanup) (CURL *handle);
153 static CURLcode (*qcurl_easy_setopt) (CURL *handle, CURLoption option, ...);
154 static CURLcode (*qcurl_easy_getinfo) (CURL *handle, CURLINFO info, ...);
155 static const char * (*qcurl_easy_strerror) (CURLcode);
157 static CURLM * (*qcurl_multi_init) (void);
158 static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles);
159 static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle);
160 static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle);
161 static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue);
162 static void (*qcurl_multi_cleanup) (CURLM *);
163 static const char * (*qcurl_multi_strerror) (CURLcode);
164 static curl_slist * (*qcurl_slist_append) (curl_slist *list, const char *string);
165 static void (*qcurl_slist_free_all) (curl_slist *list);
167 static dllfunction_t curlfuncs[] =
169 {"curl_global_init", (void **) &qcurl_global_init},
170 {"curl_global_cleanup", (void **) &qcurl_global_cleanup},
171 {"curl_easy_init", (void **) &qcurl_easy_init},
172 {"curl_easy_cleanup", (void **) &qcurl_easy_cleanup},
173 {"curl_easy_setopt", (void **) &qcurl_easy_setopt},
174 {"curl_easy_strerror", (void **) &qcurl_easy_strerror},
175 {"curl_easy_getinfo", (void **) &qcurl_easy_getinfo},
176 {"curl_multi_init", (void **) &qcurl_multi_init},
177 {"curl_multi_perform", (void **) &qcurl_multi_perform},
178 {"curl_multi_add_handle", (void **) &qcurl_multi_add_handle},
179 {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle},
180 {"curl_multi_info_read", (void **) &qcurl_multi_info_read},
181 {"curl_multi_cleanup", (void **) &qcurl_multi_cleanup},
182 {"curl_multi_strerror", (void **) &qcurl_multi_strerror},
183 {"curl_slist_append", (void **) &qcurl_slist_append},
184 {"curl_slist_free_all", (void **) &qcurl_slist_free_all},
188 // Handle for CURL DLL
189 static dllhandle_t curl_dll = NULL;
190 // will be checked at many places to find out if qcurl calls are allowed
192 #define LOADTYPE_NONE 0
193 #define LOADTYPE_PAK 1
194 #define LOADTYPE_CACHEPIC 2
195 #define LOADTYPE_SKINFRAME 3
197 void *curl_mutex = NULL;
199 typedef struct downloadinfo_s
201 char filename[MAX_OSPATH];
205 fs_offset_t startpos;
209 size_t bytes_received; // for buffer
210 double bytes_received_curl; // for throttling
211 double bytes_sent_curl; // for throttling
215 curl_slist *slist; // http headers
217 unsigned char *buffer;
219 curl_callback_t callback;
222 const unsigned char *postbuf;
224 const char *post_content_type;
225 const char *extraheaders;
228 LIST_HEAD(downloads);
229 static int numdownloads = 0;
231 static qbool noclear = false;
233 static int numdownloads_fail = 0;
234 static int numdownloads_success = 0;
235 static int numdownloads_added = 0;
236 static char command_when_done[256] = "";
237 static char command_when_error[256] = "";
243 Sets the command which is to be executed when the last download completes AND
244 all downloads since last server connect ended with a successful status.
245 Setting the command to NULL clears it.
248 static void Curl_CommandWhenDone(const char *cmd)
253 strlcpy(command_when_done, cmd, sizeof(command_when_done));
255 *command_when_done = 0;
260 Do not use yet. Not complete.
261 Problem: what counts as an error?
264 static void Curl_CommandWhenError(const char *cmd)
269 strlcpy(command_when_error, cmd, sizeof(command_when_error));
271 *command_when_error = 0;
276 Curl_Clear_forthismap
278 Clears the "will disconnect on failure" flags.
281 void Curl_Clear_forthismap(void)
286 if (curl_mutex) Thread_LockMutex(curl_mutex);
287 List_For_Each_Entry(di, &downloads, downloadinfo, list)
288 di->forthismap = false;
289 Curl_CommandWhenError(NULL);
290 Curl_CommandWhenDone(NULL);
291 numdownloads_fail = 0;
292 numdownloads_success = 0;
293 numdownloads_added = 0;
294 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
301 Returns true if a download needed for the current game is running.
304 qbool Curl_Have_forthismap(void)
306 return numdownloads_added != 0;
309 void Curl_Register_predownload(void)
311 if (curl_mutex) Thread_LockMutex(curl_mutex);
312 Curl_CommandWhenDone("cl_begindownloads");
313 Curl_CommandWhenError("cl_begindownloads");
314 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
319 Curl_CheckCommandWhenDone
321 Checks if a "done command" is to be executed.
322 All downloads finished, at least one success since connect, no single failure
323 -> execute the command.
325 static void Curl_CheckCommandWhenDone(void)
329 if(numdownloads_added && ((numdownloads_success + numdownloads_fail) == numdownloads_added))
331 if(numdownloads_fail == 0)
333 Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done);
334 Cbuf_AddText(cmd_local, "\n");
335 Cbuf_AddText(cmd_local, command_when_done);
336 Cbuf_AddText(cmd_local, "\n");
340 Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error);
341 Cbuf_AddText(cmd_local, "\n");
342 Cbuf_AddText(cmd_local, command_when_error);
343 Cbuf_AddText(cmd_local, "\n");
345 Curl_Clear_forthismap();
356 static qbool CURL_OpenLibrary (void)
358 const char* dllnames [] =
363 #elif defined(MACOSX)
364 "libcurl.4.dylib", // Mac OS X Notyetreleased
365 "libcurl.3.dylib", // Mac OS X Tiger
366 "libcurl.2.dylib", // Mac OS X Panther
370 "libcurl.so", // FreeBSD
380 return Sys_LoadDependency (dllnames, &curl_dll, curlfuncs);
391 static void CURL_CloseLibrary (void)
393 Sys_FreeLibrary (&curl_dll);
397 static CURLM *curlm = NULL;
398 static double bytes_received = 0; // used for bandwidth throttling
399 static double bytes_sent = 0; // used for bandwidth throttling
400 static double curltime = 0;
406 fwrite-compatible function that writes the data to a file. libcurl can call
410 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
412 fs_offset_t ret = -1;
413 size_t bytes = size * nmemb;
414 downloadinfo *di = (downloadinfo *) vdi;
418 if(di->bytes_received + bytes <= di->buffersize)
420 memcpy(di->buffer + di->bytes_received, data, bytes);
423 // otherwise: buffer overrun, ret stays -1
428 ret = FS_Write(di->stream, data, bytes);
431 di->bytes_received += bytes;
434 // Why not ret / nmemb?
435 // Because CURLOPT_WRITEFUNCTION docs say to return the number of bytes.
436 // Yes, this is incompatible to fwrite(2).
441 CURL_DOWNLOAD_SUCCESS = 0,
442 CURL_DOWNLOAD_FAILED,
443 CURL_DOWNLOAD_ABORTED,
444 CURL_DOWNLOAD_SERVERERROR
448 static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
450 downloadinfo *di = (downloadinfo *) cbdata;
453 case CURLCBSTATUS_OK:
454 Con_DPrintf("Download of %s: OK\n", di->filename);
456 case CURLCBSTATUS_FAILED:
457 Con_DPrintf("Download of %s: FAILED\n", di->filename);
459 case CURLCBSTATUS_ABORTED:
460 Con_DPrintf("Download of %s: ABORTED\n", di->filename);
462 case CURLCBSTATUS_SERVERERROR:
463 Con_DPrintf("Download of %s: (unknown server error)\n", di->filename);
465 case CURLCBSTATUS_UNKNOWN:
466 Con_DPrintf("Download of %s: (unknown client error)\n", di->filename);
469 Con_DPrintf("Download of %s: %d\n", di->filename, status);
474 static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
476 curl_default_callback(status, length_received, buffer, cbdata);
479 static unsigned char *decode_image(downloadinfo *di, const char *content_type)
481 unsigned char *pixels = NULL;
482 fs_offset_t filesize = 0;
483 unsigned char *data = FS_LoadFile(di->filename, tempmempool, true, &filesize);
487 if(!strcmp(content_type, "image/jpeg"))
488 pixels = JPEG_LoadImage_BGRA(data, filesize, &mip);
489 else if(!strcmp(content_type, "image/png"))
490 pixels = PNG_LoadImage_BGRA(data, filesize, &mip);
491 else if(filesize >= 7 && !strncmp((char *) data, "\xFF\xD8", 7))
492 pixels = JPEG_LoadImage_BGRA(data, filesize, &mip);
493 else if(filesize >= 7 && !strncmp((char *) data, "\x89PNG\x0D\x0A\x1A\x0A", 7))
494 pixels = PNG_LoadImage_BGRA(data, filesize, &mip);
496 Con_Printf("Did not detect content type: %s\n", content_type);
499 // do we call Image_MakeLinearColorsFromsRGB or not?
507 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
508 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
509 code from libcurl, or 0, if another error has occurred.
512 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);
513 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error, const char *content_type_)
515 char content_type[64];
521 case CURL_DOWNLOAD_SUCCESS:
523 di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
525 case CURL_DOWNLOAD_FAILED:
526 di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
528 case CURL_DOWNLOAD_ABORTED:
529 di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
531 case CURL_DOWNLOAD_SERVERERROR:
532 // reopen to enforce it to have zero bytes again
535 FS_Close(di->stream);
536 di->stream = FS_OpenRealFile(di->filename, "wb", false);
540 di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
544 di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
548 strlcpy(content_type, content_type_, sizeof(content_type));
554 qcurl_multi_remove_handle(curlm, di->curle);
555 qcurl_easy_cleanup(di->curle);
557 qcurl_slist_free_all(di->slist);
560 if(!di->callback && ok && !di->bytes_received)
562 Con_Printf("ERROR: empty file\n");
567 FS_Close(di->stream);
569 #define CLEAR_AND_RETRY() \
572 di->stream = FS_OpenRealFile(di->filename, "wb", false); \
573 FS_Close(di->stream); \
574 if(di->startpos && !di->callback) \
576 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); \
577 di->forthismap = false; \
582 if(ok && di->loadtype == LOADTYPE_PAK)
584 ok = FS_AddPack(di->filename, NULL, true);
588 else if(ok && di->loadtype == LOADTYPE_CACHEPIC)
591 unsigned char *pixels = NULL;
595 if(!strncmp(p, "dlcache/", 8))
599 pixels = decode_image(di, content_type);
601 Draw_NewPic(p, image_width, image_height, pixels, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_CLAMP);
605 else if(ok && di->loadtype == LOADTYPE_SKINFRAME)
608 unsigned char *pixels = NULL;
612 if(!strncmp(p, "dlcache/", 8))
616 pixels = decode_image(di, content_type);
618 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?
623 List_Delete(&di->list);
629 ++numdownloads_success;
640 Returns a "cleaned up" URL for display (to strip login data)
643 static const char *CleanURL(const char *url, char *urlbuf, size_t urlbuflength)
645 const char *p, *q, *r;
647 // if URL is of form anything://foo-without-slash@rest, replace by anything://rest
648 p = strstr(url, "://");
651 q = strchr(p + 3, '@');
654 r = strchr(p + 3, '/');
657 dpsnprintf(urlbuf, urlbuflength, "%.*s%s", (int)(p - url + 3), url, q + 1);
668 CheckPendingDownloads
670 checks if there are free download slots to start new downloads in.
671 To not start too many downloads at once, only one download is added at a time,
672 up to a maximum number of cl_curl_maxdownloads are running.
675 static void CheckPendingDownloads(void)
682 if(numdownloads < cl_curl_maxdownloads.integer)
685 List_For_Each_Entry(di, &downloads, downloadinfo, list)
691 Con_Printf("Downloading %s -> %s", CleanURL(di->url, urlbuf, sizeof(urlbuf)), di->filename);
693 di->stream = FS_OpenRealFile(di->filename, "ab", false);
696 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
697 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK, NULL);
700 FS_Seek(di->stream, 0, SEEK_END);
701 di->startpos = FS_Tell(di->stream);
704 Con_Printf(", resuming from position %ld", (long) di->startpos);
709 Con_DPrintf("Downloading %s -> memory\n", CleanURL(di->url, urlbuf, sizeof(urlbuf)));
713 di->curle = qcurl_easy_init();
715 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
716 if(cl_curl_useragent.integer)
719 #ifdef HTTP_USER_AGENT
726 if(*cl_curl_useragent_append.string)
727 ua = va(vabuf, sizeof(vabuf), "%s%s%s",
729 (ua[0] && ua[strlen(ua)-1] != ' ')
732 cl_curl_useragent_append.string);
733 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, ua);
736 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, "");
737 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
738 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
739 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
740 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
741 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
742 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
743 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
744 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
745 qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP);
746 if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK)
748 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");
749 //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0);
751 if(di->post_content_type)
753 qcurl_easy_setopt(di->curle, CURLOPT_POST, 1);
754 qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDS, di->postbuf);
755 qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDSIZE, di->postbufsize);
756 di->slist = qcurl_slist_append(di->slist, va(vabuf, sizeof(vabuf), "Content-Type: %s", di->post_content_type));
759 // parse extra headers into slist
760 // \n separated list!
761 h = di->extraheaders;
764 const char *hh = strchr(h, '\n');
767 char *buf = (char *) Mem_Alloc(tempmempool, hh - h + 1);
768 memcpy(buf, h, hh - h);
770 di->slist = qcurl_slist_append(di->slist, buf);
775 di->slist = qcurl_slist_append(di->slist, h);
780 qcurl_easy_setopt(di->curle, CURLOPT_HTTPHEADER, di->slist);
782 qcurl_multi_add_handle(curlm, di->curle);
785 if(numdownloads >= cl_curl_maxdownloads.integer)
796 this function MUST be called before using anything else in this file.
797 On Win32, this must be called AFTER WSAStartup has been done!
805 if (Thread_HasThreads()) curl_mutex = Thread_CreateMutex();
806 qcurl_global_init(CURL_GLOBAL_NOTHING);
807 curlm = qcurl_multi_init();
814 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
817 void Curl_ClearRequirements(void);
818 void Curl_Shutdown(void)
822 Curl_ClearRequirements();
824 if (curl_mutex) Thread_DestroyMutex(curl_mutex);
833 Finds the internal information block for a download given by file name.
836 static downloadinfo *Curl_Find(const char *filename)
841 List_For_Each_Entry(di, &downloads, downloadinfo, list)
842 if(!strcasecmp(di->filename, filename))
847 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
849 downloadinfo *di, *ndi;
852 List_For_Each_Entry_Safe(di, ndi, &downloads, downloadinfo, list)
854 if(di->callback == callback && di->callback_data == cbdata)
856 di->callback = curl_quiet_callback; // do NOT call the callback
857 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL);
866 Starts a download of a given URL to the file name portion of this URL (or name
867 if given) in the "dlcache/" folder.
870 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)
873 if(loadtype != LOADTYPE_NONE)
874 Host_Error("Curl_Begin: loadtype and buffer are both set");
876 if(!curl_dll || !cl_curl_enabled.integer)
888 // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server
889 p = strchr(URL, ':');
892 if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4))
894 char addressstring[128];
896 InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring));
897 q = strchr(addressstring, ':');
899 q = addressstring + strlen(addressstring);
902 dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3);
908 // Note: This extraction of the file name portion is NOT entirely correct.
910 // It does the following:
912 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
913 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
914 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
916 // However, I'd like to keep this "buggy" behavior so that PHP script
917 // authors can write download scripts without having to enable
918 // AcceptPathInfo on Apache. They just have to ensure that their script
919 // can be called with such a "fake" path name like
920 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
922 // By the way, such PHP scripts should either send the file or a
923 // "Location:" redirect; PHP code example:
925 // header("Location: http://www.example.com/");
927 // By the way, this will set User-Agent to something like
928 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
929 // dp://serverhost:serverport/ so you can filter on this; an example
930 // httpd log file line might be:
932 // 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"
934 if (curl_mutex) Thread_LockMutex(curl_mutex);
939 name = CleanURL(URL, urlbuf, sizeof(urlbuf));
945 name = CleanURL(URL, urlbuf, sizeof(urlbuf));
946 p = strrchr(name, '/');
947 p = p ? (p+1) : name;
949 length = q ? (size_t)(q - p) : strlen(p);
950 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
954 dpsnprintf(fn, sizeof(fn), "dlcache/%s", name);
957 name = fn; // make it point back
959 // already downloading the file?
961 downloadinfo *existingdownloadinfo = Curl_Find(fn);
962 if(existingdownloadinfo)
964 Con_Printf("Can't download %s, already getting it from %s!\n", fn, CleanURL(existingdownloadinfo->url, urlbuf, sizeof(urlbuf)));
966 // however, if it was not for this map yet...
967 if(forthismap && !existingdownloadinfo->forthismap)
969 existingdownloadinfo->forthismap = true;
970 // this "fakes" a download attempt so the client will wait for
971 // the download to finish and then reconnect
972 ++numdownloads_added;
975 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
980 if(FS_FileExists(fn))
982 if(loadtype == LOADTYPE_PAK)
984 qbool already_loaded;
985 if(FS_AddPack(fn, &already_loaded, true))
987 Con_DPrintf("%s already exists, not downloading!\n", fn);
989 Con_DPrintf("(pak was already loaded)\n");
994 ++numdownloads_added;
995 ++numdownloads_success;
999 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1004 qfile_t *f = FS_OpenRealFile(fn, "rb", false);
1008 FS_Read(f, b, sizeof(b)); // no "-1", I will use memcmp
1010 if(memcmp(b, "PK\x03\x04", 4) && memcmp(b, "PACK", 4))
1012 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
1014 f = FS_OpenRealFile(fn, "wb", false);
1028 // never resume these
1029 qfile_t *f = FS_OpenRealFile(fn, "wb", false);
1036 // if we get here, we actually want to download... so first verify the
1037 // URL scheme (so one can't read local files using file://)
1038 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
1040 Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
1041 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1046 ++numdownloads_added;
1047 di = (downloadinfo *) Z_Malloc(sizeof(*di));
1048 strlcpy(di->filename, name, sizeof(di->filename));
1049 strlcpy(di->url, URL, sizeof(di->url));
1050 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
1051 di->forthismap = forthismap;
1055 di->started = false;
1056 di->loadtype = loadtype;
1057 di->maxspeed = maxspeed;
1058 di->bytes_received = 0;
1059 di->bytes_received_curl = 0;
1060 di->bytes_sent_curl = 0;
1061 di->extraheaders = extraheaders;
1063 di->buffersize = bufsize;
1064 if(callback == NULL)
1066 di->callback = curl_default_callback;
1067 di->callback_data = di;
1071 di->callback = callback;
1072 di->callback_data = cbdata;
1075 if(post_content_type)
1077 di->post_content_type = post_content_type;
1078 di->postbuf = postbuf;
1079 di->postbufsize = postbufsize;
1083 di->post_content_type = NULL;
1085 di->postbufsize = 0;
1088 List_Add(&di->list, &downloads);
1091 Thread_UnlockMutex(curl_mutex);
1097 qbool Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, int loadtype, qbool forthismap)
1099 return Curl_Begin(URL, NULL, maxspeed, name, loadtype, forthismap, NULL, NULL, 0, NULL, 0, NULL, NULL);
1101 qbool Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
1103 return Curl_Begin(URL, NULL, maxspeed, NULL, false, false, NULL, NULL, 0, buf, bufsize, callback, cbdata);
1105 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)
1107 return Curl_Begin(URL, extraheaders, maxspeed, NULL, false, false, post_content_type, postbuf, postbufsize, buf, bufsize, callback, cbdata);
1111 ====================
1114 call this regularily as this will always download as much as possible without
1116 ====================
1118 void Curl_Frame(void)
1125 if(!cl_curl_enabled.integer)
1131 if (curl_mutex) Thread_LockMutex(curl_mutex);
1133 Curl_CheckCommandWhenDone();
1135 if(List_Is_Empty(&downloads))
1137 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1141 if(host.realtime < curltime) // throttle
1143 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1153 mc = qcurl_multi_perform(curlm, &remaining);
1155 while(mc == CURLM_CALL_MULTI_PERFORM);
1157 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1162 qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_UPLOAD, &b);
1163 bytes_sent += (b - di->bytes_sent_curl);
1164 di->bytes_sent_curl = b;
1165 qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_DOWNLOAD, &b);
1166 bytes_sent += (b - di->bytes_received_curl);
1167 di->bytes_received_curl = b;
1173 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
1176 if(msg->msg == CURLMSG_DONE)
1178 const char *ct = NULL;
1179 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
1181 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
1182 result = msg->data.result;
1185 failed = CURL_DOWNLOAD_FAILED;
1190 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
1193 case 4: // e.g. 404?
1194 case 5: // e.g. 500?
1195 failed = CURL_DOWNLOAD_SERVERERROR;
1196 result = (CURLcode) code;
1199 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_CONTENT_TYPE, &ct);
1202 Curl_EndDownload(di, failed, result, ct);
1207 CheckPendingDownloads();
1209 // when will we curl the next time?
1210 // we will wait a bit to ensure our download rate is kept.
1211 // we now know that realtime >= curltime... so set up a new curltime
1213 // use the slowest allowing download to derive the maxspeed... this CAN
1214 // be done better, but maybe later
1215 maxspeed = cl_curl_maxspeed.value;
1216 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1217 if(di->maxspeed > 0)
1218 if(di->maxspeed < maxspeed || maxspeed <= 0)
1219 maxspeed = di->maxspeed;
1223 double bytes = bytes_sent + bytes_received; // maybe smoothen a bit?
1224 curltime = host.realtime + bytes / (maxspeed * 1024.0);
1229 curltime = host.realtime;
1231 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1235 ====================
1238 Stops ALL downloads.
1239 ====================
1241 void Curl_CancelAll(void)
1246 if (curl_mutex) Thread_LockMutex(curl_mutex);
1248 while(!List_Is_Empty(&downloads))
1250 Curl_EndDownload(List_First_Entry(&downloads, downloadinfo, list), CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL);
1251 // INVARIANT: downloads will point to the next download after that!
1254 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1258 ====================
1261 returns true if there is a download running.
1262 ====================
1264 qbool Curl_Running(void)
1269 return !List_Is_Empty(&downloads);
1273 ====================
1274 Curl_GetDownloadAmount
1276 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
1277 for the given download.
1278 ====================
1280 static double Curl_GetDownloadAmount(downloadinfo *di)
1287 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1289 return (di->startpos + di->bytes_received) / (di->startpos + length);
1298 ====================
1299 Curl_GetDownloadSpeed
1301 returns the speed of the given download in bytes per second
1302 ====================
1304 static double Curl_GetDownloadSpeed(downloadinfo *di)
1311 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1319 ====================
1322 prints the download list
1323 ====================
1325 // TODO rewrite using Curl_GetDownloadInfo?
1326 static void Curl_Info_f(cmd_state_t *cmd)
1334 if (curl_mutex) Thread_LockMutex(curl_mutex);
1335 Con_Print("Currently running downloads:\n");
1336 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1338 double speed, percent;
1339 Con_Printf(" %s -> %s ", CleanURL(di->url, urlbuf, sizeof(urlbuf)), di->filename);
1340 percent = 100.0 * Curl_GetDownloadAmount(di);
1341 speed = Curl_GetDownloadSpeed(di);
1343 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1345 Con_Print("(queued)\n");
1347 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1351 Con_Print("No downloads running.\n");
1356 ====================
1359 implements the "curl" console command
1363 curl --cancel filename
1368 curl [--pak] [--forthismap] [--for filename filename...] url
1369 --pak: after downloading, load the package into the virtual file system
1370 --for filename...: only download of at least one of the named files is missing
1371 --forthismap: don't reconnect on failure
1373 curl --clear_autodownload
1374 clears the download success/failure counters
1376 curl --finish_autodownload
1377 if at least one download has been started, disconnect and drop to the menu
1378 once the last download completes successfully, reconnect to the current server
1379 ====================
1381 static void Curl_Curl_f(cmd_state_t *cmd)
1383 double maxspeed = 0;
1386 int loadtype = LOADTYPE_NONE;
1387 qbool forthismap = false;
1389 const char *name = 0;
1393 Con_Print("libcurl DLL not found, this command is inactive.\n");
1397 if(!cl_curl_enabled.integer)
1399 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1403 if(Cmd_Argc(cmd) < 2)
1405 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1409 url = Cmd_Argv(cmd, Cmd_Argc(cmd) - 1);
1410 end = Cmd_Argc(cmd);
1412 for(i = 1; i != end; ++i)
1414 const char *a = Cmd_Argv(cmd, i);
1415 if(!strcmp(a, "--info"))
1420 else if(!strcmp(a, "--cancel"))
1422 if(i == end - 1) // last argument
1426 downloadinfo *di = Curl_Find(url);
1428 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL);
1430 Con_Print("download not found\n");
1434 else if(!strcmp(a, "--pak"))
1436 loadtype = LOADTYPE_PAK;
1438 else if(!strcmp(a, "--cachepic"))
1440 loadtype = LOADTYPE_CACHEPIC;
1442 else if(!strcmp(a, "--skinframe"))
1444 loadtype = LOADTYPE_SKINFRAME;
1446 else if(!strcmp(a, "--for")) // must be last option
1448 for(i = i + 1; i != end - 1; ++i)
1450 if(!FS_FileExists(Cmd_Argv(cmd, i)))
1451 goto needthefile; // why can't I have a "double break"?
1453 // if we get here, we have all the files...
1456 else if(!strcmp(a, "--forthismap"))
1460 else if(!strcmp(a, "--as"))
1465 name = Cmd_Argv(cmd, i);
1468 else if(!strcmp(a, "--clear_autodownload"))
1470 // mark all running downloads as "not for this map", so if they
1471 // fail, it does not matter
1472 Curl_Clear_forthismap();
1475 else if(!strcmp(a, "--finish_autodownload"))
1477 if(numdownloads_added)
1479 char donecommand[256];
1482 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1484 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1485 Curl_CommandWhenDone(donecommand);
1489 Curl_CheckCommandWhenDone();
1492 Curl_Register_predownload();
1497 else if(!strncmp(a, "--maxspeed=", 11))
1499 maxspeed = atof(a + 11);
1503 Con_Printf("curl: invalid option %s\n", a);
1504 // but we ignore the option
1509 Curl_Begin_ToFile(url, maxspeed, name, loadtype, forthismap);
1513 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1515 Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1519 void Curl_CurlCat_f(cmd_state_t *cmd)
1522 const char *url = Cmd_Argv(cmd, 1);
1523 buf = Z_Malloc(16384);
1524 Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1529 ====================
1532 loads the commands and cvars this library uses
1533 ====================
1535 void Curl_Init_Commands(void)
1537 Cvar_RegisterVariable (&cl_curl_enabled);
1538 Cvar_RegisterVariable (&cl_curl_maxdownloads);
1539 Cvar_RegisterVariable (&cl_curl_maxspeed);
1540 Cvar_RegisterVariable (&sv_curl_defaulturl);
1541 Cvar_RegisterVariable (&sv_curl_serverpackages);
1542 Cvar_RegisterVariable (&sv_curl_maxspeed);
1543 Cvar_RegisterVariable (&cl_curl_useragent);
1544 Cvar_RegisterVariable (&cl_curl_useragent_append);
1545 Cmd_AddCommand(CF_CLIENT | CF_CLIENT_FROM_SERVER, "curl", Curl_Curl_f, "download data from an URL and add to search path");
1546 //Cmd_AddCommand(cmd_local, "curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1550 ====================
1551 Curl_GetDownloadInfo
1553 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1554 The number of elements in the array is returned in int *nDownloads.
1555 const char **additional_info may be set to a string of additional user
1556 information, or to NULL if no such display shall occur. The returned
1557 array must be freed later using Z_Free.
1558 ====================
1560 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info, char *addinfo, size_t addinfolength)
1564 Curl_downloadinfo_t *downinfo;
1570 *additional_info = NULL;
1574 if (curl_mutex) Thread_LockMutex(curl_mutex);
1577 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1580 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1582 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1584 // do not show infobars for background downloads
1585 if(developer.integer <= 0)
1588 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1591 downinfo[i].progress = Curl_GetDownloadAmount(di);
1592 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1593 downinfo[i].queued = false;
1597 downinfo[i].queued = true;
1604 // TODO: can I clear command_when_done as soon as the first download fails?
1605 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1607 if(!strncmp(command_when_done, "connect ", 8))
1608 dpsnprintf(addinfo, addinfolength, "(will join %s when done)", command_when_done + 8);
1609 else if(!strcmp(command_when_done, "cl_begindownloads"))
1610 dpsnprintf(addinfo, addinfolength, "(will enter the game when done)");
1612 dpsnprintf(addinfo, addinfolength, "(will do '%s' when done)", command_when_done);
1613 *additional_info = addinfo;
1616 *additional_info = NULL;
1620 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1626 ====================
1629 finds the URL where to find a given package.
1631 For this, it reads a file "curl_urls.txt" of the following format:
1634 revdm*.pk3 http://revdm/downloads/are/here/
1635 * http://any/other/stuff/is/here/
1637 The URLs should end in /. If not, downloads will still work, but the cached files
1638 can't be just put into the data directory with the same download configuration
1639 (you might want to do this if you want to tag downloaded files from your
1640 server, but you should not). "-" means "don't download".
1642 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1645 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1646 this file for obvious reasons.
1647 ====================
1649 static const char *Curl_FindPackURL(const char *filename)
1651 static char foundurl[1024]; // invoked only by server
1652 fs_offset_t filesize;
1653 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1656 // read lines of format "pattern url"
1658 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1671 if(pattern && url && patternend)
1677 if(matchpattern(filename, pattern, true))
1679 strlcpy(foundurl, url, sizeof(foundurl));
1691 if(pattern && !patternend)
1693 else if(url && !urlend)
1699 else if(pattern && patternend && !url)
1708 return sv_curl_defaulturl.string;
1711 typedef struct requirement_s
1713 struct requirement_s *next;
1714 char filename[MAX_OSPATH];
1717 static requirement *requirements = NULL;
1721 ====================
1724 Adds the given file to the list of requirements.
1725 ====================
1727 void Curl_RequireFile(const char *filename)
1729 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1730 req->next = requirements;
1731 strlcpy(req->filename, filename, sizeof(req->filename));
1736 ====================
1737 Curl_ClearRequirements
1739 Clears the list of required files for playing on the current map.
1740 This should be called at every map change.
1741 ====================
1743 void Curl_ClearRequirements(void)
1747 requirement *req = requirements;
1748 requirements = requirements->next;
1754 ====================
1755 Curl_SendRequirements
1757 Makes the current host_clients download all files he needs.
1758 This is done by sending him the following console commands:
1760 curl --clear_autodownload
1761 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1762 curl --finish_autodownload
1763 ====================
1765 static qbool Curl_SendRequirement(const char *filename, qbool foundone, char *sendbuffer, size_t sendbuffer_len)
1768 const char *thispack = FS_WhichPack(filename);
1769 const char *packurl;
1771 if(!thispack || !*thispack)
1774 p = strrchr(thispack, '/');
1778 packurl = Curl_FindPackURL(thispack);
1780 if(packurl && *packurl && strcmp(packurl, "-"))
1783 strlcat(sendbuffer, "curl --clear_autodownload\n", sendbuffer_len);
1785 strlcat(sendbuffer, "curl --pak --forthismap --as ", sendbuffer_len);
1786 strlcat(sendbuffer, thispack, sendbuffer_len);
1787 if(sv_curl_maxspeed.value > 0)
1788 dpsnprintf(sendbuffer + strlen(sendbuffer), sendbuffer_len - strlen(sendbuffer), " --maxspeed=%.1f", sv_curl_maxspeed.value);
1789 strlcat(sendbuffer, " --for ", sendbuffer_len);
1790 strlcat(sendbuffer, filename, sendbuffer_len);
1791 strlcat(sendbuffer, " ", sendbuffer_len);
1792 strlcat(sendbuffer, packurl, sendbuffer_len);
1793 strlcat(sendbuffer, thispack, sendbuffer_len);
1794 strlcat(sendbuffer, "\n", sendbuffer_len);
1801 void Curl_SendRequirements(void)
1803 // for each requirement, find the pack name
1804 char sendbuffer[4096] = "";
1806 qbool foundone = false;
1809 for(req = requirements; req; req = req->next)
1810 foundone = Curl_SendRequirement(req->filename, foundone, sendbuffer, sizeof(sendbuffer)) || foundone;
1812 p = sv_curl_serverpackages.string;
1813 while(COM_ParseToken_Simple(&p, false, false, true))
1814 foundone = Curl_SendRequirement(com_token, foundone, sendbuffer, sizeof(sendbuffer)) || foundone;
1817 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1819 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1820 SV_ClientCommands("%s", sendbuffer);
1822 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");