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:

https://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: https://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.