检查字符串是不是为有效 URL 的 RFC 兼容和工作正则表达式是啥

Posted

技术标签:

【中文标题】检查字符串是不是为有效 URL 的 RFC 兼容和工作正则表达式是啥【英文标题】:What is the RFC compliant and working regular expression to check if a string is a valid URL检查字符串是否为有效 URL 的 RFC 兼容和工作正则表达式是什么 【发布时间】:2011-06-10 10:30:09 【问题描述】:

已经有一个几乎同名的问题: What is the best regular expression to check if a string is a valid URL

我不明白这个堆栈溢出。似乎我需要声誉来评论答案。因为我没有它,所以我不知道如何告诉/询问建议的解决方案似乎不起作用。所以我不得不提出一个新问题并以这种方式寻求解决方案?

更新:所以 Reg Exp 似乎支持 IPV6,而我应该受到责备,因为 IPv6 是 应该像http://[2620:0:1cfe:face:b00c::3]/。

所以我现在唯一知道的真正问题是,它接受 example.org: 作为有效 URL。

还是 php 应该受到责备?

/**
  * Validate URL - RFC 3987 (IRI)
  *
  * https://***.com/questions/161738/what-is-the-best-regular-expression-to-check-if-a-string-is-a-valid-url
  *
  * @param string $str_url
  * @return boolean
  */
 function is_url($str_url)
 
  // RFC 3987 For absolute IRIs (internationalized):
  return (bool) preg_match('/^[a-z](?:[-a-z0-9\+\.])*:(?:\/\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\xD7FF\xF900-\xFDCF\xFDF0-\xFFEF\x10000-\x1FFFD\x20000-\x2FFFD\x30000-\x3FFFD\x40000-\x4FFFD\x50000-\x5FFFD\x60000-\x6FFFD\x70000-\x7FFFD\x80000-\x8FFFD\x90000-\x9FFFD\xA0000-\xAFFFD\xB0000-\xBFFFD\xC0000-\xCFFFD\xD0000-\xDFFFD\xE1000-\xEFFFD!\$&\'\(\)\*\+,;=:])*@)?(?:\[(?:(?:(?:[0-9a-f]1,4:)6(?:[0-9a-f]1,4:[0-9a-f]1,4|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))3)|::(?:[0-9a-f]1,4:)5(?:[0-9a-f]1,4:[0-9a-f]1,4|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))3)|(?:[0-9a-f]1,4)?::(?:[0-9a-f]1,4:)4(?:[0-9a-f]1,4:[0-9a-f]1,4|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))3)|(?:[0-9a-f]1,4:[0-9a-f]1,4)?::(?:[0-9a-f]1,4:)3(?:[0-9a-f]1,4:[0-9a-f]1,4|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))3)|(?:(?:[0-9a-f]1,4:)0,2[0-9a-f]1,4)?::(?:[0-9a-f]1,4:)2(?:[0-9a-f]1,4:[0-9a-f]1,4|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))3)|(?:(?:[0-9a-f]1,4:)0,3[0-9a-f]1,4)?::[0-9a-f]1,4:(?:[0-9a-f]1,4:[0-9a-f]1,4|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))3)|(?:(?:[0-9a-f]1,4:)0,4[0-9a-f]1,4)?::(?:[0-9a-f]1,4:[0-9a-f]1,4|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))3)|(?:(?:[0-9a-f]1,4:)0,5[0-9a-f]1,4)?::[0-9a-f]1,4|(?:(?:[0-9a-f]1,4:)0,6[0-9a-f]1,4)?::)|v[0-9a-f]+[-a-z0-9\._~!\$&\'\(\)\*\+,;=:]+)\]|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))3|(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\xD7FF\xF900-\xFDCF\xFDF0-\xFFEF\x10000-\x1FFFD\x20000-\x2FFFD\x30000-\x3FFFD\x40000-\x4FFFD\x50000-\x5FFFD\x60000-\x6FFFD\x70000-\x7FFFD\x80000-\x8FFFD\x90000-\x9FFFD\xA0000-\xAFFFD\xB0000-\xBFFFD\xC0000-\xCFFFD\xD0000-\xDFFFD\xE1000-\xEFFFD!\$&\'\(\)\*\+,;=@])*)(?::[0-9]*)?(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\xD7FF\xF900-\xFDCF\xFDF0-\xFFEF\x10000-\x1FFFD\x20000-\x2FFFD\x30000-\x3FFFD\x40000-\x4FFFD\x50000-\x5FFFD\x60000-\x6FFFD\x70000-\x7FFFD\x80000-\x8FFFD\x90000-\x9FFFD\xA0000-\xAFFFD\xB0000-\xBFFFD\xC0000-\xCFFFD\xD0000-\xDFFFD\xE1000-\xEFFFD!\$&\'\(\)\*\+,;=:@]))*)*|\/(?:(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\xD7FF\xF900-\xFDCF\xFDF0-\xFFEF\x10000-\x1FFFD\x20000-\x2FFFD\x30000-\x3FFFD\x40000-\x4FFFD\x50000-\x5FFFD\x60000-\x6FFFD\x70000-\x7FFFD\x80000-\x8FFFD\x90000-\x9FFFD\xA0000-\xAFFFD\xB0000-\xBFFFD\xC0000-\xCFFFD\xD0000-\xDFFFD\xE1000-\xEFFFD!\$&\'\(\)\*\+,;=:@]))+)(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\xD7FF\xF900-\xFDCF\xFDF0-\xFFEF\x10000-\x1FFFD\x20000-\x2FFFD\x30000-\x3FFFD\x40000-\x4FFFD\x50000-\x5FFFD\x60000-\x6FFFD\x70000-\x7FFFD\x80000-\x8FFFD\x90000-\x9FFFD\xA0000-\xAFFFD\xB0000-\xBFFFD\xC0000-\xCFFFD\xD0000-\xDFFFD\xE1000-\xEFFFD!\$&\'\(\)\*\+,;=:@]))*)*)?|(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\xD7FF\xF900-\xFDCF\xFDF0-\xFFEF\x10000-\x1FFFD\x20000-\x2FFFD\x30000-\x3FFFD\x40000-\x4FFFD\x50000-\x5FFFD\x60000-\x6FFFD\x70000-\x7FFFD\x80000-\x8FFFD\x90000-\x9FFFD\xA0000-\xAFFFD\xB0000-\xBFFFD\xC0000-\xCFFFD\xD0000-\xDFFFD\xE1000-\xEFFFD!\$&\'\(\)\*\+,;=:@]))+)(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\xD7FF\xF900-\xFDCF\xFDF0-\xFFEF\x10000-\x1FFFD\x20000-\x2FFFD\x30000-\x3FFFD\x40000-\x4FFFD\x50000-\x5FFFD\x60000-\x6FFFD\x70000-\x7FFFD\x80000-\x8FFFD\x90000-\x9FFFD\xA0000-\xAFFFD\xB0000-\xBFFFD\xC0000-\xCFFFD\xD0000-\xDFFFD\xE1000-\xEFFFD!\$&\'\(\)\*\+,;=:@]))*)*|(?!(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\xD7FF\xF900-\xFDCF\xFDF0-\xFFEF\x10000-\x1FFFD\x20000-\x2FFFD\x30000-\x3FFFD\x40000-\x4FFFD\x50000-\x5FFFD\x60000-\x6FFFD\x70000-\x7FFFD\x80000-\x8FFFD\x90000-\x9FFFD\xA0000-\xAFFFD\xB0000-\xBFFFD\xC0000-\xCFFFD\xD0000-\xDFFFD\xE1000-\xEFFFD!\$&\'\(\)\*\+,;=:@])))(?:\?(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\xD7FF\xF900-\xFDCF\xFDF0-\xFFEF\x10000-\x1FFFD\x20000-\x2FFFD\x30000-\x3FFFD\x40000-\x4FFFD\x50000-\x5FFFD\x60000-\x6FFFD\x70000-\x7FFFD\x80000-\x8FFFD\x90000-\x9FFFD\xA0000-\xAFFFD\xB0000-\xBFFFD\xC0000-\xCFFFD\xD0000-\xDFFFD\xE1000-\xEFFFD!\$&\'\(\)\*\+,;=:@])|[\xE000-\xF8FF\xF0000-\xFFFFD|\x100000-\x10FFFD\/\?])*)?(?:\#(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\xD7FF\xF900-\xFDCF\xFDF0-\xFFEF\x10000-\x1FFFD\x20000-\x2FFFD\x30000-\x3FFFD\x40000-\x4FFFD\x50000-\x5FFFD\x60000-\x6FFFD\x70000-\x7FFFD\x80000-\x8FFFD\x90000-\x9FFFD\xA0000-\xAFFFD\xB0000-\xBFFFD\xC0000-\xCFFFD\xD0000-\xDFFFD\xE1000-\xEFFFD!\$&\'\(\)\*\+,;=:@])|[\/\?])*)?$/iu',$str_url);
 

