Documentation
¶
Overview ¶
Package media manages the media library, storage drivers and image variants.
Uploads are sniffed (content must match the extension) and checked against the media.allowed_mimes allowlist and media.max_upload_mb.
SVG security: uploaded SVG is sanitized and is also served with a sandboxing Content-Security-Policy. The sanitizer keeps allowlisted elements in the SVG namespace only and drops event handlers; hrefs are checked per element (local fragments, embedded raster data:, http(s)/ mailto links on <a>). An attribute value is dropped, and a whole <style> element is dropped, when it contains (after XML entity decoding, case- and whitespace-insensitively) a script URL, @import, expression(), a url() other than url(#local), one of the resource-loading functions image-set()/-webkit-image-set(), src(), image(), cross-fade(), or any backslash: CSS escapes ("\75rl(", "@\69mport") could otherwise spell those, and plain SVG never needs them. This blocks the known script and external-fetch vectors; it is defense in depth, not a guarantee: themes or editors that INLINE SVG markup into HTML must still treat it as untrusted (prefer <img src> for media SVG).
contract.go holds the frozen wave-0 contract (types and interfaces only).
Index ¶
- Constants
- Variables
- func PublicPath(key string) string
- func SafeStorageClient() *http.Client
- func SaveDestination(ctx context.Context, database *sql.DB, store *settings.Store, d Destination) error
- func SaveDestinationTx(ctx context.Context, tx *sql.Tx, store *settings.Store, d Destination) error
- func StorageSiteID(ctx context.Context, database *sql.DB) (string, error)
- type ActiveStorage
- type DeleteResponse
- type Destination
- type FetchJob
- type Focal
- type ListQuery
- type LocalStorage
- type ManagedStorage
- func (s *ManagedStorage) Delete(ctx context.Context, key string) error
- func (s *ManagedStorage) Describe(ctx context.Context) StorageInfo
- func (s *ManagedStorage) Open(ctx context.Context, key string) (io.ReadCloser, error)
- func (s *ManagedStorage) Put(ctx context.Context, key string, r io.Reader, mime string) error
- func (s *ManagedStorage) URL(key string) string
- func (s *ManagedStorage) UploadKey(ctx context.Context, key string) (string, error)
- type Media
- type Module
- type Patch
- type S3Config
- type S3Storage
- func (s *S3Storage) Delete(ctx context.Context, key string) error
- func (s *S3Storage) Open(ctx context.Context, key string) (io.ReadCloser, error)
- func (s *S3Storage) OpenEncryptedBackup(ctx context.Context, client *http.Client, key string) (io.ReadCloser, error)
- func (s *S3Storage) ProbeBucket(ctx context.Context, client *http.Client) error
- func (s *S3Storage) Put(ctx context.Context, key string, r io.Reader, mime string) error
- func (s *S3Storage) PutEncryptedBackup(ctx context.Context, client *http.Client, key string, body []byte) error
- func (s *S3Storage) PutEncryptedBackupReader(ctx context.Context, client *http.Client, key string, body io.ReadSeeker) error
- func (s *S3Storage) SetClient(c *http.Client)
- func (s *S3Storage) URL(key string) string
- type Service
- type Storage
- type StorageInfo
- type UploadMeta
- type UploadRequest
- type Variant
Constants ¶
const ( SettingMaxUploadMB = "media.max_upload_mb" SettingAllowedMimes = "media.allowed_mimes" )
Settings keys (defaults below apply when unset).
const ( HookUploaded = "media.uploaded" HookDeleted = "media.deleted" )
Hook (event) names emitted after commit; payload is the Media record.
const DestinationPrefix = "storage.destination."
const FetchJobKind = "media.fetch_url"
FetchJobKind downloads a URL into the library (payload FetchJob). The service's FromURL is synchronous; this job lets callers do it in the background.
const RevisionMarker = "#gorun-rev-"
RevisionMarker separates a source URL from a revision suffix. When a re-imported file's bytes changed, the new media row keeps the source URL plus RevisionMarker and a content hash (source_url is unique), so FindBySourceURL and FromURL of the plain URL return the newest revision.
const ServiceStorage = "media.storage"
ServiceStorage is the kernel service name of the active media storage.
const SettingStripMetadata = "media.strip_metadata"
SettingStripMetadata turns stripping off when set to false (default on).
const URLPrefix = "/media/"
URLPrefix is where stored files are served (GET /media/{key...}).
Variables ¶
var ( ErrNotFound = errors.New("media: not found") ErrTooLarge = errors.New("media: file too large") ErrEmpty = errors.New("media: empty file") ErrNotAllowed = errors.New("media: file type not allowed") )
Upload errors.
var DefaultAllowedMimes = []string{"image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml",
"application/pdf", "application/zip", "video/mp4", "audio/mpeg"}
DefaultAllowedMimes is the allowlist used when media.allowed_mimes is unset.
var ErrFocal = errors.New("media: focal point coordinates must be between 0 and 1")
ErrFocal reports a focal point outside 0..1.
var ErrInvalidKey = errors.New("media: invalid storage key")
ErrInvalidKey is returned for keys outside keyPattern.
var ErrInvalidSVG = errors.New("media: invalid SVG document")
ErrInvalidSVG is returned for documents that are not well-formed SVG.
Functions ¶
func PublicPath ¶
PublicPath uses the native media handler, including its SVG sandbox and managed-storage routing.
func SafeStorageClient ¶
func SaveDestination ¶
func SaveDestinationTx ¶
Types ¶
type ActiveStorage ¶ added in v0.15.0
type ActiveStorage interface {
Storage
UploadKey(ctx context.Context, key string) (string, error)
Describe(ctx context.Context) StorageInfo
}
ActiveStorage is the storage new uploads use, with key mapping and a description, published for capability checks.
type DeleteResponse ¶
DeleteResponse is returned by DELETE /v1/media/{id}.
type Destination ¶
type Destination struct {
ID string `json:"id"`
Name string `json:"name"`
Purpose string `json:"purpose"`
Secret string `json:"secret"`
UpdatedAt string `json:"updated_at"`
}
func LoadDestination ¶
func PrepareDestination ¶
func PrepareDestination(name, purpose string, c S3Config, key []byte) (Destination, error)
PrepareDestination binds the location to its ID; credential rotation retains it.
type FetchJob ¶
type FetchJob struct {
URL string `json:"url"`
Meta UploadMeta `json:"meta"`
}
FetchJob is the payload of FetchJobKind.
type Focal ¶ added in v0.18.0
type Focal struct {
X float64 `json:"x" minimum:"0" maximum:"1"`
Y float64 `json:"y" minimum:"0" maximum:"1"`
}
Focal is a focal point: X from 0 (left) to 1 (right), Y from 0 (top) to 1 (bottom).
type ListQuery ¶
type ListQuery struct {
MimePrefix string `json:"-"`
MinSize, MaxSize int64 `json:"-"`
Search string `json:"search"`
Page int `json:"page"`
PerPage int `json:"per_page"`
Sort string `json:"sort"`
Order string `json:"order"`
}
ListQuery filters Service.List.
type LocalStorage ¶
type LocalStorage struct {
// contains filtered or unexported fields
}
LocalStorage stores files under a directory on disk; every access goes through os.Root so a key can never escape it.
func NewLocalStorage ¶
func NewLocalStorage(dir string) *LocalStorage
NewLocalStorage returns a driver rooted at dir (created on first Put).
func (*LocalStorage) Delete ¶
func (l *LocalStorage) Delete(_ context.Context, key string) error
Delete removes key; a missing file is not an error.
func (*LocalStorage) Open ¶
func (l *LocalStorage) Open(_ context.Context, key string) (io.ReadCloser, error)
Open returns the file at key (an *os.File, so callers may Seek/Stat).
func (*LocalStorage) URL ¶
func (l *LocalStorage) URL(key string) string
URL returns the public URL of key.
type ManagedStorage ¶
type ManagedStorage struct {
// contains filtered or unexported fields
}
ManagedStorage keeps old destinations readable after upload activation changes. Only media-purpose destinations and the public media key grammar are accepted.
func (*ManagedStorage) Delete ¶
func (s *ManagedStorage) Delete(ctx context.Context, key string) error
func (*ManagedStorage) Describe ¶ added in v0.15.0
func (s *ManagedStorage) Describe(ctx context.Context) StorageInfo
Describe reports the active backend: a managed destination from settings when selected, else the config-level S3 bucket, else local disk.
func (*ManagedStorage) Open ¶
func (s *ManagedStorage) Open(ctx context.Context, key string) (io.ReadCloser, error)
func (*ManagedStorage) URL ¶
func (s *ManagedStorage) URL(key string) string
type Media ¶
type Media struct {
ID string `json:"id"`
Filename string `json:"filename"`
StorageKey string `json:"storage_key"`
URL string `json:"url"`
Mime string `json:"mime"`
Size int64 `json:"size"`
Width int `json:"width"`
Height int `json:"height"`
Alt string `json:"alt"`
Caption string `json:"caption"`
Title string `json:"title"`
AuthorID string `json:"author_id"`
Variants map[string]Variant `json:"variants"`
SourceURL string `json:"source_url"`
Checksum string `json:"checksum"`
CreatedAt string `json:"created_at"`
// Focal is the part of the image to keep in view when a theme crops it
// to a frame; nil when none is set.
Focal *Focal `json:"focal,omitempty"`
}
type Module ¶
type Module struct{}
Module is the media kernel.Module.
type Patch ¶
type Patch struct {
Alt *string `json:"alt,omitempty"`
Caption *string `json:"caption,omitempty"`
Title *string `json:"title,omitempty"`
// Focal sets the focal point; ClearFocal removes it.
Focal *Focal `json:"focal,omitempty"`
ClearFocal bool `json:"clear_focal,omitempty"`
}
Patch updates only its non-nil fields.
type S3Config ¶
type S3Config struct {
Endpoint string // https://<account>.r2.cloudflarestorage.com, https://s3.<region>.amazonaws.com
Region string // "auto" when empty (R2)
Bucket string
AccessKey string
SecretKey string
PublicURL string // optional public base of the bucket (a CDN or R2 custom domain); else files are served from /media/
}
S3Config is an S3-compatible bucket (AWS S3, Cloudflare R2, MinIO…), addressed path-style.
func DestinationConfig ¶
func DestinationConfig(d Destination, key []byte) (S3Config, error)
type S3Storage ¶
type S3Storage struct {
// contains filtered or unexported fields
}
S3Storage stores new files in a bucket. Files stored before it was enabled stay on disk: Open falls back to the local driver and Delete removes both copies.
func NewS3Storage ¶
func NewS3Storage(c S3Config, local *LocalStorage, insecure bool) (*S3Storage, error)
NewS3Storage validates c and returns the driver. Plain http endpoints are allowed only when insecure is set (dev, a local MinIO).
func (*S3Storage) Delete ¶
Delete removes key from the bucket and from disk; a missing object is not an error.
func (*S3Storage) Open ¶
Open returns the object at key, or the local file stored before the bucket was enabled.
func (*S3Storage) OpenEncryptedBackup ¶ added in v0.18.3
func (s *S3Storage) OpenEncryptedBackup(ctx context.Context, client *http.Client, key string) (io.ReadCloser, error)
OpenEncryptedBackup reads only private backup keys with the caller's safe client. Callers authenticate the entire encrypted stream and compare its plaintext digest.
func (*S3Storage) ProbeBucket ¶
ProbeBucket verifies signed read-only bucket access with the caller's guarded client. It does not change the active media driver or upload, list, or delete objects.
func (*S3Storage) PutEncryptedBackup ¶
func (s *S3Storage) PutEncryptedBackup(ctx context.Context, client *http.Client, key string, body []byte) error
PutEncryptedBackup is the in-memory compatibility entry point.
func (*S3Storage) PutEncryptedBackupReader ¶ added in v0.18.3
func (s *S3Storage) PutEncryptedBackupReader(ctx context.Context, client *http.Client, key string, body io.ReadSeeker) error
PutEncryptedBackupReader hashes and rewinds private ciphertext for SigV4. The seekable source keeps signed payloads bounded in memory and replayable.
type Service ¶
type Service interface {
Upload(ctx context.Context, filename string, r io.Reader, meta UploadMeta) (*Media, error)
FromURL(ctx context.Context, url string, meta UploadMeta) (*Media, error) // SSRF-safe, dedupes by source_url
Get(ctx context.Context, id string) (*Media, error)
List(ctx context.Context, q ListQuery) ([]Media, int, error)
Update(ctx context.Context, id string, patch Patch) (*Media, error)
Delete(ctx context.Context, id string) error
URL(m *Media, variant string) string
FindBySourceURL(ctx context.Context, url string) (*Media, error)
}
type StorageInfo ¶ added in v0.15.0
type StorageInfo struct {
Kind string `json:"kind" enum:"local,s3"`
Source string `json:"source" doc:"settings:storage.active.media, config:s3 or local"`
Configured bool `json:"configured"`
Reason string `json:"reason"`
Remediation string `json:"remediation"`
}
StorageInfo describes which backend new uploads go to and whether it is fully configured. Configured is local validation only; a passed operation test (write/read/delete of a probe object) is the evidence that it works.
type UploadMeta ¶
type UploadMeta struct {
GeneratedContentID string `json:"generated_content_id,omitempty"`
GeneratedKind string `json:"generated_kind,omitempty"`
Alt string `json:"alt"`
Caption string `json:"caption"`
Title string `json:"title"`
AuthorID string `json:"author_id"`
SourceURL string `json:"source_url"`
}
type UploadRequest ¶
type UploadRequest struct {
File string `json:"file" format:"binary" doc:"the file part (multipart/form-data)"`
Alt string `json:"alt,omitempty"`
Caption string `json:"caption,omitempty"`
Title string `json:"title,omitempty"`
}
UploadRequest documents POST /v1/media (multipart/form-data).