如何测试URL字符串是绝对的还是相对的?


76

如果URL是Javascript或jQuery中的相对或绝对路径,如何测试URL?我想根据传入的URL是本地路径还是外部路径进行相应处理。

if (urlString starts with http:// or https://)
 //do this

Answers:


38
var pat = /^https?:\/\//i;
if (pat.test(urlString))
{
    //do stuff
}

对于协议相对URL,请使用此正则表达式:

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


14
这可以回答问题,但是您可能还需要考虑以开头的协议相关网址//
gerryster

3
如果网址包含“ file://”怎么办?繁荣!悲剧。@Philipp的答案更可靠。
Skay

2
接受的答案至少在2019年是无效的。Chrome乐于接受http:example.com。
基因S

178

快速

如果您只需要测试http://或,https://那么最有效的方法是:

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

普遍

但是,我建议一种更通用,不区分大小写,与协议无关的方法:

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('/myfolder/test.txt'); // false - relative URL
r.test('test'); // false - also relative URL

解释RegExp

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

^-字符串的
(?:开头-未捕获组的开始-'a
[a-z]+'至'z'的任何字符1次或多次
:-字符串(冒号)
)?-未捕获组的末尾。组出现0或1次
//-字符串(两个正斜杠字符)
'i'-不区分大小写的标志


为什么是az?域名中不能包含0-9和连字符吗?
Atul Gupta 2014年

3
是的,但是我们不在这里检查域名,对吗?仍然可以使用:/^(?:[a-z]+:)?\/\//i.test('https://www.ex-maple-123.com');
地理位置

方案可以包含数字吗?我们都知道http,https,ftp和mailto。有人为内部工具定义自定义方案吗?我认为OneNote和Outlook在Windows上也可以。
yzorg 2014年

1
这不会捕获“ mailto:” URL。不知道我的mailto URL是绝对的还是相对的;-)
Peter

1
new RegExp('^(//|[a-z]+:)', 'i')应匹配工作mailto:about:tel:等包括现有的测试用例。这里的想法是仍然提供相对于协议的绝对URL,同时扩展了检测绝对URL的现有功能,而无需检查双正斜杠(//)。因此,r.test('mailto:hi@example.com') === truer.test('https:example.com') === true,等等。
马特·博尔贾

22

原始答案

一个非常快速,非常灵活的检查是:

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

如果出现以下情况,它将识别出绝对URL:

  • URL的第一个字符的任何地方都包含“://” ,或者
  • 网址以“ //”开头(与协议有关)

  • 没有正则表达式。
  • 没有jQuery或其他依赖项。
  • 没有使条件区分大小写的硬编码协议名称。
  • 没有字符串操作(例如toLowerCase或类似内容)。
  • 只能用于“相对”或“绝对”检查,而不能进行其他任何健全性检查,可用于Web URL或任何内部协议。

更新1(完整功能示例)

这是一个针对给定URL返回true / false的快速函数

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

同样在ES6中:

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

更新2(URL参数内的URL)

要另外以格式寻址URL,/redirect?target=http://example.org我建议使用以下代码:

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
}

与简写形式和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)

以下是一些测试案例:

// 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

更新3(阐明相对URL)

我已经看到有关无效输出的一些评论:

  • 解决方案针对的结果返回false localhost
  • 答案失败 http:example.com

但是,这些URL确实是相对URL。很容易测试:

  1. 在您的本地主机webroot上创建一些文件夹,例如 a/b/c/
  2. 创建一个index.html文件,并将以下链接放入其中: <a href="localhost">test</a>
  3. 在浏览器中打开索引页面:http://localhost/a/b/c/index.html并单击链接。您将在http:// localhost / a / b / c / localhost上结束(而不是在http:// localhost上
  4. 将链接http:example.com放入index.html文件时,也会发生同样的情况。您以http://localhost/a/b/c/example.com而不是http://example.com结尾

4
不。我只是在跟踪项目中的错误,发现它也是如此。该网页的网址类似 /redirect?target=http://example.org
BeniBela,2016年

@BeniBela,您可以通过使用解决这个问题function isUrlAbsolute(url) { var firstSlash = url.indexOf('/'); var colonDoubleSlash = url.indexOf('://'); return ((firstSlash > 0 && colonDoubleSlash > 0 && colonDoubleSlash < firstSlash) || url.indexOf('//') === 0); }
塞巴斯蒂安

@BeniBela是的,在某些情况下可能会发生。我更新了上面的代码来解决这个问题。但是,我强烈建议对所有查询参数进行url编码,即使用/redirect?target=http%3A%2F%2Fexample.com
Philipp

这回答了问题,但是并没有真正测试输入是否为绝对值。例如,当“ / aaa / bbb”实际上是绝对的时,它以“相对”的形式返回。
N73k,

@ N73k实际上,我认为您的示例相对于域为“ / aaa / bbb”。即,如果您<img src="/aaa/bbb">在site1.com和site2.com上都有,则两个图像是不同的(即相对的)。虽然<img src="//site1.com/aaa/bbb">所有域都相同(这是绝对的)
菲利普

17

使用正则表达式:

