Skip to content

Instantly share code, notes, and snippets.

@Andrew-Lees11
Last active August 3, 2018 15:37
Show Gist options
  • Save Andrew-Lees11/f943caa4c687084d15668d6da171e609 to your computer and use it in GitHub Desktop.
Save Andrew-Lees11/f943caa4c687084d15668d6da171e609 to your computer and use it in GitHub Desktop.
/*
* 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