static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error) { qboolean ok = false; if(!curl_dll) return; switch(status) { case CURL_DOWNLOAD_SUCCESS: ok = true; di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data); break; case CURL_DOWNLOAD_FAILED: di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data); break; case CURL_DOWNLOAD_ABORTED: di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data); break; case CURL_DOWNLOAD_SERVERERROR: // reopen to enforce it to have zero bytes again if(di->stream) { FS_Close(di->stream); di->stream = FS_OpenRealFile(di->filename, "wb", false); } if(di->callback) di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data); break; default: if(di->callback) di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data); break; } if(di->curle) { qcurl_multi_remove_handle(curlm, di->curle); qcurl_easy_cleanup(di->curle); if(di->slist) qcurl_slist_free_all(di->slist); } if(!di->callback && ok && !di->bytes_received) { Con_Printf("ERROR: empty file\n"); ok = false; } if(di->stream) FS_Close(di->stream); if(ok && di->ispak) { ok = FS_AddPack(di->filename, NULL, true); if(!ok) { // pack loading failed? // this is critical // better clear the file again... di->stream = FS_OpenRealFile(di->filename, "wb", false); FS_Close(di->stream); if(di->startpos && !di->callback) { // this was a resume? // then try to redownload it without reporting the error Curl_Begin(di->url, di->extraheaders, di->maxspeed, di->filename, di->ispak, di->forthismap, di->post_content_type, di->postbuf, di->postbufsize, NULL, 0, NULL, NULL); di->forthismap = false; // don't count the error } } } if(di->prev) di->prev->next = di->next; else downloads = di->next; if(di->next) di->next->prev = di->prev; --numdownloads; if(di->forthismap) { if(ok) ++numdownloads_success; else ++numdownloads_fail; } Z_Free(di); }
/* ==================== Curl_Begin Starts a download of a given URL to the file name portion of this URL (or name if given) in the "dlcache/" folder. ==================== */ static qboolean Curl_Begin(const char *URL, const char *extraheaders, double maxspeed, const char *name, qboolean ispak, qboolean forthismap, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata) { if(!curl_dll) { return false; } else { char fn[MAX_OSPATH]; char urlbuf[1024]; const char *p, *q; size_t length; downloadinfo *di; // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server p = strchr(URL, ':'); if(p) { if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4)) { char addressstring[128]; *addressstring = 0; InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring)); q = strchr(addressstring, ':'); if(!q) q = addressstring + strlen(addressstring); if(*addressstring) { dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3); URL = urlbuf; } } } // Note: This extraction of the file name portion is NOT entirely correct. // // It does the following: // // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php // // However, I'd like to keep this "buggy" behavior so that PHP script // authors can write download scripts without having to enable // AcceptPathInfo on Apache. They just have to ensure that their script // can be called with such a "fake" path name like // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 // // By the way, such PHP scripts should either send the file or a // "Location:" redirect; PHP code example: // // header("Location: http://www.example.com/"); // // By the way, this will set User-Agent to something like // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to // dp://serverhost:serverport/ so you can filter on this; an example // httpd log file line might be: // // 141.2.16.3 - - [17/Mar/2006:22:32:43 +0100] "GET /maps/tznex07.pk3 HTTP/1.1" 200 1077455 "dp://141.2.16.7:26000/" "Nexuiz Linux 22:07:43 Mar 17 2006" if(!name) name = CleanURL(URL); if(!buf) { p = strrchr(name, '/'); p = p ? (p+1) : name; q = strchr(p, '?'); length = q ? (size_t)(q - p) : strlen(p); dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p); name = fn; // make it point back // already downloading the file? { downloadinfo *di = Curl_Find(fn); if(di) { Con_Printf("Can't download %s, already getting it from %s!\n", fn, CleanURL(di->url)); // however, if it was not for this map yet... if(forthismap && !di->forthismap) { di->forthismap = true; // this "fakes" a download attempt so the client will wait for // the download to finish and then reconnect ++numdownloads_added; } return false; } } if(ispak && FS_FileExists(fn)) { qboolean already_loaded; if(FS_AddPack(fn, &already_loaded, true)) { Con_DPrintf("%s already exists, not downloading!\n", fn); if(already_loaded) Con_DPrintf("(pak was already loaded)\n"); else { if(forthismap) { ++numdownloads_added; ++numdownloads_success; } } return false; } else { qfile_t *f = FS_OpenRealFile(fn, "rb", false); if(f) { char buf[4] = {0}; FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4)) { Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn); FS_Close(f); f = FS_OpenRealFile(fn, "wb", false); if(f) FS_Close(f); } else { // OK FS_Close(f); } } } } } // if we get here, we actually want to download... so first verify the // URL scheme (so one can't read local files using file://) if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8)) { Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL); return false; } if(forthismap) ++numdownloads_added; di = (downloadinfo *) Z_Malloc(sizeof(*di)); strlcpy(di->filename, name, sizeof(di->filename)); strlcpy(di->url, URL, sizeof(di->url)); dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid"); di->forthismap = forthismap; di->stream = NULL; di->startpos = 0; di->curle = NULL; di->started = false; di->ispak = (ispak && !buf); di->maxspeed = maxspeed; di->bytes_received = 0; di->bytes_received_curl = 0; di->bytes_sent_curl = 0; di->extraheaders = extraheaders; di->next = downloads; di->prev = NULL; if(di->next) di->next->prev = di; di->buffer = buf; di->buffersize = bufsize; if(callback == NULL) { di->callback = curl_default_callback; di->callback_data = di; } else { di->callback = callback; di->callback_data = cbdata; } if(post_content_type) { di->post_content_type = post_content_type; di->postbuf = postbuf; di->postbufsize = postbufsize; } else { di->post_content_type = NULL; di->postbuf = NULL; di->postbufsize = 0; } downloads = di; return true; } }
static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error, const char *content_type_) { char content_type[64]; qboolean ok = false; if(!curl_dll) return; switch(status) { case CURL_DOWNLOAD_SUCCESS: ok = true; di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data); break; case CURL_DOWNLOAD_FAILED: di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data); break; case CURL_DOWNLOAD_ABORTED: di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data); break; case CURL_DOWNLOAD_SERVERERROR: // reopen to enforce it to have zero bytes again if(di->stream) { FS_Close(di->stream); di->stream = FS_OpenRealFile(di->filename, "wb", false); } if(di->callback) di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data); break; default: if(di->callback) di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data); break; } if(content_type_) strlcpy(content_type, content_type_, sizeof(content_type)); else *content_type = 0; if(di->curle) { qcurl_multi_remove_handle(curlm, di->curle); qcurl_easy_cleanup(di->curle); if(di->slist) qcurl_slist_free_all(di->slist); } if(!di->callback && ok && !di->bytes_received) { Con_Printf("ERROR: empty file\n"); ok = false; } if(di->stream) FS_Close(di->stream); #define CLEAR_AND_RETRY() \ do \ { \ di->stream = FS_OpenRealFile(di->filename, "wb", false); \ FS_Close(di->stream); \ if(di->startpos && !di->callback) \ { \ Curl_Begin(di->url, di->extraheaders, di->maxspeed, di->filename, di->loadtype, di->forthismap, di->post_content_type, di->postbuf, di->postbufsize, NULL, 0, NULL, NULL); \ di->forthismap = false; \ } \ } \ while(0) if(ok && di->loadtype == LOADTYPE_PAK) { ok = FS_AddPack(di->filename, NULL, true); if(!ok) CLEAR_AND_RETRY(); } else if(ok && di->loadtype == LOADTYPE_CACHEPIC) { const char *p; unsigned char *pixels = NULL; p = di->filename; #ifdef WE_ARE_EVIL if(!strncmp(p, "dlcache/", 8)) p += 8; #endif pixels = decode_image(di, content_type); if(pixels) Draw_NewPic(p, image_width, image_height, true, pixels); else CLEAR_AND_RETRY(); } else if(ok && di->loadtype == LOADTYPE_SKINFRAME) { const char *p; unsigned char *pixels = NULL; p = di->filename; #ifdef WE_ARE_EVIL if(!strncmp(p, "dlcache/", 8)) p += 8; #endif pixels = decode_image(di, content_type); if(pixels) R_SkinFrame_LoadInternalBGRA(p, TEXF_FORCE_RELOAD | TEXF_MIPMAP | TEXF_ALPHA, pixels, image_width, image_height, false); // TODO what sRGB argument to put here? else CLEAR_AND_RETRY(); } if(di->prev) di->prev->next = di->next; else downloads = di->next; if(di->next) di->next->prev = di->prev; --numdownloads; if(di->forthismap) { if(ok) ++numdownloads_success; else ++numdownloads_fail; } Z_Free(di); }