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

knotserver: git: serve raw binary blobs

authored by anirudh.fi and committed by

Tangled 53a53a97 04e2bc40

+68 -2
+31 -1
knotserver/git/git.go
··· 37 37 } 38 38 39 39 var ( 40 - ErrBinaryFile = fmt.Errorf("binary file") 40 + ErrBinaryFile = fmt.Errorf("binary file") 41 + ErrNotBinaryFile = fmt.Errorf("not binary file") 41 42 ) 42 43 43 44 type GitRepo struct { ··· 192 191 } else { 193 192 return "", ErrBinaryFile 194 193 } 194 + } 195 + 196 + func (g *GitRepo) BinContent(path string) ([]byte, error) { 197 + c, err := g.r.CommitObject(g.h) 198 + if err != nil { 199 + return nil, fmt.Errorf("commit object: %w", err) 200 + } 201 + 202 + tree, err := c.Tree() 203 + if err != nil { 204 + return nil, fmt.Errorf("file tree: %w", err) 205 + } 206 + 207 + file, err := tree.File(path) 208 + if err != nil { 209 + return nil, err 210 + } 211 + 212 + isbin, _ := file.IsBinary() 213 + if isbin { 214 + reader, err := file.Reader() 215 + if err != nil { 216 + return nil, fmt.Errorf("opening file reader: %w", err) 217 + } 218 + defer reader.Close() 219 + 220 + return io.ReadAll(reader) 221 + } 222 + return nil, ErrNotBinaryFile 195 223 } 196 224 197 225 func (g *GitRepo) Tags() ([]*TagReference, error) {
+1
knotserver/handler.go
··· 100 100 101 101 r.Route("/blob/{ref}", func(r chi.Router) { 102 102 r.Get("/*", h.Blob) 103 + r.Get("/raw/*", h.BlobRaw) 103 104 }) 104 105 105 106 r.Get("/log/{ref}", h.Log)
+36 -1
knotserver/routes.go
··· 194 194 return 195 195 } 196 196 197 + func (h *Handle) BlobRaw(w http.ResponseWriter, r *http.Request) { 198 + treePath := chi.URLParam(r, "*") 199 + ref := chi.URLParam(r, "ref") 200 + ref, _ = url.PathUnescape(ref) 201 + 202 + l := h.l.With("handler", "BlobRaw", "ref", ref, "treePath", treePath) 203 + 204 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 205 + gr, err := git.Open(path, ref) 206 + if err != nil { 207 + notFound(w) 208 + return 209 + } 210 + 211 + contents, err := gr.BinContent(treePath) 212 + if err != nil { 213 + writeError(w, err.Error(), http.StatusBadRequest) 214 + l.Error("file content", "error", err.Error()) 215 + return 216 + } 217 + 218 + mimeType := http.DetectContentType(contents) 219 + 220 + if !strings.HasPrefix(mimeType, "image/") && !strings.HasPrefix(mimeType, "video/") { 221 + l.Error("attempted to serve non-image/video file", "mimetype", mimeType) 222 + writeError(w, "only image and video files can be accessed directly", http.StatusForbidden) 223 + return 224 + } 225 + 226 + w.Header().Set("Cache-Control", "public, max-age=86400") // cache for 24 hours 227 + w.Header().Set("ETag", fmt.Sprintf("%x", sha256.Sum256(contents))) 228 + w.Header().Set("Content-Type", mimeType) 229 + w.Write(contents) 230 + } 231 + 197 232 func (h *Handle) Blob(w http.ResponseWriter, r *http.Request) { 198 233 treePath := chi.URLParam(r, "*") 199 234 ref := chi.URLParam(r, "ref") 200 235 ref, _ = url.PathUnescape(ref) 201 236 202 - l := h.l.With("handler", "FileContent", "ref", ref, "treePath", treePath) 237 + l := h.l.With("handler", "Blob", "ref", ref, "treePath", treePath) 203 238 204 239 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 205 240 gr, err := git.Open(path, ref)