diff --git a/src/NzbDrone.Api/Config/HostConfigResource.cs b/src/NzbDrone.Api/Config/HostConfigResource.cs index 7bdc6a735..a100556ff 100644 --- a/src/NzbDrone.Api/Config/HostConfigResource.cs +++ b/src/NzbDrone.Api/Config/HostConfigResource.cs @@ -2,8 +2,7 @@ using NzbDrone.Api.REST; using NzbDrone.Core.Authentication; using NzbDrone.Core.Update; -using NzbDrone.Core.Http; -using NzbDrone.Common.Http; +using NzbDrone.Common.Http.Proxy; namespace NzbDrone.Api.Config { @@ -33,7 +32,7 @@ namespace NzbDrone.Api.Config public int ProxyPort { get; set; } public string ProxyUsername { get; set; } public string ProxyPassword { get; set; } - public string ProxySubnetFilter { get; set; } + public string ProxyBypassFilter { get; set; } public bool ProxyBypassLocalAddresses { get; set; } } } diff --git a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs index 2dfca280c..5ea03c912 100644 --- a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs +++ b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs @@ -11,6 +11,7 @@ using NUnit.Framework; using NzbDrone.Common.Cache; using NzbDrone.Common.Http; using NzbDrone.Common.Http.Dispatchers; +using NzbDrone.Common.Http.Proxy; using NzbDrone.Common.TPL; using NzbDrone.Test.Common; using NzbDrone.Test.Common.Categories; @@ -26,9 +27,14 @@ namespace NzbDrone.Common.Test.Http public void SetUp() { Mocker.SetConstant(Mocker.Resolve()); + Mocker.SetConstant(Mocker.Resolve()); Mocker.SetConstant(Mocker.Resolve()); Mocker.SetConstant>(new IHttpRequestInterceptor[0]); Mocker.SetConstant(Mocker.Resolve()); + + //Mocker.GetMock() + // .Setup(v => v.GetProxySettings(It.IsAny())) + // .Returns(new HttpProxySettings(ProxyType.Socks5, "127.0.0.1", 5476, "", false)); } [Test] diff --git a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs index 9c6118270..72f0cc30f 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs @@ -4,6 +4,7 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Net; +using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; @@ -11,8 +12,7 @@ using CurlSharp; using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; -using NzbDrone.Common.Instrumentation; -using System.Reflection; +using NzbDrone.Common.Http.Proxy; namespace NzbDrone.Common.Http.Dispatchers { diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index 5d2c1a0f7..092ac8d64 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -4,20 +4,20 @@ using NzbDrone.Common.Extensions; using com.LandonKey.SocksWebProxy.Proxy; using com.LandonKey.SocksWebProxy; using System.Net.Sockets; +using System.Linq; +using NzbDrone.Common.Http.Proxy; namespace NzbDrone.Common.Http.Dispatchers { public class ManagedHttpDispatcher : IHttpDispatcher { private readonly IHttpProxySettingsProvider _proxySettingsProvider; + private readonly ICreateManagedWebProxy _createManagedWebProxy; - private readonly ICached _webProxyCache; - - public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, ICacheManager cacheManager) + public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, ICreateManagedWebProxy createManagedWebProxy) { _proxySettingsProvider = proxySettingsProvider; - - _webProxyCache = cacheManager.GetCache(GetType(), "webProxy"); + _createManagedWebProxy = createManagedWebProxy; } public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies) @@ -90,45 +90,10 @@ namespace NzbDrone.Common.Http.Dispatchers var proxySettings = _proxySettingsProvider.GetProxySettings(request); if (proxySettings != null) { - webRequest.Proxy = _webProxyCache.Get(proxySettings.Key, () => CreateWebProxy(proxySettings), TimeSpan.FromMinutes(5)); - } - - _webProxyCache.ClearExpired(); - } - - private IWebProxy CreateWebProxy(HttpRequestProxySettings proxySettings) - { - var addresses = Dns.GetHostAddresses(proxySettings.Host); - - if(addresses.Length > 1) - { - var ipv4Only = addresses.Where(a => a.AddressFamily == AddressFamily.InterNetwork); - if (ipv4Only.Any()) - { - addresses = ipv4Only.ToArray(); - } - } - - switch (proxySettings.Type) - { - case ProxyType.Http: - if (proxySettings.Username.IsNotNullOrWhiteSpace() && proxySettings.Password.IsNotNullOrWhiteSpace()) - { - return new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.SubnetFilterAsArray, new NetworkCredential(proxySettings.Username, proxySettings.Password)); - } - else - { - return new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.SubnetFilterAsArray); - } - case ProxyType.Socks4: - return new SocksWebProxy(new ProxyConfig(IPAddress.Loopback, GetNextFreePort(), addresses[0], proxySettings.Port, ProxyConfig.SocksVersion.Four, proxySettings.Username, proxySettings.Password), false); - case ProxyType.Socks5: - return new SocksWebProxy(new ProxyConfig(IPAddress.Loopback, GetNextFreePort(), addresses[0], proxySettings.Port, ProxyConfig.SocksVersion.Five, proxySettings.Username, proxySettings.Password), false); + webRequest.Proxy = _createManagedWebProxy.GetWebProxy(proxySettings); } - - return null; } - + protected virtual void AddRequestHeaders(HttpWebRequest webRequest, HttpHeader headers) { foreach (var header in headers) @@ -177,15 +142,5 @@ namespace NzbDrone.Common.Http.Dispatchers } } } - - private static int GetNextFreePort() - { - var listener = new TcpListener(IPAddress.Loopback, 0); - listener.Start(); - var port = ((IPEndPoint)listener.LocalEndpoint).Port; - listener.Stop(); - - return port; - } } } diff --git a/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs b/src/NzbDrone.Common/Http/Proxy/HttpProxySettings.cs similarity index 85% rename from src/NzbDrone.Common/Http/HttpRequestProxySettings.cs rename to src/NzbDrone.Common/Http/Proxy/HttpProxySettings.cs index cbbff60bb..0c2e3ce3d 100644 --- a/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs +++ b/src/NzbDrone.Common/Http/Proxy/HttpProxySettings.cs @@ -2,11 +2,11 @@ using System.Net; using NzbDrone.Common.Extensions; -namespace NzbDrone.Common.Http +namespace NzbDrone.Common.Http.Proxy { - public class HttpRequestProxySettings + public class HttpProxySettings { - public HttpRequestProxySettings(ProxyType type, string host, int port, string bypassFilter, bool bypassLocalAddress, string username = null, string password = null) + public HttpProxySettings(ProxyType type, string host, int port, string bypassFilter, bool bypassLocalAddress, string username = null, string password = null) { Type = type; Host = host.IsNullOrWhiteSpace() ? "127.0.0.1" : host; diff --git a/src/NzbDrone.Common/Http/IHttpProxySettingsProvider.cs b/src/NzbDrone.Common/Http/Proxy/IHttpProxySettingsProvider.cs similarity index 56% rename from src/NzbDrone.Common/Http/IHttpProxySettingsProvider.cs rename to src/NzbDrone.Common/Http/Proxy/IHttpProxySettingsProvider.cs index 73fdd5fda..39ecbbbf0 100644 --- a/src/NzbDrone.Common/Http/IHttpProxySettingsProvider.cs +++ b/src/NzbDrone.Common/Http/Proxy/IHttpProxySettingsProvider.cs @@ -2,10 +2,10 @@ using System; using System.Collections.Generic; using System.Linq; -namespace NzbDrone.Common.Http +namespace NzbDrone.Common.Http.Proxy { public interface IHttpProxySettingsProvider { - HttpRequestProxySettings GetProxySettings(HttpRequest request); + HttpProxySettings GetProxySettings(HttpRequest request); } } diff --git a/src/NzbDrone.Common/Http/Proxy/ManagedWebProxyFactory.cs b/src/NzbDrone.Common/Http/Proxy/ManagedWebProxyFactory.cs new file mode 100644 index 000000000..a2992131d --- /dev/null +++ b/src/NzbDrone.Common/Http/Proxy/ManagedWebProxyFactory.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using com.LandonKey.SocksWebProxy; +using com.LandonKey.SocksWebProxy.Proxy; +using NzbDrone.Common.Cache; +using NzbDrone.Common.Extensions; + +namespace NzbDrone.Common.Http.Proxy +{ + public interface ICreateManagedWebProxy + { + IWebProxy GetWebProxy(HttpProxySettings proxySettings); + } + + public class ManagedWebProxyFactory : ICreateManagedWebProxy + { + private readonly ICached _webProxyCache; + + public ManagedWebProxyFactory(ICacheManager cacheManager) + { + _webProxyCache = cacheManager.GetCache(GetType(), "webProxy"); + } + + public IWebProxy GetWebProxy(HttpProxySettings proxySettings) + { + var proxy = _webProxyCache.Get(proxySettings.Key, () => CreateWebProxy(proxySettings), TimeSpan.FromMinutes(5)); + + _webProxyCache.ClearExpired(); + + return proxy; + } + private IWebProxy CreateWebProxy(HttpProxySettings proxySettings) + { + switch (proxySettings.Type) + { + case ProxyType.Http: + if (proxySettings.Username.IsNotNullOrWhiteSpace() && proxySettings.Password.IsNotNullOrWhiteSpace()) + { + return new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.SubnetFilterAsArray, new NetworkCredential(proxySettings.Username, proxySettings.Password)); + } + else + { + return new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.SubnetFilterAsArray); + } + case ProxyType.Socks4: + return new SocksWebProxy(new ProxyConfig(IPAddress.Loopback, GetNextFreePort(), GetProxyIpAddress(proxySettings.Host), proxySettings.Port, ProxyConfig.SocksVersion.Four, proxySettings.Username, proxySettings.Password), false); + case ProxyType.Socks5: + return new SocksWebProxy(new ProxyConfig(IPAddress.Loopback, GetNextFreePort(), GetProxyIpAddress(proxySettings.Host), proxySettings.Port, ProxyConfig.SocksVersion.Five, proxySettings.Username, proxySettings.Password), false); + } + + return null; + } + + private static IPAddress GetProxyIpAddress(string host) + { + IPAddress ipAddress; + if (!IPAddress.TryParse(host, out ipAddress)) + { + try + { + ipAddress = Dns.GetHostEntry(host).AddressList.OrderByDescending(a => a.AddressFamily == AddressFamily.InterNetwork).First(); + } + catch (Exception e) + { + throw new InvalidOperationException(string.Format("Unable to resolve proxy hostname '{0}' to a valid IP address.", host), e); + } + } + + return ipAddress; + } + + private static int GetNextFreePort() + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + + return port; + } + } +} diff --git a/src/NzbDrone.Common/Http/ProxyType.cs b/src/NzbDrone.Common/Http/Proxy/ProxyType.cs similarity index 82% rename from src/NzbDrone.Common/Http/ProxyType.cs rename to src/NzbDrone.Common/Http/Proxy/ProxyType.cs index a415fc749..19f3ea38e 100644 --- a/src/NzbDrone.Common/Http/ProxyType.cs +++ b/src/NzbDrone.Common/Http/Proxy/ProxyType.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; -namespace NzbDrone.Common.Http +namespace NzbDrone.Common.Http.Proxy { public enum ProxyType { diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index c335256f5..87583e16d 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -157,6 +157,7 @@ + Component @@ -168,10 +169,10 @@ - + - + @@ -180,7 +181,7 @@ - + diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 5939bb99f..553838528 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -7,8 +7,7 @@ using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Http; -using NzbDrone.Common.Http; +using NzbDrone.Common.Http.Proxy; namespace NzbDrone.Core.Configuration { @@ -344,7 +343,7 @@ namespace NzbDrone.Core.Configuration get { return GetValue("ProxyPassword", string.Empty); } } - public string ProxySubnetFilter + public string ProxyBypassFilter { get { return GetValue("ProxySubnetFilter", string.Empty); } } diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index 334f8c31f..891b0c494 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Http; using NzbDrone.Common.Http; +using NzbDrone.Common.Http.Proxy; namespace NzbDrone.Core.Configuration { @@ -74,7 +75,7 @@ namespace NzbDrone.Core.Configuration int ProxyPort { get; } string ProxyUsername { get; } string ProxyPassword { get; } - string ProxySubnetFilter { get; } + string ProxyBypassFilter { get; } bool ProxyBypassLocalAddresses { get; } } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs index be688f360..e9d2405cb 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; +using NzbDrone.Common.Cloud; namespace NzbDrone.Core.HealthCheck.Checks { @@ -15,11 +16,15 @@ namespace NzbDrone.Core.HealthCheck.Checks private readonly IConfigService _configService; private readonly IHttpClient _client; - public ProxyCheck(IConfigService configService, IHttpClient client, Logger logger) + private readonly IHttpRequestBuilderFactory _cloudRequestBuilder; + + public ProxyCheck(ISonarrCloudRequestBuilder cloudRequestBuilder, IConfigService configService, IHttpClient client, Logger logger) { _configService = configService; _client = client; _logger = logger; + + _cloudRequestBuilder = cloudRequestBuilder.Services; } public override HealthCheck Check() @@ -29,10 +34,12 @@ namespace NzbDrone.Core.HealthCheck.Checks var addresses = Dns.GetHostAddresses(_configService.ProxyHostname); if(!addresses.Any()) { - return new HealthCheck(GetType(), HealthCheckResult.Error, "Failed to resolve the IP Address for the Configured Proxy Host: " + _configService.ProxyHostname); + return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format("Failed to resolve the IP Address for the Configured Proxy Host {0}", _configService.ProxyHostname)); } - var request = new HttpRequestBuilder("https://services.sonarr.tv/ping").Build(); + var request = _cloudRequestBuilder.Create() + .Resource("/ping") + .Build(); try { @@ -41,14 +48,14 @@ namespace NzbDrone.Core.HealthCheck.Checks // We only care about 400 responses, other error codes can be ignored if (response.StatusCode == HttpStatusCode.BadRequest) { - _logger.Error("Proxy Health Check failed: {0}. Response Data: {1} ", response.StatusCode.ToString(), response.ResponseData); - return new HealthCheck(GetType(), HealthCheckResult.Error, "Failed to load https://sonarr.tv/, got HTTP " + response.StatusCode.ToString()); + _logger.Error("Proxy Health Check failed: {0}", response.StatusCode); + return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format("Failed to test proxy: StatusCode {1}", request.Url, response.StatusCode)); } } catch (Exception ex) { - _logger.ErrorException("Proxy Health Check failed.", ex); - return new HealthCheck(GetType(), HealthCheckResult.Error, "An exception occured while trying to load https://sonarr.tv/: " + ex.Message); + _logger.Error(ex, "Proxy Health Check failed: {0}", ex.Message); + return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format("Failed to test proxy: {1}", request.Url, ex.Message)); } } diff --git a/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs b/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs index 511b32948..0ca11bf12 100644 --- a/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs +++ b/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs @@ -1,11 +1,8 @@ -using NzbDrone.Common.Http; -using NzbDrone.Core.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System; using System.Net; -using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Common.Http.Proxy; +using NzbDrone.Core.Configuration; namespace NzbDrone.Core.Http { @@ -18,17 +15,17 @@ namespace NzbDrone.Core.Http _configService = configService; } - public HttpRequestProxySettings GetProxySettings(HttpRequest request) + public HttpProxySettings GetProxySettings(HttpRequest request) { if (!_configService.ProxyEnabled) { return null; } - var proxySettings = new HttpRequestProxySettings(_configService.ProxyType, + var proxySettings = new HttpProxySettings(_configService.ProxyType, _configService.ProxyHostname, _configService.ProxyPort, - _configService.ProxySubnetFilter, + _configService.ProxyBypassFilter, _configService.ProxyBypassLocalAddresses, _configService.ProxyUsername, _configService.ProxyPassword); @@ -41,7 +38,7 @@ namespace NzbDrone.Core.Http return proxySettings; } - public bool ShouldProxyBeBypassed(HttpRequestProxySettings proxySettings, HttpUri url) + public bool ShouldProxyBeBypassed(HttpProxySettings proxySettings, HttpUri url) { //We are utilising the WebProxy implementation here to save us having to reimplement it. This way we use Microsofts implementation var proxy = new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.SubnetFilterAsArray); diff --git a/src/UI/Settings/General/GeneralViewTemplate.hbs b/src/UI/Settings/General/GeneralViewTemplate.hbs index 2dce9a2da..99b5930bc 100644 --- a/src/UI/Settings/General/GeneralViewTemplate.hbs +++ b/src/UI/Settings/General/GeneralViewTemplate.hbs @@ -246,12 +246,12 @@
- +
- +