Skip to content

Instantly share code, notes, and snippets.

@nicolasff
Created August 21, 2023 02:46
Show Gist options
  • Save nicolasff/5de66eda4264a27637aebe2e767e4abf to your computer and use it in GitHub Desktop.
Save nicolasff/5de66eda4264a27637aebe2e767e4abf to your computer and use it in GitHub Desktop.
Prevent UriParser in .NET from automatically simplifying URIs as if they were paths, like removing `/./` or considering `/../` as if it was a directory traversal (.NET 7)
using System.Reflection;
using ServiceStack;
namespace ConsoleApp;
public static class Program
{
static async Task Main(string[] args)
{
DoNotSimplifyUris();
// endpoints
const string webdisEndpoint = "http://127.0.0.1:7379";
// set a key
var setKey = $"{webdisEndpoint}/SET/foo/bar";
var setResponse = await setKey.GetJsonFromUrlAsync();
Console.WriteLine($"'SET foo bar' returned: {setResponse}");
// get it back with a regular GET
var simpleGetUrl = $"{webdisEndpoint}/GET/foo";
var simpleGetResponse = await simpleGetUrl.GetJsonFromUrlAsync();
Console.WriteLine($"'GET foo' returned: {simpleGetResponse}");
// try getting it back with an invalid GET command, validating that the URI is not "simplified"
var getWithDotsUrl = $"{webdisEndpoint}/GET/././././foo";
var getWithDotsResponse = await getWithDotsUrl.GetJsonFromUrlAsync();
Console.WriteLine($"'GET . . . . foo' returned: {getWithDotsResponse}");
// and doing the same with a Uri object instead of a simple String
var httpClient = new HttpClient();
var uri = new Uri($"{webdisEndpoint}/GET/././././foo");
var uriResponse = await httpClient.GetAsync(uri);
Console.WriteLine($"(manual) 'GET . . . . foo' (using Uri instead of String) returned: " +
$"HTTP {(int)uriResponse.StatusCode} {uriResponse.StatusCode}");
Console.WriteLine($"(manual) response body: {uriResponse.Content.ReadAsString()}");
}
private static void DoNotSimplifyUris()
{
const int CanonicalizeAsFilePath = 0x01000000;
const int ConvertPathSlashes = 0x00400000;
const int CompressPath = 0x00800000; // only this one seems strictly necessary for the /././… conversion.
var getSyntaxMethod = typeof (UriParser).GetMethod("GetSyntax", BindingFlags.Static | BindingFlags.NonPublic);
if (getSyntaxMethod == null)
{
throw new MissingMethodException("UriParser", "GetSyntax");
}
foreach (var scheme in new[] { "http", "https" })
{
// call with "http" and "https" to update both UriParser objects (see UriParser class for all instances)
var uriParser = getSyntaxMethod.Invoke(null, new object[] { scheme });
if (uriParser == null)
{
throw new ArgumentNullException($"Unexpected: UriParser.getSyntax({scheme}) returned null");
}
// get reference to UriParser._flags field
var flagsFieldInfo = typeof(UriParser).GetField("_flags",
BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.SetField | BindingFlags.Instance);
if (flagsFieldInfo == null)
{
throw new MissingFieldException("UriParser", "_flags");
}
// get value of that field on the UriParser object we're looking at (either the http or https instance)
var flagsValue = flagsFieldInfo.GetValue(uriParser);
if (flagsValue == null)
{
throw new Exception($"Could not extract the value of UriParser._flags for the {scheme} instance");
}
// convert to the underlying int representation to unset some flags
var flags = (int) flagsValue;
flags &= ~CanonicalizeAsFilePath;
flags &= ~ConvertPathSlashes;
flags &= ~CompressPath;
// save the modified value on the UriParser instance
flagsFieldInfo.SetValue(uriParser, flags);
}
}
}
@nicolasff
Copy link
Author

Original GitHub issue: nicolasff/webdis#236

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment