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