检查字符串是否为有效的URL的RFC兼容且可工作的正则表达式是什么?

17

已经有一个几乎相同名称的问题: 如何使用最佳正则表达式检查字符串是否为有效URL?

我不理解这个stackoverflow。看起来我需要声望才能评论回答。由于我没有它,我不知道如何告诉/问所提出的解决方案似乎不起作用。所以我被迫提出一个新问题,并以这种方式请求解决方案?

更新:看来该Reg Exp支持IPV6,而我要受到IPv6的责备 应该像http://[2620:0:1cfe:face:b00c::3]/那样。

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

或者是PHP的问题?

/**
  * Validate URL - RFC 3987 (IRI)
  *
  * https://dev59.com/NHVC5IYBdhLWcg3w21Iq
  *
  * @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\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&\'\(\)\*\+,;=:])*@)?(?:\[(?:(?:(?:[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\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&\'\(\)\*\+,;=@])*)(?::[0-9]*)?(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&\'\(\)\*\+,;=:@]))*)*|\/(?:(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&\'\(\)\*\+,;=:@]))+)(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&\'\(\)\*\+,;=:@]))*)*)?|(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&\'\(\)\*\+,;=:@]))+)(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&\'\(\)\*\+,;=:@]))*)*|(?!(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&\'\(\)\*\+,;=:@])))(?:\?(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&\'\(\)\*\+,;=:@])|[\x{E000}-\x{F8FF}\x{F0000}-\x{FFFFD}|\x{100000}-\x{10FFFD}\/\?])*)?(?:\#(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&\'\(\)\*\+,;=:@])|[\/\?])*)?$/iu',$str_url);
 }

这是IT技术的测试内容:

$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符合和工作的正则表达式是什么?


在我看来,对于一个有用的问题点赞是正确的行为,因为它与其他问题不同(显然只针对IPv4)。如果您在SO上发布更多内容,很快就会获得更多的声望,而低声望门槛也是保持这里质量高的一部分。;-) - Lucero
3
你是在寻找绝对URI,对吧?因为即使一个空字符串也是一个有效的URI引用。 - Gumbo
1
你的IPv6示例不正确,应该是http://[2620:0:1cfe:face:b00c::3]:80/,这样解析器才能区分十六进制分隔符和可选的:80端口号。 - mario
好的,我在这里评论一下。是的,我的IPv6语法错误了。那些都可以工作,所以问题实际上出在example.org:它是一个有效的网址。所有这些网址都会破坏编辑功能,在这里我会得到一些Oauth的东西,如果我尝试编辑我的帖子,所以我不会编辑它。哦,那个OAuth的东西已经在那里了... - jmto
好的,看起来根据RFC规范,方案允许在其中使用点。所以基本上,“example.org:”是符合RFC标准的。 - jmto
4个回答

4
好的,如果你看一下,规范被分成了“块”。我建议构建正则表达式,使其更易于阅读、更易于维护和理解。因此,正则表达式的部分是(可选部分用斜体表示):
  1. 方案
  2. 用户名/密码
  3. 域名或IP地址
  4. 端口
  5. 路径
  6. 查询
  7. 锚点
所以,我们需要为每个部分构建一个正则表达式子部分。
  1. Scheme:

    $scheme = "[a-z][a-z0-9+.-]*";
    
  2. Username/Password:

    $username = "([^:@/](:[^:@/])?@)?";
    
  3. Domain or IP Address:

    Now, we need to build up the 3 possible hosts:

    1. Domain Name
    2. IPv4
    3. IPv6

    Domain Name:

    $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})])";
    

    Finally:

    $host = "($domain|$ipv4|$ipv6)";
    
  4. Port:

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

    $path = "([^?;\#]*)?";
    
  6. Query:

    $query = "(\?[^\#;]*)?";
    
  7. Anchor:

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

最终的正则表达式:

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

请注意,/是在正则表达式中而不是路径部分,因为路径可以为空。
另请注意,我没有测试过这个代码。它应该可以工作,但绝对需要确认每个部分都正确(关于在url中应该期望什么)。
还要注意,这只是一种方法。您可以使用其他不需要正则表达式或库的工具,或使用长期维护更容易的框架。
祝你好运!

1
快速查看似乎会在许多IPv6地址上失败。IPv6验证不可能如此简单。那么http://[::1]/或http://[2620:0:1cfe:face:b00c::3]/怎么样?因为通常IPv6地址写得更短。 - jmto

3
阅读了RFC 3986之后,我必须承认我错了。那个正则表达式完全有效(依我所知)。 我犯的第一个错误是IPv6地址的语法,它们被放置在[]中,第二个错误与example.org有关:(请注意结尾的双点 :)。但是,正如RFC所说,方案中可以有点,因此这也是有效的。 所以,这是一种有效的RFC方法,但人们通常(就像我一样)需要将其修改为仅接受某些模式。

0

嗯,我不是在寻找PHP中验证URL的方法,而是要将URL替换为href链接。哦,但IPv6应该在[]中,这会有所帮助。那么,那个RegExp可以处理它们,但在“example.org:”语法上失败了。 - jmto
我同意,example.org不是有效的,example.org后面也不能有双点。但问题中的正则表达式表示它是有效的: example.org bool(false) example.org: bool(true) - jmto
我看了一下PHP的做法。 http://svn.php.net/viewvc/php/php-src/trunk/ext/filter/logical_filters.c?view=markup - /* 使用parse_url - 如果它返回false,我们就会返回NULL */http://pl.php.net/manual/en/function.parse-url.php - “此函数并不意味着验证给定的URL,它只是将其分解为上面列出的部分。部分URL也被接受,parse_url()尽最大努力正确地解析它们。”PHP真有趣,它有一个过滤器用于VALIDATE_URL,但它却使用自己的函数,该函数并不意味着验证url! :D - jmto

0

谢谢ircmaxell,但是我必须稍微调整一下PHP的IPV6正则表达式才能通过preg_match编译。

我做了如下修改:

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

至:

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

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