This repository has no description
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}