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

Commit

Permalink
Fix HttpClient redirection logic on UAP (#22702)
Browse files Browse the repository at this point in the history
* Fix HttpClient redirection logic on UAP

This PR changes the implementation of sending requests in HttpClient for UAP when
dealing with redirection. In the past, we would let the WinRT layer handle auto
redirection logic. However, due to #9003, the cookies were getting lost on 3xx responses
since the .NET layer didn't see them. So, this PR implements the redirection ourselves.

One important part of redirection is that we need to drop credentials. The WinRT layer
did this for us. However, we are unable to use a single WinRT HttpBaseProtocolFilter object
since we need to remove the credential after the first redirect. So, we need to maintain
a second filter that uses no credentials and keep it in sync regarding all the other properties
of the primary filter.

With this PR, the behavior of other aspects (such as controlling max number of redirects, etc.)
now matches .NET Framework.  So, some tests were adjusted to remove the UAP specific behavior
checks.

Fixes #9003
Fixes #22191

* Disable test on Linux

* Address PR feedback

* Disable failing HTTP/2 test
  • Loading branch information
davidsh committed Jul 28, 2017
1 parent 2133661 commit f2308f8
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 135 deletions.
130 changes: 39 additions & 91 deletions src/System.Net.Http/src/uap/System/Net/HttpClientHandler.cs
Expand Up @@ -39,7 +39,7 @@ public partial class HttpClientHandler : HttpMessageHandler
private static Oid s_serverAuthOid = new Oid("1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.1");
private static readonly Lazy<bool> s_RTCookieUsageBehaviorSupported =
new Lazy<bool>(InitRTCookieUsageBehaviorSupported);
private static bool RTCookieUsageBehaviorSupported => s_RTCookieUsageBehaviorSupported.Value;
internal static bool RTCookieUsageBehaviorSupported => s_RTCookieUsageBehaviorSupported.Value;
private static readonly Lazy<bool> s_RTNoCacheSupported =
new Lazy<bool>(InitRTNoCacheSupported);
private static bool RTNoCacheSupported => s_RTNoCacheSupported.Value;
Expand All @@ -49,14 +49,16 @@ public partial class HttpClientHandler : HttpMessageHandler

#region Fields

private readonly RTHttpBaseProtocolFilter _rtFilter;
private readonly HttpHandlerToFilter _handlerToFilter;
private readonly HttpMessageHandler _diagnosticsPipeline;

private RTHttpBaseProtocolFilter _rtFilter;
private ClientCertificateOption _clientCertificateOptions;
private CookieContainer _cookieContainer;
private bool _useCookies;
private DecompressionMethods _automaticDecompression;
private bool _allowAutoRedirect;
private int _maxAutomaticRedirections;
private ICredentials _defaultProxyCredentials;
private ICredentials _credentials;
private IWebProxy _proxy;
Expand Down Expand Up @@ -227,19 +229,23 @@ public ICredentials DefaultProxyCredentials

public bool AllowAutoRedirect
{
get { return _rtFilter.AllowAutoRedirect; }
get { return _allowAutoRedirect; }
set
{
CheckDisposedOrStarted();
_rtFilter.AllowAutoRedirect = value;
_allowAutoRedirect = value;
}
}

public int MaxAutomaticRedirections
{
get { return 10; } // WinRT Windows.Web.Http constant via use of native WinINet.
get { return _maxAutomaticRedirections; }
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException("value");
}
CheckDisposedOrStarted();
}
}
Expand Down Expand Up @@ -350,36 +356,51 @@ public SslProtocols SslProtocols

public HttpClientHandler()
{
_rtFilter = new RTHttpBaseProtocolFilter();
_handlerToFilter = new HttpHandlerToFilter(_rtFilter);
_rtFilter = CreateFilter();

_handlerToFilter = new HttpHandlerToFilter(_rtFilter, this);
_handlerToFilter.RequestMessageLookupKey = RequestMessageLookupKey;
_handlerToFilter.SavedExceptionDispatchInfoLookupKey = SavedExceptionDispatchInfoLookupKey;
_diagnosticsPipeline = new DiagnosticsHandler(_handlerToFilter);

_clientCertificateOptions = ClientCertificateOption.Manual;

_useCookies = true; // deal with cookies by default.
_cookieContainer = new CookieContainer(); // default container used for dealing with auto-cookies.

_allowAutoRedirect = true;
_maxAutomaticRedirections = 50;

_automaticDecompression = DecompressionMethods.None;
}

