URL 解析正则的拆分与校验
URL 的结构是「协议冒号双斜杠、授权部分、路径、查询串、片段」,正则擅长把这几个部分切开,不擅长判定整体是否合法。原因在于路径里可能带未编码字符、授权部分可能含用户信息与 IPv6 字面量、查询串里的分隔符规则由各协议自行约定。要拿结构就用语言内置的 URL 解析器,要在混杂文本里找出 URL 才用正则。
基本信息
| 主要字段 | 协议、用户信息、主机、端口、路径、查询串、片段 |
|---|---|
| 关键分隔符 | 冒号分协议,双斜杠起授权部分,斜杠起路径,问号起查询串,井号起片段 |
| 易错点 | 路径可以包含除问号与井号以外的字符,主机名可被方括号包裹以表示 IPv6 |
| 推荐分工 | 解析用内置库,抽取用正则,语义校验用代码 |
典型使用场景
- 拆解链接字段:把域名、路径与查询串分别入库,便于统计站点分布
- 文案与日志中抽取链接:文本里混有标点和中文时,用正则圈出候选 URL 再逐个校验
- 路由规则匹配:从请求路径里提取标识段,用于灰度或限流规则
- 查询串取值:从查询串里取出指定参数,注意需要先做解码再比较含义
- 重写与替换:批量把旧域名替换为新域名,但要求只替换主机部分
拆分写法与抽取写法
拆分写法与抽取写法
# 拆分常用字段:主机里排除冒号、斜杠、问号、井号,路径不含问号与井号,片段到行尾
^(?<scheme>[A-Za-z][A-Za-z0-9+.\-]*)://(?<host>[^/:?#]+)(?::(?<port>\d{1,5}))?(?<path>/[^?#]*)?(?:\?(?<query>[^#]*))?(?:#(?<frag>.*))?$
# 查询串内部再拆分:按和号切分后逐个按等号拆键值
(?:^|&)(?<key>[^=&]+)=(?<val>[^&]*) # 只取值到下一个和号
# 从混杂文本中抽取候选链接:宽松一些,抽出来再交给解析器
https?://[^\s"'<>]+ # 遇到空白或引号即停
# 处理 IPv6 字面量:主机部分被方括号包裹,形如 http://[::1]:8080/
\[(?<v6>[0-9A-Fa-f:]+)\]字段与规则
| 字段 | 分隔与规则 | 常见错误 |
|---|---|---|
| 协议 | 字母开头,后跟字母数字与加号、点、连字符 | 写成大小写敏感判断 |
| 主机 | 不含冒号、斜杠、问号、井号 | 把端口一起吃进主机名 |
| 端口 | 两位到五位数字,范围需另行判断 | 不校验 1 到 65535 |
| 路径 | 以斜杠起,不含问号与井号 | 用贪婪匹配吃掉查询串 |
| 查询串 | 到井号为止,键值对之间用和号 | 直接断言解码后的语义 |
| 片段 | 井号之后到结尾 | 误当成服务器可见内容 |
片段只在浏览器端使用,不会随请求发往服务器;做服务端分析时把它当作客户端状态而不是资源定位的一部分,否则会把同一页面的不同锚点当成不同资源。
百分号编码让字符串书写形式与语义不同:比较参数值前应先做一次解码,但解码后不要再进行二次解码,重复解码是常见的绕过漏洞来源。