Skip to content

Commit

Permalink
FileMiddleware will provide correct mime type for non lowercase file …
Browse files Browse the repository at this point in the history
…extensions (#661)

* FileMiddleware will provide correct mime type for non lowercase file extensions

* update mailmap

* fix url init for swift 5.x on linux

* revert file extension lowercase in file middleware

* create MediaType.WellknownExtension for type safety and comparison of file extensions

* added test case for customized mime type/extension case insensitivity

* rename to FileExtension

* maintain existing api

* refactor reduce into merging for media type file extensions

* update contributors

* update test to only start server once, then loop assertions

* doc comments for the MediaType.FileExtension type

---------

Co-authored-by: Adam Fowler <[email protected]>
  • Loading branch information
mredig and adam-fowler authored Jan 29, 2025
1 parent 1c6298e commit a426cda
Show file tree
Hide file tree
Showing 6 changed files with 336 additions and 86 deletions.
3 changes: 2 additions & 1 deletion .mailmap
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ Nicholas Trienens <[email protected]> Nicholas Trienens <nick.trienens@mo
Adam Fowler <[email protected]> adam-fowler <[email protected]>
Adam Fowler <[email protected]> dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Adam Fowler <[email protected]> hummingbird-automation[bot] <168462326+hummingbird-automation[bot]@users.noreply.github.com>
Michael <[email protected]> Michael <[email protected]>
Michael Redig <[email protected]> Michael <[email protected]>
Michael Redig <[email protected]> Michael <[email protected]>
2 changes: 1 addition & 1 deletion CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ needs to be listed here.
- Joannis Orlandos <[email protected]>
- Jonathan Pulfer <[email protected]>
- Lorenzo Fiamingo <[email protected]>
- Michael <mredig@gmail.com>
- Michael Redig <juniper.fife_0b@icloud.com>
- Mirza Učanbarlić <[email protected]>
- Moritz Lang <[email protected]>
- Runar Hummelsund <[email protected]>
Expand Down
181 changes: 181 additions & 0 deletions Sources/Hummingbird/HTTP/MediaType+FileExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import HummingbirdCore

extension MediaType {
/// Type safe wrapper for file extensions. Additionally provides case insensitive comparison.
public struct FileExtension: RawRepresentable, ExpressibleByStringLiteral, Sendable, Hashable {

/// The raw file extension
public let rawValue: String

/// Initialize `FileExtension`
/// - Parameter rawValue: The raw, textual file extension
public init<S: StringProtocol>(_ rawValue: S) {
self.init(rawValue: String(rawValue))
}

/// Initialize `FileExtension`
/// - Parameter rawValue: The raw, textual file extension
public init(rawValue: String) {
self.rawValue = rawValue.lowercased()
}

/// Initialize `FileExtension` - `ExpressibleByStringLiteral` conformance
/// - Parameter value: The raw, textual file extension
public init(stringLiteral value: String) {
self.init(rawValue: value)
}
}
}

extension MediaType.FileExtension {
/// File extension for the aac format.
public static let aac: Self = "aac"
/// File extension for the abw format.
public static let abw: Self = "abw"
/// File extension for the arc format.
public static let arc: Self = "arc"
/// File extension for the azw format.
public static let azw: Self = "azw"
/// File extension for the bin format.
public static let bin: Self = "bin"
/// File extension for the bmp format.
public static let bmp: Self = "bmp"
/// File extension for the bz format.
public static let bz: Self = "bz"
/// File extension for the bz2 format.
public static let bz2: Self = "bz2"
/// File extension for the csh format.
public static let csh: Self = "csh"
/// File extension for the css format.
public static let css: Self = "css"
/// File extension for the csv format.
public static let csv: Self = "csv"
/// File extension for the doc format.
public static let doc: Self = "doc"
/// File extension for the docx format.
public static let docx: Self = "docx"
/// File extension for the eot format.
public static let eot: Self = "eot"
/// File extension for the epub format.
public static let epub: Self = "epub"
/// File extension for the gz format.
public static let gz: Self = "gz"
/// File extension for the gif format.
public static let gif: Self = "gif"
/// File extension for the htm format.
public static let htm: Self = "htm"
/// File extension for the html format.
public static let html: Self = "html"
/// File extension for the ico format.
public static let ico: Self = "ico"
/// File extension for the ics format.
public static let ics: Self = "ics"
/// File extension for the jar format.
public static let jar: Self = "jar"
/// File extension for the jpeg format.
public static let jpeg: Self = "jpeg"
/// File extension for the jpg format.
public static let jpg: Self = "jpg"
/// File extension for the js format.
public static let js: Self = "js"
/// File extension for the json format.
public static let json: Self = "json"
/// File extension for the jsonld format.
public static let jsonld: Self = "jsonld"
/// File extension for the mid format.
public static let mid: Self = "mid"
/// File extension for the midi format.
public static let midi: Self = "midi"
/// File extension for the mjs format.
public static let mjs: Self = "mjs"
/// File extension for the mp3 format.
public static let mp3: Self = "mp3"
/// File extension for the mp4 format.
public static let mp4: Self = "mp4"
/// File extension for the mpeg format.
public static let mpeg: Self = "mpeg"
/// File extension for the mpkg format.
public static let mpkg: Self = "mpkg"
/// File extension for the odp format.
public static let odp: Self = "odp"
/// File extension for the ods format.
public static let ods: Self = "ods"
/// File extension for the odt format.
public static let odt: Self = "odt"
/// File extension for the oga format.
public static let oga: Self = "oga"
/// File extension for the ogv format.
public static let ogv: Self = "ogv"
/// File extension for the ogx format.
public static let ogx: Self = "ogx"
/// File extension for the opus format.
public static let opus: Self = "opus"
/// File extension for the otf format.
public static let otf: Self = "otf"
/// File extension for the png format.
public static let png: Self = "png"
/// File extension for the pdf format.
public static let pdf: Self = "pdf"
/// File extension for the php format.
public static let php: Self = "php"
/// File extension for the ppt format.
public static let ppt: Self = "ppt"
/// File extension for the pptx format.
public static let pptx: Self = "pptx"
/// File extension for the rar format.
public static let rar: Self = "rar"
/// File extension for the rtf format.
public static let rtf: Self = "rtf"
/// File extension for the sh format.
public static let sh: Self = "sh"
/// File extension for the svg format.
public static let svg: Self = "svg"
/// File extension for the swf format.
public static let swf: Self = "swf"
/// File extension for the tar format.
public static let tar: Self = "tar"
/// File extension for the tif format.
public static let tif: Self = "tif"
/// File extension for the tiff format.
public static let tiff: Self = "tiff"
/// File extension for the ts format.
public static let ts: Self = "ts"
/// File extension for the ttf format.
public static let ttf: Self = "ttf"
/// File extension for the txt format.
public static let txt: Self = "txt"
/// File extension for the vsd format.
public static let vsd: Self = "vsd"
/// File extension for the wasm format.
public static let wasm: Self = "wasm"
/// File extension for the wav format.
public static let wav: Self = "wav"
/// File extension for the weba format.
public static let weba: Self = "weba"
/// File extension for the webm format.
public static let webm: Self = "webm"
/// File extension for the webp format.
public static let webp: Self = "webp"
/// File extension for the webmanifest format.
public static let webmanifest: Self = "webmanifest"
/// File extension for the woff format.
public static let woff: Self = "woff"
/// File extension for the woff2 format.
public static let woff2: Self = "woff2"
/// File extension for the xhtml format.
public static let xhtml: Self = "xhtml"
/// File extension for the xls format.
public static let xls: Self = "xls"
/// File extension for the xlsx format.
public static let xlsx: Self = "xlsx"
/// File extension for the xml format.
public static let xml: Self = "xml"
/// File extension for the zip format.
public static let zip: Self = "zip"
/// File extension for the 3gp format.
public static let threeGP: Self = "3gp"
/// File extension for the 3g2 format.
public static let threeG2: Self = "3g2"
/// File extension for the 7z format.
public static let sevenZ: Self = "7z"
}
159 changes: 83 additions & 76 deletions Sources/Hummingbird/HTTP/MediaType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ public struct MediaType: Sendable, CustomStringConvertible {
/// - Parameter extension: file extension
/// - Returns: media type
public static func getMediaType(forExtension extension: String) -> MediaType? {
getMediaType(forExtension: FileExtension(rawValue: `extension`))
}

/// Get media type from a file extension
/// - Parameter extension: file extension
/// - Returns: media type
public static func getMediaType(forExtension extension: FileExtension) -> MediaType? {
extensionMap[`extension`]
}

Expand Down Expand Up @@ -387,82 +394,82 @@ extension MediaType {
public static var multipartForm: Self { .init(type: .multipart, subType: "form-data") }

/// map from extension string to media type
static let extensionMap: [String: MediaType] = [
"aac": .audioAac,
"abw": .applicationAbiWord,
"arc": .applicationArc,
"azw": .applicationAmzKindleEBook,
"bin": .applicationBinary,
"bmp": .imageBmp,
"bz": .applicationBzip,
"bz2": .applicationBzip2,
"csh": .applicationCsh,
"css": .textCss,
"csv": .textCsv,
"doc": .applicationMsword,
"docx": .applicationDocx,
"eot": .applicationEot,
"epub": .applicationEpub,
"gz": .applicationGzip,
"gif": .imageGif,
"htm": .textHtml,
"html": .textHtml,
"ico": .imageIco,
"ics": .textICalendar,
"jar": .applicationJar,
"jpeg": .imageJpeg,
"jpg": .imageJpeg,
"js": .textJavascript,
"json": .applicationJson,
"jsonld": .applicationJsonLD,
"mid": .audioMidi,
"midi": .audioMidi,
"mjs": .textJavascript,
"mp3": .audioMpeg,
"mp4": .videoMp4,
"mpeg": .videoMpeg,
"mpkg": .applicationMpkg,
"odp": .applicationOdp,
"ods": .applicationOds,
"odt": .applicationOdt,
"oga": .audioOgg,
"ogv": .videoOgg,
"ogx": .applicationOgg,
"opus": .audioOpus,
"otf": .fontOtf,
"png": .imagePng,
"pdf": .applicationPdf,
"php": .applicationPhp,
"ppt": .applicationPpt,
"pptx": .applicationPptx,
"rar": .applicationRar,
"rtf": .applicationRtf,
"sh": .applicationSh,
"svg": .imageSvg,
"swf": .applicationSwf,
"tar": .applicationTar,
"tif": .imageTiff,
"tiff": .imageTiff,
"ts": .videoTs,
"ttf": .fontTtf,
"txt": .textPlain,
"vsd": .applicationVsd,
"wasm": .applicationWasm,
"wav": .audioWave,
"weba": .audioWebm,
"webm": .videoWebm,
"webp": .imageWebp,
"webmanifest": .applicationManifest,
"woff": .fontWoff,
"woff2": .fontWoff2,
"xhtml": .applicationXhtml,
"xls": .applicationXls,
"xlsx": .applicationXlsx,
"xml": .applicationXml,
"zip": .applicationZip,
"3gp": .video3gp,
"3g2": .video3g2,
"7z": .application7z,
static let extensionMap: [FileExtension: MediaType] = [
.aac: .audioAac,
.abw: .applicationAbiWord,
.arc: .applicationArc,
.azw: .applicationAmzKindleEBook,
.bin: .applicationBinary,
.bmp: .imageBmp,
.bz: .applicationBzip,
.bz2: .applicationBzip2,
.csh: .applicationCsh,
.css: .textCss,
.csv: .textCsv,
.doc: .applicationMsword,
.docx: .applicationDocx,
.eot: .applicationEot,
.epub: .applicationEpub,
.gz: .applicationGzip,
.gif: .imageGif,
.htm: .textHtml,
.html: .textHtml,
.ico: .imageIco,
.ics: .textICalendar,
.jar: .applicationJar,
.jpeg: .imageJpeg,
.jpg: .imageJpeg,
.js: .textJavascript,
.json: .applicationJson,
.jsonld: .applicationJsonLD,
.mid: .audioMidi,
.midi: .audioMidi,
.mjs: .textJavascript,
.mp3: .audioMpeg,
.mp4: .videoMp4,
.mpeg: .videoMpeg,
.mpkg: .applicationMpkg,
.odp: .applicationOdp,
.ods: .applicationOds,
.odt: .applicationOdt,
.oga: .audioOgg,
.ogv: .videoOgg,
.ogx: .applicationOgg,
.opus: .audioOpus,
.otf: .fontOtf,
.png: .imagePng,
.pdf: .applicationPdf,
.php: .applicationPhp,
.ppt: .applicationPpt,
.pptx: .applicationPptx,
.rar: .applicationRar,
.rtf: .applicationRtf,
.sh: .applicationSh,
.svg: .imageSvg,
.swf: .applicationSwf,
.tar: .applicationTar,
.tif: .imageTiff,
.tiff: .imageTiff,
.ts: .videoTs,
.ttf: .fontTtf,
.txt: .textPlain,
.vsd: .applicationVsd,
.wasm: .applicationWasm,
.wav: .audioWave,
.weba: .audioWebm,
.webm: .videoWebm,
.webp: .imageWebp,
.webmanifest: .applicationManifest,
.woff: .fontWoff,
.woff2: .fontWoff2,
.xhtml: .applicationXhtml,
.xls: .applicationXls,
.xlsx: .applicationXlsx,
.xml: .applicationXml,
.zip: .applicationZip,
.threeGP: .video3gp,
.threeG2: .video3g2,
.sevenZ: .application7z,
]
}

Expand Down
Loading

0 comments on commit a426cda

Please sign in to comment.