/* * Copyright(c) 1997-2001 Id Software, Inc. * Copyright(c) 2002 The Quakeforge Project. * Copyright(c) 2006 Quake2World. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or(at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "client.h" #ifdef __unix__ #include #endif #ifdef HAVE_CURL #include CURLM *curlm; CURL *curl; #endif // generic encapsulation for common http response codes typedef struct response_s { int code; char *text; } response_t; response_t responses[] = { {400, "Bad request"}, {401, "Unauthorized"}, {403, "Forbidden"}, {404, "Not found"}, {500, "Internal server error"}, {0, NULL} }; char curlerr[MAX_STRING_CHARS]; // curl's error buffer char url[MAX_OSPATH]; // remote url to fetch from char file[MAX_OSPATH]; // local path to save to long status, length; // for current transfer qboolean success; /* CL_HttpDownloadRecv */ size_t CL_HttpDownloadRecv(void *buffer, size_t size, size_t nmemb, void *p){ return fwrite(buffer, size, nmemb, cls.download.file); } /* CL_HttpDownload Queue up an http download. The url is resolved from cls.downloadurl and the current gamedir. We use cURL's multi interface, even tho we only ever perform one download at a time, because it is non-blocking. */ qboolean CL_HttpDownload(void){ #ifdef HAVE_CURL char game[64]; if(!curlm) return false; if(!curl) return false; memset(file, 0, sizeof(file)); // resolve local file name snprintf(file, sizeof(file) - 1, "%s/%s", FS_Gamedir(), cls.download.name); FS_CreatePath(file); // create the directory if(!(cls.download.file = fopen(file, "wb"))){ Com_Warn("Failed to open %s.\n", file); return false; // and open the file } cls.download.http = true; memset(game, 0, sizeof(game)); // resolve gamedir strncpy(game, Cvar_VariableString("game"), sizeof(game) - 1); if(!strlen(game)) // use default if not set strcpy(game, "default"); memset(url, 0, sizeof(url)); // construct url snprintf(url, sizeof(url) - 1, "%s/%s/%s", cls.downloadurl, game, cls.download.name); // set handle to default state curl_easy_reset(curl); // set url from which to retrieve the file curl_easy_setopt(curl, CURLOPT_URL, url); // set error buffer so we may print meaningful messages memset(curlerr, 0, sizeof(curlerr)); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlerr); // set the callback for when data is received curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CL_HttpDownloadRecv); curl_multi_add_handle(curlm, curl); return true; #else return false; #endif } /* CL_HttpResponseCode */ char *CL_HttpResponseCode(long code){ int i = 0; while(responses[i].code){ if(responses[i].code == code) return responses[i].text; i++; } return "Unknown"; } /* CL_HttpDownloadCleanup If a download is currently taking place, clean it up. This is called both to finalize completed downloads as well as abort incomplete ones. */ void CL_HttpDownloadCleanup(){ #ifdef HAVE_CURL char *c; if(!cls.download.file || !cls.download.http) return; curl_multi_remove_handle(curlm, curl); // cleanup curl fclose(cls.download.file); // always close the file cls.download.file = NULL; if(success){ cls.download.name[0] = 0; if(strstr(file, ".pak")) // add new paks to search paths FS_AddPakfile(file); } else { // retry via legacy udp download c = strlen(curlerr) ? curlerr : CL_HttpResponseCode(status); Com_DPrintf("Failed to download %s via HTTP: %s.\n" "Trying UDP..", cls.download.name, c); MSG_WriteByte(&cls.netchan.message, clc_stringcmd); MSG_WriteString(&cls.netchan.message, va("download %s", cls.download.name)); unlink(file); // delete partial or empty file } cls.download.percent = 0; cls.download.http = false; status = length = 0; success = false; #endif } /* CL_HttpDownloadThink Process the pending download by giving cURL some time to think. Poll it for feedback on the transfer to determine what action to take. If a transfer fails, stuff a stringcmd to download it via UDP. Since we leave cls.download.tempname in tact during our cleanup, when the download is parsed back in the client, it will fopen and begin. */ void CL_HttpDownloadThink(void){ #ifdef HAVE_CURL CURLMsg *msg; int i; if(!cls.downloadurl[0] || !cls.download.file) return; // nothing to do // process the download as long as data is avaialble while(curl_multi_perform(curlm, &i) == CURLM_CALL_MULTI_PERFORM){} // fail fast on any curl error if(strlen(curlerr)){ CL_HttpDownloadCleanup(); return; } // check for http status code if(!status){ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); if(status > 0 && status != 200){ // 404, 403, etc.. CL_HttpDownloadCleanup(); return; } } // check for completion while((msg = curl_multi_info_read(curlm, &i))){ if(msg->msg == CURLMSG_DONE){ success = true; CL_HttpDownloadCleanup(); CL_RequestNextDownload(); return; } } #endif } /* CL_InitHttpDownload */ void CL_InitHttpDownload(void){ #ifdef HAVE_CURL if(!(curlm = curl_multi_init())) return; if(!(curl = curl_easy_init())) return; #endif } /* CL_ShutdownHttpDownload */ void CL_ShutdownHttpDownload(void){ #ifdef HAVE_CURL CL_HttpDownloadCleanup(); curl_easy_cleanup(curl); curl = NULL; curl_multi_cleanup(curlm); curlm = NULL; #endif }