Monorepo for Tangled tangled.org
4

Configure Feed

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

at icy/ytnwlw 3.8 kB View raw
1package xrpc 2 3import ( 4 "crypto/sha256" 5 "fmt" 6 "io" 7 "net/http" 8 "path/filepath" 9 "slices" 10 "strconv" 11 "strings" 12 13 "github.com/bluesky-social/indigo/atproto/atclient" 14 "github.com/bluesky-social/indigo/atproto/syntax" 15 "tangled.org/core/knotmirror/xrpc/gitea" 16) 17 18func (x *Xrpc) GetBlob(w http.ResponseWriter, r *http.Request) { 19 var ( 20 repoQuery = r.URL.Query().Get("repo") 21 ref = r.URL.Query().Get("ref") // ref can be empty (git.Open handles this) 22 path = r.URL.Query().Get("path") 23 ) 24 25 repo, err := syntax.ParseDID(repoQuery) 26 if err != nil { 27 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", repoQuery)}) 28 return 29 } 30 31 l := x.logger.With("method", "git.getBlob", "repo", repo, "ref", ref, "path", path) 32 l.Debug("request") 33 34 if path == "" { 35 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: "missing path parameter"}) 36 return 37 } 38 39 ctx := r.Context() 40 41 repoPath, err := x.makeRepoPath(ctx, repo) 42 if err != nil { 43 writeJson(w, http.StatusNotFound, atclient.ErrorBody{Name: "RepoNotFound", Message: fmt.Sprintf("unknown repository: %s", repo)}) 44 return 45 } 46 47 entry, err := gitea.GetEntry(ctx, repoPath, ref, path) 48 if err != nil { 49 l.Warn("local mirror failed", "err", err) 50 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to get blob"}) 51 return 52 } 53 size, reader, err := gitea.ReadBlob(ctx, repoPath, entry.Hash) 54 if err != nil { 55 l.Warn("local mirror failed", "err", err) 56 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to get blob"}) 57 return 58 } 59 defer reader.Close() 60 61 w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) 62 63 // default to octet-stream for large blobs 64 if size > 1024*1024 { // 1MiB 65 w.Header().Set("Content-Type", "application/octet-stream") 66 if _, err := io.Copy(w, reader); err != nil { 67 l.Error("failed to serve the blob", "err", err) 68 } 69 return 70 } 71 72 contents, err := io.ReadAll(reader) 73 if err != nil { 74 l.Error("failed to read blob content", "err", err) 75 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to read the blob"}) 76 return 77 } 78 79 eTag := fmt.Sprintf("\"%x\"", sha256.Sum256(contents)) 80 if clientETag := r.Header.Get("If-None-Match"); clientETag == eTag { 81 w.WriteHeader(http.StatusNotModified) 82 return 83 } 84 w.Header().Set("ETag", eTag) 85 w.Header().Set("X-Content-Type-Options", "nosniff") 86 87 mimeType := http.DetectContentType(contents) 88 // override MIME types for formats that http.DetectContentType does not recognize 89 switch filepath.Ext(path) { 90 case ".svg": 91 mimeType = "image/svg+xml" 92 case ".avif": 93 mimeType = "image/avif" 94 case ".jxl": 95 mimeType = "image/jxl" 96 case ".heic", ".heif": 97 mimeType = "image/heif" 98 } 99 100 switch { 101 case strings.HasPrefix(mimeType, "image/"), strings.HasPrefix(mimeType, "video/"): 102 w.Header().Set("Content-Type", mimeType) 103 104 case strings.HasPrefix(mimeType, "text/") || isTextualMimeType(mimeType): 105 w.Header().Set("Cache-Control", "public, no-cache") 106 // serve all text content as text/plain 107 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 108 109 default: 110 // fallback to octet-stream 111 w.Header().Set("Content-Type", "application/octet-stream") 112 } 113 w.Write(contents) 114} 115 116var textualMimeTypes = []string{ 117 "application/json", 118 "application/xml", 119 "application/yaml", 120 "application/x-yaml", 121 "application/toml", 122 "application/javascript", 123 "application/ecmascript", 124} 125 126// isTextualMimeType returns true if the MIME type represents textual content 127// that should be served as text/plain for security reasons 128func isTextualMimeType(mimeType string) bool { 129 return slices.Contains(textualMimeTypes, mimeType) 130}