67

I have a relative or absolute url in a string. I first need to know whether it is absolute or relative. How do I do this? I then want to determine if the domain of the url is in an allow list.

Here is my allow list, as an example:

string[] Allowed =
{
   "google.com",
   "yahoo.com",
   "espn.com"
}

Once I know whether its relative or absolute, its fairly simple I think:

if (Url.IsAbsolute)
{
    if (!Url.Contains("://"))
        Url = "http://" + Url;

    return Allowed.Contains(new Uri(Url).Host);
}
else //Is Relative
{
    return true;
}
Blorgbeard
  • 101,031
  • 48
  • 228
  • 272
Mark
  • 965
  • 2
  • 8
  • 12

3 Answers3

119
bool IsAbsoluteUrl(string url)
{
    Uri result;
    return Uri.TryCreate(url, UriKind.Absolute, out result);
}
Peter Majeed
  • 5,304
  • 2
  • 32
  • 57
Muhammad Hasan Khan
  • 34,648
  • 16
  • 88
  • 131
  • 7
    You can use the `Uri.TryCreate` method instead for prettier code. – Ian Henry Oct 23 '10 at 06:07
  • 4
    System.Uri only considers it absolute if scheme (http/https/etc) is present, but browsers (user agents in general) will assume the same scheme for protocol-relative '//pirate.ru/' URLs. – yzorg Aug 14 '14 at 22:43
  • 10
    To follow on from the comment by @yzorg, you should probably also ensure that the `url` value does not start with `'//'`. If you're using this to check for valid redirects, you should definitely do so. If someone knows a better or more robust way of detecting protocol-relative URLs, I'm all ears. – JT. Mar 30 '15 at 06:37
  • 2
    This returns true from `/dir/example-img.jpg`. That's a relative path, right? – TEK May 10 '16 at 15:09
  • 5
    @TEK: an absolute *path*, but a relative *URI*. – Sz. Mar 25 '18 at 17:11
  • For me this didn't work since for `/dir/test.jpg` the `result` was `file:///dir/test.jpg` and the fuction `IsAbsolute` returned true. I had to use the next solution presented by @chris-marisic – pawellipowczan Mar 11 '20 at 10:16
  • @JT: this is a **critical** remark and unfortunately, still no other way than manual checking. – Wiktor Zychla May 26 '20 at 16:24
44

For some reason a couple of good answers were deleted by their owners:

Via @Chamika Sandamal

Uri.IsWellFormedUriString(url, UriKind.Absolute)

and

Uri.IsWellFormedUriString(url, UriKind.Relative)

The UriParser and implementations via @Marcelo Cantos

Community
  • 1
  • 1
Chris Marisic
  • 32,487
  • 24
  • 164
  • 258
  • 1
    Unfortunately, this yields `false` for `//www.google.com` which is an absolute uri (you can redirect to it and the browser hits the actual domain). It could lead to severe security issues. Check @JT comment from the answer above. – Wiktor Zychla May 26 '20 at 16:26
  • Unfortunately, 302 handles this in every browser this way. If anyone wants to check if they are vulnerable to Open Redirect and somehow google for "how to check if url is absolute", get here and just follow provided answers, the vulnerability still remains. Please, consider this comment not pointing out that your answer is wrong (it's not) but rather, as a warning to someone searching for an answer to slightly different question. Myself included. – Wiktor Zychla May 28 '20 at 09:57
  • @WiktorZychla if you're trying to prevent open redirect you test `maybeUnsafeUri.Host` – Chris Marisic Jun 01 '20 at 20:06
  • What is `maybeUnsafeUri.Host`? – Wiktor Zychla Jun 02 '20 at 10:04
  • `var maybeUnsafeUri = new Uri(externalStringUrl)` then you test Host to ensure it's a server you trust. – Chris Marisic Jun 11 '20 at 19:08
3

You can achieve what you want more directly with UriBuilder which can handle both relative and absolute URIs (see example below).

@icktoofay makes a good point as well: be sure to either include subdomains (like www.google.com) in your allowed list or do more processing on the builder.Host property to get the actual domain. If you do decide to do more processing, don't forget about URLs with complex TLDs like bbc.co.uk.

using System;
using System.Linq;
using System.Diagnostics;

namespace UriTest
{
    class Program
    {
        static bool IsAllowed(string uri, string[] allowedHosts)
        {
            UriBuilder builder = new UriBuilder(uri);
            return allowedHosts.Contains(builder.Host, StringComparer.OrdinalIgnoreCase);
        }

        static void Main(string[] args)
        {
            string[] allowedHosts =
            {
                "google.com",
                "yahoo.com",
                "espn.com"
            };

            // All true
            Debug.Assert(
                IsAllowed("google.com", allowedHosts) &&
                IsAllowed("google.com/bar", allowedHosts) &&
                IsAllowed("http://google.com/", allowedHosts) &&
                IsAllowed("http://google.com/foo/bar", allowedHosts) &&
                IsAllowed("http://google.com/foo/page.html?bar=baz", allowedHosts)
            );

            // All false
            Debug.Assert(
                !IsAllowed("foo.com", allowedHosts) &&
                !IsAllowed("foo.com/bar", allowedHosts) &&
                !IsAllowed("http://foo.com/", allowedHosts) &&
                !IsAllowed("http://foo.com/foo/bar", allowedHosts) &&
                !IsAllowed("http://foo.com/foo/page.html?bar=baz", allowedHosts)
            );
        }
    }
}
Chris Schmich
  • 29,128
  • 5
  • 77
  • 94
  • Does this work with relative urls? Something like `./page/test.aspx` Edit: just tested and it failed to handle a relative url. Does uribuilder have methods to handle relative stuff? – Mark Oct 23 '10 at 06:23
  • @Mark: no, there's no way for `UriBuilder` to know what host you want in that case. It throws an exception. I don't think any approach can handle that case, though, since that is a URL relative to nothing (or at least it's not specified). – Chris Schmich Oct 23 '10 at 06:27
  • @Mark: ignore my last comment, I see that you just want to `return true;` for the relative case, so you can use `Uri.TryCreate` as @Ian suggests. – Chris Schmich Oct 23 '10 at 06:51
  • I used the other code to determine if the url is absolute or relative, and your code for the contains part. Thanks. – Mark Oct 23 '10 at 06:55