Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dotnet/uri-escaping/.
Загадка на сегодня: что выведет код?
var uri = new Uri("http://localhost/%2F1"); Console.WriteLine(uri.OriginalString); Console.WriteLine(uri.AbsoluteUri);
Правильный ответ: зависит. Давайте немножко поразбираемся.
Так что же он выведет?
Результат работы зависит от версии .NET:
// .NET 4.0 http://localhost/%2F1 http://localhost//1 // .NET 4.5 http://localhost/%2F1 http://localhost/%2F1
Да как же так-то?
Увы, до версии .NET 4.0 имела место неприятная бага с экранированием слеша (он же %2F
). В 4.5 её решили пофиксить, чтобы поведение соответствовало RFC 3986. Сделали вроде бы правильно, но добавили дополнительной головной боли разработчикам, которые не знают про этот небольшой нюанс: теперь механизм экранирования зависит от версии Framework-а. Лучше всего использовать правильный механизм из .NET 4.5. Но что, если у нас нет .NET 4.5? Имеется путь починить поведение в .NET 4.0. Для этого необходимо добавить в *.config-файл вашего приложения магические строчки:
<configuration> <uri> <schemeSettings> <add name="http" genericUriParserOptions="DontUnescapePathDotsAndSlashes" /> </schemeSettings> </uri> </configuration>
Работает приведённый фокус-покус начиная с .NET 4.0 beta 2. Т.е., скажем, в .NET 3.5 так сделать не получится. Так что придётся крутиться и вертеться. Например, на просторах интернета можно найти вот такой чудо-хак:
void ForceCanonicalPathAndQuery(Uri uri) { string paq = uri.PathAndQuery; // need to access PathAndQuery FieldInfo flagsFieldInfo = typeof(Uri).GetField("m_Flags", BindingFlags.Instance | BindingFlags.NonPublic); ulong flags = (ulong) flagsFieldInfo.GetValue(uri); flags &= ~((ulong) 0x30); // Flags.PathNotCanonical|Flags.QueryNotCanonical flagsFieldInfo.SetValue(uri, flags); }
А что будет в Mono?
В Mono накосячили точно также. Починка осуществилась совсем недавно с выходом Mono 3.10.0 в октябре 2014. Так что если вы сидите на последней версии, то у вас уже всё должно быть хорошо. Но как же нам теперь переключаться между старым и новым поведением? Для этих целей в классе System.Uri
имеется свойство IriParsing
. Заглянем в код:
private static bool s_IriParsing; internal static bool IriParsing { get { return s_IriParsing; } set { s_IriParsing = value; } }
Выставляется свойство следующим образом:
static Uri () { #if NET_4_5 IriParsing = true; #endif var iriparsingVar = Environment.GetEnvironmentVariable ("MONO_URI_IRIPARSING"); if (iriparsingVar == "true") IriParsing = true; else if (iriparsingVar == "false") IriParsing = false; }
Т.е. выставить его проще всего через переменную окружения MONO_URI_IRIPARSING
.
Заключение
Бага не особо приятная и может стоить вам многих часов душевного спокойствия, если вы на неё случайно наткнётесь. Поэтому я решил оформить такую вот небольшую заметку, чтобы побольше людей было в курсе. Помните о неоднозначности экранирования некоторых URI и пишите стабильный код.
Ссылки по теме
- MS Connect 511010: Erroneous URI parsing for encoded, reserved characters, according to RFC 3986
- Mono Bug 16960
- StackOverflow: Getting a Uri with escaped slashes on mono
- StackOverflow: GETting a URL with an url-encoded slash
- Mono 3.10.0 release notes
- Mike Hadlow: How to stop System.Uri un-escaping forward slash characters
- Arnout's Eclectica: URL-encoded slashes in System.Uri
Комментариев нет:
Отправить комментарий