Skip to content

Create URL Protection Space from HTTPURLResponse #2761

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 3, 2020

Conversation

lxbndr
Copy link
Contributor

@lxbndr lxbndr commented Apr 5, 2020

Basic implementation of "Www-Authenticate" parsing. Some real-life responses could crash previous implementation, as it doesn't work with realm in case insensitive way. We decided to modify existing (but unused) internal helper, considering that a good step in iterative improvement of FoundationNetworking.

This also fixes access to "Www-Authenticate" header in some places,
as HTTPURLResponse does capitalize headers after #2728.

@lxbndr
Copy link
Contributor Author

lxbndr commented Apr 5, 2020

CC @compnerd @spevans @millenomi

@spevans
Copy link
Contributor

spevans commented Apr 5, 2020

@swift-ci test linux

1 similar comment
@spevans
Copy link
Contributor

spevans commented Apr 5, 2020

@swift-ci test linux

let port = response.url?.port ?? 80 //HTTP
let _protocol = response.url?.scheme
guard let wwwAuthHeader = response.allHeaderFields["Www-Authenticate"] as? String else {
fatalError("Authentication failed but no Www-Authenticate header in response")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to be stricter on the input validation, it can alway be loosened later on. Rather than defaulting values I would suggest:

guard let url = response.url, let host = url.host, let _protocol = url.scheme, _protocol == "http" || _protocol == "https" else {
    return nil
}
let port = url.port ?? (_protocol == "http" ? 80 : 443)

to tighten up the validation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. And less code. I like it. Will do.

// Typical Www-Authenticate header is something like
// Www-Authenticate: Digest realm="test", domain="/HTTP/Digest", nonce="e3d002b9b2080453fdacea2d89f2d102"

let components = authenticateValue.components(separatedBy: " ")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this split correctly if there are multiple spaces between each part?
I think split(separator: Character, maxSplits: Int = Int.max, omittingEmptySubsequences: Bool = true) -> [Substring] might be a better choice as by default it will not add empty sequences to the output array.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the grammar:

   WWW-Authenticate = *( "," OWS ) challenge *( OWS "," [ OWS challenge
    ] )

   auth-param = token BWS "=" BWS ( token / quoted-string )
   auth-scheme = token

   challenge = auth-scheme [ 1*SP ( token68 / [ ( "," / auth-param ) *(
    OWS "," [ OWS auth-param ] ) ] ) ]
   credentials = auth-scheme [ 1*SP ( token68 / [ ( "," / auth-param )
    *( OWS "," [ OWS auth-param ] ) ] ) ]

where:

     OWS            = *( SP / HTAB )
                    ; optional whitespace
…
     BWS            = OWS
                    ; "bad" whitespace

so it looks like we need to split for " " or "\t". Use the CharacterSet variants maybe?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return NSURLAuthenticationMethodNTLM
case "negotiate":
return NSURLAuthenticationMethodNegotiate
default:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it cause problems to create a URLProtectionSpace for methods that are not currently implemented (ie everything except Basic)? Could always return nil for these other ones for now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually thought about this helper point as parsing as much info as possible (and reasonable). Hmm, but on the other side it could take responsibility to cut off unsupported things. Agree, this should return nil for everything other than Basic auth.

// Basic implementation.
// Doesn't follow complete spec of quoted strings (RFC-7230, p. 3.2.6).
// Handles escaping of quotes only.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Give that this is concerned with authentication I think that the complete spec should be followed. Maybe it could be broken out into a separate function that fully parses a String into its constituent parts. If any percent decoding needs to be done on the input string then there is DataURLProtocol._PercentDecoder that could possibly be used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for hint. I guess I will also look into curl implementation of similar task. AFAIK it is pretty simple. I like the idea of separate parsing, could be also tested separately.

@millenomi
Copy link
Contributor

Good by me once the feedback is looked at.

return port
}

