Files
summercms/modules/surf/uploads.go
Jakub Zych 0b6d757b40 feat(14.2-01): opt-in storage.uploads.serve route for public uploads
- surf mounts GET {storage.uploads.public_path_prefix}/{path...} on
  attach.StaticHandlerPublic when storage.uploads.serve is true
- bucket and database resolved per request; X-Content-Type-Options: nosniff
- surf README, storage and configuration docs describe the key
2026-10-11 17:36:54 +02:00

65 lines
2.1 KiB
Go

package surf
import (
"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.
// With the key false or absent nothing is registered.
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
}
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)
}
}