diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/_HttpBase.h |
initial commit
Diffstat (limited to 'src/_HttpBase.h')
-rw-r--r-- | src/_HttpBase.h | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/src/_HttpBase.h b/src/_HttpBase.h new file mode 100644 index 0000000..a842d4a --- /dev/null +++ b/src/_HttpBase.h @@ -0,0 +1,313 @@ +#include "Http.h" +#include "String.h" +#include "Platform.h" +#include "Funcs.h" +#include "Logger.h" +#include "Stream.h" +#include "Game.h" +#include "Utils.h" +#include "Options.h" + +static cc_bool httpsOnly, httpOnly, httpsVerify; +static char skinServer_buffer[128]; +static cc_string skinServer = String_FromArray(skinServer_buffer); + +void HttpRequest_Free(struct HttpRequest* request) { + Mem_Free(request->data); + Mem_Free(request->error); + + request->data = NULL; + request->size = 0; + request->error = NULL; +} +#define HttpRequest_Copy(dst, src) Mem_Copy(dst, src, sizeof(struct HttpRequest)) + +/*########################################################################################################################* +*----------------------------------------------------Http requests list---------------------------------------------------* +*#########################################################################################################################*/ +#define HTTP_DEF_ELEMS 10 +struct RequestList { + int count, capacity; + struct HttpRequest* entries; + struct HttpRequest defaultEntries[HTTP_DEF_ELEMS]; +}; + +/* Expands request list buffer if there is no room for another request */ +static void RequestList_EnsureSpace(struct RequestList* list) { + if (list->count < list->capacity) return; + Utils_Resize((void**)&list->entries, &list->capacity, + sizeof(struct HttpRequest), HTTP_DEF_ELEMS, 10); +} + +/* Adds a request to the list */ +static void RequestList_Append(struct RequestList* list, struct HttpRequest* item, cc_uint8 flags) { + int i; + RequestList_EnsureSpace(list); + + if (flags & HTTP_FLAG_PRIORITY) { + /* Shift all requests right one place */ + for (i = list->count; i > 0; i--) + { + HttpRequest_Copy(&list->entries[i], &list->entries[i - 1]); + } + /* Insert new request at front/start */ + i = 0; + } else { + /* Insert new request at end */ + i = list->count; + } + + HttpRequest_Copy(&list->entries[i], item); + list->count++; +} + +/* Removes the request at the given index */ +static void RequestList_RemoveAt(struct RequestList* list, int i) { + if (i < 0 || i >= list->count) Logger_Abort("Tried to remove element at list end"); + + for (; i < list->count - 1; i++) + { + HttpRequest_Copy(&list->entries[i], &list->entries[i + 1]); + } + list->count--; +} + +/* Finds index of request whose id matches the given id */ +static int RequestList_Find(struct RequestList* list, int id) { + int i; + for (i = 0; i < list->count; i++) { + if (id != list->entries[i].id) continue; + return i; + } + return -1; +} + +/* Tries to remove and free given request */ +static void RequestList_TryFree(struct RequestList* list, int id) { + int i = RequestList_Find(list, id); + if (i < 0) return; + + HttpRequest_Free(&list->entries[i]); + RequestList_RemoveAt(list, i); +} + +/* Resets state to default */ +static void RequestList_Init(struct RequestList* list) { + list->capacity = HTTP_DEF_ELEMS; + list->count = 0; + list->entries = list->defaultEntries; +} + +/* Frees any dynamically allocated memory, then resets state to default */ +static void RequestList_Free(struct RequestList* list) { + if (list->entries != list->defaultEntries) Mem_Free(list->entries); + RequestList_Init(list); +} + + +/*########################################################################################################################* +*--------------------------------------------------Common downloader code-------------------------------------------------* +*#########################################################################################################################*/ +static void* processedMutex; +static struct RequestList processedReqs; +static int nextReqID; +static void HttpBackend_Add(struct HttpRequest* req, cc_uint8 flags); + +/* Adds a req to the list of pending requests, waking up worker thread if needed. */ +static int Http_Add(const cc_string* url, cc_uint8 flags, cc_uint8 type, const cc_string* lastModified, + const cc_string* etag, const void* data, cc_uint32 size, struct StringsBuffer* cookies) { + static const cc_string https = String_FromConst("https://"); + static const cc_string http = String_FromConst("http://"); + struct HttpRequest req = { 0 }; + + String_CopyToRawArray(req.url, url); + Platform_Log2("Adding %s (type %b)", url, &type); + + req.id = ++nextReqID; + req.requestType = type; + + /* Change http:// to https:// if required */ + if (httpsOnly) { + cc_string url_ = String_FromRawArray(req.url); + if (String_CaselessStarts(&url_, &http)) String_InsertAt(&url_, 4, 's'); + } + /* Change https:// to http:// if required */ + if (httpOnly) { + cc_string url_ = String_FromRawArray(req.url); + if (String_CaselessStarts(&url_, &https)) String_DeleteAt(&url_, 4); + } + + if (lastModified) { + String_CopyToRawArray(req.lastModified, lastModified); + } + if (etag) { + String_CopyToRawArray(req.etag, etag); + } + + if (data) { + req.data = (cc_uint8*)Mem_Alloc(size, 1, "Http_PostData"); + Mem_Copy(req.data, data, size); + req.size = size; + } + req.cookies = cookies; + req.progress = HTTP_PROGRESS_NOT_WORKING_ON; + + HttpBackend_Add(&req, flags); + return req.id; +} + +static const cc_string urlRewrites[] = { + String_FromConst("http://dl.dropbox.com/"), String_FromConst("https://dl.dropboxusercontent.com/"), + String_FromConst("https://dl.dropbox.com/"), String_FromConst("https://dl.dropboxusercontent.com/"), + String_FromConst("https://www.imgur.com/"), String_FromConst("https://i.imgur.com/"), + String_FromConst("https://imgur.com/"), String_FromConst("https://i.imgur.com/"), +}; +/* Converts say dl.dropbox.com/xyZ into dl.dropboxusercontent.com/xyz */ +static void Http_GetUrl(struct HttpRequest* req, cc_string* dst) { + cc_string url = String_FromRawArray(req->url); + cc_string part; + int i; + + for (i = 0; i < Array_Elems(urlRewrites); i += 2) { + if (!String_CaselessStarts(&url, &urlRewrites[i])) continue; + + part = String_UNSAFE_SubstringAt(&url, urlRewrites[i].length); + String_Format2(dst, "%s%s", &urlRewrites[i + 1], &part); + return; + } + String_Copy(dst, &url); +} + + +/* Updates state after a completed http request */ +static void Http_FinishRequest(struct HttpRequest* req) { + req->success = !req->result && req->statusCode == 200 && req->data && req->size; + + if (!req->success) { + char* error = req->error; req->error = NULL; + HttpRequest_Free(req); + req->error = error; + /* TODO don't HttpRequest_Free here? */ + } + + Mutex_Lock(processedMutex); + { + req->timeDownloaded = DateTime_CurrentUTC(); + RequestList_Append(&processedReqs, req, false); + } + Mutex_Unlock(processedMutex); +} + +/* Deletes cached responses that are over 10 seconds old */ +static void Http_CleanCacheTask(struct ScheduledTask* task) { + struct HttpRequest* item; + int i; + + Mutex_Lock(processedMutex); + { + TimeMS now = DateTime_CurrentUTC(); + for (i = processedReqs.count - 1; i >= 0; i--) { + item = &processedReqs.entries[i]; + if (now > item->timeDownloaded + 10) continue; + + HttpRequest_Free(item); + RequestList_RemoveAt(&processedReqs, i); + } + } + Mutex_Unlock(processedMutex); +} + + +/*########################################################################################################################* +*----------------------------------------------------Http public api------------------------------------------------------* +*#########################################################################################################################*/ +int Http_AsyncGetSkin(const cc_string* skinName, cc_uint8 flags) { + cc_string url; char urlBuffer[URL_MAX_SIZE]; + String_InitArray(url, urlBuffer); + + if (Utils_IsUrlPrefix(skinName)) { + String_Copy(&url, skinName); + } else { + String_Format2(&url, "%s/%s.png", &skinServer, skinName); + } + return Http_AsyncGetData(&url, flags); +} + +int Http_AsyncGetData(const cc_string* url, cc_uint8 flags) { + return Http_Add(url, flags, REQUEST_TYPE_GET, NULL, NULL, NULL, 0, NULL); +} +int Http_AsyncGetHeaders(const cc_string* url, cc_uint8 flags) { + return Http_Add(url, flags, REQUEST_TYPE_HEAD, NULL, NULL, NULL, 0, NULL); +} +int Http_AsyncPostData(const cc_string* url, cc_uint8 flags, const void* data, cc_uint32 size, struct StringsBuffer* cookies) { + return Http_Add(url, flags, REQUEST_TYPE_POST, NULL, NULL, data, size, cookies); +} +int Http_AsyncGetDataEx(const cc_string* url, cc_uint8 flags, const cc_string* lastModified, const cc_string* etag, struct StringsBuffer* cookies) { + return Http_Add(url, flags, REQUEST_TYPE_GET, lastModified, etag, NULL, 0, cookies); +} + +static cc_bool Http_UrlDirect(cc_uint8 c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') + || c == '-' || c == '_' || c == '.' || c == '~'; +} + +void Http_UrlEncode(cc_string* dst, const cc_uint8* data, int len) { + int i; + for (i = 0; i < len; i++) { + cc_uint8 c = data[i]; + + if (Http_UrlDirect(c)) { + String_Append(dst, c); + } else { + String_Append(dst, '%'); + String_AppendHex(dst, c); + } + } +} + +void Http_UrlEncodeUtf8(cc_string* dst, const cc_string* src) { + cc_uint8 data[4]; + int i, len; + + for (i = 0; i < src->length; i++) { + len = Convert_CP437ToUtf8(src->buffer[i], data); + Http_UrlEncode(dst, data, len); + } +} + +/* Outputs more detailed information about errors with http requests */ +static cc_bool HttpBackend_DescribeError(cc_result res, cc_string* dst); + +void Http_LogError(const char* action, const struct HttpRequest* item) { + cc_string msg; char msgBuffer[512]; + String_InitArray(msg, msgBuffer); + + Logger_FormatWarn(&msg, item->result, action, HttpBackend_DescribeError); + if (item->error && item->error[0]) { + String_Format1(&msg, "\n Error details: %c", item->error); + } + Logger_WarnFunc(&msg); +} + + +/*########################################################################################################################* +*-----------------------------------------------------Http component------------------------------------------------------* +*#########################################################################################################################*/ +static void Http_InitCommon(void) { +#if defined CC_BUILD_NDS + httpOnly = Options_GetBool(OPT_HTTP_ONLY, true); +#else + httpOnly = Options_GetBool(OPT_HTTP_ONLY, false); +#endif + httpsVerify = Options_GetBool(OPT_HTTPS_VERIFY, true); + + Options_Get(OPT_SKIN_SERVER, &skinServer, SKINS_SERVER); + ScheduledTask_Add(30, Http_CleanCacheTask); +} +static void Http_Init(void); + +struct IGameComponent Http_Component = { + Http_Init, /* Init */ + Http_ClearPending,/* Free */ + Http_ClearPending /* Reset */ +}; |