如何编写一个正则表达式模式来验证URI?

23
如何编写一个正则表达式,以匹配所有有效的URI字符串,并无法匹配所有无效的URI字符串?
具体而言,在我所说的URI方面,我在下面添加了一个链接,其中包含最新的URI RFC标准。它定义了我想要使用正则表达式验证的实体。
我不需要它能够解析URI。我只需要用一个正则表达式来验证。
首选.NET正则表达式格式(.NET V1.1)。

我的当前解决方案:

^([a-zA-Z0-9+.-]+):(//([a-zA-Z0-9-._~!$&'()*+,;=:]*)@)?([a-zA-Z0-9-._~!$&'()*+,;=]+)(:(\\d*))?(/?[a-zA-Z0-9-._~!$&'()*+,;=:/]+)?(\\?[a-zA-Z0-9-._~!$&'()*+,;=:/?@]+)?(#[a-zA-Z0-9-._~!$&'()*+,;=:/?@]+)?$(:(\\d*))?(/?[a-zA-Z0-9-._~!$&'()*+,;=:/]+)?(\?[a-zA-Z0-9-._~!$&'()*+,;=:/?@]+)?(\#[a-zA-Z0-9-._~!$&'()*+,;=:/?@]+)?$
7个回答

29

3
+1 这是唯一正确的答案。正则表达式不是正确的工具。我总是想知道使用正则表达式解析Uri时,标准合规和安全性如何保证。处理国际化(Unicode)域名?混淆真实路径的编码?容错?测试过吗?只需使用 .net 框架! - oɔɯǝɹ

20

URI规范表示:

以下是将格式良好的URI引用分解为其组件的正则表达式。

^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?

(我猜这与另一个答案中给出的STD66链接中的正则表达式相同。)

但是,分解并非验证。要正确验证URI,必须将BNF for URIs转换为正则表达式。虽然有些BNF无法表示为正则表达式,但我认为可以使用此BNF完成。但是不应该这样做-这将是一场巨大的混乱。最好使用库函数。


12

这个网站看起来很有前途:http://snipplr.com/view/6889/regular-expressions-for-uri-validationparsing/

他们提出了以下正则表达式:

/^([a-z0-9+.-]+):(?://(?:((?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*)@)?((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*)(?::(\d*))?(/(?:[a-z0-9-._~!$&'()*+,;=:@/]|%[0-9A-F]{2})*)?|(/?(?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9A-F]{2})+(?:[a-z0-9-._~!$&'()*+,;=:@/]|%[0-9A-F]{2})*)?)(?:\?((?:[a-z0-9-._~!$&'()*+,;=:/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&'()*+,;=:/?@]|%[0-9A-F]{2})*))?$/i

我在PHP中使用了这个技巧。对于PHP程序员,请参见此处:https://dev59.com/DHVC5IYBdhLWcg3wtzkQ - Xavi Montero
无法工作,例如“ldap://[2001:db8::7]/c=GB?objectClass?one” - ms_devel
1
@msavara 对的。最好使用bdukes的答案! - Daren Thomas
1
据我所知,Scheme名称不能以+-.开头,只能以字母开头。请参见RFC3986(第3.1节) - Gwyneth Llewelyn
那个还不错,但是它会忽略带有email:password的URL。例如:scheme://user@example.com:pass@exaple.com/。如果你需要捕获这些,请参考@Lostfields的回答。 - HuBeZa
1
@HuBeZa,我们可能不应该使用正则表达式来处理这个问题。这是很久以前的事了。我会使用bdukes提到的库函数。 - Daren Thomas

10
根据RFC 3986(https://www.rfc-editor.org/rfc/rfc3986),我提出的最佳正则表达式如下: 使用https://regexper.com的正则表达式流程图
// named groups
/^(?<scheme>[a-z][a-z0-9+.-]+):(?<authority>\/\/(?<user>[^@]+@)?(?<host>[a-z0-9.\-_~]+)(?<port>:\d+)?)?(?<path>(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@])+(?:\/(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@])*)*|(?:\/(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@])+)*)?(?<query>\?(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@]|[/?])+)?(?<fragment>\#(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@]|[/?])+)?$/i

// unnamed groups
/^([a-z][a-z0-9+.-]+):(\/\/([^@]+@)?([a-z0-9.\-_~]+)(:\d+)?)?((?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@])+(?:\/(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@])*)*|(?:\/(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@])+)*)?(\?(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@]|[/?])+)?(\#(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@]|[/?])+)?$/i

捕获组

  1. 方案
  2. 权限
  3. 用户信息
  4. 主机
  5. 端口
  6. 路径
  7. 查询
  8. 片段

@Teivaz 嗯,片段部分应该能捕获到; https://regex101.com/r/gilHCG/1 - Lostfields

8
我找到了最好的、最权威的中文指南,关于这个问题,它在这里:http://jmrware.com/articles/2009/uri_regexp/URI_regex.html(回答你的问题,请参见URI表格条目)。

RFC3986中的所有规则都在表2中重现,并提供了每个规则的正则表达式实现。

此页面提供了一个JavaScript实现:https://github.com/jhermsmeier/uri.regex
为方便起见,URI正则表达式如下:
# RFC-3986 URI component:  URI
[A-Za-z][A-Za-z0-9+\-.]* :                                      # scheme ":"
(?: //                                                          # hier-part
  (?: (?:[A-Za-z0-9\-._~!$&'()*+,;=:]|%[0-9A-Fa-f]{2})* @)?
  (?:
    \[
    (?:
      (?:
        (?:                                                    (?:[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]* )?
  (?:/ (?:[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})* )*
  )?
|        (?:[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})* )?   # [ "?" query ]
(?:\# (?:[A-Za-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})* )?   # [ "#" fragment ]

14
所有正则表达式之母啊,我的同事们认为我疯了,因为我使用并理解正则表达式... - Russ
这简直是疯了!这是一个最好的例子,展示了虽然正则表达式在许多情况下确实很有用,但它们并不适用于所有事情。就像锤子和钉子,螺丝刀和螺丝一样。 - Gwyneth Llewelyn

1

你是关心一些特定的URI还是试图找到一个验证STD66的单个正则表达式?

我本来想为您指出this regex以解析URI。理论上,您可以检查是否存在您关心的所有元素。

但我认为bdukes的答案更好。


0
对于会js的人来说,看看这个,前几行就是你需要的,如果测试通过就行。注意这个正则表达式来自:[wizard04][1]
编码愉快!

//This function

function explodeUri(str) {
    let regexUri = /^([a-z][a-z0-9+.-]*):(?:\/\/((?:(?=((?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*))(\3)@)?(?=(\[[0-9A-F:.]{2,}\]|(?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*))\5(?::(?=(\d*))\6)?)(\/(?=((?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9A-F]{2})*))\8)?|(\/?(?!\/)(?=((?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9A-F]{2})*))\10)?)(?:\?(?=((?:[a-z0-9-._~!$&'()*+,;=:@\/?]|%[0-9A-F]{2})*))\11)?(?:#(?=((?:[a-z0-9-._~!$&'()*+,;=:@\/?]|%[0-9A-F]{2})*))\12)?$/i;
    let match = str.match(regexUri);
    if (!match) {
        throw new Error('Invalid URI');
    }

    return {
        scheme: match[1],           // 1 == scheme
        userinfo: match[4],         // 4 == userinfo
        host: match[5],             // 5 == host
        port: match[6],             // 6 == port
        path: match[7] || match[9], // 7 if it has an authority, 9 if it doesn't
        query: match[11],           // 11 == query
        fragment: match[12]         // 12 == fragment
    };

/*    let { // scheme://user:info@host:port/path?query=val#fragment
        scheme, userinfo, host, port,
        path, query, fragment,
    } = explodeUri(u);                                           */
} ///https://snipplr.com/view/6889?codeview= Thanks bro!

function testUriValidation() {
    const validUrls = [
        'https://www.example.com/path/to/resource?query=value#fragment',
        'ftp://username:password@ftp.example.com:21/path/to/file',
        'http://subdomain.example.co.uk/page?param=value',
        'mailto:user@example.com',
        'tel:+123456789',
        'file:///path/to/file.txt',
        'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D',
        'https://[2001:db8::1]/path',               // IPv6 address
        'https://user@192.168.0.1',                  // IPv4 address with userinfo
        'http://user:pass@[::1]:8080/path',         // IPv6 with userinfo and port
        'http://example.com/a%20b',                 // URL-encoded path
        'https://example.com/%E2%82%AC100',         // URL-encoded path segment
        'http://example.com?name=John%20Doe',       // URL-encoded query parameter
        'https://example.com#section-3.4',          // URL fragment
        'https://example.com:8080',                 // URL with port
        'https://example.com:8080/path?query=value', // URL with path and query
    ];

    const invalidUrls = [
        'invalid-url',                          // Missing scheme
        'http://www.example.com:8080:',          // Invalid port
        'ftp://user:pass@invalid host/',         // Invalid characters in the host
        'http://[::1]',                          // IPv6 without a scheme
        'https://user@192.168.0.1:invalid',      // Invalid port
        'http://example.com/ path with space',   // Invalid space in the path
        "https://example.com?invalid=query",     // Invalid character in the query
        'ftp://example.com:8080#invalid-fragment', // Invalid character in the fragment
        "http://example.com:8080/path?query#fragment", // Both query and fragment without '='
        "http://:8080/path",                     // Empty authority
        'ftp://user@:21/path',                   // Empty host in authority
        'http://example.com/path?query#fragment%zz', // Invalid percent encoding
        'schema://user:pass@server.db.schema.table:1234?option=value#rowid'
    ];

    const uri = 'https://username:password@www.example.com:8080/path/to/resource?query=value#fragment';

    const explodedUri = explodeUri(uri);

    console.log(explodedUri);

    console.log('Valid URLs:');
    validUrls.forEach((url) => {
        console.log("validationg: ", url)
        try {
            const explodedUri = explodeUri(url);
            console.log('O:', url, explodedUri);
        } catch (error) {
            console.log('X', url, 'Error:', error.message);
        }
    });

    console.log('\nInvalid URLs:');
    invalidUrls.forEach((url) => {
        try {
            const explodedUri = explodeUri(url);
            console.log('X:', url, 'Exploded URI:', explodedUri);
        } catch (error) {
            console.log('ERROR: ', url, error.message);
        }
    });
}

testUriValidation();


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