【问题标题】:Percent-encoded slash ("/") is decoded before the request dispatch百分比编码的斜线(“/”)在请求分派之前被解码
【发布时间】:2015-06-19 06:52:13
【问题描述】:

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

如何在 PowerShell 中发出忽略百分比编码值的文字 http 请求?

使用的实际网址 (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`

未找到错误。

也试过WebClientDownloadFile方法:

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

再次请求错误的 URL 返回 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)

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

【问题讨论】:

  • 边界疯狂。我喜欢 Powershell / 我讨厌 Powershell。非常感谢。

标签: .net url powershell uri


【解决方案1】:

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

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

我在我的机器上的 .NET 框架 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 的问题。

【讨论】:

  • 顺便说一句,对我来说,控制台和 ISE 都失败了。我正在运行 PS v3,并且控制台和 ISE 都在 CRLVersion 4.0.30319.18444 上运行。所以它似乎不仅仅是 .Net 4.5 的问题。
  • 有趣。我想知道 ConsoleHost 是否触发了某种“兼容模式”行为?顺便说一句,您可以通过在 PS 安装目录中创建/更新 powershell_ise.exe.config 文件来使您的 ISE 正常工作。
  • 关于powershell_ise.exe.config,我想通了。但我无法编辑它:) 试图以管理员身份在命令行下删除/替换它 - 访问被拒绝。我也无法在管理员下更改它的访问权限设置。我想,我需要更好地研究权限主题。
  • 对我而言,在 Windows Server 2016(PS 版本 5.1.14393.4350,CLR 版本 4.0.30319.42000)上,任何调整之前的默认行为就像您一样,ISE 可以工作,但 PowerShell 失败。
【解决方案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

【讨论】:

  • 在 PS 版本 5.1.14393.4350、CLR 版本 4.0.30319.42000 的 Windows Server 2016 上无法为我工作。
【解决方案3】:

哇,这真是一个难题。有一个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。调用后,工作。

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

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

【讨论】:

  • 谢谢,您的代码运行良好。但是我标记了 Tanuj 的答案,因为他提供了一个替代方案(配置文件),在我看来这是一个更干净的解决方案。
  • 关于控制台和 ISE 性能的差异,检查它们是否运行相同的 CLR 版本 - $PSVersionTable
  • 有道理,我可以看到配置在单台机器上会更干净。我更喜欢这种方法,因为它包含在脚本中,这意味着我可以从一个中心位置将它分发或安排在多台机器上。
  • 遗憾的是,2021 年在 Windows Server 2016 上使用 PS 版本 5.1.14393.4350,CLR 版本 4.0.30319.42000,这不起作用。 ISE 可以,但 PS 不行。两者都有相同的 CLR 版本,并且高于 .NET 4.5(我认为是 4.8)。很奇怪。在具有 PS 版本 5.1.17763.1852 和完全相同的 CLR 版本的 Server 2019 上,它可以工作。编辑:我收回关于它不起作用的评论。我关闭了 PowerShell 并再次尝试,它成功了。谢谢布赖恩。
  • 谢谢@Robin,哇,我几乎不记得写过这个答案,很高兴它有帮助!
猜你喜欢
  • 1970-01-01
  • 2014-01-21
  • 1970-01-01
  • 1970-01-01
  • 2011-03-03
  • 1970-01-01
  • 1970-01-01
  • 2013-04-17
  • 2016-01-13
相关资源
最近更新 更多