An upload combines several different claims: the user's display filename, the submitted bytes, a declared media type and an eventual storage location. These claims have different authority. A filename can be useful metadata without being allowed to select a directory or overwrite another user's file. A media-type header is a client assertion, not proof of the bytes' content.
This lesson constructs an upload plan in memory. It writes no files, opens no archives and parses no images. The accepted format is deliberately small: nonempty UTF-8 text of at most 1024 bytes with no NUL. A passing check establishes that narrow text policy; it does not establish that an arbitrary file is harmless or malware-free.
The display filename is a single portable ASCII leaf with a lowercase .txt suffix, a leading letter or digit and a total length of 5–64 characters. The remaining characters may be ASCII letters, digits, dots, underscores and hyphens. Paths, backslashes, colons and header separators are rejected rather than repaired. This restricted grammar is the exercise's policy, not a claim that international filenames are inherently unsafe.
Storage identity comes from a separate server-assigned ID of 32 lowercase hexadecimal characters. The storage key is always uploads/<id>.txt; changing the display filename cannot change that key. The ID is supplied by the trusted fixture. A deployed service needs secure ID allocation, collision handling, access checks and storage permissions. Validation of an ID's spelling does not establish that its source is trustworthy or that it is unpredictable.
text = "é" * 512
print(len(text), len(text.encode("utf-8")))
key = "uploads/" + "a" * 32 + ".txt"
print(key.startswith("uploads/"))The character count is 512 and the byte count is 1024. A transport or storage limit normally measures bytes, so the build checks byte length before decoding. Invalid UTF-8 is rejected, not replaced with a replacement character. Replacing malformed input would conceal a format error and change the submitted content.
For a download key, a string prefix is insufficient. uploads/../private.txt begins with uploads/ but is not a member of the permitted key grammar. The lab accepts only the exact canonical form produced by the storage-key policy. It does not URL-decode inputs or attempt to normalize arbitrary paths into acceptance.
This canonical key model avoids using user filenames as filesystem paths, but it is not a filesystem containment proof. A real service must account for symlinks, races between checking and opening, object-store permissions and the actual storage API. Authorization is required for downloads even when a key is valid. Serving UTF-8 text also needs correct response metadata and output handling; a text upload can contain HTML-looking characters. Serving it as an attachment with an appropriate media type and nosniff, or encoding it in a view, is a separate integration decision.
The tests include traversal-shaped metadata, malformed bytes, exact size endpoints, shared display names with different storage IDs and a prefix-looking invalid key. Explain what each rejection establishes, and keep the untested filesystem and browser responsibilities visible when moving beyond this local planner.