private RTHttpBaseProtocolFilter CreateFilter()
{
var filter = new RTHttpBaseProtocolFilter();

// Always turn off WinRT cookie processing if the WinRT API supports turning it off.
// Use .NET CookieContainer handling only.
if (RTCookieUsageBehaviorSupported)
{
_rtFilter.CookieUsageBehavior = RTHttpCookieUsageBehavior.NoCookies;
filter.CookieUsageBehavior = RTHttpCookieUsageBehavior.NoCookies;
}

_useCookies = true; // deal with cookies by default.
_cookieContainer = new CookieContainer(); // default container used for dealing with auto-cookies.
// Handle redirections at the .NET layer so that we can see cookies on redirect responses
// and have control of the number of redirections allowed.
filter.AllowAutoRedirect = false;

// Managed at this layer for granularity, but uses the desktop default.
_rtFilter.AutomaticDecompression = false;
_automaticDecompression = DecompressionMethods.None;
filter.AutomaticDecompression = false;

// We don't support using the UI model in HttpBaseProtocolFilter() especially for auto-handling 401 responses.
_rtFilter.AllowUI = false;
filter.AllowUI = false;

// The .NET Desktop System.Net Http APIs (based on HttpWebRequest/HttpClient) uses no caching by default.
// To preserve app-compat, we turn off caching in the WinRT HttpClient APIs.
_rtFilter.CacheControl.ReadBehavior = RTNoCacheSupported ?
filter.CacheControl.ReadBehavior = RTNoCacheSupported ?
RTHttpCacheReadBehavior.NoCache : RTHttpCacheReadBehavior.MostRecent;
_rtFilter.CacheControl.WriteBehavior = RTHttpCacheWriteBehavior.NoCache;
filter.CacheControl.WriteBehavior = RTHttpCacheWriteBehavior.NoCache;

return filter;
}

protected override void Dispose(bool disposing)
Expand Down Expand Up @@ -408,27 +429,11 @@ protected override void Dispose(bool disposing)

private async Task ConfigureRequest(HttpRequestMessage request)
{
ApplyRequestCookies(request);

ApplyDecompressionSettings(request);

await ApplyClientCertificateSettings().ConfigureAwait(false);
}

// Taken from System.Net.CookieModule.OnSendingHeaders
private void ApplyRequestCookies(HttpRequestMessage request)
{
if (UseCookies)
{
string cookieHeader = CookieContainer.GetCookieHeader(request.RequestUri);
if (!string.IsNullOrWhiteSpace(cookieHeader))
{
bool success = request.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.Cookie, cookieHeader);
Debug.Assert(success);
}
}
}

private void ApplyDecompressionSettings(HttpRequestMessage request)
{
// Decompression: Add the Gzip and Deflate headers if not already present.
Expand Down Expand Up @@ -604,68 +609,11 @@ private void SetFilterServerCredential()
throw new HttpRequestException(SR.net_http_client_execution_error, ex);
}

ProcessResponse(response);
return response;
}

#endregion Request Execution

#region Response Processing

private void ProcessResponse(HttpResponseMessage response)
{
ProcessResponseCookies(response);
}

// Taken from System.Net.CookieModule.OnReceivedHeaders
private void ProcessResponseCookies(HttpResponseMessage response)
{
if (UseCookies)
{
IEnumerable<string> values;
if (response.Headers.TryGetValues(HttpKnownHeaderNames.SetCookie, out values))
{
foreach (string cookieString in values)
{
if (!string.IsNullOrWhiteSpace(cookieString))
{
try
{
// Parse the cookies so that we can filter some of them out
CookieContainer helper = new CookieContainer();
helper.SetCookies(response.RequestMessage.RequestUri, cookieString);
CookieCollection cookies = helper.GetCookies(response.RequestMessage.RequestUri);
foreach (Cookie cookie in cookies)
{
// We don't want to put HttpOnly cookies in the CookieContainer if the system
// doesn't support the RTHttpBaseProtocolFilter CookieUsageBehavior property.
// Prior to supporting that, the WinRT HttpClient could not turn off cookie
// processing. So, it would always be storing all cookies in its internal container.
// Putting HttpOnly cookies in the .NET CookieContainer would cause problems later
// when the .NET layer tried to add them on outgoing requests and conflicted with
// the WinRT internal cookie processing.
//
// With support for WinRT CookieUsageBehavior, cookie processing is turned off
// within the WinRT layer. This allows us to process cookies using only the .NET
// layer. So, we need to add all applicable cookies that are received to the
// CookieContainer.
if (RTCookieUsageBehaviorSupported || !cookie.HttpOnly)
{
CookieContainer.Add(response.RequestMessage.RequestUri, cookie);
}
}
}
catch (Exception)
{
}
}
}
}
}
}

#endregion Response Processing

#region Helpers

private void SetOperationStarted()
Expand Down Expand Up @@ -728,7 +676,7 @@ private static bool InitRTServerCustomValidationRequestedSupported()
"ServerCustomValidationRequested");
}

private void RTServerCertificateCallback(RTHttpBaseProtocolFilter sender, RTHttpServerCustomValidationRequestedEventArgs args)
internal void RTServerCertificateCallback(RTHttpBaseProtocolFilter sender, RTHttpServerCustomValidationRequestedEventArgs args)
{
bool success = RTServerCertificateCallbackHelper(
args.RequestMessage,
Expand Down

0 comments on commit f2308f8

Please sign in to comment.