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
4.0 kB 133 lines
1package repo 2 3import ( 4 "fmt" 5 "io" 6 "net/http" 7 "net/url" 8 "strings" 9 "time" 10 11 "github.com/go-chi/chi/v5" 12 "github.com/samber/lo" 13 "tangled.org/core/api/tangled" 14 "tangled.org/core/appview/models" 15 "tangled.org/core/gitutil" 16) 17 18const archiveRoute = "/archive/*" 19 20func newArchiveClient(headerTimeout time.Duration) *http.Client { 21 transport := http.DefaultTransport.(*http.Transport).Clone() 22 transport.ResponseHeaderTimeout = headerTimeout 23 return &http.Client{Transport: transport} 24} 25 26func (rp *Repo) DownloadArchive(w http.ResponseWriter, r *http.Request) { 27 l := rp.logger.With("handler", "DownloadArchive") 28 fail := func(status int) { 29 w.WriteHeader(status) 30 lo.Ternary(status == http.StatusServiceUnavailable, rp.pages.Error503, rp.pages.Error404)(w) 31 } 32 33 params, err := parseArchiveRequest(r) 34 if err != nil { 35 l.Warn("rejecting archive request", "err", err) 36 fail(http.StatusNotFound) 37 return 38 } 39 40 f, err := rp.repoResolver.Resolve(r) 41 if err != nil { 42 l.Error("failed to get repo and knot", "err", err) 43 fail(http.StatusNotFound) 44 return 45 } 46 47 name := gitutil.RepoName(f.Slug()) 48 params.Prefix = params.Prefix.OrDefault(name, params.Rev) 49 50 // build the xrpc url 51 xrpcURL := fmt.Sprintf("%s/xrpc/%s?%s", 52 rp.config.KnotMirror.Url, tangled.GitTempGetArchiveNSID, params.Query(f.RepoDid).Encode()) 53 54 // make the get request 55 req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, xrpcURL, nil) 56 if err != nil { 57 l.Error("failed to build XRPC repo.archive request", "err", err) 58 fail(http.StatusServiceUnavailable) 59 return 60 } 61 resp, err := rp.archiveClient.Do(req) 62 if err != nil { 63 l.Error("failed to call XRPC repo.archive", "err", err) 64 fail(http.StatusServiceUnavailable) 65 return 66 } 67 defer resp.Body.Close() 68 69 if resp.StatusCode != http.StatusOK { 70 l.Error("XRPC repo.archive failed", "status", resp.StatusCode, "ref", params.Rev) 71 overloaded := resp.StatusCode >= http.StatusInternalServerError || resp.StatusCode == http.StatusTooManyRequests 72 fail(lo.Ternary(overloaded, http.StatusServiceUnavailable, http.StatusNotFound)) 73 return 74 } 75 76 params.SetHeaders(w.Header(), name) 77 78 if resolvedRev, err := gitutil.ParseImmutableLink(resp.Header.Get("Link")); err == nil { 79 w.Header().Set("Link", gitutil.ImmutableLink(rp.immutableArchiveURL(f, params.WithRev(resolvedRev)))) 80 } 81 82 // stream the archive data directly 83 if _, err := io.Copy(w, resp.Body); err != nil { 84 l.Error("failed to write response", "err", err) 85 } 86} 87 88func parseArchiveRequest(r *http.Request) (gitutil.ArchiveParams, error) { 89 ref := chi.URLParam(r, "*") 90 if unescaped, err := url.PathUnescape(ref); err == nil && r.URL.RawPath != "" { 91 ref = unescaped 92 } 93 94 suffix, found := lo.Find(gitutil.ArchiveFormats, func(f gitutil.ArchiveFormat) bool { 95 return strings.HasSuffix(ref, "."+f.String()) 96 }) 97 if found { 98 ref = strings.TrimSuffix(ref, "."+suffix.String()) 99 } 100 101 rev, err := gitutil.ParseRev(ref) 102 if err != nil { 103 return gitutil.ArchiveParams{}, err 104 } 105 106 query := r.URL.Query() 107 query.Del("ref") 108 query.Set("format", archiveFormat(query.Get("format"), suffix, r.UserAgent()).String()) 109 params, err := gitutil.ParseArchiveParams(query) 110 if err != nil { 111 return gitutil.ArchiveParams{}, err 112 } 113 return params.WithRev(rev), nil 114} 115 116func archiveFormat(requested string, suffix gitutil.ArchiveFormat, userAgent string) gitutil.ArchiveFormat { 117 if format, err := gitutil.ParseArchiveFormat(requested); err == nil { 118 return format 119 } 120 if suffix != "" { 121 return suffix 122 } 123 ua := strings.ToLower(userAgent) 124 windows := lo.SomeBy([]string{"windows", "win64", "win32"}, func(s string) bool { return strings.Contains(ua, s) }) 125 return lo.Ternary(windows, gitutil.ArchiveZip, gitutil.ArchiveTarGz) 126} 127 128func (rp *Repo) immutableArchiveURL(f *models.Repo, params gitutil.ArchiveParams) string { 129 return fmt.Sprintf("%s/%s/archive/%s.%s?%s", 130 rp.config.Core.BaseUrl(), f.RepoIdentifier(), 131 url.PathEscape(params.Rev.String()), params.Format, 132 url.Values{"prefix": {params.Prefix.String()}}.Encode()) 133}