]> git.xonotic.org Git - xonotic/darkplaces.git/blob - libcurl.c
curl: change how failure/success is decided to prevent "black screen" problems if...
[xonotic/darkplaces.git] / libcurl.c
1 #include "quakedef.h"
2 #include "fs.h"
3 #include "libcurl.h"
4
5 static cvar_t cl_curl_maxdownloads = {CVAR_SAVE, "cl_curl_maxdownloads","1", "maximum number of concurrent HTTP/FTP downloads"};
6 static cvar_t cl_curl_maxspeed = {CVAR_SAVE, "cl_curl_maxspeed","300", "maximum download speed (KiB/s)"};
7 static cvar_t sv_curl_defaulturl = {CVAR_SAVE, "sv_curl_defaulturl","", "default autodownload source URL"};
8 static cvar_t sv_curl_serverpackages = {CVAR_SAVE, "sv_curl_serverpackages","", "list of required files for the clients, separated by spaces"};
9 static cvar_t sv_curl_maxspeed = {CVAR_SAVE, "sv_curl_maxspeed","0", "maximum download speed for clients downloading from sv_curl_defaulturl (KiB/s)"};
10 static cvar_t cl_curl_enabled = {CVAR_SAVE, "cl_curl_enabled","1", "whether client's download support is enabled"};
11
12 /*
13 =================================================================
14
15   Minimal set of definitions from libcurl
16
17   WARNING: for a matter of simplicity, several pointer types are
18   casted to "void*", and most enumerated values are not included
19
20 =================================================================
21 */
22
23 typedef struct CURL_s CURL;
24 typedef struct CURLM_s CURLM;
25 typedef struct curl_slist curl_slist;
26 typedef enum
27 {
28         CURLE_OK = 0
29 }
30 CURLcode;
31 typedef enum
32 {
33         CURLM_CALL_MULTI_PERFORM=-1, /* please call curl_multi_perform() soon */
34         CURLM_OK = 0
35 }
36 CURLMcode;
37 #define CURL_GLOBAL_NOTHING 0
38 #define CURL_GLOBAL_SSL 1
39 #define CURL_GLOBAL_WIN32 2
40 #define CURLOPTTYPE_LONG          0
41 #define CURLOPTTYPE_OBJECTPOINT   10000
42 #define CURLOPTTYPE_FUNCTIONPOINT 20000
43 #define CURLOPTTYPE_OFF_T         30000
44 #define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number
45 typedef enum
46 {
47         CINIT(WRITEDATA, OBJECTPOINT, 1),
48         CINIT(URL,  OBJECTPOINT, 2),
49         CINIT(ERRORBUFFER, OBJECTPOINT, 10),
50         CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11),
51         CINIT(POSTFIELDS, OBJECTPOINT, 15),
52         CINIT(REFERER, OBJECTPOINT, 16),
53         CINIT(USERAGENT, OBJECTPOINT, 18),
54         CINIT(LOW_SPEED_LIMIT, LONG , 19),
55         CINIT(LOW_SPEED_TIME, LONG, 20),
56         CINIT(RESUME_FROM, LONG, 21),
57         CINIT(HTTPHEADER, OBJECTPOINT, 23),
58         CINIT(POST, LONG, 47),         /* HTTP POST method */
59         CINIT(FOLLOWLOCATION, LONG, 52),  /* use Location: Luke! */
60         CINIT(POSTFIELDSIZE, LONG, 60),
61         CINIT(PRIVATE, OBJECTPOINT, 103),
62         CINIT(PROTOCOLS, LONG, 181),
63         CINIT(REDIR_PROTOCOLS, LONG, 182)
64 }
65 CURLoption;
66 #define CURLPROTO_HTTP   (1<<0)
67 #define CURLPROTO_HTTPS  (1<<1)
68 #define CURLPROTO_FTP    (1<<2)
69 typedef enum
70 {
71         CURLINFO_TEXT = 0,
72         CURLINFO_HEADER_IN,    /* 1 */
73         CURLINFO_HEADER_OUT,   /* 2 */
74         CURLINFO_DATA_IN,      /* 3 */
75         CURLINFO_DATA_OUT,     /* 4 */
76         CURLINFO_SSL_DATA_IN,  /* 5 */
77         CURLINFO_SSL_DATA_OUT, /* 6 */
78         CURLINFO_END
79 }
80 curl_infotype;
81 #define CURLINFO_STRING   0x100000
82 #define CURLINFO_LONG     0x200000
83 #define CURLINFO_DOUBLE   0x300000
84 #define CURLINFO_SLIST    0x400000
85 #define CURLINFO_MASK     0x0fffff
86 #define CURLINFO_TYPEMASK 0xf00000
87 typedef enum
88 {
89         CURLINFO_NONE, /* first, never use this */
90         CURLINFO_EFFECTIVE_URL    = CURLINFO_STRING + 1,
91         CURLINFO_RESPONSE_CODE    = CURLINFO_LONG   + 2,
92         CURLINFO_TOTAL_TIME       = CURLINFO_DOUBLE + 3,
93         CURLINFO_NAMELOOKUP_TIME  = CURLINFO_DOUBLE + 4,
94         CURLINFO_CONNECT_TIME     = CURLINFO_DOUBLE + 5,
95         CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6,
96         CURLINFO_SIZE_UPLOAD      = CURLINFO_DOUBLE + 7,
97         CURLINFO_SIZE_DOWNLOAD    = CURLINFO_DOUBLE + 8,
98         CURLINFO_SPEED_DOWNLOAD   = CURLINFO_DOUBLE + 9,
99         CURLINFO_SPEED_UPLOAD     = CURLINFO_DOUBLE + 10,
100         CURLINFO_HEADER_SIZE      = CURLINFO_LONG   + 11,
101         CURLINFO_REQUEST_SIZE     = CURLINFO_LONG   + 12,
102         CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG   + 13,
103         CURLINFO_FILETIME         = CURLINFO_LONG   + 14,
104         CURLINFO_CONTENT_LENGTH_DOWNLOAD   = CURLINFO_DOUBLE + 15,
105         CURLINFO_CONTENT_LENGTH_UPLOAD     = CURLINFO_DOUBLE + 16,
106         CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17,
107         CURLINFO_CONTENT_TYPE     = CURLINFO_STRING + 18,
108         CURLINFO_REDIRECT_TIME    = CURLINFO_DOUBLE + 19,
109         CURLINFO_REDIRECT_COUNT   = CURLINFO_LONG   + 20,
110         CURLINFO_PRIVATE          = CURLINFO_STRING + 21,
111         CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG   + 22,
112         CURLINFO_HTTPAUTH_AVAIL   = CURLINFO_LONG   + 23,
113         CURLINFO_PROXYAUTH_AVAIL  = CURLINFO_LONG   + 24,
114         CURLINFO_OS_ERRNO         = CURLINFO_LONG   + 25,
115         CURLINFO_NUM_CONNECTS     = CURLINFO_LONG   + 26,
116         CURLINFO_SSL_ENGINES      = CURLINFO_SLIST  + 27
117 }
118 CURLINFO;
119
120 typedef enum
121 {
122         CURLMSG_NONE, /* first, not used */
123         CURLMSG_DONE, /* This easy handle has completed. 'result' contains
124                                          the CURLcode of the transfer */
125         CURLMSG_LAST
126 }
127 CURLMSG;
128 typedef struct
129 {
130         CURLMSG msg;       /* what this message means */
131         CURL *easy_handle; /* the handle it concerns */
132         union
133         {
134                 void *whatever;    /* message-specific data */
135                 CURLcode result;   /* return code for transfer */
136         }
137         data;
138 }
139 CURLMsg;
140
141 static void (*qcurl_global_init) (long flags);
142 static void (*qcurl_global_cleanup) (void);
143
144 static CURL * (*qcurl_easy_init) (void);
145 static void (*qcurl_easy_cleanup) (CURL *handle);
146 static CURLcode (*qcurl_easy_setopt) (CURL *handle, CURLoption option, ...);
147 static CURLcode (*qcurl_easy_getinfo) (CURL *handle, CURLINFO info, ...);
148 static const char * (*qcurl_easy_strerror) (CURLcode);
149
150 static CURLM * (*qcurl_multi_init) (void);
151 static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles);
152 static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle);
153 static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle);
154 static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue);
155 static void (*qcurl_multi_cleanup) (CURLM *);
156 static const char * (*qcurl_multi_strerror) (CURLcode);
157 static curl_slist * (*qcurl_slist_append) (curl_slist *list, const char *string);
158 static void (*qcurl_slist_free_all) (curl_slist *list);
159
160 static dllfunction_t curlfuncs[] =
161 {
162         {"curl_global_init",            (void **) &qcurl_global_init},
163         {"curl_global_cleanup",         (void **) &qcurl_global_cleanup},
164         {"curl_easy_init",                      (void **) &qcurl_easy_init},
165         {"curl_easy_cleanup",           (void **) &qcurl_easy_cleanup},
166         {"curl_easy_setopt",            (void **) &qcurl_easy_setopt},
167         {"curl_easy_strerror",          (void **) &qcurl_easy_strerror},
168         {"curl_easy_getinfo",           (void **) &qcurl_easy_getinfo},
169         {"curl_multi_init",                     (void **) &qcurl_multi_init},
170         {"curl_multi_perform",          (void **) &qcurl_multi_perform},
171         {"curl_multi_add_handle",       (void **) &qcurl_multi_add_handle},
172         {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle},
173         {"curl_multi_info_read",        (void **) &qcurl_multi_info_read},
174         {"curl_multi_cleanup",          (void **) &qcurl_multi_cleanup},
175         {"curl_multi_strerror",         (void **) &qcurl_multi_strerror},
176         {"curl_slist_append",           (void **) &qcurl_slist_append},
177         {"curl_slist_free_all",         (void **) &qcurl_slist_free_all},
178         {NULL, NULL}
179 };
180
181 // Handle for CURL DLL
182 static dllhandle_t curl_dll = NULL;
183 // will be checked at many places to find out if qcurl calls are allowed
184
185 typedef struct downloadinfo_s
186 {
187         char filename[MAX_OSPATH];
188         char url[1024];
189         char referer[256];
190         qfile_t *stream;
191         fs_offset_t startpos;
192         CURL *curle;
193         qboolean started;
194         qboolean ispak;
195         unsigned long bytes_received; // for buffer
196         double bytes_received_curl; // for throttling
197         double bytes_sent_curl; // for throttling
198         struct downloadinfo_s *next, *prev;
199         qboolean forthismap;
200         double maxspeed;
201         curl_slist *slist; // http headers
202
203         unsigned char *buffer;
204         size_t buffersize;
205         curl_callback_t callback;
206         void *callback_data;
207
208         const unsigned char *postbuf;
209         size_t postbufsize;
210         const char *post_content_type;
211         const char *extraheaders;
212 }
213 downloadinfo;
214 static downloadinfo *downloads = NULL;
215 static int numdownloads = 0;
216
217 static qboolean noclear = FALSE;
218
219 static int numdownloads_fail = 0;
220 static int numdownloads_success = 0;
221 static int numdownloads_added = 0;
222 static char command_when_done[256] = "";
223 static char command_when_error[256] = "";
224
225 /*
226 ====================
227 Curl_CommandWhenDone
228
229 Sets the command which is to be executed when the last download completes AND
230 all downloads since last server connect ended with a successful status.
231 Setting the command to NULL clears it.
232 ====================
233 */
234 void Curl_CommandWhenDone(const char *cmd)
235 {
236         if(!curl_dll)
237                 return;
238         if(cmd)
239                 strlcpy(command_when_done, cmd, sizeof(command_when_done));
240         else
241                 *command_when_done = 0;
242 }
243
244 /*
245 FIXME
246 Do not use yet. Not complete.
247 Problem: what counts as an error?
248 */
249
250 void Curl_CommandWhenError(const char *cmd)
251 {
252         if(!curl_dll)
253                 return;
254         if(cmd)
255                 strlcpy(command_when_error, cmd, sizeof(command_when_error));
256         else
257                 *command_when_error = 0;
258 }
259
260 /*
261 ====================
262 Curl_Clear_forthismap
263
264 Clears the "will disconnect on failure" flags.
265 ====================
266 */
267 void Curl_Clear_forthismap(void)
268 {
269         downloadinfo *di;
270         if(noclear)
271                 return;
272         for(di = downloads; di; di = di->next)
273                 di->forthismap = false;
274         Curl_CommandWhenError(NULL);
275         Curl_CommandWhenDone(NULL);
276         numdownloads_fail = 0;
277         numdownloads_success = 0;
278         numdownloads_added = 0;
279 }
280
281 /*
282 ====================
283 Curl_Have_forthismap
284
285 Returns true if a download needed for the current game is running.
286 ====================
287 */
288 qboolean Curl_Have_forthismap(void)
289 {
290         return numdownloads_added != 0;
291 }
292
293 void Curl_Register_predownload(void)
294 {
295         Curl_CommandWhenDone("cl_begindownloads");
296         Curl_CommandWhenError("cl_begindownloads");
297 }
298
299 /*
300 ====================
301 Curl_CheckCommandWhenDone
302
303 Checks if a "done command" is to be executed.
304 All downloads finished, at least one success since connect, no single failure
305 -> execute the command.
306 */
307 static void Curl_CheckCommandWhenDone(void)
308 {
309         if(!curl_dll)
310                 return;
311         if(numdownloads_added && ((numdownloads_success + numdownloads_fail) == numdownloads_added))
312         {
313                 if(numdownloads_fail == 0)
314                 {
315                         Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done);
316                         Cbuf_AddText("\n");
317                         Cbuf_AddText(command_when_done);
318                         Cbuf_AddText("\n");
319                 }
320                 else
321                 {
322                         Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error);
323                         Cbuf_AddText("\n");
324                         Cbuf_AddText(command_when_error);
325                         Cbuf_AddText("\n");
326                 }
327                 Curl_Clear_forthismap();
328         }
329 }
330
331 /*
332 ====================
333 CURL_CloseLibrary
334
335 Load the cURL DLL
336 ====================
337 */
338 static qboolean CURL_OpenLibrary (void)
339 {
340         const char* dllnames [] =
341         {
342 #if defined(WIN32)
343                 "libcurl-4.dll",
344                 "libcurl-3.dll",
345 #elif defined(MACOSX)
346                 "libcurl.4.dylib", // Mac OS X Notyetreleased
347                 "libcurl.3.dylib", // Mac OS X Tiger
348                 "libcurl.2.dylib", // Mac OS X Panther
349 #else
350                 "libcurl.so.4",
351                 "libcurl.so.3",
352                 "libcurl.so", // FreeBSD
353 #endif
354                 NULL
355         };
356
357         // Already loaded?
358         if (curl_dll)
359                 return true;
360
361         // Load the DLL
362         return Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs);
363 }
364
365
366 /*
367 ====================
368 CURL_CloseLibrary
369
370 Unload the cURL DLL
371 ====================
372 */
373 static void CURL_CloseLibrary (void)
374 {
375         Sys_UnloadLibrary (&curl_dll);
376 }
377
378
379 static CURLM *curlm = NULL;
380 static double bytes_received = 0; // used for bandwidth throttling
381 static double bytes_sent = 0; // used for bandwidth throttling
382 static double curltime = 0;
383
384 /*
385 ====================
386 CURL_fwrite
387
388 fwrite-compatible function that writes the data to a file. libcurl can call
389 this.
390 ====================
391 */
392 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
393 {
394         fs_offset_t ret = -1;
395         size_t bytes = size * nmemb;
396         downloadinfo *di = (downloadinfo *) vdi;
397
398         if(di->buffer)
399         {
400                 if(di->bytes_received + bytes <= di->buffersize)
401                 {
402                         memcpy(di->buffer + di->bytes_received, data, bytes);
403                         ret = bytes;
404                 }
405                 // otherwise: buffer overrun, ret stays -1
406         }
407
408         if(di->stream)
409         {
410                 ret = FS_Write(di->stream, data, bytes);
411         }
412
413         di->bytes_received += bytes;
414
415         return ret; // why not ret / nmemb?
416 }
417
418 typedef enum
419 {
420         CURL_DOWNLOAD_SUCCESS = 0,
421         CURL_DOWNLOAD_FAILED,
422         CURL_DOWNLOAD_ABORTED,
423         CURL_DOWNLOAD_SERVERERROR
424 }
425 CurlStatus;
426
427 static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
428 {
429         downloadinfo *di = (downloadinfo *) cbdata;
430         switch(status)
431         {
432                 case CURLCBSTATUS_OK:
433                         Con_DPrintf("Download of %s: OK\n", di->filename);
434                         break;
435                 case CURLCBSTATUS_FAILED:
436                         Con_DPrintf("Download of %s: FAILED\n", di->filename);
437                         break;
438                 case CURLCBSTATUS_ABORTED:
439                         Con_DPrintf("Download of %s: ABORTED\n", di->filename);
440                         break;
441                 case CURLCBSTATUS_SERVERERROR:
442                         Con_DPrintf("Download of %s: (unknown server error)\n", di->filename);
443                         break;
444                 case CURLCBSTATUS_UNKNOWN:
445                         Con_DPrintf("Download of %s: (unknown client error)\n", di->filename);
446                         break;
447                 default:
448                         Con_DPrintf("Download of %s: %d\n", di->filename, status);
449                         break;
450         }
451 }
452
453 static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
454 {
455         curl_default_callback(status, length_received, buffer, cbdata);
456 }
457
458 /*
459 ====================
460 Curl_EndDownload
461
462 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
463 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
464 code from libcurl, or 0, if another error has occurred.
465 ====================
466 */
467 static qboolean Curl_Begin(const char *URL, const char *extraheaders, double maxspeed, const char *name, qboolean ispak, qboolean 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);
468 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error)
469 {
470         qboolean ok = false;
471         if(!curl_dll)
472                 return;
473         switch(status)
474         {
475                 case CURL_DOWNLOAD_SUCCESS:
476                         ok = true;
477                         di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
478                         break;
479                 case CURL_DOWNLOAD_FAILED:
480                         di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
481                         break;
482                 case CURL_DOWNLOAD_ABORTED:
483                         di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
484                         break;
485                 case CURL_DOWNLOAD_SERVERERROR:
486                         // reopen to enforce it to have zero bytes again
487                         if(di->stream)
488                         {
489                                 FS_Close(di->stream);
490                                 di->stream = FS_OpenRealFile(di->filename, "wb", false);
491                         }
492
493                         if(di->callback)
494                                 di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
495                         break;
496                 default:
497                         if(di->callback)
498                                 di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
499                         break;
500         }
501
502         if(di->curle)
503         {
504                 qcurl_multi_remove_handle(curlm, di->curle);
505                 qcurl_easy_cleanup(di->curle);
506                 if(di->slist)
507                         qcurl_slist_free_all(di->slist);
508         }
509
510         if(!di->callback && ok && !di->bytes_received)
511         {
512                 Con_Printf("ERROR: empty file\n");
513                 ok = false;
514         }
515
516         if(di->stream)
517                 FS_Close(di->stream);
518
519         if(ok && di->ispak)
520         {
521                 ok = FS_AddPack(di->filename, NULL, true);
522                 if(!ok)
523                 {
524                         // pack loading failed?
525                         // this is critical
526                         // better clear the file again...
527                         di->stream = FS_OpenRealFile(di->filename, "wb", false);
528                         FS_Close(di->stream);
529
530                         if(di->startpos && !di->callback)
531                         {
532                                 // this was a resume?
533                                 // then try to redownload it without reporting the error
534                                 Curl_Begin(di->url, di->extraheaders, di->maxspeed, di->filename, di->ispak, di->forthismap, di->post_content_type, di->postbuf, di->postbufsize, NULL, 0, NULL, NULL);
535                                 di->forthismap = false; // don't count the error
536                         }
537                 }
538         }
539
540         if(di->prev)
541                 di->prev->next = di->next;
542         else
543                 downloads = di->next;
544         if(di->next)
545                 di->next->prev = di->prev;
546
547         --numdownloads;
548         if(di->forthismap)
549         {
550                 if(ok)
551                         ++numdownloads_success;
552                 else
553                         ++numdownloads_fail;
554         }
555         Z_Free(di);
556 }
557
558 /*
559 ====================
560 CleanURL
561
562 Returns a "cleaned up" URL for display (to strip login data)
563 ====================
564 */
565 static const char *CleanURL(const char *url)
566 {
567         static char urlbuf[1024];
568         const char *p, *q, *r;
569
570         // if URL is of form anything://foo-without-slash@rest, replace by anything://rest
571         p = strstr(url, "://");
572         if(p)
573         {
574                 q = strchr(p + 3, '@');
575                 if(q)
576                 {
577                         r = strchr(p + 3, '/');
578                         if(!r || q < r)
579                         {
580                                 dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s%s", (int)(p - url + 3), url, q + 1);
581                                 return urlbuf;
582                         }
583                 }
584         }
585
586         return url;
587 }
588
589 /*
590 ====================
591 CheckPendingDownloads
592
593 checks if there are free download slots to start new downloads in.
594 To not start too many downloads at once, only one download is added at a time,
595 up to a maximum number of cl_curl_maxdownloads are running.
596 ====================
597 */
598 static void CheckPendingDownloads(void)
599 {
600         const char *h;
601         if(!curl_dll)
602                 return;
603         if(numdownloads < cl_curl_maxdownloads.integer)
604         {
605                 downloadinfo *di;
606                 for(di = downloads; di; di = di->next)
607                 {
608                         if(!di->started)
609                         {
610                                 if(!di->buffer)
611                                 {
612                                         Con_Printf("Downloading %s -> %s", CleanURL(di->url), di->filename);
613
614                                         di->stream = FS_OpenRealFile(di->filename, "ab", false);
615                                         if(!di->stream)
616                                         {
617                                                 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
618                                                 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
619                                                 return;
620                                         }
621                                         FS_Seek(di->stream, 0, SEEK_END);
622                                         di->startpos = FS_Tell(di->stream);
623
624                                         if(di->startpos > 0)
625                                                 Con_Printf(", resuming from position %ld", (long) di->startpos);
626                                         Con_Print("...\n");
627                                 }
628                                 else
629                                 {
630                                         Con_DPrintf("Downloading %s -> memory\n", CleanURL(di->url));
631                                         di->startpos = 0;
632                                 }
633
634                                 di->curle = qcurl_easy_init();
635                                 di->slist = NULL;
636                                 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
637                                 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
638                                 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
639                                 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
640                                 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
641                                 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
642                                 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
643                                 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
644                                 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
645                                 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
646                                 qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP);
647                                 if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK)
648                                 {
649                                         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");
650                                         //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0);
651                                 }
652                                 if(di->post_content_type)
653                                 {
654                                         qcurl_easy_setopt(di->curle, CURLOPT_POST, 1);
655                                         qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDS, di->postbuf);
656                                         qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDSIZE, di->postbufsize);
657                                         di->slist = qcurl_slist_append(di->slist, va("Content-Type: %s", di->post_content_type));
658                                 }
659
660                                 // parse extra headers into slist
661                                 // \n separated list!
662                                 h = di->extraheaders;
663                                 while(h)
664                                 {
665                                         const char *hh = strchr(h, '\n');
666                                         if(hh)
667                                         {
668                                                 char *buf = (char *) Mem_Alloc(tempmempool, hh - h + 1);
669                                                 memcpy(buf, h, hh - h);
670                                                 buf[hh - h] = 0;
671                                                 di->slist = qcurl_slist_append(di->slist, buf);
672                                                 h = hh + 1;
673                                         }
674                                         else
675                                         {
676                                                 di->slist = qcurl_slist_append(di->slist, h);
677                                                 h = NULL;
678                                         }
679                                 }
680
681                                 qcurl_easy_setopt(di->curle, CURLOPT_HTTPHEADER, di->slist);
682                                 
683                                 qcurl_multi_add_handle(curlm, di->curle);
684                                 di->started = true;
685                                 ++numdownloads;
686                                 if(numdownloads >= cl_curl_maxdownloads.integer)
687                                         break;
688                         }
689                 }
690         }
691 }
692
693 /*
694 ====================
695 Curl_Init
696
697 this function MUST be called before using anything else in this file.
698 On Win32, this must be called AFTER WSAStartup has been done!
699 ====================
700 */
701 void Curl_Init(void)
702 {
703         CURL_OpenLibrary();
704         if(!curl_dll)
705                 return;
706         qcurl_global_init(CURL_GLOBAL_NOTHING);
707         curlm = qcurl_multi_init();
708 }
709
710 /*
711 ====================
712 Curl_Shutdown
713
714 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
715 ====================
716 */
717 void Curl_ClearRequirements(void);
718 void Curl_Shutdown(void)
719 {
720         if(!curl_dll)
721                 return;
722         Curl_ClearRequirements();
723         Curl_CancelAll();
724         CURL_CloseLibrary();
725         curl_dll = NULL;
726 }
727
728 /*
729 ====================
730 Curl_Find
731
732 Finds the internal information block for a download given by file name.
733 ====================
734 */
735 static downloadinfo *Curl_Find(const char *filename)
736 {
737         downloadinfo *di;
738         if(!curl_dll)
739                 return NULL;
740         for(di = downloads; di; di = di->next)
741                 if(!strcasecmp(di->filename, filename))
742                         return di;
743         return NULL;
744 }
745
746 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
747 {
748         downloadinfo *di;
749         if(!curl_dll)
750                 return;
751         for(di = downloads; di; )
752         {
753                 if(di->callback == callback && di->callback_data == cbdata)
754                 {
755                         di->callback = curl_quiet_callback; // do NOT call the callback
756                         Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
757                         di = downloads;
758                 }
759                 else
760                         di = di->next;
761         }
762 }
763
764 /*
765 ====================
766 Curl_Begin
767
768 Starts a download of a given URL to the file name portion of this URL (or name
769 if given) in the "dlcache/" folder.
770 ====================
771 */
772 static qboolean Curl_Begin(const char *URL, const char *extraheaders, double maxspeed, const char *name, qboolean ispak, qboolean 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)
773 {
774         if(!curl_dll)
775         {
776                 return false;
777         }
778         else
779         {
780                 char fn[MAX_OSPATH];
781                 char urlbuf[1024];
782                 const char *p, *q;
783                 size_t length;
784                 downloadinfo *di;
785
786                 // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server
787                 p = strchr(URL, ':');
788                 if(p)
789                 {
790                         if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4))
791                         {
792                                 char addressstring[128];
793                                 *addressstring = 0;
794                                 InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring));
795                                 q = strchr(addressstring, ':');
796                                 if(!q)
797                                         q = addressstring + strlen(addressstring);
798                                 if(*addressstring)
799                                 {
800                                         dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3);
801                                         URL = urlbuf;
802                                 }
803                         }
804                 }
805
806                 // Note: This extraction of the file name portion is NOT entirely correct.
807                 //
808                 // It does the following:
809                 //
810                 //   http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
811                 //   http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
812                 //   http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
813                 //
814                 // However, I'd like to keep this "buggy" behavior so that PHP script
815                 // authors can write download scripts without having to enable
816                 // AcceptPathInfo on Apache. They just have to ensure that their script
817                 // can be called with such a "fake" path name like
818                 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
819                 //
820                 // By the way, such PHP scripts should either send the file or a
821                 // "Location:" redirect; PHP code example:
822                 //
823                 //   header("Location: http://www.example.com/");
824                 //
825                 // By the way, this will set User-Agent to something like
826                 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
827                 // dp://serverhost:serverport/ so you can filter on this; an example
828                 // httpd log file line might be:
829                 //
830                 //   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"
831
832                 if(!name)
833                         name = CleanURL(URL);
834
835                 if(!buf)
836                 {
837                         p = strrchr(name, '/');
838                         p = p ? (p+1) : name;
839                         q = strchr(p, '?');
840                         length = q ? (size_t)(q - p) : strlen(p);
841                         dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
842
843                         name = fn; // make it point back
844
845                         // already downloading the file?
846                         {
847                                 downloadinfo *di = Curl_Find(fn);
848                                 if(di)
849                                 {
850                                         Con_Printf("Can't download %s, already getting it from %s!\n", fn, CleanURL(di->url));
851
852                                         // however, if it was not for this map yet...
853                                         if(forthismap && !di->forthismap)
854                                         {
855                                                 di->forthismap = true;
856                                                 // this "fakes" a download attempt so the client will wait for
857                                                 // the download to finish and then reconnect
858                                                 ++numdownloads_added;
859                                         }
860
861                                         return false;
862                                 }
863                         }
864
865                         if(ispak && FS_FileExists(fn))
866                         {
867                                 qboolean already_loaded;
868                                 if(FS_AddPack(fn, &already_loaded, true))
869                                 {
870                                         Con_DPrintf("%s already exists, not downloading!\n", fn);
871                                         if(already_loaded)
872                                                 Con_DPrintf("(pak was already loaded)\n");
873                                         else
874                                         {
875                                                 if(forthismap)
876                                                 {
877                                                         ++numdownloads_added;
878                                                         ++numdownloads_success;
879                                                 }
880                                         }
881
882                                         return false;
883                                 }
884                                 else
885                                 {
886                                         qfile_t *f = FS_OpenRealFile(fn, "rb", false);
887                                         if(f)
888                                         {
889                                                 char buf[4] = {0};
890                                                 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
891
892                                                 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
893                                                 {
894                                                         Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
895                                                         FS_Close(f);
896                                                         f = FS_OpenRealFile(fn, "wb", false);
897                                                         if(f)
898                                                                 FS_Close(f);
899                                                 }
900                                                 else
901                                                 {
902                                                         // OK
903                                                         FS_Close(f);
904                                                 }
905                                         }
906                                 }
907                         }
908                 }
909
910                 // if we get here, we actually want to download... so first verify the
911                 // URL scheme (so one can't read local files using file://)
912                 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
913                 {
914                         Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
915                         return false;
916                 }
917
918                 if(forthismap)
919                         ++numdownloads_added;
920                 di = (downloadinfo *) Z_Malloc(sizeof(*di));
921                 strlcpy(di->filename, name, sizeof(di->filename));
922                 strlcpy(di->url, URL, sizeof(di->url));
923                 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
924                 di->forthismap = forthismap;
925                 di->stream = NULL;
926                 di->startpos = 0;
927                 di->curle = NULL;
928                 di->started = false;
929                 di->ispak = (ispak && !buf);
930                 di->maxspeed = maxspeed;
931                 di->bytes_received = 0;
932                 di->bytes_received_curl = 0;
933                 di->bytes_sent_curl = 0;
934                 di->extraheaders = extraheaders;
935                 di->next = downloads;
936                 di->prev = NULL;
937                 if(di->next)
938                         di->next->prev = di;
939
940                 di->buffer = buf;
941                 di->buffersize = bufsize;
942                 if(callback == NULL)
943                 {
944                         di->callback = curl_default_callback;
945                         di->callback_data = di;
946                 }
947                 else
948                 {
949                         di->callback = callback;
950                         di->callback_data = cbdata;
951                 }
952
953                 if(post_content_type)
954                 {
955                         di->post_content_type = post_content_type;
956                         di->postbuf = postbuf;
957                         di->postbufsize = postbufsize;
958                 }
959                 else
960                 {
961                         di->post_content_type = NULL;
962                         di->postbuf = NULL;
963                         di->postbufsize = 0;
964                 }
965
966                 downloads = di;
967                 return true;
968         }
969 }
970
971 qboolean Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, qboolean ispak, qboolean forthismap)
972 {
973         return Curl_Begin(URL, NULL, maxspeed, name, ispak, forthismap, NULL, NULL, 0, NULL, 0, NULL, NULL);
974 }
975 qboolean Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
976 {
977         return Curl_Begin(URL, NULL, maxspeed, NULL, false, false, NULL, NULL, 0, buf, bufsize, callback, cbdata);
978 }
979 qboolean 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)
980 {
981         return Curl_Begin(URL, extraheaders, maxspeed, NULL, false, false, post_content_type, postbuf, postbufsize, buf, bufsize, callback, cbdata);
982 }
983
984 /*
985 ====================
986 Curl_Run
987
988 call this regularily as this will always download as much as possible without
989 blocking.
990 ====================
991 */
992 void Curl_Run(void)
993 {
994         double maxspeed;
995         downloadinfo *di;
996
997         noclear = FALSE;
998
999         if(!cl_curl_enabled.integer)
1000                 return;
1001
1002         if(!curl_dll)
1003                 return;
1004
1005         Curl_CheckCommandWhenDone();
1006
1007         if(!downloads)
1008                 return;
1009
1010         if(realtime < curltime) // throttle
1011                 return;
1012
1013         {
1014                 int remaining;
1015                 CURLMcode mc;
1016
1017                 do
1018                 {
1019                         mc = qcurl_multi_perform(curlm, &remaining);
1020                 }
1021                 while(mc == CURLM_CALL_MULTI_PERFORM);
1022
1023                 for(di = downloads; di; di = di->next)
1024                 {
1025                         double b = 0;
1026                         if(di->curle)
1027                         {
1028                                 qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_UPLOAD, &b);
1029                                 bytes_sent += (b - di->bytes_sent_curl);
1030                                 di->bytes_sent_curl = b;
1031                                 qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_DOWNLOAD, &b);
1032                                 bytes_sent += (b - di->bytes_received_curl);
1033                                 di->bytes_received_curl = b;
1034                         }
1035                 }
1036
1037                 for(;;)
1038                 {
1039                         CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
1040                         if(!msg)
1041                                 break;
1042                         if(msg->msg == CURLMSG_DONE)
1043                         {
1044                                 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
1045                                 CURLcode result;
1046                                 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
1047                                 result = msg->data.result;
1048                                 if(result)
1049                                 {
1050                                         failed = CURL_DOWNLOAD_FAILED;
1051                                 }
1052                                 else
1053                                 {
1054                                         long code;
1055                                         qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
1056                                         switch(code / 100)
1057                                         {
1058                                                 case 4: // e.g. 404?
1059                                                 case 5: // e.g. 500?
1060                                                         failed = CURL_DOWNLOAD_SERVERERROR;
1061                                                         result = (CURLcode) code;
1062                                                         break;
1063                                         }
1064                                 }
1065
1066                                 Curl_EndDownload(di, failed, result);
1067                         }
1068                 }
1069         }
1070
1071         CheckPendingDownloads();
1072
1073         // when will we curl the next time?
1074         // we will wait a bit to ensure our download rate is kept.
1075         // we now know that realtime >= curltime... so set up a new curltime
1076
1077         // use the slowest allowing download to derive the maxspeed... this CAN
1078         // be done better, but maybe later
1079         maxspeed = cl_curl_maxspeed.value;
1080         for(di = downloads; di; di = di->next)
1081                 if(di->maxspeed > 0)
1082                         if(di->maxspeed < maxspeed || maxspeed <= 0)
1083                                 maxspeed = di->maxspeed;
1084
1085         if(maxspeed > 0)
1086         {
1087                 double bytes = bytes_sent + bytes_received; // maybe smoothen a bit?
1088                 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
1089                 bytes_sent = 0;
1090                 bytes_received = 0;
1091         }
1092         else
1093                 curltime = realtime;
1094 }
1095
1096 /*
1097 ====================
1098 Curl_CancelAll
1099
1100 Stops ALL downloads.
1101 ====================
1102 */
1103 void Curl_CancelAll(void)
1104 {
1105         if(!curl_dll)
1106                 return;
1107
1108         while(downloads)
1109         {
1110                 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1111                 // INVARIANT: downloads will point to the next download after that!
1112         }
1113 }
1114
1115 /*
1116 ====================
1117 Curl_Running
1118
1119 returns true iff there is a download running.
1120 ====================
1121 */
1122 qboolean Curl_Running(void)
1123 {
1124         if(!curl_dll)
1125                 return false;
1126
1127         return downloads != NULL;
1128 }
1129
1130 /*
1131 ====================
1132 Curl_GetDownloadAmount
1133
1134 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
1135 for the given download.
1136 ====================
1137 */
1138 static double Curl_GetDownloadAmount(downloadinfo *di)
1139 {
1140         if(!curl_dll)
1141                 return -2;
1142         if(di->curle)
1143         {
1144                 double length;
1145                 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1146                 if(length > 0)
1147                         return (di->startpos + di->bytes_received) / (di->startpos + length);
1148                 else
1149                         return 0;
1150         }
1151         else
1152                 return -1;
1153 }
1154
1155 /*
1156 ====================
1157 Curl_GetDownloadSpeed
1158
1159 returns the speed of the given download in bytes per second
1160 ====================
1161 */
1162 static double Curl_GetDownloadSpeed(downloadinfo *di)
1163 {
1164         if(!curl_dll)
1165                 return -2;
1166         if(di->curle)
1167         {
1168                 double speed;
1169                 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1170                 return speed;
1171         }
1172         else
1173                 return -1;
1174 }
1175
1176 /*
1177 ====================
1178 Curl_Info_f
1179
1180 prints the download list
1181 ====================
1182 */
1183 // TODO rewrite using Curl_GetDownloadInfo?
1184 static void Curl_Info_f(void)
1185 {
1186         downloadinfo *di;
1187         if(!curl_dll)
1188                 return;
1189         if(Curl_Running())
1190         {
1191                 Con_Print("Currently running downloads:\n");
1192                 for(di = downloads; di; di = di->next)
1193                 {
1194                         double speed, percent;
1195                         Con_Printf("  %s -> %s ",  CleanURL(di->url), di->filename);
1196                         percent = 100.0 * Curl_GetDownloadAmount(di);
1197                         speed = Curl_GetDownloadSpeed(di);
1198                         if(percent >= 0)
1199                                 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1200                         else
1201                                 Con_Print("(queued)\n");
1202                 }
1203         }
1204         else
1205         {
1206                 Con_Print("No downloads running.\n");
1207         }
1208 }
1209
1210 /*
1211 ====================
1212 Curl_Curl_f
1213
1214 implements the "curl" console command
1215
1216 curl --info
1217 curl --cancel
1218 curl --cancel filename
1219 curl url
1220
1221 For internal use:
1222
1223 curl [--pak] [--forthismap] [--for filename filename...] url
1224         --pak: after downloading, load the package into the virtual file system
1225         --for filename...: only download of at least one of the named files is missing
1226         --forthismap: don't reconnect on failure
1227
1228 curl --clear_autodownload
1229         clears the download success/failure counters
1230
1231 curl --finish_autodownload
1232         if at least one download has been started, disconnect and drop to the menu
1233         once the last download completes successfully, reconnect to the current server
1234 ====================
1235 */
1236 void Curl_Curl_f(void)
1237 {
1238         double maxspeed = 0;
1239         int i;
1240         int end;
1241         qboolean pak = false;
1242         qboolean forthismap = false;
1243         const char *url;
1244         const char *name = 0;
1245
1246         if(!curl_dll)
1247         {
1248                 Con_Print("libcurl DLL not found, this command is inactive.\n");
1249                 return;
1250         }
1251
1252         if(!cl_curl_enabled.integer)
1253         {
1254                 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1255                 return;
1256         }
1257
1258         if(Cmd_Argc() < 2)
1259         {
1260                 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1261                 return;
1262         }
1263
1264         url = Cmd_Argv(Cmd_Argc() - 1);
1265         end = Cmd_Argc();
1266
1267         for(i = 1; i != end; ++i)
1268         {
1269                 const char *a = Cmd_Argv(i);
1270                 if(!strcmp(a, "--info"))
1271                 {
1272                         Curl_Info_f();
1273                         return;
1274                 }
1275                 else if(!strcmp(a, "--cancel"))
1276                 {
1277                         if(i == end - 1) // last argument
1278                                 Curl_CancelAll();
1279                         else
1280                         {
1281                                 downloadinfo *di = Curl_Find(url);
1282                                 if(di)
1283                                         Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1284                                 else
1285                                         Con_Print("download not found\n");
1286                         }
1287                         return;
1288                 }
1289                 else if(!strcmp(a, "--pak"))
1290                 {
1291                         pak = true;
1292                 }
1293                 else if(!strcmp(a, "--for")) // must be last option
1294                 {
1295                         for(i = i + 1; i != end - 1; ++i)
1296                         {
1297                                 if(!FS_FileExists(Cmd_Argv(i)))
1298                                         goto needthefile; // why can't I have a "double break"?
1299                         }
1300                         // if we get here, we have all the files...
1301                         return;
1302                 }
1303                 else if(!strcmp(a, "--forthismap"))
1304                 {
1305                         forthismap = true;
1306                 }
1307                 else if(!strcmp(a, "--as"))
1308                 {
1309                         if(i < end - 1)
1310                         {
1311                                 ++i;
1312                                 name = Cmd_Argv(i);
1313                         }
1314                 }
1315                 else if(!strcmp(a, "--clear_autodownload"))
1316                 {
1317                         // mark all running downloads as "not for this map", so if they
1318                         // fail, it does not matter
1319                         Curl_Clear_forthismap();
1320                         return;
1321                 }
1322                 else if(!strcmp(a, "--finish_autodownload"))
1323                 {
1324                         if(numdownloads_added)
1325                         {
1326                                 char donecommand[256];
1327                                 if(cls.netcon)
1328                                 {
1329                                         if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1330                                         {
1331                                                 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1332                                                 Curl_CommandWhenDone(donecommand);
1333                                                 noclear = TRUE;
1334                                                 CL_Disconnect();
1335                                                 noclear = FALSE;
1336                                                 Curl_CheckCommandWhenDone();
1337                                         }
1338                                         else
1339                                                 Curl_Register_predownload();
1340                                 }
1341                         }
1342                         return;
1343                 }
1344                 else if(!strncmp(a, "--maxspeed=", 11))
1345                 {
1346                         maxspeed = atof(a + 11);
1347                 }
1348                 else if(*a == '-')
1349                 {
1350                         Con_Printf("curl: invalid option %s\n", a);
1351                         // but we ignore the option
1352                 }
1353         }
1354
1355 needthefile:
1356         Curl_Begin_ToFile(url, maxspeed, name, pak, forthismap);
1357 }
1358
1359 /*
1360 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1361 {
1362         Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1363         Z_Free(buffer);
1364 }
1365
1366 void Curl_CurlCat_f(void)
1367 {
1368         unsigned char *buf;
1369         const char *url = Cmd_Argv(1);
1370         buf = Z_Malloc(16384);
1371         Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1372 }
1373 */
1374
1375 /*
1376 ====================
1377 Curl_Init_Commands
1378
1379 loads the commands and cvars this library uses
1380 ====================
1381 */
1382 void Curl_Init_Commands(void)
1383 {
1384         Cvar_RegisterVariable (&cl_curl_enabled);
1385         Cvar_RegisterVariable (&cl_curl_maxdownloads);
1386         Cvar_RegisterVariable (&cl_curl_maxspeed);
1387         Cvar_RegisterVariable (&sv_curl_defaulturl);
1388         Cvar_RegisterVariable (&sv_curl_serverpackages);
1389         Cvar_RegisterVariable (&sv_curl_maxspeed);
1390         Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1391         //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1392 }
1393
1394 /*
1395 ====================
1396 Curl_GetDownloadInfo
1397
1398 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1399 The number of elements in the array is returned in int *nDownloads.
1400 const char **additional_info may be set to a string of additional user
1401 information, or to NULL if no such display shall occur. The returned
1402 array must be freed later using Z_Free.
1403 ====================
1404 */
1405 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1406 {
1407         int i;
1408         downloadinfo *di;
1409         Curl_downloadinfo_t *downinfo;
1410         static char addinfo[128];
1411
1412         if(!curl_dll)
1413         {
1414                 *nDownloads = 0;
1415                 if(additional_info)
1416                         *additional_info = NULL;
1417                 return NULL;
1418         }
1419
1420         i = 0;
1421         for(di = downloads; di; di = di->next)
1422                 ++i;
1423
1424         downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1425         i = 0;
1426         for(di = downloads; di; di = di->next)
1427         {
1428                 // do not show infobars for background downloads
1429                 if(developer.integer <= 0)
1430                         if(di->buffer)
1431                                 continue;
1432                 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1433                 if(di->curle)
1434                 {
1435                         downinfo[i].progress = Curl_GetDownloadAmount(di);
1436                         downinfo[i].speed = Curl_GetDownloadSpeed(di);
1437                         downinfo[i].queued = false;
1438                 }
1439                 else
1440                 {
1441                         downinfo[i].queued = true;
1442                 }
1443                 ++i;
1444         }
1445
1446         if(additional_info)
1447         {
1448                 // TODO: can I clear command_when_done as soon as the first download fails?
1449                 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1450                 {
1451                         if(!strncmp(command_when_done, "connect ", 8))
1452                                 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1453                         else if(!strcmp(command_when_done, "cl_begindownloads"))
1454                                 dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
1455                         else
1456                                 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1457                         *additional_info = addinfo;
1458                 }
1459                 else
1460                         *additional_info = NULL;
1461         }
1462
1463         *nDownloads = i;
1464         return downinfo;
1465 }
1466
1467
1468 /*
1469 ====================
1470 Curl_FindPackURL
1471
1472 finds the URL where to find a given package.
1473
1474 For this, it reads a file "curl_urls.txt" of the following format:
1475
1476         data*.pk3       -
1477         revdm*.pk3      http://revdm/downloads/are/here/
1478         *                       http://any/other/stuff/is/here/
1479
1480 The URLs should end in /. If not, downloads will still work, but the cached files
1481 can't be just put into the data directory with the same download configuration
1482 (you might want to do this if you want to tag downloaded files from your
1483 server, but you should not). "-" means "don't download".
1484
1485 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1486 location instead.
1487
1488 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1489 this file for obvious reasons.
1490 ====================
1491 */
1492 static const char *Curl_FindPackURL(const char *filename)
1493 {
1494         static char foundurl[1024];
1495         fs_offset_t filesize;
1496         char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1497         if(buf && filesize)
1498         {
1499                 // read lines of format "pattern url"
1500                 char *p = buf;
1501                 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1502                 qboolean eof = false;
1503
1504                 pattern = p;
1505                 while(!eof)
1506                 {
1507                         switch(*p)
1508                         {
1509                                 case 0:
1510                                         eof = true;
1511                                         // fallthrough
1512                                 case '\n':
1513                                 case '\r':
1514                                         if(pattern && url && patternend)
1515                                         {
1516                                                 if(!urlend)
1517                                                         urlend = p;
1518                                                 *patternend = 0;
1519                                                 *urlend = 0;
1520                                                 if(matchpattern(filename, pattern, true))
1521                                                 {
1522                                                         strlcpy(foundurl, url, sizeof(foundurl));
1523                                                         Z_Free(buf);
1524                                                         return foundurl;
1525                                                 }
1526                                         }
1527                                         pattern = NULL;
1528                                         patternend = NULL;
1529                                         url = NULL;
1530                                         urlend = NULL;
1531                                         break;
1532                                 case ' ':
1533                                 case '\t':
1534                                         if(pattern && !patternend)
1535                                                 patternend = p;
1536                                         else if(url && !urlend)
1537                                                 urlend = p;
1538                                         break;
1539                                 default:
1540                                         if(!pattern)
1541                                                 pattern = p;
1542                                         else if(pattern && patternend && !url)
1543                                                 url = p;
1544                                         break;
1545                         }
1546                         ++p;
1547                 }
1548         }
1549         if(buf)
1550                 Z_Free(buf);
1551         return sv_curl_defaulturl.string;
1552 }
1553
1554 typedef struct requirement_s
1555 {
1556         struct requirement_s *next;
1557         char filename[MAX_OSPATH];
1558 }
1559 requirement;
1560 static requirement *requirements = NULL;
1561
1562
1563 /*
1564 ====================
1565 Curl_RequireFile
1566
1567 Adds the given file to the list of requirements.
1568 ====================
1569 */
1570 void Curl_RequireFile(const char *filename)
1571 {
1572         requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1573         req->next = requirements;
1574         strlcpy(req->filename, filename, sizeof(req->filename));
1575         requirements = req;
1576 }
1577
1578 /*
1579 ====================
1580 Curl_ClearRequirements
1581
1582 Clears the list of required files for playing on the current map.
1583 This should be called at every map change.
1584 ====================
1585 */
1586 void Curl_ClearRequirements(void)
1587 {
1588         while(requirements)
1589         {
1590                 requirement *req = requirements;
1591                 requirements = requirements->next;
1592                 Z_Free(req);
1593         }
1594 }
1595
1596 /*
1597 ====================
1598 Curl_SendRequirements
1599
1600 Makes the current host_clients download all files he needs.
1601 This is done by sending him the following console commands:
1602
1603         curl --clear_autodownload
1604         curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1605         curl --finish_autodownload
1606 ====================
1607 */
1608 static qboolean Curl_SendRequirement(const char *filename, qboolean foundone, char *sendbuffer, size_t sendbuffer_len)
1609 {
1610         const char *p;
1611         const char *thispack = FS_WhichPack(filename);
1612         const char *packurl;
1613
1614         if(!thispack)
1615                 return false;
1616
1617         p = strrchr(thispack, '/');
1618         if(p)
1619                 thispack = p + 1;
1620
1621         packurl = Curl_FindPackURL(thispack);
1622
1623         if(packurl && *packurl && strcmp(packurl, "-"))
1624         {
1625                 if(!foundone)
1626                         strlcat(sendbuffer, "curl --clear_autodownload\n", sendbuffer_len);
1627
1628                 strlcat(sendbuffer, "curl --pak --forthismap --as ", sendbuffer_len);
1629                 strlcat(sendbuffer, thispack, sendbuffer_len);
1630                 if(sv_curl_maxspeed.value > 0)
1631                         dpsnprintf(sendbuffer + strlen(sendbuffer), sendbuffer_len - strlen(sendbuffer), " --maxspeed=%.1f", sv_curl_maxspeed.value);
1632                 strlcat(sendbuffer, " --for ", sendbuffer_len);
1633                 strlcat(sendbuffer, filename, sendbuffer_len);
1634                 strlcat(sendbuffer, " ", sendbuffer_len);
1635                 strlcat(sendbuffer, packurl, sendbuffer_len);
1636                 strlcat(sendbuffer, thispack, sendbuffer_len);
1637                 strlcat(sendbuffer, "\n", sendbuffer_len);
1638
1639                 return true;
1640         }
1641
1642         return false;
1643 }
1644 void Curl_SendRequirements(void)
1645 {
1646         // for each requirement, find the pack name
1647         char sendbuffer[4096] = "";
1648         requirement *req;
1649         qboolean foundone = false;
1650         const char *p;
1651
1652         for(req = requirements; req; req = req->next)
1653                 foundone = Curl_SendRequirement(req->filename, foundone, sendbuffer, sizeof(sendbuffer)) || foundone;
1654
1655         p = sv_curl_serverpackages.string;
1656         while(COM_ParseToken_Simple(&p, false, false))
1657                 foundone = Curl_SendRequirement(com_token, foundone, sendbuffer, sizeof(sendbuffer)) || foundone;
1658
1659         if(foundone)
1660                 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1661
1662         if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1663                 Host_ClientCommands("%s", sendbuffer);
1664         else
1665                 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");
1666 }