Конец с косой чертой ( "/" ) декодируется перед отправкой запроса

У меня есть URL, содержащий несколько символов косой черты (/) в качестве части имени файла (а не URL-адреса). Но когда я отправляю HTTP-запрос, процент-кодированный %2F переводится на / перед отправкой запроса, поэтому генерирует неправильный URL-адрес.

Как сделать литеральный HTTP-запрос, игнорируя процентные значения в PowerShell?

Используется фактический URL (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 из-за неправильного запрошенного URL.


Обходной путь 1 (успешно)

Обходные решения, основанные на рефлексии, предоставляемые briantist и Tanuj 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 (неудачный)

Также пытался обмануть парсер URI, заменив символ процента на его процентное значение %25. Но потом он полностью игнорировал все.

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 уже запущен или удален из системы?

Ответ 1

Я играю с вашим кодом в течение последних нескольких часов, и это doozy. Данный код и его варианты передаются при запуске в ISE Powershell, но не работают на консоли Powershell. Сама проблема, по-видимому, является документом Microsoft Connect здесь.

Интересно, что в соответствии с пользователем Glenn Block ответ по связанной проблеме, эта ошибка была исправлена .NET Framework 4.5. Вы можете проверить версию .NET framework, используемую вашей Powershell, выполнив команду $PSVersionTable. Пока значение CLRVersion имеет форму 4.0.30319.x, где x > 1700, то вы используете v4.5 для фреймворка.

Я запускаю Powershell v4.0 на .NET framework 4.5 на своей машине, поэтому объясняет, почему Powershell ISE показывает правильное поведение, но я не мог понять, почему консоль Powershell этого не делает. Я проверил сборки .NET, загруженные обоими, и они кажутся одинаковыми.

Как бы то ни было, у нас есть два варианта. Один из них - использовать отражение и установить частное поле в классе .Net, чтобы предотвратить это поведение (как указано в этом ответе). Другой - использовать обходной путь, указанный в проблеме Microsoft Connect. Это включает в себя следующие шаги:

  • Перейдите в папку установки Powershell (это было "C:\Windows\System32\WindowsPowerShell\v1.0\" на моей машине). В этой папке должен быть файл powershell.exe.
  • Создайте новый текстовый файл в этой папке и назовите его 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>

  • Сохраните этот файл. Закройте все запущенные экземпляры Powershell.

  • Начните новый экземпляр Powershell. Это заставит Powershell обнаружить созданный файл конфигурации и проанализировать его. Записи конфигурации в основном говорят библиотекам .NET об отключении автоматического отката HTTP и HTTPS uri.
  • Запустите script. Вы больше не должны видеть проблему с Uris.

Ответ 2

Если вы собираетесь использовать PowerShell, вы также можете сделать Workaround 1 в чистом PowerShell:

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

Ответ 3

Ничего себе, это довольно загадка. Об этом сообщается об ошибке в Microsoft Connect. Кажется, что для ASP.net существует обходной путь, который не поможет вам в PowerShell.

Здесь, где это становится действительно странным. Я запускаю PowerShell 4.0. Я могу воспроизвести эту проблему при запуске на консольном хосте. Однако , если я запускаю тот же самый код в ISE Host, он работает безупречно.

Я не знаю, как и почему. Я даже удалился в другую систему, не в моей сети, чтобы убедиться, что я ничем не изменил ничего странного в моей системе. Тот же результат. Итог:

$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.

Грязное обходное решение

Я взял код С# в Саймон Мауриер на вопрос Как сделать System.Uri не unescape% 2f ( слэш) в пути? ", и я адаптировал его для использования в 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. После вызова, сработал.

Как говорится в связанном ответе, это уродливый взлом, может быть разорван в будущих версиях и т.д.

Я надеюсь, что это поможет, это интересная проблема.