System.Uri doesn’t allow embedded escaped slashes


I use C# a lot to write small utilities and sometimes find that it is annoying to have to dig into the source code to figure out why .Net framework doesn’t work as I expected. This happens again when I am using LinkedIn API (BTW, this is the first occurence of such experience).

LinkedIn API allows you to get the details of user’s connection using url like the following:

http://api.linkedin.com/v1/people/url=http%3A%2F%2Fwww.linkedin.com%2Fin%2Flbeebe/connections

However, if you create a url like the above in C# and send the request to LinkedIn, the server will reject the request and complain about the wrong signature.

I hit this kind of issue several times before (due to slightly different escaping rules between the requirement of oAuth 1.0 3.6 / 2 (RFC3986) and Uri.EscapeUriString in .Net 2.0 (RFC2396)). But this time, the root cause is different.

After spending lots of time debugging by myself and searching the web later, I reached the following post: http://connect.microsoft.com/VisualStudio/feedback/details/94109/

So it is clear that .Net framework explicitly alters the input url for ‘security’ reason. But it doesn’t expose the interface to allow the user to change this behavior (even if it is simply a parser flag called ‘GenericUriParserOptions.DontUnescapePathDotsAndSlashes’ which can be set internally).

Well, I’m on my own to hack it. The workaround posted in the bug changes the parser flag for all Uri objects with the same url scheme, so I have to write a more isolated version:

      public static class HackedUri
    {
        private const GenericUriParserOptions c_Options =
            GenericUriParserOptions.Default |
            GenericUriParserOptions.DontUnescapePathDotsAndSlashes |
            GenericUriParserOptions.Idn |
            GenericUriParserOptions.IriParsing;
        private static readonly GenericUriParser s_SyntaxHttp = new GenericUriParser(c_Options);
        private static readonly GenericUriParser s_SyntaxHttps = new GenericUriParser(c_Options);

        static HackedUri()
        {
            // Initialize the scheme
            FieldInfo fieldInfoSchemeName = typeof(UriParser).GetField("m_Scheme", BindingFlags.Instance | BindingFlags.NonPublic);
            if (fieldInfoSchemeName == null)
            {
                throw new MissingFieldException("’m_Scheme’ field not found");
            }
            fieldInfoSchemeName.SetValue(s_SyntaxHttp, "http");
            fieldInfoSchemeName.SetValue(s_SyntaxHttps, "https");

            FieldInfo fieldInfoPort = typeof(UriParser).GetField("m_Port", BindingFlags.Instance | BindingFlags.NonPublic);
            if (fieldInfoPort == null)
            {
                throw new MissingFieldException("’m_Port’ field not found");
            }
            fieldInfoPort.SetValue(s_SyntaxHttp, 80);
            fieldInfoPort.SetValue(s_SyntaxHttps, 443);
        }

        public static Uri Create(string url)
        {
            Uri result = new Uri(url);

            if (url.IndexOf("%2F", StringComparison.OrdinalIgnoreCase) != -1)
            {
                UriParser parser = null;
                switch (result.Scheme.ToLowerInvariant())
                {
                    case "http":
                        parser = s_SyntaxHttp;
                        break;
                    case "https":
                        parser = s_SyntaxHttps;
                        break;
                }

                if (parser != null)
                {
                    // Associate the parser
                    FieldInfo fieldInfo = typeof(Uri).GetField("m_Syntax", BindingFlags.Instance | BindingFlags.NonPublic);
                    if (fieldInfo == null)
                    {
                        throw new MissingFieldException("’m_Syntax’ field not found");
                    }
                    fieldInfo.SetValue(result, parser);
                }
            }

            return result;
        }
    }

However, I feel guilty for hacking.

Comments (7)

  1. RichardDeeming says:

    With .NET 4.0, you can change this setting in the config file:

    msdn.microsoft.com/…/bb882619.aspx

    msdn.microsoft.com/…/ee656539.aspx

    <configuration>

     <uri>

       <schemeSettings>

         <clear/>

         <add name="http" genericUriParserOptions="DontUnescapePathDotsAndSlashes"/>

       </schemeSettings>

     </uri>

    </configuration>

  2. xiangfan says:

    Thanks!

    Does it work for component? My understanding is that the config file works for exe files.

  3. RichardDeeming says:

    You'll need to add the settings to the configuration file for your application – either YourAppName.exe.config or web.config – and it will affect all Uri parsing in the application.

  4. xiangfan says:

    My concerns,

    1. If the code is in a component, it may or may not work depending on whether the executing assembly has the correct config file.

    2. The escaped slash does expose security issue, so I don't want to enable the parser flag on all Uri objects with the same url scheme (It may be better to throw exception instead of silently alter the url).

    In my opinion, config file is more suitable for settings like assembly binding, etc.

  5. Charles Dupuis says:

    This does not work on Windows Phone -> FieldAccessException

  6. xiangfan says:

    Charles, 'SetValue' needs reflection permission (msdn.microsoft.com/…/6z33zd7h.aspx) and you normally don't have such permission under Silverlight or Windows Phone. So unfortunately this hack doesn't work for those platforms.

  7. satish says:

    xiang, I am one such phone developer stuck without a solution in sight! how do phone developers accomplish this simple call after all?