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) } }