From bfe134ee54c17a2593eb5fd7a312123c1d79635f Mon Sep 17 00:00:00 2001 From: Lloyd Sparkes Date: Tue, 2 Feb 2016 18:42:22 +0000 Subject: [PATCH 01/11] Proxy Support for Sonarr #732 Switch to use Port #0 which just uses the next free port Non Functional - Code Cleanup Tabs -> Spaces, Opps Deleted too much Refactoring & Code Cleanup & Move Config to DB remove unneeded line Clean Up Spaces Code Review Points on the UI FIx extra space Clean Up unrequired changes Add a HealthCheck. Extra Check in ProxyCheck. Correctly deal with Socks Authentication Remove SubModule Add in Nuget + Tweak ProxyCheck Code Review Points Missed Review Point Add Subnet Filtering, Add ProxyBypass for local addresses. UI updated for property changes. Fix typo, and copy&paste error Tweak URL to hit --- src/NzbDrone.Api/Config/HostConfigModule.cs | 7 +- src/NzbDrone.Api/Config/HostConfigResource.cs | 10 ++ .../Http/Dispatchers/CurlHttpDispatcher.cs | 25 ++++ .../Http/Dispatchers/ManagedHttpDispatcher.cs | 40 +++++++ src/NzbDrone.Common/Http/HttpRequest.cs | 3 +- .../Http/HttpRequestProxySettings.cs | 45 ++++++++ src/NzbDrone.Common/Http/ProxyType.cs | 14 +++ src/NzbDrone.Common/NzbDrone.Common.csproj | 8 ++ src/NzbDrone.Common/packages.config | 1 + .../Configuration/ConfigFileProvider.cs | 1 - .../Configuration/ConfigService.cs | 42 +++++++ .../Configuration/IConfigService.cs | 12 ++ .../HealthCheck/Checks/ProxyCheck.cs | 58 ++++++++++ .../Http/ProxyHttpInterceptor.cs | 40 +++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 2 + src/UI/Settings/General/GeneralView.js | 18 ++- .../Settings/General/GeneralViewTemplate.hbs | 108 ++++++++++++++++++ 17 files changed, 429 insertions(+), 5 deletions(-) create mode 100644 src/NzbDrone.Common/Http/HttpRequestProxySettings.cs create mode 100644 src/NzbDrone.Common/Http/ProxyType.cs create mode 100644 src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs create mode 100644 src/NzbDrone.Core/Http/ProxyHttpInterceptor.cs diff --git a/src/NzbDrone.Api/Config/HostConfigModule.cs b/src/NzbDrone.Api/Config/HostConfigModule.cs index 9326cf0db..6be688c69 100644 --- a/src/NzbDrone.Api/Config/HostConfigModule.cs +++ b/src/NzbDrone.Api/Config/HostConfigModule.cs @@ -15,12 +15,14 @@ namespace NzbDrone.Api.Config public class HostConfigModule : NzbDroneRestModule { private readonly IConfigFileProvider _configFileProvider; + private readonly IConfigService _configService; private readonly IUserService _userService; - public HostConfigModule(IConfigFileProvider configFileProvider, IUserService userService) + public HostConfigModule(IConfigFileProvider configFileProvider, IConfigService configService, IUserService userService) : base("/config/host") { _configFileProvider = configFileProvider; + _configService = configService; _userService = userService; GetResourceSingle = GetHostConfig; @@ -49,7 +51,7 @@ namespace NzbDrone.Api.Config private HostConfigResource GetHostConfig() { var resource = new HostConfigResource(); - resource.InjectFrom(_configFileProvider); + resource.InjectFrom(_configFileProvider, _configService); resource.Id = 1; var user = _userService.FindUser(); @@ -75,6 +77,7 @@ namespace NzbDrone.Api.Config .ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null)); _configFileProvider.SaveConfigDictionary(dictionary); + _configService.SaveConfigDictionary(dictionary); if (resource.Username.IsNotNullOrWhiteSpace() && resource.Password.IsNotNullOrWhiteSpace()) { diff --git a/src/NzbDrone.Api/Config/HostConfigResource.cs b/src/NzbDrone.Api/Config/HostConfigResource.cs index 247f46b15..7bdc6a735 100644 --- a/src/NzbDrone.Api/Config/HostConfigResource.cs +++ b/src/NzbDrone.Api/Config/HostConfigResource.cs @@ -2,6 +2,8 @@ using NzbDrone.Api.REST; using NzbDrone.Core.Authentication; using NzbDrone.Core.Update; +using NzbDrone.Core.Http; +using NzbDrone.Common.Http; namespace NzbDrone.Api.Config { @@ -25,5 +27,13 @@ namespace NzbDrone.Api.Config public bool UpdateAutomatically { get; set; } public UpdateMechanism UpdateMechanism { get; set; } public string UpdateScriptPath { get; set; } + public bool ProxyEnabled { get; set; } + public ProxyType ProxyType { get; set; } + public string ProxyHostname { get; set; } + public int ProxyPort { get; set; } + public string ProxyUsername { get; set; } + public string ProxyPassword { get; set; } + public string ProxySubnetFilter { get; set; } + public bool ProxyBypassLocalAddresses { get; set; } } } diff --git a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs index 9ea8d1a80..0a06d64d1 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs @@ -76,7 +76,32 @@ namespace NzbDrone.Common.Http.Dispatchers return s * n; }; + if(request.Proxy != null && !request.Proxy.ShouldProxyBeBypassed(request.Url)) + + { + switch (request.Proxy.Type) + { + case ProxyType.Http: + curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Http); + curlEasy.SetOpt(CurlOption.ProxyAuth, CurlHttpAuth.Basic); + curlEasy.SetOpt(CurlOption.ProxyUserPwd, request.Proxy.Username + ":" + request.Proxy.Password.ToString()); + break; + case ProxyType.Socks4: + curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Socks4); + curlEasy.SetOpt(CurlOption.ProxyUsername, request.Proxy.Username); + curlEasy.SetOpt(CurlOption.ProxyPassword, request.Proxy.Password); + break; + case ProxyType.Socks5: + curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Socks5); + curlEasy.SetOpt(CurlOption.ProxyUsername, request.Proxy.Username); + curlEasy.SetOpt(CurlOption.ProxyPassword, request.Proxy.Password); + break; + } + curlEasy.SetOpt(CurlOption.Proxy, request.Proxy.Host + ":" + request.Proxy.Port.ToString()); + } + curlEasy.Url = request.Url.FullUri; + switch (request.Method) { case HttpMethod.GET: diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index 1ffbfd35a..8c6e55633 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -1,6 +1,9 @@ using System; using System.Net; using NzbDrone.Common.Extensions; +using com.LandonKey.SocksWebProxy.Proxy; +using com.LandonKey.SocksWebProxy; +using System.Net.Sockets; namespace NzbDrone.Common.Http.Dispatchers { @@ -26,6 +29,33 @@ namespace NzbDrone.Common.Http.Dispatchers webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds); } + if (request.Proxy != null && !request.Proxy.ShouldProxyBeBypassed(request.Url)) + { + var addresses = Dns.GetHostAddresses(request.Proxy.Host); + var socksUsername = request.Proxy.Username == null ? string.Empty : request.Proxy.Username; + var socksPassword = request.Proxy.Password == null ? string.Empty : request.Proxy.Password; + + switch (request.Proxy.Type) + { + case ProxyType.Http: + if(request.Proxy.Username.IsNotNullOrWhiteSpace() && request.Proxy.Password.IsNotNullOrWhiteSpace()) + { + webRequest.Proxy = new WebProxy(request.Proxy.Host + ":" + request.Proxy.Port, request.Proxy.BypassLocalAddress, request.Proxy.SubnetFilterAsArray, new NetworkCredential(request.Proxy.Username, request.Proxy.Password)); + } + else + { + webRequest.Proxy = new WebProxy(request.Proxy.Host + ":" + request.Proxy.Port, request.Proxy.BypassLocalAddress, request.Proxy.SubnetFilterAsArray); + } + break; + case ProxyType.Socks4: + webRequest.Proxy = new SocksWebProxy(new ProxyConfig(IPAddress.Parse("127.0.0.1"), GetNextFreePort(), addresses[0], request.Proxy.Port, ProxyConfig.SocksVersion.Four, socksUsername, socksPassword), false); + break; + case ProxyType.Socks5: + webRequest.Proxy = new SocksWebProxy(new ProxyConfig(IPAddress.Parse("127.0.0.1"), GetNextFreePort(), addresses[0], request.Proxy.Port, ProxyConfig.SocksVersion.Five, socksUsername, socksPassword), false); + break; + } + } + if (request.Headers != null) { AddRequestHeaders(webRequest, request.Headers); @@ -117,5 +147,15 @@ 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/HttpRequest.cs b/src/NzbDrone.Common/Http/HttpRequest.cs index bf2fe3568..966a3b534 100644 --- a/src/NzbDrone.Common/Http/HttpRequest.cs +++ b/src/NzbDrone.Common/Http/HttpRequest.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Common.Http Headers = new HttpHeader(); AllowAutoRedirect = true; Cookies = new Dictionary(); - + if (!RuntimeInfoBase.IsProduction) { AllowAutoRedirect = false; @@ -41,6 +41,7 @@ namespace NzbDrone.Common.Http public bool StoreResponseCookie { get; set; } public TimeSpan RequestTimeout { get; set; } public TimeSpan RateLimit { get; set; } + public HttpRequestProxySettings Proxy {get; set;} public override string ToString() { diff --git a/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs b/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs new file mode 100644 index 000000000..ba533eb56 --- /dev/null +++ b/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs @@ -0,0 +1,45 @@ +using System; +using System.Net; + +namespace NzbDrone.Common.Http +{ + public class HttpRequestProxySettings + { + public HttpRequestProxySettings(ProxyType type, string host, int port, string filterSubnet, bool bypassLocalAddress, string username = null, string password = null) + { + Type = type; + Host = host; + Port = port; + Username = username; + Password = password; + } + + public ProxyType Type { get; private set; } + public string Host { get; private set; } + public int Port { get; private set; } + public string Username { get; private set; } + public string Password { get; private set; } + public string SubnetFilter { get; private set; } + public bool BypassLocalAddress { get; private set; } + + public string[] SubnetFilterAsArray + { + get + { + if (!string.IsNullOrWhiteSpace(SubnetFilter)) + { + return SubnetFilter.Split(';'); + } + return new string[] { }; + } + } + + public bool ShouldProxyBeBypassed(Uri url) + { + //We are utilising the WebProxy implementation here to save us having to reimplement it. This way we use Microsofts implementation + WebProxy proxy = new WebProxy(Host + ":" + Port, BypassLocalAddress, SubnetFilterAsArray); + + return proxy.IsBypassed(url); + } + } +} diff --git a/src/NzbDrone.Common/Http/ProxyType.cs b/src/NzbDrone.Common/Http/ProxyType.cs new file mode 100644 index 000000000..a415fc749 --- /dev/null +++ b/src/NzbDrone.Common/Http/ProxyType.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Common.Http +{ + public enum ProxyType + { + Http, + Socks4, + Socks5 + } +} diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index 8fc2c1e82..5fe24e16d 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -45,6 +45,12 @@ ..\packages\NLog.4.3.0-rc1\lib\net40\NLog.dll + + ..\packages\DotNet4.SocksProxy.1.0.0.0\lib\net40\Org.Mentalis.dll + True + + + ..\packages\DotNet4.SocksProxy.1.0.0.0\lib\net40\SocksWebProxy.dll True @@ -161,6 +167,7 @@ + @@ -171,6 +178,7 @@ + diff --git a/src/NzbDrone.Common/packages.config b/src/NzbDrone.Common/packages.config index 006141366..05011894d 100644 --- a/src/NzbDrone.Common/packages.config +++ b/src/NzbDrone.Common/packages.config @@ -1,5 +1,6 @@  + diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index b9c509e64..a2295ef75 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -16,7 +16,6 @@ using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Update; - namespace NzbDrone.Core.Configuration { public interface IConfigFileProvider : IHandleAsync, diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 1f94d5471..5939bb99f 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -7,6 +7,8 @@ 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; namespace NzbDrone.Core.Configuration { @@ -312,6 +314,46 @@ namespace NzbDrone.Core.Configuration get { return GetValue("HmacSalt", Guid.NewGuid().ToString(), true); } } + public bool ProxyEnabled + { + get { return GetValueBoolean("ProxyEnabled", false); } + } + + public ProxyType ProxyType + { + get { return GetValueEnum("ProxyType", ProxyType.Http); } + } + + public string ProxyHostname + { + get { return GetValue("ProxyHostname", string.Empty); } + } + + public int ProxyPort + { + get { return GetValueInt("ProxyPort", 8080); } + } + + public string ProxyUsername + { + get { return GetValue("ProxyUsername", string.Empty); } + } + + public string ProxyPassword + { + get { return GetValue("ProxyPassword", string.Empty); } + } + + public string ProxySubnetFilter + { + get { return GetValue("ProxySubnetFilter", string.Empty); } + } + + public bool ProxyBypassLocalAddresses + { + get { return GetValueBoolean("ProxyBypassLocalAddresses", true); } + } + private string GetValue(string key) { return GetValue(key, string.Empty); diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index b2f408595..334f8c31f 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Http; +using NzbDrone.Common.Http; namespace NzbDrone.Core.Configuration { @@ -64,5 +66,15 @@ namespace NzbDrone.Core.Configuration string HmacPassphrase { get; } string RijndaelSalt { get; } string HmacSalt { get; } + + //Proxy + bool ProxyEnabled { get; } + ProxyType ProxyType { get; } + string ProxyHostname { get; } + int ProxyPort { get; } + string ProxyUsername { get; } + string ProxyPassword { get; } + string ProxySubnetFilter { get; } + bool ProxyBypassLocalAddresses { get; } } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs new file mode 100644 index 000000000..ede53acf2 --- /dev/null +++ b/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs @@ -0,0 +1,58 @@ +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; + +namespace NzbDrone.Core.HealthCheck.Checks +{ + public class ProxyCheck : HealthCheckBase + { + private readonly Logger _logger; + private readonly IConfigService _configService; + private readonly IHttpClient _client; + + public ProxyCheck(IConfigService configService, IHttpClient client, Logger logger) + { + _configService = configService; + _client = client; + _logger = logger; + } + + public override HealthCheck Check() + { + if (_configService.ProxyEnabled) + { + var addresses = Dns.GetHostAddresses(_configService.ProxyHostname); + if(addresses.Length != 1) + { + return new HealthCheck(GetType(), HealthCheckResult.Error, "Failed to resolve the IP Address for the Configured Proxy Host: " + _configService.ProxyHostname); + } + + var request = new HttpRequestBuilder("https://services.sonarr.tv/").Build("/ping"); + + try + { + var response = _client.Execute(request); + + // 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()); + } + } + 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); + } + } + + return new HealthCheck(GetType()); + } + } +} diff --git a/src/NzbDrone.Core/Http/ProxyHttpInterceptor.cs b/src/NzbDrone.Core/Http/ProxyHttpInterceptor.cs new file mode 100644 index 000000000..1207e1281 --- /dev/null +++ b/src/NzbDrone.Core/Http/ProxyHttpInterceptor.cs @@ -0,0 +1,40 @@ +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Http +{ + public class ProxyHttpInterceptor : IHttpRequestInterceptor + { + private readonly IConfigService _configService; + + public ProxyHttpInterceptor(IConfigService configService) + { + this._configService = configService; + } + + public HttpResponse PostResponse(HttpResponse response) + { + return response; + } + + public HttpRequest PreRequest(HttpRequest request) + { + if(_configService.ProxyEnabled) + { + request.Proxy = new HttpRequestProxySettings(_configService.ProxyType, + _configService.ProxyHostname, + _configService.ProxyPort, + _configService.ProxySubnetFilter, + _configService.ProxyBypassLocalAddresses, + _configService.ProxyUsername, + _configService.ProxyPassword); + } + + return request; + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index bc0ffc2a4..920b002fd 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -487,6 +487,7 @@ + @@ -514,6 +515,7 @@ + diff --git a/src/UI/Settings/General/GeneralView.js b/src/UI/Settings/General/GeneralView.js index 57caf7545..81f638f34 100644 --- a/src/UI/Settings/General/GeneralView.js +++ b/src/UI/Settings/General/GeneralView.js @@ -11,6 +11,7 @@ var view = Marionette.ItemView.extend({ events : { 'change .x-auth' : '_setAuthOptionsVisibility', + 'change .x-proxy' : '_setProxyOptionsVisibility', 'change .x-ssl' : '_setSslOptionsVisibility', 'click .x-reset-api-key' : '_resetApiKey', 'change .x-update-mechanism' : '_setScriptGroupVisibility' @@ -25,7 +26,9 @@ var view = Marionette.ItemView.extend({ copyApiKey : '.x-copy-api-key', apiKeyInput : '.x-api-key', updateMechanism : '.x-update-mechanism', - scriptGroup : '.x-script-group' + scriptGroup : '.x-script-group', + proxyToggle : '.x-proxy', + proxyOptions : '.x-proxy-settings' }, initialize : function() { @@ -37,6 +40,10 @@ var view = Marionette.ItemView.extend({ this.ui.authOptions.hide(); } + if (!this.ui.proxyToggle.prop('checked')) { + this.ui.proxyOptions.hide(); + } + if (!this.ui.sslToggle.prop('checked')) { this.ui.sslOptions.hide(); } @@ -70,6 +77,15 @@ var view = Marionette.ItemView.extend({ } }, + _setProxyOptionsVisibility : function() { + if (this.ui.proxyToggle.prop('checked')) { + this.ui.proxyOptions.slideDown(); + } + else { + this.ui.proxyOptions.slideUp(); + } + }, + _setSslOptionsVisibility : function() { var showSslOptions = this.ui.sslToggle.prop('checked'); diff --git a/src/UI/Settings/General/GeneralViewTemplate.hbs b/src/UI/Settings/General/GeneralViewTemplate.hbs index 9c94011d4..2dce9a2da 100644 --- a/src/UI/Settings/General/GeneralViewTemplate.hbs +++ b/src/UI/Settings/General/GeneralViewTemplate.hbs @@ -162,6 +162,114 @@ + +
+ Proxy Settings + +
+ + +
+
+
+
+ +
+
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ + +
+
+
+
+
+
Logging From 6fdbb2b659421d84608816e7f2238a08fca1c2ee Mon Sep 17 00:00:00 2001 From: Lloyd Sparkes Date: Sat, 23 Apr 2016 11:06:10 +0100 Subject: [PATCH 02/11] Fix merging issues & API changes - that conflict resolution did not detect --- src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs | 2 +- src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs | 2 +- src/NzbDrone.Common/NzbDrone.Common.csproj | 1 + src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs index 0a06d64d1..109e673d6 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs @@ -76,7 +76,7 @@ namespace NzbDrone.Common.Http.Dispatchers return s * n; }; - if(request.Proxy != null && !request.Proxy.ShouldProxyBeBypassed(request.Url)) + if(request.Proxy != null && !request.Proxy.ShouldProxyBeBypassed(new Uri(request.Url.FullUri))) { switch (request.Proxy.Type) diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index 8c6e55633..1a94a92b6 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -29,7 +29,7 @@ namespace NzbDrone.Common.Http.Dispatchers webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds); } - if (request.Proxy != null && !request.Proxy.ShouldProxyBeBypassed(request.Url)) + if (request.Proxy != null && !request.Proxy.ShouldProxyBeBypassed(new Uri(request.Url.FullUri))) { var addresses = Dns.GetHostAddresses(request.Proxy.Host); var socksUsername = request.Proxy.Username == null ? string.Empty : request.Proxy.Username; diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index 5fe24e16d..8d040a199 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -45,6 +45,7 @@ ..\packages\NLog.4.3.0-rc1\lib\net40\NLog.dll + ..\packages\DotNet4.SocksProxy.1.0.0.0\lib\net40\Org.Mentalis.dll True diff --git a/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs index ede53acf2..9266a44f7 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs @@ -32,7 +32,7 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Error, "Failed to resolve the IP Address for the Configured Proxy Host: " + _configService.ProxyHostname); } - var request = new HttpRequestBuilder("https://services.sonarr.tv/").Build("/ping"); + var request = new HttpRequestBuilder("https://services.sonarr.tv/ping").Build(); try { From d68abc746c80d8fdf4760810fb070c8f3f5df8ed Mon Sep 17 00:00:00 2001 From: Lloyd Sparkes Date: Sun, 24 Apr 2016 16:56:31 +0100 Subject: [PATCH 03/11] Bug Fixes --- .../Http/Dispatchers/ManagedHttpDispatcher.cs | 10 +++++++++- src/NzbDrone.Common/Http/HttpRequestProxySettings.cs | 2 ++ src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index 1a94a92b6..335da0898 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -4,6 +4,7 @@ using NzbDrone.Common.Extensions; using com.LandonKey.SocksWebProxy.Proxy; using com.LandonKey.SocksWebProxy; using System.Net.Sockets; +using System.Linq; namespace NzbDrone.Common.Http.Dispatchers { @@ -31,7 +32,14 @@ namespace NzbDrone.Common.Http.Dispatchers if (request.Proxy != null && !request.Proxy.ShouldProxyBeBypassed(new Uri(request.Url.FullUri))) { - var addresses = Dns.GetHostAddresses(request.Proxy.Host); + var proxyHost = request.Proxy.Host; + if(request.Proxy.Host == "localhost") + { + proxyHost = "127.0.0.1"; + } + + var addresses = Dns.GetHostAddresses(proxyHost); + var socksUsername = request.Proxy.Username == null ? string.Empty : request.Proxy.Username; var socksPassword = request.Proxy.Password == null ? string.Empty : request.Proxy.Password; diff --git a/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs b/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs index ba533eb56..41c15ccfb 100644 --- a/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs +++ b/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs @@ -12,6 +12,8 @@ namespace NzbDrone.Common.Http Port = port; Username = username; Password = password; + SubnetFilter = filterSubnet; + BypassLocalAddress = bypassLocalAddress; } public ProxyType Type { get; private set; } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs index 9266a44f7..be688f360 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/ProxyCheck.cs @@ -27,7 +27,7 @@ namespace NzbDrone.Core.HealthCheck.Checks if (_configService.ProxyEnabled) { var addresses = Dns.GetHostAddresses(_configService.ProxyHostname); - if(addresses.Length != 1) + if(!addresses.Any()) { return new HealthCheck(GetType(), HealthCheckResult.Error, "Failed to resolve the IP Address for the Configured Proxy Host: " + _configService.ProxyHostname); } From f807e44a397b1d6d58be079d6c6e1f9e8e526e3e Mon Sep 17 00:00:00 2001 From: Lloyd Sparkes Date: Sun, 24 Apr 2016 17:01:01 +0100 Subject: [PATCH 04/11] Force to IPv4 --- .../Http/Dispatchers/ManagedHttpDispatcher.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index 335da0898..a195e9185 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -32,14 +32,17 @@ namespace NzbDrone.Common.Http.Dispatchers if (request.Proxy != null && !request.Proxy.ShouldProxyBeBypassed(new Uri(request.Url.FullUri))) { - var proxyHost = request.Proxy.Host; - if(request.Proxy.Host == "localhost") + var addresses = Dns.GetHostAddresses(request.Proxy.Host); + + if(addresses.Length > 1) { - proxyHost = "127.0.0.1"; + var ipv4Only = addresses.Where(a => a.AddressFamily == AddressFamily.InterNetwork); + if (ipv4Only.Any()) + { + addresses = ipv4Only.ToArray(); + } } - var addresses = Dns.GetHostAddresses(proxyHost); - var socksUsername = request.Proxy.Username == null ? string.Empty : request.Proxy.Username; var socksPassword = request.Proxy.Password == null ? string.Empty : request.Proxy.Password; From 9e7927acec389279f7060938af646c5b9d17133e Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sun, 24 Apr 2016 16:20:45 +0200 Subject: [PATCH 05/11] Create HttpProxySettingsProvider and fixed related issues. --- .../Http/Dispatchers/CurlHttpDispatcher.cs | 67 +++++++------ .../Dispatchers/FallbackHttpDispatcher.cs | 15 +-- .../Http/Dispatchers/ManagedHttpDispatcher.cs | 93 +++++++++++-------- src/NzbDrone.Common/Http/HttpClient.cs | 12 +-- src/NzbDrone.Common/Http/HttpRequest.cs | 1 - .../Http/HttpRequestProxySettings.cs | 34 ++++--- .../Http/IHttpProxySettingsProvider.cs | 11 +++ src/NzbDrone.Common/NzbDrone.Common.csproj | 1 + src/NzbDrone.Core.Test/Framework/CoreTest.cs | 3 +- .../Http/HttpProxySettingsProvider.cs | 52 +++++++++++ .../Http/ProxyHttpInterceptor.cs | 40 -------- src/NzbDrone.Core/NzbDrone.Core.csproj | 2 +- src/NzbDrone.Host/MainAppContainerBuilder.cs | 2 + src/NzbDrone.Update/UpdateContainerBuilder.cs | 3 +- 14 files changed, 199 insertions(+), 137 deletions(-) create mode 100644 src/NzbDrone.Common/Http/IHttpProxySettingsProvider.cs create mode 100644 src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs delete mode 100644 src/NzbDrone.Core/Http/ProxyHttpInterceptor.cs diff --git a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs index 109e673d6..9c6118270 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs @@ -20,7 +20,8 @@ namespace NzbDrone.Common.Http.Dispatchers { private static readonly Regex ExpiryDate = new Regex(@"(expires=)([^;]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled); - private static readonly Logger _logger = NzbDroneLogger.GetLogger(typeof(CurlHttpDispatcher)); + private readonly IHttpProxySettingsProvider _proxySettingsProvider; + private readonly Logger _logger; private const string _caBundleFileName = "curl-ca-bundle.crt"; private static readonly string _caBundleFilePath; @@ -36,8 +37,14 @@ namespace NzbDrone.Common.Http.Dispatchers _caBundleFilePath = _caBundleFileName; } } + + public CurlHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, Logger logger) + { + _proxySettingsProvider = proxySettingsProvider; + _logger = logger; + } - public static bool CheckAvailability() + public bool CheckAvailability() { try { @@ -76,32 +83,10 @@ namespace NzbDrone.Common.Http.Dispatchers return s * n; }; - if(request.Proxy != null && !request.Proxy.ShouldProxyBeBypassed(new Uri(request.Url.FullUri))) - - { - switch (request.Proxy.Type) - { - case ProxyType.Http: - curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Http); - curlEasy.SetOpt(CurlOption.ProxyAuth, CurlHttpAuth.Basic); - curlEasy.SetOpt(CurlOption.ProxyUserPwd, request.Proxy.Username + ":" + request.Proxy.Password.ToString()); - break; - case ProxyType.Socks4: - curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Socks4); - curlEasy.SetOpt(CurlOption.ProxyUsername, request.Proxy.Username); - curlEasy.SetOpt(CurlOption.ProxyPassword, request.Proxy.Password); - break; - case ProxyType.Socks5: - curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Socks5); - curlEasy.SetOpt(CurlOption.ProxyUsername, request.Proxy.Username); - curlEasy.SetOpt(CurlOption.ProxyPassword, request.Proxy.Password); - break; - } - curlEasy.SetOpt(CurlOption.Proxy, request.Proxy.Host + ":" + request.Proxy.Port.ToString()); - } - + AddProxy(curlEasy, request); + curlEasy.Url = request.Url.FullUri; - + switch (request.Method) { case HttpMethod.GET: @@ -174,6 +159,34 @@ namespace NzbDrone.Common.Http.Dispatchers } } + private void AddProxy(CurlEasy curlEasy, HttpRequest request) + { + var proxySettings = _proxySettingsProvider.GetProxySettings(request); + if (proxySettings != null) + + { + switch (proxySettings.Type) + { + case ProxyType.Http: + curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Http); + curlEasy.SetOpt(CurlOption.ProxyAuth, CurlHttpAuth.Basic); + curlEasy.SetOpt(CurlOption.ProxyUserPwd, proxySettings.Username + ":" + proxySettings.Password.ToString()); + break; + case ProxyType.Socks4: + curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Socks4); + curlEasy.SetOpt(CurlOption.ProxyUsername, proxySettings.Username); + curlEasy.SetOpt(CurlOption.ProxyPassword, proxySettings.Password); + break; + case ProxyType.Socks5: + curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Socks5); + curlEasy.SetOpt(CurlOption.ProxyUsername, proxySettings.Username); + curlEasy.SetOpt(CurlOption.ProxyPassword, proxySettings.Password); + break; + } + curlEasy.SetOpt(CurlOption.Proxy, proxySettings.Host + ":" + proxySettings.Port.ToString()); + } + } + private CurlSlist SerializeHeaders(HttpRequest request) { if (!request.Headers.ContainsKey("Accept-Encoding")) diff --git a/src/NzbDrone.Common/Http/Dispatchers/FallbackHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/FallbackHttpDispatcher.cs index 0fc64af8e..109d4aec2 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/FallbackHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/FallbackHttpDispatcher.cs @@ -8,17 +8,18 @@ namespace NzbDrone.Common.Http.Dispatchers { public class FallbackHttpDispatcher : IHttpDispatcher { - private readonly Logger _logger; - private readonly ICached _curlTLSFallbackCache; private readonly ManagedHttpDispatcher _managedDispatcher; private readonly CurlHttpDispatcher _curlDispatcher; + private readonly Logger _logger; + + private readonly ICached _curlTLSFallbackCache; - public FallbackHttpDispatcher(ICached curlTLSFallbackCache, Logger logger) + public FallbackHttpDispatcher(ManagedHttpDispatcher managedDispatcher, CurlHttpDispatcher curlDispatcher, ICacheManager cacheManager, Logger logger) { + _managedDispatcher = managedDispatcher; + _curlDispatcher = curlDispatcher; + _curlTLSFallbackCache = cacheManager.GetCache(GetType(), "curlTLSFallback"); _logger = logger; - _curlTLSFallbackCache = curlTLSFallbackCache; - _managedDispatcher = new ManagedHttpDispatcher(); - _curlDispatcher = new CurlHttpDispatcher(); } public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies) @@ -46,7 +47,7 @@ namespace NzbDrone.Common.Http.Dispatchers } } - if (CurlHttpDispatcher.CheckAvailability()) + if (_curlDispatcher.CheckAvailability()) { return _curlDispatcher.GetResponse(request, cookies); } diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index a195e9185..5d2c1a0f7 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -4,12 +4,22 @@ using NzbDrone.Common.Extensions; using com.LandonKey.SocksWebProxy.Proxy; using com.LandonKey.SocksWebProxy; using System.Net.Sockets; -using System.Linq; namespace NzbDrone.Common.Http.Dispatchers { public class ManagedHttpDispatcher : IHttpDispatcher { + private readonly IHttpProxySettingsProvider _proxySettingsProvider; + + private readonly ICached _webProxyCache; + + public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, ICacheManager cacheManager) + { + _proxySettingsProvider = proxySettingsProvider; + + _webProxyCache = cacheManager.GetCache(GetType(), "webProxy"); + } + public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies) { var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url); @@ -30,42 +40,7 @@ namespace NzbDrone.Common.Http.Dispatchers webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds); } - if (request.Proxy != null && !request.Proxy.ShouldProxyBeBypassed(new Uri(request.Url.FullUri))) - { - var addresses = Dns.GetHostAddresses(request.Proxy.Host); - - if(addresses.Length > 1) - { - var ipv4Only = addresses.Where(a => a.AddressFamily == AddressFamily.InterNetwork); - if (ipv4Only.Any()) - { - addresses = ipv4Only.ToArray(); - } - } - - var socksUsername = request.Proxy.Username == null ? string.Empty : request.Proxy.Username; - var socksPassword = request.Proxy.Password == null ? string.Empty : request.Proxy.Password; - - switch (request.Proxy.Type) - { - case ProxyType.Http: - if(request.Proxy.Username.IsNotNullOrWhiteSpace() && request.Proxy.Password.IsNotNullOrWhiteSpace()) - { - webRequest.Proxy = new WebProxy(request.Proxy.Host + ":" + request.Proxy.Port, request.Proxy.BypassLocalAddress, request.Proxy.SubnetFilterAsArray, new NetworkCredential(request.Proxy.Username, request.Proxy.Password)); - } - else - { - webRequest.Proxy = new WebProxy(request.Proxy.Host + ":" + request.Proxy.Port, request.Proxy.BypassLocalAddress, request.Proxy.SubnetFilterAsArray); - } - break; - case ProxyType.Socks4: - webRequest.Proxy = new SocksWebProxy(new ProxyConfig(IPAddress.Parse("127.0.0.1"), GetNextFreePort(), addresses[0], request.Proxy.Port, ProxyConfig.SocksVersion.Four, socksUsername, socksPassword), false); - break; - case ProxyType.Socks5: - webRequest.Proxy = new SocksWebProxy(new ProxyConfig(IPAddress.Parse("127.0.0.1"), GetNextFreePort(), addresses[0], request.Proxy.Port, ProxyConfig.SocksVersion.Five, socksUsername, socksPassword), false); - break; - } - } + AddProxy(webRequest, request); if (request.Headers != null) { @@ -110,6 +85,50 @@ namespace NzbDrone.Common.Http.Dispatchers return new HttpResponse(request, new HttpHeader(httpWebResponse.Headers), data, httpWebResponse.StatusCode); } + protected virtual void AddProxy(HttpWebRequest webRequest, HttpRequest request) + { + 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); + } + + return null; + } + protected virtual void AddRequestHeaders(HttpWebRequest webRequest, HttpHeader headers) { foreach (var header in headers) diff --git a/src/NzbDrone.Common/Http/HttpClient.cs b/src/NzbDrone.Common/Http/HttpClient.cs index baf78a2f3..87b04da62 100644 --- a/src/NzbDrone.Common/Http/HttpClient.cs +++ b/src/NzbDrone.Common/Http/HttpClient.cs @@ -35,21 +35,15 @@ namespace NzbDrone.Common.Http public HttpClient(IEnumerable requestInterceptors, ICacheManager cacheManager, IRateLimitService rateLimitService, IHttpDispatcher httpDispatcher, Logger logger) { - _logger = logger; - _rateLimitService = rateLimitService; _requestInterceptors = requestInterceptors.ToList(); - ServicePointManager.DefaultConnectionLimit = 12; + _rateLimitService = rateLimitService; _httpDispatcher = httpDispatcher; + _logger = logger; + ServicePointManager.DefaultConnectionLimit = 12; _cookieContainerCache = cacheManager.GetCache(typeof(HttpClient)); } - public HttpClient(IEnumerable requestInterceptors, ICacheManager cacheManager, IRateLimitService rateLimitService, Logger logger) - : this(requestInterceptors, cacheManager, rateLimitService, null, logger) - { - _httpDispatcher = new FallbackHttpDispatcher(cacheManager.GetCache(typeof(HttpClient), "curlTLSFallback"), _logger); - } - public HttpResponse Execute(HttpRequest request) { foreach (var interceptor in _requestInterceptors) diff --git a/src/NzbDrone.Common/Http/HttpRequest.cs b/src/NzbDrone.Common/Http/HttpRequest.cs index 966a3b534..94b5880d8 100644 --- a/src/NzbDrone.Common/Http/HttpRequest.cs +++ b/src/NzbDrone.Common/Http/HttpRequest.cs @@ -41,7 +41,6 @@ namespace NzbDrone.Common.Http public bool StoreResponseCookie { get; set; } public TimeSpan RequestTimeout { get; set; } public TimeSpan RateLimit { get; set; } - public HttpRequestProxySettings Proxy {get; set;} public override string ToString() { diff --git a/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs b/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs index 41c15ccfb..cbbff60bb 100644 --- a/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs +++ b/src/NzbDrone.Common/Http/HttpRequestProxySettings.cs @@ -1,18 +1,19 @@ using System; using System.Net; +using NzbDrone.Common.Extensions; namespace NzbDrone.Common.Http { public class HttpRequestProxySettings { - public HttpRequestProxySettings(ProxyType type, string host, int port, string filterSubnet, bool bypassLocalAddress, string username = null, string password = null) + public HttpRequestProxySettings(ProxyType type, string host, int port, string bypassFilter, bool bypassLocalAddress, string username = null, string password = null) { Type = type; - Host = host; + Host = host.IsNullOrWhiteSpace() ? "127.0.0.1" : host; Port = port; - Username = username; - Password = password; - SubnetFilter = filterSubnet; + Username = username ?? string.Empty; + Password = password ?? string.Empty; + BypassFilter = bypassFilter ?? string.Empty; BypassLocalAddress = bypassLocalAddress; } @@ -21,27 +22,34 @@ namespace NzbDrone.Common.Http public int Port { get; private set; } public string Username { get; private set; } public string Password { get; private set; } - public string SubnetFilter { get; private set; } + public string BypassFilter { get; private set; } public bool BypassLocalAddress { get; private set; } public string[] SubnetFilterAsArray { get { - if (!string.IsNullOrWhiteSpace(SubnetFilter)) + if (!string.IsNullOrWhiteSpace(BypassFilter)) { - return SubnetFilter.Split(';'); + return BypassFilter.Split(';'); } return new string[] { }; } } - public bool ShouldProxyBeBypassed(Uri url) + public string Key { - //We are utilising the WebProxy implementation here to save us having to reimplement it. This way we use Microsofts implementation - WebProxy proxy = new WebProxy(Host + ":" + Port, BypassLocalAddress, SubnetFilterAsArray); - - return proxy.IsBypassed(url); + get + { + return string.Join("_", + Type, + Host, + Port, + Username, + Password, + BypassFilter, + BypassLocalAddress); + } } } } diff --git a/src/NzbDrone.Common/Http/IHttpProxySettingsProvider.cs b/src/NzbDrone.Common/Http/IHttpProxySettingsProvider.cs new file mode 100644 index 000000000..73fdd5fda --- /dev/null +++ b/src/NzbDrone.Common/Http/IHttpProxySettingsProvider.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace NzbDrone.Common.Http +{ + public interface IHttpProxySettingsProvider + { + HttpRequestProxySettings GetProxySettings(HttpRequest request); + } +} diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index 8d040a199..c335256f5 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -171,6 +171,7 @@ + diff --git a/src/NzbDrone.Core.Test/Framework/CoreTest.cs b/src/NzbDrone.Core.Test/Framework/CoreTest.cs index 89a3a02c1..932356a55 100644 --- a/src/NzbDrone.Core.Test/Framework/CoreTest.cs +++ b/src/NzbDrone.Core.Test/Framework/CoreTest.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using NzbDrone.Common.Cache; using NzbDrone.Common.Cloud; using NzbDrone.Common.Http; +using NzbDrone.Common.Http.Dispatchers; using NzbDrone.Common.TPL; using NzbDrone.Test.Common; @@ -13,7 +14,7 @@ namespace NzbDrone.Core.Test.Framework protected void UseRealHttp() { Mocker.SetConstant(new HttpProvider(TestLogger)); - Mocker.SetConstant(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve(), Mocker.Resolve(), TestLogger)); + Mocker.SetConstant(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), TestLogger)); Mocker.SetConstant(new SonarrCloudRequestBuilder()); } } diff --git a/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs b/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs new file mode 100644 index 000000000..511b32948 --- /dev/null +++ b/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs @@ -0,0 +1,52 @@ +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using NzbDrone.Common.Extensions; + +namespace NzbDrone.Core.Http +{ + public class HttpProxySettingsProvider : IHttpProxySettingsProvider + { + private readonly IConfigService _configService; + + public HttpProxySettingsProvider(IConfigService configService) + { + _configService = configService; + } + + public HttpRequestProxySettings GetProxySettings(HttpRequest request) + { + if (!_configService.ProxyEnabled) + { + return null; + } + + var proxySettings = new HttpRequestProxySettings(_configService.ProxyType, + _configService.ProxyHostname, + _configService.ProxyPort, + _configService.ProxySubnetFilter, + _configService.ProxyBypassLocalAddresses, + _configService.ProxyUsername, + _configService.ProxyPassword); + + if (ShouldProxyBeBypassed(proxySettings, request.Url)) + { + return null; + } + + return proxySettings; + } + + public bool ShouldProxyBeBypassed(HttpRequestProxySettings 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); + + return proxy.IsBypassed((Uri)url); + } + } +} diff --git a/src/NzbDrone.Core/Http/ProxyHttpInterceptor.cs b/src/NzbDrone.Core/Http/ProxyHttpInterceptor.cs deleted file mode 100644 index 1207e1281..000000000 --- a/src/NzbDrone.Core/Http/ProxyHttpInterceptor.cs +++ /dev/null @@ -1,40 +0,0 @@ -using NzbDrone.Common.Http; -using NzbDrone.Core.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace NzbDrone.Core.Http -{ - public class ProxyHttpInterceptor : IHttpRequestInterceptor - { - private readonly IConfigService _configService; - - public ProxyHttpInterceptor(IConfigService configService) - { - this._configService = configService; - } - - public HttpResponse PostResponse(HttpResponse response) - { - return response; - } - - public HttpRequest PreRequest(HttpRequest request) - { - if(_configService.ProxyEnabled) - { - request.Proxy = new HttpRequestProxySettings(_configService.ProxyType, - _configService.ProxyHostname, - _configService.ProxyPort, - _configService.ProxySubnetFilter, - _configService.ProxyBypassLocalAddresses, - _configService.ProxyUsername, - _configService.ProxyPassword); - } - - return request; - } - } -} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 920b002fd..3cddc0350 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -515,7 +515,7 @@ - + diff --git a/src/NzbDrone.Host/MainAppContainerBuilder.cs b/src/NzbDrone.Host/MainAppContainerBuilder.cs index 7c2cdc800..33c05878e 100644 --- a/src/NzbDrone.Host/MainAppContainerBuilder.cs +++ b/src/NzbDrone.Host/MainAppContainerBuilder.cs @@ -4,6 +4,7 @@ using Nancy.Bootstrapper; using NzbDrone.Api; using NzbDrone.Common.Composition; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Http.Dispatchers; using NzbDrone.Core.Datastore; using NzbDrone.Core.Organizer; using NzbDrone.SignalR; @@ -42,6 +43,7 @@ namespace NzbDrone.Host AutoRegisterImplementations(); Container.Register(); + Container.Register(); } } } \ No newline at end of file diff --git a/src/NzbDrone.Update/UpdateContainerBuilder.cs b/src/NzbDrone.Update/UpdateContainerBuilder.cs index 4dc5e9662..e3f449586 100644 --- a/src/NzbDrone.Update/UpdateContainerBuilder.cs +++ b/src/NzbDrone.Update/UpdateContainerBuilder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using NzbDrone.Common.Composition; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Http.Dispatchers; namespace NzbDrone.Update { @@ -10,7 +11,7 @@ namespace NzbDrone.Update private UpdateContainerBuilder(IStartupContext startupContext, string[] assemblies) : base(startupContext, assemblies) { - + Container.Register(); } public static IContainer Build(IStartupContext startupContext) From b479064abdf29af3e4d8d36b1d1533e060852ce3 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Mon, 25 Apr 2016 21:53:26 +0200 Subject: [PATCH 06/11] Moved Proxy types around and refactored/renamed a few things. --- src/NzbDrone.Api/Config/HostConfigResource.cs | 5 +- .../Http/HttpClientFixture.cs | 6 ++ .../Http/Dispatchers/CurlHttpDispatcher.cs | 4 +- .../Http/Dispatchers/ManagedHttpDispatcher.cs | 59 ++----------- .../HttpProxySettings.cs} | 6 +- .../{ => Proxy}/IHttpProxySettingsProvider.cs | 4 +- .../Http/Proxy/ManagedWebProxyFactory.cs | 86 +++++++++++++++++++ .../Http/{ => Proxy}/ProxyType.cs | 2 +- src/NzbDrone.Common/NzbDrone.Common.csproj | 7 +- .../Configuration/ConfigService.cs | 5 +- .../Configuration/IConfigService.cs | 3 +- .../HealthCheck/Checks/ProxyCheck.cs | 21 +++-- .../Http/HttpProxySettingsProvider.cs | 19 ++-- .../Settings/General/GeneralViewTemplate.hbs | 4 +- 14 files changed, 141 insertions(+), 90 deletions(-) rename src/NzbDrone.Common/Http/{HttpRequestProxySettings.cs => Proxy/HttpProxySettings.cs} (85%) rename src/NzbDrone.Common/Http/{ => Proxy}/IHttpProxySettingsProvider.cs (56%) create mode 100644 src/NzbDrone.Common/Http/Proxy/ManagedWebProxyFactory.cs rename src/NzbDrone.Common/Http/{ => Proxy}/ProxyType.cs (82%) 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 @@
- +
- +
From 5fae8e7762b61353d56831969729890d5904b58f Mon Sep 17 00:00:00 2001 From: Lloyd Sparkes Date: Sun, 1 May 2016 11:51:30 +0100 Subject: [PATCH 07/11] Updating SocksWebProxy to fix issues with POST Requests --- .../Http/Dispatchers/ManagedHttpDispatcher.cs | 4 ---- src/NzbDrone.Common/NzbDrone.Common.csproj | 6 +++--- src/NzbDrone.Common/packages.config | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index 092ac8d64..b1802696a 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -1,10 +1,6 @@ using System; using System.Net; 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 diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index 87583e16d..d0d40b81f 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -47,11 +47,11 @@ ..\packages\NLog.4.3.0-rc1\lib\net40\NLog.dll - ..\packages\DotNet4.SocksProxy.1.0.0.0\lib\net40\Org.Mentalis.dll + ..\packages\DotNet4.SocksProxy.1.1.0.0\lib\net40\Org.Mentalis.dll True - - ..\packages\DotNet4.SocksProxy.1.0.0.0\lib\net40\SocksWebProxy.dll + + ..\packages\DotNet4.SocksProxy.1.1.0.0\lib\net40\SocksWebProxy.dll True diff --git a/src/NzbDrone.Common/packages.config b/src/NzbDrone.Common/packages.config index 05011894d..b1757923b 100644 --- a/src/NzbDrone.Common/packages.config +++ b/src/NzbDrone.Common/packages.config @@ -1,6 +1,6 @@  - + From c82b90aca84597a1d0fbba5cfdf682a4110d9928 Mon Sep 17 00:00:00 2001 From: Lloyd Sparkes Date: Sun, 1 May 2016 12:47:19 +0100 Subject: [PATCH 08/11] Fix tests failing due to lack of constructor less classes, after refactoring --- src/NzbDrone.Core.Test/Framework/CoreTest.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/NzbDrone.Core.Test/Framework/CoreTest.cs b/src/NzbDrone.Core.Test/Framework/CoreTest.cs index 932356a55..3a2f01a9c 100644 --- a/src/NzbDrone.Core.Test/Framework/CoreTest.cs +++ b/src/NzbDrone.Core.Test/Framework/CoreTest.cs @@ -6,6 +6,9 @@ using NzbDrone.Common.Http; using NzbDrone.Common.Http.Dispatchers; using NzbDrone.Common.TPL; using NzbDrone.Test.Common; +using NzbDrone.Common.Http.Proxy; +using NzbDrone.Core.Http; +using NzbDrone.Core.Configuration; namespace NzbDrone.Core.Test.Framework { @@ -13,6 +16,10 @@ namespace NzbDrone.Core.Test.Framework { protected void UseRealHttp() { + Mocker.SetConstant(new HttpProxySettingsProvider(Mocker.Resolve())); + Mocker.SetConstant(new ManagedWebProxyFactory(Mocker.Resolve())); + Mocker.SetConstant(new ManagedHttpDispatcher(Mocker.Resolve(), Mocker.Resolve())); + Mocker.SetConstant(new CurlHttpDispatcher(Mocker.Resolve(), Mocker.Resolve())); Mocker.SetConstant(new HttpProvider(TestLogger)); Mocker.SetConstant(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), TestLogger)); Mocker.SetConstant(new SonarrCloudRequestBuilder()); From 2b11ad4585f9caaa050b58077da56bca37764049 Mon Sep 17 00:00:00 2001 From: Lloyd Sparkes Date: Mon, 2 May 2016 11:24:51 +0100 Subject: [PATCH 09/11] Proxy BypassList tests Lightly refactored and updated documentation to make it easier for users --- .../Http/Proxy/HttpProxySettings.cs | 12 +++++- .../Http/Proxy/ManagedWebProxyFactory.cs | 4 +- .../Http/HttpProxySettingsProviderFixture.cs | 40 +++++++++++++++++++ .../NzbDrone.Core.Test.csproj | 1 + .../Http/HttpProxySettingsProvider.cs | 2 +- .../Settings/General/GeneralViewTemplate.hbs | 2 +- 6 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 src/NzbDrone.Core.Test/Http/HttpProxySettingsProviderFixture.cs diff --git a/src/NzbDrone.Common/Http/Proxy/HttpProxySettings.cs b/src/NzbDrone.Common/Http/Proxy/HttpProxySettings.cs index 0c2e3ce3d..84664bf8a 100644 --- a/src/NzbDrone.Common/Http/Proxy/HttpProxySettings.cs +++ b/src/NzbDrone.Common/Http/Proxy/HttpProxySettings.cs @@ -25,13 +25,21 @@ namespace NzbDrone.Common.Http.Proxy public string BypassFilter { get; private set; } public bool BypassLocalAddress { get; private set; } - public string[] SubnetFilterAsArray + public string[] BypassListAsArray { get { if (!string.IsNullOrWhiteSpace(BypassFilter)) { - return BypassFilter.Split(';'); + var hostlist = BypassFilter.Split(','); + for(int i = 0; i < hostlist.Length; i++) + { + if(hostlist[i].StartsWith("*")) + { + hostlist[i] = ";" + hostlist[i]; + } + } + return hostlist; } return new string[] { }; } diff --git a/src/NzbDrone.Common/Http/Proxy/ManagedWebProxyFactory.cs b/src/NzbDrone.Common/Http/Proxy/ManagedWebProxyFactory.cs index a2992131d..163435e84 100644 --- a/src/NzbDrone.Common/Http/Proxy/ManagedWebProxyFactory.cs +++ b/src/NzbDrone.Common/Http/Proxy/ManagedWebProxyFactory.cs @@ -40,11 +40,11 @@ namespace NzbDrone.Common.Http.Proxy 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)); + return new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.BypassListAsArray, new NetworkCredential(proxySettings.Username, proxySettings.Password)); } else { - return new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.SubnetFilterAsArray); + return new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.BypassListAsArray); } case ProxyType.Socks4: return new SocksWebProxy(new ProxyConfig(IPAddress.Loopback, GetNextFreePort(), GetProxyIpAddress(proxySettings.Host), proxySettings.Port, ProxyConfig.SocksVersion.Four, proxySettings.Username, proxySettings.Password), false); diff --git a/src/NzbDrone.Core.Test/Http/HttpProxySettingsProviderFixture.cs b/src/NzbDrone.Core.Test/Http/HttpProxySettingsProviderFixture.cs new file mode 100644 index 000000000..20679a417 --- /dev/null +++ b/src/NzbDrone.Core.Test/Http/HttpProxySettingsProviderFixture.cs @@ -0,0 +1,40 @@ +using NzbDrone.Core.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using FluentAssertions; +using NzbDrone.Test.Common; +using NzbDrone.Common.Http.Proxy; +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.Test.Http +{ + [TestFixture] + public class HttpProxySettingsProviderFixture : TestBase + { + private HttpProxySettings GetProxySettings() + { + return new HttpProxySettings(ProxyType.Socks5, "localhost", 8080, "*.httpbin.org,google.com", true, null, null); + } + + [Test] + public void should_bypass_proxy() + { + var settings = GetProxySettings(); + + Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://eu.httpbin.org/get")).Should().BeTrue(); + Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://google.com/get")).Should().BeTrue(); + Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://localhost:8654/get")).Should().BeTrue(); + } + + [Test] + public void should_not_bypass_proxy() + { + var settings = GetProxySettings(); + + Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://bing.com/get")).Should().BeFalse(); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index fa4a78235..9ac293d55 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -217,6 +217,7 @@ + diff --git a/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs b/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs index 0ca11bf12..6e5a23e07 100644 --- a/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs +++ b/src/NzbDrone.Core/Http/HttpProxySettingsProvider.cs @@ -41,7 +41,7 @@ namespace NzbDrone.Core.Http 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); + var proxy = new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.BypassListAsArray); return proxy.IsBypassed((Uri)url); } diff --git a/src/UI/Settings/General/GeneralViewTemplate.hbs b/src/UI/Settings/General/GeneralViewTemplate.hbs index 99b5930bc..b22abe9a6 100644 --- a/src/UI/Settings/General/GeneralViewTemplate.hbs +++ b/src/UI/Settings/General/GeneralViewTemplate.hbs @@ -242,7 +242,7 @@
- +
From 4c39594a577fb903b8ce96e7557c1aeeafd4447b Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sat, 21 May 2016 22:59:26 +0200 Subject: [PATCH 10/11] Final cleanup on proxy support. --- src/NzbDrone.Core/Configuration/ConfigService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 553838528..e8068584a 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -345,7 +345,7 @@ namespace NzbDrone.Core.Configuration public string ProxyBypassFilter { - get { return GetValue("ProxySubnetFilter", string.Empty); } + get { return GetValue("ProxyBypassFilter", string.Empty); } } public bool ProxyBypassLocalAddresses From 9ea753011b2781b830645574c960918693cee07d Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sat, 21 May 2016 23:02:02 +0200 Subject: [PATCH 11/11] Added round-robin over httpbin.org hosts for httpclient tests. --- .../Http/HttpClientFixture.cs | 71 +++++++++---------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs index 5ea03c912..d4a74e32a 100644 --- a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs +++ b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs @@ -22,7 +22,11 @@ namespace NzbDrone.Common.Test.Http [TestFixture(typeof(ManagedHttpDispatcher))] [TestFixture(typeof(CurlHttpDispatcher))] public class HttpClientFixture : TestBase where TDispatcher : IHttpDispatcher - { + { + private static string[] _httpBinHosts = new[] { "eu.httpbin.org", "httpbin.org" }; + private static int _httpBinRandom; + private string _httpBinHost; + [SetUp] public void SetUp() { @@ -32,15 +36,19 @@ namespace NzbDrone.Common.Test.Http Mocker.SetConstant>(new IHttpRequestInterceptor[0]); Mocker.SetConstant(Mocker.Resolve()); + // Used for manual testing of socks proxies. //Mocker.GetMock() // .Setup(v => v.GetProxySettings(It.IsAny())) // .Returns(new HttpProxySettings(ProxyType.Socks5, "127.0.0.1", 5476, "", false)); + + // Roundrobin over the two servers, to reduce the chance of hitting the ratelimiter. + _httpBinHost = _httpBinHosts[_httpBinRandom++ % _httpBinHosts.Length]; } [Test] public void should_execute_simple_get() { - var request = new HttpRequest("http://eu.httpbin.org/get"); + var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost)); var response = Subject.Execute(request); @@ -50,7 +58,7 @@ namespace NzbDrone.Common.Test.Http [Test] public void should_execute_https_get() { - var request = new HttpRequest("https://eu.httpbin.org/get"); + var request = new HttpRequest(string.Format("https://{0}/get", _httpBinHost)); var response = Subject.Execute(request); @@ -60,7 +68,7 @@ namespace NzbDrone.Common.Test.Http [Test] public void should_execute_typed_get() { - var request = new HttpRequest("http://eu.httpbin.org/get"); + var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost)); var response = Subject.Get(request); @@ -72,7 +80,7 @@ namespace NzbDrone.Common.Test.Http { var message = "{ my: 1 }"; - var request = new HttpRequest("http://eu.httpbin.org/post"); + var request = new HttpRequest(string.Format("http://{0}/post", _httpBinHost)); request.SetContent(message); var response = Subject.Post(request); @@ -83,7 +91,7 @@ namespace NzbDrone.Common.Test.Http [TestCase("gzip")] public void should_execute_get_using_gzip(string compression) { - var request = new HttpRequest("http://eu.httpbin.org/" + compression); + var request = new HttpRequest(string.Format("http://{0}/{1}", _httpBinHost, compression)); var response = Subject.Get(request); @@ -99,7 +107,7 @@ namespace NzbDrone.Common.Test.Http [TestCase(HttpStatusCode.BadGateway)] public void should_throw_on_unsuccessful_status_codes(int statusCode) { - var request = new HttpRequest("http://eu.httpbin.org/status/" + statusCode); + var request = new HttpRequest(string.Format("http://{0}/status/{1}", _httpBinHost, statusCode)); var exception = Assert.Throws(() => Subject.Get(request)); @@ -111,7 +119,7 @@ namespace NzbDrone.Common.Test.Http [Test] public void should_not_follow_redirects_when_not_in_production() { - var request = new HttpRequest("http://eu.httpbin.org/redirect/1"); + var request = new HttpRequest(string.Format("http://{0}/redirect/1", _httpBinHost)); Subject.Get(request); @@ -121,7 +129,7 @@ namespace NzbDrone.Common.Test.Http [Test] public void should_follow_redirects() { - var request = new HttpRequest("http://eu.httpbin.org/redirect/1"); + var request = new HttpRequest(string.Format("http://{0}/redirect/1", _httpBinHost)); request.AllowAutoRedirect = true; Subject.Get(request); @@ -132,7 +140,7 @@ namespace NzbDrone.Common.Test.Http [Test] public void should_send_user_agent() { - var request = new HttpRequest("http://eu.httpbin.org/get"); + var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost)); var response = Subject.Get(request); @@ -146,7 +154,7 @@ namespace NzbDrone.Common.Test.Http [TestCase("Accept", "text/xml, text/rss+xml, application/rss+xml")] public void should_send_headers(string header, string value) { - var request = new HttpRequest("http://eu.httpbin.org/get"); + var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost)); request.Headers.Add(header, value); var response = Subject.Get(request); @@ -169,7 +177,7 @@ namespace NzbDrone.Common.Test.Http [Test] public void should_send_cookie() { - var request = new HttpRequest("http://eu.httpbin.org/get"); + var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost)); request.Cookies["my"] = "cookie"; var response = Subject.Get(request); @@ -226,12 +234,12 @@ namespace NzbDrone.Common.Test.Http [Test] public void should_not_store_response_cookie() { - var requestSet = new HttpRequest("http://eu.httpbin.org/cookies/set?my=cookie"); + var requestSet = new HttpRequest(string.Format("http://{0}/cookies/set?my=cookie", _httpBinHost)); requestSet.AllowAutoRedirect = false; var responseSet = Subject.Get(requestSet); - var request = new HttpRequest("http://eu.httpbin.org/get"); + var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost)); var response = Subject.Get(request); @@ -243,13 +251,13 @@ namespace NzbDrone.Common.Test.Http [Test] public void should_store_response_cookie() { - var requestSet = new HttpRequest("http://eu.httpbin.org/cookies/set?my=cookie"); + var requestSet = new HttpRequest(string.Format("http://{0}/cookies/set?my=cookie", _httpBinHost)); requestSet.AllowAutoRedirect = false; requestSet.StoreResponseCookie = true; var responseSet = Subject.Get(requestSet); - var request = new HttpRequest("http://eu.httpbin.org/get"); + var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost)); var response = Subject.Get(request); @@ -265,14 +273,14 @@ namespace NzbDrone.Common.Test.Http [Test] public void should_overwrite_response_cookie() { - var requestSet = new HttpRequest("http://eu.httpbin.org/cookies/set?my=cookie"); + var requestSet = new HttpRequest(string.Format("http://{0}/cookies/set?my=cookie", _httpBinHost)); requestSet.AllowAutoRedirect = false; requestSet.StoreResponseCookie = true; requestSet.Cookies["my"] = "oldcookie"; var responseSet = Subject.Get(requestSet); - var request = new HttpRequest("http://eu.httpbin.org/get"); + var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost)); var response = Subject.Get(request); @@ -288,7 +296,7 @@ namespace NzbDrone.Common.Test.Http [Test] public void should_throw_on_http429_too_many_requests() { - var request = new HttpRequest("http://eu.httpbin.org/status/429"); + var request = new HttpRequest(string.Format("http://{0}/status/429", _httpBinHost)); Assert.Throws(() => Subject.Get(request)); @@ -308,7 +316,7 @@ namespace NzbDrone.Common.Test.Http .Setup(v => v.PostResponse(It.IsAny())) .Returns(r => r); - var request = new HttpRequest("http://eu.httpbin.org/get"); + var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost)); Subject.Get(request); @@ -330,7 +338,7 @@ namespace NzbDrone.Common.Test.Http { // the date is bad in the below - should be 13-Jul-2016 string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Wed, 13-Jul-16 16:19:50 GMT; path=/; HttpOnly"; - var requestSet = new HttpRequestBuilder("http://eu.httpbin.org/response-headers") + var requestSet = new HttpRequestBuilder(string.Format("http://{0}/response-headers", _httpBinHost)) .AddQueryParam("Set-Cookie", malformedCookie) .Build(); @@ -339,7 +347,7 @@ namespace NzbDrone.Common.Test.Http var responseSet = Subject.Get(requestSet); - var request = new HttpRequest("http://eu.httpbin.org/get"); + var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost)); var response = Subject.Get(request); @@ -364,7 +372,7 @@ namespace NzbDrone.Common.Test.Http try { // the date is bad in the below - should be 13-Jul-2016 - string url = "http://eu.httpbin.org/response-headers?Set-Cookie=" + Uri.EscapeUriString(malformedCookie); + string url = string.Format("http://{0}/response-headers?Set-Cookie={1}", _httpBinHost, Uri.EscapeUriString(malformedCookie)); var requestSet = new HttpRequest(url); requestSet.AllowAutoRedirect = false; @@ -372,7 +380,7 @@ namespace NzbDrone.Common.Test.Http var responseSet = Subject.Get(requestSet); - var request = new HttpRequest("http://eu.httpbin.org/get"); + var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost)); var response = Subject.Get(request); @@ -384,21 +392,6 @@ namespace NzbDrone.Common.Test.Http { } } - - public void should_submit_formparameters_in_body() - { - Assert.Fail(); - } - - public void should_submit_attachments_as_multipart() - { - Assert.Fail(); - } - - public void should_submit_formparameters_as_multipart_if_attachments_exist() - { - Assert.Fail(); - } } public class HttpBinResource