Documentation
¶
Overview ¶
Package blobserve writes stored binary content to an HTTP response.
Every raw-content endpoint in the platform — portal assets, asset versions, thumbnails, public share content, managed resources — serves bytes that a user uploaded or an upstream API produced, under a content type that came from the same untrusted place. They all need the same six things, and getting any of them wrong on one endpoint is a bug on that endpoint alone, which is why this is one function rather than a convention:
- A sandbox Content-Security-Policy, so bytes that reach a browser as a document cannot script the platform's origin whatever their type.
- X-Content-Type-Options: nosniff, so a browser cannot decide for itself that a text/plain response is really HTML and run it.
- A Content-Type reduced to a parsed, parameter-free media type, so a stored value like `text/plain; charset=utf-8"><script>` cannot smuggle anything into the header.
- Content-Disposition: attachment for the scriptable document families (HTML, XHTML, JSX, SVG, JavaScript, XML), which must never render inline on the platform's own origin, and inline for the passive families a viewer embeds.
- Byte-range support, so an audio or video element can seek without downloading the whole object first.
- A Cache-Control default of `private`, so an endpoint that authorizes its caller does not hand its bytes to a shared cache by saying nothing, and `private, no-cache` with an ETag for a body derived at serve time (Options.Revalidate).
Index ¶
- Constants
- func CachePrivate(w http.ResponseWriter, maxAge time.Duration)
- func Headers(opts Options) http.Header
- func Serve(w http.ResponseWriter, r *http.Request, opts Options)
- func ServeRanged(w http.ResponseWriter, r *http.Request, opts Options, obj Object) bool
- type Object
- type Options
- type RangeReader
- type RangeSource
- type ReadRange
Constants ¶
const DefaultCacheControl = "private"
DefaultCacheControl is written when the caller set no Cache-Control of its own. Every endpoint reaching this package authorizes its caller first, and a response carrying no directive at all is heuristically storable by a shared cache: with ServeContent's Last-Modified as the freshness basis, a CDN or ingress cache in front of the platform may reuse one authorized fetch for every later request to the same URL, which is authorization bypass by omission. `private` says the browser that was authorized may keep it and no shared cache may.
Unlike the CSP above this is a default rather than an override: an endpoint whose bytes are genuinely anonymous — a fully public share's thumbnail — sets `public` deliberately, and that decision belongs to the handler that knows who may read the object.
It is exported because the surfaces that do not write through Serve — the gateway raw passthrough streams to its own sink — apply the same default, and the directive is the contract rather than each surface's opinion.
Variables ¶
This section is empty.
Functions ¶
func CachePrivate ¶ added in v1.117.0
func CachePrivate(w http.ResponseWriter, maxAge time.Duration)
CachePrivate marks the response as one only the caller that was authorized for it may keep: storable by that browser for maxAge, never by a shared cache, and keyed on the credential the browser sends so a cache that does store it cannot answer a second caller from the first one's copy.
It is one call rather than two Set lines per endpoint because the pairing is the point — a `private` that lost its Vary, or a Vary that lost its directive, is the same authorization bypass the pair exists to prevent. An endpoint whose bytes are genuinely anonymous writes `public` itself, so the exception stays visible at the one place that takes it.
func Headers ¶ added in v1.117.0
Headers returns the headers that decide how a browser treats untrusted bytes: the parsed, parameter-free Content-Type, the sandbox CSP, nosniff, and a Content-Disposition whose kind follows the type's family and whose filename cannot break out of its quoted parameter.
Only Name, ContentType and ForceAttachment are read; ModTime and Data describe a body this function does not write. It is separate from Serve for the surfaces that do not answer through an http.ResponseWriter — the gateway raw passthrough streams to its own sink — so the rendering decision is made once for every byte-serving surface rather than restated per transport. Cache-Control is not included: it is a default a caller may override, not a rendering decision, and DefaultCacheControl carries it.
func Serve ¶
func Serve(w http.ResponseWriter, r *http.Request, opts Options)
Serve writes the blob to w, honoring range requests in r.
func ServeRanged ¶ added in v1.135.0
ServeRanged answers r by reading only the bytes the response needs, and reports whether it did. It reports false, having written nothing, when the store cannot read ranges or the object's size is unknown, and the caller then serves the object's bytes as it otherwise would.
It is how a content endpoint serves a viewer that reads one file many times by byte range: see Options.Source and RangeSource.
Types ¶
type Object ¶ added in v1.135.0
type Object struct {
// Store is the blob store. A store that implements RangeReader can serve
// a range; one that does not leaves the caller to serve the bytes itself.
Store any
Bucket string
Key string
Size int64
}
Object names the stored object a ranged read reads from: the store to ask, where the object sits, and how many bytes it holds.
type Options ¶
type Options struct {
// Name is the object's display filename. It is used for the
// Content-Disposition filename and must not be empty for downloads.
Name string
// ContentType is the stored media type. It is sanitized before it reaches
// the response; an empty or unparseable value serves as
// application/octet-stream.
ContentType string
// ModTime is the object's last-modified time, used for If-Range and
// conditional-request handling. The zero value disables both.
ModTime time.Time
// Data is the object's full content.
Data []byte
// Source serves the object's bytes without holding them, in place of
// Data. Serve uses it when it is set, and Data is then ignored.
//
// It exists for a viewer that reads one object many times by byte range:
// the Parquet viewer reads a footer and then a row group at a time
// (#1833), and answering each of those from a whole-object read pulls the
// file once per request. A Source reads the range asked for and nothing
// else. It is for binary content only: a body the serving rewrite derives
// from the stored bytes, and the ETag Revalidate takes over it, both need
// the whole payload and go through Data.
Source io.ReadSeeker
// ForceAttachment serves the blob as a download regardless of family. Set
// it on endpoints that exist to download, not to preview.
ForceAttachment bool
// Revalidate marks a body computed at serve time from more than the stored
// object, so a browser must ask before reusing its copy. The response
// carries `private, no-cache` and a strong ETag over Data, and no
// Last-Modified: ModTime is ignored.
//
// It exists for textual asset content, which the serving rewrite derives from the
// stored bytes AND the asset's references (#1835). A references change
// alters the body without touching the object or its time, so a copy the
// browser held under heuristic freshness kept rendering the raw mcp:// URIs
// until a hard refresh. The ETag is taken over the bytes actually served,
// so every input the body depends on, the rewrite's base URL included,
// invalidates it, and an unchanged body still revalidates to a 304 with
// nothing transferred. Last-Modified is withheld because a browser holding
// only that validator would send If-Modified-Since alone, and the object's
// time cannot see a references change. Binary content is never rewritten,
// and is left to the default so a seek does not hash the whole object.
Revalidate bool
}
Options describes one blob to serve.
type RangeReader ¶ added in v1.135.0
type RangeReader interface {
GetObjectRange(ctx context.Context, bucket, key string, offset, length int64) (body []byte, size int64, err error)
}
RangeReader is the ranged read a blob store may offer. A store that has it can answer a byte range without reading the object whole; one that does not is served from Data as before, so this is asked for by type assertion rather than added to every store's interface.
type RangeSource ¶ added in v1.135.0
type RangeSource struct {
// contains filtered or unexported fields
}
RangeSource serves an object of a known size by reading only the blocks the response needs. It satisfies Options.Source.
A viewer that reads one object many times by byte range -- the Parquet viewer reads the footer, then a row group at a time (#1833) -- otherwise costs one whole-object read per request, which for a large file is the file again for every few kilobytes served.
func NewRangeSource ¶ added in v1.135.0
func NewRangeSource(size int64, read ReadRange) *RangeSource
NewRangeSource returns a source over an object of size bytes.
func RangeSourceFor ¶ added in v1.135.0
RangeSourceFor returns a source over an object when store can read ranges, or nil when it cannot or the size is unknown, in which case the caller serves the object's bytes as it did before.