☰
pure_live 工具箱直播链接解析审计与实现解析:从“首链接正则“到 HTTP(S) URI 主机路径契约
2026/10/15 10:21:30 网站建设 项目流程
  • 音视频
  • 直播
  • 移动开发

【免费下载链接】pure_live

纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。

项目地址:https://gitcode.com/gh_mirrors/pur/pure_live
点击查看免费下载

导读

本文围绕 pure_live 开源项目在 2026-09-07 完成的"工具箱直播链接解析"专项审计展开:ToolBoxController.jumpToRoom(跳转房间)与直链获取共用LiveUrlTool.parseLiveUrl,旧实现长期依赖"取分享文本第一个 URL + 整串 contains 判断平台 + 逐平台正则提取 ID",导致大写协议、相似域名、查询参数、伪装后缀等大量误判。本文完整梳理该批审计发现的问题清单、修订后的 URI/主机/路径契约、94/94 定向回归门禁与最终源码结果,并结合仓库当前实现给出可复现的解析规则与边界说明,帮助读者理解 pure_live 如何把剪贴板/手动输入的直播分享文本稳健地解析为"平台 + 房间 ID"。

一、审计背景:剪贴板识别与手动解析共享同一契约

本批审计基线为提交96c5ad55,是 工具箱剪贴板与页面入口审查(2026-09-07) 的延续。上一轮解决了剪贴板自动填充的生命周期问题,本批则进一步区分两类解析场景:

  • 本地身份解析:从分享文本中直接提取"平台 + 房间 ID",不发起任何网络请求;
  • 远端短链/直播接口解析:对b23.tv、v.douyin.com等短链做重定向跟随,或调用直播信息接口换取身份。

两个场景共用同一个入口LiveUrlTool.parseLiveUrl(见 live_url_tool.dart),而ToolBoxController的"跳转房间"与"获取直链"两个动作在 toolbox_controller.dart 的_resolve中统一走parseLink?.call(text) ?? LiveUrlTool.parseLiveUrl(...)。

注意:审计中提到的LiveUrlTool.getLivePlayUrl是当时调用链的入口表述;当前仓库实现中,解析契约统一收敛在LiveUrlTool.parseLiveUrl,工具箱通过注入的parseLink或默认实现调用它。

审计同时与冻结上游c6c9bd70aedc503c003110dae10a83ad0bb891d8做了只读比较:上游存在相同的"首链接/contains/正则"逻辑,因此本批问题被归类为upstream-existing(上游既有问题),并非远端最新版结论。

二、首个错误状态与来源:旧实现的四大结构性缺陷

审计文档明确指出,错误在"发起直播请求之前"就已经产生。旧实现(parseLiveUrl修订前版本)的核心逻辑为:

  1. 取分享文本中的第一个 URL作为唯一候选;
  2. 用整串文本做contains判断平台(而不是解析后的主机名);
  3. 逐平台正则提取房间 ID;
  4. 对快手/CC 使用包含查询参数的整串末尾锚点;
  5. 对抖音www空路径直接调用pathSegments.last。

从源码结构看,这些做法在四个层面埋下错误:

层面旧行为典型后果
URL 提取只看第一个正则命中首个无关链接会遮蔽后面的 AcFun 房间
平台判定整串contains,不看主机相似域名、未知主机查询中嵌入的虎牙地址、userinfo 被误判
ID 提取逐平台正则 + 整串末尾锚点快手/CC 带查询参数时得到空 ID;虎牙连字符被截断;Twitch 片段进入 ID
空路径直接pathSegments.last抖音www空路径抛出Bad state: No element

本分支新增的 AcFun 解析

与旧逻辑不同,本分支新增的 AcFun 解析直接复用WebSearchRoomParser(见 web_search_room_parser.dart),并保留三条隔离规则:

  • 非零 UID:AcFun 房间 ID 使用RegExp(r'^[1-9][0-9]{0,19}$'),拒绝0开头;
  • 严格路径:仅接受live.acfun.cn/live/{id}这种两段路径形态;
  • 作者页隔离:live.acfun.cn/{作者}这类作者主页不视为房间链接。

三、红测复现:27 个离线用例中的 18 个失败

修订前先以确定性离线用例做红测(red test),记录20260906T234546780Z-quality-focused.json:27 个离线用例中 9 通过、18 失败,耗时 165.301 秒,结束时活跃重型进程 0。日志保存在local-artifacts/live-url-parser-red-20260907.log。

审计特别强调:18 是失败用例数,不是独立 Bug 数——同一根因可能覆盖多个用例。

