From 2468e98670aa484d26a0c188c65dbad51a337544 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:56:39 +0000 Subject: [PATCH 1/3] Initial plan From ca233cee4d03240ca02ff0e06e88507aa5c90636 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:08:39 +0000 Subject: [PATCH 2/3] Fix OAuth1 double-encoding issue for special characters in path segments Co-authored-by: alexeyzimarev <2821205+alexeyzimarev@users.noreply.github.com> --- .../Authenticators/OAuth/OAuthTools.cs | 7 +- .../Auth/OAuth1SignatureTests.cs | 71 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/RestSharp/Authenticators/OAuth/OAuthTools.cs b/src/RestSharp/Authenticators/OAuth/OAuthTools.cs index 1c4f7ab92..10d3cb928 100644 --- a/src/RestSharp/Authenticators/OAuth/OAuthTools.cs +++ b/src/RestSharp/Authenticators/OAuth/OAuthTools.cs @@ -154,7 +154,12 @@ static string ConstructRequestUrl(Uri url) { var secure = url is { Scheme: "https", Port: 443 }; var port = basic || secure ? "" : $":{url.Port}"; - return $"{url.Scheme}://{url.Host}{port}{url.AbsolutePath}"; + // Decode the path to avoid double-encoding when the path contains already-encoded characters + // For example, if the path contains "%21" (encoded !), we decode it back to "!" here, + // and it will be properly encoded again in UrlEncodeRelaxed + var decodedPath = Uri.UnescapeDataString(url.AbsolutePath); + + return $"{url.Scheme}://{url.Host}{port}{decodedPath}"; } /// diff --git a/test/RestSharp.Tests/Auth/OAuth1SignatureTests.cs b/test/RestSharp.Tests/Auth/OAuth1SignatureTests.cs index 782a7a121..30ed0ae71 100644 --- a/test/RestSharp.Tests/Auth/OAuth1SignatureTests.cs +++ b/test/RestSharp.Tests/Auth/OAuth1SignatureTests.cs @@ -49,4 +49,75 @@ public void Generates_correct_signature_base() { "POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521" ); } + + [Fact] + public void Handles_path_with_exclamation_mark() { + // Test that a path segment with ! is encoded correctly in the signature base + var client = new RestClient("https://api.example.com"); + var request = new RestRequest("path/with!exclamation/resource", Method.Get); + + const string method = "GET"; + var url = client.BuildUri(request).ToString(); + var parameters = new WebPairCollection(); + + _workflow.RequestUrl = url; + var oauthParameters = _workflow.BuildProtectedResourceSignature(method, parameters); + + var signatureBase = OAuthTools.ConcatenateRequestElements(method, url, oauthParameters.Parameters); + + // The URL should be encoded with ! as %21 in the signature base + signatureBase.Should().Contain("path%2Fwith%21exclamation%2Fresource"); + } + + [Theory] + [InlineData("path/with!exclamation", "%21")] + [InlineData("path/with*asterisk", "%2A")] + [InlineData("path/with'apostrophe", "%27")] + [InlineData("path/with(paren", "%28")] + [InlineData("path/with)paren", "%29")] + public void Encodes_RFC3986_special_chars_in_path(string path, string encodedChar) { + // Test that RFC 3986 special characters are properly encoded in path segments + var client = new RestClient("https://api.example.com"); + var request = new RestRequest(path, Method.Get); + + const string method = "GET"; + var url = client.BuildUri(request).ToString(); + var parameters = new WebPairCollection(); + + _workflow.RequestUrl = url; + var oauthParameters = _workflow.BuildProtectedResourceSignature(method, parameters); + + var signatureBase = OAuthTools.ConcatenateRequestElements(method, url, oauthParameters.Parameters); + + // The URL should contain the encoded character in the signature base + signatureBase.Should().Contain(encodedChar); + } + + [Theory] + [InlineData("with!exclamation")] + [InlineData("with*asterisk")] + [InlineData("with'apostrophe")] + [InlineData("with(paren")] + [InlineData("with)paren")] + public void Handles_url_segment_with_RFC3986_special_chars(string segmentValue) { + // Test that URL segment parameters with RFC 3986 special characters don't get double-encoded + var client = new RestClient("https://api.example.com"); + var request = new RestRequest("path/{segment}/resource", Method.Get); + request.AddUrlSegment("segment", segmentValue); + + const string method = "GET"; + var url = client.BuildUri(request).ToString(); + var parameters = new WebPairCollection(); + + _workflow.RequestUrl = url; + var oauthParameters = _workflow.BuildProtectedResourceSignature(method, parameters); + + var signatureBase = OAuthTools.ConcatenateRequestElements(method, url, oauthParameters.Parameters); + + // The signature base should NOT contain double-encoded characters like %2521 (which is %25 + 21) + signatureBase.Should().NotContain("%25"); + + // But it should contain properly encoded special chars + signatureBase.Should().MatchRegex("%2[0-9A-F]"); + } } \ No newline at end of file From fa6c0d46f8a9cd5d9a8f5ad93902b78babe19fca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:12:38 +0000 Subject: [PATCH 3/3] Add detailed security comment for Uri.UnescapeDataString usage Co-authored-by: alexeyzimarev <2821205+alexeyzimarev@users.noreply.github.com> --- src/RestSharp/Authenticators/OAuth/OAuthTools.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/RestSharp/Authenticators/OAuth/OAuthTools.cs b/src/RestSharp/Authenticators/OAuth/OAuthTools.cs index 10d3cb928..8b43f1dd0 100644 --- a/src/RestSharp/Authenticators/OAuth/OAuthTools.cs +++ b/src/RestSharp/Authenticators/OAuth/OAuthTools.cs @@ -154,9 +154,16 @@ static string ConstructRequestUrl(Uri url) { var secure = url is { Scheme: "https", Port: 443 }; var port = basic || secure ? "" : $":{url.Port}"; - // Decode the path to avoid double-encoding when the path contains already-encoded characters - // For example, if the path contains "%21" (encoded !), we decode it back to "!" here, - // and it will be properly encoded again in UrlEncodeRelaxed + // Decode the path to avoid double-encoding when the path contains already-encoded characters. + // For example, if a URL segment was added with AddUrlSegment("id", "value!"), it gets encoded + // to "value%21" in the URL. When we extract url.AbsolutePath, it contains "%21" (encoded). + // If we then call UrlEncodeRelaxed on it, Uri.EscapeDataString would encode the "%" to "%25", + // resulting in "%2521" (double-encoded). By decoding first, we ensure proper single encoding. + // + // Security note: This is safe because: + // - The url parameter is a validated Uri object constructed by RestSharp's BuildUri() + // - The decoded path is immediately re-encoded by UrlEncodeRelaxed before use + // - There is no direct user input involved in this internal OAuth signature calculation var decodedPath = Uri.UnescapeDataString(url.AbsolutePath); return $"{url.Scheme}://{url.Host}{port}{decodedPath}";