Mercurial
view hg-web/main.c @ 173:827c6ac504cd hg-web
Merged in default here.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 19 Jan 2026 18:59:10 -0800 |
| parents | 6de849867459 |
| children | 71ad34a8bc9a |
line wrap: on
line source
#include "seobeo/seobeo.h" #include "dowa/dowa.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #define HG_SERVE_HOST "127.0.0.1" #define HG_SERVE_PORT "4444" #define MAX_PATH 4096 static char* sanitize_path(const char *input_path, Dowa_Arena *arena) { if (!input_path || strlen(input_path) == 0) { char *empty = Dowa_Arena_Allocate(arena, 1); empty[0] = '\0'; return empty; } size_t len = strlen(input_path); char *result = Dowa_Arena_Allocate(arena, len + 1); size_t j = 0; 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] == '.') { // Skip ".." i++; continue; } // Skip "." continue; } result[j++] = input_path[i]; } result[j] = '\0'; // Remove leading/trailing slashes while (result[0] == '/') memmove(result, result + 1, strlen(result)); while (j > 0 && result[j-1] == '/') result[--j] = '\0'; return result; } Seobeo_Client_Response *hg_proxy_request( const char *method, const char *path, const char *req_body) { char full_path[MAX_PATH]; snprintf(full_path, MAX_PATH, "http://%s:%s%s", HG_SERVE_HOST, HG_SERVE_PORT, path); Seobeo_Client_Request *p_req = Seobeo_Client_Request_Create(full_path); Seobeo_Client_Request_Add_Header_Array(p_req, "User-Agent: Seobeo/1.0 (Array Mode)"); Seobeo_Client_Request_Add_Header_Array(p_req, "Accept: application/json"); Seobeo_Client_Request_Add_Header_Array(p_req, "X-Test-Header: TestValue"); if (strcmp(method, "POST")) Seobeo_Client_Request_Set_Method(p_req, "POST"); Seobeo_Client_Request_Set_Body(p_req, req_body, 0); 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) { Seobeo_Request_Entry *resp = NULL; 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); Seobeo_Url_Decode(decoded_path, rel_path); char *safe_path = sanitize_path(decoded_path, arena); Seobeo_Log(SEOBEO_INFO, "ApiListDirectory: safe_path='%s'\n", safe_path); char hg_path[MAX_PATH]; if (strlen(safe_path) > 0) snprintf(hg_path, sizeof(hg_path), "/file/tip/%s?style=json", safe_path); else snprintf(hg_path, sizeof(hg_path), "/file/tip/?style=json"); Seobeo_Client_Response *hg_response = hg_proxy_request("GET", hg_path, NULL); Seobeo_Log(SEOBEO_DEBUG, "ApiListDirectory: status=%i body_len=%zu\n", hg_response->status_code, hg_response->body_length); char status[4]; snprintf(status, 3, "%i", hg_response->status_code); 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); Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Failed to connect to hg serve\"}", arena); return resp; } Dowa_HashMap_Push_Arena(resp, "status", "200", arena); Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); Dowa_HashMap_Push_Arena(resp, "body", hg_response->body, arena); Seobeo_Client_Response_Destroy(hg_response); return resp; } Seobeo_Request_Entry* ApiGetFile(Seobeo_Request_Entry *req, Dowa_Arena *arena) { Seobeo_Request_Entry *resp = NULL; 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); 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); 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]; snprintf(hg_path, sizeof(hg_path), "/raw-file/tip/%s", safe_path); Seobeo_Client_Response *hg_response = hg_proxy_request("GET", hg_path, NULL); Seobeo_Log(SEOBEO_DEBUG, "ApiGetFile: status=%i body_len=%zu\n", hg_response->status_code, hg_response->body_length); char status[4]; snprintf(status, 3, "%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, "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); Dowa_HashMap_Push_Arena(resp, "body", hg_response->body, arena); return resp; } 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); Seobeo_Client_Response_Destroy(hg_response); return resp; } Seobeo_Request_Entry* ApiGetReadme(Seobeo_Request_Entry *req, Dowa_Arena *arena) { return ApiGetFile(req, arena); } Seobeo_Request_Entry* ApiHgWireProtocol(Seobeo_Request_Entry *req, Dowa_Arena *arena) { Seobeo_Request_Entry *resp = NULL; 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 : ""; 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); Seobeo_Client_Response *hg_response; if (strlen(query_string) > 0) { char temp_path[MAX_PATH]; snprintf(temp_path, MAX_PATH, "?%s", query_string); hg_response = hg_proxy_request(method, query_string, req_body); } else hg_response = hg_proxy_request(method, "", req_body); 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"); char status[4]; snprintf(status, 3, "%i", hg_response->status_code); Dowa_HashMap_Push_Arena(resp, "status", status, arena); Dowa_HashMap_Push_Arena(resp, "content-type", kv ? kv->value : "", arena); Dowa_HashMap_Push_Arena(resp, "body", hg_response->body, arena); return resp; } int main(void) { Seobeo_Router_Init(); Seobeo_Router_Register("GET", "/api/repo/list", ApiListDirectory); Seobeo_Router_Register("GET", "/api/repo/file", ApiGetFile); Seobeo_Router_Register("GET", "/api/repo/readme", ApiGetReadme); Seobeo_Router_Register("GET", "/repo", ApiHgWireProtocol); Seobeo_Router_Register("POST", "/repo", ApiHgWireProtocol); printf("Starting on Port 6970...\n"); int result = Seobeo_Web_Server_Start("hg-web/src", "6970", SEOBEO_MODE_EDGE, 4); Seobeo_Router_Destroy(); return result; }