mirror of
https://github.com/swift-server/async-http-client.git
synced 2026-05-03 07:32:29 +00:00
c5784ca815
Replaces all the foundation imports. One issue is that `HTTPClient.init?(httpsURLWithSocketPath socketPath: String, uri: String = "/")` uses `addingPercentEncoding()` from Foundation. So instead, we use a pure Swift impl. that does the same. We also need to disable default traits from `swift-configuration` to prevent linking Foundation, because the `JSON` trait does that. This also adds a linkage test to prevent regressions to CI.
147 lines
4.8 KiB
Swift
147 lines
4.8 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the AsyncHTTPClient open source project
|
|
//
|
|
// Copyright (c) 2021 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 NIOHTTP1
|
|
|
|
#if canImport(FoundationEssentials)
|
|
import struct FoundationEssentials.URL
|
|
#else
|
|
import struct Foundation.URL
|
|
#endif
|
|
|
|
typealias RedirectMode = HTTPClient.Configuration.RedirectConfiguration.Mode
|
|
|
|
struct RedirectState {
|
|
var config: HTTPClient.Configuration.RedirectConfiguration.FollowConfiguration
|
|
|
|
/// All visited URLs.
|
|
private var visited: [String]
|
|
}
|
|
|
|
extension RedirectState {
|
|
/// Creates a `RedirectState` from a configuration.
|
|
/// Returns nil if the user disallowed redirects,
|
|
/// otherwise an instance of `RedirectState` which respects the user defined settings.
|
|
init?(
|
|
_ configuration: RedirectMode,
|
|
initialURL: String
|
|
) {
|
|
switch configuration {
|
|
case .disallow:
|
|
return nil
|
|
case .follow(let config):
|
|
self.init(config: config, visited: [initialURL])
|
|
}
|
|
}
|
|
}
|
|
|
|
extension RedirectState {
|
|
/// Call this method when you are about to do a redirect to the given `redirectURL`.
|
|
/// This method records that URL into `self`.
|
|
/// - Parameter redirectURL: the new URL to redirect the request to
|
|
/// - Throws: if it reaches the redirect limit or detects a redirect cycle if and `allowCycles` is false
|
|
mutating func redirect(to redirectURL: String) throws {
|
|
guard self.visited.count <= config.max else {
|
|
throw HTTPClientError.redirectLimitReached
|
|
}
|
|
|
|
guard config.allowCycles || !self.visited.contains(redirectURL) else {
|
|
throw HTTPClientError.redirectCycleDetected
|
|
}
|
|
self.visited.append(redirectURL)
|
|
}
|
|
}
|
|
|
|
extension HTTPHeaders {
|
|
/// Tries to extract a redirect URL from the `location` header if the `status` indicates it should do so.
|
|
/// It also validates that we can redirect to the scheme of the extracted redirect URL from the `originalScheme`.
|
|
/// - Parameters:
|
|
/// - status: response status of the request
|
|
/// - originalURL: url of the previous request
|
|
/// - originalScheme: scheme of the previous request
|
|
/// - Returns: redirect URL to follow
|
|
func extractRedirectTarget(
|
|
status: HTTPResponseStatus,
|
|
originalURL: URL,
|
|
originalScheme: Scheme
|
|
) -> URL? {
|
|
switch status {
|
|
case .movedPermanently, .found, .seeOther, .notModified, .useProxy, .temporaryRedirect, .permanentRedirect:
|
|
break
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
guard let location = self.first(name: "Location") else {
|
|
return nil
|
|
}
|
|
|
|
guard let url = URL(string: location, relativeTo: originalURL) else {
|
|
return nil
|
|
}
|
|
|
|
guard originalScheme.supportsRedirects(to: url.scheme) else {
|
|
return nil
|
|
}
|
|
|
|
if url.isFileURL {
|
|
return nil
|
|
}
|
|
|
|
return url.absoluteURL
|
|
}
|
|
}
|
|
|
|
/// Transforms the original `requestMethod`, `requestHeaders` and `requestBody` to be ready to be send out as a new request to the `redirectURL`.
|
|
/// - Returns: New `HTTPMethod`, `HTTPHeaders` and `Body` to be send as a new request to `redirectURL`
|
|
func transformRequestForRedirect<Body>(
|
|
from originalURL: URL,
|
|
method requestMethod: HTTPMethod,
|
|
headers requestHeaders: HTTPHeaders,
|
|
body requestBody: Body?,
|
|
to redirectURL: URL,
|
|
status responseStatus: HTTPResponseStatus,
|
|
config: HTTPClient.Configuration.RedirectConfiguration.FollowConfiguration
|
|
) -> (HTTPMethod, HTTPHeaders, Body?) {
|
|
let convertToGet: Bool
|
|
if responseStatus == .seeOther, requestMethod != .HEAD {
|
|
convertToGet = true
|
|
} else if responseStatus == .movedPermanently, requestMethod == .POST {
|
|
convertToGet = !config.retainHTTPMethodAndBodyOn301
|
|
} else if responseStatus == .found, requestMethod == .POST {
|
|
convertToGet = !config.retainHTTPMethodAndBodyOn302
|
|
} else {
|
|
convertToGet = false
|
|
}
|
|
|
|
var method = requestMethod
|
|
var headers = requestHeaders
|
|
var body = requestBody
|
|
|
|
if convertToGet {
|
|
method = .GET
|
|
body = nil
|
|
headers.remove(name: "Content-Length")
|
|
headers.remove(name: "Content-Type")
|
|
}
|
|
|
|
if !originalURL.hasTheSameOrigin(as: redirectURL) {
|
|
headers.remove(name: "Origin")
|
|
headers.remove(name: "Cookie")
|
|
headers.remove(name: "Authorization")
|
|
headers.remove(name: "Proxy-Authorization")
|
|
}
|
|
return (method, headers, body)
|
|
}
|