这是对它的测试:

$urls=array('http://www.example.org/','http://www.example.org:80/','example.org','ftp://user:pass@example.org/','http://example.org/?cat=5&test=joo','http://www.fi/?cat=5&test=joo','http://[::1]/','http://[2620:0:1cfe:face:b00c::3]/','http://[2620:0:1cfe:face:b00c::3]:80/','');
foreach ($urls as $a)

    echo $a."\n";
    $a=is_url($a);
    var_dump($a);

然后输出:

"http://www.example.org/" bool(true)
"http://www.example.org:80/" bool(true)
"example.org" bool(false)
"ftp://user:pass@example.org/" bool(true)
"http://example.org/?cat=5&test=joo" bool(true)
"http://www.fi/?cat=5&test=joo" bool(true) 
"http://[::1]/" bool(true)
"http://[2620:0:1cfe:face:b00c::3]/" bool(true)
"http://[2620:0:1cfe:face:b00c::3]:80/" bool(true)
"" bool(false)

那么什么是 RFC 编译器和工作正则表达式?

【问题讨论】:

+1 是一个有用的问题,在我看来,你提出一个新问题是正确的,因为它在另一个问题中确实不同(显然仅适用于 IPv4)。如果您在 SO 上发布更多内容,您很快就会获得更多的代表,而低代表的障碍是保持高质量的一部分。 ;-) 您正在寻找绝对 URI,不是吗?因为即使是空字符串也是有效的 URI 引用。 您的 IPv6 示例不正确,它应该是 http://[2620:0:1cfe:face:b00c::3]:80/,因此解析器可以区分十六进制分隔符和可选的 :80 端口号。 好的,我会在这里评论。是的,我在 IPv6 语法上的错误。那些作品,所以真正的问题在于example.org:成为有效的。所有这些 url 都破坏了这里的编辑,如果我尝试编辑我的帖子,我会在那里得到一些 Oauth 的东西,所以我不打算编辑它。哦,OAuth 的东西已经在那里了…… 好吧,好吧,看起来 RFC 似乎允许方案中包含点。所以基本上“example.org:”根据 RFC 是有效的。 【参考方案1】:

