diff --git a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs index 094c71fab..569960b4a 100644 --- a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs +++ b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs @@ -54,7 +54,7 @@ namespace NzbDrone.Common.Test.Http response.Content.Should().NotBeNullOrWhiteSpace(); } - + [Test] public void should_execute_https_get() { @@ -137,6 +137,33 @@ namespace NzbDrone.Common.Test.Http ExceptionVerification.ExpectedErrors(0); } + [Test] + public void should_follow_redirects_to_https() + { + var request = new HttpRequestBuilder($"http://{_httpBinHost}/redirect-to") + .AddQueryParam("url", $"https://sonarr.tv/") + .Build(); + request.AllowAutoRedirect = true; + + var response = Subject.Get(request); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Content.Should().Contain("Sonarr"); + + ExceptionVerification.ExpectedErrors(0); + } + + [Test] + public void should_throw_on_too_many_redirects() + { + var request = new HttpRequest($"http://{_httpBinHost}/redirect/4"); + request.AllowAutoRedirect = true; + + Assert.Throws(() => Subject.Get(request)); + + ExceptionVerification.ExpectedErrors(0); + } + [Test] public void should_send_user_agent() { @@ -400,4 +427,4 @@ namespace NzbDrone.Common.Test.Http public string Url { get; set; } public string Data { get; set; } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs index 2c456536c..43414836d 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs @@ -104,8 +104,8 @@ namespace NzbDrone.Common.Http.Dispatchers default: throw new NotSupportedException(string.Format("HttpCurl method {0} not supported", request.Method)); } + curlEasy.FollowLocation = false; curlEasy.UserAgent = request.UseSimplifiedUserAgent ? UserAgentBuilder.UserAgentSimplified : UserAgentBuilder.UserAgent; ; - curlEasy.FollowLocation = request.AllowAutoRedirect; if (request.RequestTimeout != TimeSpan.Zero) { diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index e7713a607..6d4e6b6b3 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -30,7 +30,7 @@ namespace NzbDrone.Common.Http.Dispatchers webRequest.Method = request.Method.ToString(); webRequest.UserAgent = request.UseSimplifiedUserAgent ? UserAgentBuilder.UserAgentSimplified : UserAgentBuilder.UserAgent; webRequest.KeepAlive = request.ConnectionKeepAlive; - webRequest.AllowAutoRedirect = request.AllowAutoRedirect; + webRequest.AllowAutoRedirect = false; webRequest.CookieContainer = cookies; if (request.RequestTimeout != TimeSpan.Zero) diff --git a/src/NzbDrone.Common/Http/HttpClient.cs b/src/NzbDrone.Common/Http/HttpClient.cs index 3b81a8298..8c16ef9b3 100644 --- a/src/NzbDrone.Common/Http/HttpClient.cs +++ b/src/NzbDrone.Common/Http/HttpClient.cs @@ -7,6 +7,7 @@ using System.Net; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Extensions; using NzbDrone.Common.Http.Dispatchers; using NzbDrone.Common.TPL; @@ -43,6 +44,57 @@ namespace NzbDrone.Common.Http } public HttpResponse Execute(HttpRequest request) + { + var autoRedirectCount = 0; + var autoRedirectChain = new List(); + autoRedirectChain.Add(request.Url.ToString()); + + var response = ExecuteRequest(request); + + while (response.StatusCode == HttpStatusCode.Moved || + response.StatusCode == HttpStatusCode.MovedPermanently || + response.StatusCode == HttpStatusCode.Found) + { + if (request.AllowAutoRedirect) + { + request.Url += new HttpUri(response.Headers.GetSingleValue("Location")); + autoRedirectChain.Add(request.Url.ToString()); + + _logger.Trace("Redirected to {0}", request.Url); + + autoRedirectCount++; + if (autoRedirectCount > 3) + { + throw new WebException($"Too many automatic redirections were attempted for {string.Join(" -> ", autoRedirectChain)}", WebExceptionStatus.ProtocolError); + } + + response = ExecuteRequest(request); + } + else if (!RuntimeInfoBase.IsProduction) + { + _logger.Error("Server requested a redirect to [{0}]. Update the request URL to avoid this redirect.", response.Headers["Location"]); + break; + } + } + + if (!request.SuppressHttpError && response.HasHttpError) + { + _logger.Warn("HTTP Error - {0}", response); + + if ((int)response.StatusCode == 429) + { + throw new TooManyRequestsException(request, response); + } + else + { + throw new HttpException(request, response); + } + } + + return response; + } + + private HttpResponse ExecuteRequest(HttpRequest request) { foreach (var interceptor in _requestInterceptors) { @@ -78,28 +130,6 @@ namespace NzbDrone.Common.Http _logger.Trace("Response content ({0} bytes): {1}", response.ResponseData.Length, response.Content); } - if (!RuntimeInfoBase.IsProduction && - (response.StatusCode == HttpStatusCode.Moved || - response.StatusCode == HttpStatusCode.MovedPermanently || - response.StatusCode == HttpStatusCode.Found)) - { - _logger.Error("Server requested a redirect to [" + response.Headers["Location"] + "]. Update the request URL to avoid this redirect."); - } - - if (!request.SuppressHttpError && response.HasHttpError) - { - _logger.Warn("HTTP Error - {0}", response); - - if ((int)response.StatusCode == 429) - { - throw new TooManyRequestsException(request, response); - } - else - { - throw new HttpException(request, response); - } - } - return response; } @@ -210,4 +240,4 @@ namespace NzbDrone.Common.Http return new HttpResponse(response); } } -} \ No newline at end of file +}