forked from tangled.org/core
this repo has no description
at master 3.9 kB view raw
1package xrpc 2 3import ( 4 "crypto/sha256" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "net/http" 9 "path/filepath" 10 "slices" 11 "strings" 12 13 "tangled.sh/tangled.sh/core/api/tangled" 14 "tangled.sh/tangled.sh/core/knotserver/git" 15 xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors" 16) 17 18func (x *Xrpc) RepoBlob(w http.ResponseWriter, r *http.Request) { 19 _, repoPath, ref, err := x.parseStandardParams(r) 20 if err != nil { 21 writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest) 22 return 23 } 24 25 treePath := r.URL.Query().Get("path") 26 if treePath == "" { 27 writeError(w, xrpcerr.NewXrpcError( 28 xrpcerr.WithTag("InvalidRequest"), 29 xrpcerr.WithMessage("missing path parameter"), 30 ), http.StatusBadRequest) 31 return 32 } 33 34 raw := r.URL.Query().Get("raw") == "true" 35 36 gr, err := git.Open(repoPath, ref) 37 if err != nil { 38 writeError(w, xrpcerr.NewXrpcError( 39 xrpcerr.WithTag("RefNotFound"), 40 xrpcerr.WithMessage("repository or ref not found"), 41 ), http.StatusNotFound) 42 return 43 } 44 45 contents, err := gr.RawContent(treePath) 46 if err != nil { 47 x.Logger.Error("file content", "error", err.Error()) 48 writeError(w, xrpcerr.NewXrpcError( 49 xrpcerr.WithTag("FileNotFound"), 50 xrpcerr.WithMessage("file not found at the specified path"), 51 ), http.StatusNotFound) 52 return 53 } 54 55 mimeType := http.DetectContentType(contents) 56 57 if filepath.Ext(treePath) == ".svg" { 58 mimeType = "image/svg+xml" 59 } 60 61 if raw { 62 contentHash := sha256.Sum256(contents) 63 eTag := fmt.Sprintf("\"%x\"", contentHash) 64 65 switch { 66 case strings.HasPrefix(mimeType, "image/"), strings.HasPrefix(mimeType, "video/"): 67 if clientETag := r.Header.Get("If-None-Match"); clientETag == eTag { 68 w.WriteHeader(http.StatusNotModified) 69 return 70 } 71 w.Header().Set("ETag", eTag) 72 w.Header().Set("Content-Type", mimeType) 73 74 case strings.HasPrefix(mimeType, "text/"): 75 w.Header().Set("Cache-Control", "public, no-cache") 76 // serve all text content as text/plain 77 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 78 79 case isTextualMimeType(mimeType): 80 // handle textual application types (json, xml, etc.) as text/plain 81 w.Header().Set("Cache-Control", "public, no-cache") 82 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 83 84 default: 85 x.Logger.Error("attempted to serve disallowed file type", "mimetype", mimeType) 86 writeError(w, xrpcerr.NewXrpcError( 87 xrpcerr.WithTag("InvalidRequest"), 88 xrpcerr.WithMessage("only image, video, and text files can be accessed directly"), 89 ), http.StatusForbidden) 90 return 91 } 92 w.Write(contents) 93 return 94 } 95 96 isTextual := func(mt string) bool { 97 return strings.HasPrefix(mt, "text/") || isTextualMimeType(mt) 98 } 99 100 var content string 101 var encoding string 102 103 isBinary := !isTextual(mimeType) 104 105 if isBinary { 106 content = base64.StdEncoding.EncodeToString(contents) 107 encoding = "base64" 108 } else { 109 content = string(contents) 110 encoding = "utf-8" 111 } 112 113 response := tangled.RepoBlob_Output{ 114 Ref: ref, 115 Path: treePath, 116 Content: content, 117 Encoding: &encoding, 118 Size: &[]int64{int64(len(contents))}[0], 119 IsBinary: &isBinary, 120 } 121 122 if mimeType != "" { 123 response.MimeType = &mimeType 124 } 125 126 w.Header().Set("Content-Type", "application/json") 127 if err := json.NewEncoder(w).Encode(response); err != nil { 128 x.Logger.Error("failed to encode response", "error", err) 129 writeError(w, xrpcerr.NewXrpcError( 130 xrpcerr.WithTag("InternalServerError"), 131 xrpcerr.WithMessage("failed to encode response"), 132 ), http.StatusInternalServerError) 133 return 134 } 135} 136 137// isTextualMimeType returns true if the MIME type represents textual content 138// that should be served as text/plain for security reasons 139func isTextualMimeType(mimeType string) bool { 140 textualTypes := []string{ 141 "application/json", 142 "application/xml", 143 "application/yaml", 144 "application/x-yaml", 145 "application/toml", 146 "application/javascript", 147 "application/ecmascript", 148 } 149 150 return slices.Contains(textualTypes, mimeType) 151}