commit
54c17de849
|
@ -15,12 +15,14 @@ namespace NzbDrone.Api.Config
|
|||
public class HostConfigModule : NzbDroneRestModule<HostConfigResource>
|
||||
{
|
||||
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())
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Update;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
|
||||
namespace NzbDrone.Api.Config
|
||||
{
|
||||
|
@ -25,5 +26,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 ProxyBypassFilter { get; set; }
|
||||
public bool ProxyBypassLocalAddresses { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -21,20 +22,33 @@ namespace NzbDrone.Common.Test.Http
|
|||
[TestFixture(typeof(ManagedHttpDispatcher))]
|
||||
[TestFixture(typeof(CurlHttpDispatcher))]
|
||||
public class HttpClientFixture<TDispatcher> : TestBase<HttpClient> where TDispatcher : IHttpDispatcher
|
||||
{
|
||||
{
|
||||
private static string[] _httpBinHosts = new[] { "eu.httpbin.org", "httpbin.org" };
|
||||
private static int _httpBinRandom;
|
||||
private string _httpBinHost;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
|
||||
Mocker.SetConstant<ICreateManagedWebProxy>(Mocker.Resolve<ManagedWebProxyFactory>());
|
||||
Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>());
|
||||
Mocker.SetConstant<IEnumerable<IHttpRequestInterceptor>>(new IHttpRequestInterceptor[0]);
|
||||
Mocker.SetConstant<IHttpDispatcher>(Mocker.Resolve<TDispatcher>());
|
||||
|
||||
// Used for manual testing of socks proxies.
|
||||
//Mocker.GetMock<IHttpProxySettingsProvider>()
|
||||
// .Setup(v => v.GetProxySettings(It.IsAny<HttpRequest>()))
|
||||
// .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);
|
||||
|
||||
|
@ -44,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);
|
||||
|
||||
|
@ -54,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<HttpBinResource>(request);
|
||||
|
||||
|
@ -66,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<HttpBinResource>(request);
|
||||
|
@ -77,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<HttpBinResource>(request);
|
||||
|
||||
|
@ -93,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<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||
|
||||
|
@ -105,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);
|
||||
|
||||
|
@ -115,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);
|
||||
|
@ -126,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<HttpBinResource>(request);
|
||||
|
||||
|
@ -140,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<HttpBinResource>(request);
|
||||
|
@ -163,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<HttpBinResource>(request);
|
||||
|
@ -220,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<HttpBinResource>(request);
|
||||
|
||||
|
@ -237,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<HttpBinResource>(request);
|
||||
|
||||
|
@ -259,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<HttpBinResource>(request);
|
||||
|
||||
|
@ -282,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<TooManyRequestsException>(() => Subject.Get(request));
|
||||
|
||||
|
@ -302,7 +316,7 @@ namespace NzbDrone.Common.Test.Http
|
|||
.Setup(v => v.PostResponse(It.IsAny<HttpResponse>()))
|
||||
.Returns<HttpResponse>(r => r);
|
||||
|
||||
var request = new HttpRequest("http://eu.httpbin.org/get");
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
|
||||
Subject.Get(request);
|
||||
|
||||
|
@ -324,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();
|
||||
|
||||
|
@ -333,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<HttpBinResource>(request);
|
||||
|
||||
|
@ -358,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;
|
||||
|
@ -366,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<HttpBinResource>(request);
|
||||
|
||||
|
@ -378,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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
@ -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,7 +83,10 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||
return s * n;
|
||||
};
|
||||
|
||||
AddProxy(curlEasy, request);
|
||||
|
||||
curlEasy.Url = request.Url.FullUri;
|
||||
|
||||
switch (request.Method)
|
||||
{
|
||||
case HttpMethod.GET:
|
||||
|
@ -149,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"))
|
||||
|
|
|
@ -8,17 +8,18 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||
{
|
||||
public class FallbackHttpDispatcher : IHttpDispatcher
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly ICached<bool> _curlTLSFallbackCache;
|
||||
private readonly ManagedHttpDispatcher _managedDispatcher;
|
||||
private readonly CurlHttpDispatcher _curlDispatcher;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public FallbackHttpDispatcher(ICached<bool> curlTLSFallbackCache, Logger logger)
|
||||
private readonly ICached<bool> _curlTLSFallbackCache;
|
||||
|
||||
public FallbackHttpDispatcher(ManagedHttpDispatcher managedDispatcher, CurlHttpDispatcher curlDispatcher, ICacheManager cacheManager, Logger logger)
|
||||
{
|
||||
_managedDispatcher = managedDispatcher;
|
||||
_curlDispatcher = curlDispatcher;
|
||||
_curlTLSFallbackCache = cacheManager.GetCache<bool>(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);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
|
||||
namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
public class ManagedHttpDispatcher : IHttpDispatcher
|
||||
{
|
||||
private readonly IHttpProxySettingsProvider _proxySettingsProvider;
|
||||
private readonly ICreateManagedWebProxy _createManagedWebProxy;
|
||||
|
||||
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, ICreateManagedWebProxy createManagedWebProxy)
|
||||
{
|
||||
_proxySettingsProvider = proxySettingsProvider;
|
||||
_createManagedWebProxy = createManagedWebProxy;
|
||||
}
|
||||
|
||||
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
|
||||
{
|
||||
var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url);
|
||||
|
@ -26,6 +36,8 @@ namespace NzbDrone.Common.Http.Dispatchers
|
|||
webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds);
|
||||
}
|
||||
|
||||
AddProxy(webRequest, request);
|
||||
|
||||
if (request.Headers != null)
|
||||
{
|
||||
AddRequestHeaders(webRequest, request.Headers);
|
||||
|
@ -69,6 +81,15 @@ 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 = _createManagedWebProxy.GetWebProxy(proxySettings);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void AddRequestHeaders(HttpWebRequest webRequest, HttpHeader headers)
|
||||
{
|
||||
foreach (var header in headers)
|
||||
|
|
|
@ -35,21 +35,15 @@ namespace NzbDrone.Common.Http
|
|||
|
||||
public HttpClient(IEnumerable<IHttpRequestInterceptor> 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<CookieContainer>(typeof(HttpClient));
|
||||
}
|
||||
|
||||
public HttpClient(IEnumerable<IHttpRequestInterceptor> requestInterceptors, ICacheManager cacheManager, IRateLimitService rateLimitService, Logger logger)
|
||||
: this(requestInterceptors, cacheManager, rateLimitService, null, logger)
|
||||
{
|
||||
_httpDispatcher = new FallbackHttpDispatcher(cacheManager.GetCache<bool>(typeof(HttpClient), "curlTLSFallback"), _logger);
|
||||
}
|
||||
|
||||
public HttpResponse Execute(HttpRequest request)
|
||||
{
|
||||
foreach (var interceptor in _requestInterceptors)
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace NzbDrone.Common.Http
|
|||
Headers = new HttpHeader();
|
||||
AllowAutoRedirect = true;
|
||||
Cookies = new Dictionary<string, string>();
|
||||
|
||||
|
||||
if (!RuntimeInfoBase.IsProduction)
|
||||
{
|
||||
AllowAutoRedirect = false;
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Http.Proxy
|
||||
{
|
||||
public class HttpProxySettings
|
||||
{
|
||||
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;
|
||||
Port = port;
|
||||
Username = username ?? string.Empty;
|
||||
Password = password ?? string.Empty;
|
||||
BypassFilter = bypassFilter ?? string.Empty;
|
||||
BypassLocalAddress = bypassLocalAddress;
|
||||
}
|
||||
|
||||
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 BypassFilter { get; private set; }
|
||||
public bool BypassLocalAddress { get; private set; }
|
||||
|
||||
public string[] BypassListAsArray
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(BypassFilter))
|
||||
{
|
||||
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[] { };
|
||||
}
|
||||
}
|
||||
|
||||
public string Key
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Join("_",
|
||||
Type,
|
||||
Host,
|
||||
Port,
|
||||
Username,
|
||||
Password,
|
||||
BypassFilter,
|
||||
BypassLocalAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NzbDrone.Common.Http.Proxy
|
||||
{
|
||||
public interface IHttpProxySettingsProvider
|
||||
{
|
||||
HttpProxySettings GetProxySettings(HttpRequest request);
|
||||
}
|
||||
}
|
|
@ -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<IWebProxy> _webProxyCache;
|
||||
|
||||
public ManagedWebProxyFactory(ICacheManager cacheManager)
|
||||
{
|
||||
_webProxyCache = cacheManager.GetCache<IWebProxy>(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.BypassListAsArray, new NetworkCredential(proxySettings.Username, proxySettings.Password));
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Common.Http.Proxy
|
||||
{
|
||||
public enum ProxyType
|
||||
{
|
||||
Http,
|
||||
Socks4,
|
||||
Socks5
|
||||
}
|
||||
}
|
|
@ -45,6 +45,13 @@
|
|||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.3.0-rc1\lib\net40\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Org.Mentalis, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\DotNet4.SocksProxy.1.1.0.0\lib\net40\Org.Mentalis.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SocksWebProxy, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\DotNet4.SocksProxy.1.1.0.0\lib\net40\SocksWebProxy.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
|
@ -150,6 +157,7 @@
|
|||
<Compile Include="Http\Dispatchers\FallbackHttpDispatcher.cs" />
|
||||
<Compile Include="Http\Dispatchers\IHttpDispatcher.cs" />
|
||||
<Compile Include="Http\Dispatchers\ManagedHttpDispatcher.cs" />
|
||||
<Compile Include="Http\Proxy\ManagedWebProxyFactory.cs" />
|
||||
<Compile Include="Http\GZipWebClient.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
|
@ -161,8 +169,10 @@
|
|||
<Compile Include="Http\HttpMethod.cs" />
|
||||
<Compile Include="Http\HttpProvider.cs" />
|
||||
<Compile Include="Http\HttpRequest.cs" />
|
||||
<Compile Include="Http\Proxy\HttpProxySettings.cs" />
|
||||
<Compile Include="Http\HttpResponse.cs" />
|
||||
<Compile Include="Http\HttpUri.cs" />
|
||||
<Compile Include="Http\Proxy\IHttpProxySettingsProvider.cs" />
|
||||
<Compile Include="Http\IHttpRequestInterceptor.cs" />
|
||||
<Compile Include="Http\JsonRpcRequestBuilder.cs" />
|
||||
<Compile Include="Http\JsonRpcResponse.cs" />
|
||||
|
@ -171,6 +181,7 @@
|
|||
</Compile>
|
||||
<Compile Include="Http\HttpRequestBuilder.cs" />
|
||||
<Compile Include="Http\HttpRequestBuilderFactory.cs" />
|
||||
<Compile Include="Http\Proxy\ProxyType.cs" />
|
||||
<Compile Include="Http\TooManyRequestsException.cs" />
|
||||
<Compile Include="Extensions\IEnumerableExtensions.cs" />
|
||||
<Compile Include="Http\UserAgentBuilder.cs" />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="DotNet4.SocksProxy" version="1.1.0.0" targetFramework="net40" />
|
||||
<package id="ICSharpCode.SharpZipLib.Patched" version="0.86.5" targetFramework="net40" />
|
||||
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
|
||||
<package id="NLog" version="4.3.0-rc1" targetFramework="net40" />
|
||||
|
|
|
@ -3,8 +3,12 @@ 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;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Core.Test.Framework
|
||||
{
|
||||
|
@ -12,8 +16,12 @@ namespace NzbDrone.Core.Test.Framework
|
|||
{
|
||||
protected void UseRealHttp()
|
||||
{
|
||||
Mocker.SetConstant<IHttpProxySettingsProvider>(new HttpProxySettingsProvider(Mocker.Resolve<ConfigService>()));
|
||||
Mocker.SetConstant<ICreateManagedWebProxy>(new ManagedWebProxyFactory(Mocker.Resolve<CacheManager>()));
|
||||
Mocker.SetConstant<ManagedHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>()));
|
||||
Mocker.SetConstant<CurlHttpDispatcher>(new CurlHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<NLog.Logger>()));
|
||||
Mocker.SetConstant<IHttpProvider>(new HttpProvider(TestLogger));
|
||||
Mocker.SetConstant<IHttpClient>(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), TestLogger));
|
||||
Mocker.SetConstant<IHttpClient>(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<FallbackHttpDispatcher>(), TestLogger));
|
||||
Mocker.SetConstant<ISonarrCloudRequestBuilder>(new SonarrCloudRequestBuilder());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<HttpProxySettingsProvider>
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -220,6 +220,7 @@
|
|||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFilesFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedPendingReleasesFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\FixFutureRunScheduledTasksFixture.cs" />
|
||||
<Compile Include="Http\HttpProxySettingsProviderFixture.cs" />
|
||||
<Compile Include="Http\TorCacheHttpRequestInterceptorFixture.cs" />
|
||||
<Compile Include="IndexerSearchTests\SeriesSearchServiceFixture.cs" />
|
||||
<Compile Include="IndexerSearchTests\NzbSearchServiceFixture.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<ApplicationStartedEvent>,
|
||||
|
|
|
@ -7,6 +7,7 @@ using NzbDrone.Common.EnvironmentInfo;
|
|||
using NzbDrone.Core.Configuration.Events;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
|
||||
namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
|
@ -312,6 +313,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", 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 ProxyBypassFilter
|
||||
{
|
||||
get { return GetValue("ProxyBypassFilter", string.Empty); }
|
||||
}
|
||||
|
||||
public bool ProxyBypassLocalAddresses
|
||||
{
|
||||
get { return GetValueBoolean("ProxyBypassLocalAddresses", true); }
|
||||
}
|
||||
|
||||
private string GetValue(string key)
|
||||
{
|
||||
return GetValue(key, string.Empty);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
using System;
|
||||
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
|
||||
{
|
||||
|
@ -64,5 +67,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 ProxyBypassFilter { get; }
|
||||
bool ProxyBypassLocalAddresses { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
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;
|
||||
using NzbDrone.Common.Cloud;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
public class ProxyCheck : HealthCheckBase
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IHttpClient _client;
|
||||
|
||||
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()
|
||||
{
|
||||
if (_configService.ProxyEnabled)
|
||||
{
|
||||
var addresses = Dns.GetHostAddresses(_configService.ProxyHostname);
|
||||
if(!addresses.Any())
|
||||
{
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format("Failed to resolve the IP Address for the Configured Proxy Host {0}", _configService.ProxyHostname));
|
||||
}
|
||||
|
||||
var request = _cloudRequestBuilder.Create()
|
||||
.Resource("/ping")
|
||||
.Build();
|
||||
|
||||
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.StatusCode);
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format("Failed to test proxy: StatusCode {1}", request.Url, response.StatusCode));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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));
|
||||
}
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Core.Http
|
||||
{
|
||||
public class HttpProxySettingsProvider : IHttpProxySettingsProvider
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
|
||||
public HttpProxySettingsProvider(IConfigService configService)
|
||||
{
|
||||
_configService = configService;
|
||||
}
|
||||
|
||||
public HttpProxySettings GetProxySettings(HttpRequest request)
|
||||
{
|
||||
if (!_configService.ProxyEnabled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var proxySettings = new HttpProxySettings(_configService.ProxyType,
|
||||
_configService.ProxyHostname,
|
||||
_configService.ProxyPort,
|
||||
_configService.ProxyBypassFilter,
|
||||
_configService.ProxyBypassLocalAddresses,
|
||||
_configService.ProxyUsername,
|
||||
_configService.ProxyPassword);
|
||||
|
||||
if (ShouldProxyBeBypassed(proxySettings, request.Url))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return proxySettings;
|
||||
}
|
||||
|
||||
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.BypassListAsArray);
|
||||
|
||||
return proxy.IsBypassed((Uri)url);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -489,6 +489,7 @@
|
|||
<Compile Include="HealthCheck\Checks\IndexerCheck.cs" />
|
||||
<Compile Include="HealthCheck\Checks\MediaInfoDllCheck.cs" />
|
||||
<Compile Include="HealthCheck\Checks\MonoVersionCheck.cs" />
|
||||
<Compile Include="HealthCheck\Checks\ProxyCheck.cs" />
|
||||
<Compile Include="HealthCheck\Checks\RootFolderCheck.cs" />
|
||||
<Compile Include="HealthCheck\Checks\UpdateCheck.cs" />
|
||||
<Compile Include="HealthCheck\HealthCheck.cs" />
|
||||
|
@ -517,6 +518,7 @@
|
|||
<Compile Include="Housekeeping\HousekeepingCommand.cs" />
|
||||
<Compile Include="Housekeeping\HousekeepingService.cs" />
|
||||
<Compile Include="Housekeeping\IHousekeepingTask.cs" />
|
||||
<Compile Include="Http\HttpProxySettingsProvider.cs" />
|
||||
<Compile Include="Http\TorcacheHttpInterceptor.cs" />
|
||||
<Compile Include="Indexers\BitMeTv\BitMeTv.cs" />
|
||||
<Compile Include="Indexers\BitMeTv\BitMeTvSettings.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<NzbDronePersistentConnection>();
|
||||
|
||||
Container.Register<INancyBootstrapper, NancyBootstrapper>();
|
||||
Container.Register<IHttpDispatcher, FallbackHttpDispatcher>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<IHttpDispatcher, FallbackHttpDispatcher>();
|
||||
}
|
||||
|
||||
public static IContainer Build(IStartupContext startupContext)
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -162,6 +162,114 @@
|
|||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Proxy Settings</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Use Proxy</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="proxyEnabled" class="form-control x-proxy"/>
|
||||
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="x-proxy-settings">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Proxy Type</label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<select name="proxyType" class="form-control">
|
||||
<option value="http" selected="selected">HTTP(S)</option>
|
||||
<option value="socks4">Socks4</option>
|
||||
<option value="socks5">Socks5 (This option supports Tor)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Hostname</label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<input type="text" placeholder="localhost" name="proxyHostname" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Port</label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<input type="number" placeholder="8080" name="proxyPort" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Username</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-4 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="You only need to enter a username and password if one is required. Leave them blank otherwise."/>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4 col-sm-pull-1">
|
||||
<input type="text" name="proxyUsername" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Password</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-4 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="You only need to enter a username and password if one is required. Leave them blank otherwise."/>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4 col-sm-pull-1">
|
||||
<input type="password" name="proxyPassword" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Addresses for the proxy to ignore</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-4 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="Use ',' as a separator, and '*.' as a wildcard for subdomains"/>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4 col-sm-pull-1">
|
||||
<input type="text" name="proxyBypassFilter" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Bypass Proxy for Local Addresses</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="proxyBypassLocalAddresses" class="form-control"/>
|
||||
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Logging</legend>
|
||||
|
|
Loading…
Reference in New Issue