Documentation / @zerotal/core / http / UploadedFile
Class: UploadedFile
Defined in: http/UploadedFile.ts:46
Wraps a browser File from a multipart form upload and adds Zerotal-native
validation and one-line storage.
Instances are created by HttpContext.file() / HttpContext.files().
Example
async updateAvatar(ctx: HttpContext): HttpResponse {
const avatar = await ctx.file('avatar');
if (!avatar?.isValid({ maxSize: 2 * 1024 * 1024, mimes: ['image/jpeg', 'image/png'] })) {
return redirect().back().withErrors({ avatar: 'Must be a JPEG or PNG under 2 MB.' });
}
const path = await avatar.store('avatars', Storage.disk());
await ctx.user!.update({ avatarPath: path });
return redirect('/profile').withSuccess('Avatar updated!');
}
Constructors
Constructor
new UploadedFile(
file):UploadedFile
Defined in: http/UploadedFile.ts:49
Parameters
file
File
Returns
UploadedFile
Accessors
originalName
Get Signature
get originalName():
string
Defined in: http/UploadedFile.ts:54
Original filename as sent by the browser (may be untrusted — sanitise before display).
Returns
string
mimeType
Get Signature
get mimeType():
string
Defined in: http/UploadedFile.ts:59
MIME type as reported by the browser (e.g. 'image/jpeg').
Returns
string
size
Get Signature
get size():
number
Defined in: http/UploadedFile.ts:64
File size in bytes.
Returns
number
Methods
extension()
extension():
string
Defined in: http/UploadedFile.ts:76
Lowercase extension derived from the original filename, without the dot, stripped to
[a-z0-9].
The filename is client-supplied, so anything outside that set — a null byte, a slash,
a %00 — is a smuggling attempt rather than an extension. This is what the client
called the file; store does not trust it when naming what it writes.
Returns
string
isValid()
isValid(
options?):boolean
Defined in: http/UploadedFile.ts:89
Returns true when the file satisfies all given rules.
Pass no options to simply confirm a file was received.
Parameters
options?
Returns
boolean
Example
avatar.isValid({ maxSize: 5 * 1024 * 1024, mimes: ['image/jpeg', 'image/png'] })
bytes()
bytes():
Promise<Uint8Array<ArrayBufferLike>>
Defined in: http/UploadedFile.ts:99
Read the file as raw bytes.
Returns
Promise<Uint8Array<ArrayBufferLike>>
text()
text():
Promise<string>
Defined in: http/UploadedFile.ts:104
Read the file as a UTF-8 string.
Returns
Promise<string>
store()
store(
directory,disk,filename?):Promise<string>
Defined in: http/UploadedFile.ts:134
Write the file to a disk and return the stored path.
The filename defaults to <uuid>.<ext> — predictable, safe, and collision-free.
Pass filename to override it.
Both the extension and the stored Content-Type come from the file's own bytes,
not from the client. The multipart part's Content-Type and the filename's suffix
are claims the uploader controls: avatar sent as x.html with text/html would
otherwise be written as avatars/<uuid>.html and served as HTML, which is stored XSS
on whatever origin serves the disk. Bytes that match no known format are stored as
application/octet-stream with a .bin extension, which downloads rather than
executes. See sniffContentType.
An explicit filename is taken at face value — you chose it, so it is yours to get
right — but the sniffed content type still applies.
Parameters
directory
string
Target directory, e.g. 'avatars' or 'uploads/docs'
disk
Any StorageDisk — typically Storage.disk() or Storage.disk('s3')
filename?
string
Optional override; defaults to <uuid>.<sniffed-ext>
Returns
Promise<string>
The stored path, e.g. 'avatars/f47ac10b.jpg'
Example
const path = await avatar.store('avatars', Storage.disk());
const path = await avatar.store('docs', Storage.disk('s3'), 'terms-v2.pdf');
detectType()
detectType():
Promise<SniffedType>
Defined in: http/UploadedFile.ts:156
What the file's own bytes say it is, ignoring both client-supplied claims.
Use it to reject an upload whose contents disagree with its declared type — a .jpg
whose bytes are a ZIP, say — before storing it.
Returns
Promise<SniffedType>
The detected content type, canonical extension, and whether detection succeeded.
Example
const { contentType, recognised } = await avatar.detectType();
if (!recognised || !contentType.startsWith('image/')) return badRequest('Not an image.');
storeAndGetUrl()
storeAndGetUrl(
directory,disk,filename?):Promise<string>
Defined in: http/UploadedFile.ts:167
Store the file and immediately return its public URL.
Parameters
directory
string
disk
filename?
string
Returns
Promise<string>
Example
const url = await avatar.storeAndGetUrl('avatars', Storage.disk('s3'));
await user.update({ avatarUrl: url });
fake()
staticfake(name?,options?):UploadedFile
Defined in: http/UploadedFile.ts:191
Build an UploadedFile from scratch, for unit-testing the code that
receives one without going through a multipart request.
The contents are arbitrary bytes, so the type this reports is the type you
declare — which is the point for a size or extension check, but means
detectType and store will see unrecognised bytes and fall
back to application/octet-stream. When the test turns on what the bytes
actually are, build a real one with fakeFile from @zerotal/testing and
pass it here.
Parameters
name?
string = "file.txt"
Filename the "client" sent.
options?
type?
string
MIME type to report from mimeType.
size?
number
Size in bytes; the file is padded to it.
content?
string | Uint8Array<ArrayBuffer> | File
Exact contents, overriding size.
Returns
UploadedFile
Example
const file = UploadedFile.fake('avatar.png', { type: 'image/png', size: 1024 });
expect(file.isValid({ maxSize: 2048, mimes: ['image/png'] })).toBe(true);