brazier/upload
Types
A file type recognised by its signature (magic bytes).
pub type FileKind {
Png
Jpeg
Gif
Webp
Pdf
}
Constructors
-
Png -
Jpeg -
Gif -
Webp -
Pdf
A parsed multipart form: text fields and validated files, in order.
pub type MultipartForm {
MultipartForm(
fields: List(#(String, String)),
files: List(UploadedFile),
)
}
Constructors
-
MultipartForm( fields: List(#(String, String)), files: List(UploadedFile), )
A file stored by save.
pub type StoredFile {
StoredFile(
path: String,
stored_name: String,
original_filename: String,
content_type: String,
size: Int,
)
}
Constructors
-
StoredFile( path: String, stored_name: String, original_filename: String, content_type: String, size: Int, )Arguments
- path
-
Full path of the stored file.
- stored_name
-
The random server-side name (
<32 hex>.<ext>); pass it toserve. - original_filename
-
The client’s display name (metadata only).
- content_type
-
The sniffed MIME type.
- size
-
Size in bytes.
Upload configuration.
pub type UploadConfig {
UploadConfig(
max_size: Int,
allowed_types: List(String),
max_request_bytes: Int,
)
}
Constructors
-
UploadConfig( max_size: Int, allowed_types: List(String), max_request_bytes: Int, )Arguments
- max_size
-
Maximum size of one file in bytes (default 10 MB).
- allowed_types
-
Allowed MIME types. Each must be a supported kind (see
kind_mime); an empty list allows nothing. Default: every supported kind. - max_request_bytes
-
Maximum size of the whole multipart request body in bytes, checked against Content-Length before the body is read (default 11 MB).
Upload errors.
pub type UploadError {
FileTooLarge(max: Int, actual: Int)
TypeNotAllowed(content_type: String, allowed: List(String))
NoFile
ParseError(reason: String)
UnrecognizedContent(declared: String)
ContentTypeMismatch(declared: String, detected: String)
InvalidFilename(reason: String)
NotMultipart(content_type: String)
RequestTooLarge(max: Int, actual: Int)
BodyReadError(reason: String)
StorageError(reason: String)
InvalidStoredName(name: String)
InvalidConfig(reason: String)
}
Constructors
-
FileTooLarge(max: Int, actual: Int)A file exceeds max size.
-
TypeNotAllowed(content_type: String, allowed: List(String))The sniffed type is not in the allow-list.
-
NoFileNo file was provided.
-
ParseError(reason: String)The multipart body is malformed.
-
UnrecognizedContent(declared: String)The file’s bytes match no supported signature.
-
ContentTypeMismatch(declared: String, detected: String)The declared Content-Type disagrees with the file’s bytes.
-
InvalidFilename(reason: String)The client’s filename cannot be used even as metadata.
-
NotMultipart(content_type: String)The request is not multipart/form-data.
-
RequestTooLarge(max: Int, actual: Int)The declared request body exceeds
max_request_bytes. -
BodyReadError(reason: String)The request body could not be read.
-
StorageError(reason: String)Storing or reading a file failed.
-
InvalidStoredName(name: String)A stored name is not one
saveproduced. -
InvalidConfig(reason: String)The upload configuration is invalid.
An uploaded file, as parsed from a multipart request.
pub type UploadedFile {
UploadedFile(
filename: String,
content_type: String,
data: BitArray,
size: Int,
field_name: String,
)
}
Constructors
-
UploadedFile( filename: String, content_type: String, data: BitArray, size: Int, field_name: String, )Arguments
- filename
-
The client’s filename, reduced to a display name (no directories, no control characters, at most 255 characters). Metadata only: never use it as a path.
- content_type
-
The Content-Type the client declared for the part. Not trusted:
validatecompares it with the sniffed type. - data
-
File contents as bytes.
- size
-
File size in bytes.
- field_name
-
The form field name the file was sent under.
Values
pub fn default_config() -> UploadConfig
Default upload config: 10 MB per file, 11 MB per request, every supported kind allowed.
pub fn error_status(error: UploadError) -> Int
The HTTP status that best describes an upload error.
pub fn extension(filename: String) -> option.Option(String)
Get the file extension from a filename, lowercased in ASCII. File
extensions in type tables are ASCII; a character outside ASCII is kept
as it is ("x.\u{212A}B" gives "\u{212A}b", not "kb"), so such an
extension never equals an ASCII one.
pub fn file(
form: MultipartForm,
field_name: String,
) -> Result(UploadedFile, UploadError)
The first file sent under field_name.
pub fn kind_extension(kind: FileKind) -> String
The extension save gives a stored file of this kind.
pub fn parse_multipart(
body: BitArray,
content_type: String,
config: UploadConfig,
) -> Result(MultipartForm, UploadError)
Parse an already-read multipart/form-data body, validating every file.
pub fn read_multipart(
req: request.Request(server.Connection),
config: UploadConfig,
) -> Result(MultipartForm, UploadError)
Read and parse a multipart/form-data request, validating every file.
The Content-Type is checked before the body is touched, and the declared
Content-Length against max_request_bytes before any byte is read.
pub fn save(
file: UploadedFile,
directory: String,
) -> Result(StoredFile, UploadError)
Store a file under a random name in directory. The file’s content must
be a supported kind (it gives the extension). The file is created
exclusively, so nothing is ever overwritten. Call validate (or use
read_multipart) first to apply the allow-list and size limit.
pub fn serve(
directory: String,
stored_name: String,
download_name: String,
) -> Result(response.Response(server.ResponseBody), UploadError)
Serve a stored file as a download. stored_name must be a name save
produced (anything else, including traversal, is refused); the response
type comes from its extension, never from the request. The file is
always an attachment, with nosniff and a sandboxing CSP, so a browser
never renders it inline. download_name is the name the browser offers
(typically StoredFile.original_filename); it is reduced to a display
name and encoded per RFC 6266, and one with no usable name is refused.
pub fn sniff(data: BitArray) -> Result(FileKind, Nil)
Identify a file by its leading bytes.
pub fn supported_kinds() -> List(FileKind)
Every supported kind, in signature-check order.
pub fn validate(
file: UploadedFile,
config: UploadConfig,
) -> Result(UploadedFile, UploadError)
Validate an uploaded file: size, recognised content, declared type agreeing with the content, and the sniffed type in the allow-list.
pub fn validate_config(
config: UploadConfig,
) -> Result(Nil, UploadError)
Check a configuration: positive limits and an allow-list of supported MIME types only (a type that cannot be sniffed could never be accepted, so listing one is a configuration bug).
pub fn with_allowed_types(
config: UploadConfig,
types: List(String),
) -> UploadConfig
Set the allowed MIME types (each must be a supported kind).
pub fn with_max_request_bytes(
config: UploadConfig,
bytes: Int,
) -> UploadConfig
Set the maximum size of the whole multipart request body.
pub fn with_max_size(
config: UploadConfig,
bytes: Int,
) -> UploadConfig
Set the maximum size of one file.