Skip to main content

Command Palette

Search for a command to run...

Arabic Filenames in Production Storage

A production guide to safe uploads, Unicode-aware lookup, portable object keys, and correct Arabic download names.

Updated
9 min readView as Markdown
Arabic Filenames in Production Storage
H
I have lead the Engineering for multiple startups in UAE. I also have my own agency qualascend.com.

An Arabic filename can pass through a browser, an API, object storage, a database, a CDN, and a download response. Each layer may accept Unicode, yet the complete path can still fail. A name may become impossible to find after normalization, be interpreted as a path, exceed a byte limit, or download as percent signs and hex digits.

The reliable design is to stop asking one string to perform several jobs. Preserve the filename the user supplied for display, derive a normalized value for comparison and search, and generate a separate opaque key for storage. Validation and download handling then become explicit boundaries rather than side effects of an object-store SDK.

Cover: SultanByte editorial artwork.

Treat the uploaded name as untrusted metadata

The filename in a multipart upload comes from the client. It is not proof of file type, a safe local path, or a suitable object key. Start by decoding the request strictly and rejecting malformed text. Then extract only the final filename component. Both / and \ need attention because a value created on one operating system may be processed on another.

Reject NUL, carriage return, line feed, other disallowed controls, and path-only values such as . or ... Put a limit on the UTF-8 byte length as well as the visible length. Byte length matters because Arabic characters usually occupy more than one byte in UTF-8, and two names with the same character count can consume different storage-key budgets.

Validate the extension only after decoding and basename extraction. Use an allowlist tied to the product's actual needs, not a long denylist. A name such as صورة.jpg.php must not pass because it contains .jpg somewhere. The OWASP File Upload Cheat Sheet also warns that the client-supplied Content-Type is easy to spoof. Check the detected media type and file signature, while recognizing that neither is sufficient alone.

Filename checks are only one layer. Enforce request and decompressed-size limits, authenticate the uploader, authorize access to the destination, and scan or sandbox content where the risk warrants it. Store uploads away from a directly executable web path. These controls apply equally to Arabic and ASCII filenames.

A practical filename policy can allow Arabic letters, spaces, punctuation, and combining marks without allowing the name to control storage layout. The goal is not to force every customer into [A-Za-z0-9._-]. It is to keep a human label human while narrowing the machine-facing identifier.

Give the object a key that users never see

Generate an opaque storage key such as:

tenant/8f/8f3b6d7e-2e0c-4f52-a798-2f17fcb0d171

Keep the original extension only if an integration genuinely needs it. Do not build the key from the uploaded name, and do not use transliteration as an identity scheme. Two people can upload تقرير.pdf; retries can repeat the same name; canonically equivalent Unicode sequences can look identical while remaining byte-distinct.

A database record should connect the representations:

storage_key       generated opaque identifier
display_name      exact accepted value from the upload
name_nfc          NFC-derived value for comparison and search
media_type        server-validated type
size_bytes        measured content size

The opaque key prevents accidental overwrite and removes user-controlled separators from routing. The display name remains available to the interface and download endpoint. The normalized field makes equality and search policy visible instead of delegating it to a database collation or provider listing order.

Do not assume object metadata is a universal home for the display name. Azure's documented metadata names and values must be ASCII, for example. A database record is more portable. If metadata is used as a cache, define its encoding and keep the database authoritative.

Arabic filename production pipeline separating the user-visible name, normalized lookup value, opaque storage key, and HTTP download filename.

Source: Unicode UAX #15, OWASP File Upload Cheat Sheet, RFC 6266, RFC 8187, and AWS, Azure, and Google Cloud object-naming documentation. Credit: SultanByte editorial artwork.

Normalize for lookup, preserve for display

Unicode allows canonically equivalent text to have different code-point sequences. Unicode Standard Annex #15 defines NFC as canonical decomposition followed by canonical composition. Normalizing two inputs to the same form allows binary comparison to recognize canonical equivalence.

Use NFC for the derived comparison and search field. Keep the accepted original in display_name and return it to the user. This avoids quietly rewriting their label while still preventing a lookup from missing an equivalent sequence. It also lets support tools show both the rendered name and code points when a failure is difficult to reproduce.

NFC is not Arabic search stemming and should not be presented as one. It does not decide whether diacritics should be ignored or whether visually similar letters should match. Those are product search rules that belong in a separate, versioned search pipeline. Do not use a broader search key for authorization, ownership checks, or object identity.

Normalization also must not select the object key. If two uploads have NFC-equivalent display names, both can remain separate objects because their generated keys differ. The application may warn about a duplicate within the same folder-like view, but it should not overwrite content merely because display labels compare equal.

AWS, Azure, and GCS do not name objects alike

Cloud providers all support broad Unicode object names, but their limits and troublesome cases differ.

