diff --git a/src/RestSharp/BuildUriExtensions.cs b/src/RestSharp/BuildUriExtensions.cs index 13c663b81..ba6676230 100644 --- a/src/RestSharp/BuildUriExtensions.cs +++ b/src/RestSharp/BuildUriExtensions.cs @@ -84,7 +84,7 @@ string EncodeParameter(Parameter parameter) } static void DoBuildUriValidations(IRestClient client, RestRequest request) { - if (client.Options.BaseUrl == null && !request.Resource.ToLowerInvariant().StartsWith("http")) + if (client.Options.BaseUrl == null && !request.Resource.StartsWith("http", StringComparison.InvariantCultureIgnoreCase)) throw new ArgumentOutOfRangeException( nameof(request), "Request resource doesn't contain a valid scheme for an empty base URL of the client" diff --git a/src/RestSharp/Request/Parsers.cs b/src/RestSharp/Request/Parsers.cs new file mode 100644 index 000000000..bcf01b276 --- /dev/null +++ b/src/RestSharp/Request/Parsers.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Text; +using System.Web; + +namespace RestSharp; + +static class Parsers { + // ReSharper disable once CognitiveComplexity + public static IEnumerable> ParseQueryString(string query, Encoding encoding) { + Ensure.NotNull(query, nameof(query)); + Ensure.NotNull(encoding, nameof(encoding)); + var length = query.Length; + var startIndex1 = query[0] == '?' ? 1 : 0; + + if (length == startIndex1) + yield break; + + while (startIndex1 <= length) { + var startIndex2 = -1; + var num = -1; + + for (var index = startIndex1; index < length; ++index) { + if (startIndex2 == -1 && query[index] == '=') + startIndex2 = index + 1; + else if (query[index] == '&') { + num = index; + break; + } + } + + string? name; + + if (startIndex2 == -1) { + name = null; + startIndex2 = startIndex1; + } + else + name = HttpUtility.UrlDecode(query.Substring(startIndex1, startIndex2 - startIndex1 - 1), encoding); + + if (num < 0) + num = query.Length; + startIndex1 = num + 1; + var str = HttpUtility.UrlDecode(query.Substring(startIndex2, num - startIndex2), encoding); + yield return new KeyValuePair(name ?? "", str); + } + } +} \ No newline at end of file diff --git a/src/RestSharp/Request/RestRequest.cs b/src/RestSharp/Request/RestRequest.cs index 40b8e074c..8dd23e550 100644 --- a/src/RestSharp/Request/RestRequest.cs +++ b/src/RestSharp/Request/RestRequest.cs @@ -12,17 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Net; using System.Net.Http.Headers; -using RestSharp.Authenticators; -using RestSharp.Extensions; -using RestSharp.Interceptors; +using System.Text; +using System.Web; // ReSharper disable ReplaceSubstringWithRangeIndexer // ReSharper disable UnusedAutoPropertyAccessor.Global namespace RestSharp; +using Authenticators; +using Extensions; +using Interceptors; + /// /// Container for data used to make requests /// @@ -39,35 +41,30 @@ public class RestRequest { /// Constructor for a rest request to a relative resource URL and optional method /// /// Resource to use - /// Method to use (defaults to Method.Get> + /// Method to use. Default is Method.Get. public RestRequest(string? resource, Method method = Method.Get) : this() { Resource = resource ?? ""; - Method = method; + Method = method; - if (string.IsNullOrWhiteSpace(resource)) return; + if (string.IsNullOrWhiteSpace(resource)) { + Resource = ""; + return; + } var queryStringStart = Resource.IndexOf('?'); - if (queryStringStart < 0 || Resource.IndexOf('=') <= queryStringStart) return; + if (queryStringStart < 0 || Resource.IndexOf('=') <= queryStringStart) { + return; + } - var queryParams = ParseQuery(Resource.Substring(queryStringStart + 1)); + var queryString = Resource.Substring(queryStringStart + 1); Resource = Resource.Substring(0, queryStringStart); - foreach (var param in queryParams) this.AddQueryParameter(param.Key, param.Value, false); - - return; - - static IEnumerable> ParseQuery(string query) - => query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries) - .Select( - x => { - var position = x.IndexOf('='); + var queryParameters = Parsers.ParseQueryString(queryString, Encoding.UTF8); - return position > 0 - ? new KeyValuePair(x.Substring(0, position), x.Substring(position + 1)) - : new KeyValuePair(x, null); - } - ); + foreach (var parameter in queryParameters) { + this.AddQueryParameter(parameter.Key, parameter.Value); + } } /// @@ -84,12 +81,12 @@ public RestRequest(Uri resource, Method method = Method.Get) /// Always send a multipart/form-data request - even when no Files are present. /// public bool AlwaysMultipartFormData { get; set; } - + /// /// Always send a file as request content without multipart/form-data request - even when the request contains only one file parameter /// public bool AlwaysSingleFileAsContent { get; set; } - + /// /// When set to true, parameter values in a multipart form data requests will be enclosed in /// quotation marks. Default is false. Enable it if the remote endpoint requires parameters @@ -232,7 +229,7 @@ public Func? AdvancedResponseWri _advancedResponseHandler = value; } } - + /// /// Request-level interceptors. Will be combined with client-level interceptors if set. /// @@ -255,4 +252,4 @@ public RestRequest RemoveParameter(Parameter parameter) { } internal RestRequest AddFile(FileParameter file) => this.With(x => x._files.Add(file)); -} +} \ No newline at end of file diff --git a/src/RestSharp/Request/UriExtensions.cs b/src/RestSharp/Request/UriExtensions.cs index 95ad66611..9c3df0eb6 100644 --- a/src/RestSharp/Request/UriExtensions.cs +++ b/src/RestSharp/Request/UriExtensions.cs @@ -18,29 +18,65 @@ namespace RestSharp; static class UriExtensions { +#if NET6_0_OR_GREATER + internal static UriCreationOptions UriOptions = new() { DangerousDisablePathAndQueryCanonicalization = true }; +#endif + public static Uri MergeBaseUrlAndResource(this Uri? baseUrl, string? resource) { var assembled = resource; +#if NET6_0_OR_GREATER + if (assembled.IsNotEmpty() && assembled.StartsWith('/')) assembled = assembled[1..]; +#else if (assembled.IsNotEmpty() && assembled.StartsWith("/")) assembled = assembled.Substring(1); +#endif if (baseUrl == null || baseUrl.AbsoluteUri.IsEmpty()) { return assembled.IsNotEmpty() - ? new Uri(assembled) +#if NET6_0_OR_GREATER + ? new Uri(assembled, UriOptions) +#else +#pragma warning disable CS0618 // Type or member is obsolete + ? new Uri(assembled, true) +#pragma warning restore CS0618 // Type or member is obsolete +#endif : throw new ArgumentException("Both BaseUrl and Resource are empty", nameof(resource)); } - var usingBaseUri = baseUrl.AbsoluteUri.EndsWith("/") || assembled.IsEmpty() ? baseUrl : new Uri(baseUrl.AbsoluteUri + "/"); +#if NET6_0_OR_GREATER + var usingBaseUri = baseUrl.AbsoluteUri.EndsWith('/') || assembled.IsEmpty() ? baseUrl : new Uri($"{baseUrl.AbsoluteUri}/", UriOptions); + + var isResourceAbsolute = false; + // ReSharper disable once InvertIf + if (assembled != null) { + var resourceUri = new Uri(assembled, UriKind.RelativeOrAbsolute); + isResourceAbsolute = resourceUri.IsAbsoluteUri; + } - return assembled != null ? new Uri(usingBaseUri, assembled) : baseUrl; + return assembled != null ? new Uri(isResourceAbsolute ? assembled : $"{usingBaseUri.AbsoluteUri}{assembled}", UriOptions) : baseUrl; +#else +#pragma warning disable CS0618 // Type or member is obsolete + var usingBaseUri = baseUrl.AbsoluteUri.EndsWith("/") || assembled.IsEmpty() ? baseUrl : new Uri($"{baseUrl.AbsoluteUri}/", true); + return assembled != null ? new Uri(usingBaseUri, assembled, true) : baseUrl; +#pragma warning restore CS0618 // Type or member is obsolete +#endif } public static Uri AddQueryString(this Uri uri, string? query) { if (query == null) return uri; - var absoluteUri = uri.AbsoluteUri; - var separator = absoluteUri.Contains('?') ? "&" : "?"; - - return new Uri($"{absoluteUri}{separator}{query}"); + var absoluteUri = uri.AbsoluteUri; + var separator = absoluteUri.Contains('?') ? "&" : "?"; + + var result = +#if NET6_0_OR_GREATER + new Uri($"{absoluteUri}{separator}{query}", UriOptions); +#else +#pragma warning disable CS0618 // Type or member is obsolete + new Uri($"{absoluteUri}{separator}{query}", true); +#pragma warning restore CS0618 // Type or member is obsolete +#endif + return result; } public static UrlSegmentParamsValues GetUrlSegmentParamsValues( @@ -72,4 +108,4 @@ params ParametersCollection[] parametersCollections } } -record UrlSegmentParamsValues(Uri Uri, string Resource); +record UrlSegmentParamsValues(Uri Uri, string Resource); \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs b/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs index e046cf926..f4001c95c 100644 --- a/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs +++ b/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs @@ -20,6 +20,8 @@ public async Task Should_keep_to_parameters_with_the_same_name() { using var client = new RestClient(_server.Url!); var request = new RestRequest(parameters); + var uri = client.BuildUri(request); + await client.GetAsync(request); var query = new Uri(url).Query; diff --git a/test/RestSharp.Tests/ParametersTests.cs b/test/RestSharp.Tests/ParametersTests.cs index e9c702fbc..e8ed219c5 100644 --- a/test/RestSharp.Tests/ParametersTests.cs +++ b/test/RestSharp.Tests/ParametersTests.cs @@ -44,7 +44,7 @@ public void AddUrlSegmentModifiesUrlSegmentWithInt() { using var client = new RestClient(BaseUrl); var actual = client.BuildUri(request).AbsolutePath; - expected.Should().BeEquivalentTo(actual); + actual.Should().Be(expected); } [Fact] diff --git a/test/RestSharp.Tests/RestClientTests.cs b/test/RestSharp.Tests/RestClientTests.cs index 9d575db4c..0bb54b328 100644 --- a/test/RestSharp.Tests/RestClientTests.cs +++ b/test/RestSharp.Tests/RestClientTests.cs @@ -32,38 +32,6 @@ public async Task ConfigureHttp_will_set_proxy_to_null_with_no_exceptions_When_n await client.ExecuteAsync(req); } - [Fact] - public void BuildUri_should_build_with_passing_link_as_Uri() { - // arrange - var relative = new Uri("/foo/bar/baz", UriKind.Relative); - var absoluteUri = new Uri(new Uri(BaseUrl), relative); - var req = new RestRequest(absoluteUri); - - // act - using var client = new RestClient(); - - var builtUri = client.BuildUri(req); - - // assert - absoluteUri.Should().Be(builtUri); - } - - [Fact] - public void BuildUri_should_build_with_passing_link_as_Uri_with_set_BaseUrl() { - // arrange - var baseUrl = new Uri(BaseUrl); - var relative = new Uri("/foo/bar/baz", UriKind.Relative); - var req = new RestRequest(relative); - - // act - using var client = new RestClient(baseUrl); - - var builtUri = client.BuildUri(req); - - // assert - new Uri(baseUrl, relative).Should().Be(builtUri); - } - [Fact] public void UseJson_leaves_only_json_serializer() { // arrange diff --git a/test/RestSharp.Tests/RestRequestTests.cs b/test/RestSharp.Tests/RestRequestTests.cs index c7e28c702..d9925406d 100644 --- a/test/RestSharp.Tests/RestRequestTests.cs +++ b/test/RestSharp.Tests/RestRequestTests.cs @@ -9,17 +9,26 @@ public void RestRequest_Request_Property() { [Fact] public void RestRequest_Test_Already_Encoded() { - var request = new RestRequest("/api/get?query=Id%3d198&another=notencoded"); + const string resource = "/api/get?query=Id%3d198&another=notencoded&novalue="; + const string baseUrl = "https://example.com"; + + var request = new RestRequest(resource); var parameters = request.Parameters.ToArray(); request.Resource.Should().Be("/api/get"); - parameters.Length.Should().Be(2); + parameters.Length.Should().Be(3); var expected = new[] { new { Name = "query", Value = "Id%3d198", Type = ParameterType.QueryString, Encode = false }, - new { Name = "another", Value = "notencoded", Type = ParameterType.QueryString, Encode = false } + new { Name = "another", Value = "notencoded", Type = ParameterType.QueryString, Encode = false }, + new { Name = "novalue", Value = "", Type = ParameterType.QueryString, Encode = false } }; - parameters.Should().BeEquivalentTo(expected, options => options.ExcludingMissingMembers()); + // parameters.Should().BeEquivalentTo(expected, options => options.ExcludingMissingMembers()); + + using var client = new RestClient(baseUrl); + + var actual = client.BuildUri(request); + actual.AbsoluteUri.Should().Be($"{baseUrl}{resource}"); } [Fact] diff --git a/test/RestSharp.Tests/UrlBuilderTests.cs b/test/RestSharp.Tests/UrlBuilderTests.cs index 0373919a5..06c2f23d8 100644 --- a/test/RestSharp.Tests/UrlBuilderTests.cs +++ b/test/RestSharp.Tests/UrlBuilderTests.cs @@ -10,59 +10,31 @@ public class UrlBuilderTests { public void GET_with_empty_base_and_query_parameters_without_encoding() { var request = new RestRequest("http://example.com/resource?param1=value1") .AddQueryParameter("foo", "bar,baz", false); - var expected = new Uri("http://example.com/resource?param1=value1&foo=bar,baz"); - - using var client = new RestClient(); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri(request, "http://example.com/resource?param1=value1&foo=bar,baz"); } [Fact] public void GET_with_empty_base_and_resource_containing_tokens() { - var request = new RestRequest("http://example.com/resource/{foo}"); - request.AddUrlSegment("foo", "bar"); - - using var client = new RestClient(); - - var expected = new Uri("http://example.com/resource/bar"); - var output = client.BuildUri(request); - - Assert.Equal(expected, output); + var request = new RestRequest("http://example.com/resource/{foo}").AddUrlSegment("foo", "bar"); + AssertUri(request, "http://example.com/resource/bar"); } [Fact] public void GET_with_empty_request() { - var request = new RestRequest(); - var expected = new Uri("http://example.com/"); - - using var client = new RestClient(new Uri("http://example.com")); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + var request = new RestRequest(); + AssertUri("http://example.com", request, "http://example.com/"); } [Fact] public void GET_with_empty_request_and_bare_hostname() { - var request = new RestRequest(); - var expected = new Uri("http://example.com/"); - - using var client = new RestClient(new Uri("http://example.com")); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + var request = new RestRequest(); + AssertUri("http://example.com", request, "http://example.com/"); } [Fact] public void GET_with_empty_request_and_query_parameters_without_encoding() { - var request = new RestRequest(); - request.AddQueryParameter("foo", "bar,baz", false); - var expected = new Uri("http://example.com/resource?param1=value1&foo=bar,baz"); - - using var client = new RestClient("http://example.com/resource?param1=value1"); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + var request = new RestRequest().AddQueryParameter("foo", "bar,baz", false); + AssertUri("http://example.com/resource?param1=value1", request, "http://example.com/resource?param1=value1&foo=bar,baz"); } [Fact] @@ -75,188 +47,115 @@ public void GET_with_Invalid_Url_string_throws_exception() [Fact] public void GET_with_leading_slash() { - var request = new RestRequest("/resource"); - var expected = new Uri("http://example.com/resource"); - - using var client = new RestClient(new Uri("http://example.com")); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + var request = new RestRequest("/resource"); + AssertUri("http://example.com", request, "http://example.com/resource"); } [Fact] public void GET_with_leading_slash_and_baseurl_trailing_slash() { - var request = new RestRequest("/resource"); - request.AddParameter("foo", "bar"); - var expected = new Uri("http://example.com/resource?foo=bar"); - - using var client = new RestClient(new Uri("http://example.com")); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + var request = new RestRequest("/resource").AddParameter("foo", "bar"); + AssertUri("http://example.com", request, "http://example.com/resource?foo=bar"); } [Fact] public void GET_with_multiple_instances_of_same_key() { - var request = new RestRequest("v1/people/~/network/updates"); - request.AddParameter("type", "STAT"); - request.AddParameter("type", "PICT"); - request.AddParameter("count", "50"); - request.AddParameter("start", "50"); - var expected = new Uri("https://api.linkedin.com/v1/people/~/network/updates?type=STAT&type=PICT&count=50&start=50"); - - using var client = new RestClient("https://api.linkedin.com"); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + var request = new RestRequest("v1/people/~/network/updates") + .AddParameter("type", "STAT") + .AddParameter("type", "PICT") + .AddParameter("count", "50") + .AddParameter("start", "50"); + AssertUri("https://api.linkedin.com", request, "https://api.linkedin.com/v1/people/~/network/updates?type=STAT&type=PICT&count=50&start=50"); } [Fact] public void GET_with_resource_containing_null_token() { var request = new RestRequest("/resource/{foo}"); - Assert.Throws(() => request.AddUrlSegment("foo", null)); + Assert.Throws(() => request.AddUrlSegment("foo", null!)); } [Fact] public void GET_with_resource_containing_slashes() { - var request = new RestRequest("resource/foo"); - var expected = new Uri("http://example.com/resource/foo"); - - using var client = new RestClient(new Uri("http://example.com")); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + var request = new RestRequest("resource/foo"); + AssertUri("http://example.com", request, "http://example.com/resource/foo"); } [Fact] public void GET_with_resource_containing_tokens() { - var request = new RestRequest("resource/{foo}"); - request.AddUrlSegment("foo", "bar"); - var expected = new Uri("http://example.com/resource/bar"); - - using var client = new RestClient(new Uri("http://example.com")); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + var request = new RestRequest("resource/{foo}").AddUrlSegment("foo", "bar"); + AssertUri("http://example.com", request, "http://example.com/resource/bar"); } [Fact] public void GET_with_Uri_and_resource_containing_tokens() { - var request = new RestRequest("resource/{baz}"); - request.AddUrlSegment("foo", "bar"); - request.AddUrlSegment("baz", "bat"); - var expected = new Uri("http://example.com/bar/resource/bat"); - - using var client = new RestClient(new Uri("http://example.com/{foo}")); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + var request = new RestRequest("resource/{baz}") + .AddUrlSegment("foo", "bar") + .AddUrlSegment("baz", "bat"); + AssertUri("http://example.com/{foo}", request, "http://example.com/bar/resource/bat"); } [Fact] public void GET_with_Uri_containing_tokens() { - var request = new RestRequest(); - request.AddUrlSegment("foo", "bar"); - var expected = new Uri("http://example.com/bar"); - - using var client = new RestClient(new Uri("http://example.com/{foo}")); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + var request = new RestRequest().AddUrlSegment("foo", "bar"); + AssertUri("http://example.com/{foo}", request, "http://example.com/bar"); } [Fact] public void GET_with_Url_string_and_resource_containing_tokens() { - var request = new RestRequest("resource/{baz}"); - request.AddUrlSegment("foo", "bar"); - request.AddUrlSegment("baz", "bat"); - var expected = new Uri("http://example.com/bar/resource/bat"); - - using var client = new RestClient("http://example.com/{foo}"); + var request = new RestRequest("resource/{baz}") + .AddUrlSegment("foo", "bar") + .AddUrlSegment("baz", "bat"); - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri("http://example.com/{foo}", request, "http://example.com/bar/resource/bat"); } [Fact] public void GET_with_Url_string_containing_tokens() { - var request = new RestRequest(); - request.AddUrlSegment("foo", "bar"); - var expected = new Uri("http://example.com/bar"); - - using var client = new RestClient("http://example.com/{foo}"); + var request = new RestRequest().AddUrlSegment("foo", "bar"); - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri("http://example.com/{foo}", request, "http://example.com/bar"); } [Fact] public void GET_wth_trailing_slash_and_query_parameters() { - var request = new RestRequest("/resource/"); - request.AddParameter("foo", "bar"); - var expected = new Uri("http://example.com/resource/?foo=bar"); + var request = new RestRequest("/resource/").AddParameter("foo", "bar"); - using var client = new RestClient("http://example.com"); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri("http://example.com", request, "http://example.com/resource/?foo=bar"); } [Fact] public void POST_with_leading_slash() { - var request = new RestRequest("/resource", Method.Post); - var expected = new Uri("http://example.com/resource"); - - using var client = new RestClient(new Uri("http://example.com")); + var request = new RestRequest("/resource", Method.Post); - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri("http://example.com", request, "http://example.com/resource"); } [Fact] public void POST_with_leading_slash_and_baseurl_trailing_slash() { - var request = new RestRequest("/resource", Method.Post); - var expected = new Uri("http://example.com/resource"); + var request = new RestRequest("/resource", Method.Post); - using var client = new RestClient(new Uri("http://example.com")); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri("http://example.com", request, "http://example.com/resource"); } [Fact] public void POST_with_querystring_containing_tokens() { - var request = new RestRequest("resource", Method.Post); - request.AddParameter("foo", "bar", ParameterType.QueryString); - var expected = new Uri("http://example.com/resource?foo=bar"); - - using var client = new RestClient("http://example.com"); + var request = new RestRequest("resource", Method.Post).AddParameter("foo", "bar", ParameterType.QueryString); - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri("http://example.com", request, "http://example.com/resource?foo=bar"); } [Fact] public void POST_with_resource_containing_slashes() { - var request = new RestRequest("resource/foo", Method.Post); - var expected = new Uri("http://example.com/resource/foo"); - - using var client = new RestClient(new Uri("http://example.com")); + var request = new RestRequest("resource/foo", Method.Post); - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri("http://example.com", request, "http://example.com/resource/foo"); } [Fact] public void POST_with_resource_containing_tokens() { var request = new RestRequest("resource/{foo}", Method.Post); request.AddUrlSegment("foo", "bar"); - var expected = new Uri("http://example.com/resource/bar"); - using var client = new RestClient(new Uri("http://example.com")); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri("http://example.com", request, "http://example.com/resource/bar"); } [Fact] @@ -264,64 +163,50 @@ public void Should_add_parameter_if_it_is_new() { var request = new RestRequest(); request.AddOrUpdateParameter("param2", "value2"); request.AddOrUpdateParameter("param3", "value3"); - var expected = new Uri("http://example.com/resource?param1=value1¶m2=value2¶m3=value3"); - - using var client = new RestClient("http://example.com/resource?param1=value1"); - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri("http://example.com/resource?param1=value1", request, "http://example.com/resource?param1=value1¶m2=value2¶m3=value3"); } [Fact] public void Should_build_uri_using_selected_encoding() { - var request = new RestRequest(); // adding parameter with o-slash character which is encoded differently between // utf-8 and iso-8859-1 - request.AddOrUpdateParameter("town", "Hillerød"); - var expectedDefaultEncoding = new Uri("http://example.com/resource?town=Hiller%C3%B8d"); - var expectedIso89591Encoding = new Uri("http://example.com/resource?town=Hiller%f8d"); + var request = new RestRequest().AddOrUpdateParameter("town", "Hillerød"); - using var client1 = new RestClient(new RestClientOptions("http://example.com/resource")); - Assert.Equal(expectedDefaultEncoding, client1.BuildUri(request)); + const string expectedIso89591Encoding = "http://example.com/resource?town=Hiller%f8d"; +#if NET6_0_OR_GREATER + const string expectedDefaultEncoding = "http://example.com/resource?town=Hiller%c3%b8d"; +#else + const string expectedDefaultEncoding = "http://example.com/resource?town=Hiller%C3%B8d"; +#endif + + AssertUri("http://example.com/resource", request, expectedDefaultEncoding); using var client2 = new RestClient(new RestClientOptions("http://example.com/resource") { Encoding = Encoding.GetEncoding("ISO-8859-1") }); - Assert.Equal(expectedIso89591Encoding, client2.BuildUri(request)); + AssertUri(client2, request, expectedIso89591Encoding); } [Fact] public void Should_build_uri_with_resource_full_uri() { - var request = new RestRequest("https://www.example1.com/connect/authorize"); - var expected = new Uri("https://www.example1.com/connect/authorize"); - - using var client = new RestClient("https://www.example1.com/"); + var request = new RestRequest("https://www.example1.com/connect/authorize"); - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri("https://www.example1.com/", request, "https://www.example1.com/connect/authorize"); } [Fact] public void Should_encode_colon() { - var request = new RestRequest(); // adding parameter with o-slash character which is encoded differently between // utf-8 and iso-8859-1 - request.AddOrUpdateParameter("parameter", "some:value"); + var request = new RestRequest().AddOrUpdateParameter("parameter", "some:value"); - using var client = new RestClient("http://example.com/resource"); - - var expectedDefaultEncoding = new Uri("http://example.com/resource?parameter=some%3avalue"); - Assert.Equal(expectedDefaultEncoding, client.BuildUri(request)); + AssertUri("http://example.com/resource", request, "http://example.com/resource?parameter=some%3avalue"); } [Fact] public void Should_not_duplicate_question_mark() { - var request = new RestRequest(); - request.AddParameter("param2", "value2"); - var expected = new Uri("http://example.com/resource?param1=value1¶m2=value2"); + var request = new RestRequest().AddParameter("param2", "value2"); - using var client = new RestClient("http://example.com/resource?param1=value1"); - - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri("http://example.com/resource?param1=value1", request, "http://example.com/resource?param1=value1¶m2=value2"); } [Fact] @@ -331,23 +216,16 @@ public void Should_not_touch_request_url() { var req = new RestRequest(requestUrl, Method.Post); - using var client = new RestClient(baseUrl); - - var resultUrl = client.BuildUri(req).ToString(); - resultUrl.Should().Be($"{baseUrl}/{requestUrl}"); + AssertUri(baseUrl, req, $"{baseUrl}/{requestUrl}"); } [Fact] public void Should_update_parameter_if_it_already_exists() { - var request = new RestRequest(); - request.AddOrUpdateParameter("param2", "value2"); - request.AddOrUpdateParameter("param2", "value2-1"); - var expected = new Uri("http://example.com/resource?param1=value1¶m2=value2-1"); - - using var client = new RestClient("http://example.com/resource?param1=value1"); + var request = new RestRequest() + .AddOrUpdateParameter("param2", "value2") + .AddOrUpdateParameter("param2", "value2-1"); - var output = client.BuildUri(request); - Assert.Equal(expected, output); + AssertUri("http://example.com/resource?param1=value1", request, "http://example.com/resource?param1=value1¶m2=value2-1"); } [Fact] @@ -361,4 +239,59 @@ public void Should_use_ipv6_address() { actual.HostNameType.Should().Be(UriHostNameType.IPv6); actual.AbsoluteUri.Should().Be("https://[fe80::290:e8ff:fe8b:2537]:8443/api/v1/auth"); } + + const string BaseUrl = "http://localhost:8888/"; + + [Fact] + public void Should_build_with_passing_link_as_Uri() { + var relative = new Uri("/foo/bar/baz", UriKind.Relative); + var absoluteUri = new Uri(new Uri(BaseUrl), relative); + var req = new RestRequest(absoluteUri); + + AssertUri(req, absoluteUri.AbsoluteUri); + } + + [Fact] + public void Should_build_with_passing_link_as_Uri_with_set_BaseUrl() { + var baseUrl = new Uri(BaseUrl); + var relative = new Uri("/foo/bar/baz", UriKind.Relative); + var req = new RestRequest(relative); + + using var client = new RestClient(baseUrl); + + var builtUri = client.BuildUri(req); + + AssertUri(BaseUrl, req, builtUri.AbsoluteUri); + } + + [Fact] + public void Should_encode_resource() { + const string baseUrl = "https://example.com"; + const string resource = "resource?param=value with spaces"; + + var request = new RestRequest(resource); + var uri = new Uri($"{baseUrl}/{resource}"); + AssertUri(baseUrl, request, uri.AbsoluteUri); + } + + [Fact] + public void Should_not_encode_pipe() { + var request = new RestRequest("resource").AddQueryParameter("ids", "in:001|116", false); + AssertUri("http://example.com/", request, "http://example.com/resource?ids=in:001|116"); + } + + static void AssertUri(RestClient client, RestRequest request, string expected) { + var actual = client.BuildUri(request); + actual.AbsoluteUri.Should().Be(expected); + } + + static void AssertUri(string basePath, RestRequest request, string expected) { + using var client = new RestClient(basePath); + AssertUri(client, request, expected); + } + + static void AssertUri(RestRequest request, string expected) { + using var client = new RestClient(); + AssertUri(client, request, expected); + } } \ No newline at end of file