10 static cvar_t curl_enabled = {CF_SHARED | CF_ARCHIVE, "curl_enabled","1", "whether libcurl may be used to GET files or POST data"};
11 static cvar_t curl_maxdownloads = {CF_SHARED | CF_ARCHIVE, "curl_maxdownloads","3", "maximum number of concurrent HTTP/FTP downloads"};
12 static cvar_t curl_maxspeed = {CF_SHARED | CF_ARCHIVE, "curl_maxspeed","0", "maximum download speed (KiB/s)"};
13 static cvar_t curl_useragent = {CF_SHARED, "curl_useragent","1", "send the User-Agent string (note: turning this off may break stuff)"};
14 static cvar_t curl_useragent_append = {CF_SHARED, "curl_useragent_append","", "a string to append to the User-Agent string (useful for name and version number of your mod)"};
16 static cvar_t sv_curl_defaulturl = {CF_SERVER, "sv_curl_defaulturl","", "default autodownload source URL"};
17 static cvar_t sv_curl_serverpackages = {CF_SERVER, "sv_curl_serverpackages","", "list of required files for the clients, separated by spaces"};
18 static cvar_t sv_curl_maxspeed = {CF_SERVER, "sv_curl_maxspeed","0", "maximum download speed for clients downloading from sv_curl_defaulturl (KiB/s)"};
20 static cvar_t developer_curl = {CF_SHARED, "developer_curl","0", "whether verbose libcurl output should be printed to stderr"};
23 =================================================================
25 Minimal set of definitions from libcurl
27 WARNING: for a matter of simplicity, several pointer types are
28 casted to "void*", and most enumerated values are not included
30 =================================================================
33 typedef struct CURL_s CURL;
34 typedef struct CURLM_s CURLM;
35 typedef struct curl_slist curl_slist;
43 CURLM_CALL_MULTI_PERFORM=-1, /* please call curl_multi_perform() soon */
47 #define CURL_GLOBAL_NOTHING 0
48 #define CURL_GLOBAL_SSL 1
49 #define CURL_GLOBAL_WIN32 2
50 #define CURLOPTTYPE_LONG 0
51 #define CURLOPTTYPE_OBJECTPOINT 10000
52 #define CURLOPTTYPE_FUNCTIONPOINT 20000
53 #define CURLOPTTYPE_OFF_T 30000
54 #define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number
57 CINIT(WRITEDATA, OBJECTPOINT, 1),
58 CINIT(URL, OBJECTPOINT, 2),
59 CINIT(ERRORBUFFER, OBJECTPOINT, 10),
60 CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11),
61 CINIT(POSTFIELDS, OBJECTPOINT, 15),
62 CINIT(REFERER, OBJECTPOINT, 16),
63 CINIT(USERAGENT, OBJECTPOINT, 18),
64 CINIT(LOW_SPEED_LIMIT, LONG , 19),
65 CINIT(LOW_SPEED_TIME, LONG, 20),
66 CINIT(RESUME_FROM, LONG, 21),
67 CINIT(HTTPHEADER, OBJECTPOINT, 23),
68 CINIT(VERBOSE, LONG, 41),
69 CINIT(POST, LONG, 47), /* HTTP POST method */
70 CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */
71 CINIT(POSTFIELDSIZE, LONG, 60),
72 CINIT(PRIVATE, OBJECTPOINT, 103),
73 CINIT(PROTOCOLS, LONG, 181),
74 CINIT(REDIR_PROTOCOLS, LONG, 182)
77 #define CURLPROTO_HTTP (1<<0)
78 #define CURLPROTO_HTTPS (1<<1)
79 #define CURLPROTO_FTP (1<<2)
83 CURLINFO_HEADER_IN, /* 1 */
84 CURLINFO_HEADER_OUT, /* 2 */
85 CURLINFO_DATA_IN, /* 3 */
86 CURLINFO_DATA_OUT, /* 4 */
87 CURLINFO_SSL_DATA_IN, /* 5 */
88 CURLINFO_SSL_DATA_OUT, /* 6 */
92 #define CURLINFO_STRING 0x100000
93 #define CURLINFO_LONG 0x200000
94 #define CURLINFO_DOUBLE 0x300000
95 #define CURLINFO_SLIST 0x400000
96 #define CURLINFO_MASK 0x0fffff
97 #define CURLINFO_TYPEMASK 0xf00000
100 CURLINFO_NONE, /* first, never use this */
101 CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1,
102 CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2,
103 CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3,
104 CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4,
105 CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5,
106 CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6,
107 CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7,
108 CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8,
109 CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9,
110 CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10,
111 CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11,
112 CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12,
113 CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13,
114 CURLINFO_FILETIME = CURLINFO_LONG + 14,
115 CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15,
116 CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16,
117 CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17,
118 CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18,
119 CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19,
120 CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20,
121 CURLINFO_PRIVATE = CURLINFO_STRING + 21,
122 CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22,
123 CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23,
124 CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24,
125 CURLINFO_OS_ERRNO = CURLINFO_LONG + 25,
126 CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26,
127 CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27
133 CURLMSG_NONE, /* first, not used */
134 CURLMSG_DONE, /* This easy handle has completed. 'result' contains
135 the CURLcode of the transfer */
141 CURLMSG msg; /* what this message means */
142 CURL *easy_handle; /* the handle it concerns */
145 void *whatever; /* message-specific data */
146 CURLcode result; /* return code for transfer */
152 static void (*qcurl_global_init) (long flags);
153 static void (*qcurl_global_cleanup) (void);
155 static CURL * (*qcurl_easy_init) (void);
156 static void (*qcurl_easy_cleanup) (CURL *handle);
157 static CURLcode (*qcurl_easy_setopt) (CURL *handle, CURLoption option, ...);
158 static CURLcode (*qcurl_easy_getinfo) (CURL *handle, CURLINFO info, ...);
159 static const char * (*qcurl_easy_strerror) (CURLcode);
161 static CURLM * (*qcurl_multi_init) (void);
162 static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles);
163 static CURLMcode (*qcurl_multi_wait) (CURLM *multi_handle, void*, unsigned int extra_nfds, int timeout_ms, int *ret);
164 static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle);
165 static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle);
166 static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue);
167 static void (*qcurl_multi_cleanup) (CURLM *);
168 static const char * (*qcurl_multi_strerror) (CURLcode);
169 static curl_slist * (*qcurl_slist_append) (curl_slist *list, const char *string);
170 static void (*qcurl_slist_free_all) (curl_slist *list);
172 static dllfunction_t curlfuncs[] =
174 {"curl_global_init", (void **) &qcurl_global_init},
175 {"curl_global_cleanup", (void **) &qcurl_global_cleanup},
176 {"curl_easy_init", (void **) &qcurl_easy_init},
177 {"curl_easy_cleanup", (void **) &qcurl_easy_cleanup},
178 {"curl_easy_setopt", (void **) &qcurl_easy_setopt},
179 {"curl_easy_strerror", (void **) &qcurl_easy_strerror},
180 {"curl_easy_getinfo", (void **) &qcurl_easy_getinfo},
181 {"curl_multi_init", (void **) &qcurl_multi_init},
182 {"curl_multi_perform", (void **) &qcurl_multi_perform},
183 {"curl_multi_wait", (void **) &qcurl_multi_wait},
184 {"curl_multi_add_handle", (void **) &qcurl_multi_add_handle},
185 {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle},
186 {"curl_multi_info_read", (void **) &qcurl_multi_info_read},
187 {"curl_multi_cleanup", (void **) &qcurl_multi_cleanup},
188 {"curl_multi_strerror", (void **) &qcurl_multi_strerror},
189 {"curl_slist_append", (void **) &qcurl_slist_append},
190 {"curl_slist_free_all", (void **) &qcurl_slist_free_all},
194 // Handle for CURL DLL
195 static dllhandle_t curl_dll = NULL;
196 // will be checked at many places to find out if qcurl calls are allowed
198 #define LOADTYPE_NONE 0
199 #define LOADTYPE_PAK 1
200 #define LOADTYPE_CACHEPIC 2
201 #define LOADTYPE_SKINFRAME 3
203 void *curl_mutex = NULL;
205 typedef struct downloadinfo_s
207 char filename[MAX_OSPATH];
211 fs_offset_t startpos;
215 size_t bytes_received; // for buffer
216 double bytes_received_curl; // for throttling
217 double bytes_sent_curl; // for throttling
221 curl_slist *slist; // http headers
223 unsigned char *buffer;
225 curl_callback_t callback;
228 const unsigned char *postbuf;
230 const char *post_content_type;
231 const char *extraheaders;
234 LIST_HEAD(downloads);
235 static int numdownloads = 0;
237 static qbool noclear = false;
239 static int numdownloads_fail = 0;
240 static int numdownloads_success = 0;
241 static int numdownloads_added = 0;
242 static char command_when_done[256] = "";
243 static char command_when_error[256] = "";
249 Sets the command which is to be executed when the last download completes AND
250 all downloads since last server connect ended with a successful status.
251 Setting the command to NULL clears it.
254 static void Curl_CommandWhenDone(const char *cmd)
259 dp_strlcpy(command_when_done, cmd, sizeof(command_when_done));
261 *command_when_done = 0;
266 Do not use yet. Not complete.
267 Problem: what counts as an error?
270 static void Curl_CommandWhenError(const char *cmd)
275 dp_strlcpy(command_when_error, cmd, sizeof(command_when_error));
277 *command_when_error = 0;
282 Curl_Clear_forthismap
284 Clears the "will disconnect on failure" flags.
287 void Curl_Clear_forthismap(void)
292 if (curl_mutex) Thread_LockMutex(curl_mutex);
293 List_For_Each_Entry(di, &downloads, downloadinfo, list)
294 di->forthismap = false;
295 Curl_CommandWhenError(NULL);
296 Curl_CommandWhenDone(NULL);
297 numdownloads_fail = 0;
298 numdownloads_success = 0;
299 numdownloads_added = 0;
300 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
307 Returns true if a download needed for the current game is running.
310 qbool Curl_Have_forthismap(void)
312 return numdownloads_added != 0;
315 void Curl_Register_predownload(void)
317 if (curl_mutex) Thread_LockMutex(curl_mutex);
318 Curl_CommandWhenDone("cl_begindownloads");
319 Curl_CommandWhenError("cl_begindownloads");
320 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
325 Curl_CheckCommandWhenDone
327 Checks if a "done command" is to be executed.
328 All downloads finished, at least one success since connect, no single failure
329 -> execute the command.
331 static void Curl_CheckCommandWhenDone(void)
335 if(numdownloads_added && ((numdownloads_success + numdownloads_fail) == numdownloads_added))
337 if(numdownloads_fail == 0)
339 Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done);
340 Cbuf_AddText(cmd_local, "\n");
341 Cbuf_AddText(cmd_local, command_when_done);
342 Cbuf_AddText(cmd_local, "\n");
346 Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error);
347 Cbuf_AddText(cmd_local, "\n");
348 Cbuf_AddText(cmd_local, command_when_error);
349 Cbuf_AddText(cmd_local, "\n");
351 Curl_Clear_forthismap();
362 static qbool CURL_OpenLibrary (void)
364 const char* dllnames [] =
369 #elif defined(MACOSX)
370 "libcurl.4.dylib", // Mac OS X Notyetreleased
371 "libcurl.3.dylib", // Mac OS X Tiger
372 "libcurl.2.dylib", // Mac OS X Panther
376 "libcurl.so", // FreeBSD
386 return Sys_LoadDependency (dllnames, &curl_dll, curlfuncs);
397 static void CURL_CloseLibrary (void)
399 Sys_FreeLibrary (&curl_dll);
403 static CURLM *curlm = NULL;
404 static double bytes_received = 0; // used for bandwidth throttling
405 static double bytes_sent = 0; // used for bandwidth throttling
406 static double curltime = 0;
412 fwrite-compatible function that writes the data to a file. libcurl can call
416 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
418 fs_offset_t ret = -1;
419 size_t bytes = size * nmemb;
420 downloadinfo *di = (downloadinfo *) vdi;
424 if(di->bytes_received + bytes <= di->buffersize)
426 memcpy(di->buffer + di->bytes_received, data, bytes);
429 // otherwise: buffer overrun, ret stays -1
434 ret = FS_Write(di->stream, data, bytes);
437 di->bytes_received += bytes;
439 //Con_Printf("CURL_fwrite callback timestamp: %f bytes: %ld\n", host.realtime, ret);
442 // Why not ret / nmemb?
443 // Because CURLOPT_WRITEFUNCTION docs say to return the number of bytes.
444 // Yes, this is incompatible to fwrite(2).
449 CURL_DOWNLOAD_SUCCESS = 0,
450 CURL_DOWNLOAD_FAILED,
451 CURL_DOWNLOAD_ABORTED,
452 CURL_DOWNLOAD_SERVERERROR
456 static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
458 downloadinfo *di = (downloadinfo *) cbdata;
461 case CURLCBSTATUS_OK:
462 Con_DPrintf("Download of %s: OK\n", di->filename);
464 case CURLCBSTATUS_FAILED:
465 Con_DPrintf("Download of %s: FAILED\n", di->filename);
467 case CURLCBSTATUS_ABORTED:
468 Con_DPrintf("Download of %s: ABORTED\n", di->filename);
470 case CURLCBSTATUS_SERVERERROR:
471 Con_DPrintf("Download of %s: (unknown server error)\n", di->filename);
473 case CURLCBSTATUS_UNKNOWN:
474 Con_DPrintf("Download of %s: (unknown client error)\n", di->filename);
477 Con_DPrintf("Download of %s: %d\n", di->filename, status);
482 static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
484 curl_default_callback(status, length_received, buffer, cbdata);
487 static unsigned char *decode_image(downloadinfo *di, const char *content_type)
489 unsigned char *pixels = NULL;
490 fs_offset_t filesize = 0;
491 unsigned char *data = FS_LoadFile(di->filename, tempmempool, true, &filesize);
495 if(!strcmp(content_type, "image/jpeg"))
496 pixels = JPEG_LoadImage_BGRA(data, filesize, &mip);
497 else if(!strcmp(content_type, "image/png"))
498 pixels = PNG_LoadImage_BGRA(data, filesize, &mip);
499 else if(filesize >= 7 && !strncmp((char *) data, "\xFF\xD8", 7))
500 pixels = JPEG_LoadImage_BGRA(data, filesize, &mip);
501 else if(filesize >= 7 && !strncmp((char *) data, "\x89PNG\x0D\x0A\x1A\x0A", 7))
502 pixels = PNG_LoadImage_BGRA(data, filesize, &mip);
504 Con_Printf("Did not detect content type: %s\n", content_type);
507 // do we call Image_MakeLinearColorsFromsRGB or not?
515 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
516 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
517 code from libcurl, or 0, if another error has occurred.
520 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);
521 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error, const char *content_type_)
523 char content_type[64];
529 case CURL_DOWNLOAD_SUCCESS:
531 di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
533 case CURL_DOWNLOAD_FAILED:
534 di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
536 case CURL_DOWNLOAD_ABORTED:
537 di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
539 case CURL_DOWNLOAD_SERVERERROR:
540 // reopen to enforce it to have zero bytes again
543 FS_Close(di->stream);
544 di->stream = FS_OpenRealFile(di->filename, "wb", false);
548 di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
552 di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
556 dp_strlcpy(content_type, content_type_, sizeof(content_type));
562 qcurl_multi_remove_handle(curlm, di->curle);
563 qcurl_easy_cleanup(di->curle);
565 qcurl_slist_free_all(di->slist);
568 if(!di->callback && ok && !di->bytes_received)
570 Con_Printf("ERROR: empty file\n");
575 FS_Close(di->stream);
577 #define CLEAR_AND_RETRY() \
580 di->stream = FS_OpenRealFile(di->filename, "wb", false); \
581 FS_Close(di->stream); \
582 if(di->startpos && !di->callback) \
584 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); \
585 di->forthismap = false; \
590 if(ok && di->loadtype == LOADTYPE_PAK)
592 ok = FS_AddPack(di->filename, NULL, true, true);
596 else if(ok && di->loadtype == LOADTYPE_CACHEPIC)
599 unsigned char *pixels = NULL;
603 if(!strncmp(p, "dlcache/", 8))
607 pixels = decode_image(di, content_type);
609 Draw_NewPic(p, image_width, image_height, pixels, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_CLAMP);
613 else if(ok && di->loadtype == LOADTYPE_SKINFRAME)
616 unsigned char *pixels = NULL;
620 if(!strncmp(p, "dlcache/", 8))
624 pixels = decode_image(di, content_type);
626 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?
631 List_Delete(&di->list);
637 ++numdownloads_success;
648 Returns a "cleaned up" URL for display (to strip login data)
651 static const char *CleanURL(const char *url, char *urlbuf, size_t urlbuflength)
653 const char *p, *q, *r;
655 // if URL is of form anything://foo-without-slash@rest, replace by anything://rest
656 p = strstr(url, "://");
659 q = strchr(p + 3, '@');
662 r = strchr(p + 3, '/');
665 dpsnprintf(urlbuf, urlbuflength, "%.*s%s", (int)(p - url + 3), url, q + 1);
676 CheckPendingDownloads
678 checks if there are free download slots to start new downloads in.
679 To not start too many downloads at once, only one download is added at a time,
680 up to a maximum number of cl_curl_maxdownloads are running.
683 static void CheckPendingDownloads(void)
690 if(numdownloads < curl_maxdownloads.integer)
693 List_For_Each_Entry(di, &downloads, downloadinfo, list)
699 Con_Printf("Downloading %s -> %s", CleanURL(di->url, urlbuf, sizeof(urlbuf)), di->filename);
701 di->stream = FS_OpenRealFile(di->filename, "ab", false);
704 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
705 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK, NULL);
708 FS_Seek(di->stream, 0, SEEK_END);
709 di->startpos = FS_Tell(di->stream);
712 Con_Printf(", resuming from position %ld", (long) di->startpos);
717 Con_DPrintf("Downloading %s -> memory\n", CleanURL(di->url, urlbuf, sizeof(urlbuf)));
721 di->curle = qcurl_easy_init();
723 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
724 if(curl_useragent.integer)
727 #ifdef HTTP_USER_AGENT
734 if(*curl_useragent_append.string)
735 ua = va(vabuf, sizeof(vabuf), "%s%s%s",
737 (ua[0] && ua[strlen(ua)-1] != ' ')
740 curl_useragent_append.string);
741 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, ua);
744 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, "");
745 if(developer_curl.integer)
746 qcurl_easy_setopt(di->curle, CURLOPT_VERBOSE, (long) 1);
747 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
748 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
749 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
750 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
751 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
752 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
753 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
754 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
755 qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP);
756 if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK)
758 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");
759 //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0);
761 if(di->post_content_type)
763 qcurl_easy_setopt(di->curle, CURLOPT_POST, 1);
764 qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDS, di->postbuf);
765 qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDSIZE, di->postbufsize);
766 di->slist = qcurl_slist_append(di->slist, va(vabuf, sizeof(vabuf), "Content-Type: %s", di->post_content_type));
769 // parse extra headers into slist
770 // \n separated list!
771 h = di->extraheaders;
774 const char *hh = strchr(h, '\n');
777 char *buf = (char *) Mem_Alloc(tempmempool, hh - h + 1);
778 memcpy(buf, h, hh - h);
780 di->slist = qcurl_slist_append(di->slist, buf);
785 di->slist = qcurl_slist_append(di->slist, h);
790 qcurl_easy_setopt(di->curle, CURLOPT_HTTPHEADER, di->slist);
792 qcurl_multi_add_handle(curlm, di->curle);
795 if(numdownloads >= curl_maxdownloads.integer)
806 this function MUST be called before using anything else in this file.
807 On Win32, this must be called AFTER WSAStartup has been done!
815 if (Thread_HasThreads()) curl_mutex = Thread_CreateMutex();
816 qcurl_global_init(CURL_GLOBAL_SSL);
817 curlm = qcurl_multi_init();
824 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
827 void Curl_ClearRequirements(void);
828 void Curl_Shutdown(void)
832 Curl_ClearRequirements();
834 if (curl_mutex) Thread_DestroyMutex(curl_mutex);
839 // for VM_checkextension()
840 qbool Curl_Available(void)
842 return curl_dll ? true : false;
849 Finds the internal information block for a download given by file name.
852 static downloadinfo *Curl_Find(const char *filename)
857 List_For_Each_Entry(di, &downloads, downloadinfo, list)
858 if(!strcasecmp(di->filename, filename))
863 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
865 downloadinfo *di, *ndi;
868 List_For_Each_Entry_Safe(di, ndi, &downloads, downloadinfo, list)
870 if(di->callback == callback && di->callback_data == cbdata)
872 di->callback = curl_quiet_callback; // do NOT call the callback
873 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL);
882 Starts a download of a given URL to the file name portion of this URL (or name
883 if given) in the "dlcache/" folder.
886 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)
889 if(loadtype != LOADTYPE_NONE)
890 Host_Error("Curl_Begin: loadtype and buffer are both set");
892 if(!curl_dll || !curl_enabled.integer)
904 // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server
905 p = strchr(URL, ':');
908 if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4))
910 char addressstring[128];
912 InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring));
913 q = strchr(addressstring, ':');
915 q = addressstring + strlen(addressstring);
918 dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3);
924 // Note: This extraction of the file name portion is NOT entirely correct.
926 // It does the following:
928 // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
929 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
930 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
932 // However, I'd like to keep this "buggy" behavior so that PHP script
933 // authors can write download scripts without having to enable
934 // AcceptPathInfo on Apache. They just have to ensure that their script
935 // can be called with such a "fake" path name like
936 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
938 // By the way, such PHP scripts should either send the file or a
939 // "Location:" redirect; PHP code example:
941 // header("Location: http://www.example.com/");
943 // By the way, this will set User-Agent to something like
944 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
945 // dp://serverhost:serverport/ so you can filter on this; an example
946 // httpd log file line might be:
948 // 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"
950 if (curl_mutex) Thread_LockMutex(curl_mutex);
955 name = CleanURL(URL, urlbuf, sizeof(urlbuf));
961 name = CleanURL(URL, urlbuf, sizeof(urlbuf));
962 p = strrchr(name, '/');
963 p = p ? (p+1) : name;
965 length = q ? (size_t)(q - p) : strlen(p);
966 dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
970 dpsnprintf(fn, sizeof(fn), "dlcache/%s", name);
973 name = fn; // make it point back
975 // already downloading the file?
977 downloadinfo *existingdownloadinfo = Curl_Find(fn);
978 if(existingdownloadinfo)
980 Con_Printf("Can't download %s, already getting it from %s!\n", fn, CleanURL(existingdownloadinfo->url, urlbuf, sizeof(urlbuf)));
982 // however, if it was not for this map yet...
983 if(forthismap && !existingdownloadinfo->forthismap)
985 existingdownloadinfo->forthismap = true;
986 // this "fakes" a download attempt so the client will wait for
987 // the download to finish and then reconnect
988 ++numdownloads_added;
991 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
996 if(FS_FileExists(fn))
998 if(loadtype == LOADTYPE_PAK)
1000 qbool already_loaded;
1001 if(FS_AddPack(fn, &already_loaded, true, true))
1003 Con_DPrintf("%s already exists, not downloading!\n", fn);
1005 Con_DPrintf("(pak was already loaded)\n");
1010 ++numdownloads_added;
1011 ++numdownloads_success;
1015 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1020 qfile_t *f = FS_OpenRealFile(fn, "rb", false);
1024 FS_Read(f, b, sizeof(b)); // no "-1", I will use memcmp
1026 if(memcmp(b, "PK\x03\x04", 4) && memcmp(b, "PACK", 4))
1028 Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
1030 f = FS_OpenRealFile(fn, "wb", false);
1044 // never resume these
1045 qfile_t *f = FS_OpenRealFile(fn, "wb", false);
1052 // if we get here, we actually want to download... so first verify the
1053 // URL scheme (so one can't read local files using file://)
1054 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
1056 Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
1057 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1062 ++numdownloads_added;
1063 di = (downloadinfo *) Z_Malloc(sizeof(*di));
1064 dp_strlcpy(di->filename, name, sizeof(di->filename));
1065 dp_strlcpy(di->url, URL, sizeof(di->url));
1066 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
1067 di->forthismap = forthismap;
1071 di->started = false;
1072 di->loadtype = loadtype;
1073 di->maxspeed = maxspeed;
1074 di->bytes_received = 0;
1075 di->bytes_received_curl = 0;
1076 di->bytes_sent_curl = 0;
1077 di->extraheaders = extraheaders;
1079 di->buffersize = bufsize;
1080 if(callback == NULL)
1082 di->callback = curl_default_callback;
1083 di->callback_data = di;
1087 di->callback = callback;
1088 di->callback_data = cbdata;
1091 if(post_content_type)
1093 di->post_content_type = post_content_type;
1094 di->postbuf = postbuf;
1095 di->postbufsize = postbufsize;
1099 di->post_content_type = NULL;
1101 di->postbufsize = 0;
1104 List_Add(&di->list, &downloads);
1107 Thread_UnlockMutex(curl_mutex);
1113 qbool Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, int loadtype, qbool forthismap)
1115 return Curl_Begin(URL, NULL, maxspeed, name, loadtype, forthismap, NULL, NULL, 0, NULL, 0, NULL, NULL);
1117 qbool Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
1119 return Curl_Begin(URL, NULL, maxspeed, NULL, false, false, NULL, NULL, 0, buf, bufsize, callback, cbdata);
1121 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)
1123 return Curl_Begin(URL, extraheaders, maxspeed, NULL, false, false, post_content_type, postbuf, postbufsize, buf, bufsize, callback, cbdata);
1127 ====================
1130 call this regularily as this will always download as much as possible without
1132 ====================
1134 void Curl_Frame(void)
1141 if(!curl_enabled.integer && cls.state != ca_dedicated)
1147 if (curl_mutex) Thread_LockMutex(curl_mutex);
1149 Curl_CheckCommandWhenDone();
1151 if(List_Is_Empty(&downloads))
1153 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1157 if(host.realtime < curltime) // throttle
1159 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1169 mc = qcurl_multi_perform(curlm, &remaining);
1171 while(mc == CURLM_CALL_MULTI_PERFORM);
1173 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1178 qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_UPLOAD, &b);
1179 bytes_sent += (b - di->bytes_sent_curl);
1180 di->bytes_sent_curl = b;
1181 qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_DOWNLOAD, &b);
1182 bytes_sent += (b - di->bytes_received_curl);
1183 di->bytes_received_curl = b;
1189 CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
1192 if(msg->msg == CURLMSG_DONE)
1194 const char *ct = NULL;
1195 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
1197 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
1198 result = msg->data.result;
1201 failed = CURL_DOWNLOAD_FAILED;
1206 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
1209 case 4: // e.g. 404?
1210 case 5: // e.g. 500?
1211 failed = CURL_DOWNLOAD_SERVERERROR;
1212 result = (CURLcode) code;
1215 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_CONTENT_TYPE, &ct);
1218 Curl_EndDownload(di, failed, result, ct);
1223 CheckPendingDownloads();
1225 // when will we curl the next time?
1226 // we will wait a bit to ensure our download rate is kept.
1227 // we now know that realtime >= curltime... so set up a new curltime
1229 // use the slowest allowing download to derive the maxspeed... this CAN
1230 // be done better, but maybe later
1231 maxspeed = curl_maxspeed.value;
1232 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1233 if(di->maxspeed > 0)
1234 if(di->maxspeed < maxspeed || maxspeed <= 0)
1235 maxspeed = di->maxspeed;
1239 double bytes = bytes_sent + bytes_received; // maybe smoothen a bit?
1240 curltime = host.realtime + bytes / (maxspeed * 1024.0);
1245 curltime = host.realtime;
1247 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1251 ====================
1254 Sleeps until there's some transfer progress or a timeout is reached,
1255 unfortunately the timeout is only in milliseconds.
1256 This allows good throughput even at very low FPS.
1257 Less important on newer libcurl versions but still helps.
1259 Returns 0 immediately if there's no transfers to wait for,
1260 or > 0 if a transfer is ready or the timeout was reached.
1261 ====================
1263 int Curl_Select(uint32_t microseconds)
1268 if (List_Is_Empty(&downloads))
1271 err = qcurl_multi_wait(curlm, NULL, 0, microseconds / 1000, &numfds);
1272 if (err == CURLM_OK)
1274 Con_Printf("curl_multi_wait() failed, code %d\n", err);
1279 ====================
1282 Stops ALL downloads.
1283 ====================
1285 void Curl_CancelAll(void)
1290 if (curl_mutex) Thread_LockMutex(curl_mutex);
1292 while(!List_Is_Empty(&downloads))
1294 Curl_EndDownload(List_First_Entry(&downloads, downloadinfo, list), CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL);
1295 // INVARIANT: downloads will point to the next download after that!
1298 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1302 ====================
1305 returns true if there is a download running.
1306 ====================
1308 qbool Curl_Running(void)
1313 return !List_Is_Empty(&downloads);
1317 ====================
1318 Curl_GetDownloadAmount
1320 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
1321 for the given download.
1322 ====================
1324 static double Curl_GetDownloadAmount(downloadinfo *di)
1331 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1333 return (di->startpos + di->bytes_received) / (di->startpos + length);
1342 ====================
1343 Curl_GetDownloadSpeed
1345 returns the speed of the given download in bytes per second
1346 ====================
1348 static double Curl_GetDownloadSpeed(downloadinfo *di)
1355 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1363 ====================
1366 prints the download list
1367 ====================
1369 // TODO rewrite using Curl_GetDownloadInfo?
1370 static void Curl_Info_f(cmd_state_t *cmd)
1378 if (curl_mutex) Thread_LockMutex(curl_mutex);
1379 Con_Print("Currently running downloads:\n");
1380 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1382 double speed, percent;
1383 Con_Printf(" %s -> %s ", CleanURL(di->url, urlbuf, sizeof(urlbuf)), di->filename);
1384 percent = 100.0 * Curl_GetDownloadAmount(di);
1385 speed = Curl_GetDownloadSpeed(di);
1387 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1389 Con_Print("(queued)\n");
1391 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1395 Con_Print("No downloads running.\n");
1400 ====================
1403 implements the "curl" console command
1407 curl --cancel filename
1412 curl [--pak] [--forthismap] [--for filename filename...] url
1413 --pak: after downloading, load the package into the virtual file system
1414 --for filename...: only download of at least one of the named files is missing
1415 --forthismap: don't reconnect on failure
1417 curl --clear_autodownload
1418 clears the download success/failure counters
1420 curl --finish_autodownload
1421 if at least one download has been started, disconnect and drop to the menu
1422 once the last download completes successfully, reconnect to the current server
1423 ====================
1425 static void Curl_Curl_f(cmd_state_t *cmd)
1427 double maxspeed = 0;
1430 int loadtype = LOADTYPE_NONE;
1431 qbool forthismap = false;
1433 const char *name = 0;
1437 Con_Print("libcurl DLL not found, this command is inactive.\n");
1441 if(!curl_enabled.integer)
1443 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1447 if(Cmd_Argc(cmd) < 2)
1449 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1453 url = Cmd_Argv(cmd, Cmd_Argc(cmd) - 1);
1454 end = Cmd_Argc(cmd);
1456 for(i = 1; i != end; ++i)
1458 const char *a = Cmd_Argv(cmd, i);
1459 if(!strcmp(a, "--info"))
1464 else if(!strcmp(a, "--cancel"))
1466 if(i == end - 1) // last argument
1470 downloadinfo *di = Curl_Find(url);
1472 Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL);
1474 Con_Print("download not found\n");
1478 else if(!strcmp(a, "--pak"))
1480 loadtype = LOADTYPE_PAK;
1482 else if(!strcmp(a, "--cachepic"))
1484 loadtype = LOADTYPE_CACHEPIC;
1486 else if(!strcmp(a, "--skinframe"))
1488 loadtype = LOADTYPE_SKINFRAME;
1490 else if(!strcmp(a, "--for")) // must be last option
1492 for(i = i + 1; i != end - 1; ++i)
1494 if(!FS_FileExists(Cmd_Argv(cmd, i)))
1495 goto needthefile; // why can't I have a "double break"?
1497 // if we get here, we have all the files...
1500 else if(!strcmp(a, "--forthismap"))
1504 else if(!strcmp(a, "--as"))
1509 name = Cmd_Argv(cmd, i);
1512 else if(!strcmp(a, "--clear_autodownload"))
1514 // mark all running downloads as "not for this map", so if they
1515 // fail, it does not matter
1516 Curl_Clear_forthismap();
1519 else if(!strcmp(a, "--finish_autodownload"))
1521 if(numdownloads_added)
1523 char donecommand[256];
1526 if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1528 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1529 Curl_CommandWhenDone(donecommand);
1533 Curl_CheckCommandWhenDone();
1536 Curl_Register_predownload();
1541 else if(!strncmp(a, "--maxspeed=", 11))
1543 maxspeed = atof(a + 11);
1547 Con_Printf("curl: invalid option %s\n", a);
1548 // but we ignore the option
1553 Curl_Begin_ToFile(url, maxspeed, name, loadtype, forthismap);
1557 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1559 Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1563 void Curl_CurlCat_f(cmd_state_t *cmd)
1566 const char *url = Cmd_Argv(cmd, 1);
1567 buf = Z_Malloc(16384);
1568 Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1573 ====================
1576 loads the commands and cvars this library uses
1577 ====================
1579 void Curl_Init_Commands(void)
1581 Cvar_RegisterVariable (&curl_enabled);
1582 Cvar_RegisterVariable (&curl_maxdownloads);
1583 Cvar_RegisterVariable (&curl_maxspeed);
1584 Cvar_RegisterVariable (&curl_useragent);
1585 Cvar_RegisterVariable (&curl_useragent_append);
1586 Cvar_RegisterVirtual (&curl_enabled, "cl_curl_enabled");
1587 Cvar_RegisterVirtual (&curl_maxdownloads, "cl_curl_maxdownloads");
1588 Cvar_RegisterVirtual (&curl_maxspeed, "cl_curl_maxspeed");
1589 Cvar_RegisterVirtual (&curl_useragent, "cl_curl_useragent");
1590 Cvar_RegisterVirtual (&curl_useragent_append, "cl_curl_useragent_append");
1592 Cvar_RegisterVariable (&sv_curl_defaulturl);
1593 Cvar_RegisterVariable (&sv_curl_serverpackages);
1594 Cvar_RegisterVariable (&sv_curl_maxspeed);
1596 Cvar_RegisterVariable (&developer_curl);
1598 Cmd_AddCommand(CF_CLIENT | CF_CLIENT_FROM_SERVER, "curl", Curl_Curl_f, "download data from an URL and add to search path");
1599 //Cmd_AddCommand(cmd_local, "curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1603 ====================
1604 Curl_GetDownloadInfo
1606 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1607 The number of elements in the array is returned in int *nDownloads.
1608 const char **additional_info may be set to a string of additional user
1609 information, or to NULL if no such display shall occur. The returned
1610 array must be freed later using Z_Free.
1611 ====================
1613 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info, char *addinfo, size_t addinfolength)
1617 Curl_downloadinfo_t *downinfo;
1623 *additional_info = NULL;
1627 if (curl_mutex) Thread_LockMutex(curl_mutex);
1630 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1633 downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1635 List_For_Each_Entry(di, &downloads, downloadinfo, list)
1637 // do not show infobars for background downloads
1638 if(developer.integer <= 0)
1641 dp_strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1644 downinfo[i].progress = Curl_GetDownloadAmount(di);
1645 downinfo[i].speed = Curl_GetDownloadSpeed(di);
1646 downinfo[i].queued = false;
1650 downinfo[i].queued = true;
1657 // TODO: can I clear command_when_done as soon as the first download fails?
1658 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1660 if(!strncmp(command_when_done, "connect ", 8))
1661 dpsnprintf(addinfo, addinfolength, "(will join %s when done)", command_when_done + 8);
1662 else if(!strcmp(command_when_done, "cl_begindownloads"))
1663 dpsnprintf(addinfo, addinfolength, "(will enter the game when done)");
1665 dpsnprintf(addinfo, addinfolength, "(will do '%s' when done)", command_when_done);
1666 *additional_info = addinfo;
1669 *additional_info = NULL;
1673 if (curl_mutex) Thread_UnlockMutex(curl_mutex);
1679 ====================
1682 finds the URL where to find a given package.
1684 For this, it reads a file "curl_urls.txt" of the following format:
1687 revdm*.pk3 http://revdm/downloads/are/here/
1688 * http://any/other/stuff/is/here/
1690 The URLs should end in /. If not, downloads will still work, but the cached files
1691 can't be just put into the data directory with the same download configuration
1692 (you might want to do this if you want to tag downloaded files from your
1693 server, but you should not). "-" means "don't download".
1695 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1698 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1699 this file for obvious reasons.
1700 ====================
1702 static const char *Curl_FindPackURL(const char *filename)
1704 static char foundurl[1024]; // invoked only by server
1705 fs_offset_t filesize;
1706 char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1709 // read lines of format "pattern url"
1711 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1724 if(pattern && url && patternend)
1730 if(matchpattern(filename, pattern, true))
1732 dp_strlcpy(foundurl, url, sizeof(foundurl));
1744 if(pattern && !patternend)
1746 else if(url && !urlend)
1752 else if(pattern && patternend && !url)
1761 return sv_curl_defaulturl.string;
1764 typedef struct requirement_s
1766 struct requirement_s *next;
1767 char filename[MAX_OSPATH];
1770 static requirement *requirements = NULL;
1774 ====================
1777 Adds the given file to the list of requirements.
1778 ====================
1780 void Curl_RequireFile(const char *filename)
1782 requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1783 req->next = requirements;
1784 dp_strlcpy(req->filename, filename, sizeof(req->filename));
1789 ====================
1790 Curl_ClearRequirements
1792 Clears the list of required files for playing on the current map.
1793 This should be called at every map change.
1794 ====================
1796 void Curl_ClearRequirements(void)
1800 requirement *req = requirements;
1801 requirements = requirements->next;
1807 ====================
1808 Curl_SendRequirements
1810 Makes the current host_clients download all files he needs.
1811 This is done by sending him the following console commands:
1813 curl --clear_autodownload
1814 curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1815 curl --finish_autodownload
1816 ====================
1818 static qbool Curl_SendRequirement(const char *filename, qbool foundone, char *sendbuffer, size_t sendbuffer_len)
1821 const char *thispack = FS_WhichPack(filename);
1822 const char *packurl;
1824 if(!thispack || !*thispack)
1827 p = strrchr(thispack, '/');
1831 packurl = Curl_FindPackURL(thispack);
1833 if(packurl && *packurl && strcmp(packurl, "-"))
1836 dp_strlcat(sendbuffer, "curl --clear_autodownload\n", sendbuffer_len);
1838 dp_strlcat(sendbuffer, "curl --pak --forthismap --as ", sendbuffer_len);
1839 dp_strlcat(sendbuffer, thispack, sendbuffer_len);
1840 if(sv_curl_maxspeed.value > 0)
1841 dpsnprintf(sendbuffer + strlen(sendbuffer), sendbuffer_len - strlen(sendbuffer), " --maxspeed=%.1f", sv_curl_maxspeed.value);
1842 dp_strlcat(sendbuffer, " --for ", sendbuffer_len);
1843 dp_strlcat(sendbuffer, filename, sendbuffer_len);
1844 dp_strlcat(sendbuffer, " ", sendbuffer_len);
1845 dp_strlcat(sendbuffer, packurl, sendbuffer_len);
1846 dp_strlcat(sendbuffer, thispack, sendbuffer_len);
1847 dp_strlcat(sendbuffer, "\n", sendbuffer_len);
1854 void Curl_SendRequirements(void)
1856 // for each requirement, find the pack name
1857 char sendbuffer[4096] = "";
1859 qbool foundone = false;
1862 for(req = requirements; req; req = req->next)
1863 foundone = Curl_SendRequirement(req->filename, foundone, sendbuffer, sizeof(sendbuffer)) || foundone;
1865 p = sv_curl_serverpackages.string;
1866 while(COM_ParseToken_Simple(&p, false, false, true))
1867 foundone = Curl_SendRequirement(com_token, foundone, sendbuffer, sizeof(sendbuffer)) || foundone;
1870 dp_strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1872 if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1873 SV_ClientCommands("%s", sendbuffer);
1875 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");