From 33a1b58333a2196dfae52dbc0c0afa151ac2683f Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Thu, 1 Oct 2026 19:13:02 -1000 Subject: Send a charset only with a text type Archives, packs and the HEAD file went out as octet-stream or gzip with a UTF-8 charset on the end, since only the blob page cleared it. The headers now decide from the type itself, so the two places that cleared the charset by hand no longer need to. --- source/ui-plain.c | 10 ++-------- source/ui-shared.c | 13 ++++++++++++- source/ui-summary.c | 1 - tests/t0105-plain.sh | 4 ++-- tests/t0110-snapshot.sh | 6 +++--- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/source/ui-plain.c b/source/ui-plain.c index 7db6e07..6d02051 100644 --- a/source/ui-plain.c +++ b/source/ui-plain.c @@ -81,14 +81,8 @@ static void print_object(const struct object_id *oid, const char *path) ctx.page.mimetype = NULL; } - if (!ctx.page.mimetype) { - if (buffer_is_binary(buf, size)) { - ctx.page.mimetype = "application/octet-stream"; - ctx.page.charset = NULL; - } else { - ctx.page.mimetype = "text/plain"; - } - } + if (!ctx.page.mimetype) + ctx.page.mimetype = buffer_is_binary(buf, size) ? "application/octet-stream" : "text/plain"; ctx.page.filename = path; ctx.page.size = size; cgit_print_http_headers(); diff --git a/source/ui-shared.c b/source/ui-shared.c index 3970c58..a112fb2 100644 --- a/source/ui-shared.c +++ b/source/ui-shared.c @@ -1424,6 +1424,15 @@ void cgit_print_age(time_t t, int tz, time_t max_relative) print_rel_date(t, tz, secs * 1.0 / SECONDS_PER_YEAR, "age-years", "years"); } +/* + * Whether a content type carries text a charset applies to, which is every + * text type and the XML ones, the atom feed among them. + */ +static int type_has_charset(const char *mimetype) +{ + return starts_with(mimetype, "text/") || strstr(mimetype, "xml"); +} + void cgit_print_http_headers(void) { ctx.page.headers_sent = 1; @@ -1436,7 +1445,9 @@ void cgit_print_http_headers(void) htmlf("Status: %d %s\n", ctx.page.status, ctx.page.statusmsg); else html("Status: 200 OK\n"); - if (ctx.page.mimetype && ctx.page.charset) + // A charset only means something for text, so an archive or a pack + // is sent without one. + if (ctx.page.mimetype && ctx.page.charset && type_has_charset(ctx.page.mimetype)) htmlf("Content-Type: %s; charset=%s\n", ctx.page.mimetype, ctx.page.charset); else if (ctx.page.mimetype) htmlf("Content-Type: %s\n", ctx.page.mimetype); diff --git a/source/ui-summary.c b/source/ui-summary.c index eb04ecd..61cd6ac 100644 --- a/source/ui-summary.c +++ b/source/ui-summary.c @@ -138,7 +138,6 @@ void cgit_print_repo_readme(const char *path) mimetype = cgit_get_mimetype_for_filename(path); if (mimetype && (starts_with(mimetype, "image/") || starts_with(mimetype, "video/"))) { ctx.page.mimetype = mimetype; - ctx.page.charset = NULL; cgit_print_plain(); free(mimetype); return; diff --git a/tests/t0105-plain.sh b/tests/t0105-plain.sh index 2fbe87f..4041d98 100755 --- a/tests/t0105-plain.sh +++ b/tests/t0105-plain.sh @@ -14,7 +14,7 @@ test_expect_success 'generate foo/plain/file-1' ' test_expect_success 'a text file is served as text/plain' ' head -n 1 tmp | grep "Status: 200 OK" && - sed "/^$/q" tmp | grep "Content-Type: text/plain" + sed "/^$/q" tmp | grep "^Content-Type: text/plain; charset=UTF-8$" ' test_expect_success 'the body carries the blob byte for byte' ' @@ -43,7 +43,7 @@ test_expect_success 'a blob whose bytes look binary is served as octet-stream' ' git commit -m binary ) && cgit_url "foo/plain/blob.bin" >tmp && - sed "/^$/q" tmp | grep "Content-Type: application/octet-stream" + sed "/^$/q" tmp | grep "^Content-Type: application/octet-stream$" ' test_done diff --git a/tests/t0110-snapshot.sh b/tests/t0110-snapshot.sh index ba38b82..4852e7b 100755 --- a/tests/t0110-snapshot.sh +++ b/tests/t0110-snapshot.sh @@ -31,7 +31,7 @@ test_tar_snapshot() { test_expect_success $prereq "check $ext response headers" " head -n 1 tmp | grep 'Status: 200 OK' && - head -n 2 tmp | grep 'Content-Type: $type' && + head -n 2 tmp | grep '^Content-Type: $type\$' && head -n 3 tmp | grep 'Content-Disposition: inline; filename=.master.$ext.' " @@ -58,7 +58,7 @@ test_expect_success 'get foo/snapshot/master.tar' ' test_expect_success 'check tar response headers' ' head -n 1 tmp | grep "Status: 200 OK" && - head -n 2 tmp | grep "Content-Type: application/x-tar" && + head -n 2 tmp | grep "^Content-Type: application/x-tar$" && head -n 3 tmp | grep "Content-Disposition: inline; filename=.master.tar." ' @@ -91,7 +91,7 @@ test_expect_success 'get foo/snapshot/master.zip' ' test_expect_success 'check zip response headers' ' head -n 1 tmp | grep "Status: 200 OK" && - head -n 2 tmp | grep "Content-Type: application/zip" && + head -n 2 tmp | grep "^Content-Type: application/zip$" && head -n 3 tmp | grep "Content-Disposition: inline; filename=.master.zip." ' -- cgit v2.8.0