From c77b1cba33621e9935e96b97972d353a5ab78f3b 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 Signed-off-by: Winter --- knotserver/routes.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/knotserver/routes.go b/knotserver/routes.go index ba0afa3..ae4a2f2 100644 --- a/knotserver/routes.go +++ b/knotserver/routes.go @@ -361,14 +361,28 @@ 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 + } + + // For now, only accept `/` in a ref name if it's refs/tags/* + if strings.Contains(unescapedRef, "/") && !strings.HasPrefix(unescapedRef, "refs/tags/") { + notFound(w) + return + } + + safeRefFilename := 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 +391,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