Update: A fix has now made it's way into mono. This is good news!
Updated: Added logic to fix fragment handling.
I am trying to send a request with an encoded slash on Mono using the Uri class. This is basically the Mono equivalent of this question: GETting a URL with an url-encoded slash
The issue is that Mono similar to .NET will unescape any slashes it finds in the Uri when it is constructed. This logic was originally put in in place in order to remove vulnerabilities that could occur if paths were escape encoded and not detected.
In the previous post there is a hack which shows setting flags on the underlying Uri class via reflection which force the escaped slashes to be left alone. This behavior has been fixed in .NET 4.5 and by default the escaped slashes are allowed (as I mentioned in the comments).
I tried to do the same on Mono, but it fails as the internals of the Uri class are different. I came up with this approach to achieve what I want, which works but it is TERRIBLY hacky.
class Program { static void Main(string[] args) { var uri = new Uri("http://www.yahoo.com/%2F?Foo=Bar%2F#frag"); UriHelper.ForceCanonicalPathAndQuery(uri); Console.WriteLine ("uri.ToString() - " + uri.ToString ()); Console.WriteLine ("uri.AbsoluteUri - " + uri.AbsoluteUri); Console.WriteLine ("uri.Host - " + uri.Host); Console.WriteLine ("uri.Query - " + uri.Query); Console.WriteLine ("uri.PathAndQuery - " + uri.PathAndQuery); Console.WriteLine ("uri.AbsolutePath - " + uri.AbsolutePath); Console.WriteLine ("uri.Fragment - " + uri.Fragment); }
public class UriHelper {
private static Type uriType = typeof(Uri);
private static FieldInfo sourceField;
private static FieldInfo queryField;
private static FieldInfo pathField;
private static FieldInfo cachedToStringField;
private static FieldInfo cachedAbsoluteUriField;
static UriHelper ()
{
sourceField = uriType.GetField ("source", BindingFlags.NonPublic | BindingFlags.Instance);
queryField = uriType.GetField ("query", BindingFlags.NonPublic | BindingFlags.Instance);
pathField = uriType.GetField ("path", BindingFlags.NonPublic | BindingFlags.Instance);
cachedToStringField = uriType.GetField ("cachedToString", BindingFlags.NonPublic | BindingFlags.Instance);
cachedAbsoluteUriField = uriType.GetField ("cachedAbsoluteUri", BindingFlags.NonPublic | BindingFlags.Instance);
}
public static void ForceCanonicalPathAndQuery(Uri uri)
{
var source = (string) sourceField.GetValue (uri);
cachedToStringField.SetValue (uri, source);
cachedAbsoluteUriField.SetValue (uri, source);
var fragPos = source.IndexOf ("#");
var queryPos = source.IndexOf ("?");
var start = source.IndexOf (uri.Host) + uri.Host.Length;
var pathEnd = queryPos == -1 ? fragPos : queryPos;
if (pathEnd == -1)
pathEnd = source.Length+1;
var path = queryPos > -1 ? source.Substring (start, pathEnd - start) : source.Substring (start);
pathField.SetValue (uri, path);
queryField.SetValue(uri, fragPos > -1 ? source.Substring(queryPos, fragPos - queryPos) : source.Substring(queryPos));
}
}
}
When you run this, it outputs the following:
uri.ToString() - http://www.yahoo.com/%2F?Foo=Bar%2F#frag
uri.AbsoluteUri - http://www.yahoo.com/%2F?Foo=Bar%2F#frag
uri.Host - www.yahoo.com
uri.Query - ?Foo=Bar%2F
uri.PathAndQuery - /%2F?Foo=Bar%2F
uri.AbsolutePath - /%2F
uri.Fragment - #frag
I don't at all feel good about it, but it does work, at least for the basic scenario of taking a Uri and issuing a query.
I might be missing something in the Uri class, so if you have a better / less hacky way to do what I am doing here, I'd really appreciate it.