From e572767a6df60d49192b189de14a1b0c621c8950 Mon Sep 17 00:00:00 2001 From: Winter Date: Sat, 9 Aug 2025 23:13:36 -0400 Subject: [PATCH] knotserver: allow downloading archives of refs with slashes in their names Change-Id: nznsykstnxtwzkylxxzsmwtqmuurtplr This allows us to be more precise when downloading e.g. tags that have the same name as branches (in which case Git defaults to the branch, not sure what go-git does). Signed-off-by: Winter --- knotserver/routes.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/knotserver/routes.go b/knotserver/routes.go index ba0afa3..eba1a2e 100644 --- a/knotserver/routes.go +++ b/knotserver/routes.go @@ -361,14 +361,25 @@ func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) { ref := strings.TrimSuffix(file, ".tar.gz") + unescapedRef, err := url.PathUnescape(ref) + if err != nil { + notFound(w) + return + } + + // refs/tags/* is way more common than refs/heads/*, so we can live with an ugly file/directory + // name in the odd cases (e.g. refs-heads-master.tar.gz, but in that case the user should just + // request master.tar.gz) + safeRefFilename := strings.ReplaceAll(strings.TrimPrefix(unescapedRef, "refs/tags/"), "/", "-") + // This allows the browser to use a proper name for the file when // downloading - filename := fmt.Sprintf("%s-%s.tar.gz", name, ref) + filename := fmt.Sprintf("%s-%s.tar.gz", name, safeRefFilename) setContentDisposition(w, filename) setGZipMIME(w) path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) - gr, err := git.Open(path, ref) + gr, err := git.Open(path, unescapedRef) if err != nil { notFound(w) return @@ -377,7 +388,7 @@ func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) { gw := gzip.NewWriter(w) defer gw.Close() - prefix := fmt.Sprintf("%s-%s", name, ref) + prefix := fmt.Sprintf("%s-%s", name, safeRefFilename) err = gr.WriteTar(gw, prefix) if err != nil { // once we start writing to the body we can't report error anymore -- 2.43.0