Mercurial
diff hg-web/main.c @ 195:f8f5004a920a
Merging back hg-web-tip
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 27 Jan 2026 06:51:44 -0800 |
| parents | 9f4429c49733 |
| children |
line wrap: on
line diff
--- a/hg-web/main.c Sat Jan 24 06:37:43 2026 -0800 +++ b/hg-web/main.c Tue Jan 27 06:51:44 2026 -0800 @@ -11,37 +11,10 @@ #include <netdb.h> #define HG_SERVE_HOST "127.0.0.1" -#define HG_SERVE_PORT 4444 +#define HG_SERVE_PORT "4444" #define MAX_PATH 4096 -// TODO: Move this to seobeo.... -// Asked AI to create this lol, probably should learn to decode it myself.. -static void url_decode(char *dst, const char *src) -{ - char a, b; - while (*src) { - if ((*src == '%') && - ((a = src[1]) && (b = src[2])) && - (isxdigit(a) && isxdigit(b))) { - if (a >= 'a') a -= 'a'-'A'; - if (a >= 'A') a -= ('A' - 10); - else a -= '0'; - if (b >= 'a') b -= 'a'-'A'; - if (b >= 'A') b -= ('A' - 10); - else b -= '0'; - *dst++ = 16*a+b; - src+=3; - } else if (*src == '+') { - *dst++ = ' '; - src++; - } else { - *dst++ = *src++; - } - } - *dst = '\0'; -} - static char* sanitize_path(const char *input_path, Dowa_Arena *arena) { if (!input_path || strlen(input_path) == 0) @@ -57,8 +30,10 @@ for (size_t i = 0; i < len; i++) { - if (input_path[i] == '.' && (i == 0 || input_path[i-1] == '/')) { - if (i + 1 < len && input_path[i+1] == '.') { + if (input_path[i] == '.' && (i == 0 || input_path[i-1] == '/')) + { + if (i + 1 < len && input_path[i+1] == '.') + { // Skip ".." i++; continue; @@ -79,146 +54,33 @@ return result; } -// Helper to connect to hg serve -static int hg_proxy_connect(void) +Seobeo_Client_Response *hg_proxy_request( + const char *method, + const char *path, + const char *req_body, + const char *hg_custom) { - int sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock < 0) - { - Seobeo_Log(SEOBEO_DEBUG, "Failed to create socket\n"); - return -1; - } - - struct sockaddr_in server_addr; - memset(&server_addr, 0, sizeof(server_addr)); - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(HG_SERVE_PORT); - inet_pton(AF_INET, HG_SERVE_HOST, &server_addr.sin_addr); - - if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) - { - Seobeo_Log(SEOBEO_DEBUG, "Failed to connect to hg serve at %s:%d\n", HG_SERVE_HOST, HG_SERVE_PORT); - close(sock); - return -1; - } - - return sock; -} - -// Generic helper to proxy a request to hg serve and get the response body -// Returns allocated body on success, NULL on failure -// out_status and out_content_type are optional output parameters -// out_body_len returns the actual body length (for binary content) -static char* hg_proxy_request( - const char *method, - const char *path, - const char *req_body, - size_t body_len, - char *out_status, // should be at least 4 bytes - char *out_content_type, // should be at least 256 bytes - size_t *out_body_len, // optional: returns actual body length - Dowa_Arena *arena) -{ - int sock = hg_proxy_connect(); - if (sock < 0) return NULL; - - // Build HTTP request - char http_request[MAX_PATH * 2]; - snprintf(http_request, sizeof(http_request), - "%s %s HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Connection: close\r\n" - "Accept: application/json, text/plain, */*\r\n" - "Content-Length: %zu\r\n" - "\r\n", - method, path, HG_SERVE_HOST, HG_SERVE_PORT, body_len); - - Seobeo_Log(SEOBEO_DEBUG, "HG Proxy request: %s %s\n", method, path); - - if (send(sock, http_request, strlen(http_request), 0) < 0) - { - close(sock); - return NULL; - } - - if (body_len > 0 && req_body) - { - send(sock, req_body, body_len, 0); - } + char full_path[MAX_PATH]; + snprintf(full_path, MAX_PATH, "http://%s:%s%s", HG_SERVE_HOST, HG_SERVE_PORT, path); + Seobeo_Log(SEOBEO_DEBUG, "HG Proxy PATH %s\n", full_path); + Seobeo_Client_Request *p_req = Seobeo_Client_Request_Create(full_path); + Seobeo_Client_Request_Set_Method(p_req, method); + Seobeo_Client_Request_Add_Header_Array(p_req, "User-Agent: Seobeo/1.0"); + Seobeo_Client_Request_Add_Header_Array(p_req, "Accept: application/json"); - // Read response - int buffer_size = 1024 * 1024 * 5; // 5MB - char *response_buf = Dowa_Arena_Allocate(arena, buffer_size); - size_t total_read = 0; - ssize_t bytes_read; - - while ((bytes_read = recv(sock, response_buf + total_read, buffer_size - total_read - 1, 0)) > 0) - { - total_read += bytes_read; - if (total_read >= (size_t)(buffer_size - 1)) break; - } - response_buf[total_read] = '\0'; - close(sock); - - Seobeo_Log(SEOBEO_DEBUG, "HG Proxy: received %zu bytes\n", total_read); - - // Parse response headers - use memmem to handle binary content - char *headers_end = NULL; - for (size_t i = 0; i + 3 < total_read; i++) - { - if (response_buf[i] == '\r' && response_buf[i+1] == '\n' && - response_buf[i+2] == '\r' && response_buf[i+3] == '\n') - { - headers_end = response_buf + i; - break; - } - } - if (!headers_end) return NULL; + if (hg_custom && hg_custom[0] != '\0') + { + char buffer[1024]; + snprintf(buffer, 1024, "x-hgarg-1: %s", hg_custom); + Seobeo_Client_Request_Add_Header_Array(p_req, buffer); + Seobeo_Log(SEOBEO_DEBUG, "HG CUSTOM %s\n", buffer); + } - // Extract status - if (out_status && strncmp(response_buf, "HTTP/", 5) == 0) - { - char *status_start = strchr(response_buf, ' '); - if (status_start) - { - strncpy(out_status, status_start + 1, 3); - out_status[3] = '\0'; - } - } - - // Extract content-type - if (out_content_type) - { - out_content_type[0] = '\0'; - char *ct_header = strcasestr(response_buf, "Content-Type:"); - if (ct_header && ct_header < headers_end) - { - ct_header += 13; - while (*ct_header == ' ') ct_header++; - char *ct_end = strpbrk(ct_header, "\r\n"); - if (ct_end) - { - size_t ct_len = ct_end - ct_header; - if (ct_len < 256) - { - strncpy(out_content_type, ct_header, ct_len); - out_content_type[ct_len] = '\0'; - } - } - } - } - - // Return body (copy to fresh allocation for clean pointer) - char *body = headers_end + 4; - size_t body_size = total_read - (body - response_buf); - - if (out_body_len) *out_body_len = body_size; - - char *result = Dowa_Arena_Allocate(arena, body_size + 1); - memcpy(result, body, body_size); - result[body_size] = '\0'; - - return result; + if (req_body) + Seobeo_Client_Request_Set_Body(p_req, req_body, strlen(req_body)); + Seobeo_Client_Response *p_resp = Seobeo_Client_Request_Execute(p_req); + Seobeo_Client_Request_Destroy(p_req); + return p_resp; } Seobeo_Request_Entry* ApiListDirectory(Seobeo_Request_Entry *req, Dowa_Arena *arena) @@ -229,7 +91,7 @@ const char *rel_path = path_kv ? ((Seobeo_Request_Entry*)path_kv)->value : ""; char *decoded_path = Dowa_Arena_Allocate(arena, strlen(rel_path) + 1); - url_decode(decoded_path, rel_path); + Seobeo_Url_Decode(decoded_path, rel_path); char *safe_path = sanitize_path(decoded_path, arena); @@ -241,14 +103,11 @@ else snprintf(hg_path, sizeof(hg_path), "/file/tip/?style=json"); - char status[4] = "200"; - char content_type[256] = ""; - size_t body_len = 0; - char *hg_response = hg_proxy_request("GET", hg_path, NULL, 0, status, content_type, &body_len, arena); + Seobeo_Client_Response *hg_response = hg_proxy_request("GET", hg_path, NULL, NULL); - Seobeo_Log(SEOBEO_DEBUG, "ApiListDirectory: status=%s body_len=%zu\n", status, body_len); + Seobeo_Log(SEOBEO_DEBUG, "ApiListDirectory: status=%i body_len=%zu\n", hg_response->status_code, hg_response->body_length); - if (!hg_response || status[0] != '2') + if (hg_response->status_code != 200) { Seobeo_Log(SEOBEO_DEBUG, "Failed to get directory from hg serve\n"); Dowa_HashMap_Push_Arena(resp, "status", "502", arena); @@ -256,11 +115,79 @@ Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Failed to connect to hg serve\"}", arena); return resp; } - char *json = hg_response; + + char *temp1 = Dowa_Arena_Copy(arena, hg_response->body, hg_response->body_length); + char *temp2 = Dowa_Arena_Allocate(arena, 256); + snprintf(temp2, 256, "%zu", hg_response->body_length); Dowa_HashMap_Push_Arena(resp, "status", "200", arena); Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); - Dowa_HashMap_Push_Arena(resp, "body", json, arena); + Dowa_HashMap_Push_Arena(resp, "body", temp1, arena); + Dowa_HashMap_Push_Arena(resp, "content-length", temp2, arena); + return resp; +} + +Seobeo_Request_Entry* ApiGetGraph(Seobeo_Request_Entry *req, Dowa_Arena *arena) +{ + Seobeo_Request_Entry *resp = NULL; + + void *path_kv = Dowa_HashMap_Get_Ptr(req, "QueryString"); + const char *rel_path = path_kv ? ((Seobeo_Request_Entry*)path_kv)->value : ""; + Seobeo_Log(SEOBEO_INFO, "ApiGetGraph: rel_path='%s'\n", rel_path); + void *graph_id_kv = Dowa_HashMap_Get_Ptr(req, ":graph_id"); + char *graph_id = ((Seobeo_Request_Entry*)graph_id_kv)->value; + Seobeo_Log(SEOBEO_INFO, "ApiGetGraph: graph_id='%s'\n", graph_id); + char *decoded_path = Dowa_Arena_Allocate(arena, strlen(rel_path) + 1); + Seobeo_Url_Decode(decoded_path, rel_path); + char *safe_path = sanitize_path(decoded_path, arena); + + Seobeo_Log(SEOBEO_INFO, "ApiGetGraph: safe_path='%s'\n", safe_path); + + if (strlen(safe_path) == 0) + { + Dowa_HashMap_Push_Arena(resp, "status", "400", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", "File path required", arena); + return resp; + } + + char hg_path[MAX_PATH]; + // void *graph_id_kv = Dowa_HashMap_Get_Ptr(req, ":graph_id"); + // char *graph_id = ((Seobeo_Request_Entry*)graph_id_kv)->value; + snprintf(hg_path, sizeof(hg_path), "/graph/%s?%s", graph_id, safe_path); + Seobeo_Client_Response *hg_response = hg_proxy_request("GET", hg_path, NULL, NULL); + + Seobeo_Log(SEOBEO_DEBUG, "ApiGetGraph: status=%i body_len=%zu\n", hg_response->status_code, hg_response->body_length); + + char status[4]; + snprintf(status, 4, "%i", hg_response->status_code); + + if (!hg_response->body) + { + Dowa_HashMap_Push_Arena(resp, "status", "502", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", "Failed to connect to hg serve", arena); + return resp; + } + + if (hg_response->status_code != 200) + { + Seobeo_Log(SEOBEO_DEBUG, "ApiGetGraph: error hg_response: %s\n", hg_response->body); + Dowa_HashMap_Push_Arena(resp, "status", status, arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", hg_response->body, arena); + return resp; + } + + + char *temp1 = Dowa_Arena_Copy(arena, hg_response->body, hg_response->body_length); + char *temp2 = Dowa_Arena_Allocate(arena, 256); + snprintf(temp2, 256, "%zu", hg_response->body_length); + + Dowa_HashMap_Push_Arena(resp, "status", "200", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", temp1, arena); + Dowa_HashMap_Push_Arena(resp, "content-length", temp2, arena); return resp; } @@ -272,7 +199,7 @@ void *path_kv = Dowa_HashMap_Get_Ptr(req, "query_path"); const char *rel_path = path_kv ? ((Seobeo_Request_Entry*)path_kv)->value : ""; char *decoded_path = Dowa_Arena_Allocate(arena, strlen(rel_path) + 1); - url_decode(decoded_path, rel_path); + Seobeo_Url_Decode(decoded_path, rel_path); char *safe_path = sanitize_path(decoded_path, arena); Seobeo_Log(SEOBEO_INFO, "ApiGetFile: safe_path='%s'\n", safe_path); @@ -285,18 +212,16 @@ return resp; } - // Build hg serve URL: /raw-file/tip/<path> char hg_path[MAX_PATH]; snprintf(hg_path, sizeof(hg_path), "/raw-file/tip/%s", safe_path); + Seobeo_Client_Response *hg_response = hg_proxy_request("GET", hg_path, NULL, NULL); - char status[4] = "200"; - char content_type[256] = ""; - size_t body_len = 0; - char *body = hg_proxy_request("GET", hg_path, NULL, 0, status, content_type, &body_len, arena); + Seobeo_Log(SEOBEO_DEBUG, "ApiGetFile: status=%i body_len=%zu\n", hg_response->status_code, hg_response->body_length); - Seobeo_Log(SEOBEO_DEBUG, "ApiGetFile: status=%s body_len=%zu\n", status, body_len); + char status[4]; + snprintf(status, 4, "%i", hg_response->status_code); - if (!body) + if (!hg_response->body) { Dowa_HashMap_Push_Arena(resp, "status", "502", arena); Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); @@ -304,31 +229,24 @@ return resp; } - if (status[0] != '2') + if (hg_response->status_code != 200) { - Seobeo_Log(SEOBEO_DEBUG, "ApiGetFile: error response: %s\n", body); + Seobeo_Log(SEOBEO_DEBUG, "ApiGetFile: error hg_response: %s\n", hg_response->body); Dowa_HashMap_Push_Arena(resp, "status", status, arena); Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); - // Return actual error from hg serve if available - Dowa_HashMap_Push_Arena(resp, "body", body_len > 0 ? body : "File not found", arena); + Dowa_HashMap_Push_Arena(resp, "body", hg_response->body, arena); return resp; } - // Use content-type from hg serve, or determine from extension - const char *final_content_type = content_type; - if (strlen(content_type) == 0 || strcmp(content_type, "application/octet-stream") == 0) - { - final_content_type = "text/plain"; - if (strstr(safe_path, ".md")) final_content_type = "text/markdown"; - else if (strstr(safe_path, ".html")) final_content_type = "text/html"; - else if (strstr(safe_path, ".css")) final_content_type = "text/css"; - else if (strstr(safe_path, ".js")) final_content_type = "application/javascript"; - else if (strstr(safe_path, ".json")) final_content_type = "application/json"; - } + + char *temp1 = Dowa_Arena_Copy(arena, hg_response->body, hg_response->body_length); + char *temp2 = Dowa_Arena_Allocate(arena, 256); + snprintf(temp2, 256, "%zu", hg_response->body_length); Dowa_HashMap_Push_Arena(resp, "status", "200", arena); - Dowa_HashMap_Push_Arena(resp, "content-type", final_content_type, arena); - Dowa_HashMap_Push_Arena(resp, "body", body, arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", temp1, arena); + Dowa_HashMap_Push_Arena(resp, "content-length", temp2, arena); return resp; } @@ -337,155 +255,194 @@ return ApiGetFile(req, arena); } +// Streaming handler for hg wire protocol - pipes data directly without buffering +void StreamHgWireProtocol(Seobeo_Handle *p_client, Seobeo_Request_Entry *req, Dowa_Arena *arena) +{ + void *method_kv = Dowa_HashMap_Get_Ptr(req, "HTTP_Method"); + const char *method = method_kv ? ((Seobeo_Request_Entry*)method_kv)->value : "GET"; + + void *query_kv = Dowa_HashMap_Get_Ptr(req, "QueryString"); + const char *query_string = query_kv ? ((Seobeo_Request_Entry*)query_kv)->value : ""; + + void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body"); + const char *req_body = body_kv ? ((Seobeo_Request_Entry*)body_kv)->value : ""; + + const char *hg_custom = req[7].value; + + Seobeo_Log(SEOBEO_DEBUG, "HG Stream Proxy: method=%s query=%s\n", method, query_string); + + // THINKING: Connect to hg serve + // This kinda blows, but not a good way to handle it since my client API assumes it is all stored in + // buffer and what not. + Seobeo_Handle *p_upstream = Seobeo_Stream_Handle_Client_Create(HG_SERVE_HOST, HG_SERVE_PORT, FALSE); + if (!p_upstream || p_upstream->socket < 0) + { + const char *err_resp = "HTTP/1.1 502 Bad Gateway\r\nContent-Length: 26\r\n\r\nFailed to connect upstream"; + Seobeo_Handle_Queue(p_client, (uint8*)err_resp, strlen(err_resp)); + Seobeo_Handle_Flush(p_client); + if (p_upstream) + Seobeo_Handle_Destroy(p_upstream); + return; + } + + // Create headers + // we only allow x-hgarg-1 and content-length + char request_buf[8192]; + int req_len = snprintf(request_buf, sizeof(request_buf), + "%s /?%s HTTP/1.1\r\n" + "Host: %s:%s\r\n" + "User-Agent: Seobeo/1.0\r\n" + "Connection: close\r\n", + method, query_string, HG_SERVE_HOST, HG_SERVE_PORT); + + if (hg_custom && hg_custom[0] != '\0') + req_len += snprintf(request_buf + req_len, sizeof(request_buf) - req_len, "x-hgarg-1: %s\r\n", hg_custom); + + if (req_body && req_body[0] != '\0') + req_len += snprintf(request_buf + req_len, sizeof(request_buf) - req_len, "Content-Length: %zu\r\n\r\n%s", strlen(req_body), req_body); + else + req_len += snprintf(request_buf + req_len, sizeof(request_buf) - req_len, "\r\n"); + + Seobeo_Handle_Queue(p_upstream, (uint8*)request_buf, req_len); + if (Seobeo_Handle_Flush(p_upstream) < 0) + { + const char *err_resp = "HTTP/1.1 502 Bad Gateway\r\nContent-Length: 21\r\n\r\nUpstream write failed"; + Seobeo_Handle_Queue(p_client, (uint8*)err_resp, strlen(err_resp)); + Seobeo_Handle_Flush(p_client); + Seobeo_Handle_Destroy(p_upstream); + return; + } + + // Responses + while (1) + { + int r = Seobeo_Handle_Read(p_upstream); + if (r < 0) + { + Seobeo_Handle_Destroy(p_upstream); + return; + } + if (p_upstream->read_buffer_len >= 4 && + strstr((char*)p_upstream->read_buffer, "\r\n\r\n") != NULL) + break; + if (r == 0) + continue; + } + + // TODO: Maybe make this into a separate function instead of internal function as doing this over and over again blows. + char *hdr_end = strstr((char*)p_upstream->read_buffer, "\r\n\r\n"); + if (!hdr_end) + { + Seobeo_Handle_Destroy(p_upstream); + return; + } + size_t hdr_len = hdr_end - (char*)p_upstream->read_buffer + 4; + Seobeo_Handle_Queue(p_client, p_upstream->read_buffer, hdr_len); + Seobeo_Handle_Flush(p_client); + + // All body + size_t body_in_buffer = p_upstream->read_buffer_len - hdr_len; + if (body_in_buffer > 0) + { + Seobeo_Handle_Queue(p_client, p_upstream->read_buffer + hdr_len, body_in_buffer); + Seobeo_Handle_Flush(p_client); + } + Seobeo_Handle_Consume(p_upstream, p_upstream->read_buffer_len); + while (1) + { + int n = Seobeo_Handle_Read(p_upstream); + if (n > 0) + { + Seobeo_Handle_Queue(p_client, p_upstream->read_buffer, p_upstream->read_buffer_len); + Seobeo_Handle_Flush(p_client); + Seobeo_Handle_Consume(p_upstream, p_upstream->read_buffer_len); + } + else if (n == -2) + break; + else if (n < 0) + break; + } + + Seobeo_Handle_Destroy(p_upstream); +} + Seobeo_Request_Entry* ApiHgWireProtocol(Seobeo_Request_Entry *req, Dowa_Arena *arena) { - Seobeo_Request_Entry *resp = NULL; + Seobeo_Request_Entry *resp = NULL; - // Get method - void *method_kv = Dowa_HashMap_Get_Ptr(req, "HTTP_Method"); - const char *method = method_kv ? ((Seobeo_Request_Entry*)method_kv)->value : "GET"; - - // Get query string - void *query_kv = Dowa_HashMap_Get_Ptr(req, "QueryString"); - const char *query_string = query_kv ? ((Seobeo_Request_Entry*)query_kv)->value : ""; + void *method_kv = Dowa_HashMap_Get_Ptr(req, "HTTP_Method"); + const char *method = method_kv ? ((Seobeo_Request_Entry*)method_kv)->value : "GET"; - // Get request body for POST - void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body"); - const char *req_body = body_kv ? ((Seobeo_Request_Entry*)body_kv)->value : ""; - size_t body_len = strlen(req_body); - - Seobeo_Log(SEOBEO_DEBUG, "HG Proxy: method=%s query=%s body_len=%zu\n", method, query_string, body_len); + void *query_kv = Dowa_HashMap_Get_Ptr(req, "QueryString"); + const char *query_string = query_kv ? ((Seobeo_Request_Entry*)query_kv)->value : ""; - // Connect to hg serve - int sock = hg_proxy_connect(); - if (sock < 0) - { - Dowa_HashMap_Push_Arena(resp, "status", "502", arena); - Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); - Dowa_HashMap_Push_Arena(resp, "body", "Failed to connect to hg serve", arena); - return resp; - } + void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body"); + const char *req_body = body_kv ? ((Seobeo_Request_Entry*)body_kv)->value : ""; + size_t body_len = strlen(req_body); - // Build the HTTP request to forward to hg serve - char http_request[MAX_PATH * 2]; - if (strlen(query_string) > 0) - { - snprintf(http_request, sizeof(http_request), - "%s /?%s HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Connection: close\r\n" - "Content-Length: %zu\r\n" - "\r\n", - method, query_string, HG_SERVE_HOST, HG_SERVE_PORT, body_len); - } - else - { - snprintf(http_request, sizeof(http_request), - "%s / HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Connection: close\r\n" - "Content-Length: %zu\r\n" - "\r\n", - method, HG_SERVE_HOST, HG_SERVE_PORT, body_len); - } + const char *hg_custom = req[7].value; + Seobeo_Log(SEOBEO_DEBUG, "HG Proxy: method=%s query=%s body_len=%zu\n", method, query_string, body_len); + + Seobeo_Client_Response *hg_response; - // Send HTTP request headers - if (send(sock, http_request, strlen(http_request), 0) < 0) - { - Seobeo_Log(SEOBEO_DEBUG, "Failed to send request to hg serve\n"); - close(sock); - Dowa_HashMap_Push_Arena(resp, "status", "502", arena); - Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); - Dowa_HashMap_Push_Arena(resp, "body", "Failed to send to hg serve", arena); - return resp; - } + char hg_path[MAX_PATH]; + snprintf(hg_path, sizeof(hg_path), "/?%s", query_string); + + hg_response = hg_proxy_request(method, hg_path, req_body, hg_custom); + + Seobeo_Log(SEOBEO_DEBUG, "HG Proxy: received %zu bytes\n", hg_response->body_length); + + Seobeo_Request_Entry *kv = Dowa_HashMap_Get_Ptr(hg_response->headers, "Content-Type"); - // Send body if present - if (body_len > 0) - { - send(sock, req_body, body_len, 0); - } - - // Read response from hg serve - int buffer_size = 1024 * 1024 * 5; // 5MB - char *response_buf = Dowa_Arena_Allocate(arena, buffer_size); - size_t total_read = 0; - ssize_t bytes_read; + char *status = Dowa_Arena_Allocate(arena, 5); + snprintf(status, 4, "%i", hg_response->status_code); - while ((bytes_read = recv(sock, response_buf + total_read, buffer_size - total_read - 1, 0)) > 0) - { - total_read += bytes_read; - if (total_read >= (size_t)(buffer_size - 1)) break; - } - response_buf[total_read] = '\0'; - close(sock); - - Seobeo_Log(SEOBEO_DEBUG, "HG Proxy: received %zu bytes\n", total_read); + // Use binary-safe copy to handle null bytes in mercurial bundle data + char *temp1 = Dowa_Arena_Copy(arena, hg_response->body, hg_response->body_length); + char *temp2 = Dowa_Arena_Allocate(arena, 256); + snprintf(temp2, 256, "%zu", hg_response->body_length); - // Parse HTTP response - find headers end - char *headers_end = strstr(response_buf, "\r\n\r\n"); - if (!headers_end) - { - Dowa_HashMap_Push_Arena(resp, "status", "502", arena); - Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); - Dowa_HashMap_Push_Arena(resp, "body", "Invalid response from hg serve", arena); - return resp; - } + Dowa_HashMap_Push_Arena(resp, "status", status, arena); + Dowa_HashMap_Push_Arena(resp, "content-type", kv->value, arena); + Dowa_HashMap_Push_Arena(resp, "body", temp1, arena); + Dowa_HashMap_Push_Arena(resp, "content-length", temp2, arena); - // Extract status code from first line (e.g., "HTTP/1.1 200 OK") - char status_code[4] = "200"; - if (strncmp(response_buf, "HTTP/", 5) == 0) - { - char *status_start = strchr(response_buf, ' '); - if (status_start) - { - strncpy(status_code, status_start + 1, 3); - status_code[3] = '\0'; - } - } + return resp; +} - // Extract content-type from headers - const char *content_type = "application/mercurial-0.1"; - char *ct_header = strcasestr(response_buf, "Content-Type:"); - if (ct_header && ct_header < headers_end) - { - ct_header += 13; // Skip "Content-Type:" - while (*ct_header == ' ') ct_header++; - char *ct_end = strpbrk(ct_header, "\r\n"); - if (ct_end) - { - size_t ct_len = ct_end - ct_header; - char *ct_copy = Dowa_Arena_Allocate(arena, ct_len + 1); - strncpy(ct_copy, ct_header, ct_len); - ct_copy[ct_len] = '\0'; - content_type = ct_copy; - } - } +Seobeo_Request_Entry* GetReactHome(Seobeo_Request_Entry *req, Dowa_Arena *arena) +{ + size_t file_size = 0; + char *html = Seobeo_Web_LoadFile("/index.html", &file_size); - // Body starts after \r\n\r\n - char *body = headers_end + 4; - - Dowa_HashMap_Push_Arena(resp, "status", status_code, arena); - Dowa_HashMap_Push_Arena(resp, "content-type", content_type, arena); - Dowa_HashMap_Push_Arena(resp, "body", body, arena); - - return resp; + printf("%s", html); + Seobeo_Request_Entry *resp = NULL; + Dowa_HashMap_Push_Arena(resp, "status", "200", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/html", arena); + Dowa_HashMap_Push_Arena(resp, "body", html, arena); + return resp; } int main(void) { Seobeo_Router_Init(); + + Seobeo_Router_Register("GET", "/", GetReactHome); + Seobeo_Router_Register("GET", "/directories", GetReactHome); + Seobeo_Router_Register("GET", "/graph", GetReactHome); + Seobeo_Router_Register("GET", "/api/repo/list", ApiListDirectory); Seobeo_Router_Register("GET", "/api/repo/file", ApiGetFile); + Seobeo_Router_Register("GET", "/api/graph/:graph_id", ApiGetGraph); Seobeo_Router_Register("GET", "/api/repo/readme", ApiGetReadme); - Seobeo_Router_Register("GET", "/repo", ApiHgWireProtocol); - Seobeo_Router_Register("POST", "/repo", ApiHgWireProtocol); + // Use streaming handler for hg wire protocol... + Seobeo_Router_Register_Stream("GET", "/repo", StreamHgWireProtocol); + Seobeo_Router_Register_Stream("POST", "/repo", StreamHgWireProtocol); printf("Starting on Port 6970...\n"); - printf("Repository: %s\n", REPO_ROOT); - int result = Seobeo_Web_Server_Start("hg-web/src", "6970", SEOBEO_MODE_EDGE, 4); + int result = Seobeo_Web_Server_Start("hg-web/src", "6970", SEOBEO_MODE_EDGE, 1); Seobeo_Router_Destroy();