真实网址的URL验证正则表达式

时间:2012-01-15 11:25:55

标签: javascript regex url

我想验证给定字符串是否为URL。在文本中匹配URL也不错,但不是必需的。我进行了搜索和实验,但到目前为止,我找不到能满足这些要求的东西:

  1. 不得接受字符串,这些字符串在被视为链接时会带来安全风险。例如,<a href="javascript:alert(document.cookie)">clickme</a>是一个有效的HTML元素,并且至少在某些浏览器中确实有效(引发警报等)。我担心如果我允许任意方案(见下文),它可能会危及安全性(如上所述,例如:What is the best regular expression to check if a string is a valid URL?)。

  2. 必须在JavaScript中正常使用。

  3. 如果它在Java中的工作原理相同会很好 - 我正在开发GWT,所以这很好但不是绝对必要的。

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

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

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

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

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

  5. 在SO和其他地方有很多关于这个主题的问题,但是我没有找到满足我所有要求的正则表达式。例子:

    • Regex in GWT to match URLs - 相当不错且简单的正则表达式,但不接受非标准网址。我可以处理方案部分和百分比编码区分大小写,但不能处理其他问题。

    • https://stackoverflow.com/a/190405/96929 - 巨型正则表达式(我问自己,我使用的所有浏览器和框架是否可以处理这个大小),这看起来非常全面,但是说它符合标准,我无法制作它的头或尾。

    谢谢! : - )

2 个答案:

答案 0 :(得分:5)

  

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

实际上,URI规范非常宽松,并且允许通常由于兼容性原因而要排除的构造...

  

我想接受http://fr.wikipedia.org/wiki /Français,这是非标准的

它不是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地址的数字检查的疯狂长串。这是正则表达式根本不擅长的事情。我强烈考虑不要担心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八位字节中的数字范围验证电子邮件地址是留给读者的练习,因为如果你想要严格执行它,它本身就是一个不适合正则表达式的艰巨任务。)

答案 1 :(得分:1)

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

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