Конец с косой чертой ( "/" ) декодируется перед отправкой запроса
У меня есть 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
. После вызова, сработал.
Как говорится в связанном ответе, это уродливый взлом, может быть разорван в будущих версиях и т.д.
Я надеюсь, что это поможет, это интересная проблема.