]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - libcurl.c
fix lightmap loading from TGAs; fix display of QC-initiated downloads
[xonotic/darkplaces.git] / libcurl.c
index e8b9496b97973d4afc99748dac07d71bef83c89b..f97946fe1bd2b365c1fe399d429837574eab03dc 100644 (file)
--- a/libcurl.c
+++ b/libcurl.c
@@ -180,6 +180,11 @@ typedef struct downloadinfo_s
        unsigned long bytes_received;
        struct downloadinfo_s *next, *prev;
        qboolean forthismap;
+
+       unsigned char *buffer;
+       size_t buffersize;
+       curl_callback_t callback;
+       void *callback_data;
 }
 downloadinfo;
 static downloadinfo *downloads = NULL;
@@ -361,15 +366,28 @@ this.
 */
 static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi)
 {
-       fs_offset_t ret;
+       fs_offset_t ret = -1;
        size_t bytes = size * nmemb;
        downloadinfo *di = (downloadinfo *) vdi;
 
+       if(di->buffer)
+       {
+               if(di->bytes_received + bytes <= di->buffersize)
+               {
+                       memcpy(di->buffer + di->bytes_received, data, bytes);
+                       ret = bytes;
+               }
+               // otherwise: buffer overrun, ret stays -1
+       }
+
+       if(di->stream)
+       {
+               ret = FS_Write(di->stream, data, bytes);
+       }
+
        bytes_received += bytes;
        di->bytes_received += bytes;
 
-       ret = FS_Write(di->stream, data, bytes);
-
        return ret; // why not ret / nmemb?
 }
 
@@ -382,6 +400,38 @@ typedef enum
 }
 CurlStatus;
 
+static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
+{
+       downloadinfo *di = (downloadinfo *) cbdata;
+       switch(status)
+       {
+               case CURLCBSTATUS_OK:
+                       Con_Printf("Download of %s: OK\n", di->filename);
+                       break;
+               case CURLCBSTATUS_FAILED:
+                       Con_Printf("Download of %s: FAILED\n", di->filename);
+                       break;
+               case CURLCBSTATUS_ABORTED:
+                       Con_Printf("Download of %s: ABORTED\n", di->filename);
+                       break;
+               case CURLCBSTATUS_SERVERERROR:
+                       Con_Printf("Download of %s: (unknown server error)\n", di->filename);
+                       break;
+               case CURLCBSTATUS_UNKNOWN:
+                       Con_Printf("Download of %s: (unknown client error)\n", di->filename);
+                       break;
+               default:
+                       Con_Printf("Download of %s: %d\n", di->filename, status);
+                       break;
+       }
+}
+
+static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
+{
+       if(developer.integer)
+               curl_default_callback(status, length_received, buffer, cbdata);
+}
+
 /*
 ====================
 Curl_EndDownload
@@ -399,24 +449,29 @@ static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error
        switch(status)
        {
                case CURL_DOWNLOAD_SUCCESS:
-                       Con_Printf("Download of %s: OK\n", di->filename);
                        ok = true;
+                       di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data);
                        break;
                case CURL_DOWNLOAD_FAILED:
-                       Con_Printf("Download of %s: FAILED\n", di->filename);
-                       if(error)
-                               Con_Printf("Reason given by libcurl: %s\n", qcurl_easy_strerror(error));
+                       di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data);
                        break;
                case CURL_DOWNLOAD_ABORTED:
-                       Con_Printf("Download of %s: ABORTED\n", di->filename);
+                       di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data);
                        break;
                case CURL_DOWNLOAD_SERVERERROR:
-                       Con_Printf("Download of %s: %d\n", di->filename, (int) error);
-
                        // reopen to enforce it to have zero bytes again
-                       FS_Close(di->stream);
-                       di->stream = FS_Open(di->filename, "w", false, false);
+                       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;
        }
 
@@ -476,22 +531,30 @@ static void CheckPendingDownloads()
                {
                        if(!di->started)
                        {
-                               Con_Printf("Downloading %s -> %s", di->url, di->filename);
+                               if(!di->buffer)
+                               {
+                                       Con_Printf("Downloading %s -> %s", di->url, di->filename);
 
-                               di->stream = FS_Open(di->filename, "ab", false, false);
-                               if(!di->stream)
+                                       di->stream = FS_OpenRealFile(di->filename, "ab", false);
+                                       if(!di->stream)
+                                       {
+                                               Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
+                                               Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
+                                               return;
+                                       }
+                                       FS_Seek(di->stream, 0, SEEK_END);
+                                       di->startpos = FS_Tell(di->stream);
+
+                                       if(di->startpos > 0)
+                                               Con_Printf(", resuming from position %ld", (long) di->startpos);
+                                       Con_Print("...\n");
+                               }
+                               else
                                {
-                                       Con_Printf("\nFAILED: Could not open output file %s\n", di->filename);
-                                       Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK);
-                                       return;
+                                       Con_DPrintf("Downloading %s -> memory\n", di->url);
+                                       di->startpos = 0;
                                }
 
-                               FS_Seek(di->stream, 0, SEEK_END);
-                               di->startpos = FS_Tell(di->stream);
-                               if(di->startpos > 0)
-                                       Con_Printf(", resuming from position %ld", (long) di->startpos);
-                               Con_Print("...\n");
-
                                di->curle = qcurl_easy_init();
                                qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url);
                                qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion);
@@ -566,6 +629,24 @@ static downloadinfo *Curl_Find(const char *filename)
        return NULL;
 }
 
+void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata)
+{
+       downloadinfo *di;
+       if(!curl_dll)
+               return;
+       for(di = downloads; di; )
+       {
+               if(di->callback == callback && di->callback_data == cbdata)
+               {
+                       di->callback = curl_quiet_callback; // do NOT call the callback
+                       Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK);
+                       di = downloads;
+               }
+               else
+                       di = di->next;
+       }
+}
+
 /*
 ====================
 Curl_Begin
@@ -574,10 +655,12 @@ Starts a download of a given URL to the file name portion of this URL (or name
 if given) in the "dlcache/" folder.
 ====================
 */
