From 8e0d9d5e28a03ba4d3670ddc0f7a9dc06337cd56 Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Sat, 7 Nov 2026 15:20:19 -1000 Subject: Drop cached pages asked for with an escaped url README tells a visitor to write a plus in the query form as %2b, and cgit caches such a page under the raw query, but the hook compared that query with the url as it stands. A push to foo+bar.git left its pages under url=foo%2bbar.git/ and r=foo%2Bbar.git cached and stale until their ttl ran out. Decoding in sh would take more than the hook is worth, so a query with a % or a + now counts as stale, which costs another repository's page at most a refill. --- custom/hooks/post-receive.cgit-cache | 10 +++++++--- tests/t0507-hooks.sh | 22 ++++++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/custom/hooks/post-receive.cgit-cache b/custom/hooks/post-receive.cgit-cache index 8aae60b..b92683a 100755 --- a/custom/hooks/post-receive.cgit-cache +++ b/custom/hooks/post-receive.cgit-cache @@ -7,8 +7,9 @@ # cgit writes the request each slot answers at the head of the slot file, so # the hook reads it back and drops only this repository's pages and the # repository index, whose idle column the push changes. Other repositories -# keep their cached pages. The cached repository scan goes too, which is what -# makes a brand new repository appear without waiting out cache-scan-ttl. +# keep their cached pages, bar any asked for with an escaped query. The cached +# repository scan goes too, which is what makes a brand new repository appear +# without waiting out cache-scan-ttl. # # The hook needs the url cgit serves this repository at. A repository that # scan-path finds is served at its path below scan_path, as set below, so @@ -77,7 +78,8 @@ take() { # Whether the page a slot holds goes stale with this push: one of this # repository's, by path or by the url and r parameters, or the index. A key # that does not read as cgit's counts as stale, since dropping a page costs -# only a refill. +# only a refill, and so does a query with a % or a + in it, since only +# decoding it would tell which repository it names. stale() { rest=$1 take && take && take || return 0 @@ -99,6 +101,8 @@ stale() { ;; *"&url=&"* | *"&url=/&"*) ;; + *"&url="*[%+]* | *"&r="*[%+]*) + ;; *"&url="* | *"&r="*) return 1 ;; diff --git a/tests/t0507-hooks.sh b/tests/t0507-hooks.sh index d4dfb95..04f8490 100755 --- a/tests/t0507-hooks.sh +++ b/tests/t0507-hooks.sh @@ -33,18 +33,20 @@ fresh_slots() { rm -rf slots && cp -R pristine slots } -# A cache that cgit itself filled, for two scanned repositories and the +# A cache that cgit itself filled, for the scanned repositories and the # index, so the hook reads the keys cgit really writes. Each page has to # have a slot of its own, or a check that a page went would pass for one # that was never there. -test_expect_success 'set up a cache of two repositories and the index' ' +test_expect_success 'set up a cache of three repositories and the index' ' test_create_repo pusher && test_commit -C pusher cached && mkdir scan slots && git init -q --bare scan/a.git && git init -q --bare scan/b.git && + git init -q --bare scan/foo+bar.git && git -C pusher push -q ../scan/a.git HEAD:refs/heads/main && git -C pusher push -q ../scan/b.git HEAD:refs/heads/main && + git -C pusher push -q ../scan/foo+bar.git HEAD:refs/heads/main && { echo "cache-root=$PWD/slots" && echo "cache-size=1000" && @@ -60,6 +62,9 @@ test_expect_success 'set up a cache of two repositories and the index' ' cgit_rc cachedrc "url=b.git/refs/" >/dev/null && cgit_rc cachedrc "r=a.git&p=log" >/dev/null && cgit_rc cachedrc "r=b.git&p=log" >/dev/null && + CGIT_CONFIG="$PWD/cachedrc" PATH_INFO=/foo+bar.git/log/ QUERY_STRING= cgit >/dev/null && + cgit_rc cachedrc "url=foo%2bbar.git/refs/" >/dev/null && + cgit_rc cachedrc "r=foo%2Bbar.git&p=log" >/dev/null && echo writer >slots/0123abcd.lock && printf "not a key\0page" >slots/00000000 && cp -R slots pristine && @@ -73,6 +78,9 @@ test_expect_success 'set up a cache of two repositories and the index' ' 7|http://0|0|15|url=b.git/refs/ 7|http://0|0|13|r=a.git&p=log 7|http://0|0|13|r=b.git&p=log + 7|http://0|17|/foo+bar.git/log/0| + 7|http://0|0|23|url=foo%2bbar.git/refs/ + 7|http://0|0|21|r=foo%2Bbar.git&p=log not a key EOF cached_requests pristine >keys && @@ -180,6 +188,16 @@ do test_grep ! "|/c/" left ' + test_expect_success "a push drops the pages of a repository whose url is escaped under $shell" ' + fresh_slots && + (cd scan/foo+bar.git && $shell ../../cache-hook 2>../../err) && + test_must_be_empty err && + cached_requests slots >left && + test_grep "|/b.git/log/" left && + test_grep "url=b.git/refs/" left && + test_grep ! "foo" left + ' + test_expect_success "a repository named in cgitrc gives the hook its url under $shell" ' fresh_slots && git -C scan/b.git config hooks.cgiturl b.git && -- cgit 2.12.1