forked from tangled.org/core
this repo has no description

knotserver: git: serve raw binary blobs

Changed files
+68 -2
knotserver
+31 -1
knotserver/git/git.go
···
}
var (
-
ErrBinaryFile = fmt.Errorf("binary file")
+
ErrBinaryFile = fmt.Errorf("binary file")
+
ErrNotBinaryFile = fmt.Errorf("not binary file")
)
type GitRepo struct {
···
} else {
return "", ErrBinaryFile
}
+
}
+
+
func (g *GitRepo) BinContent(path string) ([]byte, error) {
+
c, err := g.r.CommitObject(g.h)
+
if err != nil {
+
return nil, fmt.Errorf("commit object: %w", err)
+
}
+
+
tree, err := c.Tree()
+
if err != nil {
+
return nil, fmt.Errorf("file tree: %w", err)
+
}
+
+
file, err := tree.File(path)
+
if err != nil {
+
return nil, err
+
}
+
+
isbin, _ := file.IsBinary()
+
if isbin {
+
reader, err := file.Reader()
+
if err != nil {
+
return nil, fmt.Errorf("opening file reader: %w", err)
+
}
+
defer reader.Close()
+
+
return io.ReadAll(reader)
+
}
+
return nil, ErrNotBinaryFile
}
func (g *GitRepo) Tags() ([]*TagReference, error) {
+1
knotserver/handler.go
···
r.Route("/blob/{ref}", func(r chi.Router) {
r.Get("/*", h.Blob)
+
r.Get("/raw/*", h.BlobRaw)
})
r.Get("/log/{ref}", h.Log)
+36 -1
knotserver/routes.go
···
return
}
+
func (h *Handle) BlobRaw(w http.ResponseWriter, r *http.Request) {
+
treePath := chi.URLParam(r, "*")
+
ref := chi.URLParam(r, "ref")
+
ref, _ = url.PathUnescape(ref)
+
+
l := h.l.With("handler", "BlobRaw", "ref", ref, "treePath", treePath)
+
+
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
+
gr, err := git.Open(path, ref)
+
if err != nil {
+
notFound(w)
+
return
+
}
+
+
contents, err := gr.BinContent(treePath)
+
if err != nil {
+
writeError(w, err.Error(), http.StatusBadRequest)
+
l.Error("file content", "error", err.Error())
+
return
+
}
+
+
mimeType := http.DetectContentType(contents)
+
+
if !strings.HasPrefix(mimeType, "image/") && !strings.HasPrefix(mimeType, "video/") {
+
l.Error("attempted to serve non-image/video file", "mimetype", mimeType)
+
writeError(w, "only image and video files can be accessed directly", http.StatusForbidden)
+
return
+
}
+
+
w.Header().Set("Cache-Control", "public, max-age=86400") // cache for 24 hours
+
w.Header().Set("ETag", fmt.Sprintf("%x", sha256.Sum256(contents)))
+
w.Header().Set("Content-Type", mimeType)
+
w.Write(contents)
+
}
+
func (h *Handle) Blob(w http.ResponseWriter, r *http.Request) {
treePath := chi.URLParam(r, "*")
ref := chi.URLParam(r, "ref")
ref, _ = url.PathUnescape(ref)
-
l := h.l.With("handler", "FileContent", "ref", ref, "treePath", treePath)
+
l := h.l.With("handler", "Blob", "ref", ref, "treePath", treePath)
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
gr, err := git.Open(path, ref)