用于 URL 的正则表达式匹配。维护中、安全且浏览器友好版的 url-regex。解决了 Node.js 服务器的 CVE-2020-7661。适用于 Node v14+ 和浏览器。为 Spam Scanner 和 Forward Email 维护。
在发现 CVE-2020-7661 并 公开披露(通过我在 Spam Scanner 和 Forward Email 上的工作)后,我使用了 url-regex 的一个实现,并在其上添加了一些额外逻辑来过滤掉错误的 URL 匹配。
然而,在 Forward Email 的生产环境中使用它(每周处理数十万封邮件)后,我发现并记录了 url-regex 的更多核心问题。
意识到 url-regex 已不再积极维护,截至编写本文时仍有 9 个未合并的拉取请求,并且缺乏浏览器支持后,我决定为所有人编写这个包,并合并所有开放的拉取请求。
这个包希望能更贴近现实世界中 URL 正则表达式的预期用法,并允许用户按需配置。如果这个包对你有帮助,请查看 Forward Email,并在 GitHub 上浏览我们的源代码,了解我们如何使用这个包。
注意: 此包的默认行为会尝试加载 re2(它是一个可选的 peer 依赖,用于防范正则表达式拒绝服务攻击等)。如果你想使用这一行为,必须通过 npm install re2 安装 re2;否则会回退到使用普通的 RegExp 实例。从 v4.0.0 开始,我们添加了一个选项,如果你希望强制此包不尝试加载 re2(例如,它存在于你的 node_modules 中但你不想使用它),只需传入 re2: false 作为选项。
npm:
npm install url-regex-safe
我们通过为 Node.js 使用引入 RE2 来解决了 CVE-2020-7661。通过 url-regex-safe,你无需再手动将 URL 正则表达式包装成 new RE2(urlRegex())(我们会自动为你完成)。
const urlRegexSafe = require('url-regex-safe');
const str = 'some long string with url.com in it';
const matches = str.match(urlRegexSafe());
for (const match of matches) {
console.log('match', match);
}
console.log(urlRegexSafe({ exact: true }).test('github.com'));
由于 RE2 并非为浏览器设计,因此不会在浏览器中使用,所以 CVE-2020-7661 在客户端仍然是一个问题。不过它并不严重,因为最多只会导致浏览器标签页崩溃(而在 Node.js 端则会导致整个进程崩溃并抛出内存溢出异常)。
如果你只是使用 <script> 标签,这个解决方案适合你!
<script src="https://unpkg.com/url-regex-safe"></script>
<script type="text/javascript">
(function() {
var str = 'some long string with url.com in it';
var matches = str.match(urlRegexSafe());
for (var i=0; i<matches.length; i++) {
console.log('match', matches[i]);
}
console.log(urlRegexSafe({ exact: true }).test('github.com'));
})();
</script>
假设你正在使用 browserify、webpack、rollup 或其他打包工具,你可以直接按照上面的 Node 用法操作。
要将此包与 TypeScript 一起使用,你可以安装 @types/url-regex-safe 包来获取类型定义。
npm install --save-dev @types/url-regex-safe
如果你不希望单独匹配 github.com(但 strict: false 时 www.github.com 会匹配),则必须覆盖默认值,设置 strict: true。
与已废弃且不再维护的 url-regex 包不同,我们在以下几方面做了不同处理:
strict 默认设为 false(url-regex 中该值为 true)auth 选项,默认设为 false(url-regex 匹配基本认证,该值设为 true,但该行为在 Chromium 中已被弃用)parens 和 ipv6 选项,分别默认设为 false 和 true(url-regex 中 parens 为 true,ipv6 不存在或默认为 false)此限制仅适用于你使用 re2 的情况:由于我们无法使用正则表达式的“负向后顾”功能(RE2 的限制),我们无法合并此拉取请求中的逻辑。如果合并了,我们将能够使 example.jpeg 仅在它是 example.jp 时才匹配。但目前如果你传入 example.jpeg,它会从中提取出 example.jp(因为 .jp 是一个 TLD)。可能存在其他解决方案,我们欢迎社区针对此问题贡献代码。
| 名称 | 网站 |
|---|---|
| Forward Email LLC | https://forwardemail.net |
| Kevin Mårtensson | |
| Diego Perini |
| 属性 | 类型 | 默认值 | 描述 |
|---|
re2 | Boolean | true | 尝试加载 re2 替代 RegExp 来创建新的正则表达式实例。如果传入 re2: false,则不会尝试加载 re2。 | |
exact | Boolean | false | 仅匹配精确的字符串。可用于 regex.test(str) 检查一个字符串是否为 URL。我们默认设为 false,以便匹配像 github.com 这样的字符串(而不要求协议或 www 子域名)。我们认为这更贴近该包的实际预期用途。 | |
strict | Boolean | false | 如果设为 true,则强制 URL 以有效协议或 www 开头。如果为 true,则允许任何顶级域 (TLD),只要它至少包含 2 个有效字符。如果为 false,则会根据 tlds 中的有效 TLD 列表进行匹配。 | |
auth | Boolean | false | 匹配基本认证头 (Basic Authentication) 中的 URL。我们默认设为 false,因为它在 Chromium 中已被弃用 (参见),否则会导致用户得到不需要的 URL 匹配(默认设为 false 也更贴合该包的实际预期用途)。 | |
localhost | Boolean | true | 允许在 URL 主机名部分使用 localhost。请查看 test/test.js 了解 localhost 测试的更多细节,它可能会返回一个不期望的值。欢迎提交拉取请求来解决 "pic.jp" vs. "pic.jpg" 问题。 | |
parens | Boolean | false | 匹配 Markdown 风格的尾部括号。我们设为 false,因为应由用户来解析 Markdown URL。 | |
apostrophes | Boolean | false | 匹配撇号。我们设为 false,因为我们不希望字符串 background: url('http://example.com/pic.jpg'); 匹配到 http://example.com/pic.jpg'。更多信息请参见此问题。 | |
trailingPeriod | Boolean | false | 匹配尾部句点。我们默认设为 false,因为实际行为应匹配 example.com 而非 example.com.(这与 url-regex 不同,后者在该包中会匹配尾部句点)。 | |
ipv4 | Boolean | true | 匹配 IPv4 URL。 | |
ipv6 | Boolean | true | 匹配 IPv6 URL。 | |
tlds | Array | tlds | 匹配特定的 TLD 列表,或使用 tlds 提供的默认列表。 | |
returnString | Boolean | false | 返回正则表达式字符串而不是 RegExp 对象(用于自定义逻辑,例如我们在 Spam Scanner 中那样)。 |
apostrophe 选项,默认设为 false(url-regex 中该值为 true)trailingPeriod 选项,默认设为 false(意味着匹配结果不会包含尾部句点,而 url-regex 中该值为 true)