@@ -105,7 +105,14 @@ open class URLSessionTask : NSObject, NSCopying {
105105 open private( set) var taskIdentifier : Int
106106
107107 /// May be nil if this is a stream task
108+
108109 /*@NSCopying*/ open private( set) var originalRequest : URLRequest ?
110+
111+ /// If there's an authentication failure, we'd need to create a new request with the credentials supplied by the user
112+ var authRequest : URLRequest ? = nil
113+
114+ /// Authentication failure count
115+ fileprivate var previousFailureCount = 0
109116
110117 /// May differ from originalRequest due to http server redirection
111118 /*@NSCopying*/ open internal( set) var currentRequest : URLRequest ? {
@@ -539,9 +546,38 @@ extension _ProtocolClient : URLProtocolClient {
539546 }
540547 }
541548
549+ func createProtectionSpace( _ response: HTTPURLResponse ) -> URLProtectionSpace ? {
550+ let host = response. url? . host ?? " "
551+ let port = response. url? . port ?? 80 //we're doing http
552+ let _protocol = response. url? . scheme
553+ if response. allHeaderFields [ " WWW-Authenticate " ] != nil {
554+ let wwwAuthHeaderValue = response. allHeaderFields [ " WWW-Authenticate " ] as! String
555+ let authMethod = wwwAuthHeaderValue. components ( separatedBy: " " ) [ 0 ]
556+ let realm = String ( String ( wwwAuthHeaderValue. components ( separatedBy: " realm= " ) [ 1 ] . dropFirst ( ) ) . dropLast ( ) )
557+ return URLProtectionSpace ( host: host, port: port, protocol: _protocol, realm: realm, authenticationMethod: authMethod)
558+ } else {
559+ return nil
560+ }
561+ }
562+
542563 func urlProtocolDidFinishLoading( _ protocol: URLProtocol ) {
543564 guard let task = `protocol`. task else { fatalError ( ) }
544565 guard let session = task. session as? URLSession else { fatalError ( ) }
566+ guard let response = task. response as? HTTPURLResponse else { fatalError ( " No response " ) }
567+ if response. statusCode == 401 {
568+ if let protectionSpace = createProtectionSpace ( response) {
569+ //TODO: Fetch and set proposed credentials if they exist
570+ let authenticationChallenge = URLAuthenticationChallenge ( protectionSpace: protectionSpace, proposedCredential: nil ,
571+ previousFailureCount: task. previousFailureCount, failureResponse: response, error: nil ,
572+ sender: `protocol` as! _HTTPURLProtocol )
573+ task. previousFailureCount += 1
574+ urlProtocol ( `protocol`, didReceive: authenticationChallenge)
575+ return
576+ } else {
577+ let urlError = URLError ( _nsError: NSError ( domain: NSURLErrorDomain, code: NSURLErrorUserAuthenticationRequired, userInfo: nil ) )
578+ urlProtocol ( `protocol`, didFailWithError: urlError)
579+ }
580+ }
545581 switch session. behaviour ( for: task) {
546582 case . taskDelegate( let delegate) :
547583 if let downloadDelegate = delegate as? URLSessionDownloadDelegate , let downloadTask = task as? URLSessionDownloadTask {
@@ -586,7 +622,24 @@ extension _ProtocolClient : URLProtocolClient {
586622 }
587623
588624 func urlProtocol( _ protocol: URLProtocol , didReceive challenge: URLAuthenticationChallenge ) {
589- NSUnimplemented ( )
625+ guard let task = `protocol`. task else { fatalError ( " Received response, but there's no task. " ) }
626+ guard let session = task. session as? URLSession else { fatalError ( " Task not associated with URLSession. " ) }
627+ switch session. behaviour ( for: task) {
628+ case . taskDelegate( let delegate) :
629+ session. delegateQueue. addOperation {
630+ let authScheme = challenge. protectionSpace. authenticationMethod
631+ delegate. urlSession ( session, task: task, didReceive: challenge) { disposition, credential in
632+ task. suspend ( )
633+ guard let handler = URLSessionTask . authHandler ( for: authScheme) else {
634+ fatalError ( " \( authScheme) is not supported " )
635+ }
636+ handler ( task, disposition, credential)
637+ task. _protocol = _HTTPURLProtocol ( task: task, cachedResponse: nil , client: nil )
638+ task. resume ( )
639+ }
640+ }
641+ default : return
642+ }
590643 }
591644
592645 func urlProtocol( _ protocol: URLProtocol , didLoad data: Data ) {
@@ -653,6 +706,31 @@ extension _ProtocolClient : URLProtocolClient {
653706 NSUnimplemented ( )
654707 }
655708}
709+ extension URLSessionTask {
710+ typealias _AuthHandler = ( ( URLSessionTask , URLSession . AuthChallengeDisposition , URLCredential ? ) -> ( ) )
711+
712+ static func authHandler( for authScheme: String ) -> _AuthHandler ? {
713+ let handlers : [ String : _AuthHandler ] = [
714+ " Basic " : basicAuth,
715+ " Digest " : digestAuth
716+ ]
717+ return handlers [ authScheme]
718+ }
719+
720+ //Authentication handlers
721+ static func basicAuth( _ task: URLSessionTask , _ disposition: URLSession . AuthChallengeDisposition , _ credential: URLCredential ? ) {
722+ //TODO: Handle disposition. For now, we default to .useCredential
723+ let user = credential? . user ?? " "
724+ let password = credential? . password ?? " "
725+ let encodedString = " \( user) : \( password) " . data ( using: . utf8) ? . base64EncodedString ( )
726+ task. authRequest = task. originalRequest
727+ task. authRequest? . setValue ( " Basic \( encodedString!) " , forHTTPHeaderField: " Authorization " )
728+ }
729+
730+ static func digestAuth( _ task: URLSessionTask , _ disposition: URLSession . AuthChallengeDisposition , _ credential: URLCredential ? ) {
731+ NSUnimplemented ( )
732+ }
733+ }
656734
657735extension URLProtocol {
658736 enum _PropertyKey : String {
0 commit comments