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 }