Http+Misc.swift 6.9 KB

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