好吧,如果你看一下,规范被分解成“块”。这就是我建议构建正则表达式的方式,使其更易于阅读、更易于维护和易于理解。所以,正则表达式的部分是(可选的斜体):

    方案 用户名/密码 域或 IP 地址 端口 路径 查询

因此,我们需要为每个构建一个正则表达式子部分。

    方案:

    $scheme = "[a-z][a-z0-9+.-]*";
    

    用户名/密码:

    $username = "([^:@/](:[^:@/])?@)?";
    

    域或 IP 地址:

    现在,我们需要建立 3 个可能的主机:

      域名 IPv4 IPv6

    域名:

    $segment = "([a-z][a-z0-9-]*?[a-z0-9])";
    $domain = "($segment\.)*$segment";
    

    IPv4:

    $segment = "([0|1][0-9]2|2([0-4][0-9]|5[0-5]))";
    $ipv4 = "($segment\.$segment\.$segment\.$segment)";
    

    IPv6:

    $block = "([a-f0-9]0,4)";
    $rawIpv6 = "($block:)2,8";
    $ipv4sub = "(::ffff:$ipv4)";
    $ipv6 = "([($rawIpv6|$ipv4sub)])";
    

    最后:

    $host = "($domain|$ipv4|$ipv6)";
    

    端口:

    $port = "(:[\d]1,5)?";
    

    路径:

    $path = "([^?;\#]*)?";
    

    查询:

    $query = "(\?[^\#;]*)?";
    

    锚点:

    $anchor = "(\#.*)?";
    

