]> icculus.org git repositories - divverent/darkplaces.git/blob - libcurl.c
utf8: add VM_sprintf support
[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_Printf("Download of %s: OK\n", di->filename);
413                         break;
414                 case CURLCBSTATUS_FAILED:
415                         Con_Printf("Download of %s: FAILED\n", di->filename);
416                         break;
417                 case CURLCBSTATUS_ABORTED:
418                         Con_Printf("Download of %s: ABORTED\n", di->filename);
419                         break;
420                 case CURLCBSTATUS_SERVERERROR:
421                         Con_Printf("Download of %s: (unknown server error)\n", di->filename);
422                         break;
423                 case CURLCBSTATUS_UNKNOWN:
424                         Con_Printf("Download of %s: (unknown client error)\n", di->filename);
425                         break;
426                 default:
427                         Con_Printf("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         if(developer.integer)
435                 curl_default_callback(status, length_received, buffer, cbdata);
436 }
437
438 /*
439 ====================
440 Curl_EndDownload
441
442 stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS,
443 CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error
444 code from libcurl, or 0, if another error has occurred.
445 ====================
446 */
447 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);
448 static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error)
449 {
450         qboolean ok = false;
451         if(!curl_dll)
452                 return;
453         switch(status)
454         {
455                 case CURL_DOWNLOAD_SUCCESS:
456                         ok = true;
457                         di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
458                         break;
459                 case CURL_DOWNLOAD_FAILED:
460                         di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
461                         break;
462                 case CURL_DOWNLOAD_ABORTED:
463                         di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
464                         break;
465                 case CURL_DOWNLOAD_SERVERERROR:
466                         // reopen to enforce it to have zero bytes again
467                         if(di->stream)
468                         {
469                                 FS_Close(di->stream);
470                                 di->stream = FS_OpenRealFile(di->filename, "wb", false);
471                         }
472
473                         if(di->callback)
474                                 di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data);
475                         break;
476                 default:
477                         if(di->callback)
478                                 di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data);
479                         break;
480         }
481
482         if(di->curle)
483         {
484                 qcurl_multi_remove_handle(curlm, di->curle);
485                 qcurl_easy_cleanup(di->curle);
486         }
487
488         if(!di->callback && ok && !di->bytes_received)
489         {
490                 Con_Printf("ERROR: empty file\n");
491                 ok = false;
492         }
493
494         if(di->stream)
495                 FS_Close(di->stream);
496
497         if(ok && di->ispak)
498         {
499                 ok = FS_AddPack(di->filename, NULL, true);
500                 if(!ok)
501                 {
502                         // pack loading failed?
503                         // this is critical
504                         // better clear the file again...
505                         di->stream = FS_OpenRealFile(di->filename, "wb", false);
506                         FS_Close(di->stream);
507
508                         if(di->startpos && !di->callback)
509                         {
510                                 // this was a resume?
511                                 // then try to redownload it without reporting the error
512                                 Curl_Begin(di->url, di->filename, di->ispak, di->forthismap, NULL, 0, NULL, NULL);
513                                 di->forthismap = false; // don't count the error
514                         }
515                 }
516         }
517
518         if(di->prev)
519                 di->prev->next = di->next;
520         else
521                 downloads = di->next;
522         if(di->next)
523                 di->next->prev = di->prev;
524
525         --numdownloads;
526         if(di->forthismap)
527         {
528                 if(ok)
529                         ++numdownloads_success;
530                 else
531                         ++numdownloads_fail;
532         }
533         Z_Free(di);
534 }
535
536 /*
537 ====================
538 CheckPendingDownloads
539
540 checks if there are free download slots to start new downloads in.
541 To not start too many downloads at once, only one download is added at a time,
542 up to a maximum number of cl_curl_maxdownloads are running.
543 ====================
544 */
545 static void CheckPendingDownloads(void)
546 {
547         if(!curl_dll)
548                 return;
549         if(numdownloads < cl_curl_maxdownloads.integer)
550         {
551                 downloadinfo *di;
552                 for(di = downloads; di; di = di->next)
553                 {
554                         if(!di->started)
555                         {
556                                 if(!di->buffer)
557                                 {
558                                         Con_Printf("Downloading %s -> %s", di->url, di->filename);
559
560                                         di->stream = FS_OpenRealFile(di->filename, "ab", false);
561                                         if(!di->stream)
562                                         {
563                                                 Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
564                                                 Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
565                                                 return;
566                                         }
567                                         FS_Seek(di->stream, 0, SEEK_END);
568                                         di->startpos = FS_Tell(di->stream);
569
570                                         if(di->startpos > 0)
571                                                 Con_Printf(", resuming from position %ld", (long) di->startpos);
572                                         Con_Print("...\n");
573                                 }
574                                 else
575                                 {
576                                         Con_DPrintf("Downloading %s -> memory\n", di->url);
577                                         di->startpos = 0;
578                                 }
579
580                                 di->curle = qcurl_easy_init();
581                                 qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
582                                 qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
583                                 qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer);
584                                 qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos);
585                                 qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1);
586                                 qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite);
587                                 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256);
588                                 qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45);
589                                 qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di);
590                                 qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di);
591                                 qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP);
592                                 if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK)
593                                 {
594                                         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");
595                                         //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0);
596                                 }
597                                 
598                                 qcurl_multi_add_handle(curlm, di->curle);
599                                 di->started = true;
600                                 ++numdownloads;
601                                 if(numdownloads >= cl_curl_maxdownloads.integer)
602                                         break;
603                         }
604                 }
605         }
606 }
607
608 /*
609 ====================
610 Curl_Init
611
612 this function MUST be called before using anything else in this file.
613 On Win32, this must be called AFTER WSAStartup has been done!
614 ====================
615 */
616 void Curl_Init(void)
617 {
618         CURL_OpenLibrary();
619         if(!curl_dll)
620                 return;
621         qcurl_global_init(CURL_GLOBAL_NOTHING);
622         curlm = qcurl_multi_init();
623 }
624
625 /*
626 ====================
627 Curl_Shutdown
628
629 Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET.
630 ====================
631 */
632 void Curl_ClearRequirements(void);
633 void Curl_Shutdown(void)
634 {
635         if(!curl_dll)
636                 return;
637         Curl_ClearRequirements();
638         Curl_CancelAll();
639         CURL_CloseLibrary();
640         curl_dll = NULL;
641 }
642
643 /*
644 ====================
645 Curl_Find
646
647 Finds the internal information block for a download given by file name.
648 ====================
649 */
650 static downloadinfo *Curl_Find(const char *filename)
651 {
652         downloadinfo *di;
653         if(!curl_dll)
654                 return NULL;
655         for(di = downloads; di; di = di->next)
656                 if(!strcasecmp(di->filename, filename))
657                         return di;
658         return NULL;
659 }
660
661 void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
662 {
663         downloadinfo *di;
664         if(!curl_dll)
665                 return;
666         for(di = downloads; di; )
667         {
668                 if(di->callback == callback && di->callback_data == cbdata)
669                 {
670                         di->callback = curl_quiet_callback; // do NOT call the callback
671                         Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
672                         di = downloads;
673                 }
674                 else
675                         di = di->next;
676         }
677 }
678
679 /*
680 ====================
681 Curl_Begin
682
683 Starts a download of a given URL to the file name portion of this URL (or name
684 if given) in the "dlcache/" folder.
685 ====================
686 */
687 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)
688 {
689         if(!curl_dll)
690         {
691                 return false;
692         }
693         else
694         {
695                 char fn[MAX_OSPATH];
696                 char urlbuf[1024];
697                 const char *p, *q;
698                 size_t length;
699                 downloadinfo *di;
700
701                 // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server
702                 p = strchr(URL, ':');
703                 if(p)
704                 {
705                         if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4))
706                         {
707                                 char addressstring[128];
708                                 *addressstring = 0;
709                                 InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring));
710                                 q = strchr(addressstring, ':');
711                                 if(!q)
712                                         q = addressstring + strlen(addressstring);
713                                 if(*addressstring)
714                                 {
715                                         dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3);
716                                         URL = urlbuf;
717                                 }
718                         }
719                 }
720
721                 // Note: This extraction of the file name portion is NOT entirely correct.
722                 //
723                 // It does the following:
724                 //
725                 //   http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
726                 //   http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
727                 //   http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
728                 //
729                 // However, I'd like to keep this "buggy" behavior so that PHP script
730                 // authors can write download scripts without having to enable
731                 // AcceptPathInfo on Apache. They just have to ensure that their script
732                 // can be called with such a "fake" path name like
733                 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
734                 //
735                 // By the way, such PHP scripts should either send the file or a
736                 // "Location:" redirect; PHP code example:
737                 //
738                 //   header("Location: http://www.example.com/");
739                 //
740                 // By the way, this will set User-Agent to something like
741                 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
742                 // dp://serverhost:serverport/ so you can filter on this; an example
743                 // httpd log file line might be:
744                 //
745                 //   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"
746
747                 if(!name)
748                         name = URL;
749
750                 if(!buf)
751                 {
752                         p = strrchr(name, '/');
753                         p = p ? (p+1) : name;
754                         q = strchr(p, '?');
755                         length = q ? (size_t)(q - p) : strlen(p);
756                         dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
757
758                         name = fn; // make it point back
759
760                         // already downloading the file?
761                         {
762                                 downloadinfo *di = Curl_Find(fn);
763                                 if(di)
764                                 {
765                                         Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
766
767                                         // however, if it was not for this map yet...
768                                         if(forthismap && !di->forthismap)
769                                         {
770                                                 di->forthismap = true;
771                                                 // this "fakes" a download attempt so the client will wait for
772                                                 // the download to finish and then reconnect
773                                                 ++numdownloads_added;
774                                         }
775
776                                         return false;
777                                 }
778                         }
779
780                         if(ispak && FS_FileExists(fn))
781                         {
782                                 qboolean already_loaded;
783                                 if(FS_AddPack(fn, &already_loaded, true))
784                                 {
785                                         Con_DPrintf("%s already exists, not downloading!\n", fn);
786                                         if(already_loaded)
787                                                 Con_DPrintf("(pak was already loaded)\n");
788                                         else
789                                         {
790                                                 if(forthismap)
791                                                 {
792                                                         ++numdownloads_added;
793                                                         ++numdownloads_success;
794                                                 }
795                                         }
796
797                                         return false;
798                                 }
799                                 else
800                                 {
801                                         qfile_t *f = FS_OpenRealFile(fn, "rb", false);
802                                         if(f)
803                                         {
804                                                 char buf[4] = {0};
805                                                 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
806
807                                                 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
808                                                 {
809                                                         Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
810                                                         FS_Close(f);
811                                                         f = FS_OpenRealFile(fn, "wb", false);
812                                                         if(f)
813                                                                 FS_Close(f);
814                                                 }
815                                                 else
816                                                 {
817                                                         // OK
818                                                         FS_Close(f);
819                                                 }
820                                         }
821                                 }
822                         }
823                 }
824
825                 // if we get here, we actually want to download... so first verify the
826                 // URL scheme (so one can't read local files using file://)
827                 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
828                 {
829                         Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
830                         return false;
831                 }
832
833                 if(forthismap)
834                         ++numdownloads_added;
835                 di = (downloadinfo *) Z_Malloc(sizeof(*di));
836                 strlcpy(di->filename, name, sizeof(di->filename));
837                 strlcpy(di->url, URL, sizeof(di->url));
838                 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
839                 di->forthismap = forthismap;
840                 di->stream = NULL;
841                 di->startpos = 0;
842                 di->curle = NULL;
843                 di->started = false;
844                 di->ispak = (ispak && !buf);
845                 di->bytes_received = 0;
846                 di->next = downloads;
847                 di->prev = NULL;
848                 if(di->next)
849                         di->next->prev = di;
850
851                 di->buffer = buf;
852                 di->buffersize = bufsize;
853                 if(callback == NULL)
854                 {
855                         di->callback = curl_default_callback;
856                         di->callback_data = di;
857                 }
858                 else
859                 {
860                         di->callback = callback;
861                         di->callback_data = cbdata;
862                 }
863
864                 downloads = di;
865                 return true;
866         }
867 }
868
869 qboolean Curl_Begin_ToFile(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
870 {
871         return Curl_Begin(URL, name, ispak, forthismap, NULL, 0, NULL, NULL);
872 }
873 qboolean Curl_Begin_ToMemory(const char *URL, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
874 {
875         return Curl_Begin(URL, NULL, false, false, buf, bufsize, callback, cbdata);
876 }
877
878 /*
879 ====================
880 Curl_Run
881
882 call this regularily as this will always download as much as possible without
883 blocking.
884 ====================
885 */
886 void Curl_Run(void)
887 {
888         noclear = FALSE;
889
890         if(!cl_curl_enabled.integer)
891                 return;
892
893         if(!curl_dll)
894                 return;
895
896         Curl_CheckCommandWhenDone();
897
898         if(!downloads)
899                 return;
900
901         if(realtime < curltime) // throttle
902                 return;
903
904         {
905                 int remaining;
906                 CURLMcode mc;
907
908                 do
909                 {
910                         mc = qcurl_multi_perform(curlm, &remaining);
911                 }
912                 while(mc == CURLM_CALL_MULTI_PERFORM);
913
914                 for(;;)
915                 {
916                         CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
917                         if(!msg)
918                                 break;
919                         if(msg->msg == CURLMSG_DONE)
920                         {
921                                 downloadinfo *di;
922                                 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
923                                 CURLcode result;
924                                 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
925                                 result = msg->data.result;
926                                 if(result)
927                                 {
928                                         failed = CURL_DOWNLOAD_FAILED;
929                                 }
930                                 else
931                                 {
932                                         long code;
933                                         qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
934                                         switch(code / 100)
935                                         {
936                                                 case 4: // e.g. 404?
937                                                 case 5: // e.g. 500?
938                                                         failed = CURL_DOWNLOAD_SERVERERROR;
939                                                         result = (CURLcode) code;
940                                                         break;
941                                         }
942                                 }
943
944                                 Curl_EndDownload(di, failed, result);
945                         }
946                 }
947         }
948
949         CheckPendingDownloads();
950
951         // when will we curl the next time?
952         // we will wait a bit to ensure our download rate is kept.
953         // we now know that realtime >= curltime... so set up a new curltime
954         if(cl_curl_maxspeed.value > 0)
955         {
956                 unsigned long bytes = bytes_received; // maybe smoothen a bit?
957                 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
958                 bytes_received -= bytes;
959         }
960         else
961                 curltime = realtime;
962 }
963
964 /*
965 ====================
966 Curl_CancelAll
967
968 Stops ALL downloads.
969 ====================
970 */
971 void Curl_CancelAll(void)
972 {
973         if(!curl_dll)
974                 return;
975
976         while(downloads)
977         {
978                 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
979                 // INVARIANT: downloads will point to the next download after that!
980         }
981 }
982
983 /*
984 ====================
985 Curl_Running
986
987 returns true iff there is a download running.
988 ====================
989 */
990 qboolean Curl_Running(void)
991 {
992         if(!curl_dll)
993                 return false;
994
995         return downloads != NULL;
996 }
997
998 /*
999 ====================
1000 Curl_GetDownloadAmount
1001
1002 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
1003 for the given download.
1004 ====================
1005 */
1006 static double Curl_GetDownloadAmount(downloadinfo *di)
1007 {
1008         if(!curl_dll)
1009                 return -2;
1010         if(di->curle)
1011         {
1012                 double length;
1013                 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1014                 if(length > 0)
1015                         return (di->startpos + di->bytes_received) / (di->startpos + length);
1016                 else
1017                         return 0;
1018         }
1019         else
1020                 return -1;
1021 }
1022
1023 /*
1024 ====================
1025 Curl_GetDownloadSpeed
1026
1027 returns the speed of the given download in bytes per second
1028 ====================
1029 */
1030 static double Curl_GetDownloadSpeed(downloadinfo *di)
1031 {
1032         if(!curl_dll)
1033                 return -2;
1034         if(di->curle)
1035         {
1036                 double speed;
1037                 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1038                 return speed;
1039         }
1040         else
1041                 return -1;
1042 }
1043
1044 /*
1045 ====================
1046 Curl_Info_f
1047
1048 prints the download list
1049 ====================
1050 */
1051 // TODO rewrite using Curl_GetDownloadInfo?
1052 static void Curl_Info_f(void)
1053 {
1054         downloadinfo *di;
1055         if(!curl_dll)
1056                 return;
1057         if(Curl_Running())
1058         {
1059                 Con_Print("Currently running downloads:\n");
1060                 for(di = downloads; di; di = di->next)
1061                 {
1062                         double speed, percent;
1063                         Con_Printf("  %s -> %s ",  di->url, di->filename);
1064                         percent = 100.0 * Curl_GetDownloadAmount(di);
1065                         speed = Curl_GetDownloadSpeed(di);
1066                         if(percent >= 0)
1067                                 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1068                         else
1069                                 Con_Print("(queued)\n");
1070                 }
1071         }
1072         else
1073         {
1074                 Con_Print("No downloads running.\n");
1075         }
1076 }
1077
1078 /*
1079 ====================
1080 Curl_Curl_f
1081
1082 implements the "curl" console command
1083
1084 curl --info
1085 curl --cancel
1086 curl --cancel filename
1087 curl url
1088
1089 For internal use:
1090
1091 curl [--pak] [--forthismap] [--for filename filename...] url
1092         --pak: after downloading, load the package into the virtual file system
1093         --for filename...: only download of at least one of the named files is missing
1094         --forthismap: don't reconnect on failure
1095
1096 curl --clear_autodownload
1097         clears the download success/failure counters
1098
1099 curl --finish_autodownload
1100         if at least one download has been started, disconnect and drop to the menu
1101         once the last download completes successfully, reconnect to the current server
1102 ====================
1103 */
1104 void Curl_Curl_f(void)
1105 {
1106         int i;
1107         int end;
1108         qboolean pak = false;
1109         qboolean forthismap = false;
1110         const char *url;
1111         const char *name = 0;
1112
1113         if(!curl_dll)
1114         {
1115                 Con_Print("libcurl DLL not found, this command is inactive.\n");
1116                 return;
1117         }
1118
1119         if(!cl_curl_enabled.integer)
1120         {
1121                 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1122                 return;
1123         }
1124
1125         for(i = 0; i != Cmd_Argc(); ++i)
1126                 Con_DPrintf("%s ", Cmd_Argv(i));
1127         Con_DPrint("\n");
1128
1129         if(Cmd_Argc() < 2)
1130         {
1131                 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1132                 return;
1133         }
1134
1135         url = Cmd_Argv(Cmd_Argc() - 1);
1136         end = Cmd_Argc();
1137
1138         for(i = 1; i != end; ++i)
1139         {
1140                 const char *a = Cmd_Argv(i);
1141                 if(!strcmp(a, "--info"))
1142                 {
1143                         Curl_Info_f();
1144                         return;
1145                 }
1146                 else if(!strcmp(a, "--cancel"))
1147                 {
1148                         if(i == end - 1) // last argument
1149                                 Curl_CancelAll();
1150                         else
1151                         {
1152                                 downloadinfo *di = Curl_Find(url);
1153                                 if(di)
1154                                         Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1155                                 else
1156                                         Con_Print("download not found\n");
1157                         }
1158                         return;
1159                 }
1160                 else if(!strcmp(a, "--pak"))
1161                 {
1162                         pak = true;
1163                 }
1164                 else if(!strcmp(a, "--for"))
1165                 {
1166                         for(i = i + 1; i != end - 1; ++i)
1167                         {
1168                                 if(!FS_FileExists(Cmd_Argv(i)))
1169                                         goto needthefile; // why can't I have a "double break"?
1170                         }
1171                         // if we get here, we have all the files...
1172                         return;
1173                 }
1174                 else if(!strcmp(a, "--forthismap"))
1175                 {
1176                         forthismap = true;
1177                 }
1178                 else if(!strcmp(a, "--as"))
1179                 {
1180                         if(i < end - 1)
1181                         {
1182                                 ++i;
1183                                 name = Cmd_Argv(i);
1184                         }
1185                 }
1186                 else if(!strcmp(a, "--clear_autodownload"))
1187                 {
1188                         // mark all running downloads as "not for this map", so if they
1189                         // fail, it does not matter
1190                         Curl_Clear_forthismap();
1191                         return;
1192                 }
1193                 else if(!strcmp(a, "--finish_autodownload"))
1194                 {
1195                         if(numdownloads_added)
1196                         {
1197                                 char donecommand[256];
1198                                 if(cls.netcon)
1199                                 {
1200                                         if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1201                                         {
1202                                                 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1203                                                 Curl_CommandWhenDone(donecommand);
1204                                                 noclear = TRUE;
1205                                                 CL_Disconnect();
1206                                                 noclear = FALSE;
1207                                                 Curl_CheckCommandWhenDone();
1208                                         }
1209                                         else
1210                                                 Curl_Register_predownload();
1211                                 }
1212                         }
1213                         return;
1214                 }
1215                 else if(*a == '-')
1216                 {
1217                         Con_Printf("invalid option %s\n", a);
1218                         return;
1219                 }
1220         }
1221
1222 needthefile:
1223         Curl_Begin_ToFile(url, name, pak, forthismap);
1224 }
1225
1226 /*
1227 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1228 {
1229         Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1230         Z_Free(buffer);
1231 }
1232
1233 void Curl_CurlCat_f(void)
1234 {
1235         unsigned char *buf;
1236         const char *url = Cmd_Argv(1);
1237         buf = Z_Malloc(16384);
1238         Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1239 }
1240 */
1241
1242 /*
1243 ====================
1244 Curl_Init_Commands
1245
1246 loads the commands and cvars this library uses
1247 ====================
1248 */
1249 void Curl_Init_Commands(void)
1250 {
1251         Cvar_RegisterVariable (&cl_curl_enabled);
1252         Cvar_RegisterVariable (&cl_curl_maxdownloads);
1253         Cvar_RegisterVariable (&cl_curl_maxspeed);
1254         Cvar_RegisterVariable (&sv_curl_defaulturl);
1255         Cvar_RegisterVariable (&sv_curl_serverpackages);
1256         Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1257         //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1258 }
1259
1260 /*
1261 ====================
1262 Curl_GetDownloadInfo
1263
1264 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1265 The number of elements in the array is returned in int *nDownloads.
1266 const char **additional_info may be set to a string of additional user
1267 information, or to NULL if no such display shall occur. The returned
1268 array must be freed later using Z_Free.
1269 ====================
1270 */
1271 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1272 {
1273         int i;
1274         downloadinfo *di;
1275         Curl_downloadinfo_t *downinfo;
1276         static char addinfo[128];
1277
1278         if(!curl_dll)
1279         {
1280                 *nDownloads = 0;
1281                 if(additional_info)
1282                         *additional_info = NULL;
1283                 return NULL;
1284         }
1285
1286         i = 0;
1287         for(di = downloads; di; di = di->next)
1288                 ++i;
1289
1290         downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1291         i = 0;
1292         for(di = downloads; di; di = di->next)
1293         {
1294                 // do not show infobars for background downloads
1295                 if(!developer.integer)
1296                         if(di->buffer)
1297                                 continue;
1298                 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1299                 if(di->curle)
1300                 {
1301                         downinfo[i].progress = Curl_GetDownloadAmount(di);
1302                         downinfo[i].speed = Curl_GetDownloadSpeed(di);
1303                         downinfo[i].queued = false;
1304                 }
1305                 else
1306                 {
1307                         downinfo[i].queued = true;
1308                 }
1309                 ++i;
1310         }
1311
1312         if(additional_info)
1313         {
1314                 // TODO: can I clear command_when_done as soon as the first download fails?
1315                 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1316                 {
1317                         if(!strncmp(command_when_done, "connect ", 8))
1318                                 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1319                         else if(!strcmp(command_when_done, "cl_begindownloads"))
1320                                 dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
1321                         else
1322                                 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1323                         *additional_info = addinfo;
1324                 }
1325                 else
1326                         *additional_info = NULL;
1327         }
1328
1329         *nDownloads = i;
1330         return downinfo;
1331 }
1332
1333
1334 /*
1335 ====================
1336 Curl_FindPackURL
1337
1338 finds the URL where to find a given package.
1339
1340 For this, it reads a file "curl_urls.txt" of the following format:
1341
1342         data*.pk3       -
1343         revdm*.pk3      http://revdm/downloads/are/here/
1344         *                       http://any/other/stuff/is/here/
1345
1346 The URLs should end in /. If not, downloads will still work, but the cached files
1347 can't be just put into the data directory with the same download configuration
1348 (you might want to do this if you want to tag downloaded files from your
1349 server, but you should not). "-" means "don't download".
1350
1351 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1352 location instead.
1353
1354 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1355 this file for obvious reasons.
1356 ====================
1357 */
1358 static const char *Curl_FindPackURL(const char *filename)
1359 {
1360         static char foundurl[1024];
1361         fs_offset_t filesize;
1362         char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1363         if(buf && filesize)
1364         {
1365                 // read lines of format "pattern url"
1366                 char *p = buf;
1367                 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1368                 qboolean eof = false;
1369
1370                 pattern = p;
1371                 while(!eof)
1372                 {
1373                         switch(*p)
1374                         {
1375                                 case 0:
1376                                         eof = true;
1377                                         // fallthrough
1378                                 case '\n':
1379                                 case '\r':
1380                                         if(pattern && url && patternend)
1381                                         {
1382                                                 if(!urlend)
1383                                                         urlend = p;
1384                                                 *patternend = 0;
1385                                                 *urlend = 0;
1386                                                 if(matchpattern(filename, pattern, true))
1387                                                 {
1388                                                         strlcpy(foundurl, url, sizeof(foundurl));
1389                                                         Z_Free(buf);
1390                                                         return foundurl;
1391                                                 }
1392                                         }
1393                                         pattern = NULL;
1394                                         patternend = NULL;
1395                                         url = NULL;
1396                                         urlend = NULL;
1397                                         break;
1398                                 case ' ':
1399                                 case '\t':
1400                                         if(pattern && !patternend)
1401                                                 patternend = p;
1402                                         else if(url && !urlend)
1403                                                 urlend = p;
1404                                         break;
1405                                 default:
1406                                         if(!pattern)
1407                                                 pattern = p;
1408                                         else if(pattern && patternend && !url)
1409                                                 url = p;
1410                                         break;
1411                         }
1412                         ++p;
1413                 }
1414         }
1415         if(buf)
1416                 Z_Free(buf);
1417         return sv_curl_defaulturl.string;
1418 }
1419
1420 typedef struct requirement_s
1421 {
1422         struct requirement_s *next;
1423         char filename[MAX_OSPATH];
1424 }
1425 requirement;
1426 static requirement *requirements = NULL;
1427
1428
1429 /*
1430 ====================
1431 Curl_RequireFile
1432
1433 Adds the given file to the list of requirements.
1434 ====================
1435 */
1436 void Curl_RequireFile(const char *filename)
1437 {
1438         requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1439         req->next = requirements;
1440         strlcpy(req->filename, filename, sizeof(req->filename));
1441         requirements = req;
1442 }
1443
1444 /*
1445 ====================
1446 Curl_ClearRequirements
1447
1448 Clears the list of required files for playing on the current map.
1449 This should be called at every map change.
1450 ====================
1451 */
1452 void Curl_ClearRequirements(void)
1453 {
1454         const char *p;
1455         while(requirements)
1456         {
1457                 requirement *req = requirements;
1458                 requirements = requirements->next;
1459                 Z_Free(req);
1460         }
1461         p = sv_curl_serverpackages.string;
1462         Con_DPrintf("Require all of: %s\n", p);
1463         while(COM_ParseToken_Simple(&p, false, false))
1464         {
1465                 Con_DPrintf("Require: %s\n", com_token);
1466                 Curl_RequireFile(com_token);
1467         }
1468 }
1469
1470 /*
1471 ====================
1472 Curl_SendRequirements
1473
1474 Makes the current host_clients download all files he needs.
1475 This is done by sending him the following console commands:
1476
1477         curl --clear_autodownload
1478         curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1479         curl --finish_autodownload
1480 ====================
1481 */
1482 void Curl_SendRequirements(void)
1483 {
1484         // for each requirement, find the pack name
1485         char sendbuffer[4096] = "";
1486         requirement *req;
1487         qboolean foundone = false;
1488
1489         for(req = requirements; req; req = req->next)
1490         {
1491                 const char *p;
1492                 const char *thispack = FS_WhichPack(req->filename);
1493                 const char *packurl;
1494
1495                 if(!thispack)
1496                         continue;
1497
1498                 p = strrchr(thispack, '/');
1499                 if(p)
1500                         thispack = p + 1;
1501
1502                 packurl = Curl_FindPackURL(thispack);
1503
1504                 if(packurl && *packurl && strcmp(packurl, "-"))
1505                 {
1506                         if(!foundone)
1507                                 strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
1508
1509                         strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
1510                         strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1511                         strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
1512                         strlcat(sendbuffer, req->filename, sizeof(sendbuffer));
1513                         strlcat(sendbuffer, " ", sizeof(sendbuffer));
1514                         strlcat(sendbuffer, packurl, sizeof(sendbuffer));
1515                         strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1516                         strlcat(sendbuffer, "\n", sizeof(sendbuffer));
1517
1518                         foundone = true;
1519                 }
1520         }
1521
1522         if(foundone)
1523                 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1524
1525         if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1526                 Host_ClientCommands("%s", sendbuffer);
1527         else
1528                 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");
1529 }