Mercurial
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 91:19cccf6e866a | 92:655ea0b661fd |
|---|---|
| 1 #include "seobeo/seobeo.h" | 1 #include "seobeo/seobeo.h" |
| 2 #include <time.h> | |
| 3 | |
| 4 // UUID + /tmp/ + format (max 4) | |
| 5 #define TMP_FILE_LENGTH 47 | |
| 6 #define UUID_LEN 37 | |
| 2 | 7 |
| 3 volatile sig_atomic_t stop_server = 0; | 8 volatile sig_atomic_t stop_server = 0; |
| 9 static _Atomic uint32_t counter = 0; | |
| 4 | 10 |
| 5 void handle_sigint(int sig) | 11 void handle_sigint(int sig) |
| 6 { | 12 { |
| 7 printf("Failed\n"); | 13 printf("Failed\n"); |
| 8 stop_server = 1; | 14 stop_server = 1; |
| 82 } | 88 } |
| 83 | 89 |
| 84 | 90 |
| 85 Seobeo_Request_Entry* GetMDToHTML(Seobeo_Request_Entry *req, Dowa_Arena *arena) | 91 Seobeo_Request_Entry* GetMDToHTML(Seobeo_Request_Entry *req, Dowa_Arena *arena) |
| 86 { | 92 { |
| 87 Seobeo_Request_Entry *resp = NULL; | 93 Seobeo_Request_Entry *resp = NULL; |
| 88 char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024); | 94 char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024); |
| 89 Seobeo_ServerSideRender(final_body, "/tools/markdown_to_html/index.html", arena); | 95 Seobeo_ServerSideRender(final_body, "/tools/markdown_to_html/index.html", arena); |
| 90 Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); | 96 Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); |
| 91 return resp; | 97 return resp; |
| 92 } | 98 } |
| 93 | 99 |
| 94 Seobeo_Request_Entry* GetUser(Seobeo_Request_Entry *req, Dowa_Arena *arena) | 100 Seobeo_Request_Entry* GetFileConverter(Seobeo_Request_Entry *req, Dowa_Arena *arena) |
| 95 { | 101 { |
| 96 void *id_kv = Dowa_HashMap_Get_Ptr(req, ":id"); | |
| 97 const char *user_id = id_kv ? ((Seobeo_Request_Entry*)id_kv)->value : "unknown"; | |
| 98 | |
| 99 Seobeo_Request_Entry *resp = NULL; | 102 Seobeo_Request_Entry *resp = NULL; |
| 100 char *body = Dowa_Arena_Allocate(arena, 256); | 103 char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024); |
| 101 snprintf(body, 256, "{\"id\":\"%s\",\"name\":\"John Doe\"}", user_id); | 104 Seobeo_ServerSideRender(final_body, "/tools/file_converter/index.html", arena); |
| 102 | 105 Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); |
| 106 return resp; | |
| 107 } | |
| 108 | |
| 109 Seobeo_Request_Entry *ConvertImageToWebP(Seobeo_Request_Entry *req, Dowa_Arena *arena) | |
| 110 { | |
| 111 Seobeo_Request_Entry *resp = NULL; | |
| 112 | |
| 113 if (!req) | |
| 114 { | |
| 115 printf("ERROR: Request is NULL\n"); | |
| 116 char *error_msg = "Internal error: no request data"; | |
| 117 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); | |
| 118 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 119 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 120 return resp; | |
| 121 } | |
| 122 | |
| 123 size_t req_length = Dowa_Array_Length(req); | |
| 124 printf("Request has %zu entries\n", req_length); | |
| 125 | |
| 126 for (size_t i = 0; i < req_length; i++) | |
| 127 { | |
| 128 printf(" Key[%zu]: '%s'\n", i, req[i].key); | |
| 129 } | |
| 130 | |
| 131 void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body"); | |
| 132 if (!body_kv) | |
| 133 { | |
| 134 printf("ERROR: No 'Body' key found in request\n"); | |
| 135 | |
| 136 char *error_msg = "No file data provided"; | |
| 137 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 138 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 139 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 140 return resp; | |
| 141 } | |
| 142 | |
| 143 void *cl_kv = Dowa_HashMap_Get_Ptr(req, "Content-Length"); | |
| 144 if (!cl_kv) | |
| 145 { | |
| 146 char *error_msg = "No Content-Length header"; | |
| 147 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 148 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 149 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 150 return resp; | |
| 151 } | |
| 152 | |
| 153 const char *file_data = ((Seobeo_Request_Entry*)body_kv)->value; | |
| 154 const char *content_length_str = ((Seobeo_Request_Entry*)cl_kv)->value; | |
| 155 size_t file_size = atoi(content_length_str); | |
| 156 | |
| 157 printf("DEBUG: Converting image, file_size=%zu bytes\n", file_size); | |
| 158 | |
| 159 | |
| 160 int open_flags = O_RDWR | O_CREAT | O_EXCL; | |
| 161 | |
| 162 char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); | |
| 163 uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; | |
| 164 Dowa_String_UUID(seed, uuid4); | |
| 165 char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; | |
| 166 snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4); | |
| 167 int input_fd = open(input_path, open_flags, 0600); | |
| 168 if (input_fd == -1) | |
| 169 { | |
| 170 char *error_msg = "Failed to create temporary file"; | |
| 171 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); | |
| 172 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 173 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 174 return resp; | |
| 175 } | |
| 176 write(input_fd, file_data, file_size); | |
| 177 close(input_fd); | |
| 178 | |
| 179 | |
| 180 uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); | |
| 181 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; | |
| 182 Dowa_String_UUID(seed, uuid4); | |
| 183 char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; | |
| 184 snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.webp", uuid4); | |
| 185 printf("[DEBUG] output_path %s\n", output_path); | |
| 186 printf("[DEBUG] open_flags: 0x%x\n", open_flags); | |
| 187 printf("[DEBUG] input_path: %s\n", input_path); | |
| 188 int output_fd = open(output_path, open_flags, 0600); | |
| 189 printf("[DEBUG] output_fd: %d\n", output_fd); | |
| 190 if (output_fd == -1) | |
| 191 { | |
| 192 unlink(input_path); | |
| 193 printf("[DEBUG] errno: %d (%s)\n", errno, strerror(errno)); | |
| 194 char *error_msg = "Failed to create output file"; | |
| 195 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); | |
| 196 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 197 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 198 return resp; | |
| 199 } | |
| 200 close(output_fd); | |
| 201 | |
| 202 char cmd[1024]; | |
| 203 snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>/tmp/error_log", | |
| 204 input_path, output_path); | |
| 205 int result = system(cmd); | |
| 206 if (result != 0) | |
| 207 { | |
| 208 unlink(input_path); | |
| 209 unlink(output_path); | |
| 210 char *error_msg = "FFmpeg conversion failed"; | |
| 211 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); | |
| 212 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 213 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 214 return resp; | |
| 215 } | |
| 216 | |
| 217 size_t converted_size = 0; | |
| 218 FILE *out_file = fopen(output_path, "rb"); | |
| 219 if (!out_file) | |
| 220 { | |
| 221 unlink(input_path); | |
| 222 unlink(output_path); | |
| 223 char *error_msg = "Failed to read converted file"; | |
| 224 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); | |
| 225 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 226 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 227 return resp; | |
| 228 } | |
| 229 fclose(out_file); | |
| 230 | |
| 231 unlink(input_path); | |
| 232 | |
| 233 char *filename = strrchr(output_path, '/') + 1; | |
| 234 char *response_body = Dowa_Arena_Allocate(arena, 512); | |
| 235 snprintf(response_body, 512, | |
| 236 "{\"success\":true,\"download_url\":\"/api/download/%s\",\"expires\":\"10 minutes\"}", | |
| 237 filename); | |
| 238 Dowa_HashMap_Push_Arena(resp, "status", "200", arena); | |
| 239 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 240 Dowa_HashMap_Push_Arena(resp, "body", response_body, arena); | |
| 241 | |
| 242 printf("DEBUG: Image converted, available at /api/download/%s\n", filename); | |
| 243 | |
| 244 return resp; | |
| 245 } | |
| 246 | |
| 247 Seobeo_Request_Entry *ConvertVideoToMP4(Seobeo_Request_Entry *req, Dowa_Arena *arena) | |
| 248 { | |
| 249 Seobeo_Request_Entry *resp = NULL; | |
| 250 | |
| 251 void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body"); | |
| 252 if (!body_kv) | |
| 253 { | |
| 254 char *error_msg = "No file data provided"; | |
| 255 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 256 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 257 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 258 return resp; | |
| 259 } | |
| 260 | |
| 261 // Get Content-Length to know the actual binary size | |
| 262 void *cl_kv = Dowa_HashMap_Get_Ptr(req, "Content-Length"); | |
| 263 if (!cl_kv) | |
| 264 { | |
| 265 char *error_msg = "No Content-Length header"; | |
| 266 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 267 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 268 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 269 return resp; | |
| 270 } | |
| 271 | |
| 272 const char *file_data = ((Seobeo_Request_Entry*)body_kv)->value; | |
| 273 const char *content_length_str = ((Seobeo_Request_Entry*)cl_kv)->value; | |
| 274 size_t file_size = atoi(content_length_str); | |
| 275 | |
| 276 printf("DEBUG: Converting video, file_size=%zu bytes\n", file_size); | |
| 277 | |
| 278 char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); | |
| 279 Dowa_String_UUID((uint32)time(NULL), uuid4); | |
| 280 char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH); | |
| 281 snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4); | |
| 282 int input_fd = mkstemp(input_path); | |
| 283 if (input_fd == -1) | |
| 284 { | |
| 285 char *error_msg = "Failed to create temporary file"; | |
| 286 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); | |
| 287 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 288 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 289 return resp; | |
| 290 } | |
| 291 | |
| 292 write(input_fd, file_data, file_size); | |
| 293 close(input_fd); | |
| 294 | |
| 295 int open_flags = O_RDWR | O_CREAT | O_EXCL; | |
| 296 | |
| 297 uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;; | |
| 298 Dowa_String_UUID(seed, uuid4); | |
| 299 char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; | |
| 300 snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.mp4", uuid4); | |
| 301 int output_fd = open(output_path, open_flags, 0600); | |
| 302 if (output_fd == -1) | |
| 303 { | |
| 304 unlink(input_path); | |
| 305 char *error_msg = "Failed to create output file"; | |
| 306 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); | |
| 307 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 308 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 309 return resp; | |
| 310 } | |
| 311 close(output_fd); | |
| 312 | |
| 313 char cmd[512]; | |
| 314 snprintf(cmd, sizeof(cmd), | |
| 315 "ffmpeg -y -i %s -c:v libx264 -preset fast -crf 23 -c:a aac %s 2>/tmp/error_log", | |
| 316 input_path, output_path); | |
| 317 int result = system(cmd); | |
| 318 if (result != 0) | |
| 319 { | |
| 320 unlink(input_path); | |
| 321 unlink(output_path); | |
| 322 char *error_msg = "FFmpeg conversion failed"; | |
| 323 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); | |
| 324 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 325 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 326 return resp; | |
| 327 } | |
| 328 | |
| 329 unlink(input_path); | |
| 330 char *filename = strrchr(output_path, '/') + 1; | |
| 331 char *response_body = Dowa_Arena_Allocate(arena, 512); | |
| 332 snprintf(response_body, 512, | |
| 333 "{\"success\":true,\"download_url\":\"/api/download/%s\",\"expires\":\"10 minutes\"}", | |
| 334 filename); | |
| 335 Dowa_HashMap_Push_Arena(resp, "status", "200", arena); | |
| 336 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 337 Dowa_HashMap_Push_Arena(resp, "body", response_body, arena); | |
| 338 | |
| 339 printf("DEBUG: Video converted, available at /api/download/%s\n", filename); | |
| 340 return resp; | |
| 341 } | |
| 342 | |
| 343 Seobeo_Request_Entry *DownloadConvertedFile(Seobeo_Request_Entry *req, Dowa_Arena *arena) | |
| 344 { | |
| 345 Seobeo_Request_Entry *resp = NULL; | |
| 346 | |
| 347 void *filename_kv = Dowa_HashMap_Get_Ptr(req, ":filename"); | |
| 348 if (!filename_kv) | |
| 349 { | |
| 350 char *error_msg = "No filename specified"; | |
| 351 Dowa_HashMap_Push_Arena(resp, "status", "404", arena); | |
| 352 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 353 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 354 return resp; | |
| 355 } | |
| 356 | |
| 357 const char *filename = ((Seobeo_Request_Entry*)filename_kv)->value; | |
| 358 | |
| 359 // TODO: Maybe check if the uuid is allowed or not? | |
| 360 // if (strlen(filename) != TMP_FILE_LENGTH) | |
| 361 // { | |
| 362 // char *error_msg = "Not Allowed Filename"; | |
| 363 // Dowa_HashMap_Push_Arena(resp, "status", "404", arena); | |
| 364 // Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 365 // Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 366 // return resp; | |
| 367 // } | |
| 368 // boolean allowed = FALSE; | |
| 369 // for (int i = 0; i < Dowa_Array_Length(g_uuid4_array); i++) | |
| 370 // { | |
| 371 // if strcmp(g_uuid4_array, filename) | |
| 372 // { | |
| 373 // allowed = TRUE; | |
| 374 // break; | |
| 375 // } | |
| 376 // g_uuid4_array++; | |
| 377 // } | |
| 378 | |
| 379 char filepath[512]; | |
| 380 snprintf(filepath, sizeof(filepath), "/tmp/%s", filename); | |
| 381 | |
| 382 FILE *file = fopen(filepath, "rb"); | |
| 383 if (!file) | |
| 384 { | |
| 385 char *error_msg = "File not found or expired"; | |
| 386 Dowa_HashMap_Push_Arena(resp, "status", "404", arena); | |
| 387 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 388 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 389 return resp; | |
| 390 } | |
| 391 | |
| 392 fseek(file, 0, SEEK_END); | |
| 393 size_t file_size = ftell(file); | |
| 394 fseek(file, 0, SEEK_SET); | |
| 395 | |
| 396 char *file_data = malloc(file_size + 1); | |
| 397 if (!file_data) | |
| 398 { | |
| 399 fclose(file); | |
| 400 char *error_msg = "Memory allocation failed"; | |
| 401 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); | |
| 402 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | |
| 403 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | |
| 404 return resp; | |
| 405 } | |
| 406 | |
| 407 fread(file_data, 1, file_size, file); | |
| 408 file_data[file_size] = '\0'; | |
| 409 fclose(file); | |
| 410 | |
| 411 const char *content_type = "application/octet-stream"; | |
| 412 if (strstr(filename, ".webp")) | |
| 413 content_type = "image/webp"; | |
| 414 else if (strstr(filename, ".mp4")) | |
| 415 content_type = "video/mp4"; | |
| 416 | |
| 417 char *body = Dowa_Arena_Allocate(arena, file_size + 1); | |
| 418 memcpy(body, file_data, file_size); | |
| 419 body[file_size] = '\0'; | |
| 420 free(file_data); | |
| 421 | |
| 422 unlink(filepath); | |
| 423 | |
| 424 printf("DEBUG: Served and deleted file: %s (%zu bytes)\n", filename, file_size); | |
| 425 | |
| 426 // Set proper Content-Length for binary data | |
| 427 char *content_length = Dowa_Arena_Allocate(arena, 32); | |
| 428 snprintf(content_length, 32, "%zu", file_size); | |
| 429 | |
| 430 Dowa_HashMap_Push_Arena(resp, "status", "200", arena); | |
| 431 Dowa_HashMap_Push_Arena(resp, "content-type", content_type, arena); | |
| 432 Dowa_HashMap_Push_Arena(resp, "content-length", content_length, arena); | |
| 103 Dowa_HashMap_Push_Arena(resp, "body", body, arena); | 433 Dowa_HashMap_Push_Arena(resp, "body", body, arena); |
| 434 | |
| 104 return resp; | 435 return resp; |
| 105 } | 436 } |
| 106 | 437 |
| 107 CREATE_REDIRECT_HANDLER(HomePage, "/") | 438 CREATE_REDIRECT_HANDLER(HomePage, "/") |
| 108 CREATE_REDIRECT_HANDLER(Resume, "/resume") | 439 CREATE_REDIRECT_HANDLER(Resume, "/resume") |
| 109 CREATE_REDIRECT_HANDLER(Tools, "/tools") | 440 CREATE_REDIRECT_HANDLER(Tools, "/tools") |
| 110 CREATE_REDIRECT_HANDLER(MarkDownToHtml, "/tools/markdown_to_html") | 441 CREATE_REDIRECT_HANDLER(MarkDownToHtml, "/tools/markdown_to_html") |
| 442 CREATE_REDIRECT_HANDLER(FileConverter, "/tools/file_converter") | |
| 111 | 443 |
| 112 int main(void) | 444 int main(void) |
| 113 { | 445 { |
| 114 Seobeo_Router_Init(); | 446 Seobeo_Router_Init(); |
| 115 Seobeo_Router_Register("GET", "/", GetHomePage); | 447 Seobeo_Router_Register("GET", "/", GetHomePage); |
| 122 Seobeo_Router_Register("GET", "/tools/index.html", GetRedirectTools); | 454 Seobeo_Router_Register("GET", "/tools/index.html", GetRedirectTools); |
| 123 | 455 |
| 124 Seobeo_Router_Register("GET", "/tools/markdown_to_html", GetMDToHTML); | 456 Seobeo_Router_Register("GET", "/tools/markdown_to_html", GetMDToHTML); |
| 125 Seobeo_Router_Register("GET", "/tools/markdown_to_html/index.html", GetRedirectMarkDownToHtml); | 457 Seobeo_Router_Register("GET", "/tools/markdown_to_html/index.html", GetRedirectMarkDownToHtml); |
| 126 | 458 |
| 459 Seobeo_Router_Register("GET", "/tools/file_converter", GetFileConverter); | |
| 460 Seobeo_Router_Register("GET", "/tools/file_converter/index.html", GetRedirectFileConverter); | |
| 461 | |
| 462 Seobeo_Router_Register("POST", "/api/convert/image-to-webp", ConvertImageToWebP); | |
| 463 Seobeo_Router_Register("POST", "/api/convert/video-to-mp4", ConvertVideoToMP4); | |
| 464 Seobeo_Router_Register("GET", "/api/download/:filename", DownloadConvertedFile); | |
| 465 | |
| 127 Seobeo_Web_Server_Start("mrjunejune/src", "6969", SEOBEO_MODE_EDGE, 3); | 466 Seobeo_Web_Server_Start("mrjunejune/src", "6969", SEOBEO_MODE_EDGE, 3); |
| 128 } | 467 } |