/// <summary> /// Creates, configures and processes an asynchronous request to the indicated resource. /// </summary> /// <returns>Task in which the request is executed</returns> /// <param name="request">Request provided by <see cref="System.Net.Http.HttpClient"/></param> /// <param name="cancellationToken">Cancellation token.</param> protected override async Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { AssertSelf(); if (request == null) { throw new ArgumentNullException(nameof(request)); } if (!request.RequestUri.IsAbsoluteUri) { throw new ArgumentException("Must represent an absolute URI", "request"); } var redirectState = new RequestRedirectionState { NewUrl = request.RequestUri, RedirectCounter = 0, Method = request.Method }; while (true) { URL java_url = new URL(EncodeUrl(redirectState.NewUrl)); URLConnection java_connection = java_url.OpenConnection(); HttpURLConnection httpConnection = await SetupRequestInternal(request, java_connection); HttpResponseMessage response = await ProcessRequest(request, java_url, httpConnection, cancellationToken, redirectState); if (response != null) { return(response); } if (redirectState.NewUrl == null) { throw new InvalidOperationException("Request redirected but no new URI specified"); } request.Method = redirectState.Method; } }
bool HandleRedirect(HttpStatusCode redirectCode, HttpURLConnection httpConnection, RequestRedirectionState redirectState, out bool disposeRet) { if (!AllowAutoRedirect) { disposeRet = false; return(true); // We shouldn't follow and there's no data to fetch, just return } disposeRet = true; redirectState.NewUrl = null; switch (redirectCode) { case HttpStatusCode.MultipleChoices: // 300 break; case HttpStatusCode.Moved: // 301 case HttpStatusCode.Redirect: // 302 case HttpStatusCode.SeeOther: // 303 redirectState.Method = HttpMethod.Get; break; case HttpStatusCode.TemporaryRedirect: // 307 break; default: if ((int)redirectCode >= 300 && (int)redirectCode < 400) { throw new InvalidOperationException($"HTTP Redirection status code {redirectCode} ({(int)redirectCode}) not supported"); } return(false); } IDictionary <string, IList <string> > headers = httpConnection.HeaderFields; IList <string> locationHeader; if (!headers.TryGetValue("Location", out locationHeader) || locationHeader == null || locationHeader.Count == 0) { throw new InvalidOperationException($"HTTP connection redirected with code {redirectCode} ({(int)redirectCode}) but no Location header found in response"); } if (locationHeader.Count > 1 && Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"More than one location header for HTTP {redirectCode} redirect. Will use the first one."); } redirectState.RedirectCounter++; if (redirectState.RedirectCounter >= MaxAutomaticRedirections) { throw new WebException($"Maximum automatic redirections exceeded (allowed {MaxAutomaticRedirections}, redirected {redirectState.RedirectCounter} times)"); } string redirectUrl = locationHeader [0]; string protocol = httpConnection.URL?.Protocol; if (redirectUrl.StartsWith("//", StringComparison.Ordinal)) { // When redirecting to an URL without protocol, we use the protocol of previous request // See https://tools.ietf.org/html/rfc3986#section-5 (example in section 5.4) redirectUrl = protocol + ":" + redirectUrl; } redirectState.NewUrl = new Uri(redirectUrl, UriKind.Absolute); if (Logger.LogNet) { Logger.Log(LogLevel.Debug, LOG_APP, $"Request redirected to {redirectState.NewUrl}"); } return(true); }
async Task <HttpResponseMessage> DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"{this}.DoProcessRequest ()"); } if (cancellationToken.IsCancellationRequested) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, " cancelled"); } cancellationToken.ThrowIfCancellationRequested(); } try { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $" connecting"); } await ConnectAsync(httpConnection, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $" connected"); } } catch (Java.Net.ConnectException ex) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Connection exception {ex}"); } // Wrap it nicely in a "standard" exception so that it's compatible with HttpClientHandler throw new WebException(ex.Message, ex, WebExceptionStatus.ConnectFailure, null); } if (cancellationToken.IsCancellationRequested) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, " cancelled"); } await DisconnectAsync(httpConnection).ConfigureAwait(continueOnCapturedContext: false); cancellationToken.ThrowIfCancellationRequested(); } CancellationTokenRegistration cancelRegistration = default(CancellationTokenRegistration); HttpStatusCode statusCode = HttpStatusCode.OK; Uri connectionUri = null; try { cancelRegistration = cancellationToken.Register(() => { DisconnectAsync(httpConnection).ContinueWith(t => { if (t.Exception != null) { Logger.Log(LogLevel.Info, LOG_APP, $"Disconnection exception: {t.Exception}"); } }, TaskScheduler.Default); }, useSynchronizationContext: false); if (httpConnection.DoOutput) { using (var stream = await request.Content.ReadAsStreamAsync().ConfigureAwait(false)) { await stream.CopyToAsync(httpConnection.OutputStream, 4096, cancellationToken) .ConfigureAwait(false); } } statusCode = await Task.Run(() => (HttpStatusCode)httpConnection.ResponseCode).ConfigureAwait(false); connectionUri = new Uri(httpConnection.URL.ToString()); } finally { cancelRegistration.Dispose(); } if (cancellationToken.IsCancellationRequested) { await DisconnectAsync(httpConnection).ConfigureAwait(continueOnCapturedContext: false); cancellationToken.ThrowIfCancellationRequested(); } // If the request was redirected we need to put the new URL in the request request.RequestUri = connectionUri; var ret = new AndroidHttpResponseMessage(javaUrl, httpConnection) { RequestMessage = request, ReasonPhrase = httpConnection.ResponseMessage, StatusCode = statusCode, }; if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Status code: {statusCode}"); } bool disposeRet; if (HandleRedirect(statusCode, httpConnection, redirectState, out disposeRet)) { if (disposeRet) { ret.Dispose(); ret = null; } return(ret); } switch (statusCode) { case HttpStatusCode.Unauthorized: case HttpStatusCode.ProxyAuthenticationRequired: // We don't resend the request since that would require new set of credentials if the // ones provided in Credentials are invalid (or null) and that, in turn, may require asking the // user which is not something that should be taken care of by us and in this // context. The application should be responsible for this. // HttpClientHandler throws an exception in this instance, but I think it's not a good // idea. We'll return the response message with all the information required by the // application to fill in the blanks and provide the requested credentials instead. // // We return the body of the response too, but the Java client will throw // a FileNotFound exception if we attempt to access the input stream. // Instead we try to read the error stream and return an default message if the error stream isn't readable. ret.Content = GetErrorContent(httpConnection, new StringContent("Unauthorized", Encoding.ASCII)); CopyHeaders(httpConnection, ret); if (ret.Headers.WwwAuthenticate != null) { ProxyAuthenticationRequested = false; CollectAuthInfo(ret.Headers.WwwAuthenticate); } else if (ret.Headers.ProxyAuthenticate != null) { ProxyAuthenticationRequested = true; CollectAuthInfo(ret.Headers.ProxyAuthenticate); } ret.RequestedAuthentication = RequestedAuthentication; return(ret); } if (!IsErrorStatusCode(statusCode)) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Reading..."); } ret.Content = GetContent(httpConnection, httpConnection.InputStream); } else { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Status code is {statusCode}, reading..."); } // For 400 >= response code <= 599 the Java client throws the FileNotFound exception when attempting to read from the input stream. // Instead we try to read the error stream and return an empty string if the error stream isn't readable. ret.Content = GetErrorContent(httpConnection, new StringContent(String.Empty, Encoding.ASCII)); } CopyHeaders(httpConnection, ret); IEnumerable <string> cookieHeaderValue; if (!UseCookies || CookieContainer == null || !ret.Headers.TryGetValues("Set-Cookie", out cookieHeaderValue) || cookieHeaderValue == null) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"No cookies"); } return(ret); } try { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Parsing cookies"); } CookieContainer.SetCookies(connectionUri, String.Join(",", cookieHeaderValue)); } catch (Exception ex) { // We don't want to terminate the response because of a bad cookie, hence just reporting // the issue. We might consider adding a virtual method to let the user handle the // issue, but not sure if it's really needed. Set-Cookie header will be part of the // header collection so the user can always examine it if they spot an error. if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Failed to parse cookies in the server response. {ex.GetType ()}: {ex.Message}"); } } if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Returning"); } return(ret); }
Task <HttpResponseMessage> ProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) { cancellationToken.ThrowIfCancellationRequested(); httpConnection.InstanceFollowRedirects = false; // We handle it ourselves RequestedAuthentication = null; ProxyAuthenticationRequested = false; return(DoProcessRequest(request, javaUrl, httpConnection, cancellationToken, redirectState)); }
bool HandleRedirect(HttpStatusCode redirectCode, HttpURLConnection httpConnection, RequestRedirectionState redirectState, out bool disposeRet) { if (!AllowAutoRedirect) { disposeRet = false; return(true); // We shouldn't follow and there's no data to fetch, just return } disposeRet = true; redirectState.NewUrl = null; redirectState.MethodChanged = false; switch (redirectCode) { case HttpStatusCode.MultipleChoices: // 300 break; case HttpStatusCode.Moved: // 301 case HttpStatusCode.Redirect: // 302 case HttpStatusCode.SeeOther: // 303 redirectState.MethodChanged = redirectState.Method != HttpMethod.Get; redirectState.Method = HttpMethod.Get; break; case HttpStatusCode.NotModified: // 304 disposeRet = false; return(true); // Not much happening here, just return and let the client decide // what to do with the response case HttpStatusCode.TemporaryRedirect: // 307 break; default: if ((int)redirectCode >= 300 && (int)redirectCode < 400) { throw new InvalidOperationException($"HTTP Redirection status code {redirectCode} ({(int)redirectCode}) not supported"); } return(false); } IDictionary <string, IList <string> > headers = httpConnection.HeaderFields; IList <string> locationHeader; if (!headers.TryGetValue("Location", out locationHeader) || locationHeader == null || locationHeader.Count == 0) { // As per https://tools.ietf.org/html/rfc7231#section-6.4.1 the reponse isn't required to contain the Location header and the // client should act accordingly. Since it is not documented what the action in this case should be, we're following what // Xamarin.iOS does and simply return the content of the request as if it wasn't a redirect. disposeRet = false; return(true); } if (locationHeader.Count > 1 && Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"More than one location header for HTTP {redirectCode} redirect. Will use the first one."); } redirectState.RedirectCounter++; if (redirectState.RedirectCounter >= MaxAutomaticRedirections) { throw new WebException($"Maximum automatic redirections exceeded (allowed {MaxAutomaticRedirections}, redirected {redirectState.RedirectCounter} times)"); } string redirectUrl = locationHeader [0]; string protocol = httpConnection.URL?.Protocol; if (redirectUrl.StartsWith("//", StringComparison.Ordinal)) { // When redirecting to an URL without protocol, we use the protocol of previous request // See https://tools.ietf.org/html/rfc3986#section-5 (example in section 5.4) redirectUrl = protocol + ":" + redirectUrl; } redirectState.NewUrl = new Uri(redirectUrl, UriKind.Absolute); if (Logger.LogNet) { Logger.Log(LogLevel.Debug, LOG_APP, $"Request redirected to {redirectState.NewUrl}"); } return(true); }
async Task <HttpResponseMessage> DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"{this}.DoProcessRequest ()"); } if (cancellationToken.IsCancellationRequested) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, " cancelled"); } cancellationToken.ThrowIfCancellationRequested(); } try { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $" connecting"); } await ConnectAsync(httpConnection, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $" connected"); } } catch (Java.Net.ConnectException ex) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Connection exception {ex}"); } // Wrap it nicely in a "standard" exception so that it's compatible with HttpClientHandler throw new WebException(ex.Message, ex, WebExceptionStatus.ConnectFailure, null); } if (cancellationToken.IsCancellationRequested) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, " cancelled"); } await DisconnectAsync(httpConnection).ConfigureAwait(continueOnCapturedContext: false); cancellationToken.ThrowIfCancellationRequested(); } CancellationTokenRegistration cancelRegistration = default(CancellationTokenRegistration); HttpStatusCode statusCode = HttpStatusCode.OK; Uri connectionUri = null; try { cancelRegistration = cancellationToken.Register(() => { DisconnectAsync(httpConnection).ContinueWith(t => { if (t.Exception != null) { Logger.Log(LogLevel.Info, LOG_APP, $"Disconnection exception: {t.Exception}"); } }, TaskScheduler.Default); }, useSynchronizationContext: false); if (httpConnection.DoOutput) { await WriteRequestContentToOutput(request, httpConnection, cancellationToken); } statusCode = await Task.Run(() => (HttpStatusCode)httpConnection.ResponseCode).ConfigureAwait(false); connectionUri = new Uri(httpConnection.URL.ToString()); } finally { cancelRegistration.Dispose(); } if (cancellationToken.IsCancellationRequested) { await DisconnectAsync(httpConnection).ConfigureAwait(continueOnCapturedContext: false); cancellationToken.ThrowIfCancellationRequested(); } // If the request was redirected we need to put the new URL in the request request.RequestUri = connectionUri; var ret = new AndroidHttpResponseMessage(javaUrl, httpConnection) { RequestMessage = request, ReasonPhrase = httpConnection.ResponseMessage, StatusCode = statusCode, }; if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Status code: {statusCode}"); } if (!IsErrorStatusCode(statusCode)) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Reading..."); } ret.Content = GetContent(httpConnection, httpConnection.InputStream); } else { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Status code is {statusCode}, reading..."); } // For 400 >= response code <= 599 the Java client throws the FileNotFound exception when attempting to read from the input stream. // Instead we try to read the error stream and return an empty string if the error stream isn't readable. ret.Content = GetErrorContent(httpConnection, new StringContent(String.Empty, Encoding.ASCII)); } bool disposeRet; if (HandleRedirect(statusCode, httpConnection, redirectState, out disposeRet)) { if (redirectState.MethodChanged) { // If a redirect uses GET but the original request used POST with content, then the redirected // request will fail with an exception. // There's also no way to send content using GET (except in the URL, of course), so discarding // request.Content is what we should do. // // See https://github.com/xamarin/xamarin-android/issues/1282 if (redirectState.Method == HttpMethod.Get) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Discarding content on redirect"); } request.Content = null; } } if (disposeRet) { ret.Dispose(); ret = null; } else { CopyHeaders(httpConnection, ret); ParseCookies(ret, connectionUri); } return(ret); } switch (statusCode) { case HttpStatusCode.Unauthorized: case HttpStatusCode.ProxyAuthenticationRequired: // We don't resend the request since that would require new set of credentials if the // ones provided in Credentials are invalid (or null) and that, in turn, may require asking the // user which is not something that should be taken care of by us and in this // context. The application should be responsible for this. // HttpClientHandler throws an exception in this instance, but I think it's not a good // idea. We'll return the response message with all the information required by the // application to fill in the blanks and provide the requested credentials instead. // // We return the body of the response too, but the Java client will throw // a FileNotFound exception if we attempt to access the input stream. // Instead we try to read the error stream and return an default message if the error stream isn't readable. ret.Content = GetErrorContent(httpConnection, new StringContent("Unauthorized", Encoding.ASCII)); CopyHeaders(httpConnection, ret); if (ret.Headers.WwwAuthenticate != null) { ProxyAuthenticationRequested = false; CollectAuthInfo(ret.Headers.WwwAuthenticate); } else if (ret.Headers.ProxyAuthenticate != null) { ProxyAuthenticationRequested = true; CollectAuthInfo(ret.Headers.ProxyAuthenticate); } ret.RequestedAuthentication = RequestedAuthentication; return(ret); } CopyHeaders(httpConnection, ret); ParseCookies(ret, connectionUri); if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Returning"); } return(ret); }
/// <summary> /// Creates, configures and processes an asynchronous request to the indicated resource. /// </summary> /// <returns>Task in which the request is executed</returns> /// <param name="request">Request provided by <see cref="System.Net.Http.HttpClient"/></param> /// <param name="cancellationToken">Cancellation token.</param> protected override async Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { AssertSelf(); if (request == null) { throw new ArgumentNullException(nameof(request)); } if (!request.RequestUri.IsAbsoluteUri) { throw new ArgumentException("Must represent an absolute URI", "request"); } var redirectState = new RequestRedirectionState { NewUrl = request.RequestUri, RedirectCounter = 0, Method = request.Method }; while (true) { URL java_url = new URL(EncodeUrl(redirectState.NewUrl)); URLConnection java_connection; if (UseProxy) { java_connection = java_url.OpenConnection(); } else { java_connection = java_url.OpenConnection(Java.Net.Proxy.NoProxy); } var httpsConnection = java_connection as HttpsURLConnection; if (httpsConnection != null) { IHostnameVerifier hnv = GetSSLHostnameVerifier(httpsConnection); if (hnv != null) { httpsConnection.HostnameVerifier = hnv; } } if (ConnectTimeout != TimeSpan.Zero) { java_connection.ConnectTimeout = checked ((int)ConnectTimeout.TotalMilliseconds); } if (ReadTimeout != TimeSpan.Zero) { java_connection.ReadTimeout = checked ((int)ReadTimeout.TotalMilliseconds); } HttpURLConnection httpConnection = await SetupRequestInternal(request, java_connection).ConfigureAwait(continueOnCapturedContext: false);; HttpResponseMessage response = await ProcessRequest(request, java_url, httpConnection, cancellationToken, redirectState).ConfigureAwait(continueOnCapturedContext: false);; if (response != null) { return(response); } if (redirectState.NewUrl == null) { throw new InvalidOperationException("Request redirected but no new URI specified"); } request.Method = redirectState.Method; } }
bool HandleRedirect(HttpStatusCode redirectCode, HttpURLConnection httpConnection, RequestRedirectionState redirectState, out bool disposeRet) { if (!AllowAutoRedirect) { disposeRet = false; return(true); // We shouldn't follow and there's no data to fetch, just return } disposeRet = true; redirectState.NewUrl = null; switch (redirectCode) { case HttpStatusCode.MultipleChoices: // 300 break; case HttpStatusCode.Moved: // 301 case HttpStatusCode.Redirect: // 302 case HttpStatusCode.SeeOther: // 303 redirectState.Method = HttpMethod.Get; break; case HttpStatusCode.TemporaryRedirect: // 307 break; default: if ((int)redirectCode >= 300 && (int)redirectCode < 400) { throw new InvalidOperationException($"HTTP Redirection status code {redirectCode} ({(int)redirectCode}) not supported"); } return(false); } IDictionary <string, IList <string> > headers = httpConnection.HeaderFields; IList <string> locationHeader; if (!headers.TryGetValue("Location", out locationHeader) || locationHeader == null || locationHeader.Count == 0) { throw new InvalidOperationException($"HTTP connection redirected with code {redirectCode} ({(int)redirectCode}) but no Location header found in response"); } if (locationHeader.Count > 1 && Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"More than one location header for HTTP {redirectCode} redirect. Will use the first one."); } redirectState.RedirectCounter++; if (redirectState.RedirectCounter >= MaxAutomaticRedirections) { throw new WebException($"Maximum automatic redirections exceeded (allowed {MaxAutomaticRedirections}, redirected {redirectState.RedirectCounter} times)"); } Uri location = new Uri(locationHeader [0], UriKind.Absolute); redirectState.NewUrl = location; if (Logger.LogNet) { Logger.Log(LogLevel.Debug, LOG_APP, $"Request redirected to {location}"); } return(true); }
async Task <HttpResponseMessage> DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"{this}.DoProcessRequest ()"); } try { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $" connecting"); } await httpConnection.ConnectAsync().ConfigureAwait(false); if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $" connected"); } } catch (Java.Net.ConnectException ex) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Connection exception {ex}"); } // Wrap it nicely in a "standard" exception so that it's compatible with HttpClientHandler throw new WebException(ex.Message, ex, WebExceptionStatus.ConnectFailure, null); } if (httpConnection.DoOutput) { await request.Content.CopyToAsync(httpConnection.OutputStream).ConfigureAwait(false); } var statusCode = (HttpStatusCode)httpConnection.ResponseCode; var connectionUri = new Uri(httpConnection.URL.ToString()); // If the request was redirected we need to put the new URL in the request request.RequestUri = connectionUri; var ret = new AndroidHttpResponseMessage(javaUrl, httpConnection) { RequestMessage = request, ReasonPhrase = httpConnection.ResponseMessage, StatusCode = statusCode, }; if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Status code: {statusCode}"); } bool disposeRet; if (HandleRedirect(statusCode, httpConnection, redirectState, out disposeRet)) { if (disposeRet) { ret.Dispose(); ret = null; } return(ret); } switch (statusCode) { case HttpStatusCode.Unauthorized: case HttpStatusCode.ProxyAuthenticationRequired: // We don't resend the request since that would require new set of credentials if the // ones provided in Credentials are invalid (or null) and that, in turn, may require asking the // user which is not something that should be taken care of by us and in this // context. The application should be responsible for this. // HttpClientHandler throws an exception in this instance, but I think it's not a good // idea. We'll return the response message with all the information required by the // application to fill in the blanks and provide the requested credentials instead. // // We should return the body of the response too but, alas, the Java client will throw // a, wait for it, FileNotFound exception if we attempt to access the input stream. So // no body, just a dummy. Java FTW! ret.Content = new StringContent("Unauthorized", Encoding.ASCII); CopyHeaders(httpConnection, ret); if (ret.Headers.WwwAuthenticate != null) { ProxyAuthenticationRequested = false; CollectAuthInfo(ret.Headers.WwwAuthenticate); } else if (ret.Headers.ProxyAuthenticate != null) { ProxyAuthenticationRequested = true; CollectAuthInfo(ret.Headers.ProxyAuthenticate); } ret.RequestedAuthentication = RequestedAuthentication; return(ret); } if (!IsErrorStatusCode(statusCode)) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Reading..."); } Stream inputStream = new BufferedStream(httpConnection.InputStream); if (decompress_here) { string[] encodings = httpConnection.ContentEncoding?.Split(','); if (encodings != null) { if (encodings.Contains(GZIP_ENCODING, StringComparer.OrdinalIgnoreCase)) { inputStream = new GZipStream(inputStream, CompressionMode.Decompress); } else if (encodings.Contains(DEFLATE_ENCODING, StringComparer.OrdinalIgnoreCase)) { inputStream = new DeflateStream(inputStream, CompressionMode.Decompress); } } } ret.Content = new StreamContent(inputStream); } else { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Status code is {statusCode}, returning empty content"); } // For 400 >= response code <= 599 the Java client throws the FileNotFound exeption when attempting to read from the connection // Client tests require we return no content here ret.Content = new StringContent(String.Empty, Encoding.ASCII); } CopyHeaders(httpConnection, ret); IEnumerable <string> cookieHeaderValue; if (!UseCookies || CookieContainer == null || !ret.Headers.TryGetValues("Set-Cookie", out cookieHeaderValue) || cookieHeaderValue == null) { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"No cookies"); } return(ret); } try { if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Parsing cookies"); } CookieContainer.SetCookies(connectionUri, String.Join(",", cookieHeaderValue)); } catch (Exception ex) { // We don't want to terminate the response because of a bad cookie, hence just reporting // the issue. We might consider adding a virtual method to let the user handle the // issue, but not sure if it's really needed. Set-Cookie header will be part of the // header collection so the user can always examine it if they spot an error. if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Failed to parse cookies in the server response. {ex.GetType ()}: {ex.Message}"); } } if (Logger.LogNet) { Logger.Log(LogLevel.Info, LOG_APP, $"Returning"); } return(ret); }