Last active
August 3, 2018 15:37
-
-
Save Andrew-Lees11/f943caa4c687084d15668d6da171e609 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright IBM Corporation 2016 | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
import Foundation | |
import LoggerAPI | |
// MARK: ContentType | |
/** | |
The `ContentType` class provides functions to determine the MIME content type for a given file extension. The user can pass in a complete file name e.g. "foo.png" or just the file extension e.g. "png", or they can pass in both a MIME content type and a file extension and query whether they match. | |
### Usage Example: ### | |
In this example, a `ContentType` instance is initialised called contentType. This instance is then used to obtain the MIME content type of the file "foo.png", which is identified as "image/png". | |
```swift | |
let contentType = ContentType.sharedInstance | |
let result = contentType.getContentType(forFileName: "foo.png") | |
print(String(describing: result)) | |
// Prints Optional("image/png") | |
``` | |
*/ | |
public class ContentType: CustomStringConvertible { | |
let mediaType: MediaType | |
let charset: String? | |
let boundary: String? | |
public init (mediaType: MediaType, charset: String? = nil, boundary: String? = nil) { | |
self.mediaType = mediaType | |
self.charset = charset | |
self.boundary = boundary | |
} | |
public convenience init? (_ contentType: String) { | |
let contentTypeComponents = contentType | |
.replacingOccurrences(of: " ", with: "") | |
.components(separatedBy: ";") | |
guard let mediaType = MediaType(contentTypeComponents[0]) else { | |
return nil | |
} | |
let charsetParameter = contentTypeComponents.filter {$0.lowercased().hasPrefix(("charset="))} | |
let charset = | |
charsetParameter.count == 1 | |
? charsetParameter[0].dropFirst(8).lowercased() | |
: nil | |
let boundaryParameter = contentTypeComponents.filter {$0.hasPrefix("boundary=")} | |
let boundary = | |
boundaryParameter.count == 1 | |
? String(boundaryParameter[0].dropFirst(9)) | |
: nil | |
self.init(mediaType: mediaType, charset: charset, boundary: boundary) | |
} | |
public var description: String { | |
var charsetString = "" | |
var boundaryString = "" | |
if let charset = charset { | |
charsetString = "; charset=\(charset)" | |
} | |
if let boundary = boundary { | |
boundaryString = "; boundary=\(boundary)" | |
} | |
return "\(mediaType.description)\(charsetString)\(boundaryString)" | |
} | |
/// A dictionary of extensions to MIME type descriptions | |
private var extToContentType = [String:String]() | |
/// Shared singleton instance. | |
public static let sharedInstance = ContentType() | |
/// The following function loads the MIME types from an external file | |
private init () { | |
self.mediaType = MediaType(type: .application, subtype: "json") | |
self.charset = nil | |
self.boundary = nil | |
guard let contentTypesData = contentTypesString.data(using: .utf8) else { | |
Log.error("Error parsing \(contentTypesString)") | |
return | |
} | |
let jsonParseOptions = JSONSerialization.ReadingOptions.mutableContainers | |
guard let parsedObject = try? JSONSerialization.jsonObject(with: contentTypesData, options: jsonParseOptions), | |
let jsonData = parsedObject as? [String : [String]] else { | |
Log.error("JSON could not be parsed") | |
return | |
} | |
for (contentType, exts) in jsonData { | |
for ext in exts { | |
extToContentType[ext] = contentType | |
} | |
} | |
} | |
/** | |
Get the content type for the given file extension. | |
### Usage Example: ### | |
```swift | |
let contentType = ContentType.sharedInstance | |
let result = contentType.getContentType(forExtension: "js") | |
print(String(describing: result)) | |
//Prints Optional("application/javascript") | |
``` | |
- Parameter forExtension: The file extension. | |
- Returns: An Optional String for the content type. | |
*/ | |
public func getContentType(forExtension ext: String) -> String? { | |
return extToContentType[ext] | |
} | |
/** | |
Get the content type for the given file based on its extension. | |
### Usage Example: ### | |
```swift | |
let contentType = ContentType.sharedInstance | |
let result = contentType.getContentType(forFileName: "test.html") | |
print(String(describing: result)) | |
//Prints Optional("text/html") | |
``` | |
- Parameter forFileName: The file name. | |
- Returns: An Optional String for the content type. | |
*/ | |
public func getContentType(forFileName fileName: String) -> String? { | |
let lastPathElemRange: Range<String.Index> | |
let extRange: Range<String.Index> | |
let backwards = String.CompareOptions.backwards | |
if let lastSlash = fileName.range(of: "/", options: backwards) { | |
lastPathElemRange = fileName.index(after: lastSlash.lowerBound)..<fileName.endIndex | |
} else { | |
lastPathElemRange = fileName.startIndex..<fileName.endIndex | |
} | |
if let lastDot = fileName.range(of: ".", options: backwards, range: lastPathElemRange) { | |
extRange = fileName.index(after: lastDot.lowerBound)..<fileName.endIndex | |
} else { | |
// No "extension", use the entire last path element as the "extension" | |
extRange = lastPathElemRange | |
} | |
return getContentType(forExtension: String(fileName[extRange])) | |
} | |
/** | |
Check if the message content type matches the type descriptor. | |
### Usage Example: ### | |
```swift | |
let contentType = ContentType.sharedInstance | |
var result = contentType.isContentType("application/json", ofType: "json") | |
print(String(describing: result)) | |
//Prints true | |
``` | |
- Parameter messageContentType: The content type. | |
- Parameter ofType: The description of the type. | |
- Returns: True if the types matched. | |
*/ | |
public func isContentType(_ messageContentType: String, ofType typeDescriptor: String) -> Bool { | |
let type = typeDescriptor.lowercased() | |
let typeAndSubtype = messageContentType.components(separatedBy: ";")[0].lowercased() | |
if typeAndSubtype == type { | |
return true | |
} | |
// typeDescriptor is file extension | |
if typeAndSubtype == extToContentType[type] { | |
return true | |
} | |
// typeDescriptor is a shortcut | |
let normalizedType = normalize(type: type) | |
if typeAndSubtype == normalizedType { | |
return true | |
} | |
// the types match and the subtype in typeDescriptor is "*" | |
let messageTypePair = typeAndSubtype.components(separatedBy: "/") | |
let normalizedTypePair = normalizedType.components(separatedBy: "/") | |
if messageTypePair.count == 2 && normalizedTypePair.count == 2 | |
&& messageTypePair[0] == normalizedTypePair[0] && normalizedTypePair[1] == "*" { | |
return true | |
} | |
return false | |
} | |
/// Normalize the type | |
/// | |
/// - Parameter type: the content type | |
/// | |
/// - Returns: the normalized String | |
private func normalize(type: String) -> String { | |
switch type { | |
case "urlencoded": | |
return "application/x-www-form-urlencoded" | |
case "multipart": | |
return "multipart/*" | |
case "json": | |
return "application/json" | |
// swiftlint:disable todo | |
// TODO: +json? | |
// if (type[0] === '+') { | |
// // "+json" -> "*/*+json" expando | |
// type = '*/*' + type | |
// } | |
// swiftlint:enable todo | |
default: | |
return type | |
} | |
} | |
} | |
/** | |
* Copyright IBM Corporation 2015 | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
**/ | |
import Foundation | |
import XCTest | |
@testable import Kitura | |
class TestContentType: KituraTest { | |
static var allTests: [(String, (TestContentType) -> () throws -> Void)] { | |
return [ | |
("testInitialize", testInitialize), | |
("testFilename", testFilename), | |
("testIsContentType", testIsContentType), | |
("testAllTextMediaTypeBuilder", testAllTextMediaTypeBuilder), | |
("testAllTextSlashMediaTypeBuilder", testAllTextSlashMediaTypeBuilder), | |
("testHTMLMediaTypeBuilder", testPartsHTMLMediaTypeBuilder), | |
("testMediaCaseInsensitive", testMediaCaseInsensitive), | |
("testPartsAllTextMediaTypeBuilder", testPartsAllTextMediaTypeBuilder), | |
("testPartsHTMLMediaTypeBuilder", testPartsHTMLMediaTypeBuilder), | |
("testPartsMediaCaseInsensitive", testPartsMediaCaseInsensitive), | |
("testTextContentTypeBuilder", testTextContentTypeBuilder), | |
("testCharsetCaseInsensitive", testCharsetCaseInsensitive), | |
("testMultipartContentTypeBuilder", testMultipartContentTypeBuilder), | |
("testBoundaryCaseSensitive", testBoundaryCaseSensitive), | |
] | |
} | |
let contentType = ContentType.sharedInstance | |
func testInitialize() { | |
let pngType = contentType.getContentType(forExtension: "png") | |
XCTAssertEqual(pngType, "image/png") | |
XCTAssertNotEqual(pngType, "application/javascript") | |
let htmlType = contentType.getContentType(forExtension: "html") | |
XCTAssertEqual(htmlType, "text/html") | |
XCTAssertNotEqual(pngType, "application/javascript") | |
let jsType = contentType.getContentType(forExtension: "js") | |
XCTAssertEqual(jsType, "application/javascript") | |
} | |
func testFilename() { | |
var result = contentType.getContentType(forFileName: "foo.png") | |
XCTAssertEqual(result, "image/png") | |
result = contentType.getContentType(forFileName: "a/foo.png") | |
XCTAssertEqual(result, "image/png") | |
result = contentType.getContentType(forFileName: "a/b/c/foo.png") | |
XCTAssertEqual(result, "image/png") | |
result = contentType.getContentType(forFileName: "test.html") | |
XCTAssertEqual(result, "text/html") | |
result = contentType.getContentType(forFileName: "a/b/c/test.html") | |
XCTAssertEqual(result, "text/html") | |
result = contentType.getContentType(forFileName: "test.with.periods.html") | |
XCTAssertEqual(result, "text/html") | |
result = contentType.getContentType(forFileName: "test/html") | |
XCTAssertEqual(result, "text/html") | |
} | |
func testIsContentType() { | |
var result = contentType.isContentType("application/json", ofType: "json") | |
XCTAssertTrue(result) | |
result = contentType.isContentType("json", ofType: "json") | |
XCTAssertTrue(result) | |
result = contentType.isContentType("text/html", ofType: "json") | |
XCTAssertFalse(result) | |
result = contentType.isContentType("application/x-www-form-urlencoded", ofType: "urlencoded") | |
XCTAssertTrue(result) | |
result = contentType.isContentType("multipart/form-data", ofType: "multipart") | |
XCTAssertTrue(result) | |
} | |
func testAllTextMediaTypeBuilder() { | |
let textMediaType = MediaType("text") | |
XCTAssertEqual(textMediaType?.description, "text/*") | |
XCTAssertEqual(textMediaType?.topLevelType, .text) | |
XCTAssertEqual(textMediaType?.subtype, "*") | |
} | |
func testAllTextSlashMediaTypeBuilder() { | |
let textMediaType = MediaType("text/") | |
XCTAssertEqual(textMediaType?.description, "text/*") | |
XCTAssertEqual(textMediaType?.topLevelType, .text) | |
XCTAssertEqual(textMediaType?.subtype, "*") | |
} | |
func testHTMLMediaTypeBuilder() { | |
let textMediaType = MediaType("text/html") | |
XCTAssertEqual(textMediaType?.description, "text/html") | |
XCTAssertEqual(textMediaType?.topLevelType, .text) | |
XCTAssertEqual(textMediaType?.subtype, "html") | |
} | |
func testMediaCaseInsensitive() { | |
let textMediaType = MediaType("TexT/HTml") | |
XCTAssertEqual(textMediaType?.description, "text/html") | |
XCTAssertEqual(textMediaType?.topLevelType, .text) | |
XCTAssertEqual(textMediaType?.subtype, "html") | |
} | |
func testPartsAllTextMediaTypeBuilder() { | |
let textMediaType = MediaType(type: .text) | |
XCTAssertEqual(textMediaType.description, "text/*") | |
XCTAssertEqual(textMediaType.topLevelType, .text) | |
XCTAssertEqual(textMediaType.subtype, "*") | |
} | |
func testPartsHTMLMediaTypeBuilder() { | |
let textMediaType = MediaType(type: .text, subtype: "html") | |
XCTAssertEqual(textMediaType.description, "text/html") | |
XCTAssertEqual(textMediaType.topLevelType, .text) | |
XCTAssertEqual(textMediaType.subtype, "html") | |
} | |
func testPartsMediaCaseInsensitive() { | |
let textMediaType = MediaType(type: .text , subtype: "hTmL") | |
XCTAssertEqual(textMediaType.description, "text/html") | |
XCTAssertEqual(textMediaType.topLevelType, .text) | |
XCTAssertEqual(textMediaType.subtype, "html") | |
} | |
func testTextContentTypeBuilder() { | |
let textContentType = ContentType("text/html; charset=utf-8") | |
XCTAssertEqual(textContentType?.description, "text/html; charset=utf-8") | |
XCTAssertEqual(textContentType?.mediaType.description, "text/html") | |
XCTAssertEqual(textContentType?.mediaType.topLevelType, .text) | |
XCTAssertEqual(textContentType?.mediaType.subtype, "html") | |
XCTAssertEqual(textContentType?.charset, "utf-8") | |
XCTAssertNil(textContentType?.boundary) | |
} | |
func testCharsetCaseInsensitive() { | |
let textContentType = ContentType("tEXt/hTmL; cHaRset=UTf-8") | |
XCTAssertEqual(textContentType?.description, "text/html; charset=utf-8") | |
XCTAssertEqual(textContentType?.mediaType.description, "text/html") | |
XCTAssertEqual(textContentType?.mediaType.topLevelType, .text) | |
XCTAssertEqual(textContentType?.mediaType.subtype, "html") | |
XCTAssertEqual(textContentType?.charset, "utf-8") | |
XCTAssertNil(textContentType?.boundary) | |
} | |
func testMultipartContentTypeBuilder() { | |
let textContentType = ContentType("multipart/form-data; boundary=something") | |
XCTAssertEqual(textContentType?.description, "multipart/form-data; boundary=something") | |
XCTAssertEqual(textContentType?.mediaType.description, "multipart/form-data") | |
XCTAssertEqual(textContentType?.mediaType.topLevelType, .multipart) | |
XCTAssertEqual(textContentType?.mediaType.subtype, "form-data") | |
XCTAssertNil(textContentType?.charset) | |
XCTAssertEqual(textContentType?.boundary, "something") | |
} | |
func testBoundaryCaseSensitive() { | |
let textContentType = ContentType("multipart/form-data; boundary=someTHING") | |
XCTAssertEqual(textContentType?.description, "multipart/form-data; boundary=someTHING") | |
XCTAssertEqual(textContentType?.mediaType.description, "multipart/form-data") | |
XCTAssertEqual(textContentType?.mediaType.topLevelType, .multipart) | |
XCTAssertEqual(textContentType?.mediaType.subtype, "form-data") | |
XCTAssertNil(textContentType?.charset) | |
XCTAssertEqual(textContentType?.boundary, "someTHING") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment