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