13

The UriBuilder.Query property "contains any query information included in the URI." According to the docs, "the query information is escaped according to RFC 2396."

Based on this, and since this property is writable, I assumed that when you set it, System.UriBuilder would parse your query string, and escape (url encode) according to RFC 2396. In particular, the { and } are not in the unreserved character set, and so they should be escaped according to page 9 of RFC 2396. But, it appears that System.UriBuilder is not doing any escaping.

Do I need to manually Server.URLEncode the params, or is there a way to get System.UriBuilder to handle the encoding?

Here's my sample code. You can run this on ideone.com and see that, indeed, nothing is URL encoded.

using System;

public class Test
{
    public static void Main()
    {
        var baseUrl = new System.Uri("http://www.bing.com");
        var builder = new System.UriBuilder(baseUrl);
        string name = "param";
        string val = "{'blah'}";
        builder.Query = name + "=" + val;
        
        // Try several different ouput methods; none will be URL encoded
        Console.WriteLine(builder.ToString());
        Console.WriteLine(builder.Uri.ToString());
        Console.WriteLine(builder.Query);
    }
}
Community
  • 1
  • 1
Josh
  • 7,232
  • 8
  • 48
  • 75
  • I can't see any obvious code that would perform any kind of conversion. I'm left wondering whether the documentation is incredibly badly worded and should say that the value should be escaped according to RFC2396. – Damien_The_Unbeliever Jul 04 '14 at 14:25
  • Yes, well, when the docs say that the query is escaped, they mean that a Uri object's Query property contains escaped data when read. If you set this data yourself, you have to give it escaped data to begin with. If it escaped data for you, that would give rise to an extremely error-prone `+=` workflow. – Cameron Jul 04 '14 at 14:32
  • @Damien_The_Unbeliever, I had wondered if perhaps "query information is escaped" in the docs should be "query information should be escaped," or to be even more clear, "you should escape query information before writing it to this property." – Josh Jul 04 '14 at 18:44

3 Answers3

17
builder.Uri.AbsoluteUri

is the droid you're looking for, which in your case, returns

http://www.bing.com/?param=%7B'blah'%7D

Given the difficulties with knowing whether the &, + or = symbol should be encoded or not, it's probably better to do your own escaping when you assign to the .Query property.

spender
  • 117,338
  • 33
  • 229
  • 351
  • I tried AbsoluteUri and it seems to be giving the same unencoded output... see this ideone.com test: http://ideone.com/Xl7qfp – Josh Jul 04 '14 at 18:42
  • @Josh : if you do it in a console app, it's properly escaped. IDEone is doing something funky to the console output. – spender Jul 04 '14 at 19:19
  • 1
    While this doesn't answer my precise question ("why doesn't UriBuilder escape the `Query` property?"), it does give me the answer I was really looking for. Thank you! Just for the sake of completeness, I added an answer that directly answers the precise question I asked. – Josh Jul 07 '14 at 13:31
  • I was trying to figure out why ? mark is escaped in my code. This gave the answer. Thank you! I finally replaced UriBuilder with this way https://stackoverflow.com/questions/372865/path-combine-for-urls/1527643#1527643 – Dmitry Pavlov Feb 16 '16 at 16:13
4

In practice I have found that you need to manually escape your query params yourself. System.Uri.AbsoluteUri will attempt to escape things for you (as mentioned in spender's answer), but it may not succeed. For instance, given a the value someemail+test@gmail.com, AbsoluteUri will leave the + unescaped, when it should be escaped as %2B. Otherwise, when the query string is decoded, the + will be transformed into a space, leaving someemail test@gmail.com as the final decoded value.

The bottom line is, you need to escape it yourself to ensure it is escaped correctly.

After reviewing the code in UriBuilder.Query get/set code with dotPeek, I have to conclude that the docs are simply written poorly. Instead of "the query information is escaped according to RFC 2396," it should say "the query information should be escaped according to RFC 2396."

As you can see from the dotPeek decompilation of System.UriBuilder.Query below, there is no automatic escaping happening in the query getter or setter.

[__DynamicallyInvokable]
public string Query
{
  [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get
  {
    return this.m_query;
  }
  [__DynamicallyInvokable] set
  {
    if (value == null)
      value = string.Empty;
    if (value.Length > 0)
      value = (string) (object) '?' + (object) value;
    this.m_query = value;
    this.m_changed = true;
  }
}

System.Uri.AbsoluteUri, however, makes an attempt to escape things. Note the call to this.GetParts in the getter:

[__DynamicallyInvokable]
public string Authority
{
  [__DynamicallyInvokable] get
  {
    if (this.IsNotAbsoluteUri)
      throw new InvalidOperationException(System.SR.GetString("net_uri_NotAbsolute"));
    else
      return this.GetParts(UriComponents.Host | UriComponents.Port, UriFormat.UriEscaped);
  }
}
Community
  • 1
  • 1
Josh
  • 7,232
  • 8
  • 48
  • 75
  • Yep UrlBuilder is a bit lacking... you can now see the [source](https://github.com/dotnet/corefx/blob/c02d33b18398199f6acc17d375dab154e9a1df66/src/System.Private.Uri/src/System/UriBuilder.cs#L265) and here is a bug with += [here](https://github.com/dotnet/corefx/issues/3646) – KCD Oct 21 '15 at 22:57
0

UriBuilder is utterly broken from its API up: The API it provides does not allow proper escaping to be implemented, as it cannot distinguish what you want to do (which is the whole point of escaping!). By providing a single compound Query as string, you cannot express the following different use cases:

  • companyName=Alice&Bob which needs to be escaped as companyName=Alice%26Bob
  • companyName=Alice & purgeCache which should be companyName=Alice&purgeCache

Which is complicated by possible queries like

  • companyName=100%great which should be companyName=100%25great
  • expression=1234%56 which should be expression=1234%2556

There needs to be a way to express which parts are single components which need to be escaped and which are separate pieces joined with &. A single string property cannot provide that, so UriBuilder resorts to guessing… with predictable results: often works, sometimes spectacularly breaks with no way to work around the problem (other than escaping by yourself which defeats the whole reason for the class).

Currently, when you try to set Query on UriBuilder with the above examples, it gives the following results:

  • ?companyName=Alice&Bob (wrong)
  • ?companyName=Alice&purgeCache (correct, this is the interpretation guessed by UriBuilder)
  • ?companyName=100%25great (correct, as %gr could not be a valid escape, it guessed correctly)
  • ?expression=1234V (wrong, %56 might seem like a possible escape, but it was not intended)
Mormegil
  • 7,955
  • 4
  • 42
  • 77