Monorepo for Tangled
at master 131 lines 3.6 kB view raw
1package state 2 3import ( 4 "fmt" 5 "io" 6 "net/http" 7 8 "github.com/bluesky-social/indigo/atproto/identity" 9 "github.com/go-chi/chi/v5" 10 "tangled.org/core/appview/models" 11) 12 13// allowedResponseHeaders is the set of headers we will forward from the knot 14// back to the client. everything else is stripped. 15var allowedResponseHeaders = map[string]bool{ 16 "Content-Encoding": true, 17 "Transfer-Encoding": true, 18 "Cache-Control": true, 19 "Expires": true, 20 "Pragma": true, 21} 22 23func copyAllowedHeaders(dst, src http.Header) { 24 for k, vv := range src { 25 if allowedResponseHeaders[http.CanonicalHeaderKey(k)] { 26 for _, v := range vv { 27 dst.Add(k, v) 28 } 29 } 30 } 31} 32 33func setGitHeaders(w http.ResponseWriter, contentType string) { 34 w.Header().Set("Content-Type", contentType) 35 w.Header().Set("Content-Disposition", "attachment") 36 w.Header().Set("X-Content-Type-Options", "nosniff") 37} 38 39func (s *State) InfoRefs(w http.ResponseWriter, r *http.Request) { 40 repo := r.Context().Value("repo").(*models.Repo) 41 42 scheme := "https" 43 if s.config.Core.Dev { 44 scheme = "http" 45 } 46 47 service := r.URL.Query().Get("service") 48 var contentType string 49 switch service { 50 case "git-receive-pack": 51 contentType = "application/x-git-receive-pack-advertisement" 52 default: 53 contentType = "application/x-git-upload-pack-advertisement" 54 } 55 56 targetURL := fmt.Sprintf("%s://%s/%s/info/refs?%s", scheme, repo.Knot, repo.RepoIdentifier(), r.URL.RawQuery) 57 s.proxyRequest(w, r, targetURL, contentType) 58} 59 60func (s *State) UploadArchive(w http.ResponseWriter, r *http.Request) { 61 repo := r.Context().Value("repo").(*models.Repo) 62 63 scheme := "https" 64 if s.config.Core.Dev { 65 scheme = "http" 66 } 67 68 targetURL := fmt.Sprintf("%s://%s/%s/git-upload-archive?%s", scheme, repo.Knot, repo.RepoIdentifier(), r.URL.RawQuery) 69 s.proxyRequest(w, r, targetURL, "application/x-git-upload-archive-result") 70} 71 72func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) { 73 repo := r.Context().Value("repo").(*models.Repo) 74 75 scheme := "https" 76 if s.config.Core.Dev { 77 scheme = "http" 78 } 79 80 targetURL := fmt.Sprintf("%s://%s/%s/git-upload-pack?%s", scheme, repo.Knot, repo.RepoIdentifier(), r.URL.RawQuery) 81 s.proxyRequest(w, r, targetURL, "application/x-git-upload-pack-result") 82} 83 84func (s *State) ReceivePack(w http.ResponseWriter, r *http.Request) { 85 repo := r.Context().Value("repo").(*models.Repo) 86 87 scheme := "https" 88 if s.config.Core.Dev { 89 scheme = "http" 90 } 91 92 targetURL := fmt.Sprintf("%s://%s/%s/git-receive-pack?%s", scheme, repo.Knot, repo.RepoIdentifier(), r.URL.RawQuery) 93 s.proxyRequest(w, r, targetURL, "application/x-git-receive-pack-result") 94} 95 96func (s *State) proxyRequest(w http.ResponseWriter, r *http.Request, targetURL string, contentType string) { 97 client := &http.Client{} 98 99 proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body) 100 if err != nil { 101 http.Error(w, err.Error(), http.StatusInternalServerError) 102 return 103 } 104 105 proxyReq.Header = r.Header.Clone() 106 107 repoOwnerHandle := chi.URLParam(r, "user") 108 if id, ok := r.Context().Value("resolvedId").(identity.Identity); ok && !id.Handle.IsInvalidHandle() { 109 repoOwnerHandle = id.Handle.String() 110 } 111 proxyReq.Header.Set("x-tangled-repo-owner-handle", repoOwnerHandle) 112 113 resp, err := client.Do(proxyReq) 114 if err != nil { 115 http.Error(w, err.Error(), http.StatusInternalServerError) 116 return 117 } 118 defer resp.Body.Close() 119 120 // selectively copy only allowed headers 121 copyAllowedHeaders(w.Header(), resp.Header) 122 123 setGitHeaders(w, contentType) 124 125 w.WriteHeader(resp.StatusCode) 126 127 if _, err := io.Copy(w, resp.Body); err != nil { 128 http.Error(w, err.Error(), http.StatusInternalServerError) 129 return 130 } 131}