this repo has no description
1package state 2 3import ( 4 "fmt" 5 "io" 6 "maps" 7 "net/http" 8 "strings" 9 10 "github.com/bluesky-social/indigo/atproto/identity" 11 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 12 "github.com/go-chi/chi/v5" 13 "github.com/go-git/go-git/v5/plumbing" 14 "github.com/hashicorp/go-version" 15 "tangled.org/core/api/tangled" 16 "tangled.org/core/appview/models" 17 xrpcclient "tangled.org/core/appview/xrpcclient" 18) 19 20func (s *State) InfoRefs(w http.ResponseWriter, r *http.Request) { 21 user := r.Context().Value("resolvedId").(identity.Identity) 22 repo := r.Context().Value("repo").(*models.Repo) 23 24 scheme := "https" 25 if s.config.Core.Dev { 26 scheme = "http" 27 } 28 29 targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 30 s.proxyRequest(w, r, targetURL) 31 32} 33 34func (s *State) UploadArchive(w http.ResponseWriter, r *http.Request) { 35 user, ok := r.Context().Value("resolvedId").(identity.Identity) 36 if !ok { 37 http.Error(w, "failed to resolve user", http.StatusInternalServerError) 38 return 39 } 40 repo := r.Context().Value("repo").(*models.Repo) 41 42 scheme := "https" 43 if s.config.Core.Dev { 44 scheme = "http" 45 } 46 47 targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-archive?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 48 s.proxyRequest(w, r, targetURL) 49} 50 51func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) { 52 user, ok := r.Context().Value("resolvedId").(identity.Identity) 53 if !ok { 54 http.Error(w, "failed to resolve user", http.StatusInternalServerError) 55 return 56 } 57 repo := r.Context().Value("repo").(*models.Repo) 58 59 scheme := "https" 60 if s.config.Core.Dev { 61 scheme = "http" 62 } 63 64 targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 65 s.proxyRequest(w, r, targetURL) 66} 67 68func (s *State) ReceivePack(w http.ResponseWriter, r *http.Request) { 69 user, ok := r.Context().Value("resolvedId").(identity.Identity) 70 if !ok { 71 http.Error(w, "failed to resolve user", http.StatusInternalServerError) 72 return 73 } 74 repo := r.Context().Value("repo").(*models.Repo) 75 76 scheme := "https" 77 if s.config.Core.Dev { 78 scheme = "http" 79 } 80 81 targetURL := fmt.Sprintf("%s://%s/%s/%s/git-receive-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 82 s.proxyRequest(w, r, targetURL) 83} 84 85var knotVersionDownloadArchiveConstraint = version.MustConstraints(version.NewConstraint(">= 1.12.0-alpha")) 86 87func (s *State) DownloadArchive(w http.ResponseWriter, r *http.Request) { 88 l := s.logger.With("handler", "DownloadArchive") 89 ref := chi.URLParam(r, "ref") 90 91 user, ok := r.Context().Value("resolvedId").(identity.Identity) 92 if !ok { 93 l.Error("failed to resolve user") 94 http.Error(w, "failed to resolve user", http.StatusInternalServerError) 95 return 96 } 97 repo := r.Context().Value("repo").(*models.Repo) 98 99 scheme := "https" 100 if s.config.Core.Dev { 101 scheme = "http" 102 } 103 104 host := fmt.Sprintf("%s://%s", scheme, repo.Knot) 105 xrpcc := &indigoxrpc.Client{ 106 Host: host, 107 } 108 l = l.With("knot", repo.Knot) 109 110 isCompatible := func() bool { 111 out, err := tangled.KnotVersion(r.Context(), xrpcc) 112 if err != nil { 113 l.Warn("failed to get knot version", "err", err) 114 return false 115 } 116 117 v, err := version.NewVersion(out.Version) 118 if err != nil { 119 l.Warn("failed to parse knot version", "version", out.Version, "err", err) 120 return false 121 } 122 123 if !knotVersionDownloadArchiveConstraint.Check(v) { 124 l.Warn("knot version incompatible.", "version", v) 125 return false 126 } 127 return true 128 }() 129 l.Debug("knot compatibility check", "isCompatible", isCompatible) 130 if isCompatible { 131 targetURL := fmt.Sprintf("%s://%s/%s/%s/archive/%s", scheme, repo.Knot, user.DID, repo.Name, ref) 132 s.proxyRequest(w, r, targetURL) 133 } else { 134 l.Debug("requesting xrpc/sh.tangled.repo.archive") 135 archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", ref, repo.DidSlashRepo()) 136 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 137 l.Error("failed to call XRPC repo.archive", "err", xrpcerr) 138 s.pages.Error503(w) 139 return 140 } 141 safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-") 142 filename := fmt.Sprintf("%s-%s.tar.gz", repo.Name, safeRefFilename) 143 w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) 144 w.Header().Set("Content-Type", "application/gzip") 145 w.Header().Set("Content-Length", fmt.Sprintf("%d", len(archiveBytes))) 146 w.Write(archiveBytes) 147 } 148} 149 150func (s *State) proxyRequest(w http.ResponseWriter, r *http.Request, targetURL string) { 151 client := &http.Client{} 152 153 // Create new request 154 proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body) 155 if err != nil { 156 http.Error(w, err.Error(), http.StatusInternalServerError) 157 return 158 } 159 160 // Copy original headers 161 proxyReq.Header = r.Header 162 163 repoOwnerHandle := chi.URLParam(r, "user") 164 proxyReq.Header.Add("x-tangled-repo-owner-handle", repoOwnerHandle) 165 166 // Execute request 167 resp, err := client.Do(proxyReq) 168 if err != nil { 169 http.Error(w, err.Error(), http.StatusInternalServerError) 170 return 171 } 172 defer resp.Body.Close() 173 174 // Copy response headers 175 maps.Copy(w.Header(), resp.Header) 176 177 // Set response status code 178 w.WriteHeader(resp.StatusCode) 179 180 // Copy response body 181 if _, err := io.Copy(w, resp.Body); err != nil { 182 http.Error(w, err.Error(), http.StatusInternalServerError) 183 return 184 } 185}