Uri.TryCreate throws UriFormatException?
Asked Answered
G

3

13

I have a method that tries to create a Uri and then clean it up (removes fragments, excludes some domains and query string patterns, etc.). The method looks like this:

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);
}

This method has been working fine for months. But last night it failed. Uri.TryCreate() threw an exception! Here's the stack trace:

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)

Documentation for Uri.TryCreate(Uri, String, out Uri) says that the return value is True if successful, False otherwise, but it's silent about exceptions. However, documentation for Uri.TryCreate(Uri, Uri, out Uri) says:

This method constructs the URI, puts it in canonical form, and validates it. If an unhandled exception occurs, this method catches it. If you want to create a Uri and get exceptions use one of the Uri constructors.

The stack trace shows that the exception was thrown in Uri.TryCreate(Uri, Uri, out Uri), which, according to the documentation, shouldn't happen.

This is a very rare occurrence. I've been using that code for months, running literally billions of urls through it, and haven't encountered a problem until now. Unfortunately I don't know what combination of things caused the problem. I'm hoping to construct a test case that shows the error.

Is this a known bug in Uri.TryCreate, or am I missing something?

Gilleod answered 13/7, 2009 at 15:46 Comment(3)
Do you know what URL it's trying to parse that causing the failure?Nondisjunction
If I knew that, I would have posted it.Gilleod
Use reflector to find out what "TryCreate" is doingOmasum
G
24

Unwilling to wait potentially several months for my code to encounter this situation again, I spent some time with ILDASM to figure out what TryCreate is doing, and then a little more time coming up with a way to reproduce the error.

The reason for the crash in Uri.TryCreate(Uri baseUri, Uri relativeUri, out Uri result) appears to be a badly formatted baseUri. For example, the Uri constructor allows the following:

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

According to the RFC for mailto: URIs, that shouldn't be allowed. And although the constructor creates and returns a Uri object, trying to access (some of) its properties throws UriFormatException. For example, given the above code, this line will throw an exception:

string badUriString = badUri.AbsoluteUri;

I find it rather interesting that the Uri class appears to use two different parsing algorithms: one used during construction, and one used internally for getting the individual components.

Passing this invalid Uri to TryCreate will result in the exception that I described in the original question. The TryCreate method checks the baseUri parameter for null, but doesn't (can't, I would imagine) validate it otherwise. It has to assume that, if the parameter is non-null, the passed object is a fully initialized and valid Uri instance. But at some point in constructing the result, TryCreate attempts to obtain the components of baseUri and an exception is thrown.

I can't say that my program actually encountered a mailto: URL that was formatted this way. I can say with some degree of certainty, though, that an invalid Uri object was the cause of the crash in my program, simply because the exception stack trace from my program matches the stack trace from the test program. Simply put, the bug is in the Uri constructor (and also in the TryCreate methods) which allow the invalid Uri to be created.

You can follow the bug report on Microsoft Connect.

Gilleod answered 22/7, 2009 at 19:40 Comment(1)
Since Connect has closed, what is the resolution? Do I need to worry about deferred error detection on .net-4.6+ or can I assume that new Uri(someString) not throwing is enough to indicate that someString is a valid URI according to System.Uri?Bale
C
3

Now that you know it can fail, let's get more information:

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);
}
Chorister answered 13/7, 2009 at 15:51 Comment(2)
I've added similar code to my application. Unfortunately, it could be many days (weeks or months) before the error is tripped. I'm trying to construct a test case.Gilleod
Ok. Note that the important part is to capture and display the two input parameters to TryCreate. I bet the answer will jump out at you when you see it, especially since you'll also have the stack trace.Chorister
J
-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;
    }
Judo answered 19/7, 2016 at 22:1 Comment(4)
This seems to work fine for me. if you try to use the Uri variable without initializing it, it will cause an error.Judo
Welcome to StackOverflow! When posting answers to questions, please post more than just your code. Explain it, show it's usage, do anything to make your answer as clear as possible, but an answer should always be more than just code. Thanks, and good luck!Clipclop
Thanks for the thought, but it's about seven years too late, and doesn't really address the question. You see, my point was that Uri.TryCreate was throwing an exception that it shouldn't have been throwing. Your proposed solution wouldn't help. I finally identified the problem using code similar to what John Saunders proposed in his answer. That is, catch the exception and output the URL that tripped the error.Gilleod
Your code needlessly assigns to uriResult a second time and is not related to the question. You probably want to delete your answer to salvage your rep.Bale

© 2022 - 2024 — McMap. All rights reserved.