This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / repo / archive.go
2.5 kB 99 lines
1package repo 2 3import ( 4 "fmt" 5 "io" 6 "net/http" 7 "net/url" 8 "strings" 9 10 "github.com/go-chi/chi/v5" 11) 12 13func (rp *Repo) DownloadArchive(w http.ResponseWriter, r *http.Request) { 14 l := rp.logger.With("handler", "DownloadArchive") 15 ref := chi.URLParam(r, "ref") 16 ref, _ = url.PathUnescape(ref) 17 ref = strings.TrimSuffix(ref, ".tar.gz") 18 f, err := rp.repoResolver.Resolve(r) 19 if err != nil { 20 l.Error("failed to get repo and knot", "err", err) 21 return 22 } 23 scheme := "http" 24 if !rp.config.Core.Dev { 25 scheme = "https" 26 } 27 host := fmt.Sprintf("%s://%s", scheme, f.Knot) 28 didSlashRepo := f.DidSlashRepo() 29 30 // build the xrpc url 31 u, err := url.Parse(host) 32 if err != nil { 33 l.Error("failed to parse host URL", "err", err) 34 rp.pages.Error503(w) 35 return 36 } 37 38 u.Path = "/xrpc/sh.tangled.repo.archive" 39 query := url.Values{} 40 query.Set("format", "tar.gz") 41 query.Set("prefix", r.URL.Query().Get("prefix")) 42 query.Set("ref", ref) 43 query.Set("repo", didSlashRepo) 44 u.RawQuery = query.Encode() 45 46 xrpcURL := u.String() 47 48 // make the get request 49 resp, err := http.Get(xrpcURL) 50 if err != nil { 51 l.Error("failed to call XRPC repo.archive", "err", err) 52 rp.pages.Error503(w) 53 return 54 } 55 defer resp.Body.Close() 56 57 // force application/gzip here 58 w.Header().Set("Content-Type", "application/gzip") 59 60 filename := "" 61 if cd := resp.Header.Get("Content-Disposition"); strings.HasPrefix(cd, "attachment;") { 62 filename = cd // knot has already set the attachment CD 63 } 64 if filename == "" { 65 filename = fmt.Sprintf("attachment; filename=\"%s-%s.tar.gz\"", f.Name, ref) 66 } 67 w.Header().Set("Content-Disposition", filename) 68 w.Header().Set("X-Content-Type-Options", "nosniff") 69 70 if link := resp.Header.Get("Link"); link != "" { 71 if resolvedRef, err := extractImmutableLink(link); err == nil { 72 newLink := fmt.Sprintf("<%s/%s/archive/%s.tar.gz>; rel=\"immutable\"", 73 rp.config.Core.BaseUrl(), f.DidSlashRepo(), resolvedRef) 74 w.Header().Set("Link", newLink) 75 } 76 } 77 78 // stream the archive data directly 79 if _, err := io.Copy(w, resp.Body); err != nil { 80 l.Error("failed to write response", "err", err) 81 } 82} 83 84func extractImmutableLink(linkHeader string) (string, error) { 85 trimmed := strings.TrimPrefix(linkHeader, "<") 86 trimmed = strings.TrimSuffix(trimmed, ">; rel=\"immutable\"") 87 88 parsedLink, err := url.Parse(trimmed) 89 if err != nil { 90 return "", err 91 } 92 93 resolvedRef := parsedLink.Query().Get("ref") 94 if resolvedRef == "" { 95 return "", fmt.Errorf("no ref found in link header") 96 } 97 98 return resolvedRef, nil 99}