From 23871503a2e0adf51f5bd391c0f90ceaf7d53bb7 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Tue, 1 Mar 2016 00:33:28 +0100 Subject: [PATCH] Replaced Uri with HttpUri. --- src/NzbDrone.Api/Health/HealthResource.cs | 3 +- .../Http/HttpClientFixture.cs | 2 +- .../Http/HttpRequestBuilderFixture.cs | 2 +- .../Http/Dispatchers/CurlHttpDispatcher.cs | 6 +- .../Http/Dispatchers/ManagedHttpDispatcher.cs | 2 +- src/NzbDrone.Common/Http/HttpClient.cs | 4 +- src/NzbDrone.Common/Http/HttpException.cs | 2 +- src/NzbDrone.Common/Http/HttpRequest.cs | 7 +- .../Http/HttpRequestBuilder.cs | 23 +- src/NzbDrone.Common/Http/HttpUri.cs | 261 ++++++++++++++++++ src/NzbDrone.Common/Http/UriExtensions.cs | 20 -- src/NzbDrone.Common/NzbDrone.Common.csproj | 3 +- .../Serializer/HttpUriConverter.cs | 31 +++ src/NzbDrone.Common/Serializer/Json.cs | 1 + .../Blackhole/TorrentBlackholeFixture.cs | 4 +- .../Blackhole/UsenetBlackholeFixture.cs | 4 +- .../QBittorrentTests/QBittorrentFixture.cs | 2 +- .../TorCacheHttpRequestInterceptorFixture.cs | 4 +- .../NewznabRequestGeneratorFixture.cs | 12 +- src/NzbDrone.Core/Download/DownloadService.cs | 4 +- src/NzbDrone.Core/HealthCheck/HealthCheck.cs | 17 +- .../Http/TorcacheHttpInterceptor.cs | 4 +- .../Indexers/HDBits/HDBitsParser.cs | 30 +- src/NzbDrone.Core/Indexers/HttpIndexerBase.cs | 2 +- src/NzbDrone.Core/Indexers/IndexerRequest.cs | 2 +- .../Indexers/Newznab/NewznabRssParser.cs | 2 +- src/NzbDrone.Core/Indexers/RssParser.cs | 10 +- .../Indexers/Torznab/TorznabRssParser.cs | 2 +- .../MetadataSource/SkyHook/SkyHookProxy.cs | 1 - 29 files changed, 356 insertions(+), 111 deletions(-) create mode 100644 src/NzbDrone.Common/Http/HttpUri.cs delete mode 100644 src/NzbDrone.Common/Http/UriExtensions.cs create mode 100644 src/NzbDrone.Common/Serializer/HttpUriConverter.cs diff --git a/src/NzbDrone.Api/Health/HealthResource.cs b/src/NzbDrone.Api/Health/HealthResource.cs index 96e7ec021..65a849cb4 100644 --- a/src/NzbDrone.Api/Health/HealthResource.cs +++ b/src/NzbDrone.Api/Health/HealthResource.cs @@ -1,5 +1,6 @@ using System; using NzbDrone.Api.REST; +using NzbDrone.Common.Http; using NzbDrone.Core.HealthCheck; namespace NzbDrone.Api.Health @@ -8,6 +9,6 @@ public class HealthResource : RestResource { public HealthCheckResult Type { get; set; } public string Message { get; set; } - public Uri WikiUrl { get; set; } + public HttpUri WikiUrl { get; set; } } } diff --git a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs index 60c23b904..2dfca280c 100644 --- a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs +++ b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs @@ -58,7 +58,7 @@ public void should_execute_typed_get() var response = Subject.Get(request); - response.Resource.Url.Should().Be(request.Url.AbsoluteUri); + response.Resource.Url.Should().Be(request.Url.FullUri); } [Test] diff --git a/src/NzbDrone.Common.Test/Http/HttpRequestBuilderFixture.cs b/src/NzbDrone.Common.Test/Http/HttpRequestBuilderFixture.cs index 9a1faf535..8bc2a265b 100644 --- a/src/NzbDrone.Common.Test/Http/HttpRequestBuilderFixture.cs +++ b/src/NzbDrone.Common.Test/Http/HttpRequestBuilderFixture.cs @@ -34,7 +34,7 @@ public void should_remove_duplicated_slashes() var request = builder.Resource("/v1/").Build(); - request.Url.AbsoluteUri.Should().Be("http://domain/v1/"); + request.Url.FullUri.Should().Be("http://domain/v1/"); } } diff --git a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs index 33c8ab0b3..745894ac1 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs @@ -65,7 +65,7 @@ public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies) return s * n; }; - curlEasy.Url = request.Url.AbsoluteUri; + curlEasy.Url = request.Url.FullUri; switch (request.Method) { case HttpMethod.GET: @@ -98,7 +98,7 @@ public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies) if (cookies != null) { - curlEasy.Cookie = cookies.GetCookieHeader(request.Url); + curlEasy.Cookie = cookies.GetCookieHeader((Uri)request.Url); } if (request.ContentData != null) @@ -179,7 +179,7 @@ private WebHeaderCollection ProcessHeaderStream(HttpRequest request, CookieConta { try { - cookies.SetCookies(request.Url, FixSetCookieHeader(setCookie)); + cookies.SetCookies((Uri)request.Url, FixSetCookieHeader(setCookie)); } catch (CookieException ex) { diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index 4aa83d43b..d563242df 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -8,7 +8,7 @@ public class ManagedHttpDispatcher : IHttpDispatcher { public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies) { - var webRequest = (HttpWebRequest)WebRequest.Create(request.Url); + var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url); // Deflate is not a standard and could break depending on implementation. // we should just stick with the more compatible Gzip diff --git a/src/NzbDrone.Common/Http/HttpClient.cs b/src/NzbDrone.Common/Http/HttpClient.cs index 4735abe5a..03ab0c46f 100644 --- a/src/NzbDrone.Common/Http/HttpClient.cs +++ b/src/NzbDrone.Common/Http/HttpClient.cs @@ -125,7 +125,7 @@ private CookieContainer PrepareRequestCookies(HttpRequest request) } } - var requestCookies = persistentCookieContainer.GetCookies(request.Url); + var requestCookies = persistentCookieContainer.GetCookies((Uri)request.Url); var cookieContainer = new CookieContainer(); @@ -146,7 +146,7 @@ private void HandleResponseCookies(HttpRequest request, CookieContainer cookieCo { var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer()); - var cookies = cookieContainer.GetCookies(request.Url); + var cookies = cookieContainer.GetCookies((Uri)request.Url); persistentCookieContainer.Add(cookies); } diff --git a/src/NzbDrone.Common/Http/HttpException.cs b/src/NzbDrone.Common/Http/HttpException.cs index fbe78132f..759a104c1 100644 --- a/src/NzbDrone.Common/Http/HttpException.cs +++ b/src/NzbDrone.Common/Http/HttpException.cs @@ -8,7 +8,7 @@ public class HttpException : Exception public HttpResponse Response { get; private set; } public HttpException(HttpRequest request, HttpResponse response) - : base(string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url.AbsoluteUri)) + : base(string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url)) { Request = request; Response = response; diff --git a/src/NzbDrone.Common/Http/HttpRequest.cs b/src/NzbDrone.Common/Http/HttpRequest.cs index 5ff3564fb..a855605dc 100644 --- a/src/NzbDrone.Common/Http/HttpRequest.cs +++ b/src/NzbDrone.Common/Http/HttpRequest.cs @@ -10,9 +10,9 @@ namespace NzbDrone.Common.Http { public class HttpRequest { - public HttpRequest(string uri, HttpAccept httpAccept = null) + public HttpRequest(string url, HttpAccept httpAccept = null) { - UrlBuilder = new UriBuilder(uri); + Url = new HttpUri(url); Headers = new HttpHeader(); AllowAutoRedirect = true; Cookies = new Dictionary(); @@ -28,8 +28,7 @@ public HttpRequest(string uri, HttpAccept httpAccept = null) } } - public UriBuilder UrlBuilder { get; private set; } - public Uri Url { get { return UrlBuilder.Uri; } } + public HttpUri Url { get; set; } public HttpMethod Method { get; set; } public HttpHeader Headers { get; set; } public byte[] ContentData { get; set; } diff --git a/src/NzbDrone.Common/Http/HttpRequestBuilder.cs b/src/NzbDrone.Common/Http/HttpRequestBuilder.cs index 02d2b600b..c175056dd 100644 --- a/src/NzbDrone.Common/Http/HttpRequestBuilder.cs +++ b/src/NzbDrone.Common/Http/HttpRequestBuilder.cs @@ -12,7 +12,7 @@ public class HttpRequestBuilder { public HttpMethod Method { get; set; } public HttpAccept HttpAccept { get; set; } - public Uri BaseUrl { get; private set; } + public HttpUri BaseUrl { get; private set; } public string ResourceUrl { get; set; } public List> QueryParams { get; private set; } public List> SuffixQueryParams { get; private set; } @@ -28,7 +28,7 @@ public class HttpRequestBuilder public HttpRequestBuilder(string baseUrl) { - BaseUrl = new Uri(baseUrl); + BaseUrl = new HttpUri(baseUrl); ResourceUrl = string.Empty; Method = HttpMethod.GET; QueryParams = new List>(); @@ -69,33 +69,28 @@ public virtual HttpRequestBuilder Clone() return clone; } - protected virtual Uri CreateUri() + protected virtual HttpUri CreateUri() { - var builder = new UriBuilder(new Uri(BaseUrl, ResourceUrl)); + var url = BaseUrl.CombinePath(ResourceUrl).AddQueryParams(QueryParams.Concat(SuffixQueryParams)); - foreach (var queryParam in QueryParams.Concat(SuffixQueryParams)) - { - builder.SetQueryParam(queryParam.Key, queryParam.Value); - } - if (Segments.Any()) { - var url = builder.Uri.ToString(); + var fullUri = url.FullUri; foreach (var segment in Segments) { - url = url.Replace(segment.Key, segment.Value); + fullUri = fullUri.Replace(segment.Key, segment.Value); } - builder = new UriBuilder(url); + url = new HttpUri(fullUri); } - return builder.Uri; + return url; } protected virtual HttpRequest CreateRequest() { - return new HttpRequest(CreateUri().ToString(), HttpAccept); + return new HttpRequest(CreateUri().FullUri, HttpAccept); } protected virtual void Apply(HttpRequest request) diff --git a/src/NzbDrone.Common/Http/HttpUri.cs b/src/NzbDrone.Common/Http/HttpUri.cs new file mode 100644 index 000000000..c8c83de66 --- /dev/null +++ b/src/NzbDrone.Common/Http/HttpUri.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using NzbDrone.Common.Extensions; + +namespace NzbDrone.Common.Http +{ + public class HttpUri : IEquatable + { + private static readonly Regex RegexUri = new Regex(@"^(?:(?[a-z]+):)?(?://(?[-A-Z0-9.]+)(?::(?[0-9]{1,5}))?)?(?(?:(?:(?<=^)|/)[^/?#\r\n]+)+/?|/)?(?:\?(?[^#\r\n]*))?(?:\#(?.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private readonly string _uri; + public string FullUri { get { return _uri; } } + + public HttpUri(string uri) + { + _uri = uri ?? string.Empty; + + Parse(); + } + + public HttpUri(string scheme, string host, int? port, string path, string query, string fragment) + { + StringBuilder builder = new StringBuilder(); + + if (scheme.IsNotNullOrWhiteSpace()) + { + builder.Append(scheme); + builder.Append(":"); + } + + if (host.IsNotNullOrWhiteSpace()) + { + builder.Append("//"); + builder.Append(host); + if (port.HasValue) + { + builder.Append(":"); + builder.Append(port); + } + } + + if (path.IsNotNullOrWhiteSpace()) + { + if (host.IsNotNullOrWhiteSpace() || path.StartsWith("/")) + { + builder.Append('/'); + } + builder.Append(path.TrimStart('/')); + } + + if (query.IsNotNullOrWhiteSpace()) + { + builder.Append('?'); + builder.Append(query); + } + + if (fragment.IsNotNullOrWhiteSpace()) + { + builder.Append('#'); + builder.Append(fragment); + } + + _uri = builder.ToString(); + + Parse(); + } + + private void Parse() + { + var match = RegexUri.Match(_uri); + + var scheme = match.Groups["scheme"]; + var host = match.Groups["host"]; + var port = match.Groups["port"]; + var path = match.Groups["path"]; + var query = match.Groups["query"]; + var fragment = match.Groups["fragment"]; + + if (!match.Success || scheme.Success && !host.Success && path.Success) + { + throw new ArgumentException("Uri didn't match expected pattern: " + _uri); + } + + Scheme = scheme.Value; + Host = host.Value; + Port = port.Success ? (int?)int.Parse(port.Value) : null; + Path = path.Value; + Query = query.Value; + Fragment = fragment.Value; + } + + public string Scheme { get; private set; } + public string Host { get; private set; } + public int? Port { get; private set; } + public string Path { get; private set; } + public string Query { get; private set; } + public string Fragment { get; private set; } + + private IList> _queryParams; + private IList> QueryParams + { + get + { + if (_queryParams == null) + { + var dict = new List>(); + + if (Query.IsNotNullOrWhiteSpace()) + { + foreach (var pair in Query.Split('&')) + { + var split = pair.Split(new[] { '=' }, 2); + + if (split.Length == 1) + { + dict.Add(new KeyValuePair(Uri.UnescapeDataString(split[0]), null)); + } + else + { + dict.Add(new KeyValuePair(Uri.UnescapeDataString(split[0]), Uri.UnescapeDataString(split[1]))); + } + } + } + + _queryParams = dict.AsReadOnly(); + } + return _queryParams; + } + } + + public HttpUri CombinePath(string path) + { + return new HttpUri(Scheme, Host, Port, CombinePath(Path, path), Query, Fragment); + } + + private static string CombinePath(string basePath, string relativePath) + { + if (relativePath.IsNullOrWhiteSpace()) + { + return basePath; + } + + var newPath = "/" + relativePath.TrimStart('/'); + + if (basePath != null && !relativePath.StartsWith("/")) + { + var baseSlashIndex = basePath.LastIndexOf('/'); + + if (baseSlashIndex == basePath.Length - 1) + { + newPath = basePath.TrimEnd('/') + newPath; + } + else if (baseSlashIndex != 0) + { + newPath = basePath.Substring(0, baseSlashIndex) + newPath; + } + } + + return newPath; + } + + public HttpUri SetQuery(string query) + { + return new HttpUri(Scheme, Host, Port, Path, query, Fragment); + } + + public HttpUri AddQueryParam(string key, object value) + { + var newQuery = string.Concat(Uri.EscapeDataString(key), "=", Uri.EscapeDataString(value.ToString())); + + if (Query.IsNotNullOrWhiteSpace()) + { + newQuery = string.Concat(Query, "&", newQuery); + } + + return SetQuery(newQuery); + } + + public HttpUri AddQueryParams(IEnumerable> queryParams) + { + var builder = new StringBuilder(); + builder.Append(Query); + + foreach (var pair in queryParams) + { + if (builder.Length != 0) + { + builder.Append("&"); + } + builder.Append(Uri.EscapeDataString(pair.Key)); + builder.Append("="); + builder.Append(Uri.EscapeDataString(pair.Value)); + } + + return SetQuery(builder.ToString()); + } + + + public override int GetHashCode() + { + return _uri.GetHashCode(); + } + + public override string ToString() + { + return _uri; + } + + public override bool Equals(object obj) + { + if (obj is string) + { + return _uri.Equals((string)obj); + } + else if (obj is Uri) + { + return _uri.Equals(((Uri)obj).OriginalString); + } + else + { + return Equals(obj as HttpUri); + } + } + + public bool Equals(HttpUri other) + { + if (object.ReferenceEquals(other, null)) return false; + + return _uri.Equals(other._uri); + } + + public static explicit operator Uri(HttpUri url) + { + return new Uri(url.FullUri); + } + + public static HttpUri operator +(HttpUri baseUrl, HttpUri relativeUrl) + { + if (relativeUrl.Scheme.IsNotNullOrWhiteSpace()) + { + return relativeUrl; + } + + if (relativeUrl.Host.IsNotNullOrWhiteSpace()) + { + return new HttpUri(baseUrl.Scheme, relativeUrl.Host, relativeUrl.Port, relativeUrl.Path, relativeUrl.Query, relativeUrl.Fragment); + } + + if (relativeUrl.Path.IsNotNullOrWhiteSpace()) + { + return new HttpUri(baseUrl.Scheme, baseUrl.Host, baseUrl.Port, HttpUri.CombinePath(baseUrl.Path, relativeUrl.Path), relativeUrl.Query, relativeUrl.Fragment); + } + + return new HttpUri(baseUrl.Scheme, baseUrl.Host, baseUrl.Port, baseUrl.Path, relativeUrl.Query, relativeUrl.Fragment); + } + } +} diff --git a/src/NzbDrone.Common/Http/UriExtensions.cs b/src/NzbDrone.Common/Http/UriExtensions.cs deleted file mode 100644 index d45d8c943..000000000 --- a/src/NzbDrone.Common/Http/UriExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using NzbDrone.Common.Extensions; - -namespace NzbDrone.Common.Http -{ - public static class UriExtensions - { - public static void SetQueryParam(this UriBuilder uriBuilder, string key, object value) - { - var query = uriBuilder.Query; - - if (query.IsNotNullOrWhiteSpace()) - { - query += "&"; - } - - uriBuilder.Query = query.Trim('?') + key + "=" + Uri.EscapeDataString(value.ToString()); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index 855975123..df9256683 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -162,6 +162,7 @@ + @@ -171,7 +172,6 @@ - @@ -197,6 +197,7 @@ + diff --git a/src/NzbDrone.Common/Serializer/HttpUriConverter.cs b/src/NzbDrone.Common/Serializer/HttpUriConverter.cs new file mode 100644 index 000000000..d11f76b9f --- /dev/null +++ b/src/NzbDrone.Common/Serializer/HttpUriConverter.cs @@ -0,0 +1,31 @@ +using System; +using Newtonsoft.Json; +using NzbDrone.Common.Http; + +namespace NzbDrone.Common.Serializer +{ + public class HttpUriConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteNull(); + } + else if (value is HttpUri) + { + writer.WriteValue((value as HttpUri).FullUri); + } + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return new HttpUri(reader.ReadAsString()); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(HttpUri); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Serializer/Json.cs b/src/NzbDrone.Common/Serializer/Json.cs index 0c52f849c..31e0d3f0b 100644 --- a/src/NzbDrone.Common/Serializer/Json.cs +++ b/src/NzbDrone.Common/Serializer/Json.cs @@ -26,6 +26,7 @@ static Json() SerializerSetting.Converters.Add(new StringEnumConverter { CamelCaseText = true }); //SerializerSetting.Converters.Add(new IntConverter()); SerializerSetting.Converters.Add(new VersionConverter()); + SerializerSetting.Converters.Add(new HttpUriConverter()); Serializer = JsonSerializer.Create(SerializerSetting); diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs index a7a4232e0..b34558361 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs @@ -112,7 +112,7 @@ public void Download_should_download_file_if_it_doesnt_exist() Subject.Download(remoteEpisode); - Mocker.GetMock().Verify(c => c.Get(It.Is(v => v.Url.AbsoluteUri == _downloadUrl)), Times.Once()); + Mocker.GetMock().Verify(c => c.Get(It.Is(v => v.Url.FullUri == _downloadUrl)), Times.Once()); Mocker.GetMock().Verify(c => c.OpenWriteStream(_filePath), Times.Once()); Mocker.GetMock().Verify(c => c.DownloadFile(It.IsAny(), It.IsAny()), Times.Never()); } @@ -128,7 +128,7 @@ public void Download_should_replace_illegal_characters_in_title() Subject.Download(remoteEpisode); - Mocker.GetMock().Verify(c => c.Get(It.Is(v => v.Url.AbsoluteUri == _downloadUrl)), Times.Once()); + Mocker.GetMock().Verify(c => c.Get(It.Is(v => v.Url.FullUri == _downloadUrl)), Times.Once()); Mocker.GetMock().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once()); Mocker.GetMock().Verify(c => c.DownloadFile(It.IsAny(), It.IsAny()), Times.Never()); } diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs index 933113f6b..e8f7e76c1 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs @@ -93,7 +93,7 @@ public void Download_should_download_file_if_it_doesnt_exist() Subject.Download(remoteEpisode); - Mocker.GetMock().Verify(c => c.Get(It.Is(v => v.Url.AbsoluteUri == _downloadUrl)), Times.Once()); + Mocker.GetMock().Verify(c => c.Get(It.Is(v => v.Url.FullUri == _downloadUrl)), Times.Once()); Mocker.GetMock().Verify(c => c.OpenWriteStream(_filePath), Times.Once()); Mocker.GetMock().Verify(c => c.DownloadFile(It.IsAny(), It.IsAny()), Times.Never()); } @@ -109,7 +109,7 @@ public void Download_should_replace_illegal_characters_in_title() Subject.Download(remoteEpisode); - Mocker.GetMock().Verify(c => c.Get(It.Is(v => v.Url.AbsoluteUri == _downloadUrl)), Times.Once()); + Mocker.GetMock().Verify(c => c.Get(It.Is(v => v.Url.FullUri == _downloadUrl)), Times.Once()); Mocker.GetMock().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once()); Mocker.GetMock().Verify(c => c.DownloadFile(It.IsAny(), It.IsAny()), Times.Never()); } diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs index 3845f5b99..524a8ee1e 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs @@ -53,7 +53,7 @@ protected void GivenRedirectToTorrent() httpHeader["Location"] = "http://test.sonarr.tv/not-a-real-torrent.torrent"; Mocker.GetMock() - .Setup(s => s.Get(It.Is(h => h.Url.AbsoluteUri == _downloadUrl))) + .Setup(s => s.Get(It.Is(h => h.Url.FullUri == _downloadUrl))) .Returns(r => new HttpResponse(r, httpHeader, new Byte[0], System.Net.HttpStatusCode.Found)); } diff --git a/src/NzbDrone.Core.Test/Http/TorCacheHttpRequestInterceptorFixture.cs b/src/NzbDrone.Core.Test/Http/TorCacheHttpRequestInterceptorFixture.cs index 57de87470..601ef163a 100644 --- a/src/NzbDrone.Core.Test/Http/TorCacheHttpRequestInterceptorFixture.cs +++ b/src/NzbDrone.Core.Test/Http/TorCacheHttpRequestInterceptorFixture.cs @@ -20,7 +20,7 @@ public void should_remove_query_params_from_torcache_request() var newRequest = Subject.PreRequest(request); - newRequest.Url.AbsoluteUri.Should().Be("http://torcache.net/download/123.torrent"); + newRequest.Url.FullUri.Should().Be("http://torcache.net/download/123.torrent"); } [Test] @@ -41,7 +41,7 @@ public void should_not_remove_query_params_from_other_requests(string url) var newRequest = Subject.PreRequest(request); - newRequest.Url.AbsoluteUri.Should().Be(url); + newRequest.Url.FullUri.Should().Be(url); } } } diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs index 9c1e61b10..0edb3cf34 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs @@ -71,7 +71,7 @@ public void should_not_have_duplicate_categories() var page = results.GetAllTiers().First().First(); - page.Url.Query.Should().Contain("&cat=1,2,3,4&"); + page.Url.FullUri.Should().Contain("&cat=1,2,3,4&"); } [Test] @@ -83,7 +83,7 @@ public void should_use_only_anime_categories_for_anime_search() var page = results.GetAllTiers().First().First(); - page.Url.Query.Should().Contain("&cat=3,4&"); + page.Url.FullUri.Should().Contain("&cat=3,4&"); } [Test] @@ -95,7 +95,7 @@ public void should_use_mode_search_for_anime() var page = results.GetAllTiers().First().First(); - page.Url.Query.Should().Contain("?t=search&"); + page.Url.FullUri.Should().Contain("?t=search&"); } [Test] @@ -107,9 +107,9 @@ public void should_return_subsequent_pages() var pages = results.GetAllTiers().First().Take(3).ToList(); - pages[0].Url.Query.Should().Contain("&offset=0&"); - pages[1].Url.Query.Should().Contain("&offset=100&"); - pages[2].Url.Query.Should().Contain("&offset=200&"); + pages[0].Url.FullUri.Should().Contain("&offset=0&"); + pages[1].Url.FullUri.Should().Contain("&offset=100&"); + pages[2].Url.FullUri.Should().Contain("&offset=200&"); } [Test] diff --git a/src/NzbDrone.Core/Download/DownloadService.cs b/src/NzbDrone.Core/Download/DownloadService.cs index d6da07e32..4f76b1507 100644 --- a/src/NzbDrone.Core/Download/DownloadService.cs +++ b/src/NzbDrone.Core/Download/DownloadService.cs @@ -56,8 +56,8 @@ public void DownloadReport(RemoteEpisode remoteEpisode) // Limit grabs to 2 per second. if (remoteEpisode.Release.DownloadUrl.IsNotNullOrWhiteSpace() && !remoteEpisode.Release.DownloadUrl.StartsWith("magnet:")) { - var uri = new Uri(remoteEpisode.Release.DownloadUrl); - _rateLimitService.WaitAndPulse(uri.Host, TimeSpan.FromSeconds(2)); + var url = new HttpUri(remoteEpisode.Release.DownloadUrl); + _rateLimitService.WaitAndPulse(url.Host, TimeSpan.FromSeconds(2)); } string downloadClientId; diff --git a/src/NzbDrone.Core/HealthCheck/HealthCheck.cs b/src/NzbDrone.Core/HealthCheck/HealthCheck.cs index 49b3c9f41..23b2f7dcf 100644 --- a/src/NzbDrone.Core/HealthCheck/HealthCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/HealthCheck.cs @@ -1,5 +1,6 @@ using System; using System.Text.RegularExpressions; +using NzbDrone.Common.Http; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.HealthCheck @@ -11,7 +12,7 @@ public class HealthCheck : ModelBase public Type Source { get; set; } public HealthCheckResult Type { get; set; } public string Message { get; set; } - public Uri WikiUrl { get; set; } + public HttpUri WikiUrl { get; set; } public HealthCheck(Type source) { @@ -32,19 +33,9 @@ private static string MakeWikiFragment(string message) return "#" + CleanFragmentRegex.Replace(message.ToLower(), string.Empty).Replace(' ', '-'); } - private static Uri MakeWikiUrl(string fragment) + private static HttpUri MakeWikiUrl(string fragment) { - var rootUri = new Uri("https://github.com/Sonarr/Sonarr/wiki/Health-checks"); - if (fragment.StartsWith("#")) - { // Mono doesn't understand # and generates a different url than windows. - return new Uri(rootUri + fragment); - } - else - { - var fragmentUri = new Uri(fragment, UriKind.Relative); - - return new Uri(rootUri, fragmentUri); - } + return new HttpUri("https://github.com/Sonarr/Sonarr/wiki/Health-checks") + new HttpUri(fragment); } } diff --git a/src/NzbDrone.Core/Http/TorcacheHttpInterceptor.cs b/src/NzbDrone.Core/Http/TorcacheHttpInterceptor.cs index 857cf4048..eeb0dc8f3 100644 --- a/src/NzbDrone.Core/Http/TorcacheHttpInterceptor.cs +++ b/src/NzbDrone.Core/Http/TorcacheHttpInterceptor.cs @@ -13,9 +13,9 @@ public HttpRequest PreRequest(HttpRequest request) { // torcache behaves strangely when it has query params and/or no Referer or browser User-Agent. // It's a bit vague, and we don't need the query params. So we remove the query params and set a Referer to be safe. - if (request.UrlBuilder.Host == "torcache.net") + if (request.Url.Host == "torcache.net") { - request.UrlBuilder.Query = string.Empty; + request.Url = request.Url.SetQuery(string.Empty); request.Headers.Add("Referer", request.Url.Scheme + @"://torcache.net/"); } diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBitsParser.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBitsParser.cs index ee960ce9a..97696078c 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBitsParser.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBitsParser.cs @@ -5,6 +5,7 @@ using System.Web; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NzbDrone.Common.Http; using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Parser.Model; @@ -71,33 +72,22 @@ public IList ParseResponse(IndexerResponse indexerResponse) private string GetDownloadUrl(long torrentId) { - var args = new NameValueCollection(2); - args["id"] = torrentId.ToString(); - args["passkey"] = _settings.ApiKey; + var url = new HttpUri(_settings.BaseUrl) + .CombinePath("/download.php") + .AddQueryParam("id", torrentId.ToString()) + .AddQueryParam("passkey", _settings.ApiKey); - return BuildUrl("/download.php", args); + return url.FullUri; } private string GetInfoUrl(long torrentId) { - var args = new NameValueCollection(1); - args["id"] = torrentId.ToString(); + var url = new HttpUri(_settings.BaseUrl) + .CombinePath("/details.php") + .AddQueryParam("id", torrentId.ToString()); - return BuildUrl("/details.php", args); + return url.FullUri; } - - private string BuildUrl(string path, NameValueCollection args) - { - var builder = new UriBuilder(_settings.BaseUrl); - builder.Path = path; - var queryString = HttpUtility.ParseQueryString(""); - - queryString.Add(args); - - builder.Query = queryString.ToString(); - - return builder.Uri.ToString(); - } } } diff --git a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs index b206de969..bae5dfbdd 100644 --- a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs @@ -137,7 +137,7 @@ protected virtual IList FetchReleases(IndexerPageableRequestChain p foreach (var request in pageableRequest) { - url = request.Url.AbsoluteUri; + url = request.Url.FullUri; var page = FetchPage(request, parser); diff --git a/src/NzbDrone.Core/Indexers/IndexerRequest.cs b/src/NzbDrone.Core/Indexers/IndexerRequest.cs index 374f567f9..187e46954 100644 --- a/src/NzbDrone.Core/Indexers/IndexerRequest.cs +++ b/src/NzbDrone.Core/Indexers/IndexerRequest.cs @@ -17,7 +17,7 @@ public IndexerRequest(HttpRequest httpRequest) HttpRequest = httpRequest; } - public Uri Url + public HttpUri Url { get { return HttpRequest.Url; } } diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabRssParser.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabRssParser.cs index 5e8b603ca..1ebe8945d 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabRssParser.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabRssParser.cs @@ -27,7 +27,7 @@ protected override bool PreProcess(IndexerResponse indexerResponse) throw new ApiKeyException("Invalid API key"); } - if (!indexerResponse.Request.Url.AbsoluteUri.Contains("apikey=") && (errorMessage == "Missing parameter" || errorMessage.Contains("apikey"))) + if (!indexerResponse.Request.Url.FullUri.Contains("apikey=") && (errorMessage == "Missing parameter" || errorMessage.Contains("apikey"))) { throw new ApiKeyException("Indexer requires an API key"); } diff --git a/src/NzbDrone.Core/Indexers/RssParser.cs b/src/NzbDrone.Core/Indexers/RssParser.cs index 1ccb5ed75..271c5da6b 100644 --- a/src/NzbDrone.Core/Indexers/RssParser.cs +++ b/src/NzbDrone.Core/Indexers/RssParser.cs @@ -9,6 +9,7 @@ using System.Xml.Linq; using NLog; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Parser.Model; @@ -266,14 +267,9 @@ protected virtual string ParseUrl(string value) try { - var url = new Uri(value, UriKind.RelativeOrAbsolute); + var url = _indexerResponse.HttpRequest.Url + new HttpUri(value); - if (!url.IsAbsoluteUri) - { - url = new Uri(_indexerResponse.HttpRequest.Url, url); - } - - return url.AbsoluteUri; + return url.FullUri; } catch (Exception ex) { diff --git a/src/NzbDrone.Core/Indexers/Torznab/TorznabRssParser.cs b/src/NzbDrone.Core/Indexers/Torznab/TorznabRssParser.cs index 58d1593f8..253386963 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/TorznabRssParser.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/TorznabRssParser.cs @@ -23,7 +23,7 @@ protected override bool PreProcess(IndexerResponse indexerResponse) if (code >= 100 && code <= 199) throw new ApiKeyException("Invalid API key"); - if (!indexerResponse.Request.Url.AbsoluteUri.Contains("apikey=") && errorMessage == "Missing parameter") + if (!indexerResponse.Request.Url.FullUri.Contains("apikey=") && errorMessage == "Missing parameter") { throw new ApiKeyException("Indexer requires an API key"); } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index a321aad68..3c1ca6740 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -84,7 +84,6 @@ public List SearchForNewSeries(string title) } } - var term = System.Web.HttpUtility.UrlEncode((title.ToLower().Trim())); var httpRequest = _requestBuilder.Create() .SetSegment("route", "search") .AddQueryParam("term", title.ToLower().Trim())