Skip to content

Commit 98c1c14

Browse files
committed
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.
1 parent fc60f97 commit 98c1c14

12 files changed

Lines changed: 132 additions & 24 deletions

File tree

modules/openapi-generator/src/main/resources/csharp/HttpSigningConfiguration.mustache

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,20 @@ namespace {{packageName}}.Client
130130
}
131131

132132
var httpValues = HttpUtility.ParseQueryString(string.Empty);
133+
// Determine whether URL-encoding of query parameter keys should be skipped.
134+
// On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally,
135+
// so calling UrlEncode again would cause double-encoding and produce a signature
136+
// that does not match the actual request sent by RestSharp 112+.
137+
// On .NET 8 and earlier, keys must be explicitly URL-encoded so that special
138+
// characters (e.g. '$' in OData params like $filter) are encoded the same way
139+
// in the signature as they are in the outgoing HTTP request.
140+
string framework = RuntimeInformation.FrameworkDescription;
141+
bool skipUrlEncode = framework.StartsWith(".NET ") &&
142+
int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9;
133143
foreach (var parameter in requestOptions.QueryParameters)
134144
{
135145
#if (NETCOREAPP)
136-
string framework = RuntimeInformation.FrameworkDescription;
137-
string key = framework.StartsWith(".NET 9") ? parameter.Key : {{#net90OrLater}}HttpUtility.UrlEncode({{/net90OrLater}}parameter.Key{{#net90OrLater}}){{/net90OrLater}};
146+
string key = skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
138147
if (parameter.Value.Count > 1)
139148
{ // array
140149
foreach (var value in parameter.Value)

samples/client/petstore/csharp/httpclient/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,20 @@ public Dictionary<string, string> GetHttpSignedHeader(string basePath,string met
138138
}
139139

140140
var httpValues = HttpUtility.ParseQueryString(string.Empty);
141+
// Determine whether URL-encoding of query parameter keys should be skipped.
142+
// On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally,
143+
// so calling UrlEncode again would cause double-encoding and produce a signature
144+
// that does not match the actual request sent by RestSharp 112+.
145+
// On .NET 8 and earlier, keys must be explicitly URL-encoded so that special
146+
// characters (e.g. '$' in OData params like $filter) are encoded the same way
147+
// in the signature as they are in the outgoing HTTP request.
148+
string framework = RuntimeInformation.FrameworkDescription;
149+
bool skipUrlEncode = framework.StartsWith(".NET ") &&
150+
int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9;
141151
foreach (var parameter in requestOptions.QueryParameters)
142152
{
143153
#if (NETCOREAPP)
144-
string framework = RuntimeInformation.FrameworkDescription;
145-
string key = framework.StartsWith(".NET 9") ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
154+
string key = skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
146155
if (parameter.Value.Count > 1)
147156
{ // array
148157
foreach (var value in parameter.Value)

samples/client/petstore/csharp/httpclient/net9/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,20 @@ public Dictionary<string, string> GetHttpSignedHeader(string basePath,string met
138138
}
139139

140140
var httpValues = HttpUtility.ParseQueryString(string.Empty);
141+
// Determine whether URL-encoding of query parameter keys should be skipped.
142+
// On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally,
143+
// so calling UrlEncode again would cause double-encoding and produce a signature
144+
// that does not match the actual request sent by RestSharp 112+.
145+
// On .NET 8 and earlier, keys must be explicitly URL-encoded so that special
146+
// characters (e.g. '$' in OData params like $filter) are encoded the same way
147+
// in the signature as they are in the outgoing HTTP request.
148+
string framework = RuntimeInformation.FrameworkDescription;
149+
bool skipUrlEncode = framework.StartsWith(".NET ") &&
150+
int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9;
141151
foreach (var parameter in requestOptions.QueryParameters)
142152
{
143153
#if (NETCOREAPP)
144-
string framework = RuntimeInformation.FrameworkDescription;
145-
string key = framework.StartsWith(".NET 9") ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
154+
string key = skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
146155
if (parameter.Value.Count > 1)
147156
{ // array
148157
foreach (var value in parameter.Value)

samples/client/petstore/csharp/httpclient/standard2.0/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,20 @@ public Dictionary<string, string> GetHttpSignedHeader(string basePath,string met
138138
}
139139

140140
var httpValues = HttpUtility.ParseQueryString(string.Empty);
141+
// Determine whether URL-encoding of query parameter keys should be skipped.
142+
// On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally,
143+
// so calling UrlEncode again would cause double-encoding and produce a signature
144+
// that does not match the actual request sent by RestSharp 112+.
145+
// On .NET 8 and earlier, keys must be explicitly URL-encoded so that special
146+
// characters (e.g. '$' in OData params like $filter) are encoded the same way
147+
// in the signature as they are in the outgoing HTTP request.
148+
string framework = RuntimeInformation.FrameworkDescription;
149+
bool skipUrlEncode = framework.StartsWith(".NET ") &&
150+
int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9;
141151
foreach (var parameter in requestOptions.QueryParameters)
142152
{
143153
#if (NETCOREAPP)
144-
string framework = RuntimeInformation.FrameworkDescription;
145-
string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key;
154+
string key = skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
146155
if (parameter.Value.Count > 1)
147156
{ // array
148157
foreach (var value in parameter.Value)

samples/client/petstore/csharp/restsharp/net10/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,20 @@ public Dictionary<string, string> GetHttpSignedHeader(string basePath,string met
138138
}
139139

140140
var httpValues = HttpUtility.ParseQueryString(string.Empty);
141+
// Determine whether URL-encoding of query parameter keys should be skipped.
142+
// On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally,
143+
// so calling UrlEncode again would cause double-encoding and produce a signature
144+
// that does not match the actual request sent by RestSharp 112+.
145+
// On .NET 8 and earlier, keys must be explicitly URL-encoded so that special
146+
// characters (e.g. '$' in OData params like $filter) are encoded the same way
147+
// in the signature as they are in the outgoing HTTP request.
148+
string framework = RuntimeInformation.FrameworkDescription;
149+
bool skipUrlEncode = framework.StartsWith(".NET ") &&
150+
int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9;
141151
foreach (var parameter in requestOptions.QueryParameters)
142152
{
143153
#if (NETCOREAPP)
144-
string framework = RuntimeInformation.FrameworkDescription;
145-
string key = framework.StartsWith(".NET 9") ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
154+
string key = skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
146155
if (parameter.Value.Count > 1)
147156
{ // array
148157
foreach (var value in parameter.Value)

samples/client/petstore/csharp/restsharp/net4.7/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,20 @@ public Dictionary<string, string> GetHttpSignedHeader(string basePath,string met
138138
}
139139

140140
var httpValues = HttpUtility.ParseQueryString(string.Empty);
141+
// Determine whether URL-encoding of query parameter keys should be skipped.
142+
// On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally,
143+
// so calling UrlEncode again would cause double-encoding and produce a signature
144+
// that does not match the actual request sent by RestSharp 112+.
145+
// On .NET 8 and earlier, keys must be explicitly URL-encoded so that special
146+
// characters (e.g. '$' in OData params like $filter) are encoded the same way
147+
// in the signature as they are in the outgoing HTTP request.
148+
string framework = RuntimeInformation.FrameworkDescription;
149+
bool skipUrlEncode = framework.StartsWith(".NET ") &&
150+
int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9;
141151
foreach (var parameter in requestOptions.QueryParameters)
142152
{
143153
#if (NETCOREAPP)
144-
string framework = RuntimeInformation.FrameworkDescription;
145-
string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key;
154+
string key = skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
146155
if (parameter.Value.Count > 1)
147156
{ // array
148157
foreach (var value in parameter.Value)

samples/client/petstore/csharp/restsharp/net4.8/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,20 @@ public Dictionary<string, string> GetHttpSignedHeader(string basePath,string met
138138
}
139139

140140
var httpValues = HttpUtility.ParseQueryString(string.Empty);
141+
// Determine whether URL-encoding of query parameter keys should be skipped.
142+
// On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally,
143+
// so calling UrlEncode again would cause double-encoding and produce a signature
144+
// that does not match the actual request sent by RestSharp 112+.
145+
// On .NET 8 and earlier, keys must be explicitly URL-encoded so that special
146+
// characters (e.g. '$' in OData params like $filter) are encoded the same way
147+
// in the signature as they are in the outgoing HTTP request.
148+
string framework = RuntimeInformation.FrameworkDescription;
149+
bool skipUrlEncode = framework.StartsWith(".NET ") &&
150+
int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9;
141151
foreach (var parameter in requestOptions.QueryParameters)
142152
{
143153
#if (NETCOREAPP)
144-
string framework = RuntimeInformation.FrameworkDescription;
145-
string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key;
154+
string key = skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
146155
if (parameter.Value.Count > 1)
147156
{ // array
148157
foreach (var value in parameter.Value)

samples/client/petstore/csharp/restsharp/net8/EnumMappings/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,20 @@ public Dictionary<string, string> GetHttpSignedHeader(string basePath,string met
138138
}
139139

140140
var httpValues = HttpUtility.ParseQueryString(string.Empty);
141+
// Determine whether URL-encoding of query parameter keys should be skipped.
142+
// On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally,
143+
// so calling UrlEncode again would cause double-encoding and produce a signature
144+
// that does not match the actual request sent by RestSharp 112+.
145+
// On .NET 8 and earlier, keys must be explicitly URL-encoded so that special
146+
// characters (e.g. '$' in OData params like $filter) are encoded the same way
147+
// in the signature as they are in the outgoing HTTP request.
148+
string framework = RuntimeInformation.FrameworkDescription;
149+
bool skipUrlEncode = framework.StartsWith(".NET ") &&
150+
int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9;
141151
foreach (var parameter in requestOptions.QueryParameters)
142152
{
143153
#if (NETCOREAPP)
144-
string framework = RuntimeInformation.FrameworkDescription;
145-
string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key;
154+
string key = skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
146155
if (parameter.Value.Count > 1)
147156
{ // array
148157
foreach (var value in parameter.Value)

samples/client/petstore/csharp/restsharp/net8/Petstore/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,20 @@ public Dictionary<string, string> GetHttpSignedHeader(string basePath,string met
138138
}
139139

140140
var httpValues = HttpUtility.ParseQueryString(string.Empty);
141+
// Determine whether URL-encoding of query parameter keys should be skipped.
142+
// On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally,
143+
// so calling UrlEncode again would cause double-encoding and produce a signature
144+
// that does not match the actual request sent by RestSharp 112+.
145+
// On .NET 8 and earlier, keys must be explicitly URL-encoded so that special
146+
// characters (e.g. '$' in OData params like $filter) are encoded the same way
147+
// in the signature as they are in the outgoing HTTP request.
148+
string framework = RuntimeInformation.FrameworkDescription;
149+
bool skipUrlEncode = framework.StartsWith(".NET ") &&
150+
int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9;
141151
foreach (var parameter in requestOptions.QueryParameters)
142152
{
143153
#if (NETCOREAPP)
144-
string framework = RuntimeInformation.FrameworkDescription;
145-
string key = framework.StartsWith(".NET 9") ? parameter.Key : parameter.Key;
154+
string key = skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
146155
if (parameter.Value.Count > 1)
147156
{ // array
148157
foreach (var value in parameter.Value)

samples/client/petstore/csharp/restsharp/net9/EnumMappings/src/Org.OpenAPITools/Client/HttpSigningConfiguration.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,20 @@ public Dictionary<string, string> GetHttpSignedHeader(string basePath,string met
138138
}
139139

140140
var httpValues = HttpUtility.ParseQueryString(string.Empty);
141+
// Determine whether URL-encoding of query parameter keys should be skipped.
142+
// On .NET 9+, HttpUtility.ParseQueryString already URL-encodes keys internally,
143+
// so calling UrlEncode again would cause double-encoding and produce a signature
144+
// that does not match the actual request sent by RestSharp 112+.
145+
// On .NET 8 and earlier, keys must be explicitly URL-encoded so that special
146+
// characters (e.g. '$' in OData params like $filter) are encoded the same way
147+
// in the signature as they are in the outgoing HTTP request.
148+
string framework = RuntimeInformation.FrameworkDescription;
149+
bool skipUrlEncode = framework.StartsWith(".NET ") &&
150+
int.TryParse(framework.Substring(5).Split('.')[0], out int fwMajor) && fwMajor >= 9;
141151
foreach (var parameter in requestOptions.QueryParameters)
142152
{
143153
#if (NETCOREAPP)
144-
string framework = RuntimeInformation.FrameworkDescription;
145-
string key = framework.StartsWith(".NET 9") ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
154+
string key = skipUrlEncode ? parameter.Key : HttpUtility.UrlEncode(parameter.Key);
146155
if (parameter.Value.Count > 1)
147156
{ // array
148157
foreach (var value in parameter.Value)

0 commit comments

Comments
 (0)