Files
async-http-client/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift
T
Fabian Fett e5022468bb Update swiftformat to 0.48.8 (#491)
### Motivation

Our current swiftformat version does not support async/await. Since we want to add support for async/await we must update swiftformat or disable it. I tried my very best to keep the number of changes as small as possible. I assume we want to stick with the new 0.48.8 for some time.

### Changes

- Update swiftformat to 0.48.8

### Result

We can land async/await code.
2021-11-25 10:15:36 +01:00

175 lines
6.3 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the AsyncHTTPClient open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Foundation
import NIOHTTP1
extension HTTPClient {
/// A representation of an HTTP cookie.
public struct Cookie {
/// The name of the cookie.
public var name: String
/// The cookie's string value.
public var value: String
/// The cookie's path.
public var path: String
/// The domain of the cookie.
public var domain: String?
/// The cookie's expiration date.
public var expires: Date?
/// The cookie's age in seconds.
public var maxAge: Int?
/// Whether the cookie should only be sent to HTTP servers.
public var httpOnly: Bool
/// Whether the cookie should only be sent over secure channels.
public var secure: Bool
/// Create a Cookie by parsing a `Set-Cookie` header.
///
/// - parameters:
/// - header: String representation of the `Set-Cookie` response header.
/// - defaultDomain: Default domain to use if cookie was sent without one.
/// - returns: nil if the header is invalid.
public init?(header: String, defaultDomain: String) {
let components = header.components(separatedBy: ";").map {
$0.trimmingCharacters(in: .whitespaces)
}
if components.isEmpty {
return nil
}
let nameAndValue = components[0].split(separator: "=", maxSplits: 1, omittingEmptySubsequences: false).map {
$0.trimmingCharacters(in: .whitespaces)
}
guard nameAndValue.count == 2 else {
return nil
}
self.name = nameAndValue[0]
self.value = nameAndValue[1].omittingQuotes()
guard !self.name.isEmpty else {
return nil
}
self.path = "/"
self.domain = defaultDomain
self.expires = nil
self.maxAge = nil
self.httpOnly = false
self.secure = false
for component in components[1...] {
switch self.parseComponent(component) {
case (nil, nil):
continue
case ("path", .some(let value)):
self.path = value
case ("domain", .some(let value)):
self.domain = value
case ("expires", let value):
guard let value = value else {
continue
}
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.timeZone = TimeZone(identifier: "GMT")
formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss z"
if let date = formatter.date(from: value) {
self.expires = date
continue
}
formatter.dateFormat = "EEE, dd-MMM-yy HH:mm:ss z"
if let date = formatter.date(from: value) {
self.expires = date
continue
}
formatter.dateFormat = "EEE MMM d hh:mm:s yyyy"
if let date = formatter.date(from: value) {
self.expires = date
}
case ("max-age", let value):
self.maxAge = value.flatMap(Int.init)
case ("secure", nil):
self.secure = true
case ("httponly", nil):
self.httpOnly = true
default:
continue
}
}
}
/// Create HTTP cookie.
///
/// - parameters:
/// - name: The name of the cookie.
/// - value: The cookie's string value.
/// - path: The cookie's path.
/// - domain: The domain of the cookie, defaults to nil.
/// - expires: The cookie's expiration date, defaults to nil.
/// - maxAge: The cookie's age in seconds, defaults to nil.
/// - httpOnly: Whether this cookie should be used by HTTP servers only, defaults to false.
/// - secure: Whether this cookie should only be sent using secure channels, defaults to false.
public init(name: String, value: String, path: String = "/", domain: String? = nil, expires: Date? = nil, maxAge: Int? = nil, httpOnly: Bool = false, secure: Bool = false) {
self.name = name
self.value = value
self.path = path
self.domain = domain
self.expires = expires
self.maxAge = maxAge
self.httpOnly = httpOnly
self.secure = secure
}
func parseComponent(_ component: String) -> (String?, String?) {
let nameAndValue = component.split(separator: "=", maxSplits: 1).map {
$0.trimmingCharacters(in: .whitespaces)
}
if nameAndValue.count == 2 {
return (nameAndValue[0].lowercased(), nameAndValue[1])
} else if nameAndValue.count == 1 {
return (nameAndValue[0].lowercased(), nil)
}
return (nil, nil)
}
}
}
extension String {
fileprivate func omittingQuotes() -> String {
let dquote = "\""
if !hasPrefix(dquote) || !hasSuffix(dquote) {
return self
}
let begin = index(after: startIndex)
let end = index(before: endIndex)
return String(self[begin..<end])
}
}
extension HTTPClient.Response {
/// List of HTTP cookies returned by the server.
public var cookies: [HTTPClient.Cookie] {
return self.headers["set-cookie"].compactMap { HTTPClient.Cookie(header: $0, defaultDomain: self.host) }
}
}