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