20 #include <curl/curl.h>
29 static cvar_t *cl_http_debug;
33 static cvar_t *cl_http_blocking_timeout;
37 #define MAX_DLSIZE 0x100000 // 1 MiB
38 #define MIN_DLSIZE 0x8000 // 32 KiB
42 char path[MAX_OSPATH];
78 static int progress_func(
void *clientp,
double dltotal,
double dlnow,
double ultotal,
double ulnow)
96 static size_t recv_func(
void *ptr,
size_t size,
size_t nmemb,
void *stream)
99 size_t new_size, bytes;
104 if (size > SIZE_MAX / nmemb)
110 bytes = size * nmemb;
116 if (new_size > dl->
size) {
128 Com_DPrintf(
"[HTTP] Oversize file while trying to download '%s'\n", dl->
url);
133 static int debug_func(CURL *
c, curl_infotype type,
char *data,
size_t size,
void *ptr)
135 char buffer[MAXPRINTMSG];
137 if (type == CURLINFO_TEXT) {
138 if (size >
sizeof(buffer) - 1)
139 size =
sizeof(buffer) - 1;
140 memcpy(buffer, data, size);
142 Com_LPrintf(PRINT_DEVELOPER,
"[HTTP] %s\n", buffer);
154 static const char allowed[] =
";/?:@&=+$,[]-_.!~*'()";
161 if (!Q_isalnum(
c) && !strchr(allowed,
c)) {
162 sprintf(p,
"%%%02x",
c);
174 static char buffer[32];
181 return "401 Unauthorized";
183 return "403 Forbidden";
185 return "404 Not Found";
187 return "500 Internal Server Error";
189 return "503 Service Unavailable";
192 if (response < 100 || response >= 600) {
193 Q_snprintf(buffer,
sizeof(buffer),
"%d <bad code>", response);
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);
207 Q_snprintf(buffer,
sizeof(buffer),
"%d Server Error", response);
226 char temp[MAX_QPATH];
227 char escaped[MAX_QPATH * 4];
233 if (entry->
type == DL_LIST) {
240 if (len >=
sizeof(dl->
path)) {
241 Com_EPrintf(
"[HTTP] Refusing oversize temporary file path.\n");
247 if (len >=
sizeof(temp)) {
248 Com_EPrintf(
"[HTTP] Refusing oversize server file path.\n");
262 Com_EPrintf(
"[HTTP] Couldn't open '%s' for writing: %s\n", dl->
path, strerror(errno));
268 if (len >=
sizeof(dl->
url)) {
269 Com_EPrintf(
"[HTTP] Refusing oversize download URL.\n");
278 dl->
curl = curl_easy_init();
280 curl_easy_setopt(dl->
curl, CURLOPT_ENCODING,
"");
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);
287 curl_easy_setopt(dl->
curl, CURLOPT_NOPROGRESS, 0);
289 curl_easy_setopt(dl->
curl, CURLOPT_WRITEDATA, dl->
file);
290 curl_easy_setopt(dl->
curl, CURLOPT_WRITEFUNCTION, NULL);
292 curl_easy_setopt(dl->
curl, CURLOPT_WRITEDATA, dl);
293 curl_easy_setopt(dl->
curl, CURLOPT_WRITEFUNCTION,
recv_func);
295 curl_easy_setopt(dl->
curl, CURLOPT_FAILONERROR, 1);
297 curl_easy_setopt(dl->
curl, CURLOPT_FOLLOWLOCATION, 1);
298 curl_easy_setopt(dl->
curl, CURLOPT_MAXREDIRS, 5);
300 curl_easy_setopt(dl->
curl, CURLOPT_PROGRESSDATA, dl);
303 curl_easy_setopt(dl->
curl, CURLOPT_URL, dl->
url);
306 if (ret != CURLM_OK) {
307 Com_EPrintf(
"[HTTP] Failed to add download handle: %s\n",
308 curl_multi_strerror(ret));
317 Com_DPrintf(
"[HTTP] Fetching %s...\n", dl->
url);
333 ssize_t HTTP_FetchFile(
const char *url,
void **data) {
341 curl = curl_easy_init();
345 memset(&tmp, 0,
sizeof(tmp));
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);
357 ret = curl_easy_perform(curl);
359 if (ret == CURLE_HTTP_RETURNED_ERROR)
360 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
362 curl_easy_cleanup(curl);
364 if (ret == CURLE_OK) {
369 Com_EPrintf(
"[HTTP] Failed to fetch '%s': %s\n",
370 url, ret == CURLE_HTTP_RETURNED_ERROR ?
397 for (i = 0; i < 4; i++) {
414 curl_easy_cleanup(dl->
curl);
446 cl_http_debug =
Cvar_Get(
"cl_http_debug",
"0", 0);
450 cl_http_blocking_timeout =
Cvar_Get(
"cl_http_blocking_timeout",
"15", 0);
453 curl_global_init(CURL_GLOBAL_NOTHING);
455 Com_DPrintf(
"%s initialized.\n", curl_version());
465 curl_global_cleanup();
480 Com_EPrintf(
"[HTTP] Set server without cleanup?\n");
508 if (strncmp(url,
"http://", 7)) {
509 Com_Printf(
"[HTTP] Ignoring download server URL with non-HTTP schema.\n");
534 char temp[MAX_QPATH];
549 return Q_ERR_SUCCESS;
554 if (len <
sizeof(temp))
567 if (len > 4 && !Q_stricmp(path + len - 4,
".bsp")) {
569 if (len <
sizeof(temp) - 5) {
570 memcpy(temp + len - 4,
".filelist", 10);
575 return Q_ERR_SUCCESS;
588 if (len >= MAX_QPATH)
591 ext = strrchr(path,
'.');
601 if (!strcmp(
ext,
"pak") || !strcmp(
ext,
"pkz")) {
602 Com_Printf(
"[HTTP] Filelist is requesting a .%s file '%s'\n",
ext, path);
607 Com_WPrintf(
"[HTTP] Illegal file type '%s' in filelist.\n", path);
612 if (path[0] ==
'@') {
613 if (type == DL_PAK) {
614 Com_WPrintf(
"[HTTP] '@' prefix used on a pak file '%s' in filelist.\n", path);
617 flags = FS_PATH_GAME;
620 }
else if (type == DL_PAK) {
622 flags = FS_PATH_GAME | FS_TYPE_REAL;
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);
643 if (FS_FileExistsEx(path, flags))
646 if (valid == PATH_MIXED_CASE)
667 p = strchr(list,
'\n');
669 if (p > list && *(p - 1) ==
'\r')
728 for (i = 0; i < 4; i++) {
730 if (dl->
curl == curl) {
735 Com_Error(ERR_FATAL,
"CURL handle not found for CURLMSG_DONE");
750 char size[16], speed[16];
751 char temp[MAX_OSPATH];
752 qboolean fatal_error = qfalse;
761 if (
msg->msg != CURLMSG_DONE)
764 curl =
msg->easy_handle;
779 result =
msg->data.result;
783 case CURLE_HTTP_RETURNED_ERROR:
785 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
786 if (result == CURLE_OK && response == 200) {
806 case CURLE_COULDNT_RESOLVE_HOST:
807 case CURLE_COULDNT_CONNECT:
808 case CURLE_COULDNT_RESOLVE_PROXY:
810 err = curl_easy_strerror(result);
816 err = curl_easy_strerror(result);
817 level = PRINT_WARNING;
824 "[HTTP] %s [%s] [%d remaining file%s]\n",
847 curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &sec);
848 curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &bytes);
860 Com_Printf(
"[HTTP] %s [%s, %s/sec] [%d remaining file%s]\n",
868 if (rename(dl->
path, temp))
869 Com_EPrintf(
"[HTTP] Failed to rename '%s' to '%s': %s\n",
878 }
else if (!fatal_error) {
881 }
while (msgs_in_queue > 0);
900 for (i = 0; i < 4; i++) {
921 if (q->
type == DL_PAK)
952 ret = curl_multi_perform(
curl_multi, &new_count);
959 }
while (ret == CURLM_CALL_MULTI_PERFORM);
961 if (ret != CURLM_OK) {
962 Com_EPrintf(
"[HTTP] Error running downloads: %s.\n",
963 curl_multi_strerror(ret));