Http+Misc.swift 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. //
  2. // HttpPost.swift
  3. // Swifter
  4. //
  5. // Created by Damian Kolakowski on 24/02/2017.
  6. // Copyright © 2017 Damian Kołakowski. All rights reserved.
  7. //
  8. import Foundation
  9. extension Request {
  10. public func hasToken(_ token: String, forHeader headerName: String) -> Bool {
  11. guard let (_, value) = headers.filter({ $0.0 == headerName }).first else {
  12. return false
  13. }
  14. return value
  15. .components(separatedBy: ",")
  16. .filter({ $0.trimmingCharacters(in: .whitespaces).lowercased() == token })
  17. .count > 0
  18. }
  19. }
  20. extension Request {
  21. public func parseUrlencodedForm() -> [(String, String)] {
  22. guard let (_, contentTypeHeader) = headers.filter({ $0.0 == "content-type"}).last else {
  23. return []
  24. }
  25. let contentTypeHeaderTokens = contentTypeHeader.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) }
  26. guard let contentType = contentTypeHeaderTokens.first, contentType == "application/x-www-form-urlencoded" else {
  27. return []
  28. }
  29. guard let utf8String = String(bytes: body, encoding: .utf8) else {
  30. // Consider to throw an exception here (examine the encoding from headers).
  31. return []
  32. }
  33. return utf8String.components(separatedBy: "&").map { param -> (String, String) in
  34. let tokens = param.components(separatedBy: "=")
  35. if let name = tokens.first?.removingPercentEncoding, let value = tokens.last?.removingPercentEncoding, tokens.count == 2 {
  36. return (name.replacingOccurrences(of: "+", with: " "),
  37. value.replacingOccurrences(of: "+", with: " "))
  38. }
  39. return ("","")
  40. }
  41. }
  42. }
  43. extension String {
  44. public func unquote() -> String {
  45. var scalars = self.unicodeScalars;
  46. if scalars.first == "\"" && scalars.last == "\"" && scalars.count >= 2 {
  47. scalars.removeFirst();
  48. scalars.removeLast();
  49. return String(scalars)
  50. }
  51. return self
  52. }
  53. }
  54. extension UnicodeScalar {
  55. public func asWhitespace() -> UInt8? {
  56. if self.value >= 9 && self.value <= 13 {
  57. return UInt8(self.value)
  58. }
  59. if self.value == 32 {
  60. return UInt8(self.value)
  61. }
  62. return nil
  63. }
  64. }
  65. extension Request {
  66. public struct MultiPart {
  67. public let headers: [String: String]
  68. public let body: [UInt8]
  69. public var name: String? {
  70. return valueFor("content-disposition", parameter: "name")?.unquote()
  71. }
  72. public var fileName: String? {
  73. return valueFor("content-disposition", parameter: "filename")?.unquote()
  74. }
  75. private func valueFor(_ headerName: String, parameter: String) -> String? {
  76. return headers.reduce([String]()) { (combined, header: (key: String, value: String)) -> [String] in
  77. guard header.key == headerName else {
  78. return combined
  79. }
  80. let headerValueParams = header.value.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) }
  81. return headerValueParams.reduce(combined, { (results, token) -> [String] in
  82. let parameterTokens = token.components(separatedBy: "=")
  83. if parameterTokens.first == parameter, let value = parameterTokens.last {
  84. return results + [value]
  85. }
  86. return results
  87. })
  88. }.first
  89. }
  90. }
  91. public func parseMultiPartFormData() -> [MultiPart] {
  92. guard let (_, contentTypeHeader) = headers.filter({ $0.0 == "content-type"}).last else {
  93. return []
  94. }
  95. let contentTypeHeaderTokens = contentTypeHeader.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) }
  96. guard let contentType = contentTypeHeaderTokens.first, contentType == "multipart/form-data" else {
  97. return []
  98. }
  99. var boundary: String? = nil
  100. contentTypeHeaderTokens.forEach({
  101. let tokens = $0.components(separatedBy: "=")
  102. if let key = tokens.first, key == "boundary" && tokens.count == 2 {
  103. boundary = tokens.last
  104. }
  105. })
  106. if let boundary = boundary, boundary.utf8.count > 0 {
  107. return parseMultiPartFormData(body, boundary: "--\(boundary)")
  108. }
  109. return []
  110. }
  111. private func parseMultiPartFormData(_ data: [UInt8], boundary: String) -> [MultiPart] {
  112. var generator = data.makeIterator()
  113. var result = [MultiPart]()
  114. while let part = nextMultiPart(&generator, boundary: boundary, isFirst: result.isEmpty) {
  115. result.append(part)
  116. }
  117. return result
  118. }
  119. private func nextMultiPart(_ generator: inout IndexingIterator<[UInt8]>, boundary: String, isFirst: Bool) -> MultiPart? {
  120. if isFirst {
  121. guard nextUTF8MultiPartLine(&generator) == boundary else {
  122. return nil
  123. }
  124. } else {
  125. let /* ignore */ _ = nextUTF8MultiPartLine(&generator)
  126. }
  127. var headers = [String: String]()
  128. while let line = nextUTF8MultiPartLine(&generator), !line.isEmpty {
  129. let tokens = line.components(separatedBy: ":")
  130. if let name = tokens.first, let value = tokens.last, tokens.count == 2 {
  131. headers[name.lowercased()] = value.trimmingCharacters(in: .whitespaces)
  132. }
  133. }
  134. guard let body = nextMultiPartBody(&generator, boundary: boundary) else {
  135. return nil
  136. }
  137. return MultiPart(headers: headers, body: body)
  138. }
  139. private func nextUTF8MultiPartLine(_ generator: inout IndexingIterator<[UInt8]>) -> String? {
  140. var temp = [UInt8]()
  141. while let value = generator.next() {
  142. if value > UInt8.cr {
  143. temp.append(value)
  144. }
  145. if value == UInt8.lf {
  146. break
  147. }
  148. }
  149. return String(bytes: temp, encoding: String.Encoding.utf8)
  150. }
  151. static let CR = UInt8(13)
  152. static let NL = UInt8(10)
  153. private func nextMultiPartBody(_ generator: inout IndexingIterator<[UInt8]>, boundary: String) -> [UInt8]? {
  154. var body = [UInt8]()
  155. let boundaryArray = [UInt8](boundary.utf8)
  156. var matchOffset = 0;
  157. while let x = generator.next() {
  158. matchOffset = ( x == boundaryArray[matchOffset] ? matchOffset + 1 : 0 )
  159. body.append(x)
  160. if matchOffset == boundaryArray.count {
  161. body.removeSubrange(CountableRange<Int>(body.count-matchOffset ..< body.count))
  162. if body.last == UInt8.lf {
  163. body.removeLast()
  164. if body.last == UInt8.cr {
  165. body.removeLast()
  166. }
  167. }
  168. return body
  169. }
  170. }
  171. return nil
  172. }
  173. }