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

113

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

if (urlString starts with http:// or https://)
 //do this
18个回答

218

FAST

如果您只需要测试 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('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
解释正则表达式
^(?:[a-z+]+:)?//

^ - 字符串的起始位置
(?: - 一个非捕获组的开始
[a-z+]+ - 匹配任意 'a' 到 'z' 或者 "+" 字符,出现1次或多次
: - 字符串(冒号)
)? - 非捕获组的结束。该组出现0或1次。
// - 字符串(两个正斜杠字符)
'i' - 不区分大小写的标志


为什么是a-z?域名不可以包含0-9和连字符吗? - Atul Gupta
4
没错,但我们这里并没有检查域名,对吧?这个仍然可以工作:/^(?:[a-z]+:)?\/\//i.test('https://www.ex-maple-123.com'); - Geo
1
这并不包括 "mailto:" URL。我不知道 mailto URL 是绝对路径还是相对路径;-) - Peter
3
new RegExp('^(//|[a-z]+:)', 'i') 可以用来匹配 mailto:, about:, tel: 等内容,包括现有的测试案例。这个正则表达式的想法是在扩展检测绝对 URL 的功能时,仍然提供协议相对绝对 URL 的支持,而不需要检查双斜杠(//)。因此,r.test('mailto:hi@example.com') === truer.test('https:example.com') === true,等等。 - Matt Borja
1
@编程教授,确实如此,请看:(new RegExp('^(?:[a-z+]+:)?//', 'i')).test('file:///example.com/item'); // true - 正则表达式中的第三个斜杠是超出了范围的。 - undefined
显示剩余13条评论

41

根据您的需求,我认为更可靠的确定方法是使用内置的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
3
我已经对每个基于正则表达式的答案进行了踩票,并给使用内置类(如URL)的每个答案进行了点赞。这是正确的答案。谢谢! - Ahmed Fasih
我认为这并不起作用 - new URL(urlToTest, document.baseURI).origin将始终返回相同的结果,无论URL是绝对还是相对。 - David Chouinard
1
@DavidChouinard 它确实有效。请使用完整的代码行。从 urlToTest 构建 URL 对象允许浏览器确定该 URL 的 origin 应该是什么。无论它是否是与同一源匹配的完整 URL...只要它匹配该源,您就可以确定它是否相对于当前源。 - Brad

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

对于协议相关的URL,请使用以下正则表达式:

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


17
这回答了所提出的问题,但你可能还想考虑采用以“//”开头的协议相对URL - gerryster
5
如果URL含有"file://",那会怎么样?崩溃!@Philipp的答案更可靠。 - Skay
3
接受的答案在2019年已经失效。Chrome可以轻松接受"http:example.com"。 - Gene S

31

最初的回答

一个非常快速和非常灵活的检查方法是:

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

如果以下条件之一成立,它将识别绝对URL:

  • URL中包含“://”(不区分大小写);
  • URL以“//”开头(协议相对)。

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

更新1(完整函数示例)

这是一个快速的函数,用于返回给定URL的true/false:

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

And same in ES6:

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

更新2(URL参数中的URL)

为了额外处理格式为/redirect?target=http://example.org的URL,我建议使用以下代码:

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)

以下是一些测试用例:
// 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)

我看到了一些关于无效输出的评论:

  • 解决方案对localhost返回false
  • 答案在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

6
不。我只是追踪我的项目中的一个错误,并发现它与一个函数有关。网页的url类似于/redirect?target=http://example.org - BeniBela
@BeniBela,你可以通过使用function isUrlAbsolute(url) { var firstSlash = url.indexOf('/'); var colonDoubleSlash = url.indexOf('://'); return ((firstSlash > 0 && colonDoubleSlash > 0 && colonDoubleSlash < firstSlash) || url.indexOf('//') === 0);}来解决这个问题。 - Sebastian
@BeniBela 你说得对,这在某些情况下可能会发生。我已经更新了上面的代码来处理这个问题。然而,我强烈建议对所有查询参数进行URL编码,即使用/redirect?target=http%3A%2F%2Fexample.com - Philipp
@GeneS,你的URL是相对的。你可以测试一下:创建文件example.org/a/b/c/index.html。在索引文件中添加链接<a href="http:example.org/a>转到a</a>,然后打开它。该链接将指向http://example.org/a/b/c/example.org/a - 另外:应避免使用,但允许用于向后兼容(https://tools.ietf.org/html/rfc3986#section-5.4.2)。 - Philipp
3
isUrlAbsolute('redirect') 返回 false,这是正确的,但 isUrlAbsolute('redirect?target=http://example.org') 返回 true,这是不正确的。我认为检查 :// 是否在 ?# 之后会很有用......是否存在任何可能会产生冲突的情况? - Adrian Schmidt
显示剩余9条评论

20

使用正则表达式:

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

这似乎是最通用的答案。只缺少协议相对 URL(例如 //cdn.example.com/libary.js)。 - Geo
虽然问题只提到了http和https,但一个通用的解决方案可能还需要考虑"mailto:" URL,它没有正斜杠。 - mikebridge
@mikebridge 你是说mailto:有时可以是绝对的或相对的吗? - Geo
1
@Geo:不是的;他的意思是,即使mailto:没有/字符,它仍然是绝对的。 - SLaks
请加入此处的聊天室:http://chat.stackoverflow.com/rooms/44712/absolute-or-relative-url - Geo
这个答案在 http:example.com 上失败了,但至少在 Chrome 中,这个 URL 可以正常运行。 - Gene S

12

更通用的符合RFC标准的URI方法:

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

这里列出的其他解决方案不能处理像mailto:evan@nylas.com这样的链接。

RFC 3986Scheme定义为:

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

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

虽然协议相对url在第4.2节中技术上是有效的,但Paul Irish已经反过来认为这是一种反模式。请参见:http://www.paulirish.com/2010/the-protocol-relative-url/

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

如果您想在不使用协议相对url的情况下使用正则表达式,请使用:

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

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


3
这个正则表达式中的 ^ 应该放在括号外面吗?现在的写法会匹配不在开头的 //(例如相对URL中的 #//)。另外,很重要的一点是要指定这个正则表达式是大小写不敏感的,所以完整的定义应该是 /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i - sethobrien
我认为单字符方案应该被视为驱动器字母。因此,我会用“+”替换“*”。 - Knu
你的正则表达式匹配了 //cdn.example.com/lib.js,这是一个相对URI,而不是绝对URI:https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 "以两个斜杠字符开头的相对引用被称为网络路径引用;" - Iwan Aucamp

11
你可以使用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
}

2
8年过去了,这是最好的答案。 - JulianSoto

10

现在许多服务使用协议相对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”这样的URL,您应该使用'^([A-Za-z]+://|//)'。 - Dean Meehan
3
只需将'i'标志设置为忽略大小写即可。回答已编辑。谢谢。 - rgtk

9

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

看看URI.js,它应该可以胜任: http://medialize.github.io/URI.js/docs.html#is

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

6
如果需要执行大量操作,这很有用,但是似乎使用JS库来完成这个任务有点过火。 - Evan Donovan

7

以下是关于浏览器环境的相当强大的解决方案:

让浏览器处理所有事情。无需复杂/易错的正则表达式。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接