如何验证URL是否正确,和/或从任意文本中提取有效的URL?

时间:2013-05-31 19:22:57

标签: ios objective-c macos

有时候我有一个文本输入表单,我想要禁用“接受”按钮,直到用户输入了有效的URL。在这里或网上搜索会产生大量的正则表达式,但考虑到URL规范(RFC-3986)的复杂性,它几乎不可能为它们编写自己的验证测试套件。一旦我的应用程序在App商店中,我怎么知道由于正则表达式中的缺陷我得到了多少假阴性?

其他时候我需要从网站或其他一些文本中提取所有有效的URL,并希望得到它们的数组,以便我可以将其过滤掉以仅指出那些指向图像文件的URL 。在这种情况下,错误的正则表达式不太可能成为问题,因为如果我错过了一两个图像,或者获得虚假URL,则不是主要问题。无论如何,正则表达式越好,返回的图像列表就越正确。

那么,我如何通过虚拟确定性将呈现的字符串验证为正确的URL?最好还是有办法从任意文本中提取有效的URL。

2 个答案:

答案 0 :(得分:5)

网上有大量正则表达式声称可以验证网址。大多数问题在于它们可能有效,但它们没有凭据 - 也就是说,没有任何方法可以用这种方式证明它们的正确性。

网址上的参考规范是RFC-3986,在长时间搜索最佳正则表达式时,我绊倒了Jeff Roberson的regular expression page。他所做的是从规范开始,构建小的正则表达式以匹配RFC的低级部分,并逐渐将它们构建成完整的表达式。

例如,这就是获得完整scheme的方式:

# From http://jmrware.com/articles/2009/uri_regexp/URI_regex.html Copyright @ Jeff Roberson
(⌽[A-Za-z][A-Za-z0-9+\-.]*)
# DFH Addition: change ⌽ from "?:" to "" to get capture groups of the various components