if (/^(?:[a-z]+:)?\/\//i.test(url))

这似乎是最普遍的答案。仅缺少相对协议网址(例如//cdn.example.com/libary.js)
地理位置

尽管该问题仅提及http和https,但一般的解决方案可能还必须考虑“ mailto:” URL,该URL不带正斜杠。
mikebridge

@mikebridge您是说mailto:有时候是绝对的还是相对的?
2014年

1
@Geo:不;他说那mailto:是绝对的,即使它没有/字符。
SLaks 2014年


13

更通用的RFC兼容URI方法:

(?:^[a-z][a-z0-9+.-]*:|\/\/) 正则表达式说明

对于此处列出的链接,此处列出的其他解决方案将失败 mailto:evan@nylas.com

RFC 3986将方案定义为:

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

3.1。方案 https://tools.ietf.org/html/rfc3986#section-3.1

尽管相对协议的url在技术上符合第4.2节的规定,但Paul Irish已转回另一种方式,并将其视为反模式。参见http://www.paulirish.com/2010/the-protocol-relative-url/

4.2。相对参考 http://tools.ietf.org/html/rfc3986#section-4.2

如果您想要不使用协议相对URL的正则表达式:

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

要查看其他类型的有效uri edge case的完整列表,请在此处查看列表:https : //en.wikipedia.org/wiki/URI_scheme


3
那应该^在小组之外吗?如所写,它将//在不开始的位置匹配(因此,类似的相对URL#//将匹配)。同样,指定此正则表达式不区分大小写也很重要,因此完整的定义应类似于/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i
sethobrien '16

我认为应该将单字符方案视为驱动器号。因此,将ID替换*+
克伦民族联盟

12

如今,当许多服务使用协议相对URL(例如//cdn.example.com/libary.js)时,此方法更安全:

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

if (isAbsolute.test(urlString)) {
  // go crazy here
}

1
要捕获类似“ HTTP://WWW.GOOGLE.COM ”的网址,您应该使用'^([A-Za-z]+://|//)'
Dean Meehan

3
只需设置i标志以忽略大小写。答案已编辑。谢谢。
rgtk '16

9

根据您的需求,我认为确定此问题的一种更可靠的方法是使用内置URL接口来构造几个URL对象并比较来源。

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

这使浏览器可以为您解析和解决所有这些问题,而不必担心边缘情况的副作用。


这是其他鸭式解决方案的一个很好的新补充。但是我想知道为什么您不建议new URL(document.baseURI).origin === new URL(urlToTest,document.baseURI).origin?这是否更适合网页包含的情况<base>
humanityANDpeace

1
@humanityANDpeace是的,好主意!我已经通过您的改进来更新答案。
布拉德(Brad)

5
var external = RegExp('^(https?:)?//');
if(external.test(el)){
    // do something
}

编辑:

使用下一个正则表达式,您甚至可以检查链接是指向同一个域还是指向外部域:

var external = RegExp('^((f|ht)tps?:)?//(?!' + location.host + ')');
if(external.test(el)){
    // do something
}

您需要转义.几乎可以肯定在主机名中的字符。否则foo.example.com也将匹配fooXexample.com
Quentin

5

不要使用诸如regexp等低级的东西。这些东西已经被很多其他人解决了。特别是边缘情况。

看一下URI.js,它应该可以完成工作:http : //medialize.github.io/URI.js/docs.html#is

var uri = new URI("http://example.org/");
uri.is("absolute") === true;

5
如果您需要进行很多操作,则很有用,但仅为此目的而使用JS库似乎有点过头了。
伊万·多诺万

4

这是针对浏览器环境的强大解决方案:

让浏览器处理一切。不需要一些复杂/容易出错的正则表达式。

const isAbsoluteUrl = (url) => {
  const link = document.createElement('a');
  link.href = url;
  return link.origin + link.pathname + link.search + link.hash === url;
};

2
var adress = 'http://roflmao.com';
if (adress.substr(0,7) == 'http://' || adress.substr(0,8) == 'https://') {
    //
}

对,那是真的。我不使用正则表达式,因为我很烂。无论如何,在现代浏览器中Http不会转换成http吗?
OptimusCrime 2012年

2

上述解决方案都无法解决redirect_url黑客进入/\/example.com或的黑客行为/\\/example.com。这是我想出的方法,以确定我们的重定向URL是否是相对的:

var isRelative = !redirectUrl.match(/(\:|\/\\*\/)/);  // Don't allow "//" (with optional "\"'s) or ":"

1

当超链接(即“ a”标签)上发生点击事件时,将调用以下函数,如果包含URL的标签是相对的或包含相同的主机,则新页面将被加载到同一浏览器选项卡中;如果包含不同的URL,则页面将被加载在新的浏览器标签中

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

0

它不应以斜杠或哈希开头,并且如果前面没有问号或哈希,则不应包含双斜杠?我不会用一个正则表达式来测试,匹配“没有双斜杠”将非常复杂。

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

会更容易,更清晰和更快。


0

您可以使用try,catch块来解决此问题。您可以在每个步骤中使用URL接口,而不是使用正则表达式。

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
}

-1
var isExternalURL = url.toLowerCase().indexOf('http://') === 0 || url.toLowerCase().indexOf('https://') === 0 ;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.