113

How can I test a URL if it is a relative or absolute path in Javascript or jQuery? I want to handle accordingly depending if the passed in URL is a local or external path.

if (urlString starts with http:// or https://)
 //do this
TruMan1
  • 33,665
  • 59
  • 184
  • 335

18 Answers18

215

FAST

If you only need to test for http:// or https:// then the most efficient way is:

if (urlString.indexOf('http://') === 0 || urlString.indexOf('https://') === 0)

UNIVERSAL

However, I would suggest a more universal, non case-sensitive, protocol-agnostic approach:

var r = new RegExp('^(?:[a-z+]+:)?//', 'i');
r.test('http://example.com'); // true - regular http absolute URL
r.test('HTTP://EXAMPLE.COM'); // true - HTTP upper-case absolute URL
r.test('https://www.exmaple.com'); // true - secure http absolute URL
r.test('ftp://example.com/file.txt'); // true - file transfer absolute URL
r.test('//cdn.example.com/lib.js'); // true - protocol-relative absolute URL
r.test('git+ssh://example.con/item'); // true - absolute URL with '+' in scheme
r.test('/myfolder/test.txt'); // false - relative URL
r.test('test'); // false - also relative URL

Explain the RegExp

^(?:[a-z+]+:)?//

^ - beginning of the string
(?: - beginning of a non-captured group
[a-z+]+ - any character of 'a' to 'z' or "+" 1 or more times
: - string (colon character)
)? - end of the non-captured group. Group appearing 0 or 1 times
// - string (two forward slash characters)
'i' - non case-sensitive flag

Felix Geenen
  • 2,465
  • 1
  • 28
  • 37
Geo
  • 12,666
  • 4
  • 40
  • 55
  • why a-z ? doesn't domain name can have 0-9 and hyphen in domain name? – Atul Gupta Jan 05 '14 at 18:43
  • 4
    right, but we are not checking for domain name here, are we? This will still work: `/^(?:[a-z]+:)?\/\//i.test('https://www.ex-maple-123.com');` – Geo Jan 06 '14 at 19:28
  • Can scheme contain digits? We all know http, https, ftp, and mailto. Anyone define custom schemes for internal tools? I think OneNote and Outlook do on Windows. – yzorg Aug 14 '14 at 22:37
  • This doesn't account for browser behavior. The browser will remove leading spaces. Start with trim? – yzorg Aug 14 '14 at 22:40
  • good points yzorg. You would have to use ``var r = new RegExp('^\\s*(?:[a-z0-9]+:)?//', 'i')`` – Geo Aug 18 '14 at 15:59
  • The last 2 slashes are unescaped. `//` = `\/\/` – A1rPun Sep 02 '14 at 09:03
  • you would only want to escape them if delimiters are used. Like in my comment from Jan 6 at 19:28 above – Geo Sep 03 '14 at 15:59
  • 1
    This does not capture "mailto:" URLs. Not that I know if mailto URLs are absolute or relative ;-) – Peter Oct 24 '16 at 14:51
  • nice but there is a problem, it does not handle special links like tel: mailto: sms: – Fabio C. Nov 30 '16 at 11:18
  • This is a test for __absolute__ URLs, but cannot be used to test for __relative__ urls. As noted above, things like `mailto:...`, `about:...` & `sms:...` will cause false positives when trying to find *relative* paths (also, things that are invalid *Locations*). – Zach Lysobey Jul 16 '17 at 20:59
  • `mailto:`, etc. are not URLs but rather URIs and thus should be handled differently if not blocked entirely. – Chris Smith Jun 03 '18 at 18:44
  • `view-source:` doesn't match. – Knu Oct 14 '18 at 00:38
  • This answer fails on `https:example.com`. This URL works fine at least in Chrome. – Gene S Aug 17 '19 at 08:53
  • 3
    `new RegExp('^(//|[a-z]+:)', 'i')` should work for matching `mailto:`, `about:`, `tel:`, etc. including the existing test cases. The idea here is to still provide for protocol-relative absolute URLs and while extending existing functionality of detecting absolute URLs without requiring a check for the double forward slashes (`//`). Thus, `r.test('mailto:hi@example.com') === true`, `r.test('https:example.com') === true`, and so forth. – Matt Borja Aug 26 '19 at 05:32
  • This won't work for URLs like: `git+ssh://example.com:1231/some/thing`: https://datatracker.ietf.org/doc/html/rfc3986#section-4.3 – Iwan Aucamp Oct 28 '21 at 21:24
  • @IwanAucamp good point - feel free to update your non-captured group with an escaped plus, i.e. replace `[a-z]+` with `[a-z\+]+` – Geo Oct 29 '21 at 22:58
  • @IwanAucamp I edited for that. Nice catch. – Hunter Kohler Jul 10 '22 at 18:45
