Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

don't close connection for responses to session based authentication and 100Continue #38744

Merged
merged 4 commits into from
Jul 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@ private static bool TryGetChallengeDataForScheme(string scheme, HttpHeaderValueC
return false;
}

// Helper function to determine if response is part of session-based authentication challenge.
internal static bool IsSessionAuthenticationChallenge(HttpResponseMessage response)
{
if (response.StatusCode != HttpStatusCode.Unauthorized)
{
return false;
}

HttpHeaderValueCollection<AuthenticationHeaderValue> authenticationHeaderValues = GetResponseAuthenticationHeaderValues(response, isProxyAuth: false);
foreach (AuthenticationHeaderValue ahv in authenticationHeaderValues)
{
if (StringComparer.OrdinalIgnoreCase.Equals(NegotiateScheme, ahv.Scheme) || StringComparer.OrdinalIgnoreCase.Equals(NtlmScheme, ahv.Scheme))
{
return true;
}
}

return false;
}

private static bool TryGetValidAuthenticationChallengeForScheme(string scheme, AuthenticationType authenticationType, Uri uri, ICredentials credentials,
HttpHeaderValueCollection<AuthenticationHeaderValue> authenticationHeaderValues, out AuthenticationChallenge challenge)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,33 +548,54 @@ public async Task<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request,
ParseStatusLine(await ReadNextResponseHeaderLineAsync().ConfigureAwait(false), response);
}

// If we sent an Expect: 100-continue header, and didn't receive a 100-continue. Handle the final response accordingly.
wfurt marked this conversation as resolved.
Show resolved Hide resolved
// Note that the developer may have added an Expect: 100-continue header even if there is no Content.
// Parse the response headers. Logic after this point depends on being able to examine headers in the response object.
while (true)
{
ArraySegment<byte> line = await ReadNextResponseHeaderLineAsync(foldedHeadersAllowed: true).ConfigureAwait(false);
if (IsLineEmpty(line))
{
break;
}
ParseHeaderNameValue(this, line, response);
}

if (allowExpect100ToContinue != null)
{
// If we sent an Expect: 100-continue header, and didn't receive a 100-continue. Handle the final response accordingly.
// Note that the developer may have added an Expect: 100-continue header even if there is no Content.
if ((int)response.StatusCode >= 300 &&
request.Content != null &&
(request.Content.Headers.ContentLength == null || request.Content.Headers.ContentLength.GetValueOrDefault() > Expect100ErrorSendThreshold))
(request.Content.Headers.ContentLength == null || request.Content.Headers.ContentLength.GetValueOrDefault() > Expect100ErrorSendThreshold) &&
!AuthenticationHelper.IsSessionAuthenticationChallenge(response))
{
// For error final status codes, try to avoid sending the payload if its size is unknown or if it's known to be "big".
// If we already sent a header detailing the size of the payload, if we then don't send that payload, the server may wait
// for it and assume that the next request on the connection is actually this request's payload. Thus we mark the connection
// to be closed. However, we may have also lost a race condition with the Expect: 100-continue timeout, so if it turns out
// we've already started sending the payload (we weren't able to cancel it), then we don't need to force close the connection.
// We also must not clone connection if we do NTLM or Negotiate authentication.
allowExpect100ToContinue.TrySetResult(false);

wfurt marked this conversation as resolved.
Show resolved Hide resolved
if (!allowExpect100ToContinue.Task.Result) // if Result is true, the timeout already expired and we started sending content
{
_connectionClose = true;
}
}
else
{
// For any success status codes or for errors when the request content length is known to be small, send the payload
// For any success status codes, for errors when the request content length is known to be small,
// or for session-based authentication challenges, send the payload
// (if there is one... if there isn't, Content is null and thus allowExpect100ToContinue is also null, we won't get here).
allowExpect100ToContinue.TrySetResult(true);
}
}

// Determine whether we need to force close the connection when the request/response has completed.
if (response.Headers.ConnectionClose.GetValueOrDefault())
{
_connectionClose = true;
}

// Now that we've received our final status line, wait for the request content to fully send.
// In most common scenarios, the server won't send back a response until all of the request
// content has been received, so this task should generally already be complete.
Expand All @@ -588,23 +609,6 @@ public async Task<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request,
// Now we are sure that the request was fully sent.
if (NetEventSource.IsEnabled) Trace("Request is fully sent.");

// Parse the response headers.
while (true)
{
ArraySegment<byte> line = await ReadNextResponseHeaderLineAsync(foldedHeadersAllowed: true).ConfigureAwait(false);
if (IsLineEmpty(line))
{
break;
}
ParseHeaderNameValue(this, line, response);
}

// Determine whether we need to force close the connection when the request/response has completed.
if (response.Headers.ConnectionClose.GetValueOrDefault())
{
_connectionClose = true;
}

// We're about to create the response stream, at which point responsibility for canceling
// the remainder of the response lies with the stream. Thus we dispose of our registration
// here (if an exception has occurred or does occur while creating/returning the stream,
Expand Down