Amazon S3 object keys are UTF-8 sequences up to 1,024 bytes, including prefixes and delimiters. S3 is flat even though consoles present slash-delimited prefixes as folders. AWS notes that some characters need special handling, period-only path segments can behave inconsistently across tools, and the console strips trailing periods when downloading objects whose keys end that way. Keys are sorted lexicographically by UTF-8 bytes, so provider listing order should not be used as Arabic alphabetical order.

Azure Blob Storage names are case-sensitive and can be 1 to 1,024 characters. Reserved URL characters must be escaped. Azure advises against names or path segments ending in a dot, slash, or backslash. Its segment limit differs by namespace mode: up to 254 segments without hierarchical namespace and 63 with it, including account and container segments in the latter count. Container names follow a much narrower lowercase DNS-style rule, so an Arabic blob name does not imply an Arabic container name.

Google Cloud Storage object names can contain valid Unicode, but not carriage return or line feed. A flat-namespace name is 1 to 1,024 UTF-8 bytes. With hierarchical namespace enabled, the folder-name portion and base-name portion are each limited to 512 bytes. Google also recommends avoiding XML-illegal controls and characters that its command-line tools interpret as versions or wildcards. Its documentation notes that object names can appear in URLs and listings, which is another reason not to put sensitive information in a key.

These differences make a lowest-common-denominator key attractive. A generated ASCII key with controlled slashes avoids most provider-specific naming traps and makes migration less surprising. Provider limits still belong in integration tests because prefixes added by the application count against some limits.

Build download headers from the display name

The download endpoint should look up the object by its opaque key, authorize the request, and then construct a response using the stored display name. For a forced download, RFC 6266 defines Content-Disposition: attachment and the filename and filename* parameters.

Send both parameters. Use a conservative ASCII fallback in filename, followed by the real UTF-8 name in filename*:

Content-Disposition: attachment; filename="report.pdf"; filename*=UTF-8''%D8%AA%D9%82%D8%B1%D9%8A%D8%B1-%D8%A7%D9%84%D8%B1%D8%A8%D8%B9-%D8%A7%D9%84%D8%A3%D9%88%D9%84.pdf

That extended value represents تقرير-الربع-الأول.pdf. RFC 6266 says recipients that understand both should prefer filename*, while the ASCII filename remains a fallback. RFC 8187 specifies the extended parameter format and requires producers to use UTF-8, then percent-encode bytes that are not allowed directly.

Do not paste an unvalidated original into a header. Reject CR and LF before storage to prevent header injection. Strip path components, preserve only an approved extension, and generate the header with a library that correctly quotes the ASCII fallback. Avoid hand-built encoding that turns spaces into +; RFC 8187 uses percent encoding, so a space is %20.

The download name is advisory. RFC 6266 warns recipients not to trust path information or unsafe extensions. The server should still send an accurate Content-Type, apply its validated extension policy, and avoid reflecting names such as . or ...

Test the complete round trip

A useful test matrix crosses filename class with every boundary, rather than testing one upload form in isolation.

  1. تقرير.pdf: accept it when the content passes the PDF policy. Store it under an opaque key while preserving the exact display value and its NFC-derived comparison value. The download must restore the Arabic name through filename*.
  2. Canonically equivalent sequences: decode both safely. Their NFC fields should compare equal, but their storage keys remain distinct. Each original display value must survive the round trip.
  3. صورة.jpg.php: reject it under the final-extension policy. No object should be committed and no download should be generated.
  4. ../فاتورة.pdf and ..\فاتورة.pdf: either reject the request or extract the basename, according to the documented policy. Traversal text must never reach the storage key or download name.
  5. A name containing CR, LF, or NUL: reject it before storage and header generation.
  6. An Arabic name near the configured limit: make the decision using UTF-8 bytes and the actual provider mode. Include application prefixes in the storage assertion, then verify that the full accepted display name survives.
  7. Duplicate تقرير.pdf uploads: accept both if the product permits duplicate labels. Require different opaque keys and verify that each download returns the correct content.
  8. Spaces, quotes, %, and #: apply the documented policy consistently. Never interpolate the display name into the object key, and verify HTTP quoting and percent encoding.
  9. Mismatched extension, MIME type, and signature: reject or quarantine the upload. It must not become a published object or an offered download.

Run the matrix through the browser, mobile client, API gateway, upload worker, database, object store, CDN, and real download clients. Include every provider and namespace mode used in production. Verify the response header bytes, saved filename, retrieved content hash, authorization result, and behavior on retry.

The production contract is simple to state: the user's accepted filename remains available for display, the application compares an NFC-derived value, the object store receives an unrelated opaque key, and the download response reconstructs the safe human name with both HTTP filename parameters. Keeping those roles separate is what makes Arabic filenames routine rather than fragile.