diff options
authorBryce Kwon <bryce@brycekwon.com>
committerBryce Kwon <bryce@brycekwon.com>
commit
parent
tree
download
Lead a readme's relative links into the tree
A browser resolved them against the about page, where nothing lives now that the page answers 404 below itself. They resolve the way GitHub resolves a readme's links instead, from the readme's directory and on the branch it was read from.
Diffstat (limited to '')
-rw-r--r--custom/extensions/about-render.lua61
1 file changed, 55 insertions, 6 deletions
diff --git a/custom/extensions/about-render.lua b/custom/extensions/about-render.lua
index ecb2abe..a3932af 100644
--- a/custom/extensions/about-render.lua
+++ b/custom/extensions/about-render.lua
@@ -30,7 +30,7 @@ local max_bytes = 512 * 1024
-- markers cannot drive the recursive parser into a stack overflow.
local max_depth = 24
-local filename, chunks = "", {}
+local filename, tree_base, tree_query, chunks = "", "", "", {}
-- Lua patterns backtrack, so "%s+$" costs the square of a run of spaces in
@@ -108,11 +108,56 @@ local function safe_url(url)
return url
end
+-- A readme percent-encodes what a link path cannot carry, and the path is
+-- encoded again on its way out, so it is decoded first. An encoded NUL stays
+-- encoded, since cgit's sinks would stop at a real one.
+local function unescape_path(path)
+ return (path:gsub("%%(%x%x)", function(hex)
+ local byte = tonumber(hex, 16)
+ if byte > 0 then
+ return string.char(byte)
+ end
+ end))
+end
+
+-- The path in the tree that a relative link names. Like GitHub, it resolves
+-- against the readme's own directory, or against the root when it starts
+-- with a slash, and dot segments fold without climbing above the root.
+local function tree_path(path)
+ if path:sub(1, 1) ~= "/" then
+ path = (filename:match("^(.*/)") or "") .. path
+ end
+ local segments = {}
+ for segment in path:gmatch("[^/]+") do
+ if segment == ".." then
+ segments[#segments] = nil
+ elseif segment ~= "." then
+ segments[#segments + 1] = segment
+ end
+ end
+ return table.concat(segments, "/")
+end
+
+-- Writes a link target that safe_url passed. A relative one leads into the
+-- tree at the ref the readme came from, keeping its fragment but not a
+-- query, which the tree page would read as its own. A readme with no tree
+-- behind it keeps its links as written.
+local function emit_href(url)
+ if tree_base == "" or url:find("^#") or url:find("^%a[%w%+%.%-]*:") then
+ html_attr(url)
+ return
+ end
+ html_attr(tree_base)
+ html_url_path(tree_path(unescape_path(url:match("^[^?#]*"))))
+ html_attr(tree_query)
+ html_attr(url:match("#.*$") or "")
+end
+
-- The page renders untrusted repository content. Every run of text goes out
--- through cgit's html_txt, every attribute value through html_attr, and
--- every link or image target through safe_url before html_attr. html() only
--- ever gets literal tag scaffolding.
+-- through cgit's html_txt, every attribute value through html_attr or, for a
+-- path in the tree, html_url_path, and every link or image target through
+-- safe_url first. html() only ever gets literal tag scaffolding.
local emit_inline
@@ -130,7 +175,7 @@ emit_inline = function(list)
elseif kind == "link" then
local target = safe_url(node.url)
if target then
- html("<a href='"); html_attr(target); html("'>")
+ html("<a href='"); emit_href(target); html("'>")
emit_inline(node.kids)
html("</a>")
else
@@ -632,8 +677,12 @@ for _, ext in ipairs({ "1", "2", "3", "4", "5", "6", "7", "8", "9", "man" }) do
handlers[ext] = render_man
end
-function filter_open(name)
+-- A readme tracked in the repository comes with the url of the tree page at
+-- its ref and the query that follows a path there, both escaped for a url.
+function filter_open(name, base, query)
filename = name or ""
+ tree_base = base or ""
+ tree_query = query or ""
chunks = {}
end