Uri.TryCreate выбрасывает UriFormatException?

У меня есть метод, который пытается создать Uri, а затем очистить его (удаляет фрагменты, исключает некоторые домены и шаблоны строк запроса и т.д.). Метод выглядит следующим образом:

static public bool TryCreateCleanUri(Uri baseUri, string relstr, out Uri result)
{
    if (!Uri.TryCreate(baseUri, relstr, out result))
    {
        return false;
    }
    return CleanupUri(result, out result);
}

Этот метод работает отлично в течение нескольких месяцев. Но прошлой ночью это провалилось. Uri.TryCreate() выбрасывает исключение! Здесь трассировка стека:

ERROR: Unhandled exception caught.  Program terminating.
System.UriFormatException: Invalid URI: The hostname could not be parsed.
   at System.Uri.CreateHostStringHelper(String str, UInt16 idx, UInt16 end, Flags& flags, String& scopeId)
   at System.Uri.CreateHostString()
   at System.Uri.GetComponentsHelper(UriComponents uriComponents, UriFormat uriFormat)
   at System.Uri.CombineUri(Uri basePart, String relativePart, UriFormat uriFormat)
   at System.Uri.GetCombinedString(Uri baseUri, String relativeStr, Boolean dontEscape, String& result)
   at System.Uri.ResolveHelper(Uri baseUri, Uri relativeUri, String& newUriString, Boolean& userEscaped, UriFormatException& e)
   at System.Uri.TryCreate(Uri baseUri, Uri relativeUri, Uri& result)
   at System.Uri.TryCreate(Uri baseUri, String relativeUri, Uri& result)

Документация для Uri.TryCreate(Uri, String, out Uri) говорит о том, что в противном случае возвращаемое значение True, False в противном случае, но оно умолчание об исключениях. Однако в документации для Uri.TryCreate(Uri, Uri, out Uri) указано:

Этот метод создает URI, помещает он в канонической форме и проверяет Это. Если возникает необработанное исключение, этот метод ловит его. Если ты хочешь создать использование Uri и получить исключения один из конструкторов Uri.

Трассировка стека показывает, что исключение было выбрано в Uri.TryCreate(Uri, Uri, out Uri), которое, согласно документации, не должно происходить.

Это очень редкое явление. Я использую этот код в течение нескольких месяцев, пробегая буквально миллиарды URL-адресов через него и до сих пор не сталкивался с проблемой. К сожалению, я не знаю, какая комбинация вещей вызвала проблему. Я надеюсь построить тестовый пример, который показывает ошибку.

Является ли это известной ошибкой в ​​ Uri.TryCreate, или я что-то не хватает?

Ответ 1

Не желая ждать несколько месяцев, чтобы мой код снова столкнулся с этой ситуацией, я провел некоторое время с ILDASM, чтобы выяснить, что делает TryCreate, а затем немного больше времени, чтобы воспроизвести ошибку.

Причиной аварии в Uri.TryCreate(Uri baseUri, Uri relativeUri, out Uri result) представляется плохо отформатированный baseUri. Например, конструктор Uri допускает следующее:

Uri badUri = new Uri("mailto:[email protected]@mischel.com");

В соответствии с RFC для mailto: URI, это не должно быть разрешено. И хотя конструктор создает и возвращает объект Uri, пытаясь получить доступ (некоторые из) его свойств, бросает UriFormatException. Например, учитывая приведенный выше код, эта строка генерирует исключение:

string badUriString = badUri.AbsoluteUri;

Мне кажется довольно интересным, что класс Uri, по-видимому, использует два разных алгоритма синтаксического анализа: один используется во время построения и один используется для получения отдельных компонентов.

Передача этого недопустимого Uri в TryCreate приведет к исключению, которое я описал в исходном вопросе. Метод TryCreate проверяет параметр baseUri для null, но не может (я не мог бы представить) проверить его иначе. Он должен предположить, что если параметр не является нулевым, переданный объект является полностью инициализированным и действительным экземпляром Uri. Но в какой-то момент при построении результата TryCreate пытается получить компоненты baseUri и возникает исключение.

Я не могу сказать, что моя программа действительно столкнулась с mailto: URL-адресом, который был отформатирован таким образом. Я могу с некоторой долей уверенности сказать, что недопустимый Uri объект был причиной сбоя в моей программе, просто потому, что трассировка стека исключений из моей программы соответствует трассировке стека из тестовой программы. Проще говоря, ошибка находится в конструкторе Uri (а также в методах TryCreate), которые позволяют создать недействительный Uri.

Вы можете следить за сообщением об ошибке в Microsoft Connect.

Ответ 2

Теперь, когда вы знаете, что это может дать сбой, давайте получим больше информации:

static public bool TryCreateCleanUri(Uri baseUri, string relstr, out Uri result)
{
    try {
        if (!Uri.TryCreate(baseUri, relstr, out result))
        {
            return false;
        }
    }
    catch (UriFormatException ex) {
        throw new InvalidOperationException(
            String.Format("Can create URI for base={0}, rel={1}", baseUri.ToString(), relstr),
            ex);
    }        
    return CleanupUri(result, out result);
}

Ответ 3

 public static bool CheckUrlValid(string url)
    {
        Uri uriResult;
        bool result = Uri.TryCreate(url, UriKind.Absolute, out uriResult);
        if(result)
        {
            uriResult = new Uri(url);
            if (uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp)
                return true;
        }

        return false;
    }