switch scheme {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forget if scheme is normalized?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be scheme.lowercased().

// Typical Www-Authenticate header is something like
// Www-Authenticate: Digest realm="test", domain="/HTTP/Digest", nonce="e3d002b9b2080453fdacea2d89f2d102"

let components = authenticateValue.components(separatedBy: " ")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the grammar:

   WWW-Authenticate = *( "," OWS ) challenge *( OWS "," [ OWS challenge
    ] )

   auth-param = token BWS "=" BWS ( token / quoted-string )
   auth-scheme = token

   challenge = auth-scheme [ 1*SP ( token68 / [ ( "," / auth-param ) *(
    OWS "," [ OWS auth-param ] ) ] ) ]
   credentials = auth-scheme [ 1*SP ( token68 / [ ( "," / auth-param )
    *( OWS "," [ OWS auth-param ] ) ] ) ]

where:

     OWS            = *( SP / HTAB )
                    ; optional whitespace
…
     BWS            = OWS
                    ; "bad" whitespace

so it looks like we need to split for " " or "\t". Use the CharacterSet variants maybe?

// Typical Www-Authenticate header is something like
// Www-Authenticate: Digest realm="test", domain="/HTTP/Digest", nonce="e3d002b9b2080453fdacea2d89f2d102"

let components = authenticateValue.components(separatedBy: " ")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lxbndr
Copy link
Contributor Author

lxbndr commented May 11, 2020

I'm so sorry, the solution is really delayed. It is still WIP. Had done about 3/4. I am thinking to convert this to draft, to not to look like stale PR.

@compnerd compnerd marked this pull request as draft May 12, 2020 23:15
@millenomi
Copy link
Contributor

@lxbndr are you still unable to finish this patch?

@lxbndr
Copy link
Contributor Author

lxbndr commented Aug 26, 2020

@millenomi
I am sorry for delay, again. I've revisited it lately and now think it is ready to be pushed. Will do tomorrow.
It actually blocks me from finishing another auth-related issue, so not going to postpone it anymore.

@millenomi
Copy link
Contributor

Thank you! I'll be here to facilitate.

@lxbndr lxbndr force-pushed the protection-space-realm branch from 923c868 to 507a169 Compare August 27, 2020 16:26
@lxbndr
Copy link
Contributor Author

lxbndr commented Aug 27, 2020

This all started as an attempt to parse "realm" parameter and not to crash. New implementation is RFC-compliant. I think I found perfect place for parsing code, and it is in _HTTPURLProtocol extensions, where other HTTP-related parsing already lives.

All necessary string comparisons are case-insensitive, but internally data stored as is, so _Challenge structure is more like raw parsed data.

Some simplifications made assuming we are supporting only "Basic" scheme. But in general this code could be used to adopt other schemes.

I reused as much existing code as seemed reasonable (like token parsing, whitespace trimming, character constants).

It seems like we could refactor existing code a bit to improve consistency and streamline some logic. E.g. existing code references RFC-2616, which is obsoleted by RFCs 7230-7235. To keep this patch focused I used old code as is.

The general flow of WWW-Authenticate parsing was inspired by CURL's approach, as it seems pretty robust.

@lxbndr lxbndr marked this pull request as ready for review August 27, 2020 16:43
@lxbndr
Copy link
Contributor Author

lxbndr commented Aug 27, 2020

@millenomi Could you please take a look? I think I took all comments into account. But as changes are much bigger than first time, I am expecting everything.

@millenomi
Copy link
Contributor

Nothing immediately jumps at me. At this point, this looks large and useful enough that we can start taking bug fixes as separate patches if needed.

@millenomi
Copy link
Contributor

@swift-ci please test

@lxbndr lxbndr changed the base branch from master to main September 23, 2020 07:41
@lxbndr lxbndr force-pushed the protection-space-realm branch from 507a169 to 51938c9 Compare September 25, 2020 16:30
@lxbndr
Copy link
Contributor Author

lxbndr commented Sep 25, 2020

I found that one line I've changed was unnecessary. Reverted HTTPURLProtocol.swift:137 to original.
No other changes were made.

@millenomi Is there anything else I can do here? I am not in a rush, just thought maybe I missed some requirement.

@@ -111,6 +111,10 @@ struct _Delimiters {
static let Space = UnicodeScalar(0x20)
static let HorizontalTab = UnicodeScalar(0x09)
static let Colon = UnicodeScalar(0x3a)
static let Backslash = UnicodeScalar(0x5c)!
static let Comma = UnicodeScalar(0x2c)!
static let Dquote = UnicodeScalar(0x22)!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Mind if we call this DoubleQuote? We already spell HTAB HorizontalTab instead of Htab, and it just reads a little more clearly, I think, in the context where we have comments referring to "dequoted" strings and then code that refers to "dquote".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! Anything to reduce confusion. Change pushed.

@lxbndr lxbndr force-pushed the protection-space-realm branch from 51938c9 to 686675c Compare September 25, 2020 22:10
@MaxDesiatov

This comment has been minimized.

@lxbndr
Copy link
Contributor Author

lxbndr commented Sep 28, 2020

Build failure on Linux looks unrelated. @MaxDesiatov Could re-run fix that, WDYT?

@MaxDesiatov

This comment has been minimized.

Basic implementation of authentication challenge parsing.
@lxbndr lxbndr force-pushed the protection-space-realm branch from 686675c to ca2fe94 Compare October 30, 2020 13:51
@lxbndr
Copy link
Contributor Author

lxbndr commented Oct 30, 2020

Rebased to latest main.

@MaxDesiatov

This comment has been minimized.

1 similar comment
@MaxDesiatov
Copy link
Contributor

@swift-ci please test

@MaxDesiatov
Copy link
Contributor

MaxDesiatov commented Dec 2, 2020

Would anyone mind if this is merged?

@MaxDesiatov
Copy link
Contributor

MaxDesiatov commented Dec 2, 2020

@swift-ci please test

@spevans
Copy link
Contributor

spevans commented Dec 2, 2020

@MaxDesiatov No I dont mind, lets get this in. I think have some URLSession fixes that rely on this so would be good to progress those.

@MaxDesiatov
Copy link
Contributor

Linux failure seems to be unrelated

@MaxDesiatov
Copy link
Contributor

@swift-ci please test Linux platform

1 similar comment
@spevans
Copy link
Contributor

spevans commented Dec 3, 2020

@swift-ci please test Linux platform

@spevans spevans merged commit 6897b19 into swiftlang:main Dec 3, 2020
@lxbndr lxbndr deleted the protection-space-realm branch December 3, 2020 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants