Checking uploads¶
In an app: the example. Every class and function: the API reference.
The name and the Content-Type of an upload are whatever the client chose: an HTML page sent as
cat.png would be served back as HTML. FastAPIUploads checks the file itself, with nothing but
the standard library:
from fastapi_alcazaba.uploads import IMAGES, CheckedFile, FastAPIUploads
uploads = FastAPIUploads(types=IMAGES, max_size=5 * 1024 * 1024, max_pixels=25_000_000)
@app.post("/avatars")
async def avatar(upload: Annotated[CheckedFile, Depends(uploads)]) -> dict[str, str]:
name = f"{uuid4()}{upload.extension}" # never the client's name
await storage.save(name, upload.file.file, content_type=upload.content_type)
return {"name": name}
As a dependency it reads the form field file; for other fields or several files, call
await uploads.check(file). It returns a CheckedFile: the UploadFile read back to its start,
the content_type found in its bytes, its size, its width and height (images) and a safe
filename.
- The real type (
sniff(data)): PNG, JPEG, GIF, WebP, PDF, ZIP, GZIP, MP4 and QuickTime from their signatures; Word, Excel and PowerPoint documents from the content types their[Content_Types].xmldeclares (as Office reads them), with the macro-enabled ones (.docm,.xlsm,.pptm) told apart, and anything ambiguous reported as a plain ZIP; UTF-8 text without control characters (text/plain: CSV is text, it can't be told apart). Text that starts with a tag is HTML, SVG or XML, never plain text, and SVG is never an image: all three can carry scripts.typesmust come fromKNOWN_TYPES(IMAGES,OFFICE_DOCUMENTS...), so a typo likeimage/jpgfails at start-up. Store and serve files with the detected type, never the client's, plusX-Content-Type-Options: nosniff(SecurityHeadersMiddleware). - Size: counted while the file is read in chunks, whatever
UploadFile.sizesays; 413 beyondmax_size, 415 for a type that isn't allowed, 422 for an empty file. By the time an endpoint runs the multipart parser has stored the whole request already, so limit it withBodySizeLimitMiddlewareon the route as well. - Decompression bombs: the width and height of PNG, JPEG (however much metadata comes before
the frame), GIF and WebP images are read from their headers (
image_size(data)) without decoding them; more thanmax_pixels(40 million by default) gets a 413, and an image whose dimensions can't be read a 415. - Safe names (
safe_filename(name)): NFKC-normalised, only the last path component, without control or invisible characters (such as the right-to-left override that showsexe.txtastxt.exe),<>:"|?*replaced, without leading dots or dashes,_in front of the names Windows reserves (CON,NUL,COM1...), and at most 255 bytes, keeping the extension. For display andContent-Disposition; store files under names of your own (CheckedFile.extension). - What it doesn't do: look inside the files. A PDF can carry scripts, Office documents macros, archives may expand to far more than their size, and the frames of a GIF aren't counted. Allow HTML, SVG or XML only to serve them as attachments from another domain.