14

System.Uri constructor insists on unescaping %2f sequences as foward slashes, when it sees them in path portion of URL. How could I avoid that behaiviour?

I've seen Connect ticket on this thig, but workaround is not working for .net 4 as far is I understand.

Artem Tikhomirov
  • 21,497
  • 10
  • 48
  • 68
  • 1
    http://stackoverflow.com/a/18511985/420849 - this is solution for similar problem in PowerShell – Boo Aug 29 '13 at 13:16

4 Answers4

7

It keeps the original string internally, for example, the following code:

    Uri u = new Uri("http://www.example.com/path?var=value%2fvalue");
    Console.WriteLine(u.OriginalString);

will display

http://www.example.com/path?var=value%2fvalue

EDIT: I have update the code found in the Connect Link Workaround for recent .NET versions. Here it is:

// System.UriSyntaxFlags is internal, so let's duplicate the flag privately
private const int UnEscapeDotsAndSlashes = 0x2000000;
private const int SimpleUserSyntax = 0x20000;

public static void LeaveDotsAndSlashesEscaped(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;
    uriSyntaxFlags = (int)uriSyntaxFlags & ~SimpleUserSyntax;
    fieldInfo.SetValue(uriParser, uriSyntaxFlags);
}

Of course, it's a hack, so you should use it at your own risks :-)

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Yes, but `new Uri("http://www.example.com/value%2fvalue").ToString()` produces `"http://www.example.com/value/value"`. I should edit question though - thanks for the tip. – Artem Tikhomirov Apr 25 '11 at 18:05
  • Totally offtopic, but thanks for your work on HAP. Have been using it for ages. – Artem Tikhomirov Apr 25 '11 at 18:08
  • 1
    @Artem - thanks :-). for the Uri, I think it's 100% by design, per RFC 3986. But note value%2fvalue in the PATH part or in the QUERY part is not handled the same. You can also use uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.SafeUnescaped) to have "almost" the original string (except for the PATH part). – Simon Mourier Apr 26 '11 at 06:08
  • Sad part is that HttpWebRequest uses System.Uri and one of our systems with plain JSON over HTTP interface requires %2f sequence in PATH part of the query URL. Well, I think it means sockets. – Artem Tikhomirov Apr 27 '11 at 18:19
  • @Artem - I have updated my answer with an adaptation of the "connect" workaround – Simon Mourier Apr 27 '11 at 19:46
4

As I have posted in this question, you can disable this behaviour via a configuration:

<configuration>
  <uri>
    <schemeSettings>
      <add name="http" genericUriParserOptions="DontUnescapePathDotsAndSlashes"/>
    </schemeSettings>
  </uri>
</configuration>
Community
  • 1
  • 1
Pencho Ilchev
  • 3,201
  • 18
  • 21
0

The 2011 answer above by Simon Mourier no longer works on recent versions of .NET. This is because it uses reflection to look up a field inside the UriParser class, and update its value. This field is no longer named m_Flags, but just _flags now. It's not particularly surprising that this field name would have changed in the past 12 years across many versions of .NET.

Here is an alternative that works for .NET 7 (I haven't tried any other versions). Call this function at the start of your program, and adjust the list of schemes to fit your use case:

public static void DoNotSimplifyUris()
{
    const int CanonicalizeAsFilePath = 0x01000000;
    const int ConvertPathSlashes = 0x00400000;
    const int CompressPath = 0x00800000;
    
    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);
    }
}

This should also work with other changes made by UriParser to URIs. For example, by default it simplifies /./ to simply / as if all URIs represented a file path.

I tried sending a request to /foo/./bar without calling this method, and the server received a GET /foo/bar. When calling it first, it receives GET /foo/./bar.

-1

Try to URI.EscapeUriString before construct new URI. It makes %2F looking like %252F and it's work just fine in my case.

Aliaksei Kliuchnikau
  • 13,589
  • 4
  • 59
  • 72