#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 */ };