From b56455a8b2d405b2f1d9b2009b75f64304271008 Mon Sep 17 00:00:00 2001 From: sagadira Date: Wed, 6 May 2026 22:49:20 +0530 Subject: [PATCH] fix(csharp): use runtime .NET version detection for query param URL-encoding in HTTP signature The HttpSigningConfiguration template used the Mustache conditional net90OrLater to decide whether to URL-encode query parameter keys. When targeting net8.0, this conditional evaluates to false and the UrlEncode call is omitted entirely, producing an unencoded key in the signature base string. However, RestSharp 112+ always sends URL-encoded query parameters on the wire, causing a signature mismatch and HTTP 401 on every request that contains special characters in query parameter names (e.g. OData \, \, \). Replace the compile-time Mustache conditional with runtime detection using RuntimeInformation.FrameworkDescription. On .NET 9+ the key is left as-is (ParseQueryString already encodes internally); on .NET 8 and earlier, HttpUtility.UrlEncode is called explicitly so the signature matches the actual request. --- .../csharp/HttpSigningConfiguration.mustache | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- .../Client/HttpSigningConfiguration.cs | 14 ++++++++++++-- 15 files changed, 180 insertions(+), 30 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/csharp/HttpSigningConfiguration.mustache b/modules/openapi-generator/src/main/resources/csharp/HttpSigningConfiguration.mustache index 2d56fde4aecd..2ec594b9ec8e 100644 --- a/modules/openapi-generator/src/main/resources/csharp/HttpSigningConfiguration.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/HttpSigningConfiguration.mustache @@ -25,6 +25,9 @@ namespace {{packageName}}.Client { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -67,6 +70,14 @@ namespace {{packageName}}.Client /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -133,8 +144,7 @@ namespace {{packageName}}.Client foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : {{#net90OrLater}}HttpUtility.UrlEncode({{/net90OrLater}}parameter.Key{{#net90OrLater}}){{/net90OrLater}}; + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/httpclient/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/httpclient/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index 60d62fa0d9fa..faa479b5a2b4 100644 --- a/samples/client/petstore/csharp/httpclient/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/httpclient/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/httpclient/net9/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/httpclient/net9/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index 60d62fa0d9fa..faa479b5a2b4 100644 --- a/samples/client/petstore/csharp/httpclient/net9/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/httpclient/net9/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/httpclient/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/httpclient/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index d4067aed3c3b..725ad9dee4fd 100644 --- a/samples/client/petstore/csharp/httpclient/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/httpclient/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key; + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/restsharp/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/restsharp/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index 60d62fa0d9fa..faa479b5a2b4 100644 --- a/samples/client/petstore/csharp/restsharp/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/restsharp/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/restsharp/net4.7/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/restsharp/net4.7/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index d4067aed3c3b..725ad9dee4fd 100644 --- a/samples/client/petstore/csharp/restsharp/net4.7/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/restsharp/net4.7/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key; + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/restsharp/net4.8/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/restsharp/net4.8/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index d4067aed3c3b..725ad9dee4fd 100644 --- a/samples/client/petstore/csharp/restsharp/net4.8/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/restsharp/net4.8/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key; + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/restsharp/net8/EnumMappings/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/restsharp/net8/EnumMappings/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index 805b45bd0846..faa479b5a2b4 100644 --- a/samples/client/petstore/csharp/restsharp/net8/EnumMappings/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/restsharp/net8/EnumMappings/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key; + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/restsharp/net8/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/restsharp/net8/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index 805b45bd0846..faa479b5a2b4 100644 --- a/samples/client/petstore/csharp/restsharp/net8/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/restsharp/net8/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key; + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/restsharp/net9/EnumMappings/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/restsharp/net9/EnumMappings/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index 60d62fa0d9fa..faa479b5a2b4 100644 --- a/samples/client/petstore/csharp/restsharp/net9/EnumMappings/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/restsharp/net9/EnumMappings/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/restsharp/standard2.0/ConditionalSerialization/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/restsharp/standard2.0/ConditionalSerialization/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index d4067aed3c3b..725ad9dee4fd 100644 --- a/samples/client/petstore/csharp/restsharp/standard2.0/ConditionalSerialization/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/restsharp/standard2.0/ConditionalSerialization/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key; + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/restsharp/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/restsharp/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index d4067aed3c3b..725ad9dee4fd 100644 --- a/samples/client/petstore/csharp/restsharp/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/restsharp/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key; + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/unityWebRequest/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/unityWebRequest/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index 60d62fa0d9fa..faa479b5a2b4 100644 --- a/samples/client/petstore/csharp/unityWebRequest/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/unityWebRequest/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/unityWebRequest/net9/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/unityWebRequest/net9/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index 60d62fa0d9fa..faa479b5a2b4 100644 --- a/samples/client/petstore/csharp/unityWebRequest/net9/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/unityWebRequest/net9/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value) diff --git a/samples/client/petstore/csharp/unityWebRequest/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs b/samples/client/petstore/csharp/unityWebRequest/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs index d4067aed3c3b..725ad9dee4fd 100644 --- a/samples/client/petstore/csharp/unityWebRequest/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs +++ b/samples/client/petstore/csharp/unityWebRequest/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs @@ -33,6 +33,9 @@ public HttpSigningConfiguration() { HashAlgorithm = HashAlgorithmName.SHA256; SigningAlgorithm = "PKCS1-v15"; + string framework = RuntimeInformation.FrameworkDescription; + _skipUrlEncode = framework.StartsWith(".NET ") && + int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9; } /// @@ -75,6 +78,14 @@ public HttpSigningConfiguration() /// public int SignatureValidityPeriod { get; set; } + // On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally, + // so calling UrlEncode again would cause double-encoding and produce a signature + // that does not match the actual request sent by RestSharp 112+. + // On .NET 8 and earlier, keys must be explicitly URL-encoded so that special + // characters (e.g. '$' in OData params like $filter) are encoded the same way + // in the signature as they are in the outgoing HTTP request. + private readonly bool _skipUrlEncode; + private enum PrivateKeyType { None = 0, @@ -141,8 +152,7 @@ public Dictionary GetHttpSignedHeader(string basePath,string met foreach (var parameter in requestOptions.QueryParameters) { #if (NETCOREAPP) - string framework = RuntimeInformation.FrameworkDescription; - string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key; + string key = _skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key); if (parameter.Value.Count > 1) { // array foreach (var value in parameter.Value)