From 1be367f87b22ae4f43e68dd3ef600e1ee4b30c44 Mon Sep 17 00:00:00 2001 From: "Jason A. Dönerfield" Date: Sat, 18 Jul 2026 20:50:31 +0200 Subject: md2html: rewrite relative README links to the tree/ blob URL The image-rewrite patch only fixed relative ; relative links (e.g. [docs/x.md](docs/x.md)) were still resolved by the browser against the about/summary page URL, not the repo root, so they 404d. Rewrite relative to absolute cgit URLs the same way: in-repo path -> //tree/?h= ../sibling -> resolved against the repo URL location (/) Fragments are preserved; anchors, absolute, scheme and query-only URLs are left untouched. --- filters/html-converters/md2html | 71 ++++++++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 11 deletions(-) (limited to 'filters') diff --git a/filters/html-converters/md2html b/filters/html-converters/md2html index faa71f3..90021a7 100755 --- a/filters/html-converters/md2html +++ b/filters/html-converters/md2html @@ -357,27 +357,76 @@ html = markdown.markdown( extension_configs={ "markdown.extensions.codehilite":{"css_class":"highlight"}}) -# A README's relative image paths (e.g. screenshots/x.png) are repo blobs, which -# cgit only serves via its `plain` command; but a browser resolves a relative -# against the about-page URL (//about/), so the image 404s. -# Rewrite relative to an absolute //plain/?h= URL -# (absolute so it also works where cgit renders the readme on the summary page). +# A README's relative paths point at repo content, but a browser resolves them +# against the page the readme is shown on (//about/ or the summary page +# //), not against the repo root — so they 404. cgit serves blobs only via +# its `plain`/`tree` commands, so rewrite relative references to absolute cgit +# URLs (absolute so it works both on the about page and the summary page): +# -> //plain/?h= (raw bytes for the browser) +# -> //tree/?h= (browsable file/dir view) +# Relative hrefs are resolved as paths against the repo root; a href that climbs +# out of the repo with ".." (e.g. ../sibling) is resolved against the repo's URL +# location instead (-> /sibling), matching how sibling repos are addressed. # cgit exports the repo context to about-filters via these environment vars; if -# they're absent we leave the src untouched. +# they're absent we leave the references untouched. import os, re +from urllib.parse import urljoin repo = os.environ.get("CGIT_REPO_URL", "").strip("/") if repo: branch = os.environ.get("CGIT_REPO_DEFBRANCH", "").strip() - plain = "/" + repo + "/plain/" + repo_root = "/" + repo + "/" + plain = repo_root + "plain/" query = ("?h=" + branch) if branch else "" - # leave absolute, protocol-relative, root-relative, anchor and data: URLs alone - skip = re.compile(r"^(?:[a-z][a-z0-9+.\-]*:|//|/|#)", re.I) - def rewrite(m): + # leave absolute, protocol-relative, root-relative, anchor, query-only and + # scheme (mailto:, http:, data:, ...) URLs alone + skip = re.compile(r"^(?:[a-z][a-z0-9+.\-]*:|//|/|[#?])", re.I) + + def rewrite_img(m): url = m.group(2) if not url or skip.match(url): return m.group(0) return m.group(1) + plain + re.sub(r"^\./", "", url) + query + m.group(3) - html = re.sub(r'(]*?\bsrc=")([^"]*)(")', rewrite, html, flags=re.I) + + def _split_frag(url): + # split off a trailing #fragment (kept verbatim on the rewritten URL) + if "#" in url: + path, frag = url.split("#", 1) + return path, "#" + frag + return url, "" + + def _normalize(path): + # resolve "." / ".." against the repo root; returns (climbs, cleaned) + # where `climbs` counts ".." segments that escaped above the repo root. + out, climbs = [], 0 + for seg in path.split("/"): + if seg in ("", "."): + continue + if seg == "..": + if out: + out.pop() + else: + climbs += 1 + else: + out.append(seg) + return climbs, "/".join(out) + + def rewrite_href(m): + url = m.group(2) + if not url or skip.match(url): + return m.group(0) + path, frag = _split_frag(url) + climbs, cleaned = _normalize(path) + if climbs: + # escapes the repo (sibling repo etc.): resolve against repo location + dest = urljoin(repo_root, "../" * climbs + cleaned) + elif cleaned: + dest = repo_root + "tree/" + cleaned + query + else: + dest = repo_root + (query or "") + return m.group(1) + dest + frag + m.group(3) + + html = re.sub(r'(]*?\bsrc=")([^"]*)(")', rewrite_img, html, flags=re.I) + html = re.sub(r'(]*?\bhref=")([^"]*)(")', rewrite_href, html, flags=re.I) sys.stdout.write("
") sys.stdout.write(html) -- cgit v1.3.1-sl0p