第一个“(”之后的unicode字符变为“?:”,表示非捕获组,或“”将其变为捕获组。请注意,这与单个字符匹配一个或多个第二个“[]”组中包含的字符

使用以下表达式找到完整的authority

# RFC-3986 URI component:  relative-part
(?: //                                                          # ( "//"
  (?: (⌽(?:[A-Za-z0-9\-._~!$&'()*+,;=:]|%[0-9A-Fa-f]{2}☯)* ) @)?     # authority DFH modified to grab the authority without '@'
  (⌽
    \[
    (?:
      (?:
        (?:                                                    (?:[0-9A-Fa-f]{1,4}:){6}
        |                                                   :: (?:[0-9A-Fa-f]{1,4}:){5}
        | (?:                            [0-9A-Fa-f]{1,4})? :: (?:[0-9A-Fa-f]{1,4}:){4}
        | (?: (?:[0-9A-Fa-f]{1,4}:){0,1} [0-9A-Fa-f]{1,4})? :: (?:[0-9A-Fa-f]{1,4}:){3}
        | (?: (?:[0-9A-Fa-f]{1,4}:){0,2} [0-9A-Fa-f]{1,4})? :: (?:[0-9A-Fa-f]{1,4}:){2}
        | (?: (?:[0-9A-Fa-f]{1,4}:){0,3} [0-9A-Fa-f]{1,4})? ::    [0-9A-Fa-f]{1,4}:
        | (?: (?:[0-9A-Fa-f]{1,4}:){0,4} [0-9A-Fa-f]{1,4})? ::
        ) (?:
            [0-9A-Fa-f]{1,4} : [0-9A-Fa-f]{1,4}
          | (?: (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) \.){3}
                (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
          )
      |   (?: (?:[0-9A-Fa-f]{1,4}:){0,5} [0-9A-Fa-f]{1,4})? ::    [0-9A-Fa-f]{1,4}
      |   (?: (?:[0-9A-Fa-f]{1,4}:){0,6} [0-9A-Fa-f]{1,4})? ::
      )
    | [Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&'()*+,;=:]+
    )
    \]
  | (?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
       (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
  | 
  (?:[A-Za-z0-9\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2}☯)*
  )

  (?: : (⌽[0-9]*) )? # DFH addition to grab just the port

 (⌽   # DFH addition to get one capture group
  (⌽ / (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2}☯)* )*    # path-abempty
| /                                                             # / path-absolute
  (⌽:    (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2}☯)+
    (?:/ (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2}☯)* )*
  )?
| (⌽        (?:[A-Za-z0-9\-._~!$&'()*+,;=@] |%[0-9A-Fa-f]{2}☯)+     # / path-noscheme
    (?:/ (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2}☯)* )*
   ) # DFH Wrapper
|                                                            # / path-empty
      (⌽) # DFH addition so constant number of capture groups
 )
)                                                               # )

# DFH Addition: change ☯ to "|[\u0080-\U0010ffff]" to get inline Unicode detection (making this an IRI, not a URI, but you can later hex encode it), or "" for standard behavior
# DFH Addition: change ⌽ from "?:" to "" to get capture groups of the various components

如果您阅读上述内容,您可以看到此表达式可以扩展为通过在几个地方添加“| [\ u0080- \ U0010ffff]”来查找Unicode字符。

因为他实际上是从RFC开始的,并且他的表达的所有部分都完全引用了ABNF规范,所以我对它们充满信心。

然而,当我开始测试时,我发现说http://的URL验证程序已经过了!事实证明,规范几乎允许所有内容都是空字符串!对于UI表单验证器来说,很难使用它。

所以我接受了他的表达,并做了一些小的补充。首先,我发现我可以将路径说明符从'*'更改为'?',这样在表单条目中,用户将被迫在'http://'之后键入至少一个'/'。这使验证器比它需要的更严格,但更现实。

Jeff的正则表达式只使用非捕获组,因此我研究了支持捕获组的方法,因此如果需要,可以提取URL的所有组件。

另外,想想非美国用户,他们经常需要在URL中输入非ASCII字符 - 他们想要输入重音字符 - 但普通验证器会拒绝Unicode字符。验证包含unicode字符的字符串会很好,然后在实际使用之前将unicode转换为'%'编码的十六进制。这需要通过将|[\\u0080-\\U0010ffff]添加到接受ASCII的部分来扩展表达式以接受unicode字符。

整个问题要求将一个测试工具组合在一起,该测试工具可以使用给定应用程序可能需要的选项构造一个或多个正则表达式,并且可以针对各种测试字符串测试这些选项。因此被承担URLFinderAndVerifier

测试工具使用从Jeff页面获取的扩展表达式字符串,其所有空格和注释完整无缺,并附有我做出的其他注释。这些使表达更容易阅读和理解。测试应用程序读取文本文件并删除所有注释和空格,根据UI中选择的选项对它们进行预处理,然后设置这些选项以供使用或粘贴(这样您就可以在应用程序中使用它们)。测试应用程序还允许您以交互模式使用它,在修改输入文本时它将验证。

选项:

  • 查找http / https,http / https / ftp或任何方案

  • 对于表单输入,在“scheme://”之后需要一个“/”,它使“Accept”按钮的切换更加真实(在查询的“?”之后还需要至少一个字符和frament的“ #“)

  • 启用捕获组,因此为每个URL提取方案,userinfo,主机,端口,路径以及可选的查询和/或片段)

  • 在提取模式下,包含或排除查询和/或片段

用法:

  • 克隆项目,确定你想要的正则表达式,然后将其粘贴到结果窗口并在你的应用程序中使用它(适用于代码中的文本文件或NSString)

  • 将URLFinder界面和实施文件复制到项目中

  • 实例化一个URLFinder并从第一步开始为它提供正则表达式。

答案 1 :(得分:1)

验证网址的最简单方法当然是构建NSURL对象。

NSURL *url = [NSURL URLWithString:urlString];

根据documentation

  

必须是符合RFC 2396的网址。

     

如果字符串格式错误,则返回nil。

最终,您可能希望将网址转换为NSURL对象,因此最好能够确定您的字符串是否有效。

然后,要在一个文本块中找到网址,您可以执行非常简单的正则表达式搜索,只需查找潜在的候选项。例如,像这样:

[^\s]+://[^\s]+

然后使用上述NSURL构造技术来验证这些候选人是否是真正的匹配。