#define SLON_HTTP_BUFFER_SIZE 10485760 #define SLON_HTTP_VERB_DELETE 1 #define SLON_HTTP_VERB_GET 2 #define SLON_HTTP_VERB_OPTIONS 3 #define SLON_HTTP_VERB_PATCH 4 #define SLON_HTTP_VERB_POST 5 #define SLON_HTTP_VERB_PUT 6 #define SLON_MULTIPART_CONSUME_BOUNDARY 0 #define SLON_MULTIPART_CONSUME_CONTENT_DISPOSITION_HEADER 10 #define SLON_MULTIPART_CONSUME_CONTENT_DISPOSITION_NAME_FIELD 11 #define SLON_MULTIPART_CONSUME_CONTENT_DISPOSITION_NAME 12 #define SLON_MULTIPART_CONSUME_CONTENT_DISPOSITION_TEXT_OR_FILE 13 #define SLON_MULTIPART_CONSUME_CONTENT_TYPE_HEADER 20 #define SLON_MULTIPART_CONSUME_CONTENT_TYPE 21 #define SLON_MULTIPART_CONSUME_DATA 30 #define SLON_MULTIPART_SKIP_REMAINING_HEADERS 100 #define SLON_SCRATCH_BUFFER_AND_REQUEST_JSON \ U8 scratch_buffer[256]; \ JsonObject* request_json = @slon_http_request_json(session); JsonObject* SLON_HTTP_STATUS_CODES = Json.ParseFile("M:/Slon/Settings/status_codes.json", slon_mem_task); JsonArray* SLON_TLDS = Json.ParseFile("M:/Slon/Settings/tlds.json", slon_mem_task); I64 tld_cnt = 0; U8** tld_array = CAlloc(sizeof(U8*) * SLON_TLDS->length); for (tld_cnt = 0; tld_cnt < SLON_TLDS->length; tld_cnt++) { tld_array[tld_cnt] = SLON_TLDS->@(tld_cnt); } class SlonMultipartParser { U8* data; CFifoU8* consumed; I64 pos; I64 length; I64 state; }; class SlonMultipartFile { U8* buffer; I64 size; U8* content_type; }; class SlonHttpBuffer { U8* data; I64 size; I64 capacity; }; class SlonHttpRequest { SlonHttpBuffer* buffer; JsonObject* headers; JsonObject* json; U8* data; I64 size; U8* verb; U8* raw_path; U8* path; I64 path_segments_count; U8* path_segments_src; U8** path_segments; Bool headers_have_been_parsed; }; class SlonHttpResponse { SlonHttpBuffer* buffer; JsonObject* headers; U8* data; I64 size; I64 status_code; }; class SlonHttpSession { U64 s; CTask* mem_task; SlonHttpRequest* request; SlonHttpResponse* response; I64 bytes_used; JsonObject* auth; U8* actor_for_key_id; U8* (*header)(U8* key, U8* value = NULL); U0 (*send)(U64 payload, I64 size = NULL); U8* (*path)(I64 segment = NULL); I64 (*path_count)(); I64 (*status)(I64 code = NULL); I64 (*verb)(Bool return_str = FALSE); U0 (*content_type)(U8* value); }; U64 @slon_calloc(SlonHttpSession* session, I64 size) { if (!session || !size) return NULL; U64 res = CAlloc(size, slon_mem_task); session->bytes_used += MSize2(res); // AdamLog("@slon_calloc: requested %d, total used: %d\n", MSize2(res), session->bytes_used); return res; } U0 @slon_free(SlonHttpSession* session, U64 ptr) { if (!session || !ptr) return; session->bytes_used -= MSize2(ptr); // AdamLog("@slon_free: freed %d, total used: %d\n", MSize2(ptr), session->bytes_used); Free(ptr); } U64 @slon_malloc(SlonHttpSession* session, I64 size) { if (!session || !size) return NULL; U64 res = MAlloc(size, slon_mem_task); session->bytes_used += MSize2(res); // AdamLog("@slon_malloc: requested %d, total used: %d\n", MSize2(res), session->bytes_used); return res; } U8* @slon_strnew(SlonHttpSession* session, U8* str) { if (!session || !str) return NULL; U8* new = StrNew(str, slon_mem_task); session->bytes_used += MSize2(new); // AdamLog("@slon_strnew: requested %d, total used: %d\n", MSize2(new), session->bytes_used); // AdamLog("@slon_strnew: %s\n", new); return new; } U8* @slon_http_decode_urlencoded_string(SlonHttpSession* session, U8* str) { if (!StrFind("%", str) && !StrFind("+", str)) { return @slon_strnew(session, str); } U8* decoded_string = @slon_calloc(session, StrLen(str)); I64 i = 0; I64 j; U32 code_point; while (i < StrLen(str)) { if (str[i] == '%') { code_point = 0; for (j = 2; j > 0; j--) { if (str[i + j] >= '0' && str[i + j] <= '9') code_point += (@t(j == 1, 16, 1) * (str[i + j] - '0')); if (str[i + j] >= 'A' && str[i + j] <= 'F') code_point += (@t(j == 1, 16, 1) * (10 + (str[i + j] - 'A'))); if (str[i + j] >= 'a' && str[i + j] <= 'f') code_point += (@t(j == 1, 16, 1) * (10 + (str[i + j] - 'a'))); } String.Append(decoded_string, "%c", code_point); i += 3; } else if (str[i] == '+') { String.Append(decoded_string, " "); i++; } else { String.Append(decoded_string, "%c", str[i]); i++; } } return decoded_string; } JsonObject* @slon_http_request_json(SlonHttpSession* session) { if (!session->request->json) return SLON_EMPTY_JSON_OBJECT; return session->request->json; } U0 @slon_http_set_header(SlonHttpSession* session, U8* key, U8* value) { JsonObject* headers = session->response->headers; if (!StrICmp(value, "")) { headers->unset(key); } else { headers->set(key, value, JSON_STRING); } } U0 @slon_http_set_content_type(SlonHttpSession* session, U8* value) { session->header("content-type", value); } U0 @slon_http_send_ap_json(SlonHttpSession* session, U64 json) { // a stringified copy of "json" is created, a strnew is sent, we clean up stringified copy, sender cleans up "json" session->status(200); session->content_type("application/activity+json; charset=utf-8"); U8* json_string = Json.Stringify(json, slon_mem_task); session->response->data = @slon_strnew(session, json_string); session->response->size = StrLen(session->response->data); Free(json_string); } U0 @slon_http_send_json(SlonHttpSession* session, U64 json) { // a stringified copy of "json" is created, a strnew is sent, we clean up stringified copy, sender cleans up "json" session->status(200); session->content_type("application/json; charset=utf-8"); U8* json_string = Json.Stringify(json, slon_mem_task); session->response->data = @slon_strnew(session, json_string); session->response->size = StrLen(session->response->data); Free(json_string); } U0 @slon_http_send_string(SlonHttpSession* session, U8* str) { // a strnew of "str" is sent, sender cleans up "str" session->status(200); session->response->data = @slon_strnew(session, str); session->response->size = StrLen(str); } U0 @slon_http_send(SlonHttpSession* session, U64 data, I64 size) { // a malloc copy of "data" is sent, sender cleans up "data" session->status(200); U8* data_new = @slon_malloc(session, size); MemCpy(data_new, data, size); session->response->data = data_new; session->response->size = size; } U0 @slon_http_send_file(SlonHttpSession* session, U8* path) { if (!session || !path) return; if (!FileFind(path)) return; I64 size = 0; U8* data = FileRead(path, &size); session->send(data, size); Free(data); } U0 @slon_http_send_html_file(SlonHttpSession* session, U8* path) { session->content_type("text/html"); @slon_http_send_file(session, path); } U0 @slon_http_send_json_file(SlonHttpSession* session, U8* path, U8* content_type = "application/json; charset=utf-8") { session->content_type(content_type); @slon_http_send_file(session, path); } U8* @slon_http_request_path(SlonHttpSession* session, I64 segment = NULL) { if (segment) { if (!session->request->path_segments_count || segment >= session->request->path_segments_count) { return NULL; } return session->request->path_segments[segment]; } else { return session->request->path; } } I64 @slon_http_request_verb(SlonHttpSession* session, Bool return_str = FALSE) { if (return_str) { if (!StrCmp(session->request->verb, "DELETE")) return "DELETE"; if (!StrCmp(session->request->verb, "GET")) return "GET"; if (!StrCmp(session->request->verb, "OPTIONS")) return "OPTIONS"; if (!StrCmp(session->request->verb, "PATCH")) return "PATCH"; if (!StrCmp(session->request->verb, "POST")) return "POST"; if (!StrCmp(session->request->verb, "PUT")) return "PUT"; } else { if (!StrCmp(session->request->verb, "DELETE")) return SLON_HTTP_VERB_DELETE; if (!StrCmp(session->request->verb, "GET")) return SLON_HTTP_VERB_GET; if (!StrCmp(session->request->verb, "OPTIONS")) return SLON_HTTP_VERB_OPTIONS; if (!StrCmp(session->request->verb, "PATCH")) return SLON_HTTP_VERB_PATCH; if (!StrCmp(session->request->verb, "POST")) return SLON_HTTP_VERB_POST; if (!StrCmp(session->request->verb, "PUT")) return SLON_HTTP_VERB_PUT; } return 999; } U8* @slon_http_request_header(SlonHttpSession* session, U8* key) { U64 value = session->request->headers->@(key); if (!value) return ""; return value; } Bool @slon_http_request_has_query_string(SlonHttpSession* session) { return StrFind("?", session->request->raw_path) > 0 && !String.EndsWith("?", session->request->raw_path); } #define SLON_WRAPPER_MAGIC_NUMBER 0xC0DECAFEC0DECAFE I64 @slon_session_status_wrapper_function(I64 code) { SlonHttpSession* session = SLON_WRAPPER_MAGIC_NUMBER; if (code) { session->response->status_code = code; } return session->response->status_code; } U8* @slon_session_header_wrapper_function(U8* key, U8* value = NULL) { SlonHttpSession* session = SLON_WRAPPER_MAGIC_NUMBER; if (!value) { return @slon_http_request_header(session, key); } @slon_http_set_header(session, key, value); return value; } U0 @slon_session_send_wrapper_function(U64 payload, I64 size = NULL) { SlonHttpSession* session = SLON_WRAPPER_MAGIC_NUMBER; if (!payload) { return; } if (*payload(U32*) == JSON_SIG) { @slon_http_send_json(session, payload); return; } if (!size) { @slon_http_send_string(session, payload); } else { @slon_http_send(session, payload, size); } } I64 @slon_session_verb_wrapper_function(Bool return_str = FALSE) { SlonHttpSession* session = SLON_WRAPPER_MAGIC_NUMBER; return @slon_http_request_verb(session, return_str); } U8* @slon_session_path_wrapper_function(I64 segment = NULL) { SlonHttpSession* session = SLON_WRAPPER_MAGIC_NUMBER; return @slon_http_request_path(session, segment); } I64 @slon_session_path_count_wrapper_function() { SlonHttpSession* session = SLON_WRAPPER_MAGIC_NUMBER; return session->request->path_segments_count; } U0 @slon_session_content_type_wrapper_function(U8* value) { SlonHttpSession* session = SLON_WRAPPER_MAGIC_NUMBER; session->header("content-type", value); }