refactor(surf): take the uploads inline types from attach.IsInlineContentType (quick-261028-rqe)
- the route's private inline list and its helper are gone; the disposition writer asks attach.IsInlineContentType, the same definition attach.Store uses for the disposition it stores - headers are unchanged (TestUploadsResponseHeaders, TestUploadsMount) - surf README and the storage page name the shared function
This commit is contained in:
@@ -2,7 +2,6 @@ package surf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -27,26 +26,20 @@ const defaultUploadsPrefix = "/storage/uploads"
|
||||
// embeds the file.
|
||||
const uploadsContentSecurityPolicy = "default-src 'none'; img-src 'self'; media-src 'self'; sandbox"
|
||||
|
||||
// uploadsInlineTypes are the content types the uploads route lets a browser
|
||||
// show inline: raster images. Every other successful response, SVG and PDF
|
||||
// included, is served with Content-Disposition: attachment.
|
||||
var uploadsInlineTypes = map[string]bool{
|
||||
"image/avif": true,
|
||||
"image/gif": true,
|
||||
"image/jpeg": true,
|
||||
"image/png": true,
|
||||
"image/webp": true,
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Because the files are served on the app origin, every response carries
|
||||
// X-Content-Type-Options: nosniff and uploadsContentSecurityPolicy, and a
|
||||
// file whose stored type is not an inline raster image (uploadsInlineTypes)
|
||||
// is served as an attachment: attach.Store records the type sniffed from
|
||||
// the bytes, so an uploaded text file that starts with <html> is text/html. The bucket and the database are looked up per request:
|
||||
// boot and route:list build the router before serve publishes either.
|
||||
// file whose stored type is not an inline raster image
|
||||
// (attach.IsInlineContentType) is served as an attachment: attach.Store
|
||||
// records the type sniffed from the bytes, so an uploaded text file that
|
||||
// starts with <html> is text/html. attach.Store also stores the attachment
|
||||
// disposition with the blob, but the route still decides from the type on
|
||||
// every response: attach.StaticHandlerPublic sends only the stored type, and
|
||||
// blobs written earlier or outside attach.Store carry no disposition. 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.
|
||||
@@ -125,9 +118,9 @@ func uploadsHandler(app *backpack.App, pages *errorPages, prefix string) http.Ha
|
||||
|
||||
// serveUploads runs next with the uploads response headers: nosniff and the
|
||||
// sandboxing CSP always, and Content-Disposition: attachment on a successful
|
||||
// response whose Content-Type is not in uploadsInlineTypes. The disposition
|
||||
// is decided when the status is written (or, for a HEAD that writes
|
||||
// nothing, when next returns), after next has set the Content-Type.
|
||||
// response whose Content-Type attach.IsInlineContentType rejects. The
|
||||
// disposition is decided when the status is written (or, for a HEAD that
|
||||
// writes nothing, when next returns), after next has set the Content-Type.
|
||||
func serveUploads(w http.ResponseWriter, req *http.Request, next http.Handler) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("Content-Security-Policy", uploadsContentSecurityPolicy)
|
||||
@@ -148,7 +141,7 @@ func (w *uploadsDispositionWriter) decide(status int) {
|
||||
return
|
||||
}
|
||||
w.decided = true
|
||||
if status >= 200 && status < 300 && !uploadsInline(w.Header().Get("Content-Type")) {
|
||||
if status >= 200 && status < 300 && !attach.IsInlineContentType(w.Header().Get("Content-Type")) {
|
||||
w.Header().Set("Content-Disposition", "attachment")
|
||||
}
|
||||
}
|
||||
@@ -164,9 +157,3 @@ func (w *uploadsDispositionWriter) Write(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (w *uploadsDispositionWriter) Unwrap() http.ResponseWriter { return w.ResponseWriter }
|
||||
|
||||
// uploadsInline reports whether contentType is an inline raster image.
|
||||
func uploadsInline(contentType string) bool {
|
||||
mediaType, _, err := mime.ParseMediaType(contentType)
|
||||
return err == nil && uploadsInlineTypes[strings.ToLower(mediaType)]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user