Quake II RTX doxygen  1.0 dev
http.c
Go to the documentation of this file.
1 /*
2 Copyright (C) 2008 r1ch.net
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18 
19 #include "client.h"
20 #include <curl/curl.h>
21 
22 static cvar_t *cl_http_downloads;
23 static cvar_t *cl_http_filelists;
24 static cvar_t *cl_http_max_connections;
25 static cvar_t *cl_http_proxy;
26 static cvar_t *cl_http_default_url;
27 
28 #ifdef _DEBUG
29 static cvar_t *cl_http_debug;
30 #endif
31 
32 #if USE_UI
33 static cvar_t *cl_http_blocking_timeout;
34 #endif
35 
36 // size limits for filelists, must be power of two
37 #define MAX_DLSIZE 0x100000 // 1 MiB
38 #define MIN_DLSIZE 0x8000 // 32 KiB
39 
40 typedef struct {
41  CURL *curl;
42  char path[MAX_OSPATH];
43  FILE *file;
45  size_t size;
46  size_t position;
47  char url[576];
48  char *buffer;
49  qboolean multi_added; //to prevent multiple removes
50 } dlhandle_t;
51 
52 static dlhandle_t download_handles[4]; //actual download handles, don't raise this!
53 static char download_server[512]; //base url prefix to download from
54 static char download_referer[32]; //libcurl requires a static string :(
55 static qboolean download_default_repo;
56 
57 static qboolean curl_initialized;
58 static CURLM *curl_multi;
59 static int curl_handles;
60 
61 
62 /*
63 ===============================
64 R1Q2 HTTP Downloading Functions
65 ===============================
66 HTTP downloading is used if the server provides a content
67 server url in the connect message. Any missing content the
68 client needs will then use the HTTP server instead of auto
69 downloading via UDP. CURL is used to enable multiple files
70 to be downloaded in parallel to improve performance on high
71 latency links when small files such as textures are needed.
72 Since CURL natively supports gzip content encoding, any files
73 on the HTTP server should ideally be gzipped to conserve
74 bandwidth.
75 */
76 
77 // libcurl callback to update progress info.
78 static int progress_func(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
79 {
80  dlhandle_t *dl = (dlhandle_t *)clientp;
81 
82  //don't care which download shows as long as something does :)
83  cls.download.current = dl->queue;
84 
85  if (dltotal)
86  cls.download.percent = (int)((dlnow / dltotal) * 100.0);
87  else
88  cls.download.percent = 0;
89 
90  cls.download.position = (int)dlnow;
91 
92  return 0;
93 }
94 
95 // libcurl callback for filelists.
96 static size_t recv_func(void *ptr, size_t size, size_t nmemb, void *stream)
97 {
98  dlhandle_t *dl = (dlhandle_t *)stream;
99  size_t new_size, bytes;
100 
101  if (!nmemb)
102  return 0;
103 
104  if (size > SIZE_MAX / nmemb)
105  goto oversize;
106 
107  if (dl->position > MAX_DLSIZE)
108  goto oversize;
109 
110  bytes = size * nmemb;
111  if (bytes >= MAX_DLSIZE - dl->position)
112  goto oversize;
113 
114  // grow buffer in MIN_DLSIZE chunks. +1 for NUL.
115  new_size = (dl->position + bytes + MIN_DLSIZE) & ~(MIN_DLSIZE - 1);
116  if (new_size > dl->size) {
117  dl->size = new_size;
118  dl->buffer = Z_Realloc(dl->buffer, new_size);
119  }
120 
121  memcpy(dl->buffer + dl->position, ptr, bytes);
122  dl->position += bytes;
123  dl->buffer[dl->position] = 0;
124 
125  return bytes;
126 
127 oversize:
128  Com_DPrintf("[HTTP] Oversize file while trying to download '%s'\n", dl->url);
129  return 0;
130 }
131 
132 #ifdef _DEBUG
133 static int debug_func(CURL *c, curl_infotype type, char *data, size_t size, void *ptr)
134 {
135  char buffer[MAXPRINTMSG];
136 
137  if (type == CURLINFO_TEXT) {
138  if (size > sizeof(buffer) - 1)
139  size = sizeof(buffer) - 1;
140  memcpy(buffer, data, size);
141  buffer[size] = 0;
142  Com_LPrintf(PRINT_DEVELOPER, "[HTTP] %s\n", buffer);
143  }
144 
145  return 0;
146 }
147 #endif
148 
149 // Properly escapes a path with HTTP %encoding. libcurl's function
150 // seems to treat '/' and such as illegal chars and encodes almost
151 // the entire url...
152 static void escape_path(const char *path, char *escaped)
153 {
154  static const char allowed[] = ";/?:@&=+$,[]-_.!~*'()";
155  int c;
156  char *p;
157 
158  p = escaped;
159  while (*path) {
160  c = *path++;
161  if (!Q_isalnum(c) && !strchr(allowed, c)) {
162  sprintf(p, "%%%02x", c);
163  p += 3;
164  } else {
165  *p++ = c;
166  }
167  }
168  *p = 0;
169 }
170 
171 // curl doesn't provide a way to convert HTTP response code to string...
172 static const char *http_strerror(int response)
173 {
174  static char buffer[32];
175 
176  //common codes
177  switch (response) {
178  case 200:
179  return "200 OK";
180  case 401:
181  return "401 Unauthorized";
182  case 403:
183  return "403 Forbidden";
184  case 404:
185  return "404 Not Found";
186  case 500:
187  return "500 Internal Server Error";
188  case 503:
189  return "503 Service Unavailable";
190  }
191 
192  if (response < 100 || response >= 600) {
193  Q_snprintf(buffer, sizeof(buffer), "%d <bad code>", response);
194  return buffer;
195  }
196 
197  //generic classes
198  if (response < 200) {
199  Q_snprintf(buffer, sizeof(buffer), "%d Informational", response);
200  } else if (response < 300) {
201  Q_snprintf(buffer, sizeof(buffer), "%d Success", response);
202  } else if (response < 400) {
203  Q_snprintf(buffer, sizeof(buffer), "%d Redirection", response);
204  } else if (response < 500) {
205  Q_snprintf(buffer, sizeof(buffer), "%d Client Error", response);
206  } else {
207  Q_snprintf(buffer, sizeof(buffer), "%d Server Error", response);
208  }
209 
210  return buffer;
211 }
212 
213 // Use "baseq2" instead of empty gamedir consistently for all kinds of downloads.
214 static const char *http_gamedir(void)
215 {
216  if (*fs_game->string)
217  return fs_game->string;
218 
219  return BASEGAME;
220 }
221 
222 // Actually starts a download by adding it to the curl multi handle.
223 static void start_download(dlqueue_t *entry, dlhandle_t *dl)
224 {
225  size_t len;
226  char temp[MAX_QPATH];
227  char escaped[MAX_QPATH * 4];
228  CURLMcode ret;
229  qerror_t err;
230 
231  //yet another hack to accomodate filelists, how i wish i could push :(
232  //NULL file handle indicates filelist.
233  if (entry->type == DL_LIST) {
234  dl->file = NULL;
235  dl->path[0] = 0;
236  //filelist paths are absolute
237  escape_path(entry->path, escaped);
238  } else {
239  len = Q_snprintf(dl->path, sizeof(dl->path), "%s/%s.tmp", fs_gamedir, entry->path);
240  if (len >= sizeof(dl->path)) {
241  Com_EPrintf("[HTTP] Refusing oversize temporary file path.\n");
242  goto fail;
243  }
244 
245  //prepend quake path with gamedir
246  len = Q_snprintf(temp, sizeof(temp), "%s/%s", http_gamedir(), entry->path);
247  if (len >= sizeof(temp)) {
248  Com_EPrintf("[HTTP] Refusing oversize server file path.\n");
249  goto fail;
250  }
251  escape_path(temp, escaped);
252 
253  err = FS_CreatePath(dl->path);
254  if (err < 0) {
255  Com_EPrintf("[HTTP] Couldn't create path to '%s': %s\n", dl->path, Q_ErrorString(err));
256  goto fail;
257  }
258 
259  //don't bother with http resume... too annoying if server doesn't support it.
260  dl->file = fopen(dl->path, "wb");
261  if (!dl->file) {
262  Com_EPrintf("[HTTP] Couldn't open '%s' for writing: %s\n", dl->path, strerror(errno));
263  goto fail;
264  }
265  }
266 
267  len = Q_snprintf(dl->url, sizeof(dl->url), "%s%s", download_server, escaped);
268  if (len >= sizeof(dl->url)) {
269  Com_EPrintf("[HTTP] Refusing oversize download URL.\n");
270  goto fail;
271  }
272 
273  dl->buffer = NULL;
274  dl->size = 0;
275  dl->position = 0;
276  dl->queue = entry;
277  if (!dl->curl)
278  dl->curl = curl_easy_init();
279 
280  curl_easy_setopt(dl->curl, CURLOPT_ENCODING, "");
281 #ifdef _DEBUG
282  if (cl_http_debug->integer) {
283  curl_easy_setopt(dl->curl, CURLOPT_DEBUGFUNCTION, debug_func);
284  curl_easy_setopt(dl->curl, CURLOPT_VERBOSE, 1);
285  }
286 #endif
287  curl_easy_setopt(dl->curl, CURLOPT_NOPROGRESS, 0);
288  if (dl->file) {
289  curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl->file);
290  curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, NULL);
291  } else {
292  curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl);
293  curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, recv_func);
294  }
295  curl_easy_setopt(dl->curl, CURLOPT_FAILONERROR, 1);
296  curl_easy_setopt(dl->curl, CURLOPT_PROXY, cl_http_proxy->string);
297  curl_easy_setopt(dl->curl, CURLOPT_FOLLOWLOCATION, 1);
298  curl_easy_setopt(dl->curl, CURLOPT_MAXREDIRS, 5);
299  curl_easy_setopt(dl->curl, CURLOPT_PROGRESSFUNCTION, progress_func);
300  curl_easy_setopt(dl->curl, CURLOPT_PROGRESSDATA, dl);
301  curl_easy_setopt(dl->curl, CURLOPT_USERAGENT, com_version->string);
302  curl_easy_setopt(dl->curl, CURLOPT_REFERER, download_referer);
303  curl_easy_setopt(dl->curl, CURLOPT_URL, dl->url);
304 
305  ret = curl_multi_add_handle(curl_multi, dl->curl);
306  if (ret != CURLM_OK) {
307  Com_EPrintf("[HTTP] Failed to add download handle: %s\n",
308  curl_multi_strerror(ret));
309 fail:
310  CL_FinishDownload(entry);
311 
312  // see if we have more to dl
314  return;
315  }
316 
317  Com_DPrintf("[HTTP] Fetching %s...\n", dl->url);
318  entry->state = DL_RUNNING;
319  dl->multi_added = qtrue;
320  curl_handles++;
321 }
322 
323 #if USE_UI
324 
325 /*
326 ===============
327 HTTP_FetchFile
328 
329 Fetches data from an arbitrary URL in a blocking fashion. Doesn't touch any
330 global variables and thus doesn't interfere with existing client downloads.
331 ===============
332 */
333 ssize_t HTTP_FetchFile(const char *url, void **data) {
334  dlhandle_t tmp;
335  CURL *curl;
336  CURLcode ret;
337  long response;
338 
339  *data = NULL;
340 
341  curl = curl_easy_init();
342  if (!curl)
343  return -1;
344 
345  memset(&tmp, 0, sizeof(tmp));
346 
347  curl_easy_setopt(curl, CURLOPT_ENCODING, "");
348  curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
349  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &tmp);
350  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, recv_func);
351  curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
352  curl_easy_setopt(curl, CURLOPT_PROXY, cl_http_proxy->string);
353  curl_easy_setopt(curl, CURLOPT_USERAGENT, com_version->string);
354  curl_easy_setopt(curl, CURLOPT_URL, url);
355  curl_easy_setopt(curl, CURLOPT_TIMEOUT, cl_http_blocking_timeout->integer);
356 
357  ret = curl_easy_perform(curl);
358 
359  if (ret == CURLE_HTTP_RETURNED_ERROR)
360  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
361 
362  curl_easy_cleanup(curl);
363 
364  if (ret == CURLE_OK) {
365  *data = tmp.buffer;
366  return tmp.position;
367  }
368 
369  Com_EPrintf("[HTTP] Failed to fetch '%s': %s\n",
370  url, ret == CURLE_HTTP_RETURNED_ERROR ?
371  http_strerror(response) : curl_easy_strerror(ret));
372  if (tmp.buffer)
373  Z_Free(tmp.buffer);
374  return -1;
375 }
376 
377 #endif
378 
379 /*
380 ===============
381 HTTP_CleanupDownloads
382 
383 Disconnected from server, or fatal HTTP error occured. Clean up.
384 ===============
385 */
387 {
388  dlhandle_t *dl;
389  int i;
390 
391  download_server[0] = 0;
392  download_referer[0] = 0;
393  download_default_repo = qfalse;
394 
395  curl_handles = 0;
396 
397  for (i = 0; i < 4; i++) {
398  dl = &download_handles[i];
399 
400  if (dl->file) {
401  fclose(dl->file);
402  remove(dl->path);
403  dl->file = NULL;
404  }
405 
406  if (dl->buffer) {
407  Z_Free(dl->buffer);
408  dl->buffer = NULL;
409  }
410 
411  if (dl->curl) {
412  if (curl_multi && dl->multi_added)
413  curl_multi_remove_handle(curl_multi, dl->curl);
414  curl_easy_cleanup(dl->curl);
415  dl->curl = NULL;
416  }
417 
418  dl->queue = NULL;
419  dl->multi_added = qfalse;
420  }
421 
422  if (curl_multi) {
423  curl_multi_cleanup(curl_multi);
424  curl_multi = NULL;
425  }
426 }
427 
428 
429 /*
430 ===============
431 HTTP_Init
432 
433 Init libcurl and multi handle.
434 ===============
435 */
436 void HTTP_Init(void)
437 {
438  cl_http_downloads = Cvar_Get("cl_http_downloads", "1", 0);
439  cl_http_filelists = Cvar_Get("cl_http_filelists", "1", 0);
440  cl_http_max_connections = Cvar_Get("cl_http_max_connections", "2", 0);
441  //cl_http_max_connections->changed = _cl_http_max_connections_changed;
442  cl_http_proxy = Cvar_Get("cl_http_proxy", "", 0);
443  cl_http_default_url = Cvar_Get("cl_http_default_url", "", 0);
444 
445 #ifdef _DEBUG
446  cl_http_debug = Cvar_Get("cl_http_debug", "0", 0);
447 #endif
448 
449 #if USE_UI
450  cl_http_blocking_timeout = Cvar_Get("cl_http_blocking_timeout", "15", 0);
451 #endif
452 
453  curl_global_init(CURL_GLOBAL_NOTHING);
454  curl_initialized = qtrue;
455  Com_DPrintf("%s initialized.\n", curl_version());
456 }
457 
458 void HTTP_Shutdown(void)
459 {
460  if (!curl_initialized)
461  return;
462 
464 
465  curl_global_cleanup();
466  curl_initialized = qfalse;
467 }
468 
469 
470 /*
471 ===============
472 HTTP_SetServer
473 
474 A new server is specified, so we nuke all our state.
475 ===============
476 */
477 void HTTP_SetServer(const char *url)
478 {
479  if (curl_multi) {
480  Com_EPrintf("[HTTP] Set server without cleanup?\n");
481  return;
482  }
483 
484  // ignore on the local server
485  if (NET_IsLocalAddress(&cls.serverAddress))
486  return;
487 
488  // ignore if downloads are permanently disabled
489  if (allow_download->integer == -1)
490  return;
491 
492  // ignore if HTTP downloads are disabled
493  if (cl_http_downloads->integer == 0)
494  return;
495 
496  // use default URL for servers that don't specify one. treat 404 from
497  // default repository as fatal error and revert to UDP downloading.
498  if (!url) {
499  url = cl_http_default_url->string;
500  download_default_repo = qtrue;
501  } else {
502  download_default_repo = qfalse;
503  }
504 
505  if (!*url)
506  return;
507 
508  if (strncmp(url, "http://", 7)) {
509  Com_Printf("[HTTP] Ignoring download server URL with non-HTTP schema.\n");
510  return;
511  }
512 
513  curl_multi = curl_multi_init();
514 
517  "quake2://%s", NET_AdrToString(&cls.serverAddress));
518 
519  Com_Printf("[HTTP] Download server at %s\n", download_server);
520 }
521 
522 /*
523 ===============
524 HTTP_QueueDownload
525 
526 Called from the precache check to queue a download. Return value of
527 Q_ERR_NOSYS will cause standard UDP downloading to be used instead.
528 ===============
529 */
530 qerror_t HTTP_QueueDownload(const char *path, dltype_t type)
531 {
532  size_t len;
533  qboolean need_list;
534  char temp[MAX_QPATH];
535  qerror_t ret;
536 
537  // no http server (or we got booted)
538  if (!curl_multi)
539  return Q_ERR_NOSYS;
540 
541  // first download queued, so we want the mod filelist
542  need_list = LIST_EMPTY(&cls.download.queue);
543 
544  ret = CL_QueueDownload(path, type);
545  if (ret)
546  return ret;
547 
548  if (!cl_http_filelists->integer)
549  return Q_ERR_SUCCESS;
550 
551  if (need_list) {
552  //grab the filelist
553  len = Q_snprintf(temp, sizeof(temp), "%s.filelist", http_gamedir());
554  if (len < sizeof(temp))
555  CL_QueueDownload(temp, DL_LIST);
556 
557  //this is a nasty hack to let the server know what we're doing so admins don't
558  //get confused by a ton of people stuck in CNCT state. it's assumed the server
559  //is running r1q2 if we're even able to do http downloading so hopefully this
560  //won't spew an error msg.
562  CL_ClientCommand("download http\n");
563  }
564 
565  //special case for map file lists, i really wanted a server-push mechanism for this, but oh well
566  len = strlen(path);
567  if (len > 4 && !Q_stricmp(path + len - 4, ".bsp")) {
568  len = Q_snprintf(temp, sizeof(temp), "%s/%s", http_gamedir(), path);
569  if (len < sizeof(temp) - 5) {
570  memcpy(temp + len - 4, ".filelist", 10);
571  CL_QueueDownload(temp, DL_LIST);
572  }
573  }
574 
575  return Q_ERR_SUCCESS;
576 }
577 
578 // Validate a path supplied by a filelist.
579 static void check_and_queue_download(char *path)
580 {
581  size_t len;
582  char *ext;
583  dltype_t type;
584  unsigned flags;
585  int valid;
586 
587  len = strlen(path);
588  if (len >= MAX_QPATH)
589  return;
590 
591  ext = strrchr(path, '.');
592  if (!ext)
593  return;
594 
595  ext++;
596  if (!ext[0])
597  return;
598 
599  Q_strlwr(ext);
600 
601  if (!strcmp(ext, "pak") || !strcmp(ext, "pkz")) {
602  Com_Printf("[HTTP] Filelist is requesting a .%s file '%s'\n", ext, path);
603  type = DL_PAK;
604  } else {
605  type = DL_OTHER;
607  Com_WPrintf("[HTTP] Illegal file type '%s' in filelist.\n", path);
608  return;
609  }
610  }
611 
612  if (path[0] == '@') {
613  if (type == DL_PAK) {
614  Com_WPrintf("[HTTP] '@' prefix used on a pak file '%s' in filelist.\n", path);
615  return;
616  }
617  flags = FS_PATH_GAME;
618  path++;
619  len--;
620  } else if (type == DL_PAK) {
621  //by definition paks are game-local
622  flags = FS_PATH_GAME | FS_TYPE_REAL;
623  } else {
624  flags = 0;
625  }
626 
627  len = FS_NormalizePath(path, path);
628  if (len == 0)
629  return;
630 
631  valid = FS_ValidatePath(path);
632 
633  if (valid == PATH_INVALID ||
634  !Q_ispath(path[0]) ||
635  !Q_ispath(path[len - 1]) ||
636  strstr(path, "..") ||
637  (type == DL_OTHER && !strchr(path, '/')) ||
638  (type == DL_PAK && strchr(path, '/'))) {
639  Com_WPrintf("[HTTP] Illegal path '%s' in filelist.\n", path);
640  return;
641  }
642 
643  if (FS_FileExistsEx(path, flags))
644  return;
645 
646  if (valid == PATH_MIXED_CASE)
647  Q_strlwr(path);
648 
649  if (CL_IgnoreDownload(path))
650  return;
651 
652  CL_QueueDownload(path, type);
653 }
654 
655 // A filelist is in memory, scan and validate it and queue up the files.
656 static void parse_file_list(dlhandle_t *dl)
657 {
658  char *list;
659  char *p;
660 
661  if (!dl->buffer)
662  return;
663 
664  if (cl_http_filelists->integer) {
665  list = dl->buffer;
666  while (*list) {
667  p = strchr(list, '\n');
668  if (p) {
669  if (p > list && *(p - 1) == '\r')
670  *(p - 1) = 0;
671  *p = 0;
672  }
673 
674  if (*list)
676 
677  if (!p)
678  break;
679  list = p + 1;
680  }
681  }
682 
683  Z_Free(dl->buffer);
684  dl->buffer = NULL;
685 }
686 
687 // A pak file just downloaded, let's see if we can remove some stuff from
688 // the queue which is in the .pak.
689 static void rescan_queue(void)
690 {
691  dlqueue_t *q;
692 
693  FOR_EACH_DLQ(q) {
694  if (q->state == DL_PENDING && q->type < DL_LIST && FS_FileExists(q->path))
696  }
697 }
698 
699 // Fatal HTTP error occured, remove any special entries from
700 // queue and fall back to UDP downloading.
701 static void abort_downloads(void)
702 {
703  dlqueue_t *q;
704 
706 
707  cls.download.current = NULL;
708  cls.download.percent = 0;
709  cls.download.position = 0;
710 
711  FOR_EACH_DLQ(q) {
712  if (q->state != DL_DONE && q->type >= DL_LIST)
714  else if (q->state == DL_RUNNING)
715  q->state = DL_PENDING;
716  }
717 
720 }
721 
722 // curl doesn't provide reverse-lookup of the void * ptr, so search for it
723 static dlhandle_t *find_handle(CURL *curl)
724 {
725  size_t i;
726  dlhandle_t *dl;
727 
728  for (i = 0; i < 4; i++) {
729  dl = &download_handles[i];
730  if (dl->curl == curl) {
731  return dl;
732  }
733  }
734 
735  Com_Error(ERR_FATAL, "CURL handle not found for CURLMSG_DONE");
736  return NULL;
737 }
738 
739 // A download finished, find out what it was, whether there were any errors and
740 // if so, how severe. If none, rename file and other such stuff.
741 static qboolean finish_download(void)
742 {
743  int msgs_in_queue;
744  CURLMsg *msg;
745  CURLcode result;
746  dlhandle_t *dl;
747  CURL *curl;
748  long response;
749  double sec, bytes;
750  char size[16], speed[16];
751  char temp[MAX_OSPATH];
752  qboolean fatal_error = qfalse;
753  const char *err;
754  print_type_t level;
755 
756  do {
757  msg = curl_multi_info_read(curl_multi, &msgs_in_queue);
758  if (!msg)
759  break;
760 
761  if (msg->msg != CURLMSG_DONE)
762  continue;
763 
764  curl = msg->easy_handle;
765  dl = find_handle(curl);
766 
767  cls.download.current = NULL;
768  cls.download.percent = 0;
769  cls.download.position = 0;
770 
771  //filelist processing is done on read
772  if (dl->file) {
773  fclose(dl->file);
774  dl->file = NULL;
775  }
776 
777  curl_handles--;
778 
779  result = msg->data.result;
780 
781  switch (result) {
782  //for some reason curl returns CURLE_OK for a 404...
783  case CURLE_HTTP_RETURNED_ERROR:
784  case CURLE_OK:
785  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
786  if (result == CURLE_OK && response == 200) {
787  //success
788  break;
789  }
790 
791  err = http_strerror(response);
792 
793  //404 is non-fatal unless accessing default repository
794  if (response == 404 && (!download_default_repo || !dl->path[0])) {
795  level = PRINT_ALL;
796  goto fail1;
797  }
798 
799  //every other code is treated as fatal
800  //not marking download as done since
801  //we are falling back to UDP
802  level = PRINT_ERROR;
803  fatal_error = qtrue;
804  goto fail2;
805 
806  case CURLE_COULDNT_RESOLVE_HOST:
807  case CURLE_COULDNT_CONNECT:
808  case CURLE_COULDNT_RESOLVE_PROXY:
809  //connection problems are fatal
810  err = curl_easy_strerror(result);
811  level = PRINT_ERROR;
812  fatal_error = qtrue;
813  goto fail2;
814 
815  default:
816  err = curl_easy_strerror(result);
817  level = PRINT_WARNING;
818 fail1:
819  //we mark download as done even if it errored
820  //to prevent multiple attempts.
822 fail2:
824  "[HTTP] %s [%s] [%d remaining file%s]\n",
825  dl->queue->path, err, cls.download.pending,
826  cls.download.pending == 1 ? "" : "s");
827  if (dl->path[0]) {
828  remove(dl->path);
829  dl->path[0] = 0;
830  }
831  if (dl->buffer) {
832  Z_Free(dl->buffer);
833  dl->buffer = NULL;
834  }
835  if (dl->multi_added) {
836  //remove the handle and mark it as such
837  curl_multi_remove_handle(curl_multi, curl);
838  dl->multi_added = qfalse;
839  }
840  continue;
841  }
842 
843  //mark as done
845 
846  //show some stats
847  curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &sec);
848  curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &bytes);
849  if (sec < 0.001)
850  sec = 0.001;
851  Com_FormatSizeLong(size, sizeof(size), bytes);
852  Com_FormatSizeLong(speed, sizeof(speed), bytes / sec);
853 
854  if (dl->multi_added) {
855  //remove the handle and mark it as such
856  curl_multi_remove_handle(curl_multi, curl);
857  dl->multi_added = qfalse;
858  }
859 
860  Com_Printf("[HTTP] %s [%s, %s/sec] [%d remaining file%s]\n",
861  dl->queue->path, size, speed, cls.download.pending,
862  cls.download.pending == 1 ? "" : "s");
863 
864  if (dl->path[0]) {
865  //rename the temp file
866  Q_snprintf(temp, sizeof(temp), "%s/%s", fs_gamedir, dl->queue->path);
867 
868  if (rename(dl->path, temp))
869  Com_EPrintf("[HTTP] Failed to rename '%s' to '%s': %s\n",
870  dl->path, dl->queue->path, strerror(errno));
871  dl->path[0] = 0;
872 
873  //a pak file is very special...
874  if (dl->queue->type == DL_PAK) {
875  CL_RestartFilesystem(qfalse);
876  rescan_queue();
877  }
878  } else if (!fatal_error) {
879  parse_file_list(dl);
880  }
881  } while (msgs_in_queue > 0);
882 
883  //fatal error occured, disable HTTP
884  if (fatal_error) {
885  abort_downloads();
886  return qfalse;
887  }
888 
889  // see if we have more to dl
891  return qtrue;
892 }
893 
894 // Find a free download handle to start another queue entry on.
896 {
897  dlhandle_t *dl;
898  int i;
899 
900  for (i = 0; i < 4; i++) {
901  dl = &download_handles[i];
902  if (!dl->queue || dl->queue->state == DL_DONE)
903  return dl;
904  }
905 
906  return NULL;
907 }
908 
909 // Start another HTTP download if possible.
910 static void start_next_download(void)
911 {
912  dlqueue_t *q;
913 
915  return;
916  }
917 
918  //not enough downloads running, queue some more!
919  FOR_EACH_DLQ(q) {
920  if (q->state == DL_RUNNING) {
921  if (q->type == DL_PAK)
922  break; // hack for pak file single downloading
923  } else if (q->state == DL_PENDING) {
924  dlhandle_t *dl = get_free_handle();
925  if (dl)
926  start_download(q, dl);
927  break;
928  }
929  }
930 }
931 
932 /*
933 ===============
934 HTTP_RunDownloads
935 
936 This calls curl_multi_perform do actually do stuff. Called every frame while
937 connecting to minimise latency. Also starts new downloads if we're not doing
938 the maximum already.
939 ===============
940 */
942 {
943  int new_count;
944  CURLMcode ret;
945 
946  if (!curl_multi)
947  return;
948 
950 
951  do {
952  ret = curl_multi_perform(curl_multi, &new_count);
953  if (new_count < curl_handles) {
954  //hmm, something either finished or errored out.
955  if (!finish_download())
956  return; //aborted
957  curl_handles = new_count;
958  }
959  } while (ret == CURLM_CALL_MULTI_PERFORM);
960 
961  if (ret != CURLM_OK) {
962  Com_EPrintf("[HTTP] Error running downloads: %s.\n",
963  curl_multi_strerror(ret));
964  abort_downloads();
965  return;
966  }
967 
969 }
970 
parse_file_list
static void parse_file_list(dlhandle_t *dl)
Definition: http.c:656
HTTP_RunDownloads
void HTTP_RunDownloads(void)
Definition: http.c:941
dlhandle_t::curl
CURL * curl
Definition: http.c:41
cl_http_downloads
static cvar_t * cl_http_downloads
Definition: http.c:22
NET_AdrToString
char * NET_AdrToString(const netadr_t *a)
Definition: net.c:257
dlqueue_t
Definition: client.h:367
Q_snprintf
size_t Q_snprintf(char *dest, size_t size, const char *fmt,...)
Definition: shared.c:846
Com_FormatSizeLong
size_t Com_FormatSizeLong(char *dest, size_t destsize, off_t bytes)
Definition: utils.c:482
DL_OTHER
@ DL_OTHER
Definition: client.h:351
CL_CheckDownloadExtension
qboolean CL_CheckDownloadExtension(const char *ext)
Definition: download.c:494
CL_IgnoreDownload
qboolean CL_IgnoreDownload(const char *path)
Definition: download.c:95
curl_handles
static int curl_handles
Definition: http.c:59
start_download
static void start_download(dlqueue_t *entry, dlhandle_t *dl)
Definition: http.c:223
curl_initialized
static qboolean curl_initialized
Definition: http.c:57
HTTP_CleanupDownloads
void HTTP_CleanupDownloads(void)
Definition: http.c:386
Cvar_Get
cvar_t * Cvar_Get(const char *var_name, const char *var_value, int flags)
Definition: cvar.c:257
Q_ErrorString
const char * Q_ErrorString(qerror_t error)
Definition: error.c:51
finish_download
static qboolean finish_download(void)
Definition: http.c:741
cl_http_max_connections
static cvar_t * cl_http_max_connections
Definition: http.c:24
dltype_t
dltype_t
Definition: client.h:349
HTTP_SetServer
void HTTP_SetServer(const char *url)
Definition: http.c:477
http_strerror
static const char * http_strerror(int response)
Definition: http.c:172
dlhandle_t::position
size_t position
Definition: http.c:46
HTTP_Init
void HTTP_Init(void)
Definition: http.c:436
ext
char ext[4]
Definition: images.c:657
start_next_download
static void start_next_download(void)
Definition: http.c:910
recv_func
static size_t recv_func(void *ptr, size_t size, size_t nmemb, void *stream)
Definition: http.c:96
check_and_queue_download
static void check_and_queue_download(char *path)
Definition: http.c:579
CL_FinishDownload
void CL_FinishDownload(dlqueue_t *q)
Definition: download.c:115
FS_ValidatePath
int FS_ValidatePath(const char *s)
Definition: files.c:271
rescan_queue
static void rescan_queue(void)
Definition: http.c:689
http_gamedir
static const char * http_gamedir(void)
Definition: http.c:214
com_version
cvar_t * com_version
Definition: common.c:84
HTTP_Shutdown
void HTTP_Shutdown(void)
Definition: http.c:458
download_server
static char download_server[512]
Definition: http.c:53
Com_Error
void Com_Error(error_type_t type, const char *fmt,...)
Definition: g_main.c:258
download_referer
static char download_referer[32]
Definition: http.c:54
client_static_s::current
dlqueue_t * current
Definition: client.h:440
cl_http_filelists
static cvar_t * cl_http_filelists
Definition: http.c:23
dlhandle_t::multi_added
qboolean multi_added
Definition: http.c:49
dlhandle_t
Definition: http.c:40
Z_Free
void Z_Free(void *ptr)
Definition: zone.c:147
get_free_handle
static dlhandle_t * get_free_handle(void)
Definition: http.c:895
DL_DONE
@ DL_DONE
Definition: client.h:364
download_default_repo
static qboolean download_default_repo
Definition: http.c:55
dlqueue_t::state
dlstate_t state
Definition: client.h:370
find_handle
static dlhandle_t * find_handle(CURL *curl)
Definition: http.c:723
fs_game
cvar_t * fs_game
Definition: files.c:202
escape_path
static void escape_path(const char *path, char *escaped)
Definition: http.c:152
client_static_s::pending
int pending
Definition: client.h:439
dlhandle_t::url
char url[576]
Definition: http.c:47
Q_strlcpy
size_t Q_strlcpy(char *dst, const char *src, size_t size)
Definition: shared.c:715
Com_LPrintf
void Com_LPrintf(print_type_t type, const char *fmt,...)
Definition: g_main.c:242
client_static_s::percent
int percent
Definition: client.h:441
CL_StartNextDownload
void CL_StartNextDownload(void)
Definition: download.c:294
dlqueue_t::path
char path[1]
Definition: client.h:371
MAX_DLSIZE
#define MAX_DLSIZE
Definition: http.c:37
dlhandle_t::queue
dlqueue_t * queue
Definition: http.c:44
dlhandle_t::path
char path[MAX_OSPATH]
Definition: http.c:42
HTTP_QueueDownload
qerror_t HTTP_QueueDownload(const char *path, dltype_t type)
Definition: http.c:530
cls
client_static_t cls
Definition: main.c:98
FOR_EACH_DLQ
#define FOR_EACH_DLQ(q)
Definition: client.h:344
c
statCounters_t c
Definition: main.c:30
DL_RUNNING
@ DL_RUNNING
Definition: client.h:363
client_static_s::queue
list_t queue
Definition: client.h:438
CL_RestartFilesystem
void CL_RestartFilesystem(qboolean total)
Definition: main.c:2418
CL_RequestNextDownload
void CL_RequestNextDownload(void)
Definition: download.c:735
client_static_s::download
struct client_static_s::@2 download
curl_multi
static CURLM * curl_multi
Definition: http.c:58
level
level_locals_t level
Definition: g_main.c:22
client.h
dlhandle_t::buffer
char * buffer
Definition: http.c:48
cl_http_default_url
static cvar_t * cl_http_default_url
Definition: http.c:26
err
int err
Definition: win.h:24
allow_download
cvar_t * allow_download
Definition: common.c:105
progress_func
static int progress_func(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
Definition: http.c:78
abort_downloads
static void abort_downloads(void)
Definition: http.c:701
DL_PENDING
@ DL_PENDING
Definition: client.h:362
msg
const char * msg
Definition: win.h:25
CL_ClientCommand
void CL_ClientCommand(const char *string)
Definition: main.c:299
FS_CreatePath
qerror_t FS_CreatePath(char *path)
Definition: files.c:605
client_static_s::serverAddress
netadr_t serverAddress
Definition: client.h:409
CL_QueueDownload
qerror_t CL_QueueDownload(const char *path, dltype_t type)
Definition: download.c:49
int
CONST PIXELFORMATDESCRIPTOR int
Definition: wgl.c:26
cl_http_proxy
static cvar_t * cl_http_proxy
Definition: http.c:25
dlqueue_t::type
dltype_t type
Definition: client.h:369
Z_Realloc
void * Z_Realloc(void *ptr, size_t size)
Definition: zone.c:178
fs_gamedir
char fs_gamedir[MAX_OSPATH]
Definition: files.c:171
client_static_s::position
int position
Definition: client.h:442
download_handles
static dlhandle_t download_handles[4]
Definition: http.c:52
FS_NormalizePath
size_t FS_NormalizePath(char *out, const char *in)
Definition: files.c:331
dlhandle_t::size
size_t size
Definition: http.c:45
MIN_DLSIZE
#define MIN_DLSIZE
Definition: http.c:38
dlhandle_t::file
FILE * file
Definition: http.c:43