Arnout's Eclectica

But I digress…

URL-encoded slashes in System.Uri

30 April 2008 23:11 — .NET,Development

Two weeks ago, an ex-colleague asked me to take a look at a problem that he and his team had encountered. They tried using a System.Uri with URL-encoded slashes, but those slashes kept ending up unencoded in the resulting URI:

Uri uri = new Uri("http://somesite/media/http%3A%2F%2Fsomesite%2Fimage.gif");
Console.WriteLine(uri.AbsoluteUri);
// Output: http://somesite/media/http%3A//somesite%2Fimage.gif

That's a totally different URL, which the target server refuses to process.

I was sure that they must have overlooked something, and that there would be some way to tell the Uri constructor to leave all encoded characters as-is. But no, it does not seem possible; dots and slashes are always decoded. I find that quite surprising, so if anyone can point me to an official solution, I'd be much obliged.

In the mean time, a reflection-based hack, courtesy of Reflector and the .NET Reference Source:

static class UriHacks
{
    // System.UriSyntaxFlags is internal, so let's duplicate the flag privately
    private const int UnEscapeDotsAndSlashes = 0x2000000;
 
    public static void LeaveDotsAndSlashesEscaped(this Uri uri)
    {
        if (uri == null)
        {
            throw new ArgumentNullException("uri");
        }
 
        FieldInfo fieldInfo = uri.GetType().GetField("m_Syntax", BindingFlags.Instance | BindingFlags.NonPublic);
        if (fieldInfo == null)
        {
            throw new MissingFieldException("'m_Syntax' field not found");
        }
        object uriParser = fieldInfo.GetValue(uri);
 
        fieldInfo = typeof(UriParser).GetField("m_Flags", BindingFlags.Instance | BindingFlags.NonPublic);
        if (fieldInfo == null)
        {
            throw new MissingFieldException("'m_Flags' field not found");
        }
        object uriSyntaxFlags = fieldInfo.GetValue(uriParser);
 
        // Clear the flag that we don't want
        uriSyntaxFlags = (int)uriSyntaxFlags & ~UnEscapeDotsAndSlashes;
 
        fieldInfo.SetValue(uriParser, uriSyntaxFlags);
    }
}

6 Comments

  1. Hey there,

    I need to implement a payment method for a side i’m working at. There we need to make calls to an API with URLs like “https://secure.zaypay.com///pay/price setting id/payments/payment id”. Note the 3 slashes after the “.com”. I need to set the HTTP accept header to “application/xml”, and get the xml responce from that API script. For this I use a HttpWebRequest :

    Uri link = new Uri("https://secure.zaypay.com///pay/" + priceSettingId + "/payments/" + paymentId + "?key=theKey");
    
    System.Net.HttpWebRequest textMessageRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(link);
    textMessageRequest.Method = "POST";
    textMessageRequest.Accept = "text/html";
    textMessageRequest.ContentType = "application/x-www-form-urlencoded";
    textMessageRequest.ContentLength = 0;

    Only in the URL the “///” get’s escaped into one “/”. so this is not going to work and I get an File does not exists warning.

    I have been searching for a solution for days now. Can you please give me a hand in a solution for my problem. I think your solution is almost what I need as wel. But i can’t get it to work. How accectly can I use this, I’m not that good with reflection :-(

    Many thanks in advance…..

    Comment by Justin — 28 May 2008 @ 17:29

  2. Justin,

    I just tried your code, using “https://secure.zaypay.com///pay/TestingSomething” as the URL (since I don’t have a key). It works fine for me — using Fiddler, I see that all three slashes are sent in the request.

    I did note that in your example, you’re specifying “text/html” for the Accept header, instead of “application/xml”. In my tests this doesn’t affect the result (apart from getting an HTML response from Zaypay instead of an XML one, of course), but then again, I’m using a fake URL…

    Can you try your test while running Fiddler? Curious to find out what it shows in your case. (Be sure to read my latest post to make sure HttpWebRequest and Fiddler play nicely together).

    Comment by Arnout — 29 May 2008 @ 13:57

  3. Hey,

    Thanks for the quick response. Sorry I forgot to say that this only happens with ASP.NET 2 and lower. And if you got an Vista or Windows machine with the latest updates than this doesn’t happen (Then the slashes don’t get escaped). Microsoft changed this behavior sometime ago. So on my local machine the double slashes don’t get escaped, but on the server they do :(. So I need to find a way to force ASP.NET 2 to not escape these slashes.

    Thanks for your trouble….

    Comment by Justin — 31 May 2008 @ 13:02

  4. Justin,

    I managed to reproduce the issue on an oldish W2K3 VM. Also found it mentioned on Microsoft Connect.

    After some digging with Reflector, the following code seems to do the trick:

    static class UriHacks
    {
        // System.UriSyntaxFlags is internal, so let's duplicate the flag privately
        private const int CompressPath = 0x800000;
     
        public static void LeaveMultipleSlashesAsIs(Uri uri)
        {
            if (uri == null)
            {
                throw new ArgumentNullException("uri");
            }
     
            FieldInfo fieldInfo = uri.GetType().GetField("m_Syntax", BindingFlags.Instance | BindingFlags.NonPublic);
            if (fieldInfo == null)
            {
                throw new MissingFieldException("'m_Syntax' field not found");
            }
            object uriParser = fieldInfo.GetValue(uri);
     
            fieldInfo = typeof(UriParser).GetField("m_Flags", BindingFlags.Instance | BindingFlags.NonPublic);
            if (fieldInfo == null)
            {
                throw new MissingFieldException("'m_Flags' field not found");
            }
            object uriSyntaxFlags = fieldInfo.GetValue(uriParser);
     
            // Clear the flag that we don't want
            uriSyntaxFlags = (int)uriSyntaxFlags & ~CompressPath;
     
            fieldInfo.SetValue(uriParser, uriSyntaxFlags);
        }
    }
    

    To use it, just construct your Uri, and then call LeaveMultipleSlashesAsIs() on it.

    NOTE: I didn’t further investigate what exactly was changed between .NET 2.0 RTM and subsequent versions, so no guarantees that this actually works and/or doesn’t have any horrible side-effects…

    Comment by Arnout — 31 May 2008 @ 15:59

  5. Thank you so much!! It works like a charm :-)
    Is it okay if I post this solution on the Zaypay forum with a reference to your site?
    Again, many thanks for your troubles!

    Comment by Justin — 4 June 2008 @ 12:29

  6. Sure, feel free to post this on Zaypay (be sure to include my “no guarantees” note, though :-). I would indeed appreciate a reference to my site. Thanks.

    Comment by Arnout — 4 June 2008 @ 22:36

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Copyright © 2006-2009 Arnout Grootveld — Powered by WordPress — Hosted at pair Networks