summary refs log tree commit diff
path: root/src/_HttpBase.h
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/_HttpBase.h
initial commit
Diffstat (limited to 'src/_HttpBase.h')
-rw-r--r--src/_HttpBase.h313
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 */
+};