在请求分派之前解码百分比编码的斜杠(“/”)

时间:2014-08-31 21:43:21

标签: .net url powershell uri

我有一个包含多个斜杠字符(/)的URL作为文件名(不是URL)的一部分。但是当我发送http请求时,百分比编码的%2F在请求分派之前被转换为/,因此生成了错误的URL。

如何制作文字http请求,忽略PowerShell中的百分比编码值?

使用的实际网址(Chromium browser):

https://www.googleapis.com/download/storage/v1/b/chromium-browser-continuous/o/Win_x64%2F292817%2Fchrome-win32.zip?generation=1409504089694000&alt=media


我尝试了Invoke-WebRequest cmdlet:

Invoke-WebRequest -Uri $ChromeUrl -OutFile $FilePath -Verbose

VERBOSE: GET https://www.googleapis.com/download/storage/v1/b/chromium-browser-continuous/o/Win_x64/292817/chrome-win32.zip?generation=1409504089694000&alt=media with 0-byte payload1`

未找到错误。

还尝试了WebClient DownloadFile方法:

$wclient = New-Object System.Net.WebClient
$wclient.DownloadFile($ChromeUrl, $FilePath)

由于再次请求错误的网址而返回404。


解决方法1(成功)

briantistTanuj Mathur提供的基于反思的解决方法都很有效。后者:

$UrlFixSrc = @" 
using System;
using System.Reflection;

public static class URLFix 
{ 
    public static void ForceCanonicalPathAndQuery(Uri uri)
    {
        string paq = uri.PathAndQuery;
        FieldInfo flagsFieldInfo = typeof(Uri).GetField("m_Flags", BindingFlags.Instance | BindingFlags.NonPublic);
        ulong flags = (ulong) flagsFieldInfo.GetValue(uri);
        flags &= ~((ulong) 0x30);
        flagsFieldInfo.SetValue(uri, flags);
    }
} 
"@ 

Add-Type -TypeDefinition $UrlFixSrc-Language CSharp
[URLFix]::ForceCanonicalPathAndQuery([URI]$ChromeUrl)

Invoke-WebRequest -Uri $ChromeUrl -OutFile $FilePath -Verbose

VERBOSE: GET https://www.googleapis.com/download/storage/v1/b/chromium-browser-continuous/o/Win_x64%2F292640%2Fchrome-win32.zip?generation=1409351584147000&alt=media

解决方法2(成功)

更干净的解决方案(由Tanuj Mathur提供),但需要访问系统文件,是通过添加包含以下内容的配置文件%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe.config

<?xml version="1.0" encoding="utf-8" ?> 
 <configuration> 
   <uri>
     <schemeSettings>
      <add name="http" genericUriParserOptions="DontUnescapePathDotsAndSlashes" />
      <add name="https" genericUriParserOptions="DontUnescapePathDotsAndSlashes" />
     </schemeSettings>
   </uri>
</configuration>

必须在powerhsell_ise.exe.config中进行相应的修改,才能在ISE中发挥作用。

解决方法3(失败)

我认为它是一个System.URI类构造函数问题,在隐式转换时调用,它会转换转义值。尝试了一个重载的变体Uri ([String]uriString, [Boolean]dontEscape)。但没有区别。有或没有dontEscape参数的相同结果。

$uri = new-object System.Uri($ChromeUrl, $true)
$uri | Format-List OriginalString, AbsoluteUri

  OriginalString : https://www.googleapis.com/download/storage/v1/b/chromium-browser-continuous/o/Win_x64%2F292817%2Fchrome-win32.zip?generation=1409504089694000&alt=media
  AbsoluteUri    : https://www.googleapis.com/download/storage/v1/b/chromium-browser-continuous/o/Win_x64/292817/chrome-win32.zip?generation=1409504089694000&alt=media

解决方法4(失败)

还尝试通过将百分号字符替换为百分比编码值%25来欺骗URI解析器。但后来它完全忽略了一切。

Invoke-WebRequest -Uri $ChromeUrl.Replace('%', '%25') -OutFile $DownloadPath -Verbose

VERBOSE: GET https://www.googleapis.com/download/storage/v1/b/chromium-browser-continuous/o/Win_x64%252F292817%252Fchrome-win32.zip?generation=1409504089694000&alt=media with 0-byte pa yload

解决方法5(未实施)

我发现请求URL正确的唯一方法是通过Internet Explorer实例。

$ie = New-Object -ComObject InternetExplorer.Application                
$ie.Visible = $true
$ie.Silent = $false
$ie.Navigate2($ChromeUrl)

但后来我不知道如何自动化“保存为”#39;按钮单击并将其保存到所需的路径。此外,即使实施,我也不觉得这是一个很好的解决方案。当IE已经从系统运行或卸载时会发生什么?

3 个答案:

答案 0 :(得分:5)

在过去的几个小时里,我一直在玩你的代码,这真是太过分了。在Powershell ISE中运行时,给定的代码及其变体都会通过,但在Powershell控制台上失败。 问题本身似乎是Microsoft Connect here中记录的问题。

有趣的是,根据用户Glenn Block's answer对相关问题,此错误已在.NET Framework 4.5中修复。 您可以通过运行命令$PSVersionTable来检查Powershell正在使用的.NET框架的版本。只要CLRVersion值的格式为4.0.30319.x,其中x&gt; 1700,那么你正在运行框架的v4.5。