40
var pat = /^https?:\/\//i;
if (pat.test(urlString))
{
    //do stuff
}

For protocol relative urls, use this regex:

/^https?:\/\/|^\/\//i

Trevor
  • 13,085
  • 13
  • 76
  • 99
strah
  • 6,702
  • 4
  • 33
  • 45
  • 17
    This answers the asked question but you may also want to consider [protocol relative urls](http://www.paulirish.com/2010/the-protocol-relative-url/) which start with `//`. – gerryster Jul 31 '13 at 15:31
  • 5
    What if url contains "file://"? BOOM! Tragedy. Answer from @Philipp is more reliable. – Skay Apr 09 '18 at 10:20
  • 3
    The accepted answer is invalid, at least in 2019. Chrome happily accepts http:example.com. – Gene S Aug 17 '19 at 08:50
38

Depending on your needs, I think that a more reliable way to determine this is to use the built-in URL interface to construct a couple URL objects and compare origins.

new URL(document.baseURI).origin === new URL(urlToTest, document.baseURI).origin;

This allows the browser to parse and figure all this out for you, without having to worry about the side effects of edge cases.

Brad
  • 159,648
  • 54
  • 349
  • 530
  • This is a great new addition to the other more duck-typing solutions. However I wonder why you do not suggest `new URL(document.baseURI).origin === new URL(urlToTest,document.baseURI).origin` ? Would this not be more apt for those cases where the webpage contains a [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) ? – humanityANDpeace Jul 15 '19 at 22:17
  • 1
    @humanityANDpeace Yes, good idea! I've updated the answer with your improvements. – Brad Jul 15 '19 at 22:19
  • 3
    I've downvoted every regex-based answer and upvoted every one that uses built-in classes like `URL`. This is the right answer. Thanks! – Ahmed Fasih Nov 27 '21 at 05:26
  • I don't think this actually works – `new URL(urlToTest, document.baseURI).origin` will always return the same, regardless of whether the URL is absolute or relative – David Chouinard Oct 08 '22 at 18:05
  • 1
    @DavidChouinard It does work. Please use the full line of code. Constructing the URL object from the `urlToTest` allows the browser to determine what the `origin` should be for that URL. It doesn't matter if it's a full URL matching the same origin... as long as it matches that origin, you can determine if it's relative to the current origin. – Brad Oct 09 '22 at 02:29
31

Original Answer

A very fast and very flexible check is:

if (url.indexOf('://') > 0 || url.indexOf('//') === 0 ) {
    // URL is absolute; either "http://example.com" or "//example.com"
} else {
    // URL is relative
}

This will recognize an absolute URL, if:

  • URL contains "://" anywhere after the first character, or
  • URL starts with "//" (protocol relative)

  • No regex.
  • No jQuery or other dependency.
  • No hardcoded protocol names that make the condition case sensitive.
  • No string manipulation (e.g. toLowerCase or similar).
  • Only checks for "relative or absolute" but does not make any other sanity checks, can be used for web URLs or any internal protocol.

Update 1 (full function example)

Here is a quick function that returns true/false for the given URL:

function isUrlAbsolute(url) { 
    return (url.indexOf('://') > 0 || url.indexOf('//') === 0);
}

And same in ES6:

const isUrlAbsolute = (url) => (url.indexOf('://') > 0 || url.indexOf('//') === 0)

Update 2 (URLs inside URL param)

To additionally address URLs in format /redirect?target=http://example.org I recommend to use this code:

function isUrlAbsolute(url) {
    if (url.indexOf('//') === 0) {return true;} // URL is protocol-relative (= absolute)
    if (url.indexOf('://') === -1) {return false;} // URL has no protocol (= relative)
    if (url.indexOf('.') === -1) {return false;} // URL does not contain a dot, i.e. no TLD (= relative, possibly REST)
    if (url.indexOf('/') === -1) {return false;} // URL does not contain a single slash (= relative)
    if (url.indexOf(':') > url.indexOf('/')) {return false;} // The first colon comes after the first slash (= relative)
    if (url.indexOf('://') < url.indexOf('.')) {return true;} // Protocol is defined before first dot (= absolute)
    return false; // Anything else must be relative
}

And the same in short form and ES 6

// Traditional JS, shortened
function isUrlAbsolute(url) {
    return url.indexOf('//') === 0 ? true : url.indexOf('://') === -1 ? false : url.indexOf('.') === -1 ? false : url.indexOf('/') === -1 ? false : url.indexOf(':') > url.indexOf('/') ? false : url.indexOf('://') < url.indexOf('.') ? true : false;
}

// ES 6
const isUrlAbsolute = (url) => (url.indexOf('//') === 0 ? true : url.indexOf('://') === -1 ? false : url.indexOf('.') === -1 ? false : url.indexOf('/') === -1 ? false : url.indexOf(':') > url.indexOf('/') ? false : url.indexOf('://') < url.indexOf('.') ? true : false)

Here are some test cases:

// Test
console.log( isUrlAbsolute('http://stackoverflow.com') ) // -> true
console.log( isUrlAbsolute('//stackoverflow.com') ) // -> true
console.log( isUrlAbsolute('stackoverflow.com') ) // -> false
console.log( isUrlAbsolute('Ftp://example.net') ) // -> true
console.log( isUrlAbsolute('/redirect?target=http://example.org') ) // -> false

Update 3 (clarify relative URLs)

I've seen a few comments about invalid output:

  • Solution returns false for localhost
  • Answer fails on http:example.com

However, those URLs are indeed relative URLs. It's easy to test:

  1. Create some folders on your localhost webroot, say a/b/c/
  2. Create an index.html file and place following link into it: <a href="localhost">test</a>
  3. Open the index page in your browser: http://localhost/a/b/c/index.html and click on the link. You will end on http://localhost/a/b/c/localhost (and not on http://localhost)
  4. Same happens when placing the link http:example.com into your index.html file. You end on http://localhost/a/b/c/example.com instead of http://example.com
Philipp
  • 10,240
  • 8
  • 59
  • 71
  • 6
    Nope. I was just tracking down a bug in my project and discovered it was too such a function. The webpage had a url like `/redirect?target=http://example.org` – BeniBela Dec 18 '16 at 17:59
  • @BeniBela, you could fix this by using ```function isUrlAbsolute(url) { var firstSlash = url.indexOf('/'); var colonDoubleSlash = url.indexOf('://'); return ((firstSlash > 0 && colonDoubleSlash > 0 && colonDoubleSlash < firstSlash) || url.indexOf('//') === 0); }``` – Sebastian Apr 06 '17 at 05:59
  • @BeniBela You are right, this can happen in some cases. I updated the code above to handle this. However, I strongly suggest to url-encode all query params, i.e. to use `/redirect?target=http%3A%2F%2Fexample.com` – Philipp Jul 24 '17 at 09:08
  • This answers the question, but it doesn't really test for whether the input is absolute. For example, "/aaa/bbb" comes back as "relative", when it's actually absolute. – N73k Mar 13 '19 at 22:38
  • @N73k actually I consider your example "/aaa/bbb" relative to the domain. I.e. if you have `` on on site1.com and site2.com both images are different (i.e. relative). While ``is identical for all domains (which is absolute) – Philipp Mar 14 '19 at 06:25
  • This answer fails on `http:example.com`. This URL works fine at least in Chrome 76. – Gene S Aug 17 '19 at 08:56
  • @GeneS your URL is relative. You can test it: create the file `example.org/a/b/c/index.html`. Add the link ` – Philipp Aug 17 '19 at 14:16
  • @molsson - It's surprising, but the result is correct. The URL `localhost` is indeed relative. However, `http://localhost` is absolute... You can test it (see my previous comment for an example) – Philipp Aug 17 '19 at 14:21
  • @Phillipp. Nope, that's an absolute URL. Just did what you suggested and Chrome 76 opened `http://example.org/a` – Gene S Aug 17 '19 at 14:30
  • Chrome also follows an invalid url `http:/example.com`. – Gene S Aug 17 '19 at 14:31
  • Maybe a difference between Mac and Windows? I've also tested it in Chrome, and it was treated as relative URL for me. Also the RFC states, that `"http:g" = "http://a/b/c/g"`. Possibly it's best to use the `//` to ensure consistent behavior ;-) – Philipp Aug 17 '19 at 14:47
  • Actually the difference is due to the origin URL. If the page is loaded from file:///, then the url 'http:example.com' is treated as absolute. It's treated as relative otherwise. – Gene S Aug 17 '19 at 14:56
  • 3
    `isUrlAbsolute('redirect')` gives `false`, which is correct, but `isUrlAbsolute('redirect?target=http://example.org')` gives `true`, which is not correct. I'm thinking it would be useful to check if the `://` comes after a `?` or a `#`… is there any situation where that would conflict with something? – Adrian Schmidt Jun 24 '21 at 09:13
  • Fails with test case: `http://somehostname` which is absolute. Not all host names have dots in them. – Ryan Jun 08 '22 at 04:31
