/** * @brief Called from the precache check to queue a download. * @sa CL_CheckOrDownloadFile */ bool CL_QueueHTTPDownload (const char *ufoPath) { /* no http server (or we got booted) */ if (!cls.downloadServer[0] || abortDownloads || !cl_http_downloads->integer) return false; dlqueue_t** anchor = &cls.downloadQueue; for (; *anchor; anchor = &(*anchor)->next) { /* avoid sending duplicate requests */ if (Q_streq(ufoPath, (*anchor)->ufoPath)) return true; } dlqueue_t* const q = Mem_AllocType(dlqueue_t); q->next = NULL; q->state = DLQ_STATE_NOT_STARTED; Q_strncpyz(q->ufoPath, ufoPath, sizeof(q->ufoPath)); *anchor = q; /* special case for map file lists */ if (cl_http_filelists->integer) { const char *extension = Com_GetExtension(ufoPath); if (extension != NULL && !Q_strcasecmp(extension, "bsp")) { char listPath[MAX_OSPATH]; const size_t len = strlen(ufoPath); Com_sprintf(listPath, sizeof(listPath), BASEDIRNAME"/%.*s.filelist", (int)(len - 4), ufoPath); CL_QueueHTTPDownload(listPath); } } /* if a download entry has made it this far, CL_FinishHTTPDownload is guaranteed to be called. */ pendingCount++; return true; }
/** * @return true if the file exists, otherwise it attempts to start a download via curl * @sa CL_CheckAndQueueDownload * @sa CL_RequestNextDownload */ bool CL_CheckOrDownloadFile (const char *filename) { static char lastfilename[MAX_OSPATH] = ""; if (Q_strnull(filename)) return true; /* r1: don't attempt same file many times */ if (Q_streq(filename, lastfilename)) return true; Q_strncpyz(lastfilename, filename, sizeof(lastfilename)); if (strstr(filename, "..")) { Com_Printf("Refusing to check a path with .. (%s)\n", filename); return true; } if (strchr(filename, ' ')) { Com_Printf("Refusing to check a path containing spaces (%s)\n", filename); return true; } if (strchr(filename, ':')) { Com_Printf("Refusing to check a path containing a colon (%s)\n", filename); return true; } if (filename[0] == '/') { Com_Printf("Refusing to check a path starting with / (%s)\n", filename); return true; } if (FS_LoadFile(filename, NULL) != -1) { /* it exists, no need to download */ return true; } if (CL_QueueHTTPDownload(filename)) return false; return true; }
/* =============== CL_CheckOrDownloadFile Returns true if the file exists, otherwise it attempts to start a download from the server. =============== */ qboolean CL_CheckOrDownloadFile (const char *filename) { FILE *fp; int length; char *p; char name[MAX_OSPATH]; static char lastfilename[MAX_OSPATH] = {0}; //r1: don't attempt same file many times if (!strcmp (filename, lastfilename)) return true; strcpy (lastfilename, filename); if (strstr (filename, "..")) { Com_Printf ("Refusing to check a path with .. (%s)\n", LOG_CLIENT, filename); return true; } if (strchr (filename, ' ')) { Com_Printf ("Refusing to check a path containing spaces (%s)\n", LOG_CLIENT, filename); return true; } if (strchr (filename, ':')) { Com_Printf ("Refusing to check a path containing a colon (%s)\n", LOG_CLIENT, filename); return true; } if (filename[0] == '/') { Com_Printf ("Refusing to check a path starting with / (%s)\n", LOG_CLIENT, filename); return true; } if (FS_LoadFile (filename, NULL) != -1) { // it exists, no need to download return true; } #ifdef USE_CURL if (CL_QueueHTTPDownload (filename)) { //we return true so that the precache check keeps feeding us more files. //since we have multiple HTTP connections we want to minimize latency //and be constantly sending requests, not one at a time. return true; } else #endif { strcpy (cls.downloadname, filename); //r1: fix \ to / p = cls.downloadname; while ((p = strchr(p, '\\')) != NULL) p[0] = '/'; length = (int)strlen(cls.downloadname); //normalize path p = cls.downloadname; while ((p = strstr (p, "./")) != NULL) { memmove (p, p+2, length - (p - cls.downloadname) - 1); length -= 2; } //r1: verify we are giving the server a legal path if (cls.downloadname[length-1] == '/') { Com_Printf ("Refusing to download bad path (%s)\n", LOG_CLIENT, filename); return true; } // download to a temp name, and only rename // to the real name when done, so if interrupted // a runt file wont be left COM_StripExtension (cls.downloadname, cls.downloadtempname); strcat (cls.downloadtempname, ".tmp"); //ZOID // check to see if we already have a tmp for this file, if so, try to resume // open the file if not opened yet CL_DownloadFileName(name, sizeof(name), cls.downloadtempname); // FS_CreatePath (name); fp = fopen (name, "r+b"); if (fp) { // it exists int len; fseek(fp, 0, SEEK_END); len = ftell(fp); cls.download = fp; // give the server an offset to start the download Com_Printf ("Resuming %s\n", LOG_CLIENT, cls.downloadname); MSG_WriteByte (clc_stringcmd); if (cls.serverProtocol == PROTOCOL_R1Q2) MSG_WriteString (va("download \"%s\" %i udp-zlib", cls.downloadname, len)); else MSG_WriteString (va("download \"%s\" %i", cls.downloadname, len)); } else { Com_Printf ("Downloading %s\n", LOG_CLIENT, cls.downloadname); MSG_WriteByte (clc_stringcmd); if (cls.serverProtocol == PROTOCOL_R1Q2) MSG_WriteString (va("download \"%s\" 0 udp-zlib", cls.downloadname)); else MSG_WriteString (va("download \"%s\"", cls.downloadname)); } MSG_EndWriting (&cls.netchan.message); send_packet_now = true; cls.downloadpending = true; return false; } }
/* =============== CL_ParseFileList Validate a path supplied by a filelist. =============== */ static void CL_CheckAndQueueDownload (char *path) { size_t length; char *ext; qboolean pak; qboolean gameLocal; StripHighBits (path, 1); length = strlen(path); if (length >= MAX_QPATH) return; ext = strrchr (path, '.'); if (!ext) return; ext++; if (!ext[0]) return; Q_strlwr (ext); if ( !strcmp (ext, "pak") || !strcmp (ext, "pk3") ) { Com_Printf ("NOTICE: Filelist is requesting a .pak file (%s)\n", path); pak = true; } else pak = false; if (!pak && strcmp (ext, "pcx") && strcmp (ext, "wal") && strcmp (ext, "wav") && strcmp (ext, "md2") && strcmp (ext, "sp2") && strcmp (ext, "tga") && strcmp (ext, "png") && strcmp (ext, "jpg") && strcmp (ext, "bsp") && strcmp (ext, "ent") && strcmp (ext, "txt") && strcmp (ext, "dm2") && strcmp (ext, "loc")) { Com_Printf ("WARNING: Illegal file type '%s' in filelist.\n", MakePrintable(path, length)); return; } if (path[0] == '@') { if (pak) { Com_Printf ("WARNING: @ prefix used on a pak file (%s) in filelist.\n", MakePrintable(path, length)); return; } gameLocal = true; path++; length--; } else gameLocal = false; if (strstr (path, "..") || !IsValidChar (path[0]) || !IsValidChar (path[length-1]) || strstr(path, "//") || strchr (path, '\\') || (!pak && !strchr (path, '/')) || (pak && strchr(path, '/'))) { Com_Printf ("WARNING: Illegal path '%s' in filelist.\n", MakePrintable(path, length)); return; } // by definition paks are game-local if (gameLocal || pak) { qboolean exists; FILE *f; char gamePath[MAX_OSPATH]; if (pak) { Com_sprintf (gamePath, sizeof(gamePath),"%s/%s",FS_Gamedir(), path); f = fopen (gamePath, "rb"); if (!f) { exists = false;; } else { exists = true; fclose (f); } } else { // exists = FS_ExistsInGameDir (path); exists = FS_LocalFileExists (path); } if (!exists) { if (CL_QueueHTTPDownload (path)) { //paks get bumped to the top and HTTP switches to single downloading. //this prevents someone on 28k dialup trying to do both the main .pak //and referenced configstrings data at once. if (pak) { dlqueue_t *q, *last; last = q = &cls.downloadQueue; while (q->next) { last = q; q = q->next; } last->next = NULL; q->next = cls.downloadQueue.next; cls.downloadQueue.next = q; } } } } else { CL_CheckOrDownloadFile (path); } }
/* =============== CL_QueueHTTPDownload Called from the precache check to queue a download. Return value of false will cause standard UDP downloading to be used instead. =============== */ qboolean CL_QueueHTTPDownload (const char *quakePath) { size_t len; dlqueue_t *q; qboolean needList; // no http server (or we got booted) if (!cls.downloadServer[0] || abortDownloads || thisMapAbort || !cl_http_downloads->value) return false; needList = false; // first download queued, so we want the mod filelist if (!cls.downloadQueue.next && cl_http_filelists->value) needList = true; q = &cls.downloadQueue; while (q->next) { q = q->next; //avoid sending duplicate requests if (!strcmp (quakePath, q->quakePath)) return true; } // q->next = Z_TagMalloc (sizeof(*q), TAGMALLOC_CLIENT_DOWNLOAD); q->next = Z_TagMalloc (sizeof(*q), 0); q = q->next; q->next = NULL; q->state = DLQ_STATE_NOT_STARTED; Q_strncpyz (q->quakePath, quakePath, sizeof(q->quakePath)-1); if (needList) { //grab the filelist CL_QueueHTTPDownload (va("%s.filelist", cl.gamedir)); //this is a nasty hack to let the server know what we're doing so admins don't //get confused by a ton of people stuck in CNCT state. it's assumed the server //is running r1q2 if we're even able to do http downloading so hopefully this //won't spew an error msg. // MSG_BeginWriting (clc_stringcmd); // MSG_WriteString ("download http\n"); // MSG_EndWriting (&cls.netchan.message); MSG_WriteByte (&cls.netchan.message, clc_stringcmd); MSG_WriteString (&cls.netchan.message, "download http\n"); } //special case for map file lists, i really wanted a server-push mechanism for this, but oh well len = strlen (quakePath); if (cl_http_filelists->value && len > 4 && !Q_stricmp ((char *)(quakePath + len - 4), ".bsp")) { char listPath[MAX_OSPATH]; char filePath[MAX_OSPATH]; Com_sprintf (filePath, sizeof(filePath), "%s/%s", cl.gamedir, quakePath); COM_StripExtension (filePath, listPath); // strncat (listPath, ".filelist"); Q_strncatz (listPath, ".filelist", sizeof(listPath)); CL_QueueHTTPDownload (listPath); } //if a download entry has made it this far, CL_FinishHTTPDownload is guaranteed to be called. pendingCount++; return true; }
/** * @brief Validate a path supplied by a filelist. * @param[in,out] path Pointer to file (path) to download (high bits will be stripped). * @sa CL_QueueHTTPDownload * @sa CL_ParseFileList */ static void CL_CheckAndQueueDownload (char *path) { size_t length; const char *ext; bool pak; bool gameLocal; StripHighBits(path); length = strlen(path); if (length >= MAX_QPATH) return; ext = Com_GetExtension(path); if (ext == NULL) return; if (Q_streq(ext, "pk3")) { Com_Printf("NOTICE: Filelist is requesting a .pk3 file (%s)\n", path); pak = true; } else pak = false; if (!pak && !Q_streq(ext, "bsp") && !Q_streq(ext, "wav") && !Q_streq(ext, "md2") && !Q_streq(ext, "ogg") && !Q_streq(ext, "md3") && !Q_streq(ext, "png") && !Q_streq(ext, "jpg") && !Q_streq(ext, "obj") && !Q_streq(ext, "mat") && !Q_streq(ext, "ump")) { Com_Printf("WARNING: Illegal file type '%s' in filelist.\n", path); return; } if (path[0] == '@') { if (pak) { Com_Printf("WARNING: @ prefix used on a pk3 file (%s) in filelist.\n", path); return; } gameLocal = true; path++; length--; } else gameLocal = false; if (strstr(path, "..") || !isvalidchar(path[0]) || !isvalidchar(path[length - 1]) || strstr(path, "//") || strchr(path, '\\') || (!pak && !strchr(path, '/')) || (pak && strchr(path, '/'))) { Com_Printf("WARNING: Illegal path '%s' in filelist.\n", path); return; } /* by definition pk3s are game-local */ if (gameLocal || pak) { bool exists; /* search the user homedir to find the pk3 file */ if (pak) { char gamePath[MAX_OSPATH]; FILE *f; Com_sprintf(gamePath, sizeof(gamePath), "%s/%s", FS_Gamedir(), path); f = fopen(gamePath, "rb"); if (!f) exists = false; else { exists = true; fclose(f); } } else exists = FS_CheckFile("%s", path); if (!exists) { if (CL_QueueHTTPDownload(path)) { /* pk3s get bumped to the top and HTTP switches to single downloading. * this prevents someone on 28k dialup trying to do both the main .pk3 * and referenced configstrings data at once. */ if (pak) { dlqueue_t** anchor = &cls.downloadQueue; while ((*anchor)->next) anchor = &(*anchor)->next; /* Remove the last element from the end of the list ... */ dlqueue_t* const d = *anchor; *anchor = 0; /* ... and prepend it to the list. */ d->next = cls.downloadQueue; cls.downloadQueue = d; } } } } else CL_CheckOrDownloadFile(path); }
/* =============== CL_CheckOrDownloadFile Returns true if the file exists, otherwise it attempts to start a download from the server. =============== */ qboolean CL_CheckOrDownloadFile (const char *filename) { FILE *fp; char name[MAX_OSPATH]; static char lastfilename[MAX_OSPATH] = "\0"; Q_strncpyz(name, filename, sizeof(name)); COM_FixPath(name); filename = name; //r1: don't attempt same file many times if (!strcmp (filename, lastfilename)) return true; strcpy (lastfilename, filename); if (strstr (filename, "..")) { Com_Printf ("Refusing to download a path with .. (%s)\n", filename); return true; } if (strchr (filename, ' ')) { Com_Printf ("Refusing to check a path containing spaces (%s)\n", filename); return true; } if (strchr (filename, ':')) { Com_Printf ("Refusing to check a path containing a colon (%s)\n", filename); return true; } if (FS_LoadFile (filename, NULL) != -1) { // it exists, no need to download return true; } #ifdef USE_CURL if (CL_QueueHTTPDownload (filename)) { //we return true so that the precache check keeps feeding us more files. //since we have multiple HTTP connections we want to minimize latency //and be constantly sending requests, not one at a time. return true; } #endif strcpy (cls.downloadname, filename); // download to a temp name, and only rename // to the real name when done, so if interrupted // a runt file wont be left COM_StripExtension (cls.downloadname, cls.downloadtempname); strcat (cls.downloadtempname, ".tmp"); //ZOID // check to see if we already have a tmp for this file, if so, try to resume // open the file if not opened yet CL_DownloadFileName(name, sizeof(name), cls.downloadtempname); // FS_CreatePath (name); fp = fopen (name, "r+b"); if (fp) { // it exists int len; fseek(fp, 0, SEEK_END); len = ftell(fp); cls.download = fp; // give the server an offset to start the download Com_Printf ("Resuming %s\n", cls.downloadname); MSG_WriteByte (&cls.netchan.message, clc_stringcmd); if (cls.serverProtocol == PROTOCOL_VERSION_R1Q2) MSG_WriteString (&cls.netchan.message, va("download \"%s\" %i udp-zlib", cls.downloadname, len)); else MSG_WriteString (&cls.netchan.message, va("download \"%s\" %i", cls.downloadname, len)); } else { Com_Printf ("Downloading %s\n", cls.downloadname); MSG_WriteByte (&cls.netchan.message, clc_stringcmd); if (cls.serverProtocol == PROTOCOL_VERSION_R1Q2) MSG_WriteString (&cls.netchan.message, va("download \"%s\" 0 udp-zlib", cls.downloadname)); else MSG_WriteString (&cls.netchan.message, va("download \"%s\"", cls.downloadname)); } //cls.downloadnumber++; return false; }