You may also want to read Invoke-RestMethod cmdlet proxy – more on un-escaping forward slashes in URIs

While working on RabbitMQTools module I came across a problem with un-escaping forward slashes in Urls by Invoke-RestMethod cmdlet. The default Virtual Host on RabbitMQ is called "/" and when invoking Api methods it must be encoded with %2f:

http://localhost:15672/api/exchanes/%2f

As it turns out, this will only work if PowerShell is using .Net Framework 4.5 or newer. All the previous versions will un-escape the forward slash changing Url to:

http://localhost:15672/api/exchanes//

which is not recognised by the server and results with exception. The reason for this behaviour is historical and related to security [1]. Microsoft decided to un-escape forward slash and dot characters to prevent malicious attacks. As this was valid years ago, nowadays with the progress of REST APIs, sometimes it is required to use forward slash in the Url.

If you need to support forward slashes in Url when using .NET Framework 4.0, then there is a way to prevent un-escaping by modifying internal flag on UriParser class. Unfortunately, because the field is internal, it has to be done using reflection:

# protocol for which to prevent unescaping. Change to HTTPS if necessary.
$protocol = "http"
# flag's value
$UnEscapeDotsAndSlashes = 0x2000000

# GetSyntax method, which is static internal, gets registered parsers for given protocol
$getSyntax = [System.UriParser].GetMethod("GetSyntax", 40)
# field m_Flags contains information about Uri parsing behaviour
$flags = [System.UriParser].GetField("m_Flags", 36)

$parser = $getSyntax.Invoke($null, $protocol)
# get the current Flag settings
$currentValue = $flags.GetValue($parser)

# check if un-escaping enabled
if (($currentValue -band $UnEscapeDotsAndSlashes) -eq $UnEscapeDotsAndSlashes) {
  $newValue = $currentValue -bxor $UnEscapeDotsAndSlashes
  # disable unescaping by removing UnEscapeDotsAndSlashes flag
  $flags.SetValue($parser, $newValue)
}

PreventUriUnescaping.ps1

What the code does is checking whether UnEscapeDotsAndSlashes flag is set on the UriParser class and removes it if necessary . This will stop the un-escaping of Uri allowing Invoke-RestMethod cmdlet to call the API with proper Url.

To check whether UnEscapeDotsAndSlashes flag is on run the following code:

$uri = new-object Uri("http://localhost:15672/api/exchanes/%2f")
$uri.AbsoluteUri

The caveat

Unfortunately, there is caveat with this approach. As it will modify setting for the whole Application Domain it means that once executed in PowerShell session it will apply to any consecutive operations on Uri, even those run outside of your module. If you decide to use this approach, please switch it on only when necessary and revert to previous setting once you are done. It is also a good idea to state clearly the fact that the hack is in use.

To see an example of how it is used in RabbitMQTools module, check AddRabbitMQExchange cmdlet.

Thanks

I’d like to thank stimpy77 who authored the C# solution[2] on StackOverflow.

Disclaimer

The propsed solution is invasive and modifies default behaviour of .NET Framework. You may used it at your own risk. The author doesn’t take any responsibility for any effect which are result of using that approach.

References


  1. Erroneous URI parsing for encoded, reserved characters, according to RFC 3986 on Microsoft Connect ↩︎

  2. The discussion on StackOverflow which shows the solution in C#. ↩︎