Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

appview/state: harden knot proxying

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

+59 -24
+11 -8
appview/repo/archive.go
··· 54 54 } 55 55 defer resp.Body.Close() 56 56 57 - // pass through headers from upstream response 58 - if contentDisposition := resp.Header.Get("Content-Disposition"); contentDisposition != "" { 59 - w.Header().Set("Content-Disposition", contentDisposition) 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 60 63 } 61 - if contentType := resp.Header.Get("Content-Type"); contentType != "" { 62 - w.Header().Set("Content-Type", contentType) 64 + if filename == "" { 65 + filename = fmt.Sprintf("attachment; filename=\"%s-%s.tar.gz\"", f.Name, ref) 63 66 } 64 - if contentLength := resp.Header.Get("Content-Length"); contentLength != "" { 65 - w.Header().Set("Content-Length", contentLength) 66 - } 67 + w.Header().Set("Content-Disposition", filename) 68 + w.Header().Set("X-Content-Type-Options", "nosniff") 69 + 67 70 if link := resp.Header.Get("Link"); link != "" { 68 71 if resolvedRef, err := extractImmutableLink(link); err == nil { 69 72 newLink := fmt.Sprintf("<%s/%s/archive/%s.tar.gz>; rel=\"immutable\"",
+48 -16
appview/state/git_http.go
··· 3 3 import ( 4 4 "fmt" 5 5 "io" 6 - "maps" 7 6 "net/http" 8 7 9 8 "github.com/bluesky-social/indigo/atproto/identity" 10 9 "github.com/go-chi/chi/v5" 11 10 "tangled.org/core/appview/models" 12 11 ) 12 + 13 + // allowedResponseHeaders is the set of headers we will forward from the knot 14 + // back to the client. everything else is stripped. 15 + var allowedResponseHeaders = map[string]bool{ 16 + "Content-Encoding": true, 17 + "Transfer-Encoding": true, 18 + "Cache-Control": true, 19 + "Expires": true, 20 + "Pragma": true, 21 + } 22 + 23 + func 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 + 33 + func 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 + } 13 38 14 39 func (s *State) InfoRefs(w http.ResponseWriter, r *http.Request) { 15 40 user := r.Context().Value("resolvedId").(identity.Identity) ··· 45 20 scheme = "http" 46 21 } 47 22 48 - targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 49 - s.proxyRequest(w, r, targetURL) 23 + // check for the 'service' url param 24 + service := r.URL.Query().Get("service") 25 + var contentType string 26 + switch service { 27 + case "git-receive-pack": 28 + contentType = "application/x-git-receive-pack-advertisement" 29 + default: 30 + // git-upload-pack is the default service for git-clone / git-fetch. 31 + contentType = "application/x-git-upload-pack-advertisement" 32 + } 50 33 34 + targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 35 + s.proxyRequest(w, r, targetURL, contentType) 51 36 } 52 37 53 38 func (s *State) UploadArchive(w http.ResponseWriter, r *http.Request) { ··· 74 39 } 75 40 76 41 targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-archive?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 77 - s.proxyRequest(w, r, targetURL) 42 + s.proxyRequest(w, r, targetURL, "application/x-git-upload-archive-result") 78 43 } 79 44 80 45 func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) { ··· 91 56 } 92 57 93 58 targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 94 - s.proxyRequest(w, r, targetURL) 59 + s.proxyRequest(w, r, targetURL, "application/x-git-upload-pack-result") 95 60 } 96 61 97 62 func (s *State) ReceivePack(w http.ResponseWriter, r *http.Request) { ··· 108 73 } 109 74 110 75 targetURL := fmt.Sprintf("%s://%s/%s/%s/git-receive-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 111 - s.proxyRequest(w, r, targetURL) 76 + s.proxyRequest(w, r, targetURL, "application/x-git-receive-pack-result") 112 77 } 113 78 114 - func (s *State) proxyRequest(w http.ResponseWriter, r *http.Request, targetURL string) { 79 + func (s *State) proxyRequest(w http.ResponseWriter, r *http.Request, targetURL string, contentType string) { 115 80 client := &http.Client{} 116 81 117 - // Create new request 118 82 proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body) 119 83 if err != nil { 120 84 http.Error(w, err.Error(), http.StatusInternalServerError) 121 85 return 122 86 } 123 87 124 - // Copy original headers 125 - proxyReq.Header = r.Header 88 + proxyReq.Header = r.Header.Clone() 126 89 127 90 repoOwnerHandle := chi.URLParam(r, "user") 128 - proxyReq.Header.Add("x-tangled-repo-owner-handle", repoOwnerHandle) 91 + proxyReq.Header.Set("x-tangled-repo-owner-handle", repoOwnerHandle) 129 92 130 - // Execute request 131 93 resp, err := client.Do(proxyReq) 132 94 if err != nil { 133 95 http.Error(w, err.Error(), http.StatusInternalServerError) ··· 132 100 } 133 101 defer resp.Body.Close() 134 102 135 - // Copy response headers 136 - maps.Copy(w.Header(), resp.Header) 103 + // selectively copy only allowed headers 104 + copyAllowedHeaders(w.Header(), resp.Header) 137 105 138 - // Set response status code 106 + setGitHeaders(w, contentType) 107 + 139 108 w.WriteHeader(resp.StatusCode) 140 109 141 - // Copy response body 142 110 if _, err := io.Copy(w, resp.Body); err != nil { 143 111 http.Error(w, err.Error(), http.StatusInternalServerError) 144 112 return