最后的正则表达式:

$regex = "#^$scheme://$username$host$port(/$path$query$anchor|)$#i";

请注意,/ 在正则表达式中,而不是路径部分,因为路径可以为空。

另外请注意,我没有对此进行测试。它应该可以工作,但绝对需要确认每个部分都是正确的(至于 url 中的预期内容)。

还请注意,这只是一种方法。您可以使用不需要正则表达式的其他工具或从长远来看更易于维护的库或框架。

祝你好运

【讨论】:

快速查看似乎在许多 IPv6 地址上会失败。 IPv6 验证不可能这么简单。 http://[::1]/ 或 http://[2620:0:1cfe:face:b00c::3]/ 怎么样?因为 IPv6 地址写得更短是很正常的。【参考方案2】:

阅读 RFC 3986 后,我不得不说我错了。该正则表达式完全有效(我知道)。 我遇到的第一个错误是 IPv6 地址的语法,它们放在 [] 周围,第二个错误是关于 example.org: (注意尾随双点:)。但正如 RFC 所说的方案中可以有点,所以它也是有效的。

所以这是有效的 RFC 方式,但人们通常(如我所愿)需要修改它以仅接受某些模式。

【讨论】:

【参考方案3】:

这是您可以研究的 RFC:RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax。 3.2.2 Host 部分是您要查找的内容。

很遗憾,PHP 的内置函数 filter_var() 不支持 IPv6 语法:

<?php

var_dump(filter_var('http://[2620:0:1cfe:face:b00c::3]:80/', FILTER_VALIDATE_URL));
// Output: boolean false

【讨论】:

好吧,我不是在寻找一种在 PHP 中验证 url 的方法,而是将 url 替换为 href 链接。哦,但是 IPv6 应该在 [] 中,这会有所帮助。那么 RegExp 与那些一起工作,但在“example.org:”语法上失败了。 我同意,example.org 无效,example.org 也无效:(注意尾随双点)。但是问题中的那个正则表达式说它是有效的:example.org bool(false) example.org: bool(true) 我查看了 PHP 是如何做到这一点的。 svn.php.net/viewvc/php/php-src/trunk/ext/filter/… - /* 使用 parse_url - 如果它返回 false,我们返回 NULL */ pl.php.net/manual/en/function.parse-url.php - “这个函数不是为了验证给定的 URL,它只是把它分解成上面列出的部分。部分 URL 也是接受,parse_url() 会尽力正确解析它们。”有趣的 PHP,它有一个 VALIDATE_URL 过滤器,但它使用它自己的函数来验证 url! :D【参考方案4】:

感谢 ircmaxell,但我不得不稍微调整一下 IPV6 正则表达式,以便 PHP 使用 preg_match 进行编译。

我变了:

$ipv6 = "([($rawIpv6|$ipv4sub)])";

收件人:

$ipv6 = "($rawIpv6|$ipv4sub)";

【讨论】:

以上是关于检查字符串是不是为有效 URL 的 RFC 兼容和工作正则表达式是啥的主要内容,如果未能解决你的问题,请参考以下文章

检查字符串是不是为有效 URL 的最佳正则表达式是啥?

路径部分中带有 // 的 URL 是不是有效?

使用 Google API 发送邮件时出错 - “'原始' RFC822 有效负载消息字符串或通过 /upload/* URL 上传消息”

如何在 Swift 中检查 URL 的有效性?

如何检查在 iOS 的 url 中发布的用户名和密码是不是有效

PHP 检查有效的rfc822电子邮件