Mercurial
diff mrjunejune/main.c @ 92:655ea0b661fd
[Seobeo] Added few endpoints for handling files. [Dowa] Added few functions for random number and generating uuids
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Fri, 02 Jan 2026 17:47:10 -0800 |
| parents | b5e65b01f0a3 |
| children | 70401cf61e97 |
line wrap: on
line diff
--- a/mrjunejune/main.c Thu Jan 01 16:34:51 2026 -0800 +++ b/mrjunejune/main.c Fri Jan 02 17:47:10 2026 -0800 @@ -1,6 +1,12 @@ #include "seobeo/seobeo.h" +#include <time.h> + +// UUID + /tmp/ + format (max 4) +#define TMP_FILE_LENGTH 47 +#define UUID_LEN 37 volatile sig_atomic_t stop_server = 0; +static _Atomic uint32_t counter = 0; void handle_sigint(int sig) { @@ -84,23 +90,348 @@ Seobeo_Request_Entry* GetMDToHTML(Seobeo_Request_Entry *req, Dowa_Arena *arena) { - Seobeo_Request_Entry *resp = NULL; + Seobeo_Request_Entry *resp = NULL; char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024); Seobeo_ServerSideRender(final_body, "/tools/markdown_to_html/index.html", arena); Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); return resp; } -Seobeo_Request_Entry* GetUser(Seobeo_Request_Entry *req, Dowa_Arena *arena) +Seobeo_Request_Entry* GetFileConverter(Seobeo_Request_Entry *req, Dowa_Arena *arena) +{ + Seobeo_Request_Entry *resp = NULL; + char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024); + Seobeo_ServerSideRender(final_body, "/tools/file_converter/index.html", arena); + Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); + return resp; +} + +Seobeo_Request_Entry *ConvertImageToWebP(Seobeo_Request_Entry *req, Dowa_Arena *arena) { - void *id_kv = Dowa_HashMap_Get_Ptr(req, ":id"); - const char *user_id = id_kv ? ((Seobeo_Request_Entry*)id_kv)->value : "unknown"; + Seobeo_Request_Entry *resp = NULL; + + if (!req) + { + printf("ERROR: Request is NULL\n"); + char *error_msg = "Internal error: no request data"; + Dowa_HashMap_Push_Arena(resp, "status", "500", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + + size_t req_length = Dowa_Array_Length(req); + printf("Request has %zu entries\n", req_length); + + for (size_t i = 0; i < req_length; i++) + { + printf(" Key[%zu]: '%s'\n", i, req[i].key); + } + + void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body"); + if (!body_kv) + { + printf("ERROR: No 'Body' key found in request\n"); + + char *error_msg = "No file data provided"; + Dowa_HashMap_Push_Arena(resp, "status", "400", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + + void *cl_kv = Dowa_HashMap_Get_Ptr(req, "Content-Length"); + if (!cl_kv) + { + char *error_msg = "No Content-Length header"; + Dowa_HashMap_Push_Arena(resp, "status", "400", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + + const char *file_data = ((Seobeo_Request_Entry*)body_kv)->value; + const char *content_length_str = ((Seobeo_Request_Entry*)cl_kv)->value; + size_t file_size = atoi(content_length_str); + + printf("DEBUG: Converting image, file_size=%zu bytes\n", file_size); + + + int open_flags = O_RDWR | O_CREAT | O_EXCL; + + char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); + uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; + Dowa_String_UUID(seed, uuid4); + char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; + snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4); + int input_fd = open(input_path, open_flags, 0600); + if (input_fd == -1) + { + char *error_msg = "Failed to create temporary file"; + Dowa_HashMap_Push_Arena(resp, "status", "500", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + write(input_fd, file_data, file_size); + close(input_fd); + + + uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); + seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; + Dowa_String_UUID(seed, uuid4); + char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; + snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.webp", uuid4); + printf("[DEBUG] output_path %s\n", output_path); + printf("[DEBUG] open_flags: 0x%x\n", open_flags); + printf("[DEBUG] input_path: %s\n", input_path); + int output_fd = open(output_path, open_flags, 0600); + printf("[DEBUG] output_fd: %d\n", output_fd); + if (output_fd == -1) + { + unlink(input_path); + printf("[DEBUG] errno: %d (%s)\n", errno, strerror(errno)); + char *error_msg = "Failed to create output file"; + Dowa_HashMap_Push_Arena(resp, "status", "500", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + close(output_fd); + + char cmd[1024]; + snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>/tmp/error_log", + input_path, output_path); + int result = system(cmd); + if (result != 0) + { + unlink(input_path); + unlink(output_path); + char *error_msg = "FFmpeg conversion failed"; + Dowa_HashMap_Push_Arena(resp, "status", "500", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + + size_t converted_size = 0; + FILE *out_file = fopen(output_path, "rb"); + if (!out_file) + { + unlink(input_path); + unlink(output_path); + char *error_msg = "Failed to read converted file"; + Dowa_HashMap_Push_Arena(resp, "status", "500", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + fclose(out_file); + + unlink(input_path); + + char *filename = strrchr(output_path, '/') + 1; + char *response_body = Dowa_Arena_Allocate(arena, 512); + snprintf(response_body, 512, + "{\"success\":true,\"download_url\":\"/api/download/%s\",\"expires\":\"10 minutes\"}", + filename); + Dowa_HashMap_Push_Arena(resp, "status", "200", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); + Dowa_HashMap_Push_Arena(resp, "body", response_body, arena); + + printf("DEBUG: Image converted, available at /api/download/%s\n", filename); + + return resp; +} + +Seobeo_Request_Entry *ConvertVideoToMP4(Seobeo_Request_Entry *req, Dowa_Arena *arena) +{ + Seobeo_Request_Entry *resp = NULL; + + void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body"); + if (!body_kv) + { + char *error_msg = "No file data provided"; + Dowa_HashMap_Push_Arena(resp, "status", "400", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + // Get Content-Length to know the actual binary size + void *cl_kv = Dowa_HashMap_Get_Ptr(req, "Content-Length"); + if (!cl_kv) + { + char *error_msg = "No Content-Length header"; + Dowa_HashMap_Push_Arena(resp, "status", "400", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + + const char *file_data = ((Seobeo_Request_Entry*)body_kv)->value; + const char *content_length_str = ((Seobeo_Request_Entry*)cl_kv)->value; + size_t file_size = atoi(content_length_str); + + printf("DEBUG: Converting video, file_size=%zu bytes\n", file_size); + + char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); + Dowa_String_UUID((uint32)time(NULL), uuid4); + char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH); + snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4); + int input_fd = mkstemp(input_path); + if (input_fd == -1) + { + char *error_msg = "Failed to create temporary file"; + Dowa_HashMap_Push_Arena(resp, "status", "500", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + + write(input_fd, file_data, file_size); + close(input_fd); + + int open_flags = O_RDWR | O_CREAT | O_EXCL; + + uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;; + Dowa_String_UUID(seed, uuid4); + char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; + snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.mp4", uuid4); + int output_fd = open(output_path, open_flags, 0600); + if (output_fd == -1) + { + unlink(input_path); + char *error_msg = "Failed to create output file"; + Dowa_HashMap_Push_Arena(resp, "status", "500", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + close(output_fd); + + char cmd[512]; + snprintf(cmd, sizeof(cmd), + "ffmpeg -y -i %s -c:v libx264 -preset fast -crf 23 -c:a aac %s 2>/tmp/error_log", + input_path, output_path); + int result = system(cmd); + if (result != 0) + { + unlink(input_path); + unlink(output_path); + char *error_msg = "FFmpeg conversion failed"; + Dowa_HashMap_Push_Arena(resp, "status", "500", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + + unlink(input_path); + char *filename = strrchr(output_path, '/') + 1; + char *response_body = Dowa_Arena_Allocate(arena, 512); + snprintf(response_body, 512, + "{\"success\":true,\"download_url\":\"/api/download/%s\",\"expires\":\"10 minutes\"}", + filename); + Dowa_HashMap_Push_Arena(resp, "status", "200", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); + Dowa_HashMap_Push_Arena(resp, "body", response_body, arena); + + printf("DEBUG: Video converted, available at /api/download/%s\n", filename); + return resp; +} + +Seobeo_Request_Entry *DownloadConvertedFile(Seobeo_Request_Entry *req, Dowa_Arena *arena) +{ Seobeo_Request_Entry *resp = NULL; - char *body = Dowa_Arena_Allocate(arena, 256); - snprintf(body, 256, "{\"id\":\"%s\",\"name\":\"John Doe\"}", user_id); + + void *filename_kv = Dowa_HashMap_Get_Ptr(req, ":filename"); + if (!filename_kv) + { + char *error_msg = "No filename specified"; + Dowa_HashMap_Push_Arena(resp, "status", "404", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + + const char *filename = ((Seobeo_Request_Entry*)filename_kv)->value; + + // TODO: Maybe check if the uuid is allowed or not? + // if (strlen(filename) != TMP_FILE_LENGTH) + // { + // char *error_msg = "Not Allowed Filename"; + // Dowa_HashMap_Push_Arena(resp, "status", "404", arena); + // Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + // Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + // return resp; + // } + // boolean allowed = FALSE; + // for (int i = 0; i < Dowa_Array_Length(g_uuid4_array); i++) + // { + // if strcmp(g_uuid4_array, filename) + // { + // allowed = TRUE; + // break; + // } + // g_uuid4_array++; + // } + + char filepath[512]; + snprintf(filepath, sizeof(filepath), "/tmp/%s", filename); + FILE *file = fopen(filepath, "rb"); + if (!file) + { + char *error_msg = "File not found or expired"; + Dowa_HashMap_Push_Arena(resp, "status", "404", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + + fseek(file, 0, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + char *file_data = malloc(file_size + 1); + if (!file_data) + { + fclose(file); + char *error_msg = "Memory allocation failed"; + Dowa_HashMap_Push_Arena(resp, "status", "500", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); + Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); + return resp; + } + + fread(file_data, 1, file_size, file); + file_data[file_size] = '\0'; + fclose(file); + + const char *content_type = "application/octet-stream"; + if (strstr(filename, ".webp")) + content_type = "image/webp"; + else if (strstr(filename, ".mp4")) + content_type = "video/mp4"; + + char *body = Dowa_Arena_Allocate(arena, file_size + 1); + memcpy(body, file_data, file_size); + body[file_size] = '\0'; + free(file_data); + + unlink(filepath); + + printf("DEBUG: Served and deleted file: %s (%zu bytes)\n", filename, file_size); + + // Set proper Content-Length for binary data + char *content_length = Dowa_Arena_Allocate(arena, 32); + snprintf(content_length, 32, "%zu", file_size); + + Dowa_HashMap_Push_Arena(resp, "status", "200", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", content_type, arena); + Dowa_HashMap_Push_Arena(resp, "content-length", content_length, arena); Dowa_HashMap_Push_Arena(resp, "body", body, arena); + return resp; } @@ -108,6 +439,7 @@ CREATE_REDIRECT_HANDLER(Resume, "/resume") CREATE_REDIRECT_HANDLER(Tools, "/tools") CREATE_REDIRECT_HANDLER(MarkDownToHtml, "/tools/markdown_to_html") +CREATE_REDIRECT_HANDLER(FileConverter, "/tools/file_converter") int main(void) { @@ -124,5 +456,12 @@ Seobeo_Router_Register("GET", "/tools/markdown_to_html", GetMDToHTML); Seobeo_Router_Register("GET", "/tools/markdown_to_html/index.html", GetRedirectMarkDownToHtml); + Seobeo_Router_Register("GET", "/tools/file_converter", GetFileConverter); + Seobeo_Router_Register("GET", "/tools/file_converter/index.html", GetRedirectFileConverter); + + Seobeo_Router_Register("POST", "/api/convert/image-to-webp", ConvertImageToWebP); + Seobeo_Router_Register("POST", "/api/convert/video-to-mp4", ConvertVideoToMP4); + Seobeo_Router_Register("GET", "/api/download/:filename", DownloadConvertedFile); + Seobeo_Web_Server_Start("mrjunejune/src", "6969", SEOBEO_MODE_EDGE, 3); }