restrict the protocols libcurl allows when redirecting for security reasons, and...
[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                 const char *p, *q;
699                 size_t length;
700                 downloadinfo *di;
701
702                 // Note: This extraction of the file name portion is NOT entirely correct.
703                 //
704                 // It does the following:
705                 //
706                 //   http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3
707                 //   http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3
708                 //   http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php
709                 //
710                 // However, I'd like to keep this "buggy" behavior so that PHP script
711                 // authors can write download scripts without having to enable
712                 // AcceptPathInfo on Apache. They just have to ensure that their script
713                 // can be called with such a "fake" path name like
714                 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3
715                 //
716                 // By the way, such PHP scripts should either send the file or a
717                 // "Location:" redirect; PHP code example:
718                 //
719                 //   header("Location: http://www.example.com/");
720                 //
721                 // By the way, this will set User-Agent to something like
722                 // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to
723                 // dp://serverhost:serverport/ so you can filter on this; an example
724                 // httpd log file line might be:
725                 //
726                 //   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"
727
728                 if(!name)
729                         name = URL;
730
731                 if(!buf)
732                 {
733                         p = strrchr(name, '/');
734                         p = p ? (p+1) : name;
735                         q = strchr(p, '?');
736                         length = q ? (size_t)(q - p) : strlen(p);
737                         dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p);
738
739                         name = fn; // make it point back
740
741                         // already downloading the file?
742                         {
743                                 downloadinfo *di = Curl_Find(fn);
744                                 if(di)
745                                 {
746                                         Con_Printf("Can't download %s, already getting it from %s!\n", fn, di->url);
747
748                                         // however, if it was not for this map yet...
749                                         if(forthismap && !di->forthismap)
750                                         {
751                                                 di->forthismap = true;
752                                                 // this "fakes" a download attempt so the client will wait for
753                                                 // the download to finish and then reconnect
754                                                 ++numdownloads_added;
755                                         }
756
757                                         return false;
758                                 }
759                         }
760
761                         if(ispak && FS_FileExists(fn))
762                         {
763                                 qboolean already_loaded;
764                                 if(FS_AddPack(fn, &already_loaded, true))
765                                 {
766                                         Con_DPrintf("%s already exists, not downloading!\n", fn);
767                                         if(already_loaded)
768                                                 Con_DPrintf("(pak was already loaded)\n");
769                                         else
770                                         {
771                                                 if(forthismap)
772                                                 {
773                                                         ++numdownloads_added;
774                                                         ++numdownloads_success;
775                                                 }
776                                         }
777
778                                         return false;
779                                 }
780                                 else
781                                 {
782                                         qfile_t *f = FS_OpenRealFile(fn, "rb", false);
783                                         if(f)
784                                         {
785                                                 char buf[4] = {0};
786                                                 FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp
787
788                                                 if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4))
789                                                 {
790                                                         Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
791                                                         FS_Close(f);
792                                                         f = FS_OpenRealFile(fn, "wb", false);
793                                                         if(f)
794                                                                 FS_Close(f);
795                                                 }
796                                                 else
797                                                 {
798                                                         // OK
799                                                         FS_Close(f);
800                                                 }
801                                         }
802                                 }
803                         }
804                 }
805
806                 // if we get here, we actually want to download... so first verify the
807                 // URL scheme (so one can't read local files using file://)
808                 if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8))
809                 {
810                         Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL);
811                         return false;
812                 }
813
814                 if(forthismap)
815                         ++numdownloads_added;
816                 di = (downloadinfo *) Z_Malloc(sizeof(*di));
817                 strlcpy(di->filename, name, sizeof(di->filename));
818                 strlcpy(di->url, URL, sizeof(di->url));
819                 dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid");
820                 di->forthismap = forthismap;
821                 di->stream = NULL;
822                 di->startpos = 0;
823                 di->curle = NULL;
824                 di->started = false;
825                 di->ispak = (ispak && !buf);
826                 di->bytes_received = 0;
827                 di->next = downloads;
828                 di->prev = NULL;
829                 if(di->next)
830                         di->next->prev = di;
831
832                 di->buffer = buf;
833                 di->buffersize = bufsize;
834                 if(callback == NULL)
835                 {
836                         di->callback = curl_default_callback;
837                         di->callback_data = di;
838                 }
839                 else
840                 {
841                         di->callback = callback;
842                         di->callback_data = cbdata;
843                 }
844
845                 downloads = di;
846                 return true;
847         }
848 }
849
850 qboolean Curl_Begin_ToFile(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
851 {
852         return Curl_Begin(URL, name, ispak, forthismap, NULL, 0, NULL, NULL);
853 }
854 qboolean Curl_Begin_ToMemory(const char *URL, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
855 {
856         return Curl_Begin(URL, NULL, false, false, buf, bufsize, callback, cbdata);
857 }
858
859 /*
860 ====================
861 Curl_Run
862
863 call this regularily as this will always download as much as possible without
864 blocking.
865 ====================
866 */
867 void Curl_Run(void)
868 {
869         noclear = FALSE;
870
871         if(!cl_curl_enabled.integer)
872                 return;
873
874         if(!curl_dll)
875                 return;
876
877         Curl_CheckCommandWhenDone();
878
879         if(!downloads)
880                 return;
881
882         if(realtime < curltime) // throttle
883                 return;
884
885         {
886                 int remaining;
887                 CURLMcode mc;
888
889                 do
890                 {
891                         mc = qcurl_multi_perform(curlm, &remaining);
892                 }
893                 while(mc == CURLM_CALL_MULTI_PERFORM);
894
895                 for(;;)
896                 {
897                         CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining);
898                         if(!msg)
899                                 break;
900                         if(msg->msg == CURLMSG_DONE)
901                         {
902                                 downloadinfo *di;
903                                 CurlStatus failed = CURL_DOWNLOAD_SUCCESS;
904                                 CURLcode result;
905                                 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di);
906                                 result = msg->data.result;
907                                 if(result)
908                                 {
909                                         failed = CURL_DOWNLOAD_FAILED;
910                                 }
911                                 else
912                                 {
913                                         long code;
914                                         qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
915                                         switch(code / 100)
916                                         {
917                                                 case 4: // e.g. 404?
918                                                 case 5: // e.g. 500?
919                                                         failed = CURL_DOWNLOAD_SERVERERROR;
920                                                         result = (CURLcode) code;
921                                                         break;
922                                         }
923                                 }
924
925                                 Curl_EndDownload(di, failed, result);
926                         }
927                 }
928         }
929
930         CheckPendingDownloads();
931
932         // when will we curl the next time?
933         // we will wait a bit to ensure our download rate is kept.
934         // we now know that realtime >= curltime... so set up a new curltime
935         if(cl_curl_maxspeed.value > 0)
936         {
937                 unsigned long bytes = bytes_received; // maybe smoothen a bit?
938                 curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0);
939                 bytes_received -= bytes;
940         }
941         else
942                 curltime = realtime;
943 }
944
945 /*
946 ====================
947 Curl_CancelAll
948
949 Stops ALL downloads.
950 ====================
951 */
952 void Curl_CancelAll(void)
953 {
954         if(!curl_dll)
955                 return;
956
957         while(downloads)
958         {
959                 Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK);
960                 // INVARIANT: downloads will point to the next download after that!
961         }
962 }
963
964 /*
965 ====================
966 Curl_Running
967
968 returns true iff there is a download running.
969 ====================
970 */
971 qboolean Curl_Running(void)
972 {
973         if(!curl_dll)
974                 return false;
975
976         return downloads != NULL;
977 }
978
979 /*
980 ====================
981 Curl_GetDownloadAmount
982
983 returns a value from 0.0 to 1.0 which represents the downloaded amount of data
984 for the given download.
985 ====================
986 */
987 static double Curl_GetDownloadAmount(downloadinfo *di)
988 {
989         if(!curl_dll)
990                 return -2;
991         if(di->curle)
992         {
993                 double length;
994                 qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
995                 if(length > 0)
996                         return (di->startpos + di->bytes_received) / (di->startpos + length);
997                 else
998                         return 0;
999         }
1000         else
1001                 return -1;
1002 }
1003
1004 /*
1005 ====================
1006 Curl_GetDownloadSpeed
1007
1008 returns the speed of the given download in bytes per second
1009 ====================
1010 */
1011 static double Curl_GetDownloadSpeed(downloadinfo *di)
1012 {
1013         if(!curl_dll)
1014                 return -2;
1015         if(di->curle)
1016         {
1017                 double speed;
1018                 qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed);
1019                 return speed;
1020         }
1021         else
1022                 return -1;
1023 }
1024
1025 /*
1026 ====================
1027 Curl_Info_f
1028
1029 prints the download list
1030 ====================
1031 */
1032 // TODO rewrite using Curl_GetDownloadInfo?
1033 static void Curl_Info_f(void)
1034 {
1035         downloadinfo *di;
1036         if(!curl_dll)
1037                 return;
1038         if(Curl_Running())
1039         {
1040                 Con_Print("Currently running downloads:\n");
1041                 for(di = downloads; di; di = di->next)
1042                 {
1043                         double speed, percent;
1044                         Con_Printf("  %s -> %s ",  di->url, di->filename);
1045                         percent = 100.0 * Curl_GetDownloadAmount(di);
1046                         speed = Curl_GetDownloadSpeed(di);
1047                         if(percent >= 0)
1048                                 Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0);
1049                         else
1050                                 Con_Print("(queued)\n");
1051                 }
1052         }
1053         else
1054         {
1055                 Con_Print("No downloads running.\n");
1056         }
1057 }
1058
1059 /*
1060 ====================
1061 Curl_Curl_f
1062
1063 implements the "curl" console command
1064
1065 curl --info
1066 curl --cancel
1067 curl --cancel filename
1068 curl url
1069
1070 For internal use:
1071
1072 curl [--pak] [--forthismap] [--for filename filename...] url
1073         --pak: after downloading, load the package into the virtual file system
1074         --for filename...: only download of at least one of the named files is missing
1075         --forthismap: don't reconnect on failure
1076
1077 curl --clear_autodownload
1078         clears the download success/failure counters
1079
1080 curl --finish_autodownload
1081         if at least one download has been started, disconnect and drop to the menu
1082         once the last download completes successfully, reconnect to the current server
1083 ====================
1084 */
1085 void Curl_Curl_f(void)
1086 {
1087         int i;
1088         int end;
1089         qboolean pak = false;
1090         qboolean forthismap = false;
1091         const char *url;
1092         const char *name = 0;
1093
1094         if(!curl_dll)
1095         {
1096                 Con_Print("libcurl DLL not found, this command is inactive.\n");
1097                 return;
1098         }
1099
1100         if(!cl_curl_enabled.integer)
1101         {
1102                 Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n");
1103                 return;
1104         }
1105
1106         for(i = 0; i != Cmd_Argc(); ++i)
1107                 Con_DPrintf("%s ", Cmd_Argv(i));
1108         Con_DPrint("\n");
1109
1110         if(Cmd_Argc() < 2)
1111         {
1112                 Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n");
1113                 return;
1114         }
1115
1116         url = Cmd_Argv(Cmd_Argc() - 1);
1117         end = Cmd_Argc();
1118
1119         for(i = 1; i != end; ++i)
1120         {
1121                 const char *a = Cmd_Argv(i);
1122                 if(!strcmp(a, "--info"))
1123                 {
1124                         Curl_Info_f();
1125                         return;
1126                 }
1127                 else if(!strcmp(a, "--cancel"))
1128                 {
1129                         if(i == end - 1) // last argument
1130                                 Curl_CancelAll();
1131                         else
1132                         {
1133                                 downloadinfo *di = Curl_Find(url);
1134                                 if(di)
1135                                         Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
1136                                 else
1137                                         Con_Print("download not found\n");
1138                         }
1139                         return;
1140                 }
1141                 else if(!strcmp(a, "--pak"))
1142                 {
1143                         pak = true;
1144                 }
1145                 else if(!strcmp(a, "--for"))
1146                 {
1147                         for(i = i + 1; i != end - 1; ++i)
1148                         {
1149                                 if(!FS_FileExists(Cmd_Argv(i)))
1150                                         goto needthefile; // why can't I have a "double break"?
1151                         }
1152                         // if we get here, we have all the files...
1153                         return;
1154                 }
1155                 else if(!strcmp(a, "--forthismap"))
1156                 {
1157                         forthismap = true;
1158                 }
1159                 else if(!strcmp(a, "--as"))
1160                 {
1161                         if(i < end - 1)
1162                         {
1163                                 ++i;
1164                                 name = Cmd_Argv(i);
1165                         }
1166                 }
1167                 else if(!strcmp(a, "--clear_autodownload"))
1168                 {
1169                         // mark all running downloads as "not for this map", so if they
1170                         // fail, it does not matter
1171                         Curl_Clear_forthismap();
1172                         return;
1173                 }
1174                 else if(!strcmp(a, "--finish_autodownload"))
1175                 {
1176                         if(numdownloads_added)
1177                         {
1178                                 char donecommand[256];
1179                                 if(cls.netcon)
1180                                 {
1181                                         if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect
1182                                         {
1183                                                 dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address);
1184                                                 Curl_CommandWhenDone(donecommand);
1185                                                 noclear = TRUE;
1186                                                 CL_Disconnect();
1187                                                 noclear = FALSE;
1188                                                 Curl_CheckCommandWhenDone();
1189                                         }
1190                                         else
1191                                                 Curl_Register_predownload();
1192                                 }
1193                         }
1194                         return;
1195                 }
1196                 else if(*a == '-')
1197                 {
1198                         Con_Printf("invalid option %s\n", a);
1199                         return;
1200                 }
1201         }
1202
1203 needthefile:
1204         Curl_Begin_ToFile(url, name, pak, forthismap);
1205 }
1206
1207 /*
1208 static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
1209 {
1210         Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
1211         Z_Free(buffer);
1212 }
1213
1214 void Curl_CurlCat_f(void)
1215 {
1216         unsigned char *buf;
1217         const char *url = Cmd_Argv(1);
1218         buf = Z_Malloc(16384);
1219         Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
1220 }
1221 */
1222
1223 /*
1224 ====================
1225 Curl_Init_Commands
1226
1227 loads the commands and cvars this library uses
1228 ====================
1229 */
1230 void Curl_Init_Commands(void)
1231 {
1232         Cvar_RegisterVariable (&cl_curl_enabled);
1233         Cvar_RegisterVariable (&cl_curl_maxdownloads);
1234         Cvar_RegisterVariable (&cl_curl_maxspeed);
1235         Cvar_RegisterVariable (&sv_curl_defaulturl);
1236         Cvar_RegisterVariable (&sv_curl_serverpackages);
1237         Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
1238         //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
1239 }
1240
1241 /*
1242 ====================
1243 Curl_GetDownloadInfo
1244
1245 returns an array of Curl_downloadinfo_t structs for usage by GUIs.
1246 The number of elements in the array is returned in int *nDownloads.
1247 const char **additional_info may be set to a string of additional user
1248 information, or to NULL if no such display shall occur. The returned
1249 array must be freed later using Z_Free.
1250 ====================
1251 */
1252 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
1253 {
1254         int i;
1255         downloadinfo *di;
1256         Curl_downloadinfo_t *downinfo;
1257         static char addinfo[128];
1258
1259         if(!curl_dll)
1260         {
1261                 *nDownloads = 0;
1262                 if(additional_info)
1263                         *additional_info = NULL;
1264                 return NULL;
1265         }
1266
1267         i = 0;
1268         for(di = downloads; di; di = di->next)
1269                 ++i;
1270
1271         downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
1272         i = 0;
1273         for(di = downloads; di; di = di->next)
1274         {
1275                 // do not show infobars for background downloads
1276                 if(!developer.integer)
1277                         if(di->buffer)
1278                                 continue;
1279                 strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
1280                 if(di->curle)
1281                 {
1282                         downinfo[i].progress = Curl_GetDownloadAmount(di);
1283                         downinfo[i].speed = Curl_GetDownloadSpeed(di);
1284                         downinfo[i].queued = false;
1285                 }
1286                 else
1287                 {
1288                         downinfo[i].queued = true;
1289                 }
1290                 ++i;
1291         }
1292
1293         if(additional_info)
1294         {
1295                 // TODO: can I clear command_when_done as soon as the first download fails?
1296                 if(*command_when_done && !numdownloads_fail && numdownloads_added)
1297                 {
1298                         if(!strncmp(command_when_done, "connect ", 8))
1299                                 dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8);
1300                         else if(!strcmp(command_when_done, "cl_begindownloads"))
1301                                 dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)");
1302                         else
1303                                 dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done);
1304                         *additional_info = addinfo;
1305                 }
1306                 else
1307                         *additional_info = NULL;
1308         }
1309
1310         *nDownloads = i;
1311         return downinfo;
1312 }
1313
1314
1315 /*
1316 ====================
1317 Curl_FindPackURL
1318
1319 finds the URL where to find a given package.
1320
1321 For this, it reads a file "curl_urls.txt" of the following format:
1322
1323         data*.pk3       -
1324         revdm*.pk3      http://revdm/downloads/are/here/
1325         *                       http://any/other/stuff/is/here/
1326
1327 The URLs should end in /. If not, downloads will still work, but the cached files
1328 can't be just put into the data directory with the same download configuration
1329 (you might want to do this if you want to tag downloaded files from your
1330 server, but you should not). "-" means "don't download".
1331
1332 If no single pattern matched, the cvar sv_curl_defaulturl is used as download
1333 location instead.
1334
1335 Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in
1336 this file for obvious reasons.
1337 ====================
1338 */
1339 static const char *Curl_FindPackURL(const char *filename)
1340 {
1341         static char foundurl[1024];
1342         fs_offset_t filesize;
1343         char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize);
1344         if(buf && filesize)
1345         {
1346                 // read lines of format "pattern url"
1347                 char *p = buf;
1348                 char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL;
1349                 qboolean eof = false;
1350
1351                 pattern = p;
1352                 while(!eof)
1353                 {
1354                         switch(*p)
1355                         {
1356                                 case 0:
1357                                         eof = true;
1358                                         // fallthrough
1359                                 case '\n':
1360                                 case '\r':
1361                                         if(pattern && url && patternend)
1362                                         {
1363                                                 if(!urlend)
1364                                                         urlend = p;
1365                                                 *patternend = 0;
1366                                                 *urlend = 0;
1367                                                 if(matchpattern(filename, pattern, true))
1368                                                 {
1369                                                         strlcpy(foundurl, url, sizeof(foundurl));
1370                                                         Z_Free(buf);
1371                                                         return foundurl;
1372                                                 }
1373                                         }
1374                                         pattern = NULL;
1375                                         patternend = NULL;
1376                                         url = NULL;
1377                                         urlend = NULL;
1378                                         break;
1379                                 case ' ':
1380                                 case '\t':
1381                                         if(pattern && !patternend)
1382                                                 patternend = p;
1383                                         else if(url && !urlend)
1384                                                 urlend = p;
1385                                         break;
1386                                 default:
1387                                         if(!pattern)
1388                                                 pattern = p;
1389                                         else if(pattern && patternend && !url)
1390                                                 url = p;
1391                                         break;
1392                         }
1393                         ++p;
1394                 }
1395         }
1396         if(buf)
1397                 Z_Free(buf);
1398         return sv_curl_defaulturl.string;
1399 }
1400
1401 typedef struct requirement_s
1402 {
1403         struct requirement_s *next;
1404         char filename[MAX_OSPATH];
1405 }
1406 requirement;
1407 static requirement *requirements = NULL;
1408
1409
1410 /*
1411 ====================
1412 Curl_RequireFile
1413
1414 Adds the given file to the list of requirements.
1415 ====================
1416 */
1417 void Curl_RequireFile(const char *filename)
1418 {
1419         requirement *req = (requirement *) Z_Malloc(sizeof(*requirements));
1420         req->next = requirements;
1421         strlcpy(req->filename, filename, sizeof(req->filename));
1422         requirements = req;
1423 }
1424
1425 /*
1426 ====================
1427 Curl_ClearRequirements
1428
1429 Clears the list of required files for playing on the current map.
1430 This should be called at every map change.
1431 ====================
1432 */
1433 void Curl_ClearRequirements(void)
1434 {
1435         const char *p;
1436         while(requirements)
1437         {
1438                 requirement *req = requirements;
1439                 requirements = requirements->next;
1440                 Z_Free(req);
1441         }
1442         p = sv_curl_serverpackages.string;
1443         Con_DPrintf("Require all of: %s\n", p);
1444         while(COM_ParseToken_Simple(&p, false, false))
1445         {
1446                 Con_DPrintf("Require: %s\n", com_token);
1447                 Curl_RequireFile(com_token);
1448         }
1449 }
1450
1451 /*
1452 ====================
1453 Curl_SendRequirements
1454
1455 Makes the current host_clients download all files he needs.
1456 This is done by sending him the following console commands:
1457
1458         curl --clear_autodownload
1459         curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3
1460         curl --finish_autodownload
1461 ====================
1462 */
1463 void Curl_SendRequirements(void)
1464 {
1465         // for each requirement, find the pack name
1466         char sendbuffer[4096] = "";
1467         requirement *req;
1468         qboolean foundone = false;
1469
1470         for(req = requirements; req; req = req->next)
1471         {
1472                 const char *p;
1473                 const char *thispack = FS_WhichPack(req->filename);
1474                 const char *packurl;
1475
1476                 if(!thispack)
1477                         continue;
1478
1479                 p = strrchr(thispack, '/');
1480                 if(p)
1481                         thispack = p + 1;
1482
1483                 packurl = Curl_FindPackURL(thispack);
1484
1485                 if(packurl && *packurl && strcmp(packurl, "-"))
1486                 {
1487                         if(!foundone)
1488                                 strlcat(sendbuffer, "curl --clear_autodownload\n", sizeof(sendbuffer));
1489
1490                         strlcat(sendbuffer, "curl --pak --forthismap --as ", sizeof(sendbuffer));
1491                         strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1492                         strlcat(sendbuffer, " --for ", sizeof(sendbuffer));
1493                         strlcat(sendbuffer, req->filename, sizeof(sendbuffer));
1494                         strlcat(sendbuffer, " ", sizeof(sendbuffer));
1495                         strlcat(sendbuffer, packurl, sizeof(sendbuffer));
1496                         strlcat(sendbuffer, thispack, sizeof(sendbuffer));
1497                         strlcat(sendbuffer, "\n", sizeof(sendbuffer));
1498
1499                         foundone = true;
1500                 }
1501         }
1502
1503         if(foundone)
1504                 strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer));
1505
1506         if(strlen(sendbuffer) + 1 < sizeof(sendbuffer))
1507                 Host_ClientCommands("%s", sendbuffer);
1508         else
1509                 Con_Printf("Could not initiate autodownload due to URL buffer overflow\n");
1510 }