Documentation
¶
Overview ¶
Package upload is part of the GoFastr framework. See https://github.com/DonaldMurillo/gofastr for documentation.
Index ¶
- Constants
- Variables
- func Handler(cfg Config) http.HandlerFunc
- func SanitizeFilename(name string) string
- func ValidateExt(filename string, allowed []string) error
- func ValidateMIME(file io.ReadSeeker, allowed []string) error
- func ValidateSize(size int64, max int64) error
- type Config
- type LocalStorage
- func (s *LocalStorage) Delete(_ context.Context, key string) error
- func (s *LocalStorage) Exists(_ context.Context, key string) (bool, error)
- func (s *LocalStorage) Get(_ context.Context, key string) (io.ReadCloser, error)
- func (s *LocalStorage) Save(_ context.Context, key string, r io.Reader) error
- type Metadata
- type Storage
Constants ¶
const MaxFilenameBytes = 255
MaxFilenameBytes caps the sanitised filename length. A user-supplied filename has no legitimate reason to exceed a few hundred bytes; the cap protects log lines, filesystem APIs, and database columns from pathological inputs.
const SanitizeFilenameInputBound = 4 * MaxFilenameBytes
SanitizeFilenameInputBound caps the *input* length before any of the O(n) sanitisation passes (control-byte strip, replacer, interior-ext split-and-join) run. A multipart Content-Disposition filename is a MIME-header value and is NOT counted against ParseMultipartForm's maxMemory, so without this guard an attacker can ship a multi-MiB filename (bounded only by the stdlib's 10 MiB per-header cap) that amplifies into tens of MB of transient allocation and >100ms of CPU per request — strings.Split on a ~9 MiB all-dots name produces a ~9.4M-element slice. The bound is a generous multiple of MaxFilenameBytes so legitimate names with escaped / multibyte sequences survive untouched; the final MaxFilenameBytes cap still applies after sanitisation.
Variables ¶
var ErrNotFound = errNotFound{}
ErrNotFound is wrapped by Get when the requested key doesn't exist. Callers can match on this or on errors.Is(err, os.ErrNotExist) — the returned error wraps both so existing code continues to work.
Functions ¶
func Handler ¶
func Handler(cfg Config) http.HandlerFunc
Handler returns an http.HandlerFunc that processes multipart file uploads. It expects a single file in the "file" form field. On success it responds with 200 and JSON Metadata.
func SanitizeFilename ¶
SanitizeFilename removes path separators, null bytes, and other dangerous characters from a filename to prevent path traversal attacks. It also neutralises double-extension smuggling — e.g. `shell.php.jpg` becomes `shell_php.jpg` — so a misconfigured web server can't be tricked into executing a hidden interior extension.
Control bytes (CR, LF, TAB, anything < 0x20) are dropped so a logged filename can't escape its log line via injected newlines or terminal control sequences. The final result is truncated to MaxFilenameBytes (preserving the extension) so an attacker can't ship a 10 MB filename.
func ValidateExt ¶
ValidateExt checks that the file extension is in the allowed list. Extensions are compared case-insensitively without the leading dot. If allowed is empty, all extensions are permitted.
func ValidateMIME ¶
func ValidateMIME(file io.ReadSeeker, allowed []string) error
ValidateMIME reads the first 512 bytes from file to detect MIME type, checks it against the allowed list, then resets the reader. If allowed is empty, all MIME types are permitted.
func ValidateSize ¶
ValidateSize checks that the file size does not exceed max. A max of 0 means no limit.
Types ¶
type Config ¶
type Config struct {
MaxSize int64 // Maximum file size in bytes (0 = no limit)
AllowedTypes []string // MIME type whitelist (empty = allow all)
AllowedExts []string // Extension whitelist (empty = allow all)
Storage Storage // Storage backend implementation
}
Config holds configuration for the upload handler.
type LocalStorage ¶
type LocalStorage struct {
// contains filtered or unexported fields
}
LocalStorage implements Storage using the local filesystem.
func NewLocalStorage ¶
func NewLocalStorage(baseDir string) *LocalStorage
NewLocalStorage creates a LocalStorage that saves files under baseDir.
func (*LocalStorage) Delete ¶
func (s *LocalStorage) Delete(_ context.Context, key string) error
Delete removes the file at key from the local filesystem.
func (*LocalStorage) Get ¶
func (s *LocalStorage) Get(_ context.Context, key string) (io.ReadCloser, error)
Get opens the file at key from the local filesystem for reading.
Returns ErrNotFound (wrapping os.ErrNotExist) when the key is missing — callers can match on os.ErrNotExist or upload.ErrNotFound without parsing the message. Other errors are returned with the absolute filesystem path stripped, so a 500 propagated to an end user doesn't disclose where the data lives.
type Metadata ¶
type Metadata struct {
OriginalName string `json:"original_name"`
Size int64 `json:"size"`
MimeType string `json:"mime_type"`
UploadedAt time.Time `json:"uploaded_at"`
Key string `json:"key"`
}
Metadata holds information about an uploaded file.
type Storage ¶
type Storage interface {
Save(ctx context.Context, key string, r io.Reader) error
Delete(ctx context.Context, key string) error
Get(ctx context.Context, key string) (io.ReadCloser, error)
Exists(ctx context.Context, key string) (bool, error)
}
Storage defines the interface for file storage backends.