From 5644c25c955fdeb20e2aacc32c34fbbfc8b8d123 Mon Sep 17 00:00:00 2001 From: Mavaddat Javid <5055400+mavaddat@users.noreply.github.com> Date: Tue, 24 Oct 2023 09:08:54 -0400 Subject: [PATCH 1/6] Update IAuthenticator? Authenticator in usage.md (#2151) * Update usage.md Conform to updated API * Update usage.md Conform the latest API --- docs/usage.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 150df78f8..424582f78 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -60,10 +60,10 @@ Now, we need to implement the `GetToken` function in the class: ```csharp async Task GetToken() { - var options = new RestClientOptions(_baseUrl); - using var client = new RestClient(options) { + var options = new RestClientOptions(_baseUrl){ Authenticator = new HttpBasicAuthenticator(_clientId, _clientSecret), }; + using var client = new RestClient(options); var request = new RestRequest("oauth2/token") .AddParameter("grant_type", "client_credentials"); @@ -107,11 +107,11 @@ public class TwitterClient : ITwitterClient, IDisposable { readonly RestClient _client; public TwitterClient(string apiKey, string apiKeySecret) { - var options = new RestClientOptions("https://api.twitter.com/2"); - - _client = new RestClient(options) { + var options = new RestClientOptions("https://api.twitter.com/2"){ Authenticator = new TwitterAuthenticator("https://api.twitter.com", apiKey, apiKeySecret) }; + + _client = new RestClient(options); } public async Task GetUser(string user) { From 6880e582ebc3a510e2912930a033b38258406852 Mon Sep 17 00:00:00 2001 From: softworkz <4985349+softworkz@users.noreply.github.com> Date: Tue, 24 Oct 2023 15:09:16 +0200 Subject: [PATCH 2/6] Use case insensitive comparer (#2146) ..instead of ToLower() --- src/RestSharp/KnownHeaders.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RestSharp/KnownHeaders.cs b/src/RestSharp/KnownHeaders.cs index cf678c72a..c8909a7be 100644 --- a/src/RestSharp/KnownHeaders.cs +++ b/src/RestSharp/KnownHeaders.cs @@ -42,7 +42,7 @@ public static class KnownHeaders { LastModified }; - static readonly HashSet ContentHeadersHash = new(ContentHeaders.Select(x => x.ToLower())); + static readonly HashSet ContentHeadersHash = new(ContentHeaders, StringComparer.InvariantCultureIgnoreCase); - internal static bool IsContentHeader(string key) => ContentHeadersHash.Contains(key.ToLower()); + internal static bool IsContentHeader(string key) => ContentHeadersHash.Contains(key); } From d99d49437af21688152b556f6d3661d2e739b824 Mon Sep 17 00:00:00 2001 From: softworkz <4985349+softworkz@users.noreply.github.com> Date: Tue, 24 Oct 2023 15:10:09 +0200 Subject: [PATCH 3/6] Adjust serializer selection fallback procedure (#2147) In summary: 1. Choose by request type only when the server didn't provide a content type and it wasn't possible to detect it 2. If the server did provide a content type but we don't have a deserializer for it, try detection as a fallback --- src/RestSharp/Serializers/RestSerializers.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/RestSharp/Serializers/RestSerializers.cs b/src/RestSharp/Serializers/RestSerializers.cs index 0133db6cf..93aa464ba 100644 --- a/src/RestSharp/Serializers/RestSerializers.cs +++ b/src/RestSharp/Serializers/RestSerializers.cs @@ -87,13 +87,24 @@ public IRestSerializer GetSerializer(DataFormat dataFormat) if (string.IsNullOrWhiteSpace(response.Content)) return null; var contentType = response.ContentType ?? DetectContentType()?.Value; - if (contentType == null) return null; + + if (contentType == null) { + Serializers.TryGetValue(response.Request.RequestFormat, out var serializerByRequestFormat); + return serializerByRequestFormat?.GetSerializer().Deserializer; + } var serializer = Serializers.Values.FirstOrDefault(x => x.SupportsContentType(contentType)); - var factory = serializer ?? - (Serializers.ContainsKey(response.Request.RequestFormat) ? Serializers[response.Request.RequestFormat] : null); - return factory?.GetSerializer().Deserializer; + if (serializer == null) { + var detectedType = DetectContentType()?.Value; + + if (detectedType != null && detectedType != contentType) + { + serializer = Serializers.Values.FirstOrDefault(x => x.SupportsContentType(detectedType)); + } + } + + return serializer?.GetSerializer().Deserializer; ContentType? DetectContentType() => response.Content![0] switch { From f3642c1bdc937f02c5a178305c1a437b96a39189 Mon Sep 17 00:00:00 2001 From: Peter Breen <46024001+PetesBreenCoding@users.noreply.github.com> Date: Thu, 14 Dec 2023 17:07:39 +0000 Subject: [PATCH 4/6] Set UserAgent as a default header parameter (#2157) * Set UserAgent as a default header parameter instead of modifying the HttpClient instance. * Update usage document --------- Co-authored-by: Peter Breen --- docs/usage.md | 2 +- src/RestSharp/RestClient.cs | 23 ++++++++++----- test/RestSharp.Tests/RestClientTests.cs | 39 +++++++++++++++++++------ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 424582f78..b84918e2a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -594,7 +594,7 @@ One way of doing it is to use `RestClient` constructors that accept an instance - `BaseAddress` will be used to set the base address of the `HttpClient` instance if base address is not set there already. - `MaxTimeout` -- `UserAgent` will be set if the `User-Agent` header is not set on the `HttpClient` instance already. +- `UserAgent` will be added to the `RestClient.DefaultParameters` list as a HTTP header. This will be added to each request made by the `RestClient`, and the `HttpClient` instance will not be modified. This is to allow the `HttpClient` instance to be reused for scenarios where different `User-Agent` headers are required. - `Expect100Continue` Another option is to use a simple HTTP client factory as described [above](#simple-factory). diff --git a/src/RestSharp/RestClient.cs b/src/RestSharp/RestClient.cs index d4ae91867..ccaae9f21 100644 --- a/src/RestSharp/RestClient.cs +++ b/src/RestSharp/RestClient.cs @@ -73,6 +73,7 @@ public partial class RestClient : IRestClient { ConfigureSerializers(configureSerialization); Options = new ReadOnlyRestClientOptions(options); + DefaultParameters = new DefaultParameters(Options); if (useClientFactory) { _disposeHttpClient = false; @@ -83,8 +84,6 @@ public partial class RestClient : IRestClient { HttpClient = GetClient(); } - DefaultParameters = new DefaultParameters(Options); - HttpClient GetClient() { var handler = new HttpClientHandler(); ConfigureHttpMessageHandler(handler, Options); @@ -92,6 +91,7 @@ public partial class RestClient : IRestClient { var httpClient = new HttpClient(finalHandler); ConfigureHttpClient(httpClient, options); + ConfigureDefaultParameters(options); configureDefaultHeaders?.Invoke(httpClient.DefaultRequestHeaders); return httpClient; } @@ -181,7 +181,10 @@ public partial class RestClient : IRestClient { Options = new ReadOnlyRestClientOptions(opt); DefaultParameters = new DefaultParameters(Options); - if (options != null) ConfigureHttpClient(httpClient, options); + if (options != null) { + ConfigureHttpClient(httpClient, options); + ConfigureDefaultParameters(options); + } } /// @@ -218,11 +221,6 @@ public partial class RestClient : IRestClient { static void ConfigureHttpClient(HttpClient httpClient, RestClientOptions options) { if (options.MaxTimeout > 0) httpClient.Timeout = TimeSpan.FromMilliseconds(options.MaxTimeout); - if (options.UserAgent != null && - httpClient.DefaultRequestHeaders.UserAgent.All(x => $"{x.Product?.Name}/{x.Product?.Version}" != options.UserAgent)) { - httpClient.DefaultRequestHeaders.TryAddWithoutValidation(KnownHeaders.UserAgent, options.UserAgent); - } - if (options.Expect100Continue != null) httpClient.DefaultRequestHeaders.ExpectContinue = options.Expect100Continue; } @@ -270,6 +268,15 @@ public partial class RestClient : IRestClient { AcceptedContentTypes = Serializers.GetAcceptedContentTypes(); } + void ConfigureDefaultParameters(RestClientOptions options) { + if (options.UserAgent != null) { + if (!options.AllowMultipleDefaultParametersWithSameName + && DefaultParameters.Any(parameter => parameter.Type == ParameterType.HttpHeader && parameter.Name == KnownHeaders.UserAgent)) + DefaultParameters.RemoveParameter(KnownHeaders.UserAgent, ParameterType.HttpHeader); + DefaultParameters.AddParameter(Parameter.CreateParameter(KnownHeaders.UserAgent, options.UserAgent, ParameterType.HttpHeader)); + } + } + readonly bool _disposeHttpClient; bool _disposed; diff --git a/test/RestSharp.Tests/RestClientTests.cs b/test/RestSharp.Tests/RestClientTests.cs index 276f1e92a..0bd5dd073 100644 --- a/test/RestSharp.Tests/RestClientTests.cs +++ b/test/RestSharp.Tests/RestClientTests.cs @@ -118,20 +118,41 @@ public class RestClientTests { } [Fact] - public void ConfigureHttpClient_does_not_duplicate_user_agent_for_same_client() { + public void ConfigureDefaultParameters_sets_user_agent_new_httpClient_instance() { + // arrange + var clientOptions = new RestClientOptions(); + + // act + var restClient = new RestClient(clientOptions); + + //assert + Assert.Single( + restClient.DefaultParameters, + parameter => parameter.Type == ParameterType.HttpHeader && + parameter.Name == KnownHeaders.UserAgent && + parameter.Value is string valueAsString && + valueAsString == clientOptions.UserAgent); + + Assert.Empty(restClient.HttpClient.DefaultRequestHeaders.UserAgent); + } + + [Fact] + public void ConfigureDefaultParameters_sets_user_agent_given_httpClient_instance() { // arrange var httpClient = new HttpClient(); var clientOptions = new RestClientOptions(); // act - var unused = new RestClient(httpClient, clientOptions); - var dummy = new RestClient(httpClient, clientOptions); + var restClient = new RestClient(httpClient, clientOptions); - // assert - Assert.Contains( - httpClient.DefaultRequestHeaders.UserAgent, - agent => $"{agent.Product.Name}/{agent.Product.Version}" == clientOptions.UserAgent - ); - Assert.Single(httpClient.DefaultRequestHeaders.UserAgent); + //assert + Assert.Single( + restClient.DefaultParameters, + parameter => parameter.Type == ParameterType.HttpHeader && + parameter.Name == KnownHeaders.UserAgent && + parameter.Value is string valueAsString && + valueAsString == clientOptions.UserAgent); + + Assert.Empty(httpClient.DefaultRequestHeaders.UserAgent); } } From 122a2b08e9570de1267f06b5867554bd54d64d87 Mon Sep 17 00:00:00 2001 From: elia936 Date: Wed, 7 Feb 2024 12:37:06 +0200 Subject: [PATCH 5/6] Fixes OAuth1 signature with special characters (#2126, #1945) (#2127) * Fixes OAuth1 signature with special characters (#2126, #1945) * Adds test for parameters encoding --------- Co-authored-by: Eleonora Adova --- .../Authenticators/OAuth/OAuthTools.cs | 2 +- .../RestSharp.Tests.Integrated/OAuth1Tests.cs | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/RestSharp/Authenticators/OAuth/OAuthTools.cs b/src/RestSharp/Authenticators/OAuth/OAuthTools.cs index f48400fbc..462e0bf88 100644 --- a/src/RestSharp/Authenticators/OAuth/OAuthTools.cs +++ b/src/RestSharp/Authenticators/OAuth/OAuthTools.cs @@ -142,7 +142,7 @@ static class OAuthTools { internal static IEnumerable SortParametersExcludingSignature(WebPairCollection parameters) => parameters .Where(x => !x.Name.EqualsIgnoreCase("oauth_signature")) - .Select(x => new WebPair(UrlEncodeStrict(x.Name), UrlEncodeStrict(x.Value))) + .Select(x => new WebPair(UrlEncodeStrict(x.Name), UrlEncodeRelaxed(x.Value))) .OrderBy(x => x, WebPair.Comparer) .Select(x => x.GetQueryParameter(false)); diff --git a/test/RestSharp.Tests.Integrated/OAuth1Tests.cs b/test/RestSharp.Tests.Integrated/OAuth1Tests.cs index b81fd4d51..04827b1f8 100644 --- a/test/RestSharp.Tests.Integrated/OAuth1Tests.cs +++ b/test/RestSharp.Tests.Integrated/OAuth1Tests.cs @@ -51,17 +51,29 @@ class QueueItem { actual.Should().BeEquivalentTo(expected); } - [Fact] - public void Properly_Encodes_Parameter_Names() { - var postData = new WebPairCollection { - { "name[first]", "Chuck" }, - { "name[last]", "Testa" } - }; + [Theory] + [MemberData(nameof(EncodeParametersTestData))] + public void Properly_Encodes_Parameter_Names(IList<(string, string)> parameters, string expected) { + var postData = new WebPairCollection(); + postData.AddRange(parameters.Select(x => new WebPair(x.Item1, x.Item2))); var sortedParams = OAuthTools.SortParametersExcludingSignature(postData); - sortedParams.First().Should().Be("name%5Bfirst%5D=Chuck"); + sortedParams.First().Should().Be(expected); } + public static IEnumerable EncodeParametersTestData => + new List + { + new object[] { + new List<(string, string)> { ("name[first]", "Chuck"), ("name[last]", "Testa") }, + "name%5Bfirst%5D=Chuck" + }, + new object[] { + new List<(string, string)> { ("country", "España") }, + "country=Espa%C3%B1a" + } + }; + [Fact] public void Use_RFC_3986_Encoding_For_Auth_Signature_Base() { // reserved characters for 2396 and 3986 From 1c86286b8cda3d0cfd94ff7357d8b53a2f71eba5 Mon Sep 17 00:00:00 2001 From: James Thompson Date: Wed, 3 Apr 2024 01:40:53 +1100 Subject: [PATCH 6/6] #2178 Set Package readme (#2180) --- src/Directory.Build.props | 1 + src/RestSharp/RestSharp.csproj | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 69705872a..cc4eccfef 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -16,6 +16,7 @@ true $(NoWarn);1591 11 + README.md diff --git a/src/RestSharp/RestSharp.csproj b/src/RestSharp/RestSharp.csproj index 0620c6ac9..561e30c82 100644 --- a/src/RestSharp/RestSharp.csproj +++ b/src/RestSharp/RestSharp.csproj @@ -1,4 +1,4 @@ - + true @@ -54,4 +54,7 @@ + + +