├── .codebeatignore ├── .gitignore ├── Sources └── Storage │ ├── Utilities │ ├── String+Bytes.swift │ ├── Scanner.swift │ ├── Trie.swift │ ├── DataURI.swift │ ├── S3.swift │ └── Mime.swift │ ├── PathBuilder.swift │ ├── NetworkDriver.swift │ ├── FileEntity.swift │ ├── Storage.swift │ └── Template.swift ├── .codecov.yml ├── .swiftlint.yml ├── Tests ├── LinuxMain.swift └── StorageTests │ ├── Utilities │ └── Expect.swift │ ├── PathBuilderTests.swift │ ├── FileEntityTests.swift │ ├── TemplateTests.swift │ └── AWSSignerTestSuite.swift ├── Package.swift ├── LICENSE ├── .circleci └── config.yml └── README.md /.codebeatignore: -------------------------------------------------------------------------------- 1 | Public/** 2 | Resources/Assets/** 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | .idea 4 | .DS_Store 5 | *.xcodeproj 6 | DerivedData/ 7 | Package.resolved 8 | .swiftpm 9 | -------------------------------------------------------------------------------- /Sources/Storage/Utilities/String+Bytes.swift: -------------------------------------------------------------------------------- 1 | extension String { 2 | var bytes: [UInt8] { 3 | return Array(self.utf8) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "0...100" 3 | ignore: 4 | - "Sources/Storage/Utilities" 5 | - "Sources/Storage/NetworkDriver.swift" 6 | - "Tests/" 7 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Sources 3 | function_body_length: 4 | warning: 60 5 | variable_name: 6 | min_length: 7 | warning: 2 8 | line_length: 100 9 | disabled_rules: 10 | - opening_brace 11 | colon: 12 | flexible_right_spacing: true 13 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import StorageTests 3 | 4 | XCTMain([ 5 | testCase(FileEntityTests.allTests), 6 | testCase(TemplateTests.allTests), 7 | testCase(PathBuilderTests.allTests), 8 | testCase(AWSSignerTestSuite.allTests) 9 | ]) 10 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.2 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Storage", 6 | products: [ 7 | .library( 8 | name: "Storage", 9 | targets: ["Storage"] 10 | ) 11 | ], 12 | dependencies: [ 13 | .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), 14 | ], 15 | targets: [ 16 | .target( 17 | name: "Storage", 18 | dependencies: [ 19 | "Vapor" 20 | ] 21 | ), 22 | .testTarget( 23 | name: "StorageTests", 24 | dependencies: ["Storage"] 25 | ) 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /Sources/Storage/PathBuilder.swift: -------------------------------------------------------------------------------- 1 | public protocol PathBuilder { 2 | func build(entity: FileEntity) throws -> String 3 | func generateFolder(for mime: String?) -> String? 4 | } 5 | 6 | public extension PathBuilder { 7 | func generateFolder(for mime: String?) -> String? { 8 | guard let mime = mime else { return nil } 9 | return mime.lowercased().hasPrefix("image") ? "images/original" : "data" 10 | } 11 | } 12 | 13 | public struct ConfigurablePathBuilder: PathBuilder { 14 | var template: Template 15 | 16 | public init(template: String) throws { 17 | self.template = try Template.compile(template) 18 | } 19 | 20 | public func build(entity: FileEntity) throws -> String { 21 | return try template.renderPath(for: entity, generateFolder) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2018 Nodes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Tests/StorageTests/Utilities/Expect.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | func expect( 4 | toThrow expectedError: E, 5 | file: StaticString = #file, 6 | line: UInt = #line, 7 | from closure: () throws -> ReturnType 8 | ) where E: Equatable { 9 | do { 10 | let _ = try closure() 11 | XCTFail("should have thrown", file: file, line: line) 12 | } catch let error as E { 13 | XCTAssertEqual(error, expectedError) 14 | } catch { 15 | XCTFail( 16 | "expected type \(type(of: expectedError)) got \(type(of: error))", 17 | file: file, 18 | line: line 19 | ) 20 | } 21 | } 22 | 23 | func expectNoThrow( 24 | file: StaticString = #file, 25 | line: UInt = #line, 26 | _ closure: () throws -> ReturnType 27 | ) { 28 | do { 29 | let _ = try closure() 30 | } catch { 31 | XCTFail("closure threw: \(error)", file: file, line: line) 32 | } 33 | } 34 | 35 | func expect( 36 | _ closure: () throws -> ReturnType, 37 | file: StaticString = #file, 38 | line: UInt = #line, 39 | toReturn expectedResult: ReturnType 40 | ) where ReturnType: Equatable { 41 | do { 42 | let result = try closure() 43 | XCTAssertEqual(result, expectedResult, file: file, line: line) 44 | } catch { 45 | XCTFail("closure threw: \(error)", file: file, line: line) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | MacOS: 4 | macos: 5 | xcode: "10.0.0" 6 | steps: 7 | - checkout 8 | - restore_cache: 9 | keys: 10 | - v5-spm-deps-{{ checksum "Package.swift" }} 11 | - run: 12 | name: Install CMySQL and CTLS 13 | command: | 14 | export HOMEBREW_NO_AUTO_UPDATE=1 15 | brew tap vapor/homebrew-tap 16 | brew install cmysql 17 | brew install ctls 18 | brew install libressl 19 | - run: 20 | name: Build and Run Tests 21 | no_output_timeout: 1800 22 | command: | 23 | swift package generate-xcodeproj --enable-code-coverage 24 | xcodebuild -scheme Storage-Package -enableCodeCoverage YES test | xcpretty 25 | - run: 26 | name: Report coverage to Codecov 27 | command: | 28 | bash <(curl -s https://codecov.io/bash) 29 | - save_cache: 30 | key: v5-spm-deps-{{ checksum "Package.swift" }} 31 | paths: 32 | - .build 33 | Linux: 34 | docker: 35 | - image: nodesvapor/vapor-ci:swift-4.2 36 | steps: 37 | - checkout 38 | - restore_cache: 39 | keys: 40 | - v6-spm-deps-{{ checksum "Package.swift" }} 41 | - run: 42 | name: Copy Package file 43 | command: cp Package.swift res 44 | - run: 45 | name: Build and Run Tests 46 | no_output_timeout: 1800 47 | command: | 48 | swift test -Xswiftc -DNOJSON 49 | - run: 50 | name: Restoring Package file 51 | command: mv res Package.swift 52 | - save_cache: 53 | key: v6-spm-deps-{{ checksum "Package.swift" }} 54 | paths: 55 | - .build 56 | workflows: 57 | version: 2 58 | build-and-test: 59 | jobs: 60 | - MacOS 61 | - Linux 62 | experimental: 63 | notify: 64 | branches: 65 | only: 66 | - master 67 | - develop 68 | -------------------------------------------------------------------------------- /Sources/Storage/Utilities/Scanner.swift: -------------------------------------------------------------------------------- 1 | struct Scanner { 2 | var pointer: UnsafePointer 3 | var elements: UnsafeBufferPointer 4 | // assuming you don't mutate no copy _should_ occur 5 | let elementsCopy: [Element] 6 | } 7 | 8 | extension Scanner { 9 | init(_ data: [Element]) { 10 | self.elementsCopy = data 11 | self.elements = elementsCopy.withUnsafeBufferPointer { $0 } 12 | 13 | self.pointer = elements.baseAddress! 14 | } 15 | } 16 | 17 | extension Scanner { 18 | func peek(aheadBy n: Int = 0) -> Element? { 19 | guard pointer.advanced(by: n) < elements.endAddress else { return nil } 20 | return pointer.advanced(by: n).pointee 21 | } 22 | 23 | /// - Precondition: index != bytes.endIndex. It is assumed before calling pop that you have 24 | @discardableResult 25 | mutating func pop() -> Element { 26 | assert(pointer != elements.endAddress) 27 | defer { pointer = pointer.advanced(by: 1) } 28 | return pointer.pointee 29 | } 30 | 31 | /// - Precondition: index != bytes.endIndex. It is assumed before calling pop that you have 32 | @discardableResult 33 | mutating func attemptPop() throws -> Element { 34 | guard pointer < elements.endAddress else { throw ScannerError.Reason.endOfStream } 35 | defer { pointer = pointer.advanced(by: 1) } 36 | return pointer.pointee 37 | } 38 | 39 | mutating func pop(_ n: Int) { 40 | for _ in 0.. { 63 | return baseAddress!.advanced(by: endIndex) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Storage/Utilities/Trie.swift: -------------------------------------------------------------------------------- 1 | class Trie { 2 | var key: UInt8 3 | var value: ValueType? 4 | 5 | var children: [Trie] = [] 6 | 7 | var isLeaf: Bool { 8 | return children.count == 0 9 | } 10 | 11 | convenience init() { 12 | self.init(key: 0x00) 13 | } 14 | 15 | init(key: UInt8, value: ValueType? = nil) { 16 | self.key = key 17 | self.value = value 18 | } 19 | } 20 | 21 | extension Trie { 22 | subscript(_ key: UInt8) -> Trie? { 23 | get { return children.first(where: { $0.key == key }) } 24 | set { 25 | guard let index = children.index(where: { $0.key == key }) else { 26 | guard let newValue = newValue else { return } 27 | children.append(newValue) 28 | return 29 | } 30 | 31 | guard let newValue = newValue else { 32 | children.remove(at: index) 33 | return 34 | } 35 | 36 | let child = children[index] 37 | guard child.value == nil else { 38 | print("warning: inserted duplicate tokens into Trie.") 39 | return 40 | } 41 | 42 | child.value = newValue.value 43 | } 44 | } 45 | 46 | func insert(_ keypath: [UInt8], value: ValueType) { 47 | insert(value, for: keypath) 48 | } 49 | 50 | func insert(_ value: ValueType, for keypath: [UInt8]) { 51 | var current = self 52 | 53 | for (index, key) in keypath.enumerated() { 54 | guard let next = current[key] else { 55 | let next = Trie(key: key) 56 | current[key] = next 57 | current = next 58 | 59 | if index == keypath.endIndex - 1 { 60 | next.value = value 61 | } 62 | 63 | continue 64 | } 65 | 66 | if index == keypath.endIndex - 1 && next.value == nil { 67 | next.value = value 68 | } 69 | 70 | current = next 71 | } 72 | } 73 | 74 | func contains(_ keypath: [UInt8]) -> ValueType? { 75 | var current = self 76 | 77 | for key in keypath { 78 | guard let next = current[key] else { return nil } 79 | current = next 80 | } 81 | 82 | return current.value 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Tests/StorageTests/PathBuilderTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Storage 3 | 4 | class PathBuilderTests: XCTestCase { 5 | static var allTests = [ 6 | ("testConfigurableNodesPathTemplate", testConfigurableNodesPathTemplate), 7 | ("testConfigurableAllAliases", testConfigurableAllAliases), 8 | ("testConfigurableAllAliasesFailed", testConfigurableAllAliasesFailed) 9 | ] 10 | 11 | func testConfigurableNodesPathTemplate() { 12 | let entity = FileEntity( 13 | fileName: "smile", 14 | fileExtension: "jpg", 15 | mime: "image/jpg" 16 | ) 17 | 18 | expectNoThrow() { 19 | let builder = try ConfigurablePathBuilder(template: "/myapp/#mimeFolder/#file") 20 | let path = try builder.build(entity: entity) 21 | XCTAssertEqual(path, "/myapp/images/original/smile.jpg") 22 | } 23 | } 24 | 25 | func testConfigurableAllAliases() { 26 | let entity = FileEntity( 27 | fileName: "test.png", 28 | folder: "myfolder", 29 | mime: "image/png" 30 | ) 31 | 32 | let expected: [(Template.Alias, String)] = [ 33 | (.file, "test.png"), 34 | (.fileName, "test"), 35 | (.fileExtension, "png"), 36 | (.folder, "myfolder"), 37 | (.mime, "image/png"), 38 | (.mimeFolder, "images/original") 39 | ] 40 | 41 | expectNoThrow() { 42 | try expected.forEach { (alias, expected) in 43 | let builder = try ConfigurablePathBuilder(template: alias.rawValue) 44 | let path = try builder.build(entity: entity) 45 | XCTAssertEqual(path, expected) 46 | } 47 | } 48 | } 49 | 50 | func testConfigurableAllAliasesFailed() { 51 | let entity = FileEntity() 52 | 53 | let expected: [(Template.Alias, Template.Error)] = [ 54 | (.file, .malformedFileName), 55 | (.fileName, .fileNameNotProvided), 56 | (.fileExtension, .fileExtensionNotProvided), 57 | (.folder, .folderNotProvided), 58 | (.mime, .mimeNotProvided), 59 | (.mimeFolder, .mimeFolderNotProvided) 60 | ] 61 | 62 | expected.forEach { (alias, error) in 63 | expect(toThrow: error) { 64 | let builder = try ConfigurablePathBuilder(template: alias.rawValue) 65 | _ = try builder.build(entity: entity) 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/StorageTests/FileEntityTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Storage 3 | 4 | class FileEntityTests: XCTestCase { 5 | static var allTests = [ 6 | ("testFileEntityInit", testFileEntityInit), 7 | ("testFileEntityInitNil", testFileEntityInitNil), 8 | ("testFileEntityValidate", testFileEntityValidate), 9 | ("testFileEntityValidateFailed", testFileEntityValidateFailed), 10 | ("testFileEntityGetFilePath", testFileEntityGetFilePath), 11 | ("testFileEntityGetFilePathFailed", testFileEntityGetFilePathFailed), 12 | ("testFileEntityWithExtensionInName", testFileEntityWithExtensionInName) 13 | ] 14 | 15 | func testFileEntityInit() { 16 | let entity = FileEntity( 17 | fileName: "test_image", 18 | fileExtension: "png", 19 | folder: "images" 20 | ) 21 | 22 | XCTAssertNotNil(entity.fileName) 23 | XCTAssertNotNil(entity.folder) 24 | XCTAssertNotNil(entity.fileExtension) 25 | 26 | XCTAssertEqual(entity.fileName, "test_image") 27 | XCTAssertEqual(entity.fileExtension, "png") 28 | XCTAssertEqual(entity.folder, "images") 29 | } 30 | 31 | func testFileEntityInitNil() { 32 | let entity = FileEntity() 33 | 34 | XCTAssertNil(entity.bytes) 35 | XCTAssertNil(entity.fileName) 36 | XCTAssertNil(entity.fileExtension) 37 | XCTAssertNil(entity.folder) 38 | } 39 | 40 | func testFileEntityValidate() { 41 | let entity = FileEntity( 42 | fileName: "test_image", 43 | fileExtension: "png", 44 | folder: "images" 45 | ) 46 | 47 | expectNoThrow(entity.verify) 48 | } 49 | 50 | func testFileEntityValidateFailed() { 51 | let entity = FileEntity( 52 | fileName: "test_image", 53 | folder: "images" 54 | ) 55 | 56 | expect(toThrow: FileEntity.Error.missingFileExtension, from: entity.verify) 57 | 58 | let entity2 = FileEntity( 59 | fileExtension: "png", 60 | folder: "images" 61 | ) 62 | expect(toThrow: FileEntity.Error.missingFilename, from: entity2.verify) 63 | } 64 | 65 | func testFileEntityGetFilePath() { 66 | let entity = FileEntity( 67 | fileName: "test_image", 68 | fileExtension: "png", 69 | folder: "images" 70 | ) 71 | 72 | expect(entity.getFilePath, toReturn: "images/test_image.png") 73 | } 74 | 75 | func testFileEntityGetFilePathFailed() { 76 | let entity = FileEntity( 77 | fileExtension: "jpg", 78 | folder: "images" 79 | ) 80 | 81 | expect(toThrow: FileEntity.Error.malformedFileName, from: entity.getFilePath) 82 | 83 | let entity2 = FileEntity( 84 | fileName: "profileImage", 85 | folder: "images" 86 | ) 87 | 88 | expect(toThrow: FileEntity.Error.malformedFileName, from: entity2.getFilePath) 89 | } 90 | 91 | func testFileEntityWithExtensionInName() { 92 | let entity = FileEntity( 93 | fileName: "test.png" 94 | ) 95 | 96 | XCTAssertEqual(entity.fileName, "test") 97 | XCTAssertEqual(entity.fileExtension, "png") 98 | XCTAssertEqual(entity.fullFileName, "test.png") 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Tests/StorageTests/TemplateTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | @testable import Storage 4 | 5 | class TemplateTests: XCTestCase { 6 | static var allTests = [ 7 | ("testExtractPartBasic", testExtractPartBasic), 8 | ("testExtractPartFailed", testExtractPartFailed), 9 | ("testExtractPartDoubleAlias", testExtractPartDoubleAlias), 10 | ("testExtractPartMatchingPrefixes", testExtractPartMatchingPrefixes), 11 | ("testPathPartEquatableFalse", testPathPartEquatableFalse), 12 | ("testPadDigits", testPadDigits) 13 | ] 14 | 15 | func testExtractPartBasic() { 16 | let template = "#folder/#file" 17 | let expected: [Template.PathPart] = [ 18 | .alias(.folder), 19 | .literal("/".bytes), 20 | .alias(.file) 21 | ] 22 | 23 | expectParts(expected, fromTemplate: template) 24 | } 25 | 26 | func testExtractPartFailed() { 27 | let templateString = "#madeupAlias" 28 | let scanner = Scanner(templateString.bytes) 29 | var template = Template(scanner: scanner) 30 | 31 | expect(toThrow: Template.Error.invalidAlias("#madeupAlias")) { 32 | _ = try template.extractPart() 33 | } 34 | } 35 | 36 | func testExtractPartDoubleAlias() { 37 | let template = "#folder#file" 38 | let expected: [Template.PathPart] = [ 39 | .alias(.folder), 40 | .alias(.file) 41 | ] 42 | 43 | expectParts(expected, fromTemplate: template) 44 | } 45 | 46 | func testExtractPartMatchingPrefixes() { 47 | let template = "#file#fileName#fileExtension" 48 | let expected: [Template.PathPart] = [ 49 | .alias(.file), 50 | .alias(.fileName), 51 | .alias(.fileExtension) 52 | ] 53 | 54 | expectParts(expected, fromTemplate: template) 55 | } 56 | 57 | func testPathPartEquatableFalse() { 58 | let literalA = Template.PathPart.literal("a".bytes) 59 | let literalB = Template.PathPart.literal("b".bytes) 60 | 61 | let aliasFile = Template.PathPart.alias(.file) 62 | let aliasMime = Template.PathPart.alias(.mime) 63 | 64 | XCTAssertNotEqual(literalA, literalB) 65 | XCTAssertNotEqual(aliasFile, aliasMime) 66 | XCTAssertNotEqual(literalA, aliasMime) 67 | } 68 | 69 | func testPadDigits() { 70 | let template = Template(scanner: Scanner([])) 71 | 72 | let zeroFour = template.padDigitLeft(4) 73 | XCTAssertEqual(zeroFour, "04") 74 | 75 | let twenty = template.padDigitLeft(20) 76 | XCTAssertEqual(twenty, "20") 77 | } 78 | 79 | func testTimeFormat() { 80 | let template = Template(scanner: Scanner([])) 81 | 82 | let time = template.formatTime(hours: 4, minutes: 30, seconds: 5) 83 | XCTAssertEqual(time, "04:30:05") 84 | } 85 | } 86 | 87 | extension TemplateTests { 88 | func expectParts( 89 | _ expectedParts: [Template.PathPart], 90 | fromTemplate template: String, 91 | file: StaticString = #file, 92 | line: UInt = #line 93 | ) { 94 | let bytes = template.bytes 95 | let scanner = Scanner(bytes) 96 | var template = Template(scanner: scanner) 97 | 98 | for expected in expectedParts { 99 | var part: Template.PathPart? = nil 100 | 101 | expectNoThrow() { 102 | part = try template.extractPart() 103 | } 104 | 105 | XCTAssertNotNil(part, "should have extracted non-nil", file: file, line: line) 106 | XCTAssert(part! == expected, file: file, line: line) 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Sources/Storage/NetworkDriver.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import Vapor 3 | import Foundation 4 | 5 | public protocol NetworkDriver: Service { 6 | var pathBuilder: PathBuilder { get set } 7 | 8 | func upload(entity: inout FileEntity, access: AccessControlList?, on container: Container) throws -> Future 9 | func get(path: String, on container: Container) throws -> Future 10 | func delete(path: String, on container: Container) throws -> Future 11 | } 12 | 13 | extension NetworkDriver { 14 | func upload( 15 | entity: inout FileEntity, 16 | on container: Container 17 | ) throws -> Future { 18 | try upload(entity: &entity, access: nil, on: container) 19 | } 20 | } 21 | 22 | public final class S3Driver: NetworkDriver { 23 | public enum Error: Swift.Error { 24 | case nilFileUpload 25 | case missingFileExtensionAndType 26 | case pathMissingForwardSlash 27 | } 28 | 29 | public var pathBuilder: PathBuilder 30 | let s3: S3 31 | 32 | public init( 33 | bucket: String, 34 | host: String = "s3.amazonaws.com", 35 | accessKey: String, 36 | secretKey: String, 37 | region: S3.Region = .euWest1, 38 | pathTemplate: String = "" 39 | ) throws { 40 | self.pathBuilder = try ConfigurablePathBuilder(template: pathTemplate) 41 | self.s3 = S3( 42 | host: "\(bucket).\(host)", 43 | accessKey: accessKey, 44 | secretKey: secretKey, 45 | region: region 46 | ) 47 | } 48 | 49 | public func upload( 50 | bytes: Data, 51 | fileName: String? = nil, 52 | fileExtension: String? = nil, 53 | mime: String? = nil, 54 | folder: String? = nil, 55 | access: AccessControlList = .publicRead, 56 | on container: Container 57 | ) throws -> Future { 58 | var entity = FileEntity( 59 | bytes: bytes, 60 | fileName: fileName, 61 | fileExtension: fileExtension, 62 | folder: folder, 63 | mime: mime 64 | ) 65 | 66 | return try upload(entity: &entity, access: access, on: container) 67 | } 68 | 69 | public func upload( 70 | entity: inout FileEntity, 71 | access: AccessControlList?, 72 | on container: Container 73 | ) throws -> Future { 74 | guard let bytes = entity.bytes else { 75 | throw Error.nilFileUpload 76 | } 77 | 78 | entity.sanitize() 79 | 80 | guard entity.fileExtension != nil || entity.loadFileExtensionFromMime() else { 81 | throw Error.missingFileExtensionAndType 82 | } 83 | 84 | if entity.mime == nil { 85 | entity.loadMimeFromFileExtension() 86 | } 87 | 88 | guard let mime = entity.mime else { 89 | throw Error.missingFileExtensionAndType 90 | } 91 | 92 | let path = try pathBuilder.build(entity: entity) 93 | 94 | guard path.hasPrefix("/") else { 95 | print("The S3 driver requires your path to begin with `/`") 96 | print("Please check `template` in `storage.json`.") 97 | throw Error.pathMissingForwardSlash 98 | } 99 | 100 | return try s3.upload( 101 | bytes: Data(bytes), 102 | path: path, 103 | contentType: mime, 104 | access: access ?? .publicRead, 105 | on: container 106 | ).transform(to: path) 107 | } 108 | 109 | public func get(path: String, on container: Container) throws -> Future { 110 | return try s3.get(path: path, on: container).map { $0 } 111 | } 112 | 113 | public func delete(path: String, on container: Container) throws -> Future { 114 | return try s3.delete(path: path, on: container).map { $0 } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Sources/Storage/FileEntity.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | 3 | /// Representation of a to-be-uploaded file. 4 | public struct FileEntity { 5 | public enum Error: Swift.Error { 6 | case missingFilename 7 | case missingFileExtension 8 | case malformedFileName 9 | } 10 | 11 | /// The raw bytes of the file. 12 | var bytes: Data? 13 | 14 | // The file's name with the extension. 15 | var fullFileName: String? { 16 | guard let fileName = fileName, let fileExtension = fileExtension else { 17 | return nil 18 | } 19 | 20 | return [fileName, fileExtension].joined(separator: ".") 21 | } 22 | 23 | /// The file's name without the extension. 24 | var fileName: String? 25 | 26 | /// The file's extension. 27 | var fileExtension: String? 28 | 29 | /// The folder the file was uploaded from. 30 | var folder: String? 31 | 32 | /// The type of the file. 33 | var mime: String? 34 | 35 | /** 36 | FileEntity's default initializer. 37 | 38 | - Parameters: 39 | - bytes: The raw bytes of the file. 40 | - fileName: The file's name. 41 | - fileExtension: The file's extension. 42 | - folder: The folder the file was uploaded from. 43 | - mime: The type of the file. 44 | */ 45 | public init( 46 | bytes: Data? = nil, 47 | fileName: String? = nil, 48 | fileExtension: String? = nil, 49 | folder: String? = nil, 50 | mime: String? = nil 51 | ) { 52 | self.bytes = bytes 53 | self.fileName = fileName 54 | self.fileExtension = fileExtension 55 | self.folder = folder 56 | self.mime = mime 57 | 58 | sanitize() 59 | } 60 | } 61 | 62 | extension FileEntity { 63 | func verify() throws { 64 | guard fileName != nil else { 65 | throw Error.missingFilename 66 | } 67 | 68 | guard fileExtension != nil else { 69 | throw Error.missingFileExtension 70 | } 71 | } 72 | } 73 | 74 | extension FileEntity { 75 | func getFilePath() throws -> String { 76 | guard let fileName = fullFileName else { 77 | throw Error.malformedFileName 78 | } 79 | 80 | var path = [ 81 | fileName 82 | ] 83 | 84 | if let folder = folder { 85 | path.insert(folder, at: 0) 86 | } 87 | 88 | return path.joined(separator: "/") 89 | } 90 | } 91 | 92 | extension FileEntity { 93 | mutating func sanitize() { 94 | guard let fileName = fileName, fileName.contains(".") else { return } 95 | 96 | let components = fileName.components(separatedBy: ".") 97 | 98 | // don't override if a programmer provided an extension 99 | if fileExtension == nil { 100 | fileExtension = components.last 101 | } 102 | 103 | self.fileName = components.dropLast().joined(separator: ".") 104 | } 105 | 106 | @discardableResult 107 | mutating func loadMimeFromFileExtension() -> Bool { 108 | guard let fileExtension = fileExtension?.lowercased() else { return false } 109 | 110 | // MimeLib doesn't support `jpg` so do a check here first 111 | guard fileExtension != "jpg" else { 112 | self.mime = "image/jpeg" 113 | return true 114 | } 115 | 116 | guard let mime = getMime(for: fileExtension) else { 117 | return false 118 | } 119 | 120 | self.mime = mime 121 | return true 122 | } 123 | 124 | @discardableResult 125 | mutating func loadFileExtensionFromMime() -> Bool { 126 | guard let mime = mime else { return false } 127 | 128 | guard let fileExtension = getExtension(for: mime) else { 129 | return false 130 | } 131 | 132 | self.fileExtension = fileExtension 133 | return true 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Sources/Storage/Utilities/DataURI.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A parser for decoding Data URIs. 4 | public struct DataURIParser { 5 | enum Error: Swift.Error { 6 | case invalidScheme 7 | case invalidURI 8 | } 9 | 10 | var scanner: Scanner 11 | 12 | init(scanner: Scanner) { 13 | self.scanner = scanner 14 | } 15 | } 16 | 17 | extension DataURIParser { 18 | /** 19 | Parses a Data URI and returns its type and data. 20 | 21 | - Parameters: 22 | - uri: The URI to be parsed. 23 | 24 | - Returns: (data: [UInt8], type: [UInt8], typeMetadata: [UInt8]?) 25 | */ 26 | public static func parse(uri: String) throws -> (Data, [UInt8], [UInt8]?) { 27 | guard uri.hasPrefix("data:") else { 28 | throw Error.invalidScheme 29 | } 30 | 31 | var scanner: Scanner = Scanner(uri.bytes) 32 | //pop the bytes "data:" 33 | scanner.pop(5) 34 | 35 | var parser = DataURIParser(scanner: scanner) 36 | var (type, typeMetadata) = try parser.extractType() 37 | var data = try parser.extractData() 38 | 39 | //Required by RFC 2397 40 | if type.isEmpty { 41 | type = "text/plain;charset=US-ASCII".bytes 42 | } 43 | 44 | if let typeMetadata = typeMetadata, typeMetadata == "base64".bytes { 45 | data = Data(base64Encoded: data) ?? Data() 46 | } 47 | 48 | return (data, type, typeMetadata) 49 | } 50 | } 51 | 52 | extension DataURIParser { 53 | mutating func extractType() throws -> ([UInt8], [UInt8]?) { 54 | let type = consume(until: [.comma, .semicolon]) 55 | 56 | guard let byte = scanner.peek() else { 57 | throw Error.invalidURI 58 | } 59 | 60 | var typeMetadata: [UInt8]? 61 | 62 | if byte == .semicolon { 63 | typeMetadata = try extractTypeMetadata() 64 | } 65 | 66 | return (type, typeMetadata) 67 | } 68 | 69 | mutating func extractTypeMetadata() throws -> [UInt8] { 70 | assert(scanner.peek() == .semicolon) 71 | scanner.pop() 72 | 73 | return consume(until: [.comma]) 74 | } 75 | 76 | mutating func extractData() throws -> Data { 77 | assert(scanner.peek() == .comma) 78 | scanner.pop() 79 | return try Data(consumePercentDecoded()) 80 | } 81 | } 82 | 83 | extension DataURIParser { 84 | @discardableResult 85 | mutating func consume() -> [UInt8] { 86 | var bytes: [UInt8] = [] 87 | 88 | while let byte = scanner.peek() { 89 | scanner.pop() 90 | bytes.append(byte) 91 | } 92 | 93 | return bytes 94 | } 95 | 96 | @discardableResult 97 | mutating func consumePercentDecoded() throws -> [UInt8] { 98 | var bytes: [UInt8] = [] 99 | 100 | while var byte = scanner.peek() { 101 | if byte == .percent { 102 | byte = try decodePercentEncoding() 103 | } 104 | 105 | scanner.pop() 106 | bytes.append(byte) 107 | } 108 | 109 | return bytes 110 | } 111 | 112 | @discardableResult 113 | mutating func consume(until terminators: Set) -> [UInt8] { 114 | var bytes: [UInt8] = [] 115 | 116 | while let byte = scanner.peek(), !terminators.contains(byte) { 117 | scanner.pop() 118 | bytes.append(byte) 119 | } 120 | 121 | return bytes 122 | } 123 | 124 | @discardableResult 125 | mutating func consume(while conditional: (UInt8) -> Bool) -> [UInt8] { 126 | var bytes: [UInt8] = [] 127 | 128 | while let byte = scanner.peek(), conditional(byte) { 129 | scanner.pop() 130 | bytes.append(byte) 131 | } 132 | 133 | return bytes 134 | } 135 | } 136 | 137 | extension DataURIParser { 138 | mutating func decodePercentEncoding() throws -> UInt8 { 139 | assert(scanner.peek() == .percent) 140 | 141 | guard 142 | let leftMostDigit = scanner.peek(aheadBy: 1), 143 | let rightMostDigit = scanner.peek(aheadBy: 2) 144 | else { 145 | throw Error.invalidURI 146 | } 147 | 148 | scanner.pop(2) 149 | 150 | return (leftMostDigit.asciiCode * 0x10) + rightMostDigit.asciiCode 151 | } 152 | } 153 | 154 | extension UInt8 { 155 | internal var asciiCode: UInt8 { 156 | if self >= 48 && self <= 57 { 157 | return self - 48 158 | } else if self >= 65 && self <= 70 { 159 | return self - 55 160 | } else { 161 | return 0 162 | } 163 | } 164 | } 165 | 166 | extension String { 167 | /** 168 | Parses a Data URI and returns its data and type. 169 | 170 | - Returns: The type of the file and its data as bytes. 171 | */ 172 | public func dataURIDecoded() throws -> (data: Data, type: String) { 173 | let (data, type, _) = try DataURIParser.parse(uri: self) 174 | return (data, String(bytes: type, encoding: .utf8) ?? "") 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Sources/Storage/Storage.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import HTTP 3 | import Vapor 4 | import Foundation 5 | 6 | public class Storage { 7 | public enum Error: Swift.Error { 8 | case missingNetworkDriver 9 | case cdnBaseURLNotSet 10 | case missingFileName 11 | } 12 | 13 | public static var cdnBaseURL: String? 14 | 15 | public static var cdnPathBuilder: ((String, String) -> String)? 16 | 17 | /** 18 | Uploads the given `FileEntity`. 19 | 20 | - Parameters: 21 | - entity: The `FileEntity` to be uploaded. 22 | 23 | - Returns: The path the file was uploaded to. 24 | */ 25 | @discardableResult 26 | public static func upload( 27 | entity: inout FileEntity, 28 | access: AccessControlList? = nil, 29 | on container: Container 30 | ) throws -> Future { 31 | let networkDriver = try container.make(NetworkDriver.self) 32 | return try networkDriver.upload(entity: &entity, access: access, on: container) 33 | } 34 | 35 | /** 36 | Uploads bytes to a storage server. 37 | 38 | - Parameters: 39 | - bytes: The raw bytes of the file. 40 | - fileName: The name of the file. 41 | - fileExtension: The extension of the file. 42 | - mime: The mime type of the file. 43 | - folder: The folder to save the file in. 44 | 45 | - Returns: The path the file was uploaded to. 46 | */ 47 | @discardableResult 48 | public static func upload( 49 | bytes: Data, 50 | fileName: String? = nil, 51 | fileExtension: String? = nil, 52 | mime: String? = nil, 53 | folder: String? = nil, 54 | access: AccessControlList? = nil, 55 | on container: Container 56 | ) throws -> Future { 57 | var entity = FileEntity( 58 | bytes: bytes, 59 | fileName: fileName, 60 | fileExtension: fileExtension, 61 | folder: folder, 62 | mime: mime 63 | ) 64 | 65 | return try upload(entity: &entity, access: access, on: container) 66 | } 67 | 68 | /** 69 | Decodes and uploads a data URI. 70 | 71 | - Parameters: 72 | - dataURI: The data URI to be decoded. 73 | - fileName: The name of the file. 74 | - fileExtension: The extension of the file. 75 | - folder: The folder to save the file in. 76 | 77 | - Returns: The path the file was uploaded to. 78 | */ 79 | @discardableResult 80 | public static func upload( 81 | dataURI: String, 82 | fileName: String? = nil, 83 | fileExtension: String? = nil, 84 | folder: String? = nil, 85 | access: AccessControlList? = nil, 86 | on container: Container 87 | ) throws -> Future { 88 | let (bytes, type) = try dataURI.dataURIDecoded() 89 | return try upload( 90 | bytes: bytes, 91 | fileName: fileName, 92 | fileExtension: fileExtension, 93 | mime: type, 94 | folder: folder, 95 | access: access, 96 | on: container 97 | ) 98 | } 99 | 100 | /** 101 | Downloads the file at `path`. 102 | 103 | - Parameters: 104 | - path: The path of the file to be downloaded. 105 | 106 | - Returns: The downloaded file. 107 | */ 108 | public static func get(path: String, on container: Container) throws -> Future { 109 | let networkDriver = try container.make(NetworkDriver.self) 110 | return try networkDriver.get(path: path, on: container) 111 | } 112 | 113 | /// Appends the asset's path with the base CDN URL. 114 | public static func getCDNPath(for path: String) throws -> String { 115 | guard let cdnBaseURL = cdnBaseURL else { 116 | throw Error.cdnBaseURLNotSet 117 | } 118 | 119 | if let cdnPathBuilder = cdnPathBuilder { 120 | return cdnPathBuilder(cdnBaseURL, path) 121 | } 122 | 123 | return cdnBaseURL + path 124 | } 125 | 126 | /// Appends the asset's path with the base CDN URL. With support for optional 127 | public static func getCDNPath(optional path: String?) throws -> String? { 128 | guard let pathUnwrapped = path else { 129 | return nil 130 | } 131 | 132 | guard let cdnBaseURL = cdnBaseURL else { 133 | throw Error.cdnBaseURLNotSet 134 | } 135 | 136 | if let cdnPathBuilder = cdnPathBuilder { 137 | return cdnPathBuilder(cdnBaseURL, pathUnwrapped) 138 | } 139 | 140 | return cdnBaseURL + pathUnwrapped 141 | } 142 | 143 | /** 144 | Deletes the file at `path`. 145 | 146 | - Parameters: 147 | - path: The path of the file to be deleted. 148 | */ 149 | public static func delete(path: String, on container: Container) throws -> Future { 150 | let networkDriver = try container.make(NetworkDriver.self) 151 | return try networkDriver.delete(path: path, on: container) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Storage 🗄 2 | [![Swift Version](https://img.shields.io/badge/Swift-4.2-brightgreen.svg)](http://swift.org) 3 | [![Vapor Version](https://img.shields.io/badge/Vapor-3-30B6FC.svg)](http://vapor.codes) 4 | [![Circle CI](https://circleci.com/gh/nodes-vapor/storage/tree/master.svg?style=shield)](https://circleci.com/gh/nodes-vapor/storage) 5 | [![codebeat badge](https://codebeat.co/badges/58eeca2c-7b58-4aea-9b09-d80e3b79de19)](https://codebeat.co/projects/github-com-nodes-vapor-storage-master) 6 | [![codecov](https://codecov.io/gh/nodes-vapor/storage/branch/master/graph/badge.svg)](https://codecov.io/gh/nodes-vapor/storage) 7 | [![Readme Score](http://readme-score-api.herokuapp.com/score.svg?url=https://github.com/nodes-vapor/storage)](http://clayallsopp.github.io/readme-score?url=https://github.com/nodes-vapor/storage) 8 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nodes-vapor/storage/master/LICENSE) 9 | 10 | A package to ease the use of multiple storage and CDN services. 11 | 12 | 13 | ##### Table of Contents 14 | * [Getting started](#getting-started-) 15 | * [Upload a file](#upload-a-file-) 16 | * [Base 64 and data URI](#base-64-and-data-uri-) 17 | * [Download a file](#download-a-file-) 18 | * [Get CDN path](#get-cdn-path-) 19 | * [Delete a file](#delete-a-file-) 20 | * [Configuration](#configuration-) 21 | * [Network driver](#network-driver-) 22 | * [Upload path](#upload-path-) 23 | 24 | 25 | ## 📦 Installation 26 | 27 | Add `Storage` to the package dependencies (in your `Package.swift` file): 28 | ```swift 29 | dependencies: [ 30 | ..., 31 | .package(url: "https://github.com/nodes-vapor/storage.git", from: "1.0.0") 32 | ] 33 | ``` 34 | 35 | as well as to your target (e.g. "App"): 36 | 37 | ```swift 38 | targets: [ 39 | ... 40 | .target( 41 | name: "App", 42 | dependencies: [... "Storage" ...] 43 | ), 44 | ... 45 | ] 46 | ``` 47 | 48 | ## Getting started 🚀 49 | Storage makes it easy to start uploading and downloading files. Just register a [network driver](#network-driver-) and get going. 50 | 51 | ## Upload a file 🌐 52 | 53 | There are a few different interfaces for uploading a file, the simplest being the following: 54 | ```swift 55 | Storage.upload( 56 | bytes: [UInt8], 57 | fileName: String?, 58 | fileExtension: String?, 59 | mime: String?, 60 | folder: String, 61 | on container: Container 62 | ) throws -> String 63 | ``` 64 | The aforementioned function will attempt to upload the file using your [selected driver and template](#configuration-) and will return a `String` representing the location of the file. 65 | 66 | If you want to upload an image named `profile.png` your call site would look like: 67 | ```swift 68 | try Storage.upload( 69 | bytes: bytes, 70 | fileName: "profile.png", 71 | on: req 72 | ) 73 | ``` 74 | 75 | #### Base64 and data URI 📡 76 | Is your file a base64 or data URI? No problem! 77 | ```swift 78 | Storage.upload(base64: "SGVsbG8sIFdvcmxkIQ==", fileName: "base64.txt", on: req) 79 | Storage.upload(dataURI: "data:,Hello%2C%20World!", fileName: "data-uri.txt", on: req) 80 | ``` 81 | 82 | #### Remote resources 83 | Download an asset from a URL and then reupload it to your storage server. 84 | ```swift 85 | Storage.upload(url: "http://mysite.com/myimage.png", fileName: "profile.png", on: req) 86 | ``` 87 | 88 | 89 | ## Download a file ✅ 90 | 91 | To download a file that was previously uploaded you simply use the generated path. 92 | ```swift 93 | // download image as `Foundation.Data` 94 | let data = try Storage.get("/images/profile.png", on: req) 95 | ``` 96 | 97 | 98 | ## Get CDN path 99 | 100 | In order to use the CDN path convenience, you'll have to set the CDN base url on Storage, e.g. in your `configure.swift` file: 101 | 102 | ```swift 103 | Storage.cdnBaseURL = "https://cdn.vapor.cloud" 104 | ``` 105 | 106 | Here is how you generate the CDN path to a given asset. 107 | ```swift 108 | let cdnPath = try Storage.getCDNPath(for: path) 109 | ``` 110 | 111 | If your CDN path is more involved than `cdnUrl` + `path`, you can build out Storage's optional completionhandler to override the default functionality. 112 | 113 | ```swift 114 | Storage.cdnPathBuilder = { baseURL, path in 115 | let joinedPath = (baseURL + path) 116 | return joinedPath.replacingOccurrences(of: "/images/original/", with: "/image/") 117 | } 118 | ``` 119 | 120 | 121 | ## Delete a file ❌ 122 | 123 | Deleting a file can be done as follows. 124 | ```swift 125 | try Storage.delete("/images/profile.png") 126 | ``` 127 | 128 | ## Configuration ⚙ 129 | 130 | `Storage` has a variety of configurable options. 131 | 132 | #### Network driver 🔨 133 | The network driver is the module responsible for interacting with your 3rd party service. The default, and currently the only, driver is `s3`. 134 | ```swift 135 | import Storage 136 | 137 | let driver = try S3Driver( 138 | bucket: "bucket", 139 | accessKey: "access", 140 | secretKey: "secret" 141 | ) 142 | 143 | services.register(driver, as: NetworkDriver.self) 144 | ``` 145 | `bucket`, `accessKey`and `secretKey` are required by the S3 driver, while `template`, `host` and `region` are optional. `region` will default to `eu-west-1` and `host` will default to `s3.amazonaws.com`. 146 | 147 | #### Upload path 🛣 148 | A times, you may need to upload files to a different scheme than `/file.ext`. You can achieve this by passing in the `pathTemplate` parameter when creating the `S3Driver`. If the parameter is omitted it will default to `/#file`. 149 | 150 | The following template will upload `profile.png` from the folder `images` to `/myapp/images/profile.png` 151 | ```swift 152 | let driver = try S3Driver( 153 | bucket: "mybucket", 154 | accessKey: "myaccesskey", 155 | secretKey: "mysecretkey", 156 | pathTemplate: "/myapp/#folder/#file" 157 | ) 158 | ``` 159 | 160 | ##### Aliases 161 | Aliases are special keys in your template that will be replaced with dynamic information at the time of upload. 162 | 163 | *Note: if you use an alias and the information wasn't provided at the file upload's callsite, Storage will throw a `missingX`/`malformedX` error.* 164 | 165 | `#file`: The file's name and extension. 166 | 167 | ``` 168 | File: "test.png" 169 | Returns: test.png 170 | ``` 171 | 172 | --- 173 | 174 | `#fileName`: The file's name. 175 | 176 | ``` 177 | File: "test.png" 178 | Returns: test 179 | ``` 180 | 181 | --- 182 | 183 | `#fileExtension`: The file's extension. 184 | 185 | ``` 186 | File: "test.png" 187 | Returns: png 188 | ``` 189 | 190 | --- 191 | 192 | `#folder`: The provided folder. 193 | 194 | ``` 195 | File: "uploads/test.png" 196 | Returns: uploads 197 | ``` 198 | 199 | --- 200 | 201 | `#mime`: The file's content type. 202 | 203 | ``` 204 | File: "test.png" 205 | Returns: image/png 206 | ``` 207 | 208 | --- 209 | 210 | `#mimeFolder`: A folder generated according to the file's mime. 211 | 212 | This alias will check the file's mime and if it's an image, it will return `images/original` else it will return `data` 213 | 214 | ``` 215 | File: "test.png" 216 | Returns: images/original 217 | ``` 218 | 219 | --- 220 | 221 | `#day`: The current day. 222 | 223 | ``` 224 | File: "test.png" 225 | Date: 12/12/2012 226 | Returns: 12 227 | ``` 228 | 229 | --- 230 | 231 | `#month`: The current month. 232 | 233 | ``` 234 | File: "test.png" 235 | Date: 12/12/2012 236 | Returns: 12 237 | ``` 238 | 239 | --- 240 | 241 | `#year`: The current year. 242 | 243 | ``` 244 | File: "test.png" 245 | Date: 12/12/2012 246 | Returns: 2012 247 | ``` 248 | 249 | --- 250 | 251 | `#timestamp`: The time of upload. 252 | 253 | ``` 254 | File: "test.png" 255 | Time: 17:05:00 256 | Returns: 17:05:00 257 | ``` 258 | 259 | --- 260 | 261 | `#uuid`: A generated UUID. 262 | 263 | ``` 264 | File: "test.png" 265 | Returns: 123e4567-e89b-12d3-a456-426655440000 266 | ``` 267 | 268 | --- 269 | ## 🏆 Credits 270 | 271 | This package is developed and maintained by the Vapor team at [Nodes](https://www.nodesagency.com). 272 | 273 | ## 📄 License 274 | 275 | This package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) 276 | -------------------------------------------------------------------------------- /Sources/Storage/Template.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import Random 3 | import Foundation 4 | 5 | extension UInt8 { 6 | /// # 7 | static var octothorp: UInt8 = 0x23 8 | } 9 | 10 | struct Template { 11 | let calendar = Calendar(identifier: .gregorian) 12 | 13 | enum Error: Swift.Error { 14 | case invalidAlias(String) 15 | case failedToExtractDate 16 | case malformedFileName 17 | case fileNameNotProvided 18 | case fileExtensionNotProvided 19 | case folderNotProvided 20 | case mimeNotProvided 21 | case mimeFolderNotProvided 22 | } 23 | 24 | enum Alias: String { 25 | case file = "#file" 26 | case fileName = "#fileName" 27 | case fileExtension = "#fileExtension" 28 | case folder = "#folder" 29 | case mime = "#mime" 30 | case mimeFolder = "#mimeFolder" 31 | case day = "#day" 32 | case month = "#month" 33 | case year = "#year" 34 | case timestamp = "#timestamp" 35 | case uuid = "#uuid" 36 | } 37 | 38 | enum PathPart { 39 | case literal([UInt8]) 40 | case alias(Alias) 41 | } 42 | 43 | var scanner: Scanner 44 | var parts: [PathPart] = [] 45 | 46 | init(scanner: Scanner) { 47 | self.scanner = scanner 48 | } 49 | } 50 | 51 | extension Template { 52 | static func compile(_ templateString: String) throws -> Template { 53 | var template = Template(scanner: Scanner(templateString.bytes)) 54 | 55 | while let part = try template.extractPart() { 56 | template.parts.append(part) 57 | } 58 | 59 | return template 60 | } 61 | 62 | func renderPath( 63 | for entity: FileEntity, 64 | _ mimeFolderBuilder: (String?) -> String? 65 | ) throws -> String { 66 | let dateComponents = getDateComponents() 67 | 68 | var pathUInt8s: [UInt8] = [] 69 | 70 | for part in parts { 71 | switch part { 72 | case .literal(let bytes): 73 | pathUInt8s += bytes 74 | case .alias(let alias): 75 | switch alias { 76 | case .file: 77 | guard let fullFileName = entity.fullFileName else { 78 | throw Error.malformedFileName 79 | } 80 | pathUInt8s += fullFileName.bytes 81 | 82 | case .fileName: 83 | guard let fileName = entity.fileName else { 84 | throw Error.fileNameNotProvided 85 | } 86 | pathUInt8s += fileName.bytes 87 | 88 | case .fileExtension: 89 | guard let fileExtension = entity.fileExtension else { 90 | throw Error.fileExtensionNotProvided 91 | } 92 | pathUInt8s += fileExtension.bytes 93 | 94 | case .folder: 95 | guard let folder = entity.folder else { 96 | throw Error.folderNotProvided 97 | } 98 | pathUInt8s += folder.bytes 99 | 100 | case .mime: 101 | guard let mime = entity.mime else { 102 | throw Error.mimeNotProvided 103 | } 104 | pathUInt8s += mime.bytes 105 | 106 | case .mimeFolder: 107 | guard let mimeFolder = mimeFolderBuilder(entity.mime) else { 108 | throw Error.mimeFolderNotProvided 109 | } 110 | pathUInt8s += mimeFolder.bytes 111 | 112 | case .day: 113 | guard let day = dateComponents.day else { 114 | throw Error.failedToExtractDate 115 | } 116 | pathUInt8s += "\(day)".bytes 117 | 118 | case .month: 119 | guard let month = dateComponents.month else { 120 | throw Error.failedToExtractDate 121 | } 122 | pathUInt8s += "\(month)".bytes 123 | 124 | case .year: 125 | guard let year = dateComponents.year else { 126 | throw Error.failedToExtractDate 127 | } 128 | pathUInt8s += "\(year)".bytes 129 | 130 | case .timestamp: 131 | guard 132 | let hours = dateComponents.hour, 133 | let minutes = dateComponents.minute, 134 | let seconds = dateComponents.second 135 | else { 136 | throw Error.failedToExtractDate 137 | } 138 | let time = formatTime(hours: hours, minutes: minutes, seconds: seconds) 139 | pathUInt8s += time.bytes 140 | 141 | case .uuid: 142 | let uuidUInt8s = UUID().uuidString.bytes 143 | pathUInt8s += uuidUInt8s 144 | } 145 | } 146 | } 147 | 148 | return String(bytes: pathUInt8s, encoding: .utf8) ?? "" 149 | } 150 | } 151 | 152 | extension Template { 153 | mutating func extractPart() throws -> PathPart? { 154 | guard let byte = scanner.peek() else { return nil } 155 | 156 | if byte == UInt8.octothorp { 157 | return try extractAlias() 158 | } else { 159 | return extractLiteral() 160 | } 161 | } 162 | } 163 | 164 | extension Template { 165 | //convenience for adding aliases to trie 166 | static func insert(into trie: Trie, _ alias: Template.Alias) { 167 | trie.insert(alias, for: alias.rawValue.bytes) 168 | } 169 | } 170 | 171 | extension Template { 172 | static let trie: Trie = { 173 | let _trie = Trie() 174 | insert(into: _trie, Alias.file) 175 | insert(into: _trie, Alias.fileName) 176 | insert(into: _trie, Alias.fileExtension) 177 | insert(into: _trie, Alias.folder) 178 | insert(into: _trie, Alias.mime) 179 | insert(into: _trie, Alias.mimeFolder) 180 | insert(into: _trie, Alias.day) 181 | insert(into: _trie, Alias.month) 182 | insert(into: _trie, Alias.year) 183 | insert(into: _trie, Alias.timestamp) 184 | insert(into: _trie, Alias.uuid) 185 | return _trie 186 | }() 187 | 188 | mutating func extractAlias() throws -> PathPart { 189 | var partial: [UInt8] = [] 190 | 191 | var peeked = 0 192 | defer { scanner.pop(peeked) } 193 | 194 | var current = Template.trie 195 | 196 | while let byte = scanner.peek(aheadBy: peeked) { 197 | peeked += 1 198 | 199 | guard let next = current[byte] else { break } 200 | 201 | if let value = next.value { 202 | if let nextUInt8 = scanner.peek(aheadBy: peeked) { 203 | guard next[nextUInt8] != nil else { 204 | return .alias(value) 205 | } 206 | partial.append(byte) 207 | current = next 208 | continue 209 | } 210 | 211 | return .alias(value) 212 | } 213 | 214 | partial.append(byte) 215 | current = next 216 | } 217 | 218 | let invalidAlias = String(bytes: partial, encoding: .utf8) ?? "" 219 | throw Error.invalidAlias(invalidAlias) 220 | } 221 | 222 | mutating func extractLiteral() -> PathPart { 223 | var partial: [UInt8] = [] 224 | var peeked = 0 225 | defer { scanner.pop(peeked) } 226 | 227 | while 228 | let byte = scanner.peek(aheadBy: peeked), 229 | byte != UInt8.octothorp 230 | { 231 | peeked += 1 232 | partial.append(byte) 233 | } 234 | 235 | return .literal(partial) 236 | } 237 | } 238 | 239 | extension Template { 240 | func getDateComponents() -> DateComponents { 241 | return calendar.dateComponents( 242 | [.day, .month, .year, .second, .minute, .hour], 243 | from: Date() 244 | ) 245 | } 246 | 247 | func padDigitLeft(_ digit: Int) -> String { 248 | return digit < 10 ? "0\(digit)" : "\(digit)" 249 | } 250 | 251 | func formatTime(hours: Int, minutes: Int, seconds: Int) -> String { 252 | let hours = padDigitLeft(hours) 253 | let minutes = padDigitLeft(minutes) 254 | let seconds = padDigitLeft(seconds) 255 | 256 | return "\(hours):\(minutes):\(seconds)" 257 | } 258 | } 259 | 260 | extension Template.Error: Equatable { 261 | static func ==(lhs: Template.Error, rhs: Template.Error) -> Bool { 262 | switch (lhs, rhs) { 263 | case (.invalidAlias, .invalidAlias), 264 | (.malformedFileName, .malformedFileName), 265 | (.failedToExtractDate, .failedToExtractDate), 266 | (.fileNameNotProvided, .fileNameNotProvided), 267 | (.fileExtensionNotProvided, .fileExtensionNotProvided), 268 | (.folderNotProvided, .folderNotProvided), 269 | (.mimeNotProvided, .mimeNotProvided), 270 | (.mimeFolderNotProvided, .mimeFolderNotProvided): 271 | return true 272 | 273 | default: 274 | return false 275 | } 276 | } 277 | } 278 | 279 | extension Template.PathPart: Equatable { 280 | static func ==(lhs: Template.PathPart, rhs: Template.PathPart) -> Bool { 281 | switch (lhs, rhs) { 282 | case (.literal(let a), literal(let b)): 283 | return a == b 284 | 285 | case (.alias(let a), .alias(let b)): 286 | return a == b 287 | 288 | default: 289 | return false 290 | } 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /Sources/Storage/Utilities/S3.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import Vapor 3 | import Crypto 4 | import Foundation 5 | 6 | extension S3 { 7 | public struct Region { 8 | let code: String 9 | 10 | public static var usEast1: Region = .init(code: "us-east-1") 11 | public static var usEast2: Region = .init(code: "us-east-2") 12 | public static var usWest1: Region = .init(code: "us-west-1") 13 | public static var usWest2: Region = .init(code: "us-west-2") 14 | public static var euWest1: Region = .init(code: "eu-west-1") 15 | public static var euWest2: Region = .init(code: "eu-west-2") 16 | public static var euWest3: Region = .init(code: "eu-west-3") 17 | public static var euCentral1: Region = .init(code: "eu-central-1") 18 | public static var apSouth1: Region = .init(code: "ap-south-1") 19 | public static var apSoutheast1: Region = .init(code: "ap-southeast-1") 20 | public static var apSoutheast2: Region = .init(code: "ap-southeast-2") 21 | public static var apNortheast1: Region = .init(code: "ap-northeast-1") 22 | public static var apNortheast2: Region = .init(code: "ap-northeast-2") 23 | public static var saEast1: Region = .init(code: "sa-east-1") 24 | 25 | public init(code: String) { 26 | self.code = code 27 | } 28 | 29 | public var host: String { 30 | return "s3-\(code).amazonaws.com" 31 | } 32 | } 33 | } 34 | 35 | public enum Payload { 36 | case bytes(Data) 37 | case unsigned 38 | case none 39 | } 40 | 41 | extension Payload { 42 | func hashed() throws -> String { 43 | switch self { 44 | case .bytes(let bytes): 45 | return try SHA256.hash(bytes).hexEncodedString() 46 | 47 | case .unsigned: 48 | return "UNSIGNED-PAYLOAD" 49 | 50 | case .none: 51 | // SHA256 hash of '' 52 | return "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 53 | } 54 | } 55 | } 56 | 57 | extension Payload { 58 | var bytes: Data { 59 | switch self { 60 | case .bytes(let bytes): 61 | return bytes 62 | 63 | default: 64 | return Data() 65 | } 66 | } 67 | } 68 | 69 | extension String { 70 | public static let awsQueryAllowed = CharacterSet( 71 | charactersIn: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~=&" 72 | ) 73 | 74 | public static let awsPathAllowed = CharacterSet( 75 | charactersIn: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~/" 76 | ) 77 | } 78 | 79 | public enum AccessControlList: String { 80 | case privateAccess = "private" 81 | case publicRead = "public-read" 82 | case publicReadWrite = "public-read-write" 83 | case awsExecRead = "aws-exec-read" 84 | case authenticatedRead = "authenticated-read" 85 | case bucketOwnerRead = "bucket-owner-read" 86 | case bucketOwnerFullControl = "bucket-owner-full-control" 87 | } 88 | 89 | public struct AWSSignatureV4 { 90 | public enum Method: String { 91 | case delete = "DELETE" 92 | case get = "GET" 93 | case post = "POST" 94 | case put = "PUT" 95 | } 96 | 97 | let service: String 98 | let host: String 99 | let region: String 100 | let accessKey: String 101 | let secretKey: String 102 | 103 | internal var unitTestDate: Date? 104 | 105 | var amzDate: String { 106 | let dateFormatter = DateFormatter() 107 | dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) 108 | dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'" 109 | return dateFormatter.string(from: unitTestDate ?? Date()) 110 | } 111 | 112 | public init( 113 | service: String, 114 | host: String, 115 | region: S3.Region, 116 | accessKey: String, 117 | secretKey: String 118 | ) { 119 | self.service = service 120 | self.host = host 121 | self.region = region.code 122 | self.accessKey = accessKey 123 | self.secretKey = secretKey 124 | } 125 | 126 | func getStringToSign( 127 | algorithm: String, 128 | date: String, 129 | scope: String, 130 | canonicalHash: String 131 | ) -> String { 132 | return [ 133 | algorithm, 134 | date, 135 | scope, 136 | canonicalHash 137 | ].joined(separator: "\n") 138 | } 139 | 140 | func getSignature(_ stringToSign: String) throws -> String { 141 | let dateHMAC = try HMAC.SHA256.authenticate(dateStamp(), key: "AWS4\(secretKey)") 142 | let regionHMAC = try HMAC.SHA256.authenticate(region, key: dateHMAC) 143 | let serviceHMAC = try HMAC.SHA256.authenticate(service, key: regionHMAC) 144 | let signingHMAC = try HMAC.SHA256.authenticate("aws4_request", key: serviceHMAC) 145 | 146 | let signature = try HMAC.SHA256.authenticate(stringToSign, key: signingHMAC) 147 | return signature.hexEncodedString() 148 | } 149 | 150 | func getCredentialScope() -> String { 151 | return [ 152 | dateStamp(), 153 | region, 154 | service, 155 | "aws4_request" 156 | ].joined(separator: "/") 157 | } 158 | 159 | func getCanonicalRequest( 160 | payloadHash: String, 161 | method: Method, 162 | path: String, 163 | query: String, 164 | canonicalHeaders: String, 165 | signedHeaders: String 166 | ) throws -> String { 167 | let path = path.addingPercentEncoding(withAllowedCharacters: String.awsPathAllowed) ?? "" 168 | let query = query.addingPercentEncoding(withAllowedCharacters: String.awsQueryAllowed) ?? "" 169 | return [ 170 | method.rawValue, 171 | path, 172 | query, 173 | canonicalHeaders, 174 | "", 175 | signedHeaders, 176 | payloadHash 177 | ].joined(separator: "\n") 178 | } 179 | 180 | func dateStamp() -> String { 181 | let date = unitTestDate ?? Date() 182 | let dateFormatter = DateFormatter() 183 | dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) 184 | dateFormatter.dateFormat = "yyyyMMdd" 185 | return dateFormatter.string(from: date) 186 | } 187 | } 188 | 189 | extension AWSSignatureV4 { 190 | func generateHeadersToSign( 191 | headers: inout [String: String], 192 | host: String, 193 | hash: String 194 | ) { 195 | headers["Host"] = host 196 | headers["X-Amz-Date"] = amzDate 197 | 198 | if hash != "UNSIGNED-PAYLOAD" { 199 | headers["x-amz-content-sha256"] = hash 200 | } 201 | } 202 | 203 | func alphabetize(_ dict: [String: String]) -> [(key: String, value: String)] { 204 | return dict.sorted(by: { $0.0.lowercased() < $1.0.lowercased() }) 205 | } 206 | 207 | func createCanonicalHeaders(_ headers: [(key: String, value: String)]) -> String { 208 | return headers.map { 209 | "\($0.key.lowercased()):\($0.value)" 210 | }.joined(separator: "\n") 211 | } 212 | 213 | func createAuthorizationHeader( 214 | algorithm: String, 215 | credentialScope: String, 216 | signature: String, 217 | signedHeaders: String 218 | ) -> String { 219 | return "\(algorithm) Credential=\(accessKey)/\(credentialScope), SignedHeaders=\(signedHeaders), Signature=\(signature)" 220 | } 221 | } 222 | 223 | extension AWSSignatureV4 { 224 | /** 225 | Sign a request to be sent to an AWS API. 226 | - returns: 227 | A dictionary with headers to attach to a request 228 | - parameters: 229 | - payload: A hash of this data will be included in the headers 230 | - contentType: Mime type of file 231 | - method: Type of HTTP request 232 | - path: API call being referenced 233 | - query: Additional querystring in key-value format ("?key=value&key2=value2") 234 | - headers: HTTP headers added to the request 235 | */ 236 | public func sign( 237 | payload: Payload = .none, 238 | contentType: String, 239 | method: Method = .get, 240 | path: String, 241 | query: String? = nil, 242 | headers: [String: String] = [:] 243 | ) throws -> [String: String] { 244 | let algorithm = "AWS4-HMAC-SHA256" 245 | let credentialScope = getCredentialScope() 246 | let payloadHash = try payload.hashed() 247 | 248 | var headers = headers 249 | 250 | generateHeadersToSign(headers: &headers, host: host, hash: payloadHash) 251 | 252 | let sortedHeaders = alphabetize(headers) 253 | let signedHeaders = sortedHeaders.map { $0.key.lowercased() }.joined(separator: ";") 254 | let canonicalHeaders = createCanonicalHeaders(sortedHeaders) 255 | 256 | let canonicalRequest = try getCanonicalRequest( 257 | payloadHash: payloadHash, 258 | method: method, 259 | path: path, 260 | query: query ?? "", 261 | canonicalHeaders: canonicalHeaders, 262 | signedHeaders: signedHeaders 263 | ) 264 | 265 | let canonicalHash = try SHA256.hash(canonicalRequest).hexEncodedString() 266 | 267 | let stringToSign = getStringToSign( 268 | algorithm: algorithm, 269 | date: amzDate, 270 | scope: credentialScope, 271 | canonicalHash: canonicalHash 272 | ) 273 | 274 | let signature = try getSignature(stringToSign) 275 | 276 | let authorizationHeader = createAuthorizationHeader( 277 | algorithm: algorithm, 278 | credentialScope: credentialScope, 279 | signature: signature, 280 | signedHeaders: signedHeaders 281 | ) 282 | 283 | var requestHeaders: [String: String] = [ 284 | "X-Amz-Date": amzDate, 285 | "Content-Type": contentType, 286 | "x-amz-content-sha256": payloadHash, 287 | "Authorization": authorizationHeader, 288 | "Host": host 289 | ] 290 | 291 | headers.forEach { key, value in 292 | requestHeaders[key] = value 293 | } 294 | 295 | return requestHeaders 296 | } 297 | } 298 | 299 | public struct S3: Service { 300 | public enum Error: Swift.Error { 301 | case unimplemented 302 | case invalidPath 303 | case invalidResponse(status: HTTPStatus, reason: String) 304 | } 305 | 306 | let signer: AWSSignatureV4 307 | public var host: String 308 | 309 | public init( 310 | host: String, 311 | accessKey: String, 312 | secretKey: String, 313 | region: S3.Region 314 | ) { 315 | self.host = host 316 | signer = AWSSignatureV4( 317 | service: "s3", 318 | host: host, 319 | region: region, 320 | accessKey: accessKey, 321 | secretKey: secretKey 322 | ) 323 | } 324 | 325 | public func upload( 326 | bytes: Data, 327 | path: String, 328 | contentType: String = "application/x-www-form-urlencoded; charset=utf-8", 329 | access: AccessControlList = .publicRead, 330 | on container: Container 331 | ) throws -> Future { 332 | guard let url = URL(string: generateURL(for: path)) else { 333 | throw Error.invalidPath 334 | } 335 | 336 | let signedHeaders = try signer.sign( 337 | payload: .bytes(bytes), 338 | contentType: contentType, 339 | method: .put, 340 | path: path, 341 | headers: ["x-amz-acl": access.rawValue] 342 | ) 343 | 344 | var headers: HTTPHeaders = [:] 345 | signedHeaders.forEach { 346 | headers.add(name: $0.key, value: $0.value) 347 | } 348 | 349 | let client = try container.client() 350 | let req = Request(using: container) 351 | req.http.method = .PUT 352 | req.http.headers = headers 353 | req.http.body = HTTPBody(data: bytes) 354 | req.http.url = url 355 | return client.send(req).map { res in 356 | let http = res.http 357 | guard http.status == .ok else { 358 | throw Error.invalidResponse(status: http.status, reason: http.body.description) 359 | } 360 | return res 361 | } 362 | } 363 | 364 | public func get( 365 | path: String, 366 | contentType: String = "application/x-www-form-urlencoded; charset=utf-8", 367 | access: AccessControlList = .publicRead, 368 | on container: Container 369 | ) throws -> Future { 370 | guard let url = URL(string: generateURL(for: path)) else { 371 | throw Error.invalidPath 372 | } 373 | 374 | let signedHeaders = try signer.sign( 375 | contentType: contentType, 376 | method: .get, 377 | path: path, 378 | headers: ["x-amz-acl": access.rawValue] 379 | ) 380 | 381 | var headers: HTTPHeaders = [:] 382 | signedHeaders.forEach { 383 | headers.add(name: $0.key, value: $0.value) 384 | } 385 | 386 | let client = try container.client() 387 | let req = Request(using: container) 388 | req.http.method = .GET 389 | req.http.headers = headers 390 | req.http.url = url 391 | return client.send(req) 392 | } 393 | 394 | public func delete( 395 | path: String, 396 | contentType: String = "application/x-www-form-urlencoded; charset=utf-8", 397 | access: AccessControlList = .publicRead, 398 | on container: Container 399 | ) throws -> Future { 400 | guard let url = URL(string: generateURL(for: path)) else { 401 | throw Error.invalidPath 402 | } 403 | 404 | let signedHeaders = try signer.sign( 405 | contentType: contentType, 406 | method: .delete, 407 | path: path, 408 | headers: ["x-amz-acl": access.rawValue] 409 | ) 410 | 411 | var headers: HTTPHeaders = [:] 412 | signedHeaders.forEach { 413 | headers.add(name: $0.key, value: $0.value) 414 | } 415 | 416 | let client = try container.client() 417 | let req = Request(using: container) 418 | req.http.method = .DELETE 419 | req.http.headers = headers 420 | req.http.url = url 421 | return client.send(req) 422 | } 423 | } 424 | 425 | extension S3 { 426 | func generateURL(for path: String) -> String { 427 | return "https://\(host)\(path)" 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /Tests/StorageTests/AWSSignerTestSuite.swift: -------------------------------------------------------------------------------- 1 | /** 2 | All tests are based off of Amazon's Signature Test Suite 3 | See: http://docs.aws.amazon.com/general/latest/gr/signature-v4-test-suite.html 4 | 5 | They also include the [`x-amz-content-sha256` header](http://docs.aws.amazon.com/AmazonS3/latest/API/bucket-policy-s3-sigv4-conditions.html). 6 | */ 7 | 8 | import XCTest 9 | 10 | import HTTP 11 | import Foundation 12 | 13 | @testable import Storage 14 | 15 | class AWSSignerTestSuite: XCTestCase { 16 | static var allTests = [ 17 | ("testGetUnreserved", testGetUnreserved), 18 | ("testGetUTF8", testGetUTF8), 19 | ("testGetVanilla", testGetVanilla), 20 | ("testGetVanillaQuery", testGetVanillaQuery), 21 | ("testGetVanillaEmptyQueryKey", testGetVanillaEmptyQueryKey), 22 | ("testGetVanillaQueryUnreserved", testGetVanillaQueryUnreserved), 23 | ("testGetVanillaQueryUTF8", testGetVanillaQueryUTF8), 24 | ("testPostVanilla", testPostVanilla), 25 | ("testPostVanillaQuery", testPostVanillaQuery), 26 | ("testPostVanillaQueryNonunreserved", testPostVanillaQueryNonunreserved) 27 | ] 28 | 29 | static let dateFormatter: DateFormatter = { 30 | let _dateFormatter = DateFormatter() 31 | _dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) 32 | _dateFormatter.dateFormat = "YYYYMMdd'T'HHmmss'Z'" 33 | return _dateFormatter 34 | }() 35 | 36 | func testGetUnreserved() { 37 | let expectedCanonicalRequest = "GET\n/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 38 | 39 | let expectedCredentialScope = "20150830/us-east-1/service/aws4_request" 40 | 41 | let expectedCanonicalHeaders: [String : String] = [ 42 | "X-Amz-Date": "20150830T123600Z", 43 | "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=feae8f2b49f6807d4ca43941e2d6c7aacaca499df09935d14e97eed7647da5dc" 44 | ] 45 | 46 | let result = sign( 47 | method: .get, 48 | path: "/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 49 | ) 50 | result.expect( 51 | canonicalRequest: expectedCanonicalRequest, 52 | credentialScope: expectedCredentialScope, 53 | canonicalHeaders: expectedCanonicalHeaders 54 | ) 55 | } 56 | 57 | func testGetUTF8() { 58 | let expectedCanonicalRequest = "GET\n/%E1%88%B4\n\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 59 | 60 | let expectedCredentialScope = "20150830/us-east-1/service/aws4_request" 61 | 62 | let expectedCanonicalHeaders: [String : String] = [ 63 | "X-Amz-Date": "20150830T123600Z", 64 | "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=29d69532444b4f32a4c1b19af2afc116589685058ece54d8e43f0be05aeff6c0" 65 | ] 66 | 67 | let result = sign(method: .get, path: "/ሴ") 68 | result.expect( 69 | canonicalRequest: expectedCanonicalRequest, 70 | credentialScope: expectedCredentialScope, 71 | canonicalHeaders: expectedCanonicalHeaders 72 | ) 73 | } 74 | 75 | func testGetVanilla() { 76 | let expectedCanonicalRequest = "GET\n/\n\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 77 | 78 | let expectedCredentialScope = "20150830/us-east-1/service/aws4_request" 79 | 80 | let expectedCanonicalHeaders: [String : String] = [ 81 | "X-Amz-Date": "20150830T123600Z", 82 | "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=726c5c4879a6b4ccbbd3b24edbd6b8826d34f87450fbbf4e85546fc7ba9c1642" 83 | ] 84 | 85 | let result = sign(method: .get, path: "/") 86 | result.expect( 87 | canonicalRequest: expectedCanonicalRequest, 88 | credentialScope: expectedCredentialScope, 89 | canonicalHeaders: expectedCanonicalHeaders 90 | ) 91 | } 92 | 93 | //duplicate as `testGetVanilla`, but is in Amazon Test Suite 94 | //will keep until I figure out why there's a duplicate test 95 | func testGetVanillaQuery() { 96 | let expectedCanonicalRequest = "GET\n/\n\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 97 | 98 | let expectedCredentialScope = "20150830/us-east-1/service/aws4_request" 99 | 100 | let expectedCanonicalHeaders: [String : String] = [ 101 | "X-Amz-Date": "20150830T123600Z", 102 | "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=726c5c4879a6b4ccbbd3b24edbd6b8826d34f87450fbbf4e85546fc7ba9c1642" 103 | ] 104 | 105 | let result = sign(method: .get, path: "/") 106 | result.expect( 107 | canonicalRequest: expectedCanonicalRequest, 108 | credentialScope: expectedCredentialScope, 109 | canonicalHeaders: expectedCanonicalHeaders 110 | ) 111 | } 112 | 113 | func testGetVanillaEmptyQueryKey() { 114 | let expectedCanonicalRequest = "GET\n/\nParam1=value1\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 115 | 116 | let expectedCredentialScope = "20150830/us-east-1/service/aws4_request" 117 | 118 | let expectedCanonicalHeaders: [String : String] = [ 119 | "X-Amz-Date": "20150830T123600Z", 120 | "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=2287c0f96af21b7ccf3ee4a2905bcbb2d6f9a94c68d0849f3d1715ef003f2a05" 121 | ] 122 | 123 | let result = sign(method: .get, path: "/", query: "Param1=value1") 124 | result.expect( 125 | canonicalRequest: expectedCanonicalRequest, 126 | credentialScope: expectedCredentialScope, 127 | canonicalHeaders: expectedCanonicalHeaders 128 | ) 129 | } 130 | 131 | func testGetVanillaQueryUnreserved() { 132 | let expectedCanonicalRequest = "GET\n/\n-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 133 | 134 | let expectedCredentialScope = "20150830/us-east-1/service/aws4_request" 135 | 136 | let expectedCanonicalHeaders: [String : String] = [ 137 | "X-Amz-Date": "20150830T123600Z", 138 | "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=e86fe49a4c0dda9163bed3b1b40d530d872eb612e2c366de300bfefdf356fd6a" 139 | ] 140 | 141 | let result = sign( 142 | method: .get, 143 | path: "/", 144 | query:"-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 145 | ) 146 | result.expect( 147 | canonicalRequest: expectedCanonicalRequest, 148 | credentialScope: expectedCredentialScope, 149 | canonicalHeaders: expectedCanonicalHeaders 150 | ) 151 | } 152 | 153 | func testGetVanillaQueryUTF8() { 154 | let expectedCanonicalRequest = "GET\n/\n%E1%88%B4=bar\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 155 | 156 | let expectedCredentialScope = "20150830/us-east-1/service/aws4_request" 157 | 158 | let expectedCanonicalHeaders: [String : String] = [ 159 | "X-Amz-Date": "20150830T123600Z", 160 | "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=6753d65781ac8f6964cb6fb90445ee138d65d9663df21f28f478bd09add64fd8" 161 | ] 162 | 163 | let result = sign(method: .get, path: "/", query: "ሴ=bar") 164 | result.expect( 165 | canonicalRequest: expectedCanonicalRequest, 166 | credentialScope: expectedCredentialScope, 167 | canonicalHeaders: expectedCanonicalHeaders 168 | ) 169 | } 170 | 171 | func testPostVanilla() { 172 | let expectedCanonicalRequest = "POST\n/\n\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 173 | 174 | let expectedCredentialScope = "20150830/us-east-1/service/aws4_request" 175 | 176 | let expectedCanonicalHeaders: [String : String] = [ 177 | "X-Amz-Date": "20150830T123600Z", 178 | "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=3ad5e249949a59b862eedd9f1bf1ece4693c3042bf860ef5e3351b8925316f98" 179 | ] 180 | 181 | let result = sign(method: .post, path: "/") 182 | result.expect( 183 | canonicalRequest: expectedCanonicalRequest, 184 | credentialScope: expectedCredentialScope, 185 | canonicalHeaders: expectedCanonicalHeaders 186 | ) 187 | } 188 | 189 | func testPostVanillaQuery() { 190 | let expectedCanonicalRequest = "POST\n/\nParam1=value1\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 191 | 192 | let expectedCredentialScope = "20150830/us-east-1/service/aws4_request" 193 | 194 | let expectedCanonicalHeaders: [String : String] = [ 195 | "X-Amz-Date": "20150830T123600Z", 196 | "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=d43fd95e1dfefe02247ce8858649e1a063f9dd10f25f170f7ebda6ee3e9b6fbc" 197 | ] 198 | 199 | let result = sign(method: .post, path: "/", query: "Param1=value1") 200 | result.expect( 201 | canonicalRequest: expectedCanonicalRequest, 202 | credentialScope: expectedCredentialScope, 203 | canonicalHeaders: expectedCanonicalHeaders 204 | ) 205 | } 206 | 207 | /** 208 | This test isn't based on the test suite, but tracks handling of special characters. 209 | */ 210 | func testPostVanillaQueryNonunreserved() { 211 | let expectedCanonicalRequest = "POST\n/\n%40%23%24%25%5E&%2B=%2F%2C%3F%3E%3C%60%22%3B%3A%5C%7C%5D%5B%7B%7D\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 212 | 213 | let expectedCredentialScope = "20150830/us-east-1/service/aws4_request" 214 | 215 | let expectedCanonicalHeaders: [String : String] = [ 216 | "X-Amz-Date": "20150830T123600Z", 217 | "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=3db24d76713a5ccb9afe4a26acb83ae4cfa3e67d9e10f165bdf99bda199c625d" 218 | ] 219 | 220 | let result = sign(method: .post, path: "/", query: "@#$%^&+=/,?><`\";:\\|][{}") 221 | result.expect( 222 | canonicalRequest: expectedCanonicalRequest, 223 | credentialScope: expectedCredentialScope, 224 | canonicalHeaders: expectedCanonicalHeaders 225 | ) 226 | 227 | } 228 | } 229 | 230 | extension AWSSignerTestSuite { 231 | var testDate: Date { 232 | return AWSSignerTestSuite.dateFormatter.date(from: "20150830T123600Z")! 233 | } 234 | 235 | 236 | /** 237 | Preparation of data to sign a canonical request. 238 | 239 | Intended to handle the preparation in the AWSSignatureV4's `sign` function 240 | 241 | - returns: 242 | Hash value and multiple versions of headers 243 | 244 | - parameters: 245 | - auth: Signature struct to use for calculations 246 | - host: Hostname to sign for 247 | */ 248 | func prepCanonicalRequest(auth: AWSSignatureV4, host: String) -> (String, String, String) { 249 | let payloadHash = try! Payload.none.hashed() 250 | var headers = [String:String]() 251 | auth.generateHeadersToSign(headers: &headers, host: host, hash: payloadHash) 252 | 253 | let sortedHeaders = auth.alphabetize(headers) 254 | let signedHeaders = sortedHeaders.map { $0.key.lowercased() }.joined(separator: ";") 255 | let canonicalHeaders = auth.createCanonicalHeaders(sortedHeaders) 256 | return (payloadHash, signedHeaders, canonicalHeaders) 257 | } 258 | 259 | func sign( 260 | method: AWSSignatureV4.Method, 261 | path: String, 262 | query: String = "" 263 | ) -> SignerResult { 264 | let host = "example.amazonaws.com" 265 | var auth = AWSSignatureV4( 266 | service: "service", 267 | host: host, 268 | region: .usEast1, 269 | accessKey: "AKIDEXAMPLE", 270 | secretKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" 271 | ) 272 | 273 | auth.unitTestDate = testDate 274 | let (payloadHash, signedHeaders, preppedCanonicalHeaders) = prepCanonicalRequest(auth: auth, host: host) 275 | let canonicalRequest = try! auth.getCanonicalRequest(payloadHash: payloadHash, method: method, path: path, query: query, canonicalHeaders: preppedCanonicalHeaders, signedHeaders: signedHeaders) 276 | 277 | 278 | let credentialScope = auth.getCredentialScope() 279 | 280 | //FIXME(Brett): handle throwing 281 | let canonicalHeaders = try! auth.sign( 282 | payload: .none, 283 | contentType: "image/jpeg", 284 | method: method, 285 | path: path, 286 | query: query 287 | ) 288 | 289 | return SignerResult( 290 | canonicalRequest: canonicalRequest, 291 | credentialScope: credentialScope, 292 | canonicalHeaders: canonicalHeaders 293 | ) 294 | } 295 | } 296 | 297 | struct SignerResult { 298 | let canonicalRequest: String 299 | let credentialScope: String 300 | let canonicalHeaders: [String: String] 301 | } 302 | 303 | extension SignerResult { 304 | func expect( 305 | canonicalRequest: String, 306 | credentialScope: String, 307 | canonicalHeaders: [String: String], 308 | file: StaticString = #file, 309 | line: UInt = #line 310 | ) { 311 | XCTAssertEqual(self.canonicalRequest, canonicalRequest, file: file, line: line) 312 | XCTAssertEqual(self.credentialScope, credentialScope, file: file, line: line) 313 | 314 | canonicalHeaders.forEach { 315 | if $0.key == "Authorization" { 316 | for (givenLine, expectedLine) in zip(self.canonicalHeaders[$0.key]!.components(separatedBy: " "), $0.value.components(separatedBy: " ")) { 317 | XCTAssertEqual(givenLine, expectedLine) 318 | } 319 | } else { 320 | XCTAssertEqual(self.canonicalHeaders[$0.key], $0.value, file: file, line: line) 321 | } 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /Sources/Storage/Utilities/Mime.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Generated using the following link and lots of love 3 | http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types 4 | */ 5 | 6 | func getExtension(for mime: String) -> String? { 7 | switch mime { 8 | case "application/andrew-inset": 9 | return "ez" 10 | case "application/applixware": 11 | return "aw" 12 | case "application/atom+xml": 13 | return "atom" 14 | case "application/atomcat+xml": 15 | return "atomcat" 16 | case "application/atomsvc+xml": 17 | return "atomsvc" 18 | case "application/ccxml+xml": 19 | return "ccxml" 20 | case "application/cdmi-capability": 21 | return "cdmia" 22 | case "application/cdmi-container": 23 | return "cdmic" 24 | case "application/cdmi-domain": 25 | return "cdmid" 26 | case "application/cdmi-object": 27 | return "cdmio" 28 | case "application/cdmi-queue": 29 | return "cdmiq" 30 | case "application/cu-seeme": 31 | return "cu" 32 | case "application/davmount+xml": 33 | return "davmount" 34 | case "application/docbook+xml": 35 | return "dbk" 36 | case "application/dssc+der": 37 | return "dssc" 38 | case "application/dssc+xml": 39 | return "xdssc" 40 | case "application/ecmascript": 41 | return "ecma" 42 | case "application/emma+xml": 43 | return "emma" 44 | case "application/epub+zip": 45 | return "epub" 46 | case "application/exi": 47 | return "exi" 48 | case "application/font-tdpfr": 49 | return "pfr" 50 | case "application/gml+xml": 51 | return "gml" 52 | case "application/gpx+xml": 53 | return "gpx" 54 | case "application/gxf": 55 | return "gxf" 56 | case "application/hyperstudio": 57 | return "stk" 58 | case "application/inkml+xml": 59 | return "ink" 60 | case "application/ipfix": 61 | return "ipfix" 62 | case "application/java-archive": 63 | return "jar" 64 | case "application/java-serialized-object": 65 | return "ser" 66 | case "application/java-vm": 67 | return "class" 68 | case "application/javascript": 69 | return "js" 70 | case "application/json": 71 | return "json" 72 | case "application/jsonml+json": 73 | return "jsonml" 74 | case "application/lost+xml": 75 | return "lostxml" 76 | case "application/mac-binhex40": 77 | return "hqx" 78 | case "application/mac-compactpro": 79 | return "cpt" 80 | case "application/mads+xml": 81 | return "mads" 82 | case "application/marc": 83 | return "mrc" 84 | case "application/marcxml+xml": 85 | return "mrcx" 86 | case "application/mathematica": 87 | return "ma" 88 | case "application/mathml+xml": 89 | return "mathml" 90 | case "application/mbox": 91 | return "mbox" 92 | case "application/mediaservercontrol+xml": 93 | return "mscml" 94 | case "application/metalink+xml": 95 | return "metalink" 96 | case "application/metalink4+xml": 97 | return "meta4" 98 | case "application/mets+xml": 99 | return "mets" 100 | case "application/mods+xml": 101 | return "mods" 102 | case "application/mp21": 103 | return "m21" 104 | case "application/mp4": 105 | return "mp4s" 106 | case "application/msword": 107 | return "doc" 108 | case "application/mxf": 109 | return "mxf" 110 | case "application/octet-stream": 111 | return "bin" 112 | case "application/oda": 113 | return "oda" 114 | case "application/oebps-package+xml": 115 | return "opf" 116 | case "application/ogg": 117 | return "ogx" 118 | case "application/omdoc+xml": 119 | return "omdoc" 120 | case "application/onenote": 121 | return "onetoc" 122 | case "application/oxps": 123 | return "oxps" 124 | case "application/patch-ops-error+xml": 125 | return "xer" 126 | case "application/pdf": 127 | return "pdf" 128 | case "application/pgp-encrypted": 129 | return "pgp" 130 | case "application/pgp-signature": 131 | return "asc" 132 | case "application/pics-rules": 133 | return "prf" 134 | case "application/pkcs10": 135 | return "p10" 136 | case "application/pkcs7-mime": 137 | return "p7m" 138 | case "application/pkcs7-signature": 139 | return "p7s" 140 | case "application/pkcs8": 141 | return "p8" 142 | case "application/pkix-attr-cert": 143 | return "ac" 144 | case "application/pkix-cert": 145 | return "cer" 146 | case "application/pkix-crl": 147 | return "crl" 148 | case "application/pkix-pkipath": 149 | return "pkipath" 150 | case "application/pkixcmp": 151 | return "pki" 152 | case "application/pls+xml": 153 | return "pls" 154 | case "application/postscript": 155 | return "ai" 156 | case "application/prs.cww": 157 | return "cww" 158 | case "application/pskc+xml": 159 | return "pskcxml" 160 | case "application/rdf+xml": 161 | return "rdf" 162 | case "application/reginfo+xml": 163 | return "rif" 164 | case "application/relax-ng-compact-syntax": 165 | return "rnc" 166 | case "application/resource-lists+xml": 167 | return "rl" 168 | case "application/resource-lists-diff+xml": 169 | return "rld" 170 | case "application/rls-services+xml": 171 | return "rs" 172 | case "application/rpki-ghostbusters": 173 | return "gbr" 174 | case "application/rpki-manifest": 175 | return "mft" 176 | case "application/rpki-roa": 177 | return "roa" 178 | case "application/rsd+xml": 179 | return "rsd" 180 | case "application/rss+xml": 181 | return "rss" 182 | case "application/rtf": 183 | return "rtf" 184 | case "application/sbml+xml": 185 | return "sbml" 186 | case "application/scvp-cv-request": 187 | return "scq" 188 | case "application/scvp-cv-response": 189 | return "scs" 190 | case "application/scvp-vp-request": 191 | return "spq" 192 | case "application/scvp-vp-response": 193 | return "spp" 194 | case "application/sdp": 195 | return "sdp" 196 | case "application/set-payment-initiation": 197 | return "setpay" 198 | case "application/set-registration-initiation": 199 | return "setreg" 200 | case "application/shf+xml": 201 | return "shf" 202 | case "application/smil+xml": 203 | return "smi" 204 | case "application/sparql-query": 205 | return "rq" 206 | case "application/sparql-results+xml": 207 | return "srx" 208 | case "application/srgs": 209 | return "gram" 210 | case "application/srgs+xml": 211 | return "grxml" 212 | case "application/sru+xml": 213 | return "sru" 214 | case "application/ssdl+xml": 215 | return "ssdl" 216 | case "application/ssml+xml": 217 | return "ssml" 218 | case "application/tei+xml": 219 | return "tei" 220 | case "application/thraud+xml": 221 | return "tfi" 222 | case "application/timestamped-data": 223 | return "tsd" 224 | case "application/vnd.3gpp.pic-bw-large": 225 | return "plb" 226 | case "application/vnd.3gpp.pic-bw-small": 227 | return "psb" 228 | case "application/vnd.3gpp.pic-bw-var": 229 | return "pvb" 230 | case "application/vnd.3gpp2.tcap": 231 | return "tcap" 232 | case "application/vnd.3m.post-it-notes": 233 | return "pwn" 234 | case "application/vnd.accpac.simply.aso": 235 | return "aso" 236 | case "application/vnd.accpac.simply.imp": 237 | return "imp" 238 | case "application/vnd.acucobol": 239 | return "acu" 240 | case "application/vnd.acucorp": 241 | return "atc" 242 | case "application/vnd.adobe.air-application-installer-package+zip": 243 | return "air" 244 | case "application/vnd.adobe.formscentral.fcdt": 245 | return "fcdt" 246 | case "application/vnd.adobe.fxp": 247 | return "fxp" 248 | case "application/vnd.adobe.xdp+xml": 249 | return "xdp" 250 | case "application/vnd.adobe.xfdf": 251 | return "xfdf" 252 | case "application/vnd.ahead.space": 253 | return "ahead" 254 | case "application/vnd.airzip.filesecure.azf": 255 | return "azf" 256 | case "application/vnd.airzip.filesecure.azs": 257 | return "azs" 258 | case "application/vnd.amazon.ebook": 259 | return "azw" 260 | case "application/vnd.americandynamics.acc": 261 | return "acc" 262 | case "application/vnd.amiga.ami": 263 | return "ami" 264 | case "application/vnd.android.package-archive": 265 | return "apk" 266 | case "application/vnd.anser-web-certificate-issue-initiation": 267 | return "cii" 268 | case "application/vnd.anser-web-funds-transfer-initiation": 269 | return "fti" 270 | case "application/vnd.antix.game-component": 271 | return "atx" 272 | case "application/vnd.apple.installer+xml": 273 | return "mpkg" 274 | case "application/vnd.apple.mpegurl": 275 | return "m3u8" 276 | case "application/vnd.aristanetworks.swi": 277 | return "swi" 278 | case "application/vnd.astraea-software.iota": 279 | return "iota" 280 | case "application/vnd.audiograph": 281 | return "aep" 282 | case "application/vnd.blueice.multipass": 283 | return "mpm" 284 | case "application/vnd.bmi": 285 | return "bmi" 286 | case "application/vnd.businessobjects": 287 | return "rep" 288 | case "application/vnd.chemdraw+xml": 289 | return "cdxml" 290 | case "application/vnd.chipnuts.karaoke-mmd": 291 | return "mmd" 292 | case "application/vnd.cinderella": 293 | return "cdy" 294 | case "application/vnd.claymore": 295 | return "cla" 296 | case "application/vnd.cloanto.rp9": 297 | return "rp9" 298 | case "application/vnd.clonk.c4group": 299 | return "c4g" 300 | case "application/vnd.cluetrust.cartomobile-config": 301 | return "c11amc" 302 | case "application/vnd.cluetrust.cartomobile-config-pkg": 303 | return "c11amz" 304 | case "application/vnd.commonspace": 305 | return "csp" 306 | case "application/vnd.contact.cmsg": 307 | return "cdbcmsg" 308 | case "application/vnd.cosmocaller": 309 | return "cmc" 310 | case "application/vnd.crick.clicker": 311 | return "clkx" 312 | case "application/vnd.crick.clicker.keyboard": 313 | return "clkk" 314 | case "application/vnd.crick.clicker.palette": 315 | return "clkp" 316 | case "application/vnd.crick.clicker.template": 317 | return "clkt" 318 | case "application/vnd.crick.clicker.wordbank": 319 | return "clkw" 320 | case "application/vnd.criticaltools.wbs+xml": 321 | return "wbs" 322 | case "application/vnd.ctc-posml": 323 | return "pml" 324 | case "application/vnd.cups-ppd": 325 | return "ppd" 326 | case "application/vnd.curl.car": 327 | return "car" 328 | case "application/vnd.curl.pcurl": 329 | return "pcurl" 330 | case "application/vnd.dart": 331 | return "dart" 332 | case "application/vnd.data-vision.rdz": 333 | return "rdz" 334 | case "application/vnd.dece.data": 335 | return "uvf" 336 | case "application/vnd.dece.ttml+xml": 337 | return "uvt" 338 | case "application/vnd.dece.unspecified": 339 | return "uvx" 340 | case "application/vnd.dece.zip": 341 | return "uvz" 342 | case "application/vnd.denovo.fcselayout-link": 343 | return "fe_launch" 344 | case "application/vnd.dna": 345 | return "dna" 346 | case "application/vnd.dolby.mlp": 347 | return "mlp" 348 | case "application/vnd.dpgraph": 349 | return "dpg" 350 | case "application/vnd.dreamfactory": 351 | return "dfac" 352 | case "application/vnd.ds-keypoint": 353 | return "kpxx" 354 | case "application/vnd.dvb.ait": 355 | return "ait" 356 | case "application/vnd.dvb.service": 357 | return "svc" 358 | case "application/vnd.dynageo": 359 | return "geo" 360 | case "application/vnd.ecowin.chart": 361 | return "mag" 362 | case "application/vnd.enliven": 363 | return "nml" 364 | case "application/vnd.epson.esf": 365 | return "esf" 366 | case "application/vnd.epson.msf": 367 | return "msf" 368 | case "application/vnd.epson.quickanime": 369 | return "qam" 370 | case "application/vnd.epson.salt": 371 | return "slt" 372 | case "application/vnd.epson.ssf": 373 | return "ssf" 374 | case "application/vnd.eszigno3+xml": 375 | return "es3" 376 | case "application/vnd.ezpix-album": 377 | return "ez2" 378 | case "application/vnd.ezpix-package": 379 | return "ez3" 380 | case "application/vnd.fdf": 381 | return "fdf" 382 | case "application/vnd.fdsn.mseed": 383 | return "mseed" 384 | case "application/vnd.fdsn.seed": 385 | return "seed" 386 | case "application/vnd.flographit": 387 | return "gph" 388 | case "application/vnd.fluxtime.clip": 389 | return "ftc" 390 | case "application/vnd.framemaker": 391 | return "fm" 392 | case "application/vnd.frogans.fnc": 393 | return "fnc" 394 | case "application/vnd.frogans.ltf": 395 | return "ltf" 396 | case "application/vnd.fsc.weblaunch": 397 | return "fsc" 398 | case "application/vnd.fujitsu.oasys": 399 | return "oas" 400 | case "application/vnd.fujitsu.oasys2": 401 | return "oa2" 402 | case "application/vnd.fujitsu.oasys3": 403 | return "oa3" 404 | case "application/vnd.fujitsu.oasysgp": 405 | return "fg5" 406 | case "application/vnd.fujitsu.oasysprs": 407 | return "bh2" 408 | case "application/vnd.fujixerox.ddd": 409 | return "ddd" 410 | case "application/vnd.fujixerox.docuworks": 411 | return "xdw" 412 | case "application/vnd.fujixerox.docuworks.binder": 413 | return "xbd" 414 | case "application/vnd.fuzzysheet": 415 | return "fzs" 416 | case "application/vnd.genomatix.tuxedo": 417 | return "txd" 418 | case "application/vnd.geogebra.file": 419 | return "ggb" 420 | case "application/vnd.geogebra.tool": 421 | return "ggt" 422 | case "application/vnd.geometry-explorer": 423 | return "gex" 424 | case "application/vnd.geonext": 425 | return "gxt" 426 | case "application/vnd.geoplan": 427 | return "g2w" 428 | case "application/vnd.geospace": 429 | return "g3w" 430 | case "application/vnd.gmx": 431 | return "gmx" 432 | case "application/vnd.google-earth.kml+xml": 433 | return "kml" 434 | case "application/vnd.google-earth.kmz": 435 | return "kmz" 436 | case "application/vnd.grafeq": 437 | return "gqf" 438 | case "application/vnd.groove-account": 439 | return "gac" 440 | case "application/vnd.groove-help": 441 | return "ghf" 442 | case "application/vnd.groove-identity-message": 443 | return "gim" 444 | case "application/vnd.groove-injector": 445 | return "grv" 446 | case "application/vnd.groove-tool-message": 447 | return "gtm" 448 | case "application/vnd.groove-tool-template": 449 | return "tpl" 450 | case "application/vnd.groove-vcard": 451 | return "vcg" 452 | case "application/vnd.hal+xml": 453 | return "hal" 454 | case "application/vnd.handheld-entertainment+xml": 455 | return "zmm" 456 | case "application/vnd.hbci": 457 | return "hbci" 458 | case "application/vnd.hhe.lesson-player": 459 | return "les" 460 | case "application/vnd.hp-hpgl": 461 | return "hpgl" 462 | case "application/vnd.hp-hpid": 463 | return "hpid" 464 | case "application/vnd.hp-hps": 465 | return "hps" 466 | case "application/vnd.hp-jlyt": 467 | return "jlt" 468 | case "application/vnd.hp-pcl": 469 | return "pcl" 470 | case "application/vnd.hp-pclxl": 471 | return "pclxl" 472 | case "application/vnd.hydrostatix.sof-data": 473 | return "sfd-hdstx" 474 | case "application/vnd.ibm.minipay": 475 | return "mpy" 476 | case "application/vnd.ibm.modcap": 477 | return "afp" 478 | case "application/vnd.ibm.rights-management": 479 | return "irm" 480 | case "application/vnd.ibm.secure-container": 481 | return "sc" 482 | case "application/vnd.iccprofile": 483 | return "icc" 484 | case "application/vnd.igloader": 485 | return "igl" 486 | case "application/vnd.immervision-ivp": 487 | return "ivp" 488 | case "application/vnd.immervision-ivu": 489 | return "ivu" 490 | case "application/vnd.insors.igm": 491 | return "igm" 492 | case "application/vnd.intercon.formnet": 493 | return "xpw" 494 | case "application/vnd.intergeo": 495 | return "i2g" 496 | case "application/vnd.intu.qbo": 497 | return "qbo" 498 | case "application/vnd.intu.qfx": 499 | return "qfx" 500 | case "application/vnd.ipunplugged.rcprofile": 501 | return "rcprofile" 502 | case "application/vnd.irepository.package+xml": 503 | return "irp" 504 | case "application/vnd.is-xpr": 505 | return "xpr" 506 | case "application/vnd.isac.fcs": 507 | return "fcs" 508 | case "application/vnd.jam": 509 | return "jam" 510 | case "application/vnd.jcp.javame.midlet-rms": 511 | return "rms" 512 | case "application/vnd.jisp": 513 | return "jisp" 514 | case "application/vnd.joost.joda-archive": 515 | return "joda" 516 | case "application/vnd.kahootz": 517 | return "ktz" 518 | case "application/vnd.kde.karbon": 519 | return "karbon" 520 | case "application/vnd.kde.kchart": 521 | return "chrt" 522 | case "application/vnd.kde.kformula": 523 | return "kfo" 524 | case "application/vnd.kde.kivio": 525 | return "flw" 526 | case "application/vnd.kde.kontour": 527 | return "kon" 528 | case "application/vnd.kde.kpresenter": 529 | return "kpr" 530 | case "application/vnd.kde.kspread": 531 | return "ksp" 532 | case "application/vnd.kde.kword": 533 | return "kwd" 534 | case "application/vnd.kenameaapp": 535 | return "htke" 536 | case "application/vnd.kidspiration": 537 | return "kia" 538 | case "application/vnd.kinar": 539 | return "kne" 540 | case "application/vnd.koan": 541 | return "skp" 542 | case "application/vnd.kodak-descriptor": 543 | return "sse" 544 | case "application/vnd.las.las+xml": 545 | return "lasxml" 546 | case "application/vnd.llamagraphics.life-balance.desktop": 547 | return "lbd" 548 | case "application/vnd.llamagraphics.life-balance.exchange+xml": 549 | return "lbe" 550 | case "application/vnd.lotus-1-2-3": 551 | return "123" 552 | case "application/vnd.lotus-approach": 553 | return "apr" 554 | case "application/vnd.lotus-freelance": 555 | return "pre" 556 | case "application/vnd.lotus-notes": 557 | return "nsf" 558 | case "application/vnd.lotus-organizer": 559 | return "org" 560 | case "application/vnd.lotus-screencam": 561 | return "scm" 562 | case "application/vnd.lotus-wordpro": 563 | return "lwp" 564 | case "application/vnd.macports.portpkg": 565 | return "portpkg" 566 | case "application/vnd.mcd": 567 | return "mcd" 568 | case "application/vnd.medcalcdata": 569 | return "mc1" 570 | case "application/vnd.mediastation.cdkey": 571 | return "cdkey" 572 | case "application/vnd.mfer": 573 | return "mwf" 574 | case "application/vnd.mfmp": 575 | return "mfm" 576 | case "application/vnd.micrografx.flo": 577 | return "flo" 578 | case "application/vnd.micrografx.igx": 579 | return "igx" 580 | case "application/vnd.mif": 581 | return "mif" 582 | case "application/vnd.mobius.daf": 583 | return "daf" 584 | case "application/vnd.mobius.dis": 585 | return "dis" 586 | case "application/vnd.mobius.mbk": 587 | return "mbk" 588 | case "application/vnd.mobius.mqy": 589 | return "mqy" 590 | case "application/vnd.mobius.msl": 591 | return "msl" 592 | case "application/vnd.mobius.plc": 593 | return "plc" 594 | case "application/vnd.mobius.txf": 595 | return "txf" 596 | case "application/vnd.mophun.application": 597 | return "mpn" 598 | case "application/vnd.mophun.certificate": 599 | return "mpc" 600 | case "application/vnd.mozilla.xul+xml": 601 | return "xul" 602 | case "application/vnd.ms-artgalry": 603 | return "cil" 604 | case "application/vnd.ms-cab-compressed": 605 | return "cab" 606 | case "application/vnd.ms-excel": 607 | return "xls" 608 | case "application/vnd.ms-excel.addin.macroenabled.12": 609 | return "xlam" 610 | case "application/vnd.ms-excel.sheet.binary.macroenabled.12": 611 | return "xlsb" 612 | case "application/vnd.ms-excel.sheet.macroenabled.12": 613 | return "xlsm" 614 | case "application/vnd.ms-excel.template.macroenabled.12": 615 | return "xltm" 616 | case "application/vnd.ms-fontobject": 617 | return "eot" 618 | case "application/vnd.ms-htmlhelp": 619 | return "chm" 620 | case "application/vnd.ms-ims": 621 | return "ims" 622 | case "application/vnd.ms-lrm": 623 | return "lrm" 624 | case "application/vnd.ms-officetheme": 625 | return "thmx" 626 | case "application/vnd.ms-pki.seccat": 627 | return "cat" 628 | case "application/vnd.ms-pki.stl": 629 | return "stl" 630 | case "application/vnd.ms-powerpoint": 631 | return "ppt" 632 | case "application/vnd.ms-powerpoint.addin.macroenabled.12": 633 | return "ppam" 634 | case "application/vnd.ms-powerpoint.presentation.macroenabled.12": 635 | return "pptm" 636 | case "application/vnd.ms-powerpoint.slide.macroenabled.12": 637 | return "sldm" 638 | case "application/vnd.ms-powerpoint.slideshow.macroenabled.12": 639 | return "ppsm" 640 | case "application/vnd.ms-powerpoint.template.macroenabled.12": 641 | return "potm" 642 | case "application/vnd.ms-project": 643 | return "mpp" 644 | case "application/vnd.ms-word.document.macroenabled.12": 645 | return "docm" 646 | case "application/vnd.ms-word.template.macroenabled.12": 647 | return "dotm" 648 | case "application/vnd.ms-works": 649 | return "wps" 650 | case "application/vnd.ms-wpl": 651 | return "wpl" 652 | case "application/vnd.ms-xpsdocument": 653 | return "xps" 654 | case "application/vnd.mseq": 655 | return "mseq" 656 | case "application/vnd.musician": 657 | return "mus" 658 | case "application/vnd.muvee.style": 659 | return "msty" 660 | case "application/vnd.mynfc": 661 | return "taglet" 662 | case "application/vnd.neurolanguage.nlu": 663 | return "nlu" 664 | case "application/vnd.nitf": 665 | return "ntf" 666 | case "application/vnd.noblenet-directory": 667 | return "nnd" 668 | case "application/vnd.noblenet-sealer": 669 | return "nns" 670 | case "application/vnd.noblenet-web": 671 | return "nnw" 672 | case "application/vnd.nokia.n-gage.data": 673 | return "ngdat" 674 | case "application/vnd.nokia.n-gage.symbian.install": 675 | return "n-gage" 676 | case "application/vnd.nokia.radio-preset": 677 | return "rpst" 678 | case "application/vnd.nokia.radio-presets": 679 | return "rpss" 680 | case "application/vnd.novadigm.edm": 681 | return "edm" 682 | case "application/vnd.novadigm.edx": 683 | return "edx" 684 | case "application/vnd.novadigm.ext": 685 | return "ext" 686 | case "application/vnd.oasis.opendocument.chart": 687 | return "odc" 688 | case "application/vnd.oasis.opendocument.chart-template": 689 | return "otc" 690 | case "application/vnd.oasis.opendocument.database": 691 | return "odb" 692 | case "application/vnd.oasis.opendocument.formula": 693 | return "odf" 694 | case "application/vnd.oasis.opendocument.formula-template": 695 | return "odft" 696 | case "application/vnd.oasis.opendocument.graphics": 697 | return "odg" 698 | case "application/vnd.oasis.opendocument.graphics-template": 699 | return "otg" 700 | case "application/vnd.oasis.opendocument.image": 701 | return "odi" 702 | case "application/vnd.oasis.opendocument.image-template": 703 | return "oti" 704 | case "application/vnd.oasis.opendocument.presentation": 705 | return "odp" 706 | case "application/vnd.oasis.opendocument.presentation-template": 707 | return "otp" 708 | case "application/vnd.oasis.opendocument.spreadsheet": 709 | return "ods" 710 | case "application/vnd.oasis.opendocument.spreadsheet-template": 711 | return "ots" 712 | case "application/vnd.oasis.opendocument.text": 713 | return "odt" 714 | case "application/vnd.oasis.opendocument.text-master": 715 | return "odm" 716 | case "application/vnd.oasis.opendocument.text-template": 717 | return "ott" 718 | case "application/vnd.oasis.opendocument.text-web": 719 | return "oth" 720 | case "application/vnd.olpc-sugar": 721 | return "xo" 722 | case "application/vnd.oma.dd2+xml": 723 | return "dd2" 724 | case "application/vnd.openofficeorg.extension": 725 | return "oxt" 726 | case "application/vnd.openxmlformats-officedocument.presentationml.presentation": 727 | return "pptx" 728 | case "application/vnd.openxmlformats-officedocument.presentationml.slide": 729 | return "sldx" 730 | case "application/vnd.openxmlformats-officedocument.presentationml.slideshow": 731 | return "ppsx" 732 | case "application/vnd.openxmlformats-officedocument.presentationml.template": 733 | return "potx" 734 | case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": 735 | return "xlsx" 736 | case "application/vnd.openxmlformats-officedocument.spreadsheetml.template": 737 | return "xltx" 738 | case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": 739 | return "docx" 740 | case "application/vnd.openxmlformats-officedocument.wordprocessingml.template": 741 | return "dotx" 742 | case "application/vnd.osgeo.mapguide.package": 743 | return "mgp" 744 | case "application/vnd.osgi.dp": 745 | return "dp" 746 | case "application/vnd.osgi.subsystem": 747 | return "esa" 748 | case "application/vnd.palm": 749 | return "pdb" 750 | case "application/vnd.pawaafile": 751 | return "paw" 752 | case "application/vnd.pg.format": 753 | return "str" 754 | case "application/vnd.pg.osasli": 755 | return "ei6" 756 | case "application/vnd.picsel": 757 | return "efif" 758 | case "application/vnd.pmi.widget": 759 | return "wg" 760 | case "application/vnd.pocketlearn": 761 | return "plf" 762 | case "application/vnd.powerbuilder6": 763 | return "pbd" 764 | case "application/vnd.previewsystems.box": 765 | return "box" 766 | case "application/vnd.proteus.magazine": 767 | return "mgz" 768 | case "application/vnd.publishare-delta-tree": 769 | return "qps" 770 | case "application/vnd.pvi.ptid1": 771 | return "ptid" 772 | case "application/vnd.quark.quarkxpress": 773 | return "qxd" 774 | case "application/vnd.realvnc.bed": 775 | return "bed" 776 | case "application/vnd.recordare.musicxml": 777 | return "mxl" 778 | case "application/vnd.recordare.musicxml+xml": 779 | return "musicxml" 780 | case "application/vnd.rig.cryptonote": 781 | return "cryptonote" 782 | case "application/vnd.rim.cod": 783 | return "cod" 784 | case "application/vnd.rn-realmedia": 785 | return "rm" 786 | case "application/vnd.rn-realmedia-vbr": 787 | return "rmvb" 788 | case "application/vnd.route66.link66+xml": 789 | return "link66" 790 | case "application/vnd.sailingtracker.track": 791 | return "st" 792 | case "application/vnd.seemail": 793 | return "see" 794 | case "application/vnd.sema": 795 | return "sema" 796 | case "application/vnd.semd": 797 | return "semd" 798 | case "application/vnd.semf": 799 | return "semf" 800 | case "application/vnd.shana.informed.formdata": 801 | return "ifm" 802 | case "application/vnd.shana.informed.formtemplate": 803 | return "itp" 804 | case "application/vnd.shana.informed.interchange": 805 | return "iif" 806 | case "application/vnd.shana.informed.package": 807 | return "ipk" 808 | case "application/vnd.simtech-mindmapper": 809 | return "twd" 810 | case "application/vnd.smaf": 811 | return "mmf" 812 | case "application/vnd.smart.teacher": 813 | return "teacher" 814 | case "application/vnd.solent.sdkm+xml": 815 | return "sdkm" 816 | case "application/vnd.spotfire.dxp": 817 | return "dxp" 818 | case "application/vnd.spotfire.sfs": 819 | return "sfs" 820 | case "application/vnd.stardivision.calc": 821 | return "sdc" 822 | case "application/vnd.stardivision.draw": 823 | return "sda" 824 | case "application/vnd.stardivision.impress": 825 | return "sdd" 826 | case "application/vnd.stardivision.math": 827 | return "smf" 828 | case "application/vnd.stardivision.writer": 829 | return "sdw" 830 | case "application/vnd.stardivision.writer-global": 831 | return "sgl" 832 | case "application/vnd.stepmania.package": 833 | return "smzip" 834 | case "application/vnd.stepmania.stepchart": 835 | return "sm" 836 | case "application/vnd.sun.xml.calc": 837 | return "sxc" 838 | case "application/vnd.sun.xml.calc.template": 839 | return "stc" 840 | case "application/vnd.sun.xml.draw": 841 | return "sxd" 842 | case "application/vnd.sun.xml.draw.template": 843 | return "std" 844 | case "application/vnd.sun.xml.impress": 845 | return "sxi" 846 | case "application/vnd.sun.xml.impress.template": 847 | return "sti" 848 | case "application/vnd.sun.xml.math": 849 | return "sxm" 850 | case "application/vnd.sun.xml.writer": 851 | return "sxw" 852 | case "application/vnd.sun.xml.writer.global": 853 | return "sxg" 854 | case "application/vnd.sun.xml.writer.template": 855 | return "stw" 856 | case "application/vnd.sus-calendar": 857 | return "sus" 858 | case "application/vnd.svd": 859 | return "svd" 860 | case "application/vnd.symbian.install": 861 | return "sis" 862 | case "application/vnd.syncml+xml": 863 | return "xsm" 864 | case "application/vnd.syncml.dm+wbxml": 865 | return "bdm" 866 | case "application/vnd.syncml.dm+xml": 867 | return "xdm" 868 | case "application/vnd.tao.intent-module-archive": 869 | return "tao" 870 | case "application/vnd.tcpdump.pcap": 871 | return "pcap" 872 | case "application/vnd.tmobile-livetv": 873 | return "tmo" 874 | case "application/vnd.trid.tpt": 875 | return "tpt" 876 | case "application/vnd.triscape.mxs": 877 | return "mxs" 878 | case "application/vnd.trueapp": 879 | return "tra" 880 | case "application/vnd.ufdl": 881 | return "ufd" 882 | case "application/vnd.uiq.theme": 883 | return "utz" 884 | case "application/vnd.umajin": 885 | return "umj" 886 | case "application/vnd.unity": 887 | return "unityweb" 888 | case "application/vnd.uoml+xml": 889 | return "uoml" 890 | case "application/vnd.vcx": 891 | return "vcx" 892 | case "application/vnd.visio": 893 | return "vsd" 894 | case "application/vnd.visionary": 895 | return "vis" 896 | case "application/vnd.vsf": 897 | return "vsf" 898 | case "application/vnd.wap.wbxml": 899 | return "wbxml" 900 | case "application/vnd.wap.wmlc": 901 | return "wmlc" 902 | case "application/vnd.wap.wmlscriptc": 903 | return "wmlsc" 904 | case "application/vnd.webturbo": 905 | return "wtb" 906 | case "application/vnd.wolfram.player": 907 | return "nbp" 908 | case "application/vnd.wordperfect": 909 | return "wpd" 910 | case "application/vnd.wqd": 911 | return "wqd" 912 | case "application/vnd.wt.stf": 913 | return "stf" 914 | case "application/vnd.xara": 915 | return "xar" 916 | case "application/vnd.xfdl": 917 | return "xfdl" 918 | case "application/vnd.yamaha.hv-dic": 919 | return "hvd" 920 | case "application/vnd.yamaha.hv-script": 921 | return "hvs" 922 | case "application/vnd.yamaha.hv-voice": 923 | return "hvp" 924 | case "application/vnd.yamaha.openscoreformat": 925 | return "osf" 926 | case "application/vnd.yamaha.openscoreformat.osfpvg+xml": 927 | return "osfpvg" 928 | case "application/vnd.yamaha.smaf-audio": 929 | return "saf" 930 | case "application/vnd.yamaha.smaf-phrase": 931 | return "spf" 932 | case "application/vnd.yellowriver-custom-menu": 933 | return "cmp" 934 | case "application/vnd.zul": 935 | return "zir" 936 | case "application/vnd.zzazz.deck+xml": 937 | return "zaz" 938 | case "application/voicexml+xml": 939 | return "vxml" 940 | case "application/widget": 941 | return "wgt" 942 | case "application/winhlp": 943 | return "hlp" 944 | case "application/wsdl+xml": 945 | return "wsdl" 946 | case "application/wspolicy+xml": 947 | return "wspolicy" 948 | case "application/x-7z-compressed": 949 | return "7z" 950 | case "application/x-abiword": 951 | return "abw" 952 | case "application/x-ace-compressed": 953 | return "ace" 954 | case "application/x-apple-diskimage": 955 | return "dmg" 956 | case "application/x-authorware-bin": 957 | return "aab" 958 | case "application/x-authorware-map": 959 | return "aam" 960 | case "application/x-authorware-seg": 961 | return "aas" 962 | case "application/x-bcpio": 963 | return "bcpio" 964 | case "application/x-bittorrent": 965 | return "torrent" 966 | case "application/x-blorb": 967 | return "blb" 968 | case "application/x-bzip": 969 | return "bz" 970 | case "application/x-bzip2": 971 | return "bz2" 972 | case "application/x-cbr": 973 | return "cbr" 974 | case "application/x-cdlink": 975 | return "vcd" 976 | case "application/x-cfs-compressed": 977 | return "cfs" 978 | case "application/x-chat": 979 | return "chat" 980 | case "application/x-chess-pgn": 981 | return "pgn" 982 | case "application/x-conference": 983 | return "nsc" 984 | case "application/x-cpio": 985 | return "cpio" 986 | case "application/x-csh": 987 | return "csh" 988 | case "application/x-debian-package": 989 | return "deb" 990 | case "application/x-dgc-compressed": 991 | return "dgc" 992 | case "application/x-director": 993 | return "dir" 994 | case "application/x-doom": 995 | return "wad" 996 | case "application/x-dtbncx+xml": 997 | return "ncx" 998 | case "application/x-dtbook+xml": 999 | return "dtb" 1000 | case "application/x-dtbresource+xml": 1001 | return "res" 1002 | case "application/x-dvi": 1003 | return "dvi" 1004 | case "application/x-envoy": 1005 | return "evy" 1006 | case "application/x-eva": 1007 | return "eva" 1008 | case "application/x-font-bdf": 1009 | return "bdf" 1010 | case "application/x-font-ghostscript": 1011 | return "gsf" 1012 | case "application/x-font-linux-psf": 1013 | return "psf" 1014 | case "application/x-font-pcf": 1015 | return "pcf" 1016 | case "application/x-font-snf": 1017 | return "snf" 1018 | case "application/x-font-type1": 1019 | return "pfa" 1020 | case "application/x-freearc": 1021 | return "arc" 1022 | case "application/x-futuresplash": 1023 | return "spl" 1024 | case "application/x-gca-compressed": 1025 | return "gca" 1026 | case "application/x-glulx": 1027 | return "ulx" 1028 | case "application/x-gnumeric": 1029 | return "gnumeric" 1030 | case "application/x-gramps-xml": 1031 | return "gramps" 1032 | case "application/x-gtar": 1033 | return "gtar" 1034 | case "application/x-hdf": 1035 | return "hdf" 1036 | case "application/x-install-instructions": 1037 | return "install" 1038 | case "application/x-iso9660-image": 1039 | return "iso" 1040 | case "application/x-java-jnlp-file": 1041 | return "jnlp" 1042 | case "application/x-latex": 1043 | return "latex" 1044 | case "application/x-lzh-compressed": 1045 | return "lzh" 1046 | case "application/x-mie": 1047 | return "mie" 1048 | case "application/x-mobipocket-ebook": 1049 | return "prc" 1050 | case "application/x-ms-application": 1051 | return "application" 1052 | case "application/x-ms-shortcut": 1053 | return "lnk" 1054 | case "application/x-ms-wmd": 1055 | return "wmd" 1056 | case "application/x-ms-wmz": 1057 | return "wmz" 1058 | case "application/x-ms-xbap": 1059 | return "xbap" 1060 | case "application/x-msaccess": 1061 | return "mdb" 1062 | case "application/x-msbinder": 1063 | return "obd" 1064 | case "application/x-mscardfile": 1065 | return "crd" 1066 | case "application/x-msclip": 1067 | return "clp" 1068 | case "application/x-msdownload": 1069 | return "exe" 1070 | case "application/x-msmediaview": 1071 | return "mvb" 1072 | case "application/x-msmetafile": 1073 | return "wmf" 1074 | case "application/x-msmoney": 1075 | return "mny" 1076 | case "application/x-mspublisher": 1077 | return "pub" 1078 | case "application/x-msschedule": 1079 | return "scd" 1080 | case "application/x-msterminal": 1081 | return "trm" 1082 | case "application/x-mswrite": 1083 | return "wri" 1084 | case "application/x-netcdf": 1085 | return "nc" 1086 | case "application/x-nzb": 1087 | return "nzb" 1088 | case "application/x-pkcs12": 1089 | return "p12" 1090 | case "application/x-pkcs7-certificates": 1091 | return "p7b" 1092 | case "application/x-pkcs7-certreqresp": 1093 | return "p7r" 1094 | case "application/x-rar-compressed": 1095 | return "rar" 1096 | case "application/x-research-info-systems": 1097 | return "ris" 1098 | case "application/x-sh": 1099 | return "sh" 1100 | case "application/x-shar": 1101 | return "shar" 1102 | case "application/x-shockwave-flash": 1103 | return "swf" 1104 | case "application/x-silverlight-app": 1105 | return "xap" 1106 | case "application/x-sql": 1107 | return "sql" 1108 | case "application/x-stuffit": 1109 | return "sit" 1110 | case "application/x-stuffitx": 1111 | return "sitx" 1112 | case "application/x-subrip": 1113 | return "srt" 1114 | case "application/x-sv4cpio": 1115 | return "sv4cpio" 1116 | case "application/x-sv4crc": 1117 | return "sv4crc" 1118 | case "application/x-t3vm-image": 1119 | return "t3" 1120 | case "application/x-tads": 1121 | return "gam" 1122 | case "application/x-tar": 1123 | return "tar" 1124 | case "application/x-tcl": 1125 | return "tcl" 1126 | case "application/x-tex": 1127 | return "tex" 1128 | case "application/x-tex-tfm": 1129 | return "tfm" 1130 | case "application/x-texinfo": 1131 | return "texinfo" 1132 | case "application/x-tgif": 1133 | return "obj" 1134 | case "application/x-ustar": 1135 | return "ustar" 1136 | case "application/x-wais-source": 1137 | return "src" 1138 | case "application/x-x509-ca-cert": 1139 | return "der" 1140 | case "application/x-xfig": 1141 | return "fig" 1142 | case "application/x-xliff+xml": 1143 | return "xlf" 1144 | case "application/x-xpinstall": 1145 | return "xpi" 1146 | case "application/x-xz": 1147 | return "xz" 1148 | case "application/x-zmachine": 1149 | return "z1" 1150 | case "application/xaml+xml": 1151 | return "xaml" 1152 | case "application/xcap-diff+xml": 1153 | return "xdf" 1154 | case "application/xenc+xml": 1155 | return "xenc" 1156 | case "application/xhtml+xml": 1157 | return "xhtml" 1158 | case "application/xml": 1159 | return "xml" 1160 | case "application/xml-dtd": 1161 | return "dtd" 1162 | case "application/xop+xml": 1163 | return "xop" 1164 | case "application/xproc+xml": 1165 | return "xpl" 1166 | case "application/xslt+xml": 1167 | return "xslt" 1168 | case "application/xspf+xml": 1169 | return "xspf" 1170 | case "application/xv+xml": 1171 | return "mxml" 1172 | case "application/yang": 1173 | return "yang" 1174 | case "application/yin+xml": 1175 | return "yin" 1176 | case "application/zip": 1177 | return "zip" 1178 | case "audio/adpcm": 1179 | return "adp" 1180 | case "audio/basic": 1181 | return "au" 1182 | case "audio/midi": 1183 | return "mid" 1184 | case "audio/mp4": 1185 | return "m4a" 1186 | case "audio/mpeg": 1187 | return "mpga" 1188 | case "audio/ogg": 1189 | return "ogg" 1190 | case "audio/s3m": 1191 | return "s3m" 1192 | case "audio/silk": 1193 | return "sil" 1194 | case "audio/vnd.dece.audio": 1195 | return "uvva" 1196 | case "audio/vnd.digital-winds": 1197 | return "eol" 1198 | case "audio/vnd.dra": 1199 | return "dra" 1200 | case "audio/vnd.dts": 1201 | return "dts" 1202 | case "audio/vnd.dts.hd": 1203 | return "dtshd" 1204 | case "audio/vnd.lucent.voice": 1205 | return "lvp" 1206 | case "audio/vnd.ms-playready.media.pya": 1207 | return "pya" 1208 | case "audio/vnd.nuera.ecelp4800": 1209 | return "ecelp4800" 1210 | case "audio/vnd.nuera.ecelp7470": 1211 | return "ecelp7470" 1212 | case "audio/vnd.nuera.ecelp9600": 1213 | return "ecelp9600" 1214 | case "audio/vnd.rip": 1215 | return "rip" 1216 | case "audio/webm": 1217 | return "weba" 1218 | case "audio/x-aac": 1219 | return "aac" 1220 | case "audio/x-aiff": 1221 | return "aif" 1222 | case "audio/x-caf": 1223 | return "caf" 1224 | case "audio/x-flac": 1225 | return "flac" 1226 | case "audio/x-matroska": 1227 | return "mka" 1228 | case "audio/x-mpegurl": 1229 | return "m3u" 1230 | case "audio/x-ms-wax": 1231 | return "wax" 1232 | case "audio/x-ms-wma": 1233 | return "wma" 1234 | case "audio/x-pn-realaudio": 1235 | return "ram" 1236 | case "audio/x-pn-realaudio-plugin": 1237 | return "rmp" 1238 | case "audio/x-wav": 1239 | return "wav" 1240 | case "audio/xm": 1241 | return "xm" 1242 | case "chemical/x-cdx": 1243 | return "cdx" 1244 | case "chemical/x-cif": 1245 | return "cif" 1246 | case "chemical/x-cmdf": 1247 | return "cmdf" 1248 | case "chemical/x-cml": 1249 | return "cml" 1250 | case "chemical/x-csml": 1251 | return "csml" 1252 | case "chemical/x-xyz": 1253 | return "xyz" 1254 | case "font/collection": 1255 | return "ttc" 1256 | case "font/otf": 1257 | return "otf" 1258 | case "font/ttf": 1259 | return "ttf" 1260 | case "font/woff": 1261 | return "woff" 1262 | case "font/woff2": 1263 | return "woff2" 1264 | case "image/bmp": 1265 | return "bmp" 1266 | case "image/cgm": 1267 | return "cgm" 1268 | case "image/g3fax": 1269 | return "g3" 1270 | case "image/gif": 1271 | return "gif" 1272 | case "image/ief": 1273 | return "ief" 1274 | case "image/jpeg": 1275 | return "jpeg" 1276 | case "image/ktx": 1277 | return "ktx" 1278 | case "image/png": 1279 | return "png" 1280 | case "image/prs.btif": 1281 | return "btif" 1282 | case "image/sgi": 1283 | return "sgi" 1284 | case "image/svg+xml": 1285 | return "svg" 1286 | case "image/tiff": 1287 | return "tiff" 1288 | case "image/vnd.adobe.photoshop": 1289 | return "psd" 1290 | case "image/vnd.dece.graphic": 1291 | return "uvi" 1292 | case "image/vnd.djvu": 1293 | return "djvu" 1294 | case "image/vnd.dvb.subtitle": 1295 | return "sub" 1296 | case "image/vnd.dwg": 1297 | return "dwg" 1298 | case "image/vnd.dxf": 1299 | return "dxf" 1300 | case "image/vnd.fastbidsheet": 1301 | return "fbs" 1302 | case "image/vnd.fpx": 1303 | return "fpx" 1304 | case "image/vnd.fst": 1305 | return "fst" 1306 | case "image/vnd.fujixerox.edmics-mmr": 1307 | return "mmr" 1308 | case "image/vnd.fujixerox.edmics-rlc": 1309 | return "rlc" 1310 | case "image/vnd.ms-modi": 1311 | return "mdi" 1312 | case "image/vnd.ms-photo": 1313 | return "wdp" 1314 | case "image/vnd.net-fpx": 1315 | return "npx" 1316 | case "image/vnd.wap.wbmp": 1317 | return "wbmp" 1318 | case "image/vnd.xiff": 1319 | return "xif" 1320 | case "image/webp": 1321 | return "webp" 1322 | case "image/x-3ds": 1323 | return "3ds" 1324 | case "image/x-cmu-raster": 1325 | return "ras" 1326 | case "image/x-cmx": 1327 | return "cmx" 1328 | case "image/x-freehand": 1329 | return "fh" 1330 | case "image/x-icon": 1331 | return "ico" 1332 | case "image/x-mrsid-image": 1333 | return "sid" 1334 | case "image/x-pcx": 1335 | return "pcx" 1336 | case "image/x-pict": 1337 | return "pic" 1338 | case "image/x-portable-anymap": 1339 | return "pnm" 1340 | case "image/x-portable-bitmap": 1341 | return "pbm" 1342 | case "image/x-portable-graymap": 1343 | return "pgm" 1344 | case "image/x-portable-pixmap": 1345 | return "ppm" 1346 | case "image/x-rgb": 1347 | return "rgb" 1348 | case "image/x-tga": 1349 | return "tga" 1350 | case "image/x-xbitmap": 1351 | return "xbm" 1352 | case "image/x-xpixmap": 1353 | return "xpm" 1354 | case "image/x-xwindowdump": 1355 | return "xwd" 1356 | case "message/rfc822": 1357 | return "eml" 1358 | case "model/iges": 1359 | return "igs" 1360 | case "model/mesh": 1361 | return "msh" 1362 | case "model/vnd.collada+xml": 1363 | return "dae" 1364 | case "model/vnd.dwf": 1365 | return "dwf" 1366 | case "model/vnd.gdl": 1367 | return "gdl" 1368 | case "model/vnd.gtw": 1369 | return "gtw" 1370 | case "model/vnd.mts": 1371 | return "mts" 1372 | case "model/vnd.vtu": 1373 | return "vtu" 1374 | case "model/vrml": 1375 | return "wrl" 1376 | case "model/x3d+binary": 1377 | return "x3db" 1378 | case "model/x3d+vrml": 1379 | return "x3dv" 1380 | case "model/x3d+xml": 1381 | return "x3d" 1382 | case "text/cache-manifest": 1383 | return "appcache" 1384 | case "text/calendar": 1385 | return "ics" 1386 | case "text/css": 1387 | return "css" 1388 | case "text/csv": 1389 | return "csv" 1390 | case "text/html": 1391 | return "html" 1392 | case "text/n3": 1393 | return "n3" 1394 | case "text/plain": 1395 | return "txt" 1396 | case "text/prs.lines.tag": 1397 | return "dsc" 1398 | case "text/richtext": 1399 | return "rtx" 1400 | case "text/sgml": 1401 | return "sgml" 1402 | case "text/tab-separated-values": 1403 | return "tsv" 1404 | case "text/troff": 1405 | return "t" 1406 | case "text/turtle": 1407 | return "ttl" 1408 | case "text/uri-list": 1409 | return "uri" 1410 | case "text/vcard": 1411 | return "vcard" 1412 | case "text/vnd.curl": 1413 | return "curl" 1414 | case "text/vnd.curl.dcurl": 1415 | return "dcurl" 1416 | case "text/vnd.curl.mcurl": 1417 | return "mcurl" 1418 | case "text/vnd.curl.scurl": 1419 | return "scurl" 1420 | case "text/vnd.dvb.subtitle": 1421 | return "sub" 1422 | case "text/vnd.fly": 1423 | return "fly" 1424 | case "text/vnd.fmi.flexstor": 1425 | return "flx" 1426 | case "text/vnd.graphviz": 1427 | return "gv" 1428 | case "text/vnd.in3d.3dml": 1429 | return "3dml" 1430 | case "text/vnd.in3d.spot": 1431 | return "spot" 1432 | case "text/vnd.sun.j2me.app-descriptor": 1433 | return "jad" 1434 | case "text/vnd.wap.wml": 1435 | return "wml" 1436 | case "text/vnd.wap.wmlscript": 1437 | return "wmls" 1438 | case "text/x-asm": 1439 | return "s" 1440 | case "text/x-c": 1441 | return "c" 1442 | case "text/x-fortran": 1443 | return "f" 1444 | case "text/x-java-source": 1445 | return "java" 1446 | case "text/x-nfo": 1447 | return "nfo" 1448 | case "text/x-opml": 1449 | return "opml" 1450 | case "text/x-pascal": 1451 | return "p" 1452 | case "text/x-setext": 1453 | return "etx" 1454 | case "text/x-sfv": 1455 | return "sfv" 1456 | case "text/x-uuencode": 1457 | return "uu" 1458 | case "text/x-vcalendar": 1459 | return "vcs" 1460 | case "text/x-vcard": 1461 | return "vcf" 1462 | case "video/3gpp": 1463 | return "3gp" 1464 | case "video/3gpp2": 1465 | return "3g2" 1466 | case "video/h261": 1467 | return "h261" 1468 | case "video/h263": 1469 | return "h263" 1470 | case "video/h264": 1471 | return "h264" 1472 | case "video/jpeg": 1473 | return "jpgv" 1474 | case "video/jpm": 1475 | return "jpm" 1476 | case "video/mj2": 1477 | return "mj2" 1478 | case "video/mp4": 1479 | return "mp4" 1480 | case "video/mpeg": 1481 | return "mpeg" 1482 | case "video/ogg": 1483 | return "ogv" 1484 | case "video/quicktime": 1485 | return "mov" 1486 | case "video/vnd.dece.hd": 1487 | return "uvh" 1488 | case "video/vnd.dece.mobile": 1489 | return "uvm" 1490 | case "video/vnd.dece.pd": 1491 | return "uvp" 1492 | case "video/vnd.dece.sd": 1493 | return "uvs" 1494 | case "video/vnd.dece.video": 1495 | return "uvv" 1496 | case "video/vnd.dvb.file": 1497 | return "dvb" 1498 | case "video/vnd.fvt": 1499 | return "fvt" 1500 | case "video/vnd.mpegurl": 1501 | return "mxu" 1502 | case "video/vnd.ms-playready.media.pyv": 1503 | return "pyv" 1504 | case "video/vnd.uvvu.mp4": 1505 | return "uvu" 1506 | case "video/vnd.vivo": 1507 | return "viv" 1508 | case "video/webm": 1509 | return "webm" 1510 | case "video/x-f4v": 1511 | return "f4v" 1512 | case "video/x-fli": 1513 | return "fli" 1514 | case "video/x-flv": 1515 | return "flv" 1516 | case "video/x-m4v": 1517 | return "m4v" 1518 | case "video/x-matroska": 1519 | return "mkv" 1520 | case "video/x-mng": 1521 | return "mng" 1522 | case "video/x-ms-asf": 1523 | return "asf" 1524 | case "video/x-ms-vob": 1525 | return "vob" 1526 | case "video/x-ms-wm": 1527 | return "wm" 1528 | case "video/x-ms-wmv": 1529 | return "wmv" 1530 | case "video/x-ms-wmx": 1531 | return "wmx" 1532 | case "video/x-ms-wvx": 1533 | return "wvx" 1534 | case "video/x-msvideo": 1535 | return "avi" 1536 | case "video/x-sgi-movie": 1537 | return "movie" 1538 | case "video/x-smv": 1539 | return "smv" 1540 | case "x-conference/x-cooltalk": 1541 | return "ice" 1542 | default: 1543 | return nil 1544 | } 1545 | } 1546 | 1547 | func getMime(for extension: String) -> String? { 1548 | switch `extension` { 1549 | case "ez": 1550 | return "application/andrew-inset" 1551 | case "aw": 1552 | return "application/applixware" 1553 | case "atom": 1554 | return "application/atom+xml" 1555 | case "atomcat": 1556 | return "application/atomcat+xml" 1557 | case "atomsvc": 1558 | return "application/atomsvc+xml" 1559 | case "ccxml": 1560 | return "application/ccxml+xml" 1561 | case "cdmia": 1562 | return "application/cdmi-capability" 1563 | case "cdmic": 1564 | return "application/cdmi-container" 1565 | case "cdmid": 1566 | return "application/cdmi-domain" 1567 | case "cdmio": 1568 | return "application/cdmi-object" 1569 | case "cdmiq": 1570 | return "application/cdmi-queue" 1571 | case "cu": 1572 | return "application/cu-seeme" 1573 | case "davmount": 1574 | return "application/davmount+xml" 1575 | case "dbk": 1576 | return "application/docbook+xml" 1577 | case "dssc": 1578 | return "application/dssc+der" 1579 | case "xdssc": 1580 | return "application/dssc+xml" 1581 | case "ecma": 1582 | return "application/ecmascript" 1583 | case "emma": 1584 | return "application/emma+xml" 1585 | case "epub": 1586 | return "application/epub+zip" 1587 | case "exi": 1588 | return "application/exi" 1589 | case "pfr": 1590 | return "application/font-tdpfr" 1591 | case "gml": 1592 | return "application/gml+xml" 1593 | case "gpx": 1594 | return "application/gpx+xml" 1595 | case "gxf": 1596 | return "application/gxf" 1597 | case "stk": 1598 | return "application/hyperstudio" 1599 | case "ink", "inkml": 1600 | return "application/inkml+xml" 1601 | case "ipfix": 1602 | return "application/ipfix" 1603 | case "jar": 1604 | return "application/java-archive" 1605 | case "ser": 1606 | return "application/java-serialized-object" 1607 | case "class": 1608 | return "application/java-vm" 1609 | case "js": 1610 | return "application/javascript" 1611 | case "json": 1612 | return "application/json" 1613 | case "jsonml": 1614 | return "application/jsonml+json" 1615 | case "lostxml": 1616 | return "application/lost+xml" 1617 | case "hqx": 1618 | return "application/mac-binhex40" 1619 | case "cpt": 1620 | return "application/mac-compactpro" 1621 | case "mads": 1622 | return "application/mads+xml" 1623 | case "mrc": 1624 | return "application/marc" 1625 | case "mrcx": 1626 | return "application/marcxml+xml" 1627 | case "ma", "nb", "mb": 1628 | return "application/mathematica" 1629 | case "mathml": 1630 | return "application/mathml+xml" 1631 | case "mbox": 1632 | return "application/mbox" 1633 | case "mscml": 1634 | return "application/mediaservercontrol+xml" 1635 | case "metalink": 1636 | return "application/metalink+xml" 1637 | case "meta4": 1638 | return "application/metalink4+xml" 1639 | case "mets": 1640 | return "application/mets+xml" 1641 | case "mods": 1642 | return "application/mods+xml" 1643 | case "m21", "mp21": 1644 | return "application/mp21" 1645 | case "mp4s": 1646 | return "application/mp4" 1647 | case "doc", "dot": 1648 | return "application/msword" 1649 | case "mxf": 1650 | return "application/mxf" 1651 | case "bin", "dms", "lrf", "mar", "so", "dist", "distz", "pkg", "bpk", "dump", "elc", "deploy": 1652 | return "application/octet-stream" 1653 | case "oda": 1654 | return "application/oda" 1655 | case "opf": 1656 | return "application/oebps-package+xml" 1657 | case "ogx": 1658 | return "application/ogg" 1659 | case "omdoc": 1660 | return "application/omdoc+xml" 1661 | case "onetoc", "onetoc2", "onetmp", "onepkg": 1662 | return "application/onenote" 1663 | case "oxps": 1664 | return "application/oxps" 1665 | case "xer": 1666 | return "application/patch-ops-error+xml" 1667 | case "pdf": 1668 | return "application/pdf" 1669 | case "pgp": 1670 | return "application/pgp-encrypted" 1671 | case "asc", "sig": 1672 | return "application/pgp-signature" 1673 | case "prf": 1674 | return "application/pics-rules" 1675 | case "p10": 1676 | return "application/pkcs10" 1677 | case "p7m", "p7c": 1678 | return "application/pkcs7-mime" 1679 | case "p7s": 1680 | return "application/pkcs7-signature" 1681 | case "p8": 1682 | return "application/pkcs8" 1683 | case "ac": 1684 | return "application/pkix-attr-cert" 1685 | case "cer": 1686 | return "application/pkix-cert" 1687 | case "crl": 1688 | return "application/pkix-crl" 1689 | case "pkipath": 1690 | return "application/pkix-pkipath" 1691 | case "pki": 1692 | return "application/pkixcmp" 1693 | case "pls": 1694 | return "application/pls+xml" 1695 | case "ai", "eps", "ps": 1696 | return "application/postscript" 1697 | case "cww": 1698 | return "application/prs.cww" 1699 | case "pskcxml": 1700 | return "application/pskc+xml" 1701 | case "rdf": 1702 | return "application/rdf+xml" 1703 | case "rif": 1704 | return "application/reginfo+xml" 1705 | case "rnc": 1706 | return "application/relax-ng-compact-syntax" 1707 | case "rl": 1708 | return "application/resource-lists+xml" 1709 | case "rld": 1710 | return "application/resource-lists-diff+xml" 1711 | case "rs": 1712 | return "application/rls-services+xml" 1713 | case "gbr": 1714 | return "application/rpki-ghostbusters" 1715 | case "mft": 1716 | return "application/rpki-manifest" 1717 | case "roa": 1718 | return "application/rpki-roa" 1719 | case "rsd": 1720 | return "application/rsd+xml" 1721 | case "rss": 1722 | return "application/rss+xml" 1723 | case "rtf": 1724 | return "application/rtf" 1725 | case "sbml": 1726 | return "application/sbml+xml" 1727 | case "scq": 1728 | return "application/scvp-cv-request" 1729 | case "scs": 1730 | return "application/scvp-cv-response" 1731 | case "spq": 1732 | return "application/scvp-vp-request" 1733 | case "spp": 1734 | return "application/scvp-vp-response" 1735 | case "sdp": 1736 | return "application/sdp" 1737 | case "setpay": 1738 | return "application/set-payment-initiation" 1739 | case "setreg": 1740 | return "application/set-registration-initiation" 1741 | case "shf": 1742 | return "application/shf+xml" 1743 | case "smi", "smil": 1744 | return "application/smil+xml" 1745 | case "rq": 1746 | return "application/sparql-query" 1747 | case "srx": 1748 | return "application/sparql-results+xml" 1749 | case "gram": 1750 | return "application/srgs" 1751 | case "grxml": 1752 | return "application/srgs+xml" 1753 | case "sru": 1754 | return "application/sru+xml" 1755 | case "ssdl": 1756 | return "application/ssdl+xml" 1757 | case "ssml": 1758 | return "application/ssml+xml" 1759 | case "tei", "teicorpus": 1760 | return "application/tei+xml" 1761 | case "tfi": 1762 | return "application/thraud+xml" 1763 | case "tsd": 1764 | return "application/timestamped-data" 1765 | case "plb": 1766 | return "application/vnd.3gpp.pic-bw-large" 1767 | case "psb": 1768 | return "application/vnd.3gpp.pic-bw-small" 1769 | case "pvb": 1770 | return "application/vnd.3gpp.pic-bw-var" 1771 | case "tcap": 1772 | return "application/vnd.3gpp2.tcap" 1773 | case "pwn": 1774 | return "application/vnd.3m.post-it-notes" 1775 | case "aso": 1776 | return "application/vnd.accpac.simply.aso" 1777 | case "imp": 1778 | return "application/vnd.accpac.simply.imp" 1779 | case "acu": 1780 | return "application/vnd.acucobol" 1781 | case "atc", "acutc": 1782 | return "application/vnd.acucorp" 1783 | case "air": 1784 | return "application/vnd.adobe.air-application-installer-package+zip" 1785 | case "fcdt": 1786 | return "application/vnd.adobe.formscentral.fcdt" 1787 | case "fxp", "fxpl": 1788 | return "application/vnd.adobe.fxp" 1789 | case "xdp": 1790 | return "application/vnd.adobe.xdp+xml" 1791 | case "xfdf": 1792 | return "application/vnd.adobe.xfdf" 1793 | case "ahead": 1794 | return "application/vnd.ahead.space" 1795 | case "azf": 1796 | return "application/vnd.airzip.filesecure.azf" 1797 | case "azs": 1798 | return "application/vnd.airzip.filesecure.azs" 1799 | case "azw": 1800 | return "application/vnd.amazon.ebook" 1801 | case "acc": 1802 | return "application/vnd.americandynamics.acc" 1803 | case "ami": 1804 | return "application/vnd.amiga.ami" 1805 | case "apk": 1806 | return "application/vnd.android.package-archive" 1807 | case "cii": 1808 | return "application/vnd.anser-web-certificate-issue-initiation" 1809 | case "fti": 1810 | return "application/vnd.anser-web-funds-transfer-initiation" 1811 | case "atx": 1812 | return "application/vnd.antix.game-component" 1813 | case "mpkg": 1814 | return "application/vnd.apple.installer+xml" 1815 | case "m3u8": 1816 | return "application/vnd.apple.mpegurl" 1817 | case "swi": 1818 | return "application/vnd.aristanetworks.swi" 1819 | case "iota": 1820 | return "application/vnd.astraea-software.iota" 1821 | case "aep": 1822 | return "application/vnd.audiograph" 1823 | case "mpm": 1824 | return "application/vnd.blueice.multipass" 1825 | case "bmi": 1826 | return "application/vnd.bmi" 1827 | case "rep": 1828 | return "application/vnd.businessobjects" 1829 | case "cdxml": 1830 | return "application/vnd.chemdraw+xml" 1831 | case "mmd": 1832 | return "application/vnd.chipnuts.karaoke-mmd" 1833 | case "cdy": 1834 | return "application/vnd.cinderella" 1835 | case "cla": 1836 | return "application/vnd.claymore" 1837 | case "rp9": 1838 | return "application/vnd.cloanto.rp9" 1839 | case "c4g", "c4d", "c4f", "c4p", "c4u": 1840 | return "application/vnd.clonk.c4group" 1841 | case "c11amc": 1842 | return "application/vnd.cluetrust.cartomobile-config" 1843 | case "c11amz": 1844 | return "application/vnd.cluetrust.cartomobile-config-pkg" 1845 | case "csp": 1846 | return "application/vnd.commonspace" 1847 | case "cdbcmsg": 1848 | return "application/vnd.contact.cmsg" 1849 | case "cmc": 1850 | return "application/vnd.cosmocaller" 1851 | case "clkx": 1852 | return "application/vnd.crick.clicker" 1853 | case "clkk": 1854 | return "application/vnd.crick.clicker.keyboard" 1855 | case "clkp": 1856 | return "application/vnd.crick.clicker.palette" 1857 | case "clkt": 1858 | return "application/vnd.crick.clicker.template" 1859 | case "clkw": 1860 | return "application/vnd.crick.clicker.wordbank" 1861 | case "wbs": 1862 | return "application/vnd.criticaltools.wbs+xml" 1863 | case "pml": 1864 | return "application/vnd.ctc-posml" 1865 | case "ppd": 1866 | return "application/vnd.cups-ppd" 1867 | case "car": 1868 | return "application/vnd.curl.car" 1869 | case "pcurl": 1870 | return "application/vnd.curl.pcurl" 1871 | case "dart": 1872 | return "application/vnd.dart" 1873 | case "rdz": 1874 | return "application/vnd.data-vision.rdz" 1875 | case "uvf", "uvvf", "uvd", "uvvd": 1876 | return "application/vnd.dece.data" 1877 | case "uvt", "uvvt": 1878 | return "application/vnd.dece.ttml+xml" 1879 | case "uvx", "uvvx": 1880 | return "application/vnd.dece.unspecified" 1881 | case "uvz", "uvvz": 1882 | return "application/vnd.dece.zip" 1883 | case "fe_launch": 1884 | return "application/vnd.denovo.fcselayout-link" 1885 | case "dna": 1886 | return "application/vnd.dna" 1887 | case "mlp": 1888 | return "application/vnd.dolby.mlp" 1889 | case "dpg": 1890 | return "application/vnd.dpgraph" 1891 | case "dfac": 1892 | return "application/vnd.dreamfactory" 1893 | case "kpxx": 1894 | return "application/vnd.ds-keypoint" 1895 | case "ait": 1896 | return "application/vnd.dvb.ait" 1897 | case "svc": 1898 | return "application/vnd.dvb.service" 1899 | case "geo": 1900 | return "application/vnd.dynageo" 1901 | case "mag": 1902 | return "application/vnd.ecowin.chart" 1903 | case "nml": 1904 | return "application/vnd.enliven" 1905 | case "esf": 1906 | return "application/vnd.epson.esf" 1907 | case "msf": 1908 | return "application/vnd.epson.msf" 1909 | case "qam": 1910 | return "application/vnd.epson.quickanime" 1911 | case "slt": 1912 | return "application/vnd.epson.salt" 1913 | case "ssf": 1914 | return "application/vnd.epson.ssf" 1915 | case "es3", "et3": 1916 | return "application/vnd.eszigno3+xml" 1917 | case "ez2": 1918 | return "application/vnd.ezpix-album" 1919 | case "ez3": 1920 | return "application/vnd.ezpix-package" 1921 | case "fdf": 1922 | return "application/vnd.fdf" 1923 | case "mseed": 1924 | return "application/vnd.fdsn.mseed" 1925 | case "seed", "dataless": 1926 | return "application/vnd.fdsn.seed" 1927 | case "gph": 1928 | return "application/vnd.flographit" 1929 | case "ftc": 1930 | return "application/vnd.fluxtime.clip" 1931 | case "fm", "frame", "maker", "book": 1932 | return "application/vnd.framemaker" 1933 | case "fnc": 1934 | return "application/vnd.frogans.fnc" 1935 | case "ltf": 1936 | return "application/vnd.frogans.ltf" 1937 | case "fsc": 1938 | return "application/vnd.fsc.weblaunch" 1939 | case "oas": 1940 | return "application/vnd.fujitsu.oasys" 1941 | case "oa2": 1942 | return "application/vnd.fujitsu.oasys2" 1943 | case "oa3": 1944 | return "application/vnd.fujitsu.oasys3" 1945 | case "fg5": 1946 | return "application/vnd.fujitsu.oasysgp" 1947 | case "bh2": 1948 | return "application/vnd.fujitsu.oasysprs" 1949 | case "ddd": 1950 | return "application/vnd.fujixerox.ddd" 1951 | case "xdw": 1952 | return "application/vnd.fujixerox.docuworks" 1953 | case "xbd": 1954 | return "application/vnd.fujixerox.docuworks.binder" 1955 | case "fzs": 1956 | return "application/vnd.fuzzysheet" 1957 | case "txd": 1958 | return "application/vnd.genomatix.tuxedo" 1959 | case "ggb": 1960 | return "application/vnd.geogebra.file" 1961 | case "ggt": 1962 | return "application/vnd.geogebra.tool" 1963 | case "gex", "gre": 1964 | return "application/vnd.geometry-explorer" 1965 | case "gxt": 1966 | return "application/vnd.geonext" 1967 | case "g2w": 1968 | return "application/vnd.geoplan" 1969 | case "g3w": 1970 | return "application/vnd.geospace" 1971 | case "gmx": 1972 | return "application/vnd.gmx" 1973 | case "kml": 1974 | return "application/vnd.google-earth.kml+xml" 1975 | case "kmz": 1976 | return "application/vnd.google-earth.kmz" 1977 | case "gqf", "gqs": 1978 | return "application/vnd.grafeq" 1979 | case "gac": 1980 | return "application/vnd.groove-account" 1981 | case "ghf": 1982 | return "application/vnd.groove-help" 1983 | case "gim": 1984 | return "application/vnd.groove-identity-message" 1985 | case "grv": 1986 | return "application/vnd.groove-injector" 1987 | case "gtm": 1988 | return "application/vnd.groove-tool-message" 1989 | case "tpl": 1990 | return "application/vnd.groove-tool-template" 1991 | case "vcg": 1992 | return "application/vnd.groove-vcard" 1993 | case "hal": 1994 | return "application/vnd.hal+xml" 1995 | case "zmm": 1996 | return "application/vnd.handheld-entertainment+xml" 1997 | case "hbci": 1998 | return "application/vnd.hbci" 1999 | case "les": 2000 | return "application/vnd.hhe.lesson-player" 2001 | case "hpgl": 2002 | return "application/vnd.hp-hpgl" 2003 | case "hpid": 2004 | return "application/vnd.hp-hpid" 2005 | case "hps": 2006 | return "application/vnd.hp-hps" 2007 | case "jlt": 2008 | return "application/vnd.hp-jlyt" 2009 | case "pcl": 2010 | return "application/vnd.hp-pcl" 2011 | case "pclxl": 2012 | return "application/vnd.hp-pclxl" 2013 | case "sfd-hdstx": 2014 | return "application/vnd.hydrostatix.sof-data" 2015 | case "mpy": 2016 | return "application/vnd.ibm.minipay" 2017 | case "afp", "listafp", "list3820": 2018 | return "application/vnd.ibm.modcap" 2019 | case "irm": 2020 | return "application/vnd.ibm.rights-management" 2021 | case "sc": 2022 | return "application/vnd.ibm.secure-container" 2023 | case "icc", "icm": 2024 | return "application/vnd.iccprofile" 2025 | case "igl": 2026 | return "application/vnd.igloader" 2027 | case "ivp": 2028 | return "application/vnd.immervision-ivp" 2029 | case "ivu": 2030 | return "application/vnd.immervision-ivu" 2031 | case "igm": 2032 | return "application/vnd.insors.igm" 2033 | case "xpw", "xpx": 2034 | return "application/vnd.intercon.formnet" 2035 | case "i2g": 2036 | return "application/vnd.intergeo" 2037 | case "qbo": 2038 | return "application/vnd.intu.qbo" 2039 | case "qfx": 2040 | return "application/vnd.intu.qfx" 2041 | case "rcprofile": 2042 | return "application/vnd.ipunplugged.rcprofile" 2043 | case "irp": 2044 | return "application/vnd.irepository.package+xml" 2045 | case "xpr": 2046 | return "application/vnd.is-xpr" 2047 | case "fcs": 2048 | return "application/vnd.isac.fcs" 2049 | case "jam": 2050 | return "application/vnd.jam" 2051 | case "rms": 2052 | return "application/vnd.jcp.javame.midlet-rms" 2053 | case "jisp": 2054 | return "application/vnd.jisp" 2055 | case "joda": 2056 | return "application/vnd.joost.joda-archive" 2057 | case "ktz", "ktr": 2058 | return "application/vnd.kahootz" 2059 | case "karbon": 2060 | return "application/vnd.kde.karbon" 2061 | case "chrt": 2062 | return "application/vnd.kde.kchart" 2063 | case "kfo": 2064 | return "application/vnd.kde.kformula" 2065 | case "flw": 2066 | return "application/vnd.kde.kivio" 2067 | case "kon": 2068 | return "application/vnd.kde.kontour" 2069 | case "kpr", "kpt": 2070 | return "application/vnd.kde.kpresenter" 2071 | case "ksp": 2072 | return "application/vnd.kde.kspread" 2073 | case "kwd", "kwt": 2074 | return "application/vnd.kde.kword" 2075 | case "htke": 2076 | return "application/vnd.kenameaapp" 2077 | case "kia": 2078 | return "application/vnd.kidspiration" 2079 | case "kne", "knp": 2080 | return "application/vnd.kinar" 2081 | case "skp", "skd", "skt", "skm": 2082 | return "application/vnd.koan" 2083 | case "sse": 2084 | return "application/vnd.kodak-descriptor" 2085 | case "lasxml": 2086 | return "application/vnd.las.las+xml" 2087 | case "lbd": 2088 | return "application/vnd.llamagraphics.life-balance.desktop" 2089 | case "lbe": 2090 | return "application/vnd.llamagraphics.life-balance.exchange+xml" 2091 | case "123": 2092 | return "application/vnd.lotus-1-2-3" 2093 | case "apr": 2094 | return "application/vnd.lotus-approach" 2095 | case "pre": 2096 | return "application/vnd.lotus-freelance" 2097 | case "nsf": 2098 | return "application/vnd.lotus-notes" 2099 | case "org": 2100 | return "application/vnd.lotus-organizer" 2101 | case "scm": 2102 | return "application/vnd.lotus-screencam" 2103 | case "lwp": 2104 | return "application/vnd.lotus-wordpro" 2105 | case "portpkg": 2106 | return "application/vnd.macports.portpkg" 2107 | case "mcd": 2108 | return "application/vnd.mcd" 2109 | case "mc1": 2110 | return "application/vnd.medcalcdata" 2111 | case "cdkey": 2112 | return "application/vnd.mediastation.cdkey" 2113 | case "mwf": 2114 | return "application/vnd.mfer" 2115 | case "mfm": 2116 | return "application/vnd.mfmp" 2117 | case "flo": 2118 | return "application/vnd.micrografx.flo" 2119 | case "igx": 2120 | return "application/vnd.micrografx.igx" 2121 | case "mif": 2122 | return "application/vnd.mif" 2123 | case "daf": 2124 | return "application/vnd.mobius.daf" 2125 | case "dis": 2126 | return "application/vnd.mobius.dis" 2127 | case "mbk": 2128 | return "application/vnd.mobius.mbk" 2129 | case "mqy": 2130 | return "application/vnd.mobius.mqy" 2131 | case "msl": 2132 | return "application/vnd.mobius.msl" 2133 | case "plc": 2134 | return "application/vnd.mobius.plc" 2135 | case "txf": 2136 | return "application/vnd.mobius.txf" 2137 | case "mpn": 2138 | return "application/vnd.mophun.application" 2139 | case "mpc": 2140 | return "application/vnd.mophun.certificate" 2141 | case "xul": 2142 | return "application/vnd.mozilla.xul+xml" 2143 | case "cil": 2144 | return "application/vnd.ms-artgalry" 2145 | case "cab": 2146 | return "application/vnd.ms-cab-compressed" 2147 | case "xls", "xlm", "xla", "xlc", "xlt", "xlw": 2148 | return "application/vnd.ms-excel" 2149 | case "xlam": 2150 | return "application/vnd.ms-excel.addin.macroenabled.12" 2151 | case "xlsb": 2152 | return "application/vnd.ms-excel.sheet.binary.macroenabled.12" 2153 | case "xlsm": 2154 | return "application/vnd.ms-excel.sheet.macroenabled.12" 2155 | case "xltm": 2156 | return "application/vnd.ms-excel.template.macroenabled.12" 2157 | case "eot": 2158 | return "application/vnd.ms-fontobject" 2159 | case "chm": 2160 | return "application/vnd.ms-htmlhelp" 2161 | case "ims": 2162 | return "application/vnd.ms-ims" 2163 | case "lrm": 2164 | return "application/vnd.ms-lrm" 2165 | case "thmx": 2166 | return "application/vnd.ms-officetheme" 2167 | case "cat": 2168 | return "application/vnd.ms-pki.seccat" 2169 | case "stl": 2170 | return "application/vnd.ms-pki.stl" 2171 | case "ppt", "pps", "pot": 2172 | return "application/vnd.ms-powerpoint" 2173 | case "ppam": 2174 | return "application/vnd.ms-powerpoint.addin.macroenabled.12" 2175 | case "pptm": 2176 | return "application/vnd.ms-powerpoint.presentation.macroenabled.12" 2177 | case "sldm": 2178 | return "application/vnd.ms-powerpoint.slide.macroenabled.12" 2179 | case "ppsm": 2180 | return "application/vnd.ms-powerpoint.slideshow.macroenabled.12" 2181 | case "potm": 2182 | return "application/vnd.ms-powerpoint.template.macroenabled.12" 2183 | case "mpp", "mpt": 2184 | return "application/vnd.ms-project" 2185 | case "docm": 2186 | return "application/vnd.ms-word.document.macroenabled.12" 2187 | case "dotm": 2188 | return "application/vnd.ms-word.template.macroenabled.12" 2189 | case "wps", "wks", "wcm", "wdb": 2190 | return "application/vnd.ms-works" 2191 | case "wpl": 2192 | return "application/vnd.ms-wpl" 2193 | case "xps": 2194 | return "application/vnd.ms-xpsdocument" 2195 | case "mseq": 2196 | return "application/vnd.mseq" 2197 | case "mus": 2198 | return "application/vnd.musician" 2199 | case "msty": 2200 | return "application/vnd.muvee.style" 2201 | case "taglet": 2202 | return "application/vnd.mynfc" 2203 | case "nlu": 2204 | return "application/vnd.neurolanguage.nlu" 2205 | case "ntf", "nitf": 2206 | return "application/vnd.nitf" 2207 | case "nnd": 2208 | return "application/vnd.noblenet-directory" 2209 | case "nns": 2210 | return "application/vnd.noblenet-sealer" 2211 | case "nnw": 2212 | return "application/vnd.noblenet-web" 2213 | case "ngdat": 2214 | return "application/vnd.nokia.n-gage.data" 2215 | case "n-gage": 2216 | return "application/vnd.nokia.n-gage.symbian.install" 2217 | case "rpst": 2218 | return "application/vnd.nokia.radio-preset" 2219 | case "rpss": 2220 | return "application/vnd.nokia.radio-presets" 2221 | case "edm": 2222 | return "application/vnd.novadigm.edm" 2223 | case "edx": 2224 | return "application/vnd.novadigm.edx" 2225 | case "ext": 2226 | return "application/vnd.novadigm.ext" 2227 | case "odc": 2228 | return "application/vnd.oasis.opendocument.chart" 2229 | case "otc": 2230 | return "application/vnd.oasis.opendocument.chart-template" 2231 | case "odb": 2232 | return "application/vnd.oasis.opendocument.database" 2233 | case "odf": 2234 | return "application/vnd.oasis.opendocument.formula" 2235 | case "odft": 2236 | return "application/vnd.oasis.opendocument.formula-template" 2237 | case "odg": 2238 | return "application/vnd.oasis.opendocument.graphics" 2239 | case "otg": 2240 | return "application/vnd.oasis.opendocument.graphics-template" 2241 | case "odi": 2242 | return "application/vnd.oasis.opendocument.image" 2243 | case "oti": 2244 | return "application/vnd.oasis.opendocument.image-template" 2245 | case "odp": 2246 | return "application/vnd.oasis.opendocument.presentation" 2247 | case "otp": 2248 | return "application/vnd.oasis.opendocument.presentation-template" 2249 | case "ods": 2250 | return "application/vnd.oasis.opendocument.spreadsheet" 2251 | case "ots": 2252 | return "application/vnd.oasis.opendocument.spreadsheet-template" 2253 | case "odt": 2254 | return "application/vnd.oasis.opendocument.text" 2255 | case "odm": 2256 | return "application/vnd.oasis.opendocument.text-master" 2257 | case "ott": 2258 | return "application/vnd.oasis.opendocument.text-template" 2259 | case "oth": 2260 | return "application/vnd.oasis.opendocument.text-web" 2261 | case "xo": 2262 | return "application/vnd.olpc-sugar" 2263 | case "dd2": 2264 | return "application/vnd.oma.dd2+xml" 2265 | case "oxt": 2266 | return "application/vnd.openofficeorg.extension" 2267 | case "pptx": 2268 | return "application/vnd.openxmlformats-officedocument.presentationml.presentation" 2269 | case "sldx": 2270 | return "application/vnd.openxmlformats-officedocument.presentationml.slide" 2271 | case "ppsx": 2272 | return "application/vnd.openxmlformats-officedocument.presentationml.slideshow" 2273 | case "potx": 2274 | return "application/vnd.openxmlformats-officedocument.presentationml.template" 2275 | case "xlsx": 2276 | return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" 2277 | case "xltx": 2278 | return "application/vnd.openxmlformats-officedocument.spreadsheetml.template" 2279 | case "docx": 2280 | return "application/vnd.openxmlformats-officedocument.wordprocessingml.document" 2281 | case "dotx": 2282 | return "application/vnd.openxmlformats-officedocument.wordprocessingml.template" 2283 | case "mgp": 2284 | return "application/vnd.osgeo.mapguide.package" 2285 | case "dp": 2286 | return "application/vnd.osgi.dp" 2287 | case "esa": 2288 | return "application/vnd.osgi.subsystem" 2289 | case "pdb", "pqa", "oprc": 2290 | return "application/vnd.palm" 2291 | case "paw": 2292 | return "application/vnd.pawaafile" 2293 | case "str": 2294 | return "application/vnd.pg.format" 2295 | case "ei6": 2296 | return "application/vnd.pg.osasli" 2297 | case "efif": 2298 | return "application/vnd.picsel" 2299 | case "wg": 2300 | return "application/vnd.pmi.widget" 2301 | case "plf": 2302 | return "application/vnd.pocketlearn" 2303 | case "pbd": 2304 | return "application/vnd.powerbuilder6" 2305 | case "box": 2306 | return "application/vnd.previewsystems.box" 2307 | case "mgz": 2308 | return "application/vnd.proteus.magazine" 2309 | case "qps": 2310 | return "application/vnd.publishare-delta-tree" 2311 | case "ptid": 2312 | return "application/vnd.pvi.ptid1" 2313 | case "qxd", "qxt", "qwd", "qwt", "qxl", "qxb": 2314 | return "application/vnd.quark.quarkxpress" 2315 | case "bed": 2316 | return "application/vnd.realvnc.bed" 2317 | case "mxl": 2318 | return "application/vnd.recordare.musicxml" 2319 | case "musicxml": 2320 | return "application/vnd.recordare.musicxml+xml" 2321 | case "cryptonote": 2322 | return "application/vnd.rig.cryptonote" 2323 | case "cod": 2324 | return "application/vnd.rim.cod" 2325 | case "rm": 2326 | return "application/vnd.rn-realmedia" 2327 | case "rmvb": 2328 | return "application/vnd.rn-realmedia-vbr" 2329 | case "link66": 2330 | return "application/vnd.route66.link66+xml" 2331 | case "st": 2332 | return "application/vnd.sailingtracker.track" 2333 | case "see": 2334 | return "application/vnd.seemail" 2335 | case "sema": 2336 | return "application/vnd.sema" 2337 | case "semd": 2338 | return "application/vnd.semd" 2339 | case "semf": 2340 | return "application/vnd.semf" 2341 | case "ifm": 2342 | return "application/vnd.shana.informed.formdata" 2343 | case "itp": 2344 | return "application/vnd.shana.informed.formtemplate" 2345 | case "iif": 2346 | return "application/vnd.shana.informed.interchange" 2347 | case "ipk": 2348 | return "application/vnd.shana.informed.package" 2349 | case "twd", "twds": 2350 | return "application/vnd.simtech-mindmapper" 2351 | case "mmf": 2352 | return "application/vnd.smaf" 2353 | case "teacher": 2354 | return "application/vnd.smart.teacher" 2355 | case "sdkm", "sdkd": 2356 | return "application/vnd.solent.sdkm+xml" 2357 | case "dxp": 2358 | return "application/vnd.spotfire.dxp" 2359 | case "sfs": 2360 | return "application/vnd.spotfire.sfs" 2361 | case "sdc": 2362 | return "application/vnd.stardivision.calc" 2363 | case "sda": 2364 | return "application/vnd.stardivision.draw" 2365 | case "sdd": 2366 | return "application/vnd.stardivision.impress" 2367 | case "smf": 2368 | return "application/vnd.stardivision.math" 2369 | case "sdw", "vor": 2370 | return "application/vnd.stardivision.writer" 2371 | case "sgl": 2372 | return "application/vnd.stardivision.writer-global" 2373 | case "smzip": 2374 | return "application/vnd.stepmania.package" 2375 | case "sm": 2376 | return "application/vnd.stepmania.stepchart" 2377 | case "sxc": 2378 | return "application/vnd.sun.xml.calc" 2379 | case "stc": 2380 | return "application/vnd.sun.xml.calc.template" 2381 | case "sxd": 2382 | return "application/vnd.sun.xml.draw" 2383 | case "std": 2384 | return "application/vnd.sun.xml.draw.template" 2385 | case "sxi": 2386 | return "application/vnd.sun.xml.impress" 2387 | case "sti": 2388 | return "application/vnd.sun.xml.impress.template" 2389 | case "sxm": 2390 | return "application/vnd.sun.xml.math" 2391 | case "sxw": 2392 | return "application/vnd.sun.xml.writer" 2393 | case "sxg": 2394 | return "application/vnd.sun.xml.writer.global" 2395 | case "stw": 2396 | return "application/vnd.sun.xml.writer.template" 2397 | case "sus", "susp": 2398 | return "application/vnd.sus-calendar" 2399 | case "svd": 2400 | return "application/vnd.svd" 2401 | case "sis", "sisx": 2402 | return "application/vnd.symbian.install" 2403 | case "xsm": 2404 | return "application/vnd.syncml+xml" 2405 | case "bdm": 2406 | return "application/vnd.syncml.dm+wbxml" 2407 | case "xdm": 2408 | return "application/vnd.syncml.dm+xml" 2409 | case "tao": 2410 | return "application/vnd.tao.intent-module-archive" 2411 | case "pcap", "cap", "dmp": 2412 | return "application/vnd.tcpdump.pcap" 2413 | case "tmo": 2414 | return "application/vnd.tmobile-livetv" 2415 | case "tpt": 2416 | return "application/vnd.trid.tpt" 2417 | case "mxs": 2418 | return "application/vnd.triscape.mxs" 2419 | case "tra": 2420 | return "application/vnd.trueapp" 2421 | case "ufd", "ufdl": 2422 | return "application/vnd.ufdl" 2423 | case "utz": 2424 | return "application/vnd.uiq.theme" 2425 | case "umj": 2426 | return "application/vnd.umajin" 2427 | case "unityweb": 2428 | return "application/vnd.unity" 2429 | case "uoml": 2430 | return "application/vnd.uoml+xml" 2431 | case "vcx": 2432 | return "application/vnd.vcx" 2433 | case "vsd", "vst", "vss", "vsw": 2434 | return "application/vnd.visio" 2435 | case "vis": 2436 | return "application/vnd.visionary" 2437 | case "vsf": 2438 | return "application/vnd.vsf" 2439 | case "wbxml": 2440 | return "application/vnd.wap.wbxml" 2441 | case "wmlc": 2442 | return "application/vnd.wap.wmlc" 2443 | case "wmlsc": 2444 | return "application/vnd.wap.wmlscriptc" 2445 | case "wtb": 2446 | return "application/vnd.webturbo" 2447 | case "nbp": 2448 | return "application/vnd.wolfram.player" 2449 | case "wpd": 2450 | return "application/vnd.wordperfect" 2451 | case "wqd": 2452 | return "application/vnd.wqd" 2453 | case "stf": 2454 | return "application/vnd.wt.stf" 2455 | case "xar": 2456 | return "application/vnd.xara" 2457 | case "xfdl": 2458 | return "application/vnd.xfdl" 2459 | case "hvd": 2460 | return "application/vnd.yamaha.hv-dic" 2461 | case "hvs": 2462 | return "application/vnd.yamaha.hv-script" 2463 | case "hvp": 2464 | return "application/vnd.yamaha.hv-voice" 2465 | case "osf": 2466 | return "application/vnd.yamaha.openscoreformat" 2467 | case "osfpvg": 2468 | return "application/vnd.yamaha.openscoreformat.osfpvg+xml" 2469 | case "saf": 2470 | return "application/vnd.yamaha.smaf-audio" 2471 | case "spf": 2472 | return "application/vnd.yamaha.smaf-phrase" 2473 | case "cmp": 2474 | return "application/vnd.yellowriver-custom-menu" 2475 | case "zir", "zirz": 2476 | return "application/vnd.zul" 2477 | case "zaz": 2478 | return "application/vnd.zzazz.deck+xml" 2479 | case "vxml": 2480 | return "application/voicexml+xml" 2481 | case "wgt": 2482 | return "application/widget" 2483 | case "hlp": 2484 | return "application/winhlp" 2485 | case "wsdl": 2486 | return "application/wsdl+xml" 2487 | case "wspolicy": 2488 | return "application/wspolicy+xml" 2489 | case "7z": 2490 | return "application/x-7z-compressed" 2491 | case "abw": 2492 | return "application/x-abiword" 2493 | case "ace": 2494 | return "application/x-ace-compressed" 2495 | case "dmg": 2496 | return "application/x-apple-diskimage" 2497 | case "aab", "x32", "u32", "vox": 2498 | return "application/x-authorware-bin" 2499 | case "aam": 2500 | return "application/x-authorware-map" 2501 | case "aas": 2502 | return "application/x-authorware-seg" 2503 | case "bcpio": 2504 | return "application/x-bcpio" 2505 | case "torrent": 2506 | return "application/x-bittorrent" 2507 | case "blb", "blorb": 2508 | return "application/x-blorb" 2509 | case "bz": 2510 | return "application/x-bzip" 2511 | case "bz2", "boz": 2512 | return "application/x-bzip2" 2513 | case "cbr", "cba", "cbt", "cbz", "cb7": 2514 | return "application/x-cbr" 2515 | case "vcd": 2516 | return "application/x-cdlink" 2517 | case "cfs": 2518 | return "application/x-cfs-compressed" 2519 | case "chat": 2520 | return "application/x-chat" 2521 | case "pgn": 2522 | return "application/x-chess-pgn" 2523 | case "nsc": 2524 | return "application/x-conference" 2525 | case "cpio": 2526 | return "application/x-cpio" 2527 | case "csh": 2528 | return "application/x-csh" 2529 | case "deb", "udeb": 2530 | return "application/x-debian-package" 2531 | case "dgc": 2532 | return "application/x-dgc-compressed" 2533 | case "dir", "dcr", "dxr", "cst", "cct", "cxt", "w3d", "fgd", "swa": 2534 | return "application/x-director" 2535 | case "wad": 2536 | return "application/x-doom" 2537 | case "ncx": 2538 | return "application/x-dtbncx+xml" 2539 | case "dtb": 2540 | return "application/x-dtbook+xml" 2541 | case "res": 2542 | return "application/x-dtbresource+xml" 2543 | case "dvi": 2544 | return "application/x-dvi" 2545 | case "evy": 2546 | return "application/x-envoy" 2547 | case "eva": 2548 | return "application/x-eva" 2549 | case "bdf": 2550 | return "application/x-font-bdf" 2551 | case "gsf": 2552 | return "application/x-font-ghostscript" 2553 | case "psf": 2554 | return "application/x-font-linux-psf" 2555 | case "pcf": 2556 | return "application/x-font-pcf" 2557 | case "snf": 2558 | return "application/x-font-snf" 2559 | case "pfa", "pfb", "pfm", "afm": 2560 | return "application/x-font-type1" 2561 | case "arc": 2562 | return "application/x-freearc" 2563 | case "spl": 2564 | return "application/x-futuresplash" 2565 | case "gca": 2566 | return "application/x-gca-compressed" 2567 | case "ulx": 2568 | return "application/x-glulx" 2569 | case "gnumeric": 2570 | return "application/x-gnumeric" 2571 | case "gramps": 2572 | return "application/x-gramps-xml" 2573 | case "gtar": 2574 | return "application/x-gtar" 2575 | case "hdf": 2576 | return "application/x-hdf" 2577 | case "install": 2578 | return "application/x-install-instructions" 2579 | case "iso": 2580 | return "application/x-iso9660-image" 2581 | case "jnlp": 2582 | return "application/x-java-jnlp-file" 2583 | case "latex": 2584 | return "application/x-latex" 2585 | case "lzh", "lha": 2586 | return "application/x-lzh-compressed" 2587 | case "mie": 2588 | return "application/x-mie" 2589 | case "prc", "mobi": 2590 | return "application/x-mobipocket-ebook" 2591 | case "application": 2592 | return "application/x-ms-application" 2593 | case "lnk": 2594 | return "application/x-ms-shortcut" 2595 | case "wmd": 2596 | return "application/x-ms-wmd" 2597 | case "wmz": 2598 | return "application/x-ms-wmz" 2599 | case "xbap": 2600 | return "application/x-ms-xbap" 2601 | case "mdb": 2602 | return "application/x-msaccess" 2603 | case "obd": 2604 | return "application/x-msbinder" 2605 | case "crd": 2606 | return "application/x-mscardfile" 2607 | case "clp": 2608 | return "application/x-msclip" 2609 | case "exe", "dll", "com", "bat", "msi": 2610 | return "application/x-msdownload" 2611 | case "mvb", "m13", "m14": 2612 | return "application/x-msmediaview" 2613 | case "wmf", "emf", "emz": 2614 | return "application/x-msmetafile" 2615 | case "mny": 2616 | return "application/x-msmoney" 2617 | case "pub": 2618 | return "application/x-mspublisher" 2619 | case "scd": 2620 | return "application/x-msschedule" 2621 | case "trm": 2622 | return "application/x-msterminal" 2623 | case "wri": 2624 | return "application/x-mswrite" 2625 | case "nc", "cdf": 2626 | return "application/x-netcdf" 2627 | case "nzb": 2628 | return "application/x-nzb" 2629 | case "p12", "pfx": 2630 | return "application/x-pkcs12" 2631 | case "p7b", "spc": 2632 | return "application/x-pkcs7-certificates" 2633 | case "p7r": 2634 | return "application/x-pkcs7-certreqresp" 2635 | case "rar": 2636 | return "application/x-rar-compressed" 2637 | case "ris": 2638 | return "application/x-research-info-systems" 2639 | case "sh": 2640 | return "application/x-sh" 2641 | case "shar": 2642 | return "application/x-shar" 2643 | case "swf": 2644 | return "application/x-shockwave-flash" 2645 | case "xap": 2646 | return "application/x-silverlight-app" 2647 | case "sql": 2648 | return "application/x-sql" 2649 | case "sit": 2650 | return "application/x-stuffit" 2651 | case "sitx": 2652 | return "application/x-stuffitx" 2653 | case "srt": 2654 | return "application/x-subrip" 2655 | case "sv4cpio": 2656 | return "application/x-sv4cpio" 2657 | case "sv4crc": 2658 | return "application/x-sv4crc" 2659 | case "t3": 2660 | return "application/x-t3vm-image" 2661 | case "gam": 2662 | return "application/x-tads" 2663 | case "tar": 2664 | return "application/x-tar" 2665 | case "tcl": 2666 | return "application/x-tcl" 2667 | case "tex": 2668 | return "application/x-tex" 2669 | case "tfm": 2670 | return "application/x-tex-tfm" 2671 | case "texinfo", "texi": 2672 | return "application/x-texinfo" 2673 | case "obj": 2674 | return "application/x-tgif" 2675 | case "ustar": 2676 | return "application/x-ustar" 2677 | case "src": 2678 | return "application/x-wais-source" 2679 | case "der", "crt": 2680 | return "application/x-x509-ca-cert" 2681 | case "fig": 2682 | return "application/x-xfig" 2683 | case "xlf": 2684 | return "application/x-xliff+xml" 2685 | case "xpi": 2686 | return "application/x-xpinstall" 2687 | case "xz": 2688 | return "application/x-xz" 2689 | case "z1", "z2", "z3", "z4", "z5", "z6", "z7", "z8": 2690 | return "application/x-zmachine" 2691 | case "xaml": 2692 | return "application/xaml+xml" 2693 | case "xdf": 2694 | return "application/xcap-diff+xml" 2695 | case "xenc": 2696 | return "application/xenc+xml" 2697 | case "xhtml", "xht": 2698 | return "application/xhtml+xml" 2699 | case "xml", "xsl": 2700 | return "application/xml" 2701 | case "dtd": 2702 | return "application/xml-dtd" 2703 | case "xop": 2704 | return "application/xop+xml" 2705 | case "xpl": 2706 | return "application/xproc+xml" 2707 | case "xslt": 2708 | return "application/xslt+xml" 2709 | case "xspf": 2710 | return "application/xspf+xml" 2711 | case "mxml", "xhvml", "xvml", "xvm": 2712 | return "application/xv+xml" 2713 | case "yang": 2714 | return "application/yang" 2715 | case "yin": 2716 | return "application/yin+xml" 2717 | case "zip": 2718 | return "application/zip" 2719 | case "adp": 2720 | return "audio/adpcm" 2721 | case "au", "snd": 2722 | return "audio/basic" 2723 | case "mid", "midi", "kar", "rmi": 2724 | return "audio/midi" 2725 | case "m4a", "mp4a": 2726 | return "audio/mp4" 2727 | case "mpga", "mp2", "mp2a", "mp3", "m2a", "m3a": 2728 | return "audio/mpeg" 2729 | case "oga", "ogg", "spx": 2730 | return "audio/ogg" 2731 | case "s3m": 2732 | return "audio/s3m" 2733 | case "sil": 2734 | return "audio/silk" 2735 | case "uva", "uvva": 2736 | return "audio/vnd.dece.audio" 2737 | case "eol": 2738 | return "audio/vnd.digital-winds" 2739 | case "dra": 2740 | return "audio/vnd.dra" 2741 | case "dts": 2742 | return "audio/vnd.dts" 2743 | case "dtshd": 2744 | return "audio/vnd.dts.hd" 2745 | case "lvp": 2746 | return "audio/vnd.lucent.voice" 2747 | case "pya": 2748 | return "audio/vnd.ms-playready.media.pya" 2749 | case "ecelp4800": 2750 | return "audio/vnd.nuera.ecelp4800" 2751 | case "ecelp7470": 2752 | return "audio/vnd.nuera.ecelp7470" 2753 | case "ecelp9600": 2754 | return "audio/vnd.nuera.ecelp9600" 2755 | case "rip": 2756 | return "audio/vnd.rip" 2757 | case "weba": 2758 | return "audio/webm" 2759 | case "aac": 2760 | return "audio/x-aac" 2761 | case "aif", "aiff", "aifc": 2762 | return "audio/x-aiff" 2763 | case "caf": 2764 | return "audio/x-caf" 2765 | case "flac": 2766 | return "audio/x-flac" 2767 | case "mka": 2768 | return "audio/x-matroska" 2769 | case "m3u": 2770 | return "audio/x-mpegurl" 2771 | case "wax": 2772 | return "audio/x-ms-wax" 2773 | case "wma": 2774 | return "audio/x-ms-wma" 2775 | case "ram", "ra": 2776 | return "audio/x-pn-realaudio" 2777 | case "rmp": 2778 | return "audio/x-pn-realaudio-plugin" 2779 | case "wav": 2780 | return "audio/x-wav" 2781 | case "xm": 2782 | return "audio/xm" 2783 | case "cdx": 2784 | return "chemical/x-cdx" 2785 | case "cif": 2786 | return "chemical/x-cif" 2787 | case "cmdf": 2788 | return "chemical/x-cmdf" 2789 | case "cml": 2790 | return "chemical/x-cml" 2791 | case "csml": 2792 | return "chemical/x-csml" 2793 | case "xyz": 2794 | return "chemical/x-xyz" 2795 | case "ttc": 2796 | return "font/collection" 2797 | case "otf": 2798 | return "font/otf" 2799 | case "ttf": 2800 | return "font/ttf" 2801 | case "woff": 2802 | return "font/woff" 2803 | case "woff2": 2804 | return "font/woff2" 2805 | case "bmp": 2806 | return "image/bmp" 2807 | case "cgm": 2808 | return "image/cgm" 2809 | case "g3": 2810 | return "image/g3fax" 2811 | case "gif": 2812 | return "image/gif" 2813 | case "ief": 2814 | return "image/ief" 2815 | case "jpeg", "jpg", "jpe": 2816 | return "image/jpeg" 2817 | case "ktx": 2818 | return "image/ktx" 2819 | case "png": 2820 | return "image/png" 2821 | case "btif": 2822 | return "image/prs.btif" 2823 | case "sgi": 2824 | return "image/sgi" 2825 | case "svg", "svgz": 2826 | return "image/svg+xml" 2827 | case "tiff", "tif": 2828 | return "image/tiff" 2829 | case "psd": 2830 | return "image/vnd.adobe.photoshop" 2831 | case "uvi", "uvvi", "uvg", "uvvg": 2832 | return "image/vnd.dece.graphic" 2833 | case "djvu", "djv": 2834 | return "image/vnd.djvu" 2835 | case "sub": 2836 | return "image/vnd.dvb.subtitle" 2837 | case "dwg": 2838 | return "image/vnd.dwg" 2839 | case "dxf": 2840 | return "image/vnd.dxf" 2841 | case "fbs": 2842 | return "image/vnd.fastbidsheet" 2843 | case "fpx": 2844 | return "image/vnd.fpx" 2845 | case "fst": 2846 | return "image/vnd.fst" 2847 | case "mmr": 2848 | return "image/vnd.fujixerox.edmics-mmr" 2849 | case "rlc": 2850 | return "image/vnd.fujixerox.edmics-rlc" 2851 | case "mdi": 2852 | return "image/vnd.ms-modi" 2853 | case "wdp": 2854 | return "image/vnd.ms-photo" 2855 | case "npx": 2856 | return "image/vnd.net-fpx" 2857 | case "wbmp": 2858 | return "image/vnd.wap.wbmp" 2859 | case "xif": 2860 | return "image/vnd.xiff" 2861 | case "webp": 2862 | return "image/webp" 2863 | case "3ds": 2864 | return "image/x-3ds" 2865 | case "ras": 2866 | return "image/x-cmu-raster" 2867 | case "cmx": 2868 | return "image/x-cmx" 2869 | case "fh", "fhc", "fh4", "fh5", "fh7": 2870 | return "image/x-freehand" 2871 | case "ico": 2872 | return "image/x-icon" 2873 | case "sid": 2874 | return "image/x-mrsid-image" 2875 | case "pcx": 2876 | return "image/x-pcx" 2877 | case "pic", "pct": 2878 | return "image/x-pict" 2879 | case "pnm": 2880 | return "image/x-portable-anymap" 2881 | case "pbm": 2882 | return "image/x-portable-bitmap" 2883 | case "pgm": 2884 | return "image/x-portable-graymap" 2885 | case "ppm": 2886 | return "image/x-portable-pixmap" 2887 | case "rgb": 2888 | return "image/x-rgb" 2889 | case "tga": 2890 | return "image/x-tga" 2891 | case "xbm": 2892 | return "image/x-xbitmap" 2893 | case "xpm": 2894 | return "image/x-xpixmap" 2895 | case "xwd": 2896 | return "image/x-xwindowdump" 2897 | case "eml", "mime": 2898 | return "message/rfc822" 2899 | case "igs", "iges": 2900 | return "model/iges" 2901 | case "msh", "mesh", "silo": 2902 | return "model/mesh" 2903 | case "dae": 2904 | return "model/vnd.collada+xml" 2905 | case "dwf": 2906 | return "model/vnd.dwf" 2907 | case "gdl": 2908 | return "model/vnd.gdl" 2909 | case "gtw": 2910 | return "model/vnd.gtw" 2911 | case "mts": 2912 | return "model/vnd.mts" 2913 | case "vtu": 2914 | return "model/vnd.vtu" 2915 | case "wrl", "vrml": 2916 | return "model/vrml" 2917 | case "x3db", "x3dbz": 2918 | return "model/x3d+binary" 2919 | case "x3dv", "x3dvz": 2920 | return "model/x3d+vrml" 2921 | case "x3d", "x3dz": 2922 | return "model/x3d+xml" 2923 | case "appcache": 2924 | return "text/cache-manifest" 2925 | case "ics", "ifb": 2926 | return "text/calendar" 2927 | case "css": 2928 | return "text/css" 2929 | case "csv": 2930 | return "text/csv" 2931 | case "html", "htm": 2932 | return "text/html" 2933 | case "n3": 2934 | return "text/n3" 2935 | case "txt", "text", "conf", "def", "list", "log", "in": 2936 | return "text/plain" 2937 | case "dsc": 2938 | return "text/prs.lines.tag" 2939 | case "rtx": 2940 | return "text/richtext" 2941 | case "sgml", "sgm": 2942 | return "text/sgml" 2943 | case "tsv": 2944 | return "text/tab-separated-values" 2945 | case "t", "tr", "roff", "man", "me", "ms": 2946 | return "text/troff" 2947 | case "ttl": 2948 | return "text/turtle" 2949 | case "uri", "uris", "urls": 2950 | return "text/uri-list" 2951 | case "vcard": 2952 | return "text/vcard" 2953 | case "curl": 2954 | return "text/vnd.curl" 2955 | case "dcurl": 2956 | return "text/vnd.curl.dcurl" 2957 | case "mcurl": 2958 | return "text/vnd.curl.mcurl" 2959 | case "scurl": 2960 | return "text/vnd.curl.scurl" 2961 | case "fly": 2962 | return "text/vnd.fly" 2963 | case "flx": 2964 | return "text/vnd.fmi.flexstor" 2965 | case "gv": 2966 | return "text/vnd.graphviz" 2967 | case "3dml": 2968 | return "text/vnd.in3d.3dml" 2969 | case "spot": 2970 | return "text/vnd.in3d.spot" 2971 | case "jad": 2972 | return "text/vnd.sun.j2me.app-descriptor" 2973 | case "wml": 2974 | return "text/vnd.wap.wml" 2975 | case "wmls": 2976 | return "text/vnd.wap.wmlscript" 2977 | case "s", "asm": 2978 | return "text/x-asm" 2979 | case "c", "cc", "cxx", "cpp", "h", "hh", "dic": 2980 | return "text/x-c" 2981 | case "f", "for", "f77", "f90": 2982 | return "text/x-fortran" 2983 | case "java": 2984 | return "text/x-java-source" 2985 | case "nfo": 2986 | return "text/x-nfo" 2987 | case "opml": 2988 | return "text/x-opml" 2989 | case "p", "pas": 2990 | return "text/x-pascal" 2991 | case "etx": 2992 | return "text/x-setext" 2993 | case "sfv": 2994 | return "text/x-sfv" 2995 | case "uu": 2996 | return "text/x-uuencode" 2997 | case "vcs": 2998 | return "text/x-vcalendar" 2999 | case "vcf": 3000 | return "text/x-vcard" 3001 | case "3gp": 3002 | return "video/3gpp" 3003 | case "3g2": 3004 | return "video/3gpp2" 3005 | case "h261": 3006 | return "video/h261" 3007 | case "h263": 3008 | return "video/h263" 3009 | case "h264": 3010 | return "video/h264" 3011 | case "jpgv": 3012 | return "video/jpeg" 3013 | case "jpm", "jpgm": 3014 | return "video/jpm" 3015 | case "mj2", "mjp2": 3016 | return "video/mj2" 3017 | case "mp4", "mp4v", "mpg4": 3018 | return "video/mp4" 3019 | case "mpeg", "mpg", "mpe", "m1v", "m2v": 3020 | return "video/mpeg" 3021 | case "ogv": 3022 | return "video/ogg" 3023 | case "qt", "mov": 3024 | return "video/quicktime" 3025 | case "uvh", "uvvh": 3026 | return "video/vnd.dece.hd" 3027 | case "uvm", "uvvm": 3028 | return "video/vnd.dece.mobile" 3029 | case "uvp", "uvvp": 3030 | return "video/vnd.dece.pd" 3031 | case "uvs", "uvvs": 3032 | return "video/vnd.dece.sd" 3033 | case "uvv", "uvvv": 3034 | return "video/vnd.dece.video" 3035 | case "dvb": 3036 | return "video/vnd.dvb.file" 3037 | case "fvt": 3038 | return "video/vnd.fvt" 3039 | case "mxu", "m4u": 3040 | return "video/vnd.mpegurl" 3041 | case "pyv": 3042 | return "video/vnd.ms-playready.media.pyv" 3043 | case "uvu", "uvvu": 3044 | return "video/vnd.uvvu.mp4" 3045 | case "viv": 3046 | return "video/vnd.vivo" 3047 | case "webm": 3048 | return "video/webm" 3049 | case "f4v": 3050 | return "video/x-f4v" 3051 | case "fli": 3052 | return "video/x-fli" 3053 | case "flv": 3054 | return "video/x-flv" 3055 | case "m4v": 3056 | return "video/x-m4v" 3057 | case "mkv", "mk3d", "mks": 3058 | return "video/x-matroska" 3059 | case "mng": 3060 | return "video/x-mng" 3061 | case "asf", "asx": 3062 | return "video/x-ms-asf" 3063 | case "vob": 3064 | return "video/x-ms-vob" 3065 | case "wm": 3066 | return "video/x-ms-wm" 3067 | case "wmv": 3068 | return "video/x-ms-wmv" 3069 | case "wmx": 3070 | return "video/x-ms-wmx" 3071 | case "wvx": 3072 | return "video/x-ms-wvx" 3073 | case "avi": 3074 | return "video/x-msvideo" 3075 | case "movie": 3076 | return "video/x-sgi-movie" 3077 | case "smv": 3078 | return "video/x-smv" 3079 | case "ice": 3080 | return "x-conference/x-cooltalk" 3081 | default: 3082 | return nil 3083 | } 3084 | } 3085 | --------------------------------------------------------------------------------