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