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