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