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