真实世界URL的URL验证正则表达式

URL validation regex for real-world URLs

本文关键字:URL 正则表达式 验证 真实世界      更新时间:2023-09-26

我想验证给定的字符串是否为URL。匹配文本中的URL也很好,但不是必需的。我已经搜索并进行了实验,但到目前为止,我还没有找到满足这些要求的东西:

  1. 当被视为链接时,不得接受会带来安全风险的字符串。例如,<a href="javascript:alert(document.cookie)">clickme</a>是一个有效的HTML元素,并且至少在某些浏览器中确实有效(引发警报等)。我担心,如果我允许任意方案(见下文),它可能会危及安全性(例如,如前所述:检查字符串是否为有效URL的最佳正则表达式是什么?)。

  2. 必须在JavaScript中正常工作。

  3. 如果它在Java中也能工作,那就太好了——我是在GWT中开发的,所以这很好,但不是绝对必要的。

  4. 必须接受在实践中使用的URL,而不仅仅是符合标准的URL具体示例:

    a。我想接受http://fr.wikipedia.org/wiki/Français,由于非英文字符,这是非标准的,但我的参考浏览器IE(7+)和Chrome都接受了它。

    b。我想接受http://fr.wikipedia.org/wiki/Fran%c3%a7ais,这是非标准的,因为编码十六进制的百分比应该是大写的,但IE和Chrome也接受。我想我可以做一个不区分大小写的匹配——你能想到什么缺点吗?

    c。我想接受http://localhost/localpath/servlet#action?param=value,这是非标准的,因为片段部分(从"#"到结尾)不应包括"?"和其他字符,但也有一些应用程序生成这样的URL,浏览器接受它们。

    d。我想接受任何方案/协议的URL(不仅仅是http、https和ftp),因为我集成的各种应用程序及其用户可能需要传递这样的URL。我可以禁止"javascript:",并允许其他一切;如果你认为这会危及安全,请这样说。

在SO和其他地方有很多关于这个主题的问题,但我没有找到一个正则表达式来回答我的所有需求。示例:

  • 在GWT中使用Regex来匹配URL——非常好且简单的Regex,但不接受非标准URL。我可以处理方案部分和编码区分大小写的百分比,但不能处理其他问题。

  • https://stackoverflow.com/a/190405/96929——Giant regex(我问自己,我使用的所有浏览器和框架是否都能处理这个大小),它看起来非常全面,但说它符合标准,我无法理解它

谢谢!:-)

必须接受在实践中使用的URL,而不仅仅是符合标准的URL

事实上,URI规范是非常自由的,并且允许通常出于兼容性原因想要排除的构造。。。

我想接受http://fr.wikipedia.org/wiki/Fran非标准

它不是URI,但一个相当标准的IRI。

  • 非标准,因为编码十六进制的百分比应为大写
  • 非标准,因为片段部分(从"#"到结尾)不应包括"?"

根据URI标准,这两者都是完全可以接受的。RFC 3986建议但不要求在创建百分比编码时使用大写。

我可以禁止"javascript:",并允许其他一切;如果你认为这会危及安全,请这样说。

会的。不幸的是,URI方案名称空间中已经添加了多个潜在的危险内容,而且毫无疑问,将来还会继续添加。此外,还有一些潜在的方法可以避免将使用的编码字符和控制字符列入黑名单。

此外,任意方案匹配意味着检测文本中地址的次要目标在使用冒号的大多数情况下都会产生假阳性。

白名单是唯一可行的方法,所以你只需要根据具体情况手动允许每个新方案。这需要一些小心;例如,data:方案看似无害且有用,但可能会遇到与javascript:相同的XSS问题。

您还需要了解有关每个方案的一些信息。像httpftp这样的方案有一个"基于服务器的命名机构":它们可以在主机中包括一个单独的主机名和资源路径;另外,您可能要求它们是绝对URI。如果你想允许文件URI,你必须检查它是否是无主机的(file:///)。对于其他方案,URI标准本身可能不需要具体的语法,但可能有其他限制,例如mailto:必须采用有效的电子邮件地址。

巨大的正则表达式(我问自己,我使用的所有浏览器和框架是否都能处理这个大小),它似乎是非常全面的

这在JavaScript中不起作用,因为它具有不受支持的'x{code point}语法。此外,像JavaScript这样的语言,其正则表达式引擎以UTF-16代码单元而不是完整的Unicode代码点工作,将无法处理BMP之外的字符范围。

您必须将长'x{A0}...'x{1FFFD}组替换为类似'u00A0-'uFFFD的更简单的组,然后单独检查无效的代理项对,以及0xnnFFFE–F非字符,如果您关心这些(可能不关心)的话。

可以说,在你进行IRI验证之前,你可能已经在一般输入扫描级别上删除了任何糟糕的代理和非字符;没有理由允许它们出现在任何文本输入中。在单独的步骤中这样做比试图将所有内容硬塞进一个正则表达式更有意义。

替换后,带引号的正则表达式中最长的部分是试图验证数字IP地址的超长数字检查字符串。这是regex根本不擅长的事情。我强烈考虑不要为IPv6和IPv未来的数字地址而烦恼:即使假设IPv6很快被广泛采用,在可预见的未来也不会有人使用它们。(你甚至想允许链接到数字地址吗?这取决于你的应用程序在做什么,但通常不是。)

您还可以考虑禁止userinfo@hostname前缀(因为它们传统上除了欺骗攻击之外没有任何用处)和百分比编码主机名(因为Punycode的存在,它们没有任何用途,而且在某些浏览器中不起作用)。

因此,IRI验证没有一个单一的答案,但这里是你可以开始的地方:

(
    https?://
    (
        ([0-9]{1-3}('.[0-9]{1-3}){3})|
        ([-0-9a-z'u00A0-'uFFFD]{1-63}('.[-0-9a-z'u00A0-'uFFFD]{1-63})*)
    )
    (:[0-9]+)?/
    (
        %[0-9a-f][0-9a-f]|
        [-._!$&'()*+,:;=@~0-9a-z'u00A0-'uFFFD/?#]
    )*
)|(
    ftp://                                    // same again but with no ?query
    ...                                       // or port number
)|(
    mailto:                                   // specify requirements for
    ...                                       // other accepted schemes
)

(假设不区分大小写。这应用了不属于URI规范本身的DNS约束,尽管不完全,因为它没有检查DNS标签中的前导/尾随-,或IPv4八位字节中的数字范围。验证电子邮件地址只是读者的一项练习,因为如果你想严格执行,这本身就是一项艰巨的任务,不适合regex。)

由于您在服务器端使用Java,我建议您使用URI。它会接受你想要的所有"奇怪"的东西,只需要.getScheme()来检查它是否真的是HTTP或HTTPS。

URL不同,URI不会尝试进行名称解析!