红测复现的失败模式可归为三组:

第一组:协议/主机形态识别失败

  • 大写协议/主机未识别(如HTTPS://、WWW.);
  • 虎牙链接中的连字符被截断,ID 提取不完整;
  • 快手链接带查询参数、ID 含下划线/连字符时得到空 ID;
  • CC 链接带查询参数得到空 ID;
  • Twitch 链接的片段(fragment)部分进入房间 ID。

第二组:链接定位与误判

  • 首个无关链接遮蔽后面的 AcFun 房间;
  • 相似域名被误判(子域边界判断不严谨);
  • 未知主机的查询参数里嵌入的虎牙地址被当成第二个链接提取;
  • 含 userinfo(如https://user:pass@host/...)的输入被误判;
  • 伪装虎牙后缀的地址得到空 ID 与平台的半有效结果。

第三组:空路径与非法输入

  • 抖音www空路径抛出Bad state: No element(对应旧实现pathSegments.last);
  • Twitch directory、虎牙 search、FTP 输入未被正确排除;
  • 其他平台的空路径返回半有效结果。

四、修订方案:先提取完整 URL,再核对主机与路径

修订后的核心思想是:剪贴板识别和手动解析共用同一套 HTTP(S) URI 提取逻辑;先分离出完整 URL,再核对主机及路径,绝不从未知主机的查询参数里提取第二个嵌套链接。

4.1 统一 URI 提取:sharedHttpUrls

修订后新增静态方法sharedHttpUrls/sharedHttpUris(见 live_url_tool.dart),取代旧的"第一个正则命中":

// 提取完整 HTTP URL 后再检查 host/path,避免把 FTP URL 内嵌的 // www 地址误当成第二个 HTTP 链接。 static Iterable<Uri> sharedHttpUris(String text) => sharedHttpUrls(text).map(Uri.parse); static Iterable<String> sharedHttpUrls(String text) sync* { final urls = RegExp(r'(?:[a-z][a-z0-9+.-]*://|www\.)[^\s<>]+', caseSensitive: false); for (final match in urls.allMatches(text)) { var candidate = match.group(0)!; if (candidate.toLowerCase().startsWith('www.')) candidate = 'https://$candidate'; // ... 尾部标点清理(含 XHS/微博中文散文粘连处理)... final uri = Uri.tryParse(candidate); if (uri == null || uri.userInfo.isNotEmpty || uri.host.isEmpty || (uri.scheme != 'http' && uri.scheme != 'https')) { continue; // 排除 userinfo、非 HTTP(S)、空主机 } // Uri.tryParse 校验百分号转义语法,但解码后的访问器仍可能因 // 非法 UTF-8(如 /%FF)抛出 FormatException,逐层排除。 try { uri.pathSegments; uri.queryParametersAll; } on FormatException { continue; } yield candidate; } }

这套提取器解决了红测中的多类问题:

  • 支持无协议www.开头:自动补https://前缀;
  • 支持分享末尾标点:对,。!?、;:)》」』”’及 ASCII 标点统一清理尾部;
  • 排除 userinfo、非 HTTP(S)、空主机/空路径与畸形路径编码(/%FF这类非法 UTF-8 会被pathSegments/queryParametersAll的FormatException挡住);
  • 不从未知主机的查询参数里提取第二个嵌套链接——FTP://...www...这类场景不再产生误提取;
  • 对小红书(xhslink.com / xiaohongshu.com)和微博(weibo.com)分享保留特殊的中文散文粘连处理:在中文标点处截断并清理尾部标点,同时保留/.、/..这类属于 URL 结构而非散文标点的路径组件。

4.2 主机判断与 ID 边界

修订后的主机判断不再依赖整串contains,而是按完整根域或点分隔子域判断(_hostIs(host, root)等价于host == root || host.endsWith('.$root')),且查询/片段不参与房间 ID:

static bool _hostIs(String host, String root) => host == root || host.endsWith('.$root');

抖音www空路径的崩溃也得到修复:_douyinWebRoomId(见 live_url_tool.dart)只接受恰好一个非空路径段且为 1~20 位数字的形态,/video/{id}(回放)与/search/{query}(结果页)的尾部数字不再被当作直播房间 ID。

4.3 按文本顺序寻找首个可解析目标

修订后的_parseLiveUrl(见 live_url_tool.dart)按文本出现顺序遍历sharedHttpUrls提取出的每个候选,逐个尝试平台解析;只有当前候选完全无法解析时才继续下一个。这样"首个无关链接不再遮蔽后续的 AcFun 房间"。

4.4 优先复用 WebSearchRoomParser 的平台规则

手动解析优先调用WebSearchRoomParser.parse(realUrl)(见 web_search_room_parser.dart),该解析器统一覆盖了 niconico、Kilakila、小红书、微博、Missevan、Inke、Picarto、TwitCasting、Showroom、CHZZK、17LIVE、LiveMe、TikTok、YouTube、Bigo、PandaLive、FC2、SteamBroadcast、京东直播、酷狗直播、百度直播、六间房、LOOK 直播,以及虎牙、抖音、斗鱼、快手、CC、bilibili、Twitch、SOOP、YY、AcFun 的网页房间形态。

保留导航词检查是其中的关键:_reservedSegments(web_search_room_parser.dart)收录了search、category、categories、directory、directories、game、games、video、videos、user、users、index、topic、topics、downloads、settings、login、signup等词。任何"平台首页/分类/搜索/档案页"的 URL,即使路径首段长得像房间 ID,也会因命中保留词而被拒绝——这直接对应红测中"Twitch directory、虎牙 search 未被正确排除"的修复。isRoomIdentifier被提取为公共方法(见 web_search_room_parser.dart),供containsSupportedLink复用,原搜索规则保持不变。

4.5 手动工具保留的兼容别名入口

在WebSearchRoomParser之外,手动解析保留了几个不暴露给网页搜索解析器的兼容入口(live_url_tool.dart):

  • 斗鱼/CC 别名:www.douyu.com(含douyu.com子域)与cc.163.com的首段;
  • 快手cn域:live.kuaishou.cn/u/{id};
  • SOOPcom域:sooplive.com(网页搜索解析器只认sooplive.co.kr系);
  • bilibiliwww域:www.bilibili.com/{数字}(区别于live.bilibili.com);
  • 抖音回放形态:webcast.amemv.com/douyin/webcast/reflow/{id}的reflow数字段。

这些入口均通过isRoomIdentifier校验后才能返回,半有效结果被杜绝。

4.6 翻译资产:补上 AcFun 房间格式

中英文支持说明同步补充了 AcFun 房间格式,见 assets/translations/zh.json 与 assets/translations/en.json 的toolbox_support_content:

AcFun直播: https://live.acfun.cn/live/123456

审计明确提醒:示例地址只是格式说明,不是在线房间证据。

4.7 抖音的保守边界

  • 抖音www/video与webcast reflow用例只锁定已有 ID 提取行为;
  • 内部room_id与web_rid的真实转换、视频页是否具备直播身份,仍待接口证据,不据此宣称播放成功。

五、回归门禁:75/75 → 93/94 → 94/94

5.1 首次修订:75/75 通过

首次修订后定向回归75/75 通过,构成如下:

类别数量
URL 输入27 项
既有网页搜索3 项
链接检测(containsSupportedLink)31 项
剪贴板/页面回归14 项

记录20260906T234902234Z-quality-focused.json,耗时 140.818 秒,结束时活跃重型进程 0。

5.2 扩展门禁:44 项 URL 输入 + 2 项翻译说明

随后扩展为44 项 URL 输入和 2 项翻译说明检查。URL 用例通过HttpOverrides记录并阻止所有网络尝试,覆盖:

  • 未知主机嵌套短链;
  • 空短链路径;
  • 畸形编码(非法百分号转义/非法 UTF-8);
  • 多个链接的顺序与兼容别名。

全程没有请求任何真实平台。扩展门禁20260907T000046539Z-quality-focused.json为93/94,唯一失败用例是中文弯引号结尾——提取器的尾部标点集合漏了弯引号;补齐后重新复测,不跳过用例。该次flutter analyze有 1 条大括号风格提示(360.2 秒),一并补齐。

过程记录说明:该记录耗时 620.609 秒且结束时仍有 1 个活跃重型进程,不作为最终通过证据;后续20260907T000310949Z-quality-focused.json在格式检查阶段停止,按资源守卫格式化变更文件后重跑门禁。最终证据如下。

5.3 最终源码结果

  • 94/94 定向测试通过:44 项直接 URL + 2 项翻译说明 + 3 项网页搜索 + 31 项链接检测 + 14 项剪贴板/页面交互;
  • flutter analyze无诊断,249.3 秒;
  • 记录20260907T001156727Z-quality-focused.json,基于96c5ad55的修订工作树,耗时 461.445 秒,结束时活跃重型进程 0;
  • 日志:local-artifacts/live-url-parser-final3-20260907.log。

资源门禁串行运行;遇到其他项目的活跃 Java 进程时等待而非终止。审计同时声明边界:仅完成定向源码门禁,不等同全量回归、APK 或原生构建通过。

六、关键源码解读:解析调用链与超时/取消契约

6.1 从工具箱到解析器的调用链

以"跳转房间"为例,完整调用链为:

  1. ToolBoxPage的按钮回调调用controller.jumpToRoom(text, context: context)(toolbox_controller.dart);
  2. jumpToRoom进入_runAction的ActionScope(超时默认 12 秒,见 toolbox_controller.dart);
  3. _resolve调用parseLink ?? LiveUrlTool.parseLiveUrl(text, cancelToken: scope.cancelToken);
  4. 解析结果[roomId, platform]需通过Sites.isSupported校验,才组装LiveRoom并跳转。

"获取直链"(getPlayUrl)走同一_resolve,随后进入LiveDirectLinkFlow.run(见 direct_link_flow.dart),依次执行getRoomDetail→discoverPlayQualities→ 用户选择清晰度 →resolvePlayUrls→ 用户选择线路 → 复制。用户选择环节不受超时限制(timed: false),只有网络/平台操作才被计时。

6.2parseLiveUrl的短链会话契约

parseLiveUrl每次调用创建一个LiveShortLinkSession(见 live_short_link_session.dart):

  • 专属客户端:clientFactory必须返回全新 Dio 客户端,绝不使用应用 HTTP 单例;
  • 总期限:connect/send/receive 均设为传入的timeout(默认 12 秒),Future.timeout到期主动取消并强制session.close();
  • 请求预算:单次解析最多8 次 HTTP 请求,配合已访问 URL 集合防止循环,片段(fragment)不改变网络请求身份;
  • 幂等释放:成功、失败、超时都在finally中ownedCancel.cancel()+session.close(),迟到结果由关闭状态挡住(见 live_url_tool.dart)。

这套契约正是对旧实现"b23 递归新建 Dio 无访问集合/跳数/总期限、抖音自动跟随 maxRedirects=100、临时客户端不 close"等问题的回应。需要说明:该契约属于后续 直播短链接请求边界审查(2026-09-07) 的完成范围(138/138 通过、analyze 无诊断),本批审计仅锁定直接链接解析。

七、剩余工作与回滚边界

7.1 明确未完成项

审计明确声明"整个工具箱保持未闭环":

  • 下一组明确任务:b23 重定向循环/深度、相对 Location、请求超时与临时 HTTP 客户端释放、抖音短链映射(后由 短链请求边界 完成);
  • 再下一组:工具箱动作重复触发、关闭后的迟到导航、画质/线路选择、取消和复制;
  • 当前远端辅助函数仍保留旧递归与跳转上限设计,本批不声称这些问题已解决;
  • Android/Windows 原生体验、播放/录制、全量门禁与发布另行验收;已安装的67612cc9APK不包含本批变更。

7.2 回滚范围

回滚范围为四个源文件/资产与新增测试,无持久化格式变化:

  • live_url_tool.dart
  • web_search_room_parser.dart
  • toolbox_controller.dart
  • 两份翻译资产(assets/translations/zh.json、assets/translations/en.json)
  • 新增测试

回滚会恢复上文已复现的直接链接解析问题。

八、结论与后续批次状态

本批审计把 pure_live 工具箱的直播链接解析从"首链接 + 整串 contains + 逐平台正则"重构为"HTTP(S) URI 提取 + 主机/路径核对 + 文本顺序首个可解析目标"的契约,并通过 94/94 定向测试与无诊断的flutter analyze锁定行为。仓库中的当前实现(live_url_tool.dart、web_search_room_parser.dart)即为该批修订的最终落地形态;相关回归用例可参考 test/shared/platforms/xiaohongshu_link_test.dart(小红书分享文本提取的既有契约测试)以及审计日志中记录的定向门禁。

后续状态请见 直播短链接请求边界审查(2026-09-07)(完成跳转/期限/资源释放/loopback IO,138/138 通过)与 工具箱剪贴板与页面入口审查(2026-09-07)(剪贴板生命周期,65/65 通过)。真实平台条件、工具箱操作与原生验收仍独立待验——解析通过不等于平台在线、更不等于播放成功,这是整个审计链反复强调的证据边界。

  • 音视频
  • 直播
  • 移动开发

【免费下载链接】pure_live

纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。

项目地址:https://gitcode.com/gh_mirrors/pur/pure_live
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询