20

Use a regex:

if (/^(?:[a-z]+:)?\/\//i.test(url))
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • this seems to be the most universal answer. Only lacks a protocol-relative URL (e.g. //cdn.example.com/libary.js) – Geo Oct 31 '13 at 14:38
  • Although the question only mentions http and https, a general solution might also have to take into account a "mailto:" url, which doesn't have forward slashes. – mikebridge Nov 29 '13 at 18:04
  • @mikebridge are you saying that `mailto:` can be absolute or relative sometimes? – Geo Jan 06 '14 at 19:34
  • 1
    @Geo: No; he's saying that `mailto:` is absolute even though it has no `/` characters. – SLaks Jan 06 '14 at 19:35
  • please join chat here http://chat.stackoverflow.com/rooms/44712/absolute-or-relative-url – Geo Jan 07 '14 at 14:35
  • This answer fails on `http:example.com`. This URL works fine at least in Chrome. – Gene S Aug 17 '19 at 08:53
12

Even more Universal RFC-compliant URI approach:

(?:^[a-z][a-z0-9+\.-]*:|\/\/) regex explanation

The other solutions listed here would fail for links like mailto:evan@nylas.com

RFC 3986 defines a Scheme as:

scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )

3.1. Scheme https://www.rfc-editor.org/rfc/rfc3986#section-3.1

While the protocol-relative url is technically valid as per section 4.2, Paul Irish has swung back the other way and considers this an anti-pattern. See http://www.paulirish.com/2010/the-protocol-relative-url/

4.2. Relative Reference https://www.rfc-editor.org/rfc/rfc3986#section-4.2

If you'd like the regex without protocol-relative url's use:

^[a-z][a-z0-9+\.-]*:

To see a full list of other types of valid uri edge cases, check out the list here: https://en.wikipedia.org/wiki/URI_scheme

Community
  • 1
  • 1
Evan
  • 7,396
  • 4
  • 32
  • 31
  • 3
    Should that `^` go outside the group? As written it would match `//` in the not-starting position (so a relative URL like `#//` would match). Also, it's important to specify that this regex should be case-insensitive, so the complete definition would look like `/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i`. – sethobrien Nov 09 '16 at 19:45
  • Id reckon that one-character schemes should be considered drive letters. So Id replace `*` with `+`. – Knu Nov 03 '18 at 03:51
  • Your regex matches `//cdn.example.com/lib.js` which is a relative-uri, not an absolute URI: https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 "A relative reference that begins with two slash characters is termed a network-path reference;" – Iwan Aucamp Oct 28 '21 at 21:23
11

You can use a try, catch block to help with this. Rather than using a regular expression, you can use the URL interface at every step.

isExternalUrl (urlString) {
  try {
    const url = new URL(urlString) // THROW ON MISSING SCHEME

    // DOES THIS URL ORIGINATE FROM THIS WEBSITE?
    if (url.origin !== new URL(document.URL, document.baseURI).origin) {
      return true // IS EXTERNAL URL
    }
  } catch (_e) {
    // THROWS WHEN URL DOES NOT HAVE A SCHEME
    new URL(urlString, document.baseURL) // THROW AN EXCEPTION IF THE URL IS TRULY MALFORMED IN SOME WAY
  }

  return false
}
Jonathan
  • 10,936
  • 8
  • 64
  • 79
10

Nowdays, when a lot of services use protocol-relative URL (eg. //cdn.example.com/libary.js), this method is safer:

var isAbsolute = new RegExp('^([a-z]+://|//)', 'i');

if (isAbsolute.test(urlString)) {
  // go crazy here
}
Community
  • 1
  • 1
rgtk
  • 3,240
  • 1
  • 30
  • 36
9

Don't use low-level stuff like regexp etc. These things have been solved by so many other people. Especially the edge cases.

Have a look at URI.js, it should do the job: http://medialize.github.io/URI.js/docs.html#is

var uri = new URI("http://example.org/");
uri.is("absolute") === true;
koppor
  • 19,079
  • 15
  • 119
  • 161
7

Here's a pretty robust solution for the browser environment:

Let the browser handle everything. No need for some complicated/error prone regexes.

const isAbsoluteUrl = (url) => {
  const link = document.createElement('a');
  link.href = url;
  return link.origin + link.pathname + link.search + link.hash === url;
};
Etienne Martin
  • 10,018
  • 3
  • 35
  • 47
4
var external = RegExp('^(https?:)?//');
if(external.test(el)){
    // do something
}

EDIT:

With the next regular expression, you can even check if the link goes to the same domain or to an external one:

var external = RegExp('^((f|ht)tps?:)?//(?!' + location.host + ')');
if(external.test(el)){
    // do something
}
davids
  • 6,259
  • 3
  • 29
  • 50
  • You need to escape the `.` characters that will almost certainly be in the hostname. Otherwise foo.example.com will also match fooXexample.com – Quentin May 21 '12 at 15:15
2
var adress = 'http://roflmao.com';
if (adress.substr(0,7) == 'http://' || adress.substr(0,8) == 'https://') {
    //
}
OptimusCrime
  • 14,662
  • 13
  • 58
  • 96
  • yeah that's true. I don't use regex cause I suck at it. Anyways, won't Http be converted into http in modern browsers? – OptimusCrime May 21 '12 at 14:34
2

Neither of the mentioned solutions solved a redirect_url hack where the hacker entered /\/example.com or /\\/example.com. This is what I came up with to determine if our redirect url was relative:

var isRelative = !redirectUrl.match(/(\:|\/\\*\/)/);  // Don't allow "//" (with optional "\"'s) or ":"
Dustin
  • 3,965
  • 5
  • 27
  • 33
1

It should not start with a slash or hash, and it should not contain a double slash if not preceded by question mark or hash? I would not test that with a single regexp, it would be very complicated to match "no double slash".

function test(s) {
    return s.charAt(0) != "#"
      && s.charAt(0) != "/"
      && ( s.indexOf("//") == -1 
        || s.indexOf("//") > s.indexOf("#")
        || s.indexOf("//") > s.indexOf("?")
    );
}

would be easier, clearer and imho faster.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

Following function will get called when click event occurs on a hyperlink i.e 'a' tag if the tag contains url will be relative or contains same host then that new page will get loaded into same browser tab, If it contains different url then page will load in new browser tab

jQuery(document).ready(function() {
    $('a').click(function(){

        var a = this;
        var a_href = $(this).attr('href');
        var regex = new RegExp('^(?:[a-z]+:)?//', 'i');     

        if(a.host == location.host || regex.test(a_href) == false){
            a.target = '_self';
        }else{
            a.target = '_blank';
        }
    }); 
});
Prajyot
  • 9
  • 1
  • 4
0

Using the standard URL constructor can help:

const url = new URL(urlToTest);
const isAbsolute = url.protocol && url.hostname

and as a bonus it will check if your value is a valid URL.

Jérôme Beau
  • 10,608
  • 5
  • 48
  • 52
0

Rationale for this late answer: The question phrasing makes two separate questions and we have many problematic answers on both accounts. First of all, /1.html is an absolute path. However, in the question text, it is stated that the links with protocol included should be tested. I will answer both (checking absolute path and testing for protocol).

function hasProtocol(path) {
    return /^\w+:/.test(path)
}

function isAbsolutePath(path) {
    return path.startsWith("/") || hasProtocol(path);
}

Some examples:

console.log(isAbsolutePath("FILE:/1.html"));
console.log(isAbsolutePath("/1.html"));
console.log(isAbsolutePath("1.html"));
console.log(isAbsolutePath("relative/to/1.html"));
console.log(isAbsolutePath("mailto:1.html"));
console.log(isAbsolutePath("https://example.com/1.html"));

console.log(hasProtocol("FILE:/1.html"));
console.log(hasProtocol("/1.html"));
console.log(hasProtocol("1.html"));
console.log(hasProtocol("relative/to/1.html"));
console.log(hasProtocol("mailto:1.html"));
console.log(hasProtocol("https://example.com/1.html"));
Cem Kalyoncu
  • 14,120
  • 4
  • 40
  • 62
-1
var isExternalURL = url.toLowerCase().indexOf('http://') === 0 || url.toLowerCase().indexOf('https://') === 0 ;
Gynteniuxas
  • 7,035
  • 18
  • 38
  • 54
rinjan
  • 550
  • 5
  • 19