Josh
Josh

Reputation: 7602

Why doesn't UriBuilder.query escaping (url encoding) the query string?

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);
    }
}

Upvotes: 14

Views: 14269

Answers (3)

Mormegil
Mormegil

Reputation: 8071

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)

Upvotes: 3

Josh
Josh

Reputation: 7602

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 [email protected], 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 [email protected] 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);
  }
}

Upvotes: 5

spender
spender

Reputation: 120450

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.

Upvotes: 20

Related Questions