-void Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
+static qboolean Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean forthismap, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
 {
        if(!curl_dll)
-               return;
+       {
+               return false;
+       }
        else
        {
                char fn[MAX_QPATH];
@@ -613,96 +696,94 @@ void Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean fort
 
                if(!name)
                        name = URL;
-               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);
 
-               if(cls.demorecording)
+               if(!buf)
                {
-                       void *demobuf; fs_offset_t demofilesize;
-                       char msg[MAX_QPATH + 16];
-                       sizebuf_t sb;
-
-                       sb.data = (void *) msg;
-                       sb.maxsize = sizeof(msg);
-                       SZ_Clear(&sb);
-                       MSG_WriteByte(&sb, svc_stufftext);
-                       MSG_WriteString(&sb, va("\ncurl --pak \"%.*s\"\n", (int)length, p));
-
-                       CL_CutDemo(&demobuf, &demofilesize);
-                       CL_WriteDemoMessage(&sb);
-                       CL_PasteDemo(&demobuf, &demofilesize);
-               }
+                       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);
 
-               // already downloading the file?
-               {
-                       downloadinfo *di = Curl_Find(fn);
-                       if(di)
-                       {
-                               Con_Printf("Can't download %s, already getting it from %s!\n", fn, 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;
-                       }
-               }
+                       name = fn; // make it point back
 
-               if(ispak && FS_FileExists(fn))
-               {
-                       qboolean already_loaded;
-                       if(FS_AddPack(fn, &already_loaded, true))
+                       // already downloading the file?
                        {
-                               Con_DPrintf("%s already exists, not downloading!\n", fn);
-                               if(already_loaded)
-                                       Con_DPrintf("(pak was already loaded)\n");
-                               else
+                               downloadinfo *di = Curl_Find(fn);
+                               if(di)
                                {
-                                       if(forthismap)
+                                       Con_Printf("Can't download %s, already getting it from %s!\n", fn, 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;
-                                               ++numdownloads_success;
                                        }
+
+                                       return false;
                                }
-                               return;
                        }
-                       else
+
+                       if(ispak && FS_FileExists(fn))
                        {
-                               qfile_t *f = FS_Open(fn, "rb", false, false);
-                               if(f)
+                               qboolean already_loaded;
+                               if(FS_AddPack(fn, &already_loaded, true))
                                {
-                                       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("%s already exists, not downloading!\n", fn);
+                                       if(already_loaded)
+                                               Con_DPrintf("(pak was already loaded)\n");
+                                       else
                                        {
-                                               Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn);
-                                               FS_Close(f);
-                                               f = FS_Open(fn, "w", false, false);
-                                               if(f)
-                                                       FS_Close(f);
+                                               if(forthismap)
+                                               {
+                                                       ++numdownloads_added;
+                                                       ++numdownloads_success;
+                                               }
                                        }
-                                       else
+
+                                       return false;
+                               }
+                               else
+                               {
+                                       qfile_t *f = FS_OpenVirtualFile(fn, false);
+                                       if(f)
                                        {
-                                               // OK
-                                               FS_Close(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, fn, sizeof(di->filename));
+               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;
@@ -716,10 +797,33 @@ void Curl_Begin(const char *URL, const char *name, qboolean ispak, qboolean fort
                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;
+               }
+
                downloads = di;
+               return true;
        }
 }
 
+qboolean Curl_Begin_ToFile(const char *URL, const char *name, qboolean ispak, qboolean forthismap)
+{
+       return Curl_Begin(URL, name, ispak, forthismap, NULL, 0, NULL, NULL);
+}
+qboolean Curl_Begin_ToMemory(const char *URL, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata)
+{
+       return Curl_Begin(URL, NULL, false, false, buf, bufsize, callback, cbdata);
+}
 
 /*
 ====================
@@ -1066,9 +1170,25 @@ void Curl_Curl_f(void)
        }
 
 needthefile:
-       Curl_Begin(url, name, pak, forthismap);
+       Curl_Begin_ToFile(url, name, pak, forthismap);
+}
+
+/*
+static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata)
+{
+       Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer);
+       Z_Free(buffer);
 }
 
+void Curl_CurlCat_f(void)
+{
+       unsigned char *buf;
+       const char *url = Cmd_Argv(1);
+       buf = Z_Malloc(16384);
+       Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL);
+}
+*/
+
 /*
 ====================
 Curl_Init_Commands
@@ -1084,6 +1204,7 @@ void Curl_Init_Commands(void)
        Cvar_RegisterVariable (&sv_curl_defaulturl);
        Cvar_RegisterVariable (&sv_curl_serverpackages);
        Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path");
+       //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)");
 }
 
 /*
@@ -1099,7 +1220,7 @@ array must be freed later using Z_Free.
 */
 Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info)
 {
-       int n, i;
+       int i;
        downloadinfo *di;
        Curl_downloadinfo_t *downinfo;
        static char addinfo[128];
@@ -1112,14 +1233,18 @@ Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **addition
                return NULL;
        }
 
-       n = 0;
+       i = 0;
        for(di = downloads; di; di = di->next)
-               ++n;
+               ++i;
 
-       downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * n);
+       downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i);
        i = 0;
        for(di = downloads; di; di = di->next)
        {
+               // do not show infobars for background downloads
+               if(!developer.integer)
+                       if(di->buffer)
+                               continue;
                strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename));
                if(di->curle)
                {
@@ -1151,7 +1276,7 @@ Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **addition
                        *additional_info = NULL;
        }
 
-       *nDownloads = n;
+       *nDownloads = i;
        return downinfo;
 }