Re-derive a useful content type for uploaded documents, in Swift. Zero dependencies beyond the standard library, no network calls.
Many storage backends and upload paths return a content type of
application/octet-stream (or nothing at all) for files they could not sniff.
When that blob is later served to a browser, an octet-stream is treated as a
download instead of being rendered inline, so a PDF the user expects to preview
becomes a forced save dialog.
MimeFix fixes that deterministically. It re-derives the real content type from the filename extension, and optionally confirms it from the leading "magic" bytes of the content, preferring the bytes when a file was renamed to lie about its type.
Add MimeFix to your Package.swift dependencies:
.package(url: "https://github.com/maxed-oss/mimefix.git", from: "0.1.0")Then add "MimeFix" to your target's dependencies:
.target(name: "YourApp", dependencies: ["MimeFix"])import MimeFix
// The common case: storage handed you an octet-stream for a real PDF.
let result = MimeFix.normalize(
reported: "application/octet-stream",
filename: "invoice.pdf"
)
result.contentType // "application/pdf"
result.changed // true
result.renderableInline // true -> safe to serve inline
// A specific, trustworthy reported type is kept untouched.
MimeFix.normalize(reported: "image/png", filename: "x.bin").contentType
// "image/png"
// Magic bytes win over a misleading extension (a PNG named ".pdf").
let png: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
MimeFix.normalize(
reported: nil,
filename: "screenshot.pdf",
magicBytes: png
).contentType
// "image/png"
// Direct lookups are available too.
MimeFix.contentType(forExtension: "statement.CSV") // "text/csv"
MimeFix.contentType(forMagicBytes: [0x25, 0x50, 0x44, 0x46]) // "application/pdf"normalize(reported:filename:magicBytes:) follows a small, predictable set of
rules:
- If
reportedis specific and trustworthy (anything other than a generic placeholder), keep it. MimeFix never second-guesses a server that already knows the type. A; charset=...parameter is ignored when judging this. - Otherwise, derive the type. Magic bytes are preferred over the extension, so a renamed file is served as what it actually is. The extension is used when no bytes are provided or the bytes are unrecognized.
- If nothing can be determined, fall back to
application/octet-streamand flag the result as not inline-renderable, so callers always get a valid type and a clear signal.
The Result tells you what happened:
| Field | Meaning |
|---|---|
contentType |
The content type to use going forward. |
changed |
True when MimeFix replaced the caller-supplied type. |
renderableInline |
True when the result is no longer an opaque octet-stream and is safe to render inline. |
The extension table and signature set cover the document, image, data, archive,
and media formats that flow through real upload pipelines. They are intentionally
not a full IANA mirror: the goal is to recover inline-renderable types reliably,
not to enumerate every registered type. Adding an entry is a one-line change to
Tables.swift.
ZIP-container Office formats (.docx, .xlsx, .pptx) all share the ZIP magic
signature, so byte-sniffing reports them as application/zip; the filename
extension is the better source for the specific Office type, and normalize
uses it accordingly.
swift testMIT. See LICENSE.