HTTPHeaders.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  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, case insensitively.
  82. public mutating func sort() {
  83. headers.sort { $0.name.lowercased() < $1.name.lowercased() }
  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. var headers = self
  90. headers.sort()
  91. return headers
  92. }
  93. /// Case-insensitively find a header's value by name.
  94. ///
  95. /// - Parameter name: The name of the header to search for, case-insensitively.
  96. ///
  97. /// - Returns: The value of header, if it exists.
  98. public func value(for name: String) -> String? {
  99. guard let index = headers.index(of: name) else { return nil }
  100. return headers[index].value
  101. }
  102. /// Case-insensitively access the header with the given name.
  103. ///
  104. /// - Parameter name: The name of the header.
  105. public subscript(_ name: String) -> String? {
  106. get { value(for: name) }
  107. set {
  108. if let value = newValue {
  109. update(name: name, value: value)
  110. } else {
  111. remove(name: name)
  112. }
  113. }
  114. }
  115. /// The dictionary representation of all headers.
  116. ///
  117. /// This representation does not preserve the current order of the instance.
  118. public var dictionary: [String: String] {
  119. let namesAndValues = headers.map { ($0.name, $0.value) }
  120. return Dictionary(namesAndValues, uniquingKeysWith: { _, last in last })
  121. }
  122. }
  123. extension HTTPHeaders: ExpressibleByDictionaryLiteral {
  124. public init(dictionaryLiteral elements: (String, String)...) {
  125. self.init()
  126. elements.forEach { update(name: $0.0, value: $0.1) }
  127. }
  128. }
  129. extension HTTPHeaders: ExpressibleByArrayLiteral {
  130. public init(arrayLiteral elements: HTTPHeader...) {
  131. self.init(elements)
  132. }
  133. }
  134. extension HTTPHeaders: Sequence {
  135. public func makeIterator() -> IndexingIterator<[HTTPHeader]> {
  136. headers.makeIterator()
  137. }
  138. }
  139. extension HTTPHeaders: Collection {
  140. public var startIndex: Int {
  141. headers.startIndex
  142. }
  143. public var endIndex: Int {
  144. headers.endIndex
  145. }
  146. public subscript(position: Int) -> HTTPHeader {
  147. headers[position]
  148. }
  149. public func index(after i: Int) -> Int {
  150. headers.index(after: i)
  151. }
  152. }
  153. extension HTTPHeaders: CustomStringConvertible {
  154. public var description: String {
  155. headers.map(\.description)
  156. .joined(separator: "\n")
  157. }
  158. }
  159. // MARK: - HTTPHeader
  160. /// A representation of a single HTTP header's name / value pair.
  161. public struct HTTPHeader: Hashable {
  162. /// Name of the header.
  163. public let name: String
  164. /// Value of the header.
  165. public let value: String
  166. /// Creates an instance from the given `name` and `value`.
  167. ///
  168. /// - Parameters:
  169. /// - name: The name of the header.
  170. /// - value: The value of the header.
  171. public init(name: String, value: String) {
  172. self.name = name
  173. self.value = value
  174. }
  175. }
  176. extension HTTPHeader: CustomStringConvertible {
  177. public var description: String {
  178. "\(name): \(value)"
  179. }
  180. }
  181. extension HTTPHeader {
  182. /// Returns an `Accept` header.
  183. ///
  184. /// - Parameter value: The `Accept` value.
  185. /// - Returns: The header.
  186. public static func accept(_ value: String) -> HTTPHeader {
  187. HTTPHeader(name: "Accept", value: value)
  188. }
  189. /// Returns an `Accept-Charset` header.
  190. ///
  191. /// - Parameter value: The `Accept-Charset` value.
  192. /// - Returns: The header.
  193. public static func acceptCharset(_ value: String) -> HTTPHeader {
  194. HTTPHeader(name: "Accept-Charset", value: value)
  195. }
  196. /// Returns an `Accept-Language` header.
  197. ///
  198. /// Alamofire offers a default Accept-Language header that accumulates and encodes the system's preferred languages.
  199. /// Use `HTTPHeader.defaultAcceptLanguage`.
  200. ///
  201. /// - Parameter value: The `Accept-Language` value.
  202. ///
  203. /// - Returns: The header.
  204. public static func acceptLanguage(_ value: String) -> HTTPHeader {
  205. HTTPHeader(name: "Accept-Language", value: value)
  206. }
  207. /// Returns an `Accept-Encoding` header.
  208. ///
  209. /// Alamofire offers a default accept encoding value that provides the most common values. Use
  210. /// `HTTPHeader.defaultAcceptEncoding`.
  211. ///
  212. /// - Parameter value: The `Accept-Encoding` value.
  213. ///
  214. /// - Returns: The header
  215. public static func acceptEncoding(_ value: String) -> HTTPHeader {
  216. HTTPHeader(name: "Accept-Encoding", value: value)
  217. }
  218. /// Returns a `Basic` `Authorization` header using the `username` and `password` provided.
  219. ///
  220. /// - Parameters:
  221. /// - username: The username of the header.
  222. /// - password: The password of the header.
  223. ///
  224. /// - Returns: The header.
  225. public static func authorization(username: String, password: String) -> HTTPHeader {
  226. let credential = Data("\(username):\(password)".utf8).base64EncodedString()
  227. return authorization("Basic \(credential)")
  228. }
  229. /// Returns a `Bearer` `Authorization` header using the `bearerToken` provided
  230. ///
  231. /// - Parameter bearerToken: The bearer token.
  232. ///
  233. /// - Returns: The header.
  234. public static func authorization(bearerToken: String) -> HTTPHeader {
  235. authorization("Bearer \(bearerToken)")
  236. }
  237. /// Returns an `Authorization` header.
  238. ///
  239. /// Alamofire provides built-in methods to produce `Authorization` headers. For a Basic `Authorization` header use
  240. /// `HTTPHeader.authorization(username:password:)`. For a Bearer `Authorization` header, use
  241. /// `HTTPHeader.authorization(bearerToken:)`.
  242. ///
  243. /// - Parameter value: The `Authorization` value.
  244. ///
  245. /// - Returns: The header.
  246. public static func authorization(_ value: String) -> HTTPHeader {
  247. HTTPHeader(name: "Authorization", value: value)
  248. }
  249. /// Returns a `Content-Disposition` header.
  250. ///
  251. /// - Parameter value: The `Content-Disposition` value.
  252. ///
  253. /// - Returns: The header.
  254. public static func contentDisposition(_ value: String) -> HTTPHeader {
  255. HTTPHeader(name: "Content-Disposition", value: value)
  256. }
  257. /// Returns a `Content-Encoding` header.
  258. ///
  259. /// - Parameter value: The `Content-Encoding`.
  260. ///
  261. /// - Returns: The header.
  262. public static func contentEncoding(_ value: String) -> HTTPHeader {
  263. HTTPHeader(name: "Content-Encoding", value: value)
  264. }
  265. /// Returns a `Content-Type` header.
  266. ///
  267. /// All Alamofire `ParameterEncoding`s and `ParameterEncoder`s set the `Content-Type` of the request, so it may not
  268. /// be necessary to manually set this value.
  269. ///
  270. /// - Parameter value: The `Content-Type` value.
  271. ///
  272. /// - Returns: The header.
  273. public static func contentType(_ value: String) -> HTTPHeader {
  274. HTTPHeader(name: "Content-Type", value: value)
  275. }
  276. /// Returns a `User-Agent` header.
  277. ///
  278. /// - Parameter value: The `User-Agent` value.
  279. ///
  280. /// - Returns: The header.
  281. public static func userAgent(_ value: String) -> HTTPHeader {
  282. HTTPHeader(name: "User-Agent", value: value)
  283. }
  284. }
  285. extension Array where Element == HTTPHeader {
  286. /// Case-insensitively finds the index of an `HTTPHeader` with the provided name, if it exists.
  287. func index(of name: String) -> Int? {
  288. let lowercasedName = name.lowercased()
  289. return firstIndex { $0.name.lowercased() == lowercasedName }
  290. }
  291. }
  292. // MARK: - Defaults
  293. extension HTTPHeaders {
  294. /// The default set of `HTTPHeaders` used by Alamofire. Includes `Accept-Encoding`, `Accept-Language`, and
  295. /// `User-Agent`.
  296. public static let `default`: HTTPHeaders = [.defaultAcceptEncoding,
  297. .defaultAcceptLanguage,
  298. .defaultUserAgent]
  299. }
  300. extension HTTPHeader {
  301. /// Returns Alamofire's default `Accept-Encoding` header, appropriate for the encodings supported by particular OS
  302. /// versions.
  303. ///
  304. /// See the [Accept-Encoding HTTP header documentation](https://tools.ietf.org/html/rfc7230#section-4.2.3) .
  305. public static let defaultAcceptEncoding: HTTPHeader = {
  306. let encodings: [String]
  307. if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *) {
  308. encodings = ["br", "gzip", "deflate"]
  309. } else {
  310. encodings = ["gzip", "deflate"]
  311. }
  312. return .acceptEncoding(encodings.qualityEncoded())
  313. }()
  314. /// Returns Alamofire's default `Accept-Language` header, generated by querying `Locale` for the user's
  315. /// `preferredLanguages`.
  316. ///
  317. /// See the [Accept-Language HTTP header documentation](https://tools.ietf.org/html/rfc7231#section-5.3.5).
  318. public static let defaultAcceptLanguage: HTTPHeader = .acceptLanguage(Locale.preferredLanguages.prefix(6).qualityEncoded())
  319. /// Returns Alamofire's default `User-Agent` header.
  320. ///
  321. /// See the [User-Agent header documentation](https://tools.ietf.org/html/rfc7231#section-5.5.3).
  322. ///
  323. /// Example: `iOS Example/1.0 (org.alamofire.iOS-Example; build:1; iOS 13.0.0) Alamofire/5.0.0`
  324. public static let defaultUserAgent: HTTPHeader = {
  325. let info = Bundle.main.infoDictionary
  326. let executable = (info?["CFBundleExecutable"] as? String) ??
  327. (ProcessInfo.processInfo.arguments.first?.split(separator: "/").last.map(String.init)) ??
  328. "Unknown"
  329. let bundle = info?["CFBundleIdentifier"] as? String ?? "Unknown"
  330. let appVersion = info?["CFBundleShortVersionString"] as? String ?? "Unknown"
  331. let appBuild = info?["CFBundleVersion"] as? String ?? "Unknown"
  332. let osNameVersion: String = {
  333. let version = ProcessInfo.processInfo.operatingSystemVersion
  334. let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
  335. let osName: String = {
  336. #if os(iOS)
  337. #if targetEnvironment(macCatalyst)
  338. return "macOS(Catalyst)"
  339. #else
  340. return "iOS"
  341. #endif
  342. #elseif os(watchOS)
  343. return "watchOS"
  344. #elseif os(tvOS)
  345. return "tvOS"
  346. #elseif os(macOS)
  347. return "macOS"
  348. #elseif os(Linux)
  349. return "Linux"
  350. #elseif os(Windows)
  351. return "Windows"
  352. #else
  353. return "Unknown"
  354. #endif
  355. }()
  356. return "\(osName) \(versionString)"
  357. }()
  358. let alamofireVersion = "Alamofire/\(version)"
  359. let userAgent = "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)"
  360. return .userAgent(userAgent)
  361. }()
  362. }
  363. extension Collection where Element == String {
  364. func qualityEncoded() -> String {
  365. enumerated().map { index, encoding in
  366. let quality = 1.0 - (Double(index) * 0.1)
  367. return "\(encoding);q=\(quality)"
  368. }.joined(separator: ", ")
  369. }
  370. }
  371. // MARK: - System Type Extensions
  372. extension URLRequest {
  373. /// Returns `allHTTPHeaderFields` as `HTTPHeaders`.
  374. public var headers: HTTPHeaders {
  375. get { allHTTPHeaderFields.map(HTTPHeaders.init) ?? HTTPHeaders() }
  376. set { allHTTPHeaderFields = newValue.dictionary }
  377. }
  378. }
  379. extension HTTPURLResponse {
  380. /// Returns `allHeaderFields` as `HTTPHeaders`.
  381. public var headers: HTTPHeaders {
  382. (allHeaderFields as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders()
  383. }
  384. }
  385. extension URLSessionConfiguration {
  386. /// Returns `httpAdditionalHeaders` as `HTTPHeaders`.
  387. public var headers: HTTPHeaders {
  388. get { (httpAdditionalHeaders as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders() }
  389. set { httpAdditionalHeaders = newValue.dictionary }
  390. }
  391. }