1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
|
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (C) 2002 The Quakeforge Project.
* Copyright (C) 2006 Quake2World.
* Copyright (C) 2010 COR Entertainment, LLC.
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "client.h"
#if defined HAVE_UNISTD_H
#include <unistd.h>
#endif
#if defined HAVE_UNLINK && !defined HAVE__UNLINK
#define _unlink unlink
#endif
#include "curl/curl.h"
static CURLM *curlm;
static CURL *curl;
// 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 dnld_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);
}
/*
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){
char game[64];
if(!curlm)
return false;
if(!curl)
return false;
memset(dnld_file, 0, sizeof(dnld_file)); // resolve local file name
Com_sprintf(dnld_file, sizeof(dnld_file) - 1, "%s/%s", FS_Gamedir(), cls.downloadname);
FS_CreatePath(dnld_file); // create the directory
if(!(cls.download = fopen(dnld_file, "wb"))){
Com_Printf("Failed to open %s.\n", dnld_file);
return false; // and open the file
}
cls.downloadhttp = 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, "arena");
memset(url, 0, sizeof(url)); // construct url
Com_sprintf(url, sizeof(url) - 1, "%s/%s/%s", cls.downloadurl, game, cls.downloadname);
// set handle to default state
curl_easy_reset(curl);
// set url from which to retrieve the file
if (curl_easy_setopt(curl, CURLOPT_URL, url) != CURLE_OK) return false;
// time out in 5s
if (curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5) != CURLE_OK) return false;
// set error buffer so we may print meaningful messages
memset(curlerr, 0, sizeof(curlerr));
if (curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlerr) != CURLE_OK) return false;
// set the callback for when data is received
if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CL_HttpDownloadRecv) != CURLE_OK) return false;
if (curl_multi_add_handle(curlm, curl) != CURLM_OK) return false;
return true;
}
/*
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(){
char *c;
if(!cls.download || !cls.downloadhttp)
return;
(void)curl_multi_remove_handle(curlm, curl); // cleanup curl
fclose(cls.download); // always close the file
cls.download = NULL;
if(success){
cls.downloadname[0] = 0;
}
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.downloadname, c);
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
MSG_WriteString(&cls.netchan.message, va("download %s", cls.downloadname));
_unlink(dnld_file); // delete partial or empty file
}
cls.downloadpercent = 0;
cls.downloadhttp = false;
status = length = 0;
success = false;
}
/*
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){
CURLMsg *msg;
int i;
int runt;
if(!cls.downloadurl[0] || !cls.download)
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){
if (curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status) != CURLE_OK ||
(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){
//not so fast, curl gives false positives sometimes
runt = FS_LoadFile (cls.downloadname, NULL);
if(runt > 2048) { //the curl bug produces a 2kb chunk of data
success = true;
CL_HttpDownloadCleanup();
CL_RequestNextDownload();
}
else {
success = false;
CL_HttpDownloadCleanup();
}
return;
}
}
}
/*
CL_InitHttpDownload
*/
void CL_InitHttpDownload(void){
if(!(curlm = curl_multi_init()))
return;
if(!(curl = curl_easy_init()))
return;
}
/*
CL_ShutdownHttpDownload
*/
void CL_ShutdownHttpDownload(void){
CL_HttpDownloadCleanup();
curl_easy_cleanup(curl);
curl = NULL;
(void)curl_multi_cleanup(curlm);
curlm = NULL;
}
|