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