Skip to content

Commit 75d4893

Browse files
committed
1 parent 2af130d commit 75d4893

File tree

5 files changed

+84
-49
lines changed

5 files changed

+84
-49
lines changed

RestSharp.Tests/UrlBuilderTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ public void GET_with_empty_request_and_bare_hostname()
3333
Assert.AreEqual(expected, output);
3434
}
3535

36+
[Test]
37+
public void GET_with_empty_base_and_query_parameters_without_encoding()
38+
{
39+
var request = new RestRequest("http://example.com/resource?param1=value1")
40+
.AddQueryParameter("foo", "bar,baz", false);
41+
42+
var client = new RestClient();
43+
var expected = new Uri("http://example.com/resource?param1=value1&foo=bar,baz");
44+
var output = client.BuildUri(request);
45+
46+
Assert.AreEqual(expected, output);
47+
}
48+
3649
[Test]
3750
public void GET_with_empty_request_and_query_parameters_without_encoding()
3851
{
@@ -122,6 +135,20 @@ public void GET_with_resource_containing_slashes()
122135
Assert.AreEqual(expected, output);
123136
}
124137

138+
[Test]
139+
public void GET_with_empty_base_and_resource_containing_tokens()
140+
{
141+
var request = new RestRequest("http://example.com/resource/{foo}");
142+
143+
request.AddUrlSegment("foo", "bar");
144+
145+
var client = new RestClient();
146+
var expected = new Uri("http://example.com/resource/bar");
147+
var output = client.BuildUri(request);
148+
149+
Assert.AreEqual(expected, output);
150+
}
151+
125152
[Test]
126153
public void GET_with_resource_containing_tokens()
127154
{

RestSharp/Authenticators/OAuth1Authenticator.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,20 +258,22 @@ private void AddOAuthData(IRestClient client, IRestRequest request, OAuthWorkflo
258258
{
259259
case OAuthParameterHandling.HttpAuthorizationHeader:
260260
parameters.Add("oauth_signature", oauth.Signature);
261-
request.AddHeader("Authorization", GetAuthorizationHeader(parameters));
261+
request.AddOrUpdateParameter("Authorization", GetAuthorizationHeader(parameters), ParameterType.HttpHeader);
262262
break;
263263

264264
case OAuthParameterHandling.UrlOrPostParameters:
265265
parameters.Add("oauth_signature", oauth.Signature);
266-
request.Parameters.AddRange(
266+
var headers =
267267
parameters.Where(p => !p.Name.IsNullOrBlank() &&
268268
(p.Name.StartsWith("oauth_") || p.Name.StartsWith("x_auth_")))
269269
.Select(p => new Parameter
270270
{
271271
Name = p.Name,
272272
Value = HttpUtility.UrlDecode(p.Value),
273273
Type = ParameterType.GetOrPost
274-
}));
274+
});
275+
foreach (var header in headers)
276+
request.AddOrUpdateParameter(header);
275277
break;
276278

277279
default:

RestSharp/IRestClient.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,21 @@ RestRequestAsyncHandle ExecuteAsync<T>(IRestRequest request,
8484
IRestResponse<T> Deserialize<T>(IRestResponse response);
8585

8686
/// <summary>
87-
/// Allows to use a custom way to encode parameters
87+
/// Allows to use a custom way to encode URL parameters
8888
/// </summary>
89-
/// <param name="encoder">A delegate to encode parameters</param>
89+
/// <param name="encoder">A delegate to encode URL parameters</param>
9090
/// <example>client.UseUrlEncoder(s => HttpUtility.UrlEncode(s));</example>
9191
/// <returns></returns>
9292
IRestClient UseUrlEncoder(Func<string, string> encoder);
9393

94+
/// <summary>
95+
/// Allows to use a custom way to encode query parameters
96+
/// </summary>
97+
/// <param name="queryEncoder">A delegate to encode query parameters</param>
98+
/// <example>client.UseUrlEncoder((s, encoding) => HttpUtility.UrlEncode(s, encoding));</example>
99+
/// <returns></returns>
100+
IRestClient UseQueryEncoder(Func<string, Encoding, string> queryEncoder);
101+
94102
IRestResponse Execute(IRestRequest request);
95103

96104
IRestResponse Execute(IRestRequest request, Method httpMethod);

RestSharp/RestClient.cs

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,11 @@
2727
using System.Net.Security;
2828
using System.Reflection;
2929
using System.Security.Cryptography.X509Certificates;
30-
using System.Security.Principal;
3130
using System.Text;
3231
using System.Text.RegularExpressions;
3332
using RestSharp.Serialization;
3433
using RestSharp.Serialization.Json;
3534
using RestSharp.Serialization.Xml;
36-
using RestSharp.Serializers;
3735

3836
namespace RestSharp
3937
{
@@ -94,12 +92,8 @@ public RestClient(string baseUrl) : this()
9492
/// <param name="serializer">The custom serializer instance</param>
9593
/// <returns></returns>
9694
[Obsolete("Use the overload that accepts the delegate factory")]
97-
public IRestClient UseSerializer(IRestSerializer serializer)
98-
{
99-
UseSerializer(() => serializer);
100-
101-
return this;
102-
}
95+
public IRestClient UseSerializer(IRestSerializer serializer) =>
96+
Fluent(() => UseSerializer(() => serializer));
10397

10498
/// <summary>
10599
/// Replace the default serializer with a custom one
@@ -130,15 +124,15 @@ public IRestClient UseSerializer(Func<IRestSerializer> serializerFactory)
130124
/// <param name="encoder">A delegate to encode parameters</param>
131125
/// <example>client.UseUrlEncoder(s => HttpUtility.UrlEncode(s));</example>
132126
/// <returns></returns>
133-
public IRestClient UseUrlEncoder(Func<string, string> encoder)
134-
{
135-
Encode = encoder;
136-
return this;
137-
}
127+
public IRestClient UseUrlEncoder(Func<string, string> encoder) => Fluent(() => Encode = encoder);
128+
129+
public IRestClient UseQueryEncoder(Func<string, Encoding, string> queryEncoder) =>
130+
Fluent(() => EncodeQuery = queryEncoder);
138131

139132
private IDictionary<string, Func<IDeserializer>> ContentHandlers { get; }
140133
internal IDictionary<DataFormat, IRestSerializer> Serializers { get; }
141134
private Func<string, string> Encode { get; set; } = s => s.UrlEncode();
135+
private Func<string, Encoding, string> EncodeQuery { get; set; } = (s, encoding) => s.UrlEncode(encoding);
142136

143137
private IList<string> AcceptTypes { get; }
144138

@@ -370,10 +364,9 @@ string IRestClient.BuildUriWithoutQueryParameters(IRestRequest request)
370364

371365
private void DoBuildUriValidations(IRestRequest request)
372366
{
373-
if (BaseUrl == null)
374-
{
375-
throw new NullReferenceException("RestClient must contain a value for BaseUrl");
376-
}
367+
if (BaseUrl == null && !request.Resource.ToLower().StartsWith("http"))
368+
throw new ArgumentOutOfRangeException(nameof(request),
369+
"Request resource doesn't contain a valid scheme for an empty client base URL");
377370

378371
var nullValuedParams = request.Parameters
379372
.Where(p => p.Type == ParameterType.UrlSegment && p.Value == null)
@@ -390,21 +383,21 @@ private void DoBuildUriValidations(IRestRequest request)
390383

391384
private UrlSegmentParamsValues GetUrlSegmentParamsValues(IRestRequest request)
392385
{
393-
var assembled = request.Resource;
394-
var hasResource = !string.IsNullOrEmpty(assembled);
386+
var assembled = BaseUrl == null ? "" : request.Resource;
387+
var baseUrl = BaseUrl ?? new Uri(request.Resource);
388+
389+
var hasResource = !assembled.IsEmpty();
395390
var parameters = request.Parameters.Where(p => p.Type == ParameterType.UrlSegment).ToList();
396391
parameters.AddRange(DefaultParameters.Where(p => p.Type == ParameterType.UrlSegment));
397-
var builder = new UriBuilder(BaseUrl);
392+
var builder = new UriBuilder(baseUrl);
398393

399394
foreach (var parameter in parameters)
400395
{
401396
var paramPlaceHolder = $"{{{parameter.Name}}}";
402397
var paramValue = Encode(parameter.Value.ToString());
403398

404399
if (hasResource)
405-
{
406400
assembled = assembled.Replace(paramPlaceHolder, paramValue);
407-
}
408401

409402
builder.Path = builder.Path.UrlDecode().Replace(paramPlaceHolder, paramValue);
410403
}
@@ -479,15 +472,15 @@ private Func<IDeserializer> GetHandler(string contentType)
479472
if (contentType.IsEmpty() && ContentHandlers.ContainsKey("*"))
480473
return ContentHandlers["*"];
481474

482-
if (contentType.IsEmpty())
475+
if (contentType.IsEmpty())
483476
return ContentHandlers.First().Value;
484477

485478
int semicolonIndex = contentType.IndexOf(';');
486479

487480
if (semicolonIndex > -1)
488481
contentType = contentType.Substring(0, semicolonIndex);
489482

490-
if (ContentHandlers.TryGetValue(contentType, out var contentHandler))
483+
if (ContentHandlers.TryGetValue(contentType, out var contentHandler))
491484
return contentHandler;
492485

493486
// Avoid unnecessary use of regular expressions in checking for structured syntax suffix by looking for a '+' first
@@ -512,18 +505,18 @@ private Func<IDeserializer> GetHandler(string contentType)
512505
private void AuthenticateIfNeeded(RestClient client, IRestRequest request) =>
513506
Authenticator?.Authenticate(client, request);
514507

515-
private static string EncodeParameters(IEnumerable<Parameter> parameters, Encoding encoding) =>
508+
private string EncodeParameters(IEnumerable<Parameter> parameters, Encoding encoding) =>
516509
string.Join("&", parameters.Select(parameter => EncodeParameter(parameter, encoding)).ToArray());
517510

518-
private static string EncodeParameter(Parameter parameter, Encoding encoding) =>
519-
parameter.Value == null
520-
? parameter.Type == ParameterType.QueryStringWithoutEncode
521-
? string.Concat(parameter.Name, "=")
522-
: string.Concat(parameter.Name.UrlEncode(encoding), "=")
523-
: parameter.Type == ParameterType.QueryStringWithoutEncode
524-
? string.Concat(parameter.Name, "=", parameter.Value.ToString())
525-
: string.Concat(parameter.Name.UrlEncode(encoding), "=",
526-
parameter.Value.ToString().UrlEncode(encoding));
511+
private string EncodeParameter(Parameter parameter, Encoding encoding)
512+
{
513+
return
514+
parameter.Type == ParameterType.QueryStringWithoutEncode
515+
? $"{parameter.Name}={StringOrEmpty(parameter.Value)}"
516+
: $"{EncodeQuery(parameter.Name, encoding)}={EncodeQuery(StringOrEmpty(parameter.Value), encoding)}";
517+
518+
string StringOrEmpty(object value) => value == null ? "" : value.ToString();
519+
}
527520

528521
private static readonly ParameterType[] MultiParameterTypes =
529522
{ParameterType.QueryString, ParameterType.GetOrPost};
@@ -550,8 +543,9 @@ internal IHttp ConfigureHttp(IRestRequest request)
550543
foreach (var defaultParameter in DefaultParameters)
551544
{
552545
var parameterExists =
553-
request.Parameters.Any(p => p.Name.Equals(defaultParameter.Name, StringComparison.InvariantCultureIgnoreCase)
554-
&& p.Type == defaultParameter.Type);
546+
request.Parameters.Any(p =>
547+
p.Name.Equals(defaultParameter.Name, StringComparison.InvariantCultureIgnoreCase)
548+
&& p.Type == defaultParameter.Type);
555549

556550
if (AllowMultipleDefaultParametersWithSameName)
557551
{
@@ -563,7 +557,8 @@ internal IHttp ConfigureHttp(IRestRequest request)
563557
}
564558

565559
// Add Accept header based on registered deserializers if none has been set by the caller.
566-
if (requestParameters.All(p => !string.Equals(p.Name, "accept", StringComparison.InvariantCultureIgnoreCase)))
560+
if (requestParameters.All(
561+
p => !string.Equals(p.Name, "accept", StringComparison.InvariantCultureIgnoreCase)))
567562
{
568563
var accepts = string.Join(", ", AcceptTypes.ToArray());
569564

@@ -743,8 +738,8 @@ private IRestResponse<T> Deserialize<T>(IRestRequest request, IRestResponse raw)
743738
catch (Exception ex)
744739
{
745740
if (FailOnDeserializationError)
746-
response.ResponseStatus = ResponseStatus.Error;
747-
741+
response.ResponseStatus = ResponseStatus.Error;
742+
748743
response.ErrorMessage = ex.Message;
749744
response.ErrorException = ex;
750745
}
@@ -773,6 +768,12 @@ private static bool IsWildcardStructuredSuffixSyntax(string contentType)
773768
return StructuredSyntaxSuffixWildcardRegex.IsMatch(contentType);
774769
}
775770

771+
private IRestClient Fluent(Action action)
772+
{
773+
action();
774+
return this;
775+
}
776+
776777
private class UrlSegmentParamsValues
777778
{
778779
public UrlSegmentParamsValues(Uri builderUri, string assembled)

RestSharp/RestRequest.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,8 @@ public RestRequest()
6161
/// Sets Method property to value of method
6262
/// </summary>
6363
/// <param name="method">Method to use for this request</param>
64-
public RestRequest(Method method) : this()
65-
{
66-
Method = method;
67-
}
68-
64+
public RestRequest(Method method) : this() => Method = method;
65+
6966
public RestRequest(string resource, Method method) : this(resource, method, DataFormat.Xml)
7067
{
7168
}

0 commit comments

Comments
 (0)