knotserver: allow downloading archives of refs with slashes in their names #441

merged
opened by winter.bsky.social targeting master from winter.bsky.social/core: push-vtrouwuvwzqv
Changed files
+14 -3
knotserver
+14 -3
knotserver/routes.go
··· 361 361 362 362 ref := strings.TrimSuffix(file, ".tar.gz") 363 363 364 + unescapedRef, err := url.PathUnescape(ref) 365 + if err != nil { 366 + notFound(w) 367 + return 368 + } 369 + 370 + // refs/tags/* is way more common than refs/heads/*, so we can live with an ugly file/directory 371 + // name in the odd cases (e.g. refs-heads-master.tar.gz, but in that case the user should just 372 + // request master.tar.gz) 373 + safeRefFilename := strings.ReplaceAll(strings.TrimPrefix(unescapedRef, "refs/tags/"), "/", "-") 374 + 364 375 // This allows the browser to use a proper name for the file when 365 376 // downloading 366 - filename := fmt.Sprintf("%s-%s.tar.gz", name, ref) 377 + filename := fmt.Sprintf("%s-%s.tar.gz", name, safeRefFilename) 367 378 setContentDisposition(w, filename) 368 379 setGZipMIME(w) 369 380 370 381 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 371 - gr, err := git.Open(path, ref) 382 + gr, err := git.Open(path, unescapedRef) 372 383 if err != nil { 373 384 notFound(w) 374 385 return ··· 377 388 gw := gzip.NewWriter(w) 378 389 defer gw.Close() 379 390 380 - prefix := fmt.Sprintf("%s-%s", name, ref) 391 + prefix := fmt.Sprintf("%s-%s", name, safeRefFilename) 381 392 err = gr.WriteTar(gw, prefix) 382 393 if err != nil { 383 394 // once we start writing to the body we can't report error anymore