Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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
}