426024af
fix(server): cap plain-text readme render and document url assumption
alex emery 2026-04-13 05:43
diff --git a/src/server/http/repo/readme.rs b/src/server/http/repo/readme.rs index 6672521..a7bab9e 100644 --- a/src/server/http/repo/readme.rs +++ b/src/server/http/repo/readme.rs @@ -57,11 +57,24 @@ pub fn load_readme( rendered } } ReadmeKind::Plain => render_plain(text), ReadmeKind::Plain => { let rendered = render_plain(text); if rendered.len() > MAX_HTML_BYTES { too_large_notice_with_repo(repo_name, branch, &entry.name) } else { rendered } } }; Some(RenderedReadme { html }) } /// Build the "README too large" fallback HTML block. The link path is /// assembled from `repo_name`, `branch`, and `file_name` after html-escaping. /// We do NOT percent-encode: callers are expected to pass URL-safe values /// (git branch and repo names that follow normal slug conventions, and one /// of the fixed README filenames). Slashes in branch names are intentionally /// preserved as path separators. fn too_large_notice_with_repo(repo_name: &str, branch: &str, file_name: &str) -> String { format!( "<p><em>README too large to render. \ @@ -412,6 +425,15 @@ mod tests { } #[test] fn plain_text_post_render_cap_trips() { // 500 KiB of '&' → ~2.5 MiB of "&" inside a <pre>. let bytes = vec![b'&'; 500 * 1024]; let (repo, _tmp) = repo_with_files(&[("README", &bytes)]); let r = load(&repo).unwrap(); assert!(r.html.contains("too large"), "expected plain-text bomb to trip cap"); } #[test] fn binary_blob_returns_none() { let (repo, _tmp) = repo_with_files(&[("README.md", &[0u8, 1, 2, 3, 0xff, 0xfe])]); assert!(load(&repo).is_none());