我在我的机器上运行.NET Framework 4.5上的Powershell v4.0,这解释了为什么Powershell ISE显示正确的行为,但我无法弄清楚为什么Powershell控制台没有。我验证了两者加载的.NET程序集,它们看起来是一样的。

根据情况,我们有两种选择。 一种是使用反射并在.Net类上设置一个私有字段以防止这种行为(如this answer中所述)。 另一种方法是使用Microsoft Connect问题中列出的解决方法。这涉及以下步骤:

  1. 转到您的Powershell安装文件夹(我的计算机上为"C:\Windows\System32\WindowsPowerShell\v1.0\")。此文件夹中应包含文件powershell.exe
  2. 在此文件夹中创建一个新文本文件,并将其命名为powershell.exe.config
  3. 在文本编辑器中打开此文件,并将以下文本粘贴到其中: <?xml version="1.0" encoding="utf-8" ?> <configuration> <uri> <schemeSettings> <add name="http" genericUriParserOptions="DontUnescapePathDotsAndSlashes" /> <add name="https" genericUriParserOptions="DontUnescapePathDotsAndSlashes" /> </schemeSettings> </uri> </configuration>

  4. 保存此文件。关闭所有正在运行的Powershell实例。

  5. 启动Powershell的新实例。这将导致Powershell检测您创建的配置文件并解析它。配置条目基本上告诉.NET库禁用HTTP和HTTPS uri的自动转义。
  6. 运行您的脚本。你不应该再看到Uris的问题了。

答案 1 :(得分:2)

如果您要使用PowerShell,您还可以在纯PowerShell中执行解决方法1:

function UrlFix([Uri]$url) {
    $url.PathAndQuery | Out-Null
    $m_Flags = [Uri].GetField("m_Flags", $([Reflection.BindingFlags]::Instance -bor [Reflection.BindingFlags]::NonPublic))
    [uint64]$flags = $m_Flags.GetValue($url)
    $m_Flags.SetValue($url, $($flags -bxor 0x30))
}

UrlFix $ChromeUrl
Invoke-WebRequest -Uri $ChromeUrl -OutFile $FilePath -Verbose

答案 2 :(得分:1)

哇,这是一个相当难的问题。有一个bug report about this on Microsoft Connect。似乎ASP.net有一个解决方法,它在PowerShell中无法帮助你。

但这里真的很奇怪。我正在运行PowerShell 4.0。在控制台主机中运行时,我可以重现此问题。但是,如果我在ISE主机中运行完全相同的代码,它可以完美运行。

我不知道如何或为什么。我甚至远程连接到另一个不在我的网络上的系统,以确保我没有以某种方式改变我的系统上任何奇怪的东西。结果相同。底线:

$a = 'https://www.googleapis.com/download/storage/v1/b/chromium-browser-continuous/o/Win_x64%2F292817%2Fchrome-win32.zip?generation=1409504089694000&alt=media'
Invoke-WebRequest -Uri $a

这在ISE中有效,在控制台主机中不起作用。我甚至尝试用-UseBasicParsing来确保它不是一个奇怪的DOM解析怪癖。

肮脏的解决方法

我在Simon Maurier对“How to make System.Uri not to unescape %2f (slash) in path?”的回答中使用了C#代码,并将其改编为在PowerShell中使用:

$uriFixerDef = @'
using System;
using System.Reflection;

public class UriFixer
{
    private const int UnEscapeDotsAndSlashes = 0x2000000;
    private const int SimpleUserSyntax = 0x20000;

    public static void LeaveDotsAndSlashesEscaped(Uri uri)
    {
        if (uri == null)
            throw new ArgumentNullException("uri");

        FieldInfo fieldInfo = uri.GetType().GetField("m_Syntax", BindingFlags.Instance | BindingFlags.NonPublic);
        if (fieldInfo == null)
            throw new MissingFieldException("'m_Syntax' field not found");

        object uriParser = fieldInfo.GetValue(uri);
        fieldInfo = typeof(UriParser).GetField("m_Flags", BindingFlags.Instance | BindingFlags.NonPublic);
        if (fieldInfo == null)
            throw new MissingFieldException("'m_Flags' field not found");

        object uriSyntaxFlags = fieldInfo.GetValue(uriParser);

        // Clear the flag that we do not want
        uriSyntaxFlags = (int)uriSyntaxFlags & ~UnEscapeDotsAndSlashes;
        uriSyntaxFlags = (int)uriSyntaxFlags & ~SimpleUserSyntax;
        fieldInfo.SetValue(uriParser, uriSyntaxFlags);
    }
}
'@

Add-Type -TypeDefinition $uriFixerDef

$u = 'https://www.googleapis.com/download/storage/v1/b/chromium-browser-continuous/o/Win_x64%2F292817%2Fchrome-win32.zip?generation=1409504089694000&alt=media'

[UriFixer]::LeaveDotsAndSlashesEscaped($u)

Invoke-WebRequest -Uri $u

我首先在ISE中测试它然后发现ISE无论如何都在工作。所以我确实在一个干净的控制台主机环境中尝试了这个,在调用该方法之前,我得到了notfound。在调用之后,工作。

正如在链接的答案中所说,这是一个丑陋的黑客,可能在未来的版本中破坏等。

我希望它有所帮助,这是一个有趣的问题。