- TestUploadsMount: key off registers nothing; on, a public blob is served with its content type and nosniff, HEAD has no body, private, missing, partition and encoded traversal paths 404, an unpublished bucket or database 404s, a custom prefix is honoured - mountUploads refuses a plugin route at or under the prefix, so BuildRouter fails instead of only an identical pattern failing at Assemble (README and storage docs updated) - TestAccessLog: pattern and status for 200, handler 404 and recovered panic, path only for unmatched requests, locale-prefixed requests keep the pattern, integer duration_ms, no query, header or Authorization value, ResponseController flush still works
73 lines
2.5 KiB
Go
73 lines
2.5 KiB
Go
package surf
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/lagoon/attach"
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
"gocloud.dev/blob"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// uploadsPluginID owns the framework uploads route in the route table.
|
|
const uploadsPluginID = "summercms.surf"
|
|
|
|
// defaultUploadsPrefix matches the prefix attach.OpenBucket records when
|
|
// storage.uploads.public_path_prefix is empty.
|
|
const defaultUploadsPrefix = "/storage/uploads"
|
|
|
|
// mountUploads registers GET {storage.uploads.public_path_prefix}/{path...}
|
|
// when storage.uploads.serve is true. The handler delegates to
|
|
// attach.StaticHandlerPublic, so missing rows and is_public=false rows 404,
|
|
// and adds X-Content-Type-Options: nosniff because the files are served on
|
|
// the app origin. The bucket and the database are looked up per request:
|
|
// boot and route:list build the router before serve publishes either.
|
|
// The route owns the whole prefix: a plugin route at or under it fails
|
|
// BuildRouter, as a route under the admin prefix does. With the key false
|
|
// or absent nothing is registered and nothing is checked.
|
|
func mountUploads(r *Router, app *backpack.App) error {
|
|
if r == nil || app == nil || app.Config == nil {
|
|
return nil
|
|
}
|
|
if !app.Config.Bool("storage.uploads.serve") {
|
|
return nil
|
|
}
|
|
prefix := strings.TrimSuffix(strings.TrimSpace(app.Config.String("storage.uploads.public_path_prefix")), "/")
|
|
if prefix == "" {
|
|
prefix = defaultUploadsPrefix
|
|
}
|
|
for _, rt := range r.routes {
|
|
if rt.path == prefix || strings.HasPrefix(rt.path, prefix+"/") {
|
|
return fmt.Errorf("surf: route %s %s (plugin %q) is under the uploads prefix storage.uploads.public_path_prefix %s, which storage.uploads.serve mounts", rt.method, rt.path, rt.pluginID, prefix)
|
|
}
|
|
}
|
|
handler := uploadsHandler(app, prefix)
|
|
r.BindPlugin(uploadsPluginID)
|
|
r.GroupRaw(prefix, nil, func(g pact.Router) {
|
|
g.Get("/{path...}", handler)
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// uploadsHandler resolves the published bucket and database on every request
|
|
// and answers 404 while either is missing.
|
|
func uploadsHandler(app *backpack.App, prefix string) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, req *http.Request) {
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
bucket, ok := app.Lookup[*blob.Bucket]()
|
|
if !ok || bucket == nil {
|
|
http.NotFound(w, req)
|
|
return
|
|
}
|
|
gdb, ok := app.Lookup[*gorm.DB]()
|
|
if !ok || gdb == nil {
|
|
http.NotFound(w, req)
|
|
return
|
|
}
|
|
attach.StaticHandlerPublic(bucket, prefix, gdb).ServeHTTP(w, req)
|
|
}
|
|
}
|