61 const unsigned char c = *(
string++);
63 if (c >= 32 && c <= 127)
72 if (!isalnum(c) && c !=
'_' && c !=
'-')
81 static int CL_HTTP_Progress (
void* clientp,
double dltotal,
double dlnow,
double ultotal,
double ulnow)
110 size_t len = strlen(filePath);
111 for (
int i = 0;
i <
len;
i++) {
112 if (!isalnum(filePath[
i]) && filePath[i] !=
';' && filePath[i] !=
'/' &&
113 filePath[i] !=
'?' && filePath[i] !=
':' && filePath[i] !=
'@' && filePath[i] !=
'&' &&
114 filePath[i] !=
'=' && filePath[i] !=
'+' && filePath[i] !=
'$' && filePath[i] !=
',' &&
115 filePath[i] !=
'[' && filePath[i] !=
']' && filePath[i] !=
'-' && filePath[i] !=
'_' &&
116 filePath[i] !=
'.' && filePath[i] !=
'!' && filePath[i] !=
'~' && filePath[i] !=
'*' &&
117 filePath[i] !=
'\'' && filePath[i] !=
'(' && filePath[i] !=
')') {
118 sprintf(p,
"%%%02x", filePath[i]);
129 len = strlen(escaped);
131 while ((p = strstr (p,
"./"))) {
132 memmove(p, p + 2, len - (p - escaped) - 1);
147 if (extension !=
nullptr &&
Q_streq(extension,
"filelist")) {
179 dl->
curl = curl_easy_init();
183 curl_easy_setopt(dl->
curl, CURLOPT_ENCODING,
"");
185 curl_easy_setopt(dl->
curl, CURLOPT_VERBOSE, 1);
187 curl_easy_setopt(dl->
curl, CURLOPT_NOPROGRESS, 0);
189 curl_easy_setopt(dl->
curl, CURLOPT_WRITEDATA, dl->
file);
190 curl_easy_setopt(dl->
curl, CURLOPT_WRITEFUNCTION,
nullptr);
192 curl_easy_setopt(dl->
curl, CURLOPT_WRITEDATA, dl);
193 curl_easy_setopt(dl->
curl, CURLOPT_WRITEFUNCTION,
HTTP_Recv);
197 curl_easy_setopt(dl->
curl, CURLOPT_FAILONERROR, 1);
199 curl_easy_setopt(dl->
curl, CURLOPT_FOLLOWLOCATION, 1);
200 curl_easy_setopt(dl->
curl, CURLOPT_MAXREDIRS, 5);
201 curl_easy_setopt(dl->
curl, CURLOPT_WRITEHEADER, dl);
204 curl_easy_setopt(dl->
curl, CURLOPT_PROGRESSDATA, dl);
207 curl_easy_setopt(dl->
curl, CURLOPT_URL, dl->
URL);
208 curl_easy_setopt(dl->
curl, CURLOPT_NOSIGNAL, 1);
210 if (curl_multi_add_handle(
multi, dl->
curl) != CURLM_OK) {
218 Com_Printf(
"CL_StartHTTPDownload: Fetching %s...\n", dl->
URL);
239 multi = curl_multi_init();
273 for (
int i = 0;
i < 4;
i++) {
293 for (; *anchor; anchor = &(*anchor)->
next) {
295 if (
Q_streq(ufoPath, (*anchor)->ufoPath))
306 if (cl_http_filelists->
integer) {
308 if (extension !=
nullptr && !
Q_strcasecmp(extension,
"bsp")) {
310 const size_t len = strlen(ufoPath);
349 if (
Q_streq(filename, lastfilename))
352 Q_strncpyz(lastfilename, filename,
sizeof(lastfilename));
354 if (strstr(filename,
"..")) {
355 Com_Printf(
"Refusing to check a path with .. (%s)\n", filename);
359 if (strchr(filename,
' ')) {
360 Com_Printf(
"Refusing to check a path containing spaces (%s)\n", filename);
364 if (strchr(filename,
':')) {
365 Com_Printf(
"Refusing to check a path containing a colon (%s)\n", filename);
369 if (filename[0] ==
'/') {
370 Com_Printf(
"Refusing to check a path starting with / (%s)\n", filename);
395 size_t length = strlen(path);
406 Com_Printf(
"NOTICE: Filelist is requesting a .pk3 file (%s)\n", path);
422 Com_Printf(
"WARNING: Illegal file type '%s' in filelist.\n", path);
427 if (path[0] ==
'@') {
429 Com_Printf(
"WARNING: @ prefix used on a pk3 file (%s) in filelist.\n", path);
438 if (strstr(path,
"..") || !
isvalidchar(path[0]) || !
isvalidchar(path[length - 1]) || strstr(path,
"//") ||
439 strchr(path,
'\\') || (!pak && !strchr(path,
'/')) || (pak && strchr(path,
'/'))) {
440 Com_Printf(
"WARNING: Illegal path '%s' in filelist.\n", path);
445 if (gameLocal || pak) {
469 while ((*anchor)->next) anchor = &(*anchor)->
next;
488 if (!cl_http_filelists->
integer)
494 char* p = strchr(list,
'\n');
534 for (
int i = 0;
i < 4;
i++) {
548 curl_multi_remove_handle(
multi, dl->
curl);
549 curl_easy_cleanup(dl->
curl);
557 curl_multi_cleanup(
multi);
570 CURLMsg* msg = curl_multi_info_read(
multi, &messagesInQueue);
574 Com_Printf(
"CL_FinishHTTPDownload: Odd, no message for us...\n");
578 if (msg->msg != CURLMSG_DONE) {
579 Com_Printf(
"CL_FinishHTTPDownload: Got some weird message...\n");
583 CURL* curl = msg->easy_handle;
586 for (
int i = 0;
i < 4;
i++) {
619 CURLcode result = msg->data.result;
622 double timeTaken, fileSize;
626 case CURLE_HTTP_RETURNED_ERROR:
628 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
629 if (responseCode == 404) {
631 if (extension !=
nullptr &&
Q_streq(extension,
"pk3"))
637 curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &fileSize);
638 if (fileSize > 512) {
641 result = CURLE_FILESIZE_EXCEEDED;
642 Com_Printf(
"Oversized 404 body received (%d bytes), aborting HTTP downloading.\n", (
int)fileSize);
644 curl_multi_remove_handle(
multi, dl->
curl);
647 }
else if (responseCode == 200) {
656 case CURLE_COULDNT_RESOLVE_HOST:
657 case CURLE_COULDNT_CONNECT:
658 case CURLE_COULDNT_RESOLVE_PROXY:
661 Com_Printf(
"Fatal HTTP error: %s\n", curl_easy_strerror(result));
662 curl_multi_remove_handle(
multi, dl->
curl);
673 Com_Printf(
"HTTP download failed: %s\n", curl_easy_strerror(result));
674 curl_multi_remove_handle(
multi, dl->
curl);
686 int i = strlen(tempName);
687 if (
Q_streq(tempName + i - 4,
".pk3")) {
695 curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &timeTaken);
696 curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &fileSize);
703 curl_multi_remove_handle(
multi, dl->
curl);
705 Com_Printf(
"HTTP(%s): %.f bytes, %.2fkB/sec [%d remaining files]\n",
707 }
while (messagesInQueue > 0);
736 const size_t len = strlen(q->ufoPath);
737 if (len > 4 && !
Q_strcasecmp(q->ufoPath + len - 4,
".pk3"))
762 !
downloadingPK3 && handleCount < cl_http_max_connections->integer)
767 ret = curl_multi_perform(
multi, &newHandleCount);
774 }
while (ret == CURLM_CALL_MULTI_PERFORM);
776 if (ret != CURLM_OK) {
777 Com_Printf(
"curl_multi_perform error. Aborting HTTP downloads.\n");
783 !
downloadingPK3 && handleCount < cl_http_max_connections->integer)
789 cl_http_filelists =
Cvar_Get(
"cl_http_filelists",
"1");
790 cl_http_downloads =
Cvar_Get(
"cl_http_downloads",
"1", 0,
"Try to download files via http");
791 cl_http_max_connections =
Cvar_Get(
"cl_http_max_connections",
"1");
bool Q_strnull(const char *string)
void CL_CancelHTTPDownloads(bool permKill)
Cancel all downloads and nuke the queue.
static dlhandle_t * CL_GetFreeDLHandle(void)
Find a free download handle to start another queue entry on.
int FS_CheckFile(const char *fmt,...)
Just returns the filelength and -1 if the file wasn't found.
static cvar_t * cl_http_filelists
const char * Com_GetExtension(const char *path)
size_t HTTP_Header(void *ptr, size_t size, size_t nmemb, void *stream)
libcurl callback to update header info.
This is a cvar definition. Cvars can be user modified and used in our menus e.g.
static int CL_HTTP_Progress(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
libcurl callback to update progress info. Mainly just used as a way to cancel the transfer if require...
static int abortDownloads
bool Com_sprintf(char *dest, size_t size, const char *fmt,...)
copies formatted string with buffer-size checking
void FS_RestartFilesystem(const char *gamedir)
Restart the filesystem (reload all pk3 files)
void FS_RemoveFile(const char *osPath)
int FS_LoadFile(const char *path, byte **buffer)
Filenames are relative to the quake search path.
dlqueue_t * downloadQueue
bool CL_PendingHTTPDownloads(void)
See if we're still busy with some downloads. Called by precacher just before it loads the map since w...
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
void Com_Printf(const char *const fmt,...)
size_t HTTP_Recv(void *ptr, size_t size, size_t nmemb, void *stream)
libcurl callback for HTTP_GetURL
static void CL_EscapeHTTPPath(const char *filePath, char *escaped)
Properly escapes a path with HTTP encoding. libcurl's function seems to treat '/' and such as illegal...
char filePath[MAX_OSPATH]
void Com_Error(int code, const char *fmt,...)
static bool downloadingPK3
const char * FS_Gamedir(void)
Called to find where to write a file (savegames, etc)
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
cvar_t * Cvar_Get(const char *var_name, const char *var_value, int flags, const char *desc)
Init or return a cvar.
void CL_HTTP_Cleanup(void)
UFO is exiting or we're changing servers. Clean up.
static void StripHighBits(char *string)
QGL_EXTERN GLuint GLsizei GLsizei * length
dlhandle_t HTTPHandles[4]
static void CL_ParseFileList(dlhandle_t *dl)
A filelist is in memory, scan and validate it and queue up the files.
static void CL_ReVerifyHTTPQueue(void)
A pk3 file just downloaded, let's see if we can remove some stuff from the queue which is in the ...
int Sys_Remove(const char *filename)
bool CL_CheckOrDownloadFile(const char *filename)
#define Q_strcasecmp(a, b)
static bool isvalidchar(int c)
static void CL_StartHTTPDownload(dlqueue_t *entry, dlhandle_t *dl)
Actually starts a download by adding it to the curl multi handle.
static cvar_t * cl_http_max_connections
static void CL_StartNextHTTPDownload(void)
Start another HTTP download if possible.
void CL_RunHTTPDownloads(void)
This calls curl_multi_perform do actually do stuff. Called every frame while connecting to minimise l...
void CL_SetHTTPServer(const char *URL)
A new server is specified, so we nuke all our state.
bool FS_RenameFile(const char *from, const char *to, bool relative)
Renames a file.
QGL_EXTERN GLuint GLchar GLuint * len
static void CL_FinishHTTPDownload(void)
A download finished, find out what it was, whether there were any errors and if so, how severe. If none, rename file and other such stuff.
FILE * Sys_Fopen(const char *filename, const char *mode)
#define Mem_AllocType(type)
Primary header for client.
bool CL_QueueHTTPDownload(const char *ufoPath)
Called from the precache check to queue a download.
static void CL_CheckAndQueueDownload(char *path)
Validate a path supplied by a filelist.
char downloadName[MAX_OSPATH]
void FS_CreatePath(const char *path)
Creates any directories needed to store the given filename.
static cvar_t * cl_http_downloads
void CL_RequestNextDownload(void)
void HTTP_InitStartup(void)