HTTPHeaders.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. //
  2. // HTTPHeaders.swift
  3. //
  4. // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. //
  24. import Foundation
  25. /// An order-preserving and case-insensitive representation of HTTP headers.
  26. public struct HTTPHeaders {
  27. private var headers: [HTTPHeader] = []
  28. /// Creates an empty instance.
  29. public init() { }
  30. /// Creates an instance from an array of `HTTPHeader`s. Duplicate case-insensitive names are collapsed into the last
  31. /// name and value encountered.
  32. public init(_ headers: [HTTPHeader]) {
  33. self.init()
  34. headers.forEach { update($0) }
  35. }
  36. /// Creates an instance from a `[String: String]`. Duplicate case-insensitive names are collapsed into the last name
  37. /// and value encountered.
  38. public init(_ dictionary: [String: String]) {
  39. self.init()
  40. dictionary.forEach { update(HTTPHeader(name: $0.key, value: $0.value)) }
  41. }
  42. /// Case-insensitively updates or appends an `HTTPHeader` into the instance using the provided `name` and `value`.
  43. ///
  44. /// - Parameters:
  45. /// - name: The `HTTPHeader` name.
  46. /// - value: The `HTTPHeader value.
  47. public mutating func add(name: String, value: String) {
  48. update(HTTPHeader(name: name, value: value))
  49. }
  50. /// Case-insensitively updates or appends the provided `HTTPHeader` into the instance.
  51. ///
  52. /// - Parameter header: The `HTTPHeader` to update or append.
  53. public mutating func add(_ header: HTTPHeader) {
  54. update(header)
  55. }
  56. /// Case-insensitively updates or appends an `HTTPHeader` into the instance using the provided `name` and `value`.
  57. ///
  58. /// - Parameters:
  59. /// - name: The `HTTPHeader` name.
  60. /// - value: The `HTTPHeader value.
  61. public mutating func update(name: String, value: String) {
  62. update(HTTPHeader(name: name, value: value))
  63. }
  64. /// Case-insensitively updates or appends the provided `HTTPHeader` into the instance.
  65. ///
  66. /// - Parameter header: The `HTTPHeader` to update or append.
  67. public mutating func update(_ header: HTTPHeader) {
  68. guard let index = headers.index(of: header.name) else {
  69. headers.append(header)
  70. return
  71. }
  72. headers.replaceSubrange(index...index, with: [header])
  73. }
  74. /// Case-insensitively removes an `HTTPHeader`, if it exists, from the instance.
  75. ///
  76. /// - Parameter name: The name of the `HTTPHeader` to remove.
  77. public mutating func remove(name: String) {
  78. guard let index = headers.index(of: name) else { return }
  79. headers.remove(at: index)
  80. }
  81. /// Sort the current instance by header name.
  82. mutating public func sort() {
  83. headers.sort { $0.name < $1.name }
  84. }
  85. /// Returns an instance sorted by header name.
  86. ///
  87. /// - Returns: A copy of the current instance sorted by name.
  88. public func sorted() -> HTTPHeaders {
  89. return HTTPHeaders(headers.sorted { $0.name < $1.name })
  90. }
  91. /// Case-insensitively find a header's value by name.
  92. ///
  93. /// - Parameter name: The name of the header to search for, case-insensitively.
  94. ///
  95. /// - Returns: The value of header, if it exists.
  96. public func value(for name: String) -> String? {
  97. guard let index = headers.index(of: name) else { return nil }
  98. return headers[index].value
  99. }
  100. /// Case-insensitively access the header with the given name.
  101. ///
  102. /// - Parameter name: The name of the header.
  103. public subscript(_ name: String) -> String? {
  104. get { return value(for: name) }
  105. set {
  106. if let value = newValue {
  107. update(name: name, value: value)
  108. } else {
  109. remove(name: name)
  110. }
  111. }
  112. }
  113. /// The dictionary representation of all headers.
  114. ///
  115. /// This representation does not preserve the current order of the instance.
  116. public var dictionary: [String: String] {
  117. let namesAndValues = headers.map { ($0.name, $0.value) }
  118. return Dictionary(namesAndValues, uniquingKeysWith: { (_, last) in last })
  119. }
  120. }
  121. extension HTTPHeaders: ExpressibleByDictionaryLiteral {
  122. public init(dictionaryLiteral elements: (String, String)...) {
  123. self.init()
  124. elements.forEach { update(name: $0.0, value: $0.1) }
  125. }
  126. }
  127. extension HTTPHeaders: ExpressibleByArrayLiteral {
  128. public init(arrayLiteral elements: HTTPHeader...) {
  129. self.init(elements)
  130. }
  131. }
  132. extension HTTPHeaders: Sequence {
  133. public func makeIterator() -> IndexingIterator<Array<HTTPHeader>> {
  134. return headers.makeIterator()
  135. }
  136. }
  137. extension HTTPHeaders: Collection {
  138. public var startIndex: Int {
  139. return headers.startIndex
  140. }
  141. public var endIndex: Int {
  142. return headers.endIndex
  143. }
  144. public subscript(position: Int) -> HTTPHeader {
  145. return headers[position]
  146. }
  147. public func index(after i: Int) -> Int {
  148. return headers.index(after: i)
  149. }
  150. }
  151. extension HTTPHeaders: CustomStringConvertible {
  152. public var description: String {
  153. return headers.map { $0.description }
  154. .joined(separator: "\n")
  155. }
  156. }
  157. // MARK: - HTTPHeader
  158. /// A representation of a single HTTP header's name / value pair.
  159. public struct HTTPHeader: Hashable {
  160. /// Name of the header.
  161. public let name: String
  162. /// Value of the header.
  163. public let value: String
  164. /// Creates an instance from the given `name` and `value`.
  165. ///
  166. /// - Parameters:
  167. /// - name: The name of the header.
  168. /// - value: The value of the header.
  169. public init(name: String, value: String) {
  170. self.name = name
  171. self.value = value
  172. }
  173. }
  174. extension HTTPHeader: CustomStringConvertible {
  175. public var description: String {
  176. return "\(name): \(value)"
  177. }
  178. }
  179. extension HTTPHeader {
  180. /// Returns an `Accept-Charset` header.
  181. ///
  182. /// - Parameter value: The `Accept-Charset` value.
  183. /// - Returns: The header.
  184. public static func acceptCharset(_ value: String) -> HTTPHeader {
  185. return HTTPHeader(name: "Accept-Charset", value: value)
  186. }
  187. /// Returns an `Accept-Language` header.
  188. ///
  189. /// Alamofire offers a default Accept-Language header that accumulates and encodes the system's preferred languages.
  190. /// Use `HTTPHeader.defaultAcceptLanguage`.
  191. ///
  192. /// - Parameter value: The `Accept-Language` value.
  193. ///
  194. /// - Returns: The header.
  195. public static func acceptLanguage(_ value: String) -> HTTPHeader {
  196. return HTTPHeader(name: "Accept-Language", value: value)
  197. }
  198. /// Returns an `Accept-Encoding` header.
  199. ///
  200. /// Alamofire offers a default accept encoding value that provides the most common values. Use
  201. /// `HTTPHeader.defaultAcceptEncoding`.
  202. ///
  203. /// - Parameter value: The `Accept-Encoding` value.
  204. ///
  205. /// - Returns: The header
  206. public static func acceptEncoding(_ value: String) -> HTTPHeader {
  207. return HTTPHeader(name: "Accept-Encoding", value: value)
  208. }
  209. /// Returns a `Basic` `Authorization` header using the `username` and `password` provided.
  210. ///
  211. /// - Parameters:
  212. /// - username: The username of the header.
  213. /// - password: The password of the header.
  214. ///
  215. /// - Returns: The header.
  216. public static func authorization(username: String, password: String) -> HTTPHeader {
  217. let credential = Data("\(username):\(password)".utf8).base64EncodedString()
  218. return authorization("Basic \(credential)")
  219. }
  220. /// Returns a `Bearer` `Authorization` header using the `bearerToken` provided
  221. ///
  222. /// - Parameter bearerToken: The bearer token.
  223. ///
  224. /// - Returns: The header.
  225. public static func authorization(bearerToken: String) -> HTTPHeader {
  226. return authorization("Bearer \(bearerToken)")
  227. }
  228. /// Returns an `Authorization` header.
  229. ///
  230. /// Alamofire provides built-in methods to produce `Authorization` headers. For a Basic `Authorization` header use
  231. /// `HTTPHeader.authorization(username: password:)`. For a Bearer `Authorization` header, use
  232. /// `HTTPHeader.authorization(bearerToken:)`.
  233. ///
  234. /// - Parameter value: The `Authorization` value.
  235. ///
  236. /// - Returns: The header.
  237. public static func authorization(_ value: String) -> HTTPHeader {
  238. return HTTPHeader(name: "Authorization", value: value)
  239. }
  240. /// Returns a `Content-Disposition` header.
  241. ///
  242. /// - Parameter value: The `Content-Disposition` value.
  243. ///
  244. /// - Returns: The header.
  245. public static func contentDisposition(_ value: String) -> HTTPHeader {
  246. return HTTPHeader(name: "Content-Disposition", value: value)
  247. }
  248. /// Returns a `Content-Type` header.
  249. ///
  250. /// All Alamofire `ParameterEncoding`s set the `Content-Type` of the request, so it may not be necessary to manually
  251. /// set this value.
  252. ///
  253. /// - Parameter value: The `Content-Type` value.
  254. ///
  255. /// - Returns: The header.
  256. public static func contentType(_ value: String) -> HTTPHeader {
  257. return HTTPHeader(name: "Content-Type", value: value)
  258. }
  259. /// Returns a `User-Agent` header.
  260. ///
  261. /// - Parameter value: The `User-Agent` value.
  262. ///
  263. /// - Returns: The header.
  264. public static func userAgent(_ value: String) -> HTTPHeader {
  265. return HTTPHeader(name: "User-Agent", value: value)
  266. }
  267. }
  268. extension Array where Element == HTTPHeader {
  269. /// Case-insensitively finds the index of an `HTTPHeader` with the provided name, if it exists.
  270. func index(of name: String) -> Int? {
  271. let lowercasedName = name.lowercased()
  272. return firstIndex { $0.name.lowercased() == lowercasedName }
  273. }
  274. }
  275. // MARK: - Defaults
  276. public extension HTTPHeaders {
  277. /// The default set of `HTTPHeaders` used by Alamofire. Includes `Accept-Encoding`, `Accept-Language`, and
  278. /// `User-Agent`.
  279. static let `default`: HTTPHeaders = [.defaultAcceptEncoding,
  280. .defaultAcceptLanguage,
  281. .defaultUserAgent]
  282. }
  283. extension HTTPHeader {
  284. /// Returns Alamofire's default `Accept-Encoding` header, appropriate for the encodings supporte by particular OS
  285. /// versions.
  286. ///
  287. /// See the [Accept-Encoding HTTP header documentation](https://tools.ietf.org/html/rfc7230#section-4.2.3) .
  288. public static let defaultAcceptEncoding: HTTPHeader = {
  289. let encodings: [String]
  290. if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *) {
  291. encodings = ["br", "gzip", "deflate"]
  292. } else {
  293. encodings = ["gzip", "deflate"]
  294. }
  295. return .acceptEncoding(encodings.qualityEncoded())
  296. }()
  297. /// Returns Alamofire's default `Accept-Language` header, generated by querying `Locale` for the user's
  298. /// `preferredLanguages`.
  299. ///
  300. /// See the [Accept-Language HTTP header documentation](https://tools.ietf.org/html/rfc7231#section-5.3.5).
  301. public static let defaultAcceptLanguage: HTTPHeader = {
  302. .acceptLanguage(Locale.preferredLanguages.prefix(6).qualityEncoded())
  303. }()
  304. /// Returns Alamofire's default `User-Agent` header.
  305. ///
  306. /// See the [User-Agent header documentation](https://tools.ietf.org/html/rfc7231#section-5.5.3).
  307. ///
  308. /// Example: `iOS Example/1.0 (org.alamofire.iOS-Example; build:1; iOS 12.0.0) Alamofire/5.0.0`
  309. public static let defaultUserAgent: HTTPHeader = {
  310. let userAgent: String = {
  311. if let info = Bundle.main.infoDictionary {
  312. let executable = info[kCFBundleExecutableKey as String] as? String ?? "Unknown"
  313. let bundle = info[kCFBundleIdentifierKey as String] as? String ?? "Unknown"
  314. let appVersion = info["CFBundleShortVersionString"] as? String ?? "Unknown"
  315. let appBuild = info[kCFBundleVersionKey as String] as? String ?? "Unknown"
  316. let osNameVersion: String = {
  317. let version = ProcessInfo.processInfo.operatingSystemVersion
  318. let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
  319. let osName: String = {
  320. #if os(iOS)
  321. return "iOS"
  322. #elseif os(watchOS)
  323. return "watchOS"
  324. #elseif os(tvOS)
  325. return "tvOS"
  326. #elseif os(macOS)
  327. return "macOS"
  328. #elseif os(Linux)
  329. return "Linux"
  330. #else
  331. return "Unknown"
  332. #endif
  333. }()
  334. return "\(osName) \(versionString)"
  335. }()
  336. let alamofireVersion: String = {
  337. guard
  338. let afInfo = Bundle(for: Session.self).infoDictionary,
  339. let build = afInfo["CFBundleShortVersionString"]
  340. else { return "Unknown" }
  341. return "Alamofire/\(build)"
  342. }()
  343. return "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)"
  344. }
  345. return "Alamofire"
  346. }()
  347. return .userAgent(userAgent)
  348. }()
  349. }
  350. extension Collection where Element == String {
  351. func qualityEncoded() -> String {
  352. return enumerated().map { (index, encoding) in
  353. let quality = 1.0 - (Double(index) * 0.1)
  354. return "\(encoding);q=\(quality)"
  355. }.joined(separator: ", ")
  356. }
  357. }
  358. // MARK: - System Type Extensions
  359. extension URLRequest {
  360. /// Returns `allHTTPHeaderFields` as `HTTPHeaders`.
  361. public var headers: HTTPHeaders {
  362. get { return allHTTPHeaderFields.map(HTTPHeaders.init) ?? HTTPHeaders() }
  363. set { allHTTPHeaderFields = newValue.dictionary }
  364. }
  365. }
  366. extension HTTPURLResponse {
  367. /// Returns `allHeaderFields` as `HTTPHeaders`.
  368. public var headers: HTTPHeaders {
  369. return (allHeaderFields as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders()
  370. }
  371. }
  372. public extension URLSessionConfiguration {
  373. /// Returns `httpAdditionalHeaders` as `HTTPHeaders`.
  374. var headers: HTTPHeaders {
  375. get { return (httpAdditionalHeaders as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders() }
  376. set { httpAdditionalHeaders = newValue.dictionary }
  377. }
  378. }