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)