Monorepo for Tangled
tangled.org
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 "tangled.org/core/api/tangled"
12)
13
14func (rp *Repo) DownloadArchive(w http.ResponseWriter, r *http.Request) {
15 l := rp.logger.With("handler", "DownloadArchive")
16 ref := chi.URLParam(r, "ref")
17 ref, _ = url.PathUnescape(ref)
18 ref = strings.TrimSuffix(ref, ".tar.gz")
19 f, err := rp.repoResolver.Resolve(r)
20 if err != nil {
21 l.Error("failed to get repo and knot", "err", err)
22 return
23 }
24
25 // build the xrpc url
26 query := url.Values{}
27 query.Set("repo", f.RepoDid)
28 query.Set("ref", ref)
29 query.Set("format", "tar.gz")
30 query.Set("prefix", r.URL.Query().Get("prefix"))
31 xrpcURL := fmt.Sprintf(
32 "%s/xrpc/%s?%s",
33 rp.config.KnotMirror.Url,
34 tangled.GitTempGetArchiveNSID,
35 query.Encode(),
36 )
37
38 // make the get request
39 resp, err := http.Get(xrpcURL)
40 if err != nil {
41 l.Error("failed to call XRPC repo.archive", "err", err)
42 rp.pages.Error503(w)
43 return
44 }
45 defer resp.Body.Close()
46
47 // force application/gzip here
48 w.Header().Set("Content-Type", "application/gzip")
49
50 filename := ""
51 if cd := resp.Header.Get("Content-Disposition"); strings.HasPrefix(cd, "attachment;") {
52 filename = cd // knot has already set the attachment CD
53 }
54 if filename == "" {
55 filename = fmt.Sprintf("attachment; filename=\"%s-%s.tar.gz\"", f.Name, ref)
56 }
57 w.Header().Set("Content-Disposition", filename)
58 w.Header().Set("X-Content-Type-Options", "nosniff")
59
60 if link := resp.Header.Get("Link"); link != "" {
61 if resolvedRef, err := extractImmutableLink(link); err == nil {
62 newLink := fmt.Sprintf("<%s/%s/archive/%s.tar.gz>; rel=\"immutable\"",
63 rp.config.Core.BaseUrl(), f.RepoIdentifier(), resolvedRef)
64 w.Header().Set("Link", newLink)
65 }
66 }
67
68 // stream the archive data directly
69 if _, err := io.Copy(w, resp.Body); err != nil {
70 l.Error("failed to write response", "err", err)
71 }
72}
73
74func extractImmutableLink(linkHeader string) (string, error) {
75 trimmed := strings.TrimPrefix(linkHeader, "<")
76 trimmed = strings.TrimSuffix(trimmed, ">; rel=\"immutable\"")
77
78 parsedLink, err := url.Parse(trimmed)
79 if err != nil {
80 return "", err
81 }
82
83 resolvedRef := parsedLink.Query().Get("ref")
84 if resolvedRef == "" {
85 return "", fmt.Errorf("no ref found in link header")
86 }
87
88 return resolvedRef, nil
89}