@@ -227,15 +227,20 @@ public class HTTPClient {
227227 channelEL: EventLoop ? = nil ,
228228 deadline: NIODeadline ? = nil ) -> Task < Delegate . Response > {
229229 let redirectHandler : RedirectHandler < Delegate . Response > ?
230- if self . configuration. followRedirects {
230+ switch self . configuration. redirectConfiguration. configuration {
231+ case . follow( let max, let allowCycles) :
232+ var request = request
233+ if request. redirectState == nil {
234+ request. redirectState = . init( count: max, visited: allowCycles ? nil : Set ( ) )
235+ }
231236 redirectHandler = RedirectHandler < Delegate . Response > ( request: request) { newRequest in
232237 self . execute ( request: newRequest,
233238 delegate: delegate,
234239 eventLoop: delegateEL,
235240 channelEL: channelEL,
236241 deadline: deadline)
237242 }
238- } else {
243+ case . disallow :
239244 redirectHandler = nil
240245 }
241246
@@ -325,7 +330,7 @@ public class HTTPClient {
325330 /// - `305: Use Proxy`
326331 /// - `307: Temporary Redirect`
327332 /// - `308: Permanent Redirect`
328- public var followRedirects : Bool
333+ public var redirectConfiguration : RedirectConfiguration
329334 /// Default client timeout, defaults to no timeouts.
330335 public var timeout : Timeout
331336 /// Upstream proxy, defaults to no proxy.
@@ -336,27 +341,27 @@ public class HTTPClient {
336341 public var ignoreUncleanSSLShutdown : Bool
337342
338343 public init ( tlsConfiguration: TLSConfiguration ? = nil ,
339- followRedirects : Bool = false ,
344+ redirectConfiguration : RedirectConfiguration ? = nil ,
340345 timeout: Timeout = Timeout ( ) ,
341346 proxy: Proxy ? = nil ,
342347 ignoreUncleanSSLShutdown: Bool = false ,
343348 decompression: Decompression = . disabled) {
344349 self . tlsConfiguration = tlsConfiguration
345- self . followRedirects = followRedirects
350+ self . redirectConfiguration = redirectConfiguration ?? RedirectConfiguration ( )
346351 self . timeout = timeout
347352 self . proxy = proxy
348353 self . ignoreUncleanSSLShutdown = ignoreUncleanSSLShutdown
349354 self . decompression = decompression
350355 }
351356
352357 public init ( certificateVerification: CertificateVerification ,
353- followRedirects : Bool = false ,
358+ redirectConfiguration : RedirectConfiguration ? = nil ,
354359 timeout: Timeout = Timeout ( ) ,
355360 proxy: Proxy ? = nil ,
356361 ignoreUncleanSSLShutdown: Bool = false ,
357362 decompression: Decompression = . disabled) {
358363 self . tlsConfiguration = TLSConfiguration . forClient ( certificateVerification: certificateVerification)
359- self . followRedirects = followRedirects
364+ self . redirectConfiguration = redirectConfiguration ?? RedirectConfiguration ( )
360365 self . timeout = timeout
361366 self . proxy = proxy
362367 self . ignoreUncleanSSLShutdown = ignoreUncleanSSLShutdown
@@ -439,6 +444,38 @@ extension HTTPClient.Configuration {
439444 self . read = read
440445 }
441446 }
447+
448+ /// Specifies redirect processing settings.
449+ public struct RedirectConfiguration {
450+ enum Configuration {
451+ /// Redirects are not followed.
452+ case disallow
453+ /// Redirects are followed with a specified limit.
454+ case follow( max: Int , allowCycles: Bool )
455+ }
456+
457+ var configuration : Configuration
458+
459+ init ( ) {
460+ self . configuration = . follow( max: 5 , allowCycles: false )
461+ }
462+
463+ init ( configuration: Configuration ) {
464+ self . configuration = configuration
465+ }
466+
467+ /// Redirects are not followed.
468+ public static let disallow = RedirectConfiguration ( configuration: . disallow)
469+
470+ /// Redirects are followed with a specified limit.
471+ ///
472+ /// - parameters:
473+ /// - max: The maximum number of allowed redirects.
474+ /// - allowCycles: Whether cycles are allowed.
475+ ///
476+ /// - warning: Cycle detection will keep all visited URLs in memory which means a malicious server could use this as a denial-of-service vector.
477+ public static func follow( max: Int , allowCycles: Bool ) -> RedirectConfiguration { return . init( configuration: . follow( max: max, allowCycles: allowCycles) ) }
478+ }
442479}
443480
444481private extension ChannelPipeline {
@@ -488,6 +525,8 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
488525 case invalidProxyResponse
489526 case contentLengthMissing
490527 case proxyAuthenticationRequired
528+ case redirectLimitReached
529+ case redirectCycleDetected
491530 }
492531
493532 private var code : Code
@@ -526,4 +565,8 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
526565 public static let contentLengthMissing = HTTPClientError ( code: . contentLengthMissing)
527566 /// Proxy Authentication Required.
528567 public static let proxyAuthenticationRequired = HTTPClientError ( code: . proxyAuthenticationRequired)
568+ /// Redirect Limit reached.
569+ public static let redirectLimitReached = HTTPClientError ( code: . redirectLimitReached)
570+ /// Redirect Cycle detected.
571+ public static let redirectCycleDetected = HTTPClientError ( code: . redirectCycleDetected)
529572}
0 commit comments