Mercurial
diff hg-web/main.c @ 224:3007ef5fc0ed hg-web
[hg-web] Simplify visuals and preview static files
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sun, 02 Aug 2026 13:51:11 -0700 |
| parents | 0e7b9464248d |
| children | c5129452493e |
line wrap: on
line diff
--- a/hg-web/main.c Sun Aug 02 10:07:40 2026 -0700 +++ b/hg-web/main.c Sun Aug 02 13:51:11 2026 -0700 @@ -198,6 +198,77 @@ strchr(value, '\n') == NULL; } +static boolean extension_is(const char *extension, const char *expected) +{ + return extension && strcasecmp(extension, expected) == 0; +} + +static const char *repository_file_content_type( + const char *path, + boolean *inline_preview, + boolean *sandbox_content) +{ + const char *extension = strrchr(path, '.'); + *inline_preview = TRUE; + *sandbox_content = FALSE; + + if (extension_is(extension, ".png")) return "image/png"; + if (extension_is(extension, ".jpg") || + extension_is(extension, ".jpeg")) return "image/jpeg"; + if (extension_is(extension, ".gif")) return "image/gif"; + if (extension_is(extension, ".webp")) return "image/webp"; + if (extension_is(extension, ".avif")) return "image/avif"; + if (extension_is(extension, ".bmp")) return "image/bmp"; + if (extension_is(extension, ".ico")) return "image/x-icon"; + if (extension_is(extension, ".svg")) + { + *sandbox_content = TRUE; + return "image/svg+xml"; + } + if (extension_is(extension, ".mp4") || + extension_is(extension, ".m4v")) return "video/mp4"; + if (extension_is(extension, ".webm")) return "video/webm"; + if (extension_is(extension, ".mov")) return "video/quicktime"; + if (extension_is(extension, ".ogv")) return "video/ogg"; + if (extension_is(extension, ".mp3")) return "audio/mpeg"; + if (extension_is(extension, ".wav")) return "audio/wav"; + if (extension_is(extension, ".ogg") || + extension_is(extension, ".oga")) return "audio/ogg"; + if (extension_is(extension, ".flac")) return "audio/flac"; + if (extension_is(extension, ".m4a")) return "audio/mp4"; + if (extension_is(extension, ".aac")) return "audio/aac"; + if (extension_is(extension, ".pdf")) return "application/pdf"; + if (extension_is(extension, ".wasm")) return "application/wasm"; + + *inline_preview = FALSE; + if (extension_is(extension, ".md") || + extension_is(extension, ".markdown")) return "text/markdown; charset=utf-8"; + if (extension_is(extension, ".txt") || + extension_is(extension, ".log") || + extension_is(extension, ".c") || + extension_is(extension, ".h") || + extension_is(extension, ".cc") || + extension_is(extension, ".cpp") || + extension_is(extension, ".js") || + extension_is(extension, ".jsx") || + extension_is(extension, ".ts") || + extension_is(extension, ".tsx") || + extension_is(extension, ".css") || + extension_is(extension, ".html") || + extension_is(extension, ".htm") || + extension_is(extension, ".xml") || + extension_is(extension, ".json") || + extension_is(extension, ".yaml") || + extension_is(extension, ".yml") || + extension_is(extension, ".toml") || + extension_is(extension, ".sh") || + extension_is(extension, ".py") || + extension_is(extension, ".rs") || + extension_is(extension, ".go")) + return "text/plain; charset=utf-8"; + return "application/octet-stream"; +} + static Seobeo_Client_Response *hg_proxy_request( const char *method, const char *path, @@ -243,6 +314,7 @@ static Seobeo_Request_Entry *forward_hg_response( Seobeo_Client_Response *hg_response, const char *default_content_type, + const char *override_content_type, Dowa_Arena *arena) { if (!hg_response) @@ -252,7 +324,9 @@ const char *upstream_content_type = map_value_case_insensitive(hg_response->headers, "Content-Type"); const char *upstream_or_default_content_type = - upstream_content_type ? upstream_content_type : default_content_type; + override_content_type + ? override_content_type + : upstream_content_type ? upstream_content_type : default_content_type; if (!upstream_or_default_content_type) upstream_or_default_content_type = "application/octet-stream"; char *content_type = arena_string(arena, upstream_or_default_content_type); @@ -297,6 +371,7 @@ return forward_hg_response( hg_proxy_request("GET", hg_path, NULL, 0, NULL, "application/json"), "application/json", + NULL, arena); } @@ -314,10 +389,28 @@ if (length < 0 || (size_t)length >= sizeof(hg_path)) return text_response(arena, "400", "text/plain", "File path is too long"); - return forward_hg_response( - hg_proxy_request("GET", hg_path, NULL, 0, NULL, "application/octet-stream"), - "application/octet-stream", + Seobeo_Client_Response *hg_response = + hg_proxy_request("GET", hg_path, NULL, 0, NULL, "application/octet-stream"); + if (!hg_response) + return forward_hg_response(NULL, "application/json", NULL, arena); + + boolean inline_preview = FALSE; + boolean sandbox_content = FALSE; + const char *content_type = + repository_file_content_type(path, &inline_preview, &sandbox_content); + Seobeo_Request_Entry *response = forward_hg_response( + hg_response, "application/octet-stream", content_type, arena); + Dowa_HashMap_Push_Arena( + response, + "Content-Disposition", + inline_preview ? "inline" : "attachment", arena); + Dowa_HashMap_Push_Arena( + response, "X-Content-Type-Options", "nosniff", arena); + if (sandbox_content) + Dowa_HashMap_Push_Arena( + response, "Content-Security-Policy", "sandbox", arena); + return response; } Seobeo_Request_Entry *ApiGetReadme(Seobeo_Request_Entry *request, Dowa_Arena *arena) @@ -351,7 +444,8 @@ Seobeo_Client_Response_Destroy(hg_response); return text_response(arena, "204", "text/markdown", ""); } - return forward_hg_response(hg_response, "text/markdown", arena); + return forward_hg_response( + hg_response, "text/markdown", "text/markdown; charset=utf-8", arena); } Seobeo_Request_Entry *ApiGetGraph(Seobeo_Request_Entry *request, Dowa_Arena *arena) @@ -385,6 +479,7 @@ return forward_hg_response( hg_proxy_request("GET", hg_path, NULL, 0, NULL, "application/json"), "application/json", + NULL, arena); } @@ -402,6 +497,7 @@ return forward_hg_response( hg_proxy_request("GET", hg_path, NULL, 0, NULL, "application/json"), "application/json", + NULL, arena); }