Skip to content

Commit 159c8a7

Browse files
Make BuildUrl an extension (#2039)
* Make BuildUrl an extension, add DefaultParameters to the interface, change the type to guard against concurrent updates. * Null value allowed for OAuth1Authenticator Serializers cache (fixes #1988) * Updated dependencies, added System.Text.Json to all targets (fixes #2033) * Update package version * Add sealed to Parameter.ToString() and also exposed GetRequestQuery extension (fixes #2035) * Quote OAuth parameter value * Fix the boundary quotation, was using the wrong option * One more place missed * Fix the accepted header null value
1 parent d184961 commit 159c8a7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+381
-254
lines changed

RestSharp.sln.DotSettings

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/CodeEditing/SuppressUninitializedWarningFix/Enabled/@EntryValue">False</s:Boolean>
23
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeConstructorOrDestructorBody/@EntryIndexedValue">SUGGESTION</s:String>
34
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeLocalFunctionBody/@EntryIndexedValue">SUGGESTION</s:String>
45
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeMethodOrOperatorBody/@EntryIndexedValue">SUGGESTION</s:String>

benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="AutoFixture" Version="4.17.0" />
11+
<PackageReference Include="AutoFixture" Version="4.18.0" />
1212
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
1313
</ItemGroup>
1414
<ItemGroup>

gen/SourceGenerator/SourceGenerator.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<ItemGroup>
1414
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="All"/>
15-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="All"/>
15+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" PrivateAssets="All"/>
1616
</ItemGroup>
1717

1818
<ItemGroup>

src/Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
<ItemGroup>
2222
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
23-
<PackageReference Include="MinVer" Version="4.2.0" PrivateAssets="All"/>
23+
<PackageReference Include="MinVer" Version="4.3.0" PrivateAssets="All"/>
2424
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" PrivateAssets="All"/>
2525
</ItemGroup>
2626
<ItemGroup>

src/RestSharp/Authenticators/AuthenticatorBase.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ public abstract class AuthenticatorBase : IAuthenticator {
2121

2222
protected abstract ValueTask<Parameter> GetAuthenticationParameter(string accessToken);
2323

24-
public async ValueTask Authenticate(RestClient client, RestRequest request)
24+
public async ValueTask Authenticate(IRestClient client, RestRequest request)
2525
=> request.AddOrUpdateParameter(await GetAuthenticationParameter(Token).ConfigureAwait(false));
2626
}

src/RestSharp/Authenticators/IAuthenticator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
namespace RestSharp.Authenticators;
1616

1717
public interface IAuthenticator {
18-
ValueTask Authenticate(RestClient client, RestRequest request);
18+
ValueTask Authenticate(IRestClient client, RestRequest request);
1919
}

src/RestSharp/Authenticators/OAuth/OAuth1Authenticator.cs

+13-23
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using RestSharp.Extensions;
1717
using System.Web;
1818

19+
// ReSharper disable NotResolvedInText
1920
// ReSharper disable CheckNamespace
2021

2122
namespace RestSharp.Authenticators;
@@ -38,7 +39,7 @@ public class OAuth1Authenticator : IAuthenticator {
3839
public virtual string? ClientUsername { get; set; }
3940
public virtual string? ClientPassword { get; set; }
4041

41-
public ValueTask Authenticate(RestClient client, RestRequest request) {
42+
public ValueTask Authenticate(IRestClient client, RestRequest request) {
4243
var workflow = new OAuthWorkflow {
4344
ConsumerKey = ConsumerKey,
4445
ConsumerSecret = ConsumerSecret,
@@ -64,8 +65,8 @@ public static OAuth1Authenticator ForRequestToken(
6465
string consumerKey,
6566
string? consumerSecret,
6667
OAuthSignatureMethod signatureMethod = OAuthSignatureMethod.HmacSha1
67-
) {
68-
var authenticator = new OAuth1Authenticator {
68+
)
69+
=> new() {
6970
ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
7071
SignatureMethod = signatureMethod,
7172
SignatureTreatment = OAuthSignatureTreatment.Escaped,
@@ -74,10 +75,6 @@ public static OAuth1Authenticator ForRequestToken(
7475
Type = OAuthType.RequestToken
7576
};
7677

77-
return authenticator;
78-
}
79-
80-
[PublicAPI]
8178
public static OAuth1Authenticator ForRequestToken(string consumerKey, string? consumerSecret, string callbackUrl) {
8279
var authenticator = ForRequestToken(consumerKey, consumerSecret);
8380

@@ -105,7 +102,6 @@ public static OAuth1Authenticator ForAccessToken(
105102
Type = OAuthType.AccessToken
106103
};
107104

108-
[PublicAPI]
109105
public static OAuth1Authenticator ForAccessToken(
110106
string consumerKey,
111107
string? consumerSecret,
@@ -171,7 +167,6 @@ public static OAuth1Authenticator ForClientAuthentication(
171167
Type = OAuthType.ClientAuthentication
172168
};
173169

174-
[PublicAPI]
175170
public static OAuth1Authenticator ForProtectedResource(
176171
string consumerKey,
177172
string? consumerSecret,
@@ -190,7 +185,7 @@ public static OAuth1Authenticator ForProtectedResource(
190185
TokenSecret = accessTokenSecret
191186
};
192187

193-
void AddOAuthData(RestClient client, RestRequest request, OAuthWorkflow workflow) {
188+
void AddOAuthData(IRestClient client, RestRequest request, OAuthWorkflow workflow) {
194189
var requestUrl = client.BuildUriWithoutQueryParameters(request).AbsoluteUri;
195190

196191
if (requestUrl.Contains('?'))
@@ -201,11 +196,9 @@ void AddOAuthData(RestClient client, RestRequest request, OAuthWorkflow workflow
201196
var url = client.BuildUri(request).ToString();
202197
var queryStringStart = url.IndexOf('?');
203198

204-
if (queryStringStart != -1)
205-
url = url.Substring(0, queryStringStart);
206-
207-
var method = request.Method.ToString().ToUpperInvariant();
199+
if (queryStringStart != -1) url = url.Substring(0, queryStringStart);
208200

201+
var method = request.Method.ToString().ToUpperInvariant();
209202
var parameters = new WebPairCollection();
210203

211204
// include all GET and POST parameters before generating the signature
@@ -247,21 +240,18 @@ void AddOAuthData(RestClient client, RestRequest request, OAuthWorkflow workflow
247240

248241
request.AddOrUpdateParameters(oauthParameters);
249242

250-
IEnumerable<Parameter> CreateHeaderParameters()
251-
=> new[] { new HeaderParameter(KnownHeaders.Authorization, GetAuthorizationHeader()) };
243+
IEnumerable<Parameter> CreateHeaderParameters() => new[] { new HeaderParameter(KnownHeaders.Authorization, GetAuthorizationHeader()) };
252244

253-
IEnumerable<Parameter> CreateUrlParameters()
254-
=> oauth.Parameters.Select(p => new GetOrPostParameter(p.Name, HttpUtility.UrlDecode(p.Value)));
245+
IEnumerable<Parameter> CreateUrlParameters() => oauth.Parameters.Select(p => new GetOrPostParameter(p.Name, HttpUtility.UrlDecode(p.Value)));
255246

256247
string GetAuthorizationHeader() {
257248
var oathParameters =
258249
oauth.Parameters
259250
.OrderBy(x => x, WebPair.Comparer)
260-
.Select(x => $"{x.Name}=\"{x.WebValue}\"")
251+
.Select(x => x.GetQueryParameter(true))
261252
.ToList();
262253

263-
if (!Realm.IsEmpty())
264-
oathParameters.Insert(0, $"realm=\"{OAuthTools.UrlEncodeRelaxed(Realm!)}\"");
254+
if (!Realm.IsEmpty()) oathParameters.Insert(0, $"realm=\"{OAuthTools.UrlEncodeRelaxed(Realm)}\"");
265255

266256
return $"OAuth {string.Join(",", oathParameters)}";
267257
}
@@ -270,5 +260,5 @@ string GetAuthorizationHeader() {
270260

271261
static class ParametersExtensions {
272262
internal static IEnumerable<WebPair> ToWebParameters(this IEnumerable<Parameter> p)
273-
=> p.Select(x => new WebPair(Ensure.NotNull(x.Name, "Parameter name"), Ensure.NotNull(x.Value, "Parameter value").ToString()!));
274-
}
263+
=> p.Select(x => new WebPair(Ensure.NotNull(x.Name, "Parameter name"), x.Value?.ToString()));
264+
}

src/RestSharp/Authenticators/OAuth/OAuthTools.cs

+12-7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
using System.Diagnostics.CodeAnalysis;
1516
using System.Security.Cryptography;
1617
using System.Text;
1718
using RestSharp.Authenticators.OAuth.Extensions;
@@ -88,7 +89,10 @@ public static string GetNonce() {
8889
/// actually worked (which in my experiments it <i>doesn't</i>), we can't rely on every
8990
/// host actually having this configuration element present.
9091
/// </remarks>
91-
public static string UrlEncodeRelaxed(string value) {
92+
[return: NotNullIfNotNull(nameof(value))]
93+
public static string? UrlEncodeRelaxed(string? value) {
94+
if (value == null) return null;
95+
9296
// Escape RFC 3986 chars first.
9397
var escapedRfc3986 = new StringBuilder(value);
9498

@@ -117,8 +121,9 @@ public static string UrlEncodeRelaxed(string value) {
117121
// Generic Syntax," .) section 2.3) MUST be encoded.
118122
// ...
119123
// unreserved = ALPHA, DIGIT, '-', '.', '_', '~'
120-
public static string UrlEncodeStrict(string value)
121-
=> string.Join("", value.Select(x => Unreserved.Contains(x) ? x.ToString() : $"%{(byte)x:X2}"));
124+
[return: NotNullIfNotNull(nameof(value))]
125+
public static string? UrlEncodeStrict(string? value)
126+
=> value == null ? null : string.Join("", value.Select(x => Unreserved.Contains(x) ? x.ToString() : $"%{(byte)x:X2}"));
122127

123128
/// <summary>
124129
/// Sorts a collection of key-value pairs by name, and then value if equal,
@@ -137,9 +142,9 @@ public static string UrlEncodeStrict(string value)
137142
public static IEnumerable<string> SortParametersExcludingSignature(WebPairCollection parameters)
138143
=> parameters
139144
.Where(x => !x.Name.EqualsIgnoreCase("oauth_signature"))
140-
.Select(x => new WebPair(UrlEncodeStrict(x.Name), UrlEncodeStrict(x.Value), x.Encode))
145+
.Select(x => new WebPair(UrlEncodeStrict(x.Name), UrlEncodeStrict(x.Value)))
141146
.OrderBy(x => x, WebPair.Comparer)
142-
.Select(x => $"{x.Name}={x.Value}");
147+
.Select(x => x.GetQueryParameter(false));
143148

144149
/// <summary>
145150
/// Creates a request URL suitable for making OAuth requests.
@@ -151,8 +156,8 @@ public static IEnumerable<string> SortParametersExcludingSignature(WebPairCollec
151156
static string ConstructRequestUrl(Uri url) {
152157
Ensure.NotNull(url, nameof(url));
153158

154-
var basic = url.Scheme == "http" && url.Port == 80;
155-
var secure = url.Scheme == "https" && url.Port == 443;
159+
var basic = url is { Scheme: "http", Port : 80 };
160+
var secure = url is { Scheme: "https", Port: 443 };
156161
var port = basic || secure ? "" : $":{url.Port}";
157162

158163
return $"{url.Scheme}://{url.Host}{port}{url.AbsolutePath}";

src/RestSharp/Authenticators/OAuth/WebPair.cs

+10-7
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@
1515
namespace RestSharp.Authenticators.OAuth;
1616

1717
class WebPair {
18-
public WebPair(string name, string value, bool encode = false) {
18+
public WebPair(string name, string? value, bool encode = false) {
1919
Name = name;
2020
Value = value;
2121
WebValue = encode ? OAuthTools.UrlEncodeRelaxed(value) : value;
22-
Encode = encode;
2322
}
2423

25-
public string Name { get; }
26-
public string Value { get; }
27-
public string WebValue { get; }
28-
public bool Encode { get; }
24+
public string Name { get; }
25+
public string? Value { get; }
26+
string? WebValue { get; }
27+
28+
public string GetQueryParameter(bool web) {
29+
var value = web ? $"\"{WebValue}\"" : Value;
30+
return value == null ? Name : $"{Name}={value}";
31+
}
2932

3033
internal static WebPairComparer Comparer { get; } = new();
3134

@@ -36,4 +39,4 @@ public int Compare(WebPair? x, WebPair? y) {
3639
return compareName != 0 ? compareName : string.CompareOrdinal(x?.Value, y?.Value);
3740
}
3841
}
39-
}
42+
}

src/RestSharp/BuildUriExtensions.cs

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) .NET Foundation and Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
16+
namespace RestSharp;
17+
18+
public static class BuildUriExtensions {
19+
/// <summary>
20+
/// Builds the URI for the request
21+
/// </summary>
22+
/// <param name="client">Client instance</param>
23+
/// <param name="request">Request instance</param>
24+
/// <returns></returns>
25+
public static Uri BuildUri(this IRestClient client, RestRequest request) {
26+
DoBuildUriValidations(client, request);
27+
28+
var (uri, resource) = client.Options.BaseUrl.GetUrlSegmentParamsValues(
29+
request.Resource,
30+
client.Options.Encode,
31+
request.Parameters,
32+
client.DefaultParameters
33+
);
34+
var mergedUri = uri.MergeBaseUrlAndResource(resource);
35+
var query = client.GetRequestQuery(request);
36+
return mergedUri.AddQueryString(query);
37+
}
38+
39+
/// <summary>
40+
/// Builds the URI for the request without query parameters.
41+
/// </summary>
42+
/// <param name="client">Client instance</param>
43+
/// <param name="request">Request instance</param>
44+
/// <returns></returns>
45+
public static Uri BuildUriWithoutQueryParameters(this IRestClient client, RestRequest request) {
46+
DoBuildUriValidations(client, request);
47+
48+
var (uri, resource) = client.Options.BaseUrl.GetUrlSegmentParamsValues(
49+
request.Resource,
50+
client.Options.Encode,
51+
request.Parameters,
52+
client.DefaultParameters
53+
);
54+
return uri.MergeBaseUrlAndResource(resource);
55+
}
56+
57+
/// <summary>
58+
/// Gets the query string for the request.
59+
/// </summary>
60+
/// <param name="client">Client instance</param>
61+
/// <param name="request">Request instance</param>
62+
/// <returns></returns>
63+
[PublicAPI]
64+
public static string? GetRequestQuery(this IRestClient client, RestRequest request) {
65+
var parametersCollections = new ParametersCollection[] { request.Parameters, client.DefaultParameters };
66+
67+
var parameters = parametersCollections.SelectMany(x => x.GetQueryParameters(request.Method)).ToList();
68+
69+
return parameters.Count == 0 ? null : string.Join("&", parameters.Select(EncodeParameter).ToArray());
70+
71+
string GetString(string name, string? value, Func<string, string>? encode) {
72+
var val = encode != null && value != null ? encode(value) : value;
73+
return val == null ? name : $"{name}={val}";
74+
}
75+
76+
string EncodeParameter(Parameter parameter)
77+
=> !parameter.Encode
78+
? GetString(parameter.Name!, parameter.Value?.ToString(), null)
79+
: GetString(
80+
client.Options.EncodeQuery(parameter.Name!, client.Options.Encoding),
81+
parameter.Value?.ToString(),
82+
x => client.Options.EncodeQuery(x, client.Options.Encoding)
83+
);
84+
}
85+
86+
static void DoBuildUriValidations(IRestClient client, RestRequest request) {
87+
if (client.Options.BaseUrl == null && !request.Resource.ToLowerInvariant().StartsWith("http"))
88+
throw new ArgumentOutOfRangeException(
89+
nameof(request),
90+
"Request resource doesn't contain a valid scheme for an empty base URL of the client"
91+
);
92+
}
93+
}

src/RestSharp/IRestClient.cs

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ public interface IRestClient : IDisposable {
2828
/// </summary>
2929
RestSerializers Serializers { get; }
3030

31+
/// <summary>
32+
/// Default parameters to use on every request made with this client instance.
33+
/// </summary>
34+
DefaultParameters DefaultParameters { get; }
35+
3136
/// <summary>
3237
/// Executes the request asynchronously, authenticating if needed
3338
/// </summary>

0 commit comments

Comments
 (0)