| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- //
- // HttpRequest.swift
- // Swifter
- //
- // Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
- //
- import Foundation
- public class HttpRequest {
- public var path: String = ""
- public var queryParams: [(String, String)] = []
- public var method: String = ""
- public var headers: [String: String] = [:]
- public var body: [UInt8] = []
- public var address: String? = ""
- public var params: [String: String] = [:]
- public init() {}
- public func hasTokenForHeader(_ headerName: String, token: String) -> Bool {
- guard let headerValue = headers[headerName] else {
- return false
- }
- return headerValue.components(separatedBy: ",").filter({ $0.trimmingCharacters(in: .whitespaces).lowercased() == token }).count > 0
- }
- public func parseUrlencodedForm() -> [(String, String)] {
- guard let contentTypeHeader = headers["content-type"] else {
- return []
- }
- let contentTypeHeaderTokens = contentTypeHeader.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) }
- guard let contentType = contentTypeHeaderTokens.first, contentType == "application/x-www-form-urlencoded" else {
- return []
- }
- guard let utf8String = String(bytes: body, encoding: .utf8) else {
- // Consider to throw an exception here (examine the encoding from headers).
- return []
- }
- return utf8String.components(separatedBy: "&").map { param -> (String, String) in
- let tokens = param.components(separatedBy: "=")
- if let name = tokens.first?.removingPercentEncoding, let value = tokens.last?.removingPercentEncoding, tokens.count == 2 {
- return (name.replacingOccurrences(of: "+", with: " "),
- value.replacingOccurrences(of: "+", with: " "))
- }
- return ("", "")
- }
- }
- public struct MultiPart {
- public let headers: [String: String]
- public let body: [UInt8]
- public var name: String? {
- return valueFor("content-disposition", parameter: "name")?.unquote()
- }
- public var fileName: String? {
- return valueFor("content-disposition", parameter: "filename")?.unquote()
- }
- private func valueFor(_ headerName: String, parameter: String) -> String? {
- return headers.reduce([String]()) { (combined, header: (key: String, value: String)) -> [String] in
- guard header.key == headerName else {
- return combined
- }
- let headerValueParams = header.value.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) }
- return headerValueParams.reduce(combined, { (results, token) -> [String] in
- let parameterTokens = token.components(separatedBy: "=")
- if parameterTokens.first == parameter, let value = parameterTokens.last {
- return results + [value]
- }
- return results
- })
- }.first
- }
- }
- public func parseMultiPartFormData() -> [MultiPart] {
- guard let contentTypeHeader = headers["content-type"] else {
- return []
- }
- let contentTypeHeaderTokens = contentTypeHeader.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) }
- guard let contentType = contentTypeHeaderTokens.first, contentType == "multipart/form-data" else {
- return []
- }
- var boundary: String?
- contentTypeHeaderTokens.forEach({
- let tokens = $0.components(separatedBy: "=")
- if let key = tokens.first, key == "boundary" && tokens.count == 2 {
- boundary = tokens.last
- }
- })
- if let boundary = boundary, boundary.utf8.count > 0 {
- return parseMultiPartFormData(body, boundary: "--\(boundary)")
- }
- return []
- }
- private func parseMultiPartFormData(_ data: [UInt8], boundary: String) -> [MultiPart] {
- var generator = data.makeIterator()
- var result = [MultiPart]()
- while let part = nextMultiPart(&generator, boundary: boundary, isFirst: result.isEmpty) {
- result.append(part)
- }
- return result
- }
- private func nextMultiPart(_ generator: inout IndexingIterator<[UInt8]>, boundary: String, isFirst: Bool) -> MultiPart? {
- if isFirst {
- guard nextUTF8MultiPartLine(&generator) == boundary else {
- return nil
- }
- } else {
- let /* ignore */ _ = nextUTF8MultiPartLine(&generator)
- }
- var headers = [String: String]()
- while let line = nextUTF8MultiPartLine(&generator), !line.isEmpty {
- let tokens = line.components(separatedBy: ":")
- if let name = tokens.first, let value = tokens.last, tokens.count == 2 {
- headers[name.lowercased()] = value.trimmingCharacters(in: .whitespaces)
- }
- }
- guard let body = nextMultiPartBody(&generator, boundary: boundary) else {
- return nil
- }
- return MultiPart(headers: headers, body: body)
- }
- private func nextUTF8MultiPartLine(_ generator: inout IndexingIterator<[UInt8]>) -> String? {
- var temp = [UInt8]()
- while let value = generator.next() {
- if value > HttpRequest.CR {
- temp.append(value)
- }
- if value == HttpRequest.NL {
- break
- }
- }
- return String(bytes: temp, encoding: String.Encoding.utf8)
- }
- // swiftlint:disable identifier_name
- static let CR = UInt8(13)
- static let NL = UInt8(10)
- private func nextMultiPartBody(_ generator: inout IndexingIterator<[UInt8]>, boundary: String) -> [UInt8]? {
- var body = [UInt8]()
- let boundaryArray = [UInt8](boundary.utf8)
- var matchOffset = 0
- while let x = generator.next() {
- matchOffset = ( x == boundaryArray[matchOffset] ? matchOffset + 1 : 0 )
- body.append(x)
- if matchOffset == boundaryArray.count {
- #if swift(>=4.2)
- body.removeSubrange(body.count-matchOffset ..< body.count)
- #else
- body.removeSubrange(CountableRange<Int>(body.count-matchOffset ..< body.count))
- #endif
- if body.last == HttpRequest.NL {
- body.removeLast()
- if body.last == HttpRequest.CR {
- body.removeLast()
- }
- }
- return body
- }
- }
- return nil
- }
- }
|