From d1b50598c6ba7f0ca61bb0ea5e4ff9c66c226b4e Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Sun, 19 Jul 2026 15:47:52 -1000 Subject: Keep line structure in plaintext readmes --- assets/cgit.css | 18 ++++++++++++++++++ assets/cgit.js | 3 +++ source/ui-summary.c | 5 ++++- tests/t0200-security.sh | 4 ++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/assets/cgit.css b/assets/cgit.css index 10fcf66..f441faa 100644 --- a/assets/cgit.css +++ b/assets/cgit.css @@ -496,6 +496,24 @@ div#cgit div#summary pre { overflow-x: auto; } +/* A plaintext readme served without an about filter. Wrapped instead of + * scrolled, since prose is the common case. */ +div#cgit pre.plaintext { + white-space: pre-wrap; + overflow-wrap: anywhere; + font-family: var(--font-mono); + line-height: 1.5; + margin: 0; +} + +/* Markdown source before the client-side renderer has run, and for good + * when scripting is off. Keeps the line structure so it reads as text. */ +div#cgit .markdown[data-markdown] { + white-space: pre-wrap; + overflow-wrap: anywhere; + font-family: var(--font-mono); +} + div#cgit .markdown { line-height: 1.6; overflow-wrap: break-word; diff --git a/assets/cgit.js b/assets/cgit.js index 4e0ac70..347e977 100644 --- a/assets/cgit.js +++ b/assets/cgit.js @@ -300,6 +300,9 @@ document.addEventListener("DOMContentLoaded", function () { continue; try { el.innerHTML = render(text); + /* The attribute doubles as the style hook for the + * unrendered source, so drop it once rendered. */ + el.removeAttribute("data-markdown"); } catch (e) { /* leave the escaped source in place on any failure */ } diff --git a/source/ui-summary.c b/source/ui-summary.c index b5a6a22..8dd191b 100644 --- a/source/ui-summary.c +++ b/source/ui-summary.c @@ -167,8 +167,10 @@ void cgit_print_repo_readme(const char *path) /* No about-filter is configured, so there is nothing to turn * the readme source into safe HTML. Escape it rather than serve * repo content raw, which would let an untrusted repository - * inject script into the about page. + * inject script into the about page. The pre keeps the line + * structure of the text, which bare escaped output loses. */ + html("
");
 		if (ref) {
 			cgit_print_file(filename, ref, 1, 1);
 		} else {
@@ -177,6 +179,7 @@ void cgit_print_repo_readme(const char *path)
 				html_txt(sb.buf);
 			strbuf_release(&sb);
 		}
+		html("
"); } else { /* An about-filter is configured and is responsible for turning * the source into safe HTML, so pass it through the filter raw. diff --git a/tests/t0200-security.sh b/tests/t0200-security.sh index afce715..d3ac472 100644 --- a/tests/t0200-security.sh +++ b/tests/t0200-security.sh @@ -100,6 +100,10 @@ test_expect_success 'non-markdown readme without a filter is escaped' ' ! grep "" tmp ' +test_expect_success 'non-markdown readme keeps its line structure' ' + grep "pre class=.plaintext." tmp +' + # --- Auto-submitting selects carry no inline handlers ------------------------ # A Content-Security-Policy without unsafe-inline blocks inline onchange # handlers, so the forms mark their selects and cgit.js wires them up. -- cgit v2.8.0