<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>CLI — mizore / notes</title>
    <link>https://mizore.blog/tags/cli/</link>
    <link xmlns="http://www.w3.org/2005/Atom" href="https://mizore.blog/tags/cli/feed.xml" rel="self" type="application/rss+xml"></link>
    <description>1 篇</description>
    <language>zh-CN</language>
    <lastBuildDate>Sun, 25 Oct 2026 00:00:00 +0000</lastBuildDate>
    <item>
      <title>gdirect：终端直连 Google 云盘的原理与配置指南</title>
      <link>https://mizore.blog/posts/gdirect/</link>
      <description>&lt;p&gt;平时从 Google Drive 拖几十 GB 的数据集或备份，走代理往往既烧流量又受限于节点带宽；用浏览器下载还经常在中途断流后从头再来。为此我写了 &lt;a href=&#34;https://github.com/mizorewww/gdirect&#34;&gt;gdirect&lt;/a&gt;：一个在终端里浏览、下载、上传和挂载 Google 云盘的工具。&lt;/p&gt;&#xA;&lt;p&gt;这篇文章按三个部分组织：&lt;strong&gt;先用几分钟讲清楚它是怎么“伪装”并直连 Google 的（TL;DR）&lt;/strong&gt;，接着是&lt;strong&gt;开箱即用的安装与 10 分钟图文配置指南&lt;/strong&gt;，最后再深入源码做&lt;strong&gt;底层技术解析&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;tldr我们是怎么伪装直连的&#34;&gt;TL;DR：我们是怎么“伪装”直连的？&lt;/h2&gt;&#xA;&lt;p&gt;在大陆网络下，Google Drive 的域名（&lt;code&gt;drive.google.com&lt;/code&gt;、&lt;code&gt;www.googleapis.com&lt;/code&gt;、&lt;code&gt;drive.usercontent.google.com&lt;/code&gt;）处于被封锁状态；但与此同时，Google 为了给国内提供 Chrome 更新、Android 连通性校验、字体等基础服务，在大陆境内（电信、联通、移动骨干网内）保留了一批可直连的边缘服务器——也就是分流规则集（如 &lt;a href=&#34;https://github.com/DustinWin/ruleset_geodata&#34;&gt;DustinWin/ruleset_geodata&lt;/a&gt;）里归入 &lt;strong&gt;&lt;code&gt;google-cn&lt;/code&gt;&lt;/strong&gt; 的那批域名（如 &lt;code&gt;dl.google.com&lt;/code&gt;、&lt;code&gt;g.cn&lt;/code&gt;、&lt;code&gt;fonts.googleapis.com&lt;/code&gt;）背后的国内 IP（例如广州电信的 &lt;code&gt;183.56.143.147&lt;/code&gt;）。&lt;/p&gt;&#xA;&lt;p&gt;关键在于：&lt;strong&gt;这些位于墙内的 &lt;code&gt;google-cn&lt;/code&gt; 节点，跑的同样是 Google 统一的前端反向代理（GFE，Google Front End），背后通过 Google 内部专线直通全球云盘后端，并且持有 &lt;code&gt;*.googleapis.com&lt;/code&gt; 与 &lt;code&gt;*.googleusercontent.com&lt;/code&gt; 的合法官方 TLS 证书。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;gdirect&lt;/code&gt; 就是利用这一点，用&lt;strong&gt;“借 &lt;code&gt;google-cn&lt;/code&gt; 的国内肉身（IP），发 Google Drive 的原始请求（域名与证书）”&lt;/strong&gt;完成伪装与直连的：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;底层 TCP 指向国内 &lt;code&gt;google-cn&lt;/code&gt; 节点 IP&lt;/strong&gt;：绕过被污染的系统 DNS，从内置种子 IP（&lt;code&gt;183.56.143.147&lt;/code&gt;）和候选池出发，直接连向目标的 443 端口。经过实测探针（核验 Google 官方 TLS 证书 + 验证 Drive v3 API 响应）确认可用后存入本地 SQLite。在底层的套接字建立阶段（Rust 的 &lt;code&gt;reqwest::resolve&lt;/code&gt; 和定制 C++ &lt;code&gt;libaria2&lt;/code&gt; 的 &lt;code&gt;getaddrinfo&lt;/code&gt; 钩子），哪怕下载中途发生多次 302 跨域名重定向，所有连接的目标 IP 都会被强制锁死在这个国内 &lt;code&gt;google-cn&lt;/code&gt; 节点上——因为目标 IP 本身就在国内骨干网，物理链路根本不过墙。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;上层 TLS / HTTP 保持真实云盘域名与证书校验&lt;/strong&gt;：连上国内 &lt;code&gt;google-cn&lt;/code&gt; 的 IP 后，应用层请求写的依然是标准的 &lt;code&gt;https://www.googleapis.com&lt;/code&gt;（API 与分块上传）和 &lt;code&gt;https://drive.usercontent.google.com&lt;/code&gt; / &lt;code&gt;https://*.googleusercontent.com&lt;/code&gt;（文件下载）。国内 GFE 节点收到后，按主机名通过 Google 内网把云盘数据拉回来；本机也照常按真实域名严格校验 TLS 证书（若遇到针对明文域名的拦截，还可用 &lt;code&gt;--sni&lt;/code&gt; / &lt;code&gt;--no-sni&lt;/code&gt; 改写或剥离 TLS 握手中的 SNI）。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;控制面与数据面分离&lt;/strong&gt;：只有几 KB 的登录与刷新令牌（&lt;code&gt;accounts.google.com&lt;/code&gt;、&lt;code&gt;oauth2.googleapis.com&lt;/code&gt;）照常走系统网络（翻墙代理），保证随时能登得上；真正吃带宽的目录浏览、多连接并发下载、32 MiB 分块上传全部走上述国内直连 IP，不耗一字节代理流量就能跑满本地宽带。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;流量类型&lt;/th&gt;&#xA;&lt;th&gt;涉及命令 / 域名&lt;/th&gt;&#xA;&lt;th&gt;走向&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;云盘 API、下载、上传&lt;/td&gt;&#xA;&lt;td&gt;&lt;code&gt;browse&lt;/code&gt;、&lt;code&gt;ls&lt;/code&gt;、&lt;code&gt;download&lt;/code&gt;、&lt;code&gt;upload&lt;/code&gt;（&lt;code&gt;www.googleapis.com&lt;/code&gt;、&lt;code&gt;drive.usercontent.google.com&lt;/code&gt;、&lt;code&gt;*.googleusercontent.com&lt;/code&gt;）&lt;/td&gt;&#xA;&lt;td&gt;扫描到的国内 &lt;code&gt;google-cn&lt;/code&gt; 可用 IP。主机名和证书校验不变，可用 &lt;code&gt;--sni&lt;/code&gt; / &lt;code&gt;--no-sni&lt;/code&gt; 改写或去掉 TLS SNI&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;登录与刷新令牌&lt;/td&gt;&#xA;&lt;td&gt;&lt;code&gt;login&lt;/code&gt; 及自动刷新（&lt;code&gt;accounts.google.com&lt;/code&gt;、&lt;code&gt;oauth2.googleapis.com&lt;/code&gt;）&lt;/td&gt;&#xA;&lt;td&gt;系统网络（走系统 DNS 与翻墙代理）&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;磁盘挂载&lt;/td&gt;&#xA;&lt;td&gt;&lt;code&gt;mount&lt;/code&gt;（底层调用 &lt;code&gt;rclone&lt;/code&gt;）&lt;/td&gt;&#xA;&lt;td&gt;系统网络&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;大陆网络环境特别提示：&lt;/strong&gt;&#xA;就算你的翻墙软件已经把 &lt;a href=&#34;https://github.com/DustinWin/ruleset_geodata&#34;&gt;DustinWin/ruleset_geodata&lt;/a&gt; 的 &lt;code&gt;google-cn&lt;/code&gt; 设为了直连，开着翻墙软件（尤其是 TUN 模式）跑 &lt;code&gt;gdirect&lt;/code&gt; 时&lt;strong&gt;依然有可能走代理&lt;/strong&gt;！因为 &lt;code&gt;gdirect&lt;/code&gt; 虽然连的是 &lt;code&gt;google-cn&lt;/code&gt; 的 IP，但 TLS 握手发的是云盘域名（&lt;code&gt;drive.usercontent.google.com&lt;/code&gt;、&lt;code&gt;www.googleapis.com&lt;/code&gt;），而这些域名在规则集里属于需要翻墙的 &lt;code&gt;google&lt;/code&gt; 而非 &lt;code&gt;google-cn&lt;/code&gt;，会被翻墙软件嗅探（Sniff）出域名后半路截胡送进代理。&#xA;因此大陆用户请务必二选一（详见下文&lt;a href=&#34;#%E5%A4%A7%E9%99%86%E7%BD%91%E7%BB%9C%E4%B8%8E%E4%BB%A3%E7%90%86%E9%85%8D%E7%BD%AE%E6%8C%87%E5%8D%97&#34;&gt;大陆网络与代理配置指南&lt;/a&gt;）：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;在翻墙软件中配置规则&lt;/strong&gt;：显式让上述云盘域名（或 &lt;code&gt;gdirect&lt;/code&gt; 进程）走 &lt;code&gt;DIRECT&lt;/code&gt; 直连并关闭 &lt;code&gt;fake-ip&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;退出翻墙软件或关闭 TUN&lt;/strong&gt;：开着代理完成 &lt;code&gt;gdirect login&lt;/code&gt; 登录好之后，在 &lt;code&gt;scan&lt;/code&gt; 和下载时退出翻墙软件（或关闭 TUN 模式）。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;安装指南&#34;&gt;安装指南&lt;/h2&gt;&#xA;&lt;p&gt;预编译二进制支持 &lt;strong&gt;macOS 15+&lt;/strong&gt;（Apple Silicon / Intel）、&lt;strong&gt;Linux x86_64&lt;/strong&gt;（glibc 2.35+，如 Ubuntu 22.04+、Debian 12+、Fedora、Arch）和 &lt;strong&gt;Windows x86_64&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;h3 id=&#34;方式一一键安装脚本&#34;&gt;方式一：一键安装脚本&lt;/h3&gt;&#xA;&lt;p&gt;macOS / Linux：&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;curl -fsSL https://raw.githubusercontent.com/mizorewww/gdirect/main/scripts/install.sh &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; bash&lt;/code&gt;&lt;/pre&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;span class=&#34;lang&#34;&gt;sh&lt;/span&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figure&gt;&#xA;&lt;p&gt;Windows PowerShell：&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;scriptblock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Invoke-RestMethod&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;https://raw.githubusercontent.com/mizorewww/gdirect/main/scripts/install.ps1&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;span class=&#34;lang&#34;&gt;powershell&lt;/span&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figure&gt;&#xA;&lt;p&gt;安装脚本会把 &lt;code&gt;gdirect&lt;/code&gt; 和配套校验用的 &lt;code&gt;cacert.pem&lt;/code&gt; 放到 &lt;code&gt;~/.local/bin&lt;/code&gt;（Windows 为 &lt;code&gt;%LOCALAPPDATA%\gdirect\bin&lt;/code&gt;）。&lt;/p&gt;&#xA;&lt;p&gt;如果你还想用 &lt;code&gt;gdirect mount&lt;/code&gt; 把云盘挂载成本地磁盘，在安装命令末尾加上 &lt;code&gt;--with-mount&lt;/code&gt;（PowerShell 用 &lt;code&gt;-WithMount&lt;/code&gt;），脚本会顺带装好依赖（&lt;code&gt;rclone 1.75.2+&lt;/code&gt;，Linux 额外安装 &lt;code&gt;fuse3&lt;/code&gt;，Windows 通过 &lt;code&gt;winget&lt;/code&gt; 安装 &lt;code&gt;WinFsp&lt;/code&gt;）：&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;curl -fsSL https://raw.githubusercontent.com/mizorewww/gdirect/main/scripts/install.sh &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; bash -s -- --with-mount&lt;/code&gt;&lt;/pre&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;span class=&#34;lang&#34;&gt;sh&lt;/span&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figure&gt;&#xA;&lt;h3 id=&#34;方式二homebrewmacos--linux&#34;&gt;方式二：Homebrew（macOS / Linux）&lt;/h3&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;brew tap mizorewww/gdirect https://github.com/mizorewww/gdirect&#xA;brew install mizorewww/gdirect/gdirect&#xA;brew install rclone   &lt;span class=&#34;c1&#34;&gt;# 仅挂载功能需要；Linux 另需用系统包管理器安装 fuse3&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;span class=&#34;lang&#34;&gt;sh&lt;/span&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figure&gt;&#xA;&lt;p&gt;后续升级执行 &lt;code&gt;brew update &amp;amp;&amp;amp; brew upgrade gdirect&lt;/code&gt; 即可。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;google-cloud-一次性配置指南约-10-分钟&#34;&gt;Google Cloud 一次性配置指南（约 10 分钟）&lt;/h2&gt;&#xA;&lt;p&gt;为了避免共用 OAuth 客户端导致全员配额耗尽（429 限流）或被封禁，&lt;code&gt;gdirect&lt;/code&gt; 不内置公共客户端，而是让你在自己的 Google Cloud 项目里创建一个 &lt;strong&gt;Desktop app（桌面应用）&lt;/strong&gt; 客户端。只需操作一次，Google 完全免费。&lt;/p&gt;&#xA;&lt;h3 id=&#34;1-新建项目并启用-drive-api&#34;&gt;1. 新建项目并启用 Drive API&lt;/h3&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;打开 &lt;a href=&#34;https://console.cloud.google.com/&#34;&gt;Google Cloud 控制台&lt;/a&gt;，点击页面顶部的项目选择器，再点击右上角的 &lt;strong&gt;New project（新建项目）&lt;/strong&gt;。名称随意（例如 &lt;code&gt;gdirect&lt;/code&gt;），个人账号的 &lt;strong&gt;Location（位置）&lt;/strong&gt; 保持 &lt;strong&gt;No organization（无组织）&lt;/strong&gt; 即可。点击 &lt;strong&gt;Create（创建）&lt;/strong&gt; 后，确认顶部已经切换到这个新项目。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/227da6b0853f1e9f1bd3d6804f483597ce7217450ec04377447a2b6c51eb51f6.webp&#34; alt=&#34;在 Google Cloud 控制台中新建项目&#34; decoding=&#34;async&#34; fetchpriority=&#34;high&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/25e786a94dff774300f684fbe675525a7f82a90a4f3b8578bdba3ddf1863ec7a.webp 720w, https://mizore.blog/images/227da6b0853f1e9f1bd3d6804f483597ce7217450ec04377447a2b6c51eb51f6.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;打开 &lt;a href=&#34;https://console.cloud.google.com/apis/library/drive.googleapis.com&#34;&gt;Google Drive API&lt;/a&gt; 页面，确认顶部是刚才新建的项目，点击 &lt;strong&gt;Enable（启用）&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/b84f0601e820434a99963946f57ff312f6110e50881f0d7c1347e5c6836b9990.webp&#34; alt=&#34;启用 Google Drive API&#34; decoding=&#34;async&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/d2d2e7c8c0eb550cd9975ae7df86bb2e549b4d5cb69577b9e4f207c38136ac89.webp 720w, https://mizore.blog/images/b84f0601e820434a99963946f57ff312f6110e50881f0d7c1347e5c6836b9990.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;完成后，页面会显示服务名称 &lt;code&gt;drive.googleapis.com&lt;/code&gt;，状态变为 &lt;strong&gt;Enabled（已启用）&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/5fa0e312eeb0c5ce4708715c0655f507d6f1dd8ffc9a1b68e7076f8bcee35ae5.webp&#34; alt=&#34;确认 Google Drive API 已启用&#34; decoding=&#34;async&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/5aa003706de18fec8763a7f1f1416adca22b86b1743de5ee1d1c4bd0f9cc63b2.webp 720w, https://mizore.blog/images/5fa0e312eeb0c5ce4708715c0655f507d6f1dd8ffc9a1b68e7076f8bcee35ae5.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;2-配置-oauth-应用&#34;&gt;2. 配置 OAuth 应用&lt;/h3&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;打开 &lt;a href=&#34;https://console.cloud.google.com/auth/overview&#34;&gt;Google Auth Platform&lt;/a&gt;，点击 &lt;strong&gt;Get started（开始）&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;App information（应用信息）&lt;/strong&gt;：填写应用名称（比如 &lt;code&gt;gdirect&lt;/code&gt;，登录授权时会显示），用户支持邮箱选择你自己的邮箱，点击 &lt;strong&gt;Next（下一步）&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/af8d860d5859c321ae318dab7d90038bfbe88e59dc1acf358da250843f599fe4.webp&#34; alt=&#34;填写 OAuth 应用名称与支持邮箱&#34; decoding=&#34;async&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/868cf435178d0450318cc0d4956be7eb2de154bacb68cdba2595af118853e3e7.webp 720w, https://mizore.blog/images/af8d860d5859c321ae318dab7d90038bfbe88e59dc1acf358da250843f599fe4.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Audience（受众群体）&lt;/strong&gt;：选择 &lt;strong&gt;External（外部）&lt;/strong&gt;，点击 &lt;strong&gt;Next（下一步）&lt;/strong&gt;（个人账号无法选 Internal）。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/2d099de3d6f1255b437cbc02c7d2d32abc06f3ae9e4de4afbd07d2244469e395.webp&#34; alt=&#34;受众群体选择外部&#34; decoding=&#34;async&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/a4a128070612087c09608ca332e65ce1e44ade5851d37de2563181f254d94e8b.webp 720w, https://mizore.blog/images/2d099de3d6f1255b437cbc02c7d2d32abc06f3ae9e4de4afbd07d2244469e395.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Contact information（联系信息）&lt;/strong&gt;：填写你的邮箱，点击 &lt;strong&gt;Next（下一步）&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Finish（完成）&lt;/strong&gt;：勾选同意 Google API 服务用户数据政策，点击 &lt;strong&gt;Continue（继续）&lt;/strong&gt;，再点击 &lt;strong&gt;Create（创建）&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/1f8b2061017b9ec81fdbf3adb251dc75de414bbc9b3499220dfe593c0abff777.webp&#34; alt=&#34;勾选同意数据政策并创建应用&#34; decoding=&#34;async&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/01a49b478f3b483adb4d7ce4ca05a48a229302970ff8546ed3b423e7ddd9be42.webp 720w, https://mizore.blog/images/1f8b2061017b9ec81fdbf3adb251dc75de414bbc9b3499220dfe593c0abff777.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;3-添加-drive-权限范围scope&#34;&gt;3. 添加 Drive 权限范围（Scope）&lt;/h3&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;在左侧菜单打开 &lt;strong&gt;Data Access（数据访问）&lt;/strong&gt;，点击 &lt;strong&gt;Add or remove scopes（添加或移除范围）&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;在弹出的面板中勾选 &lt;code&gt;.../auth/drive&lt;/code&gt;（说明为“查看、修改、创建和删除您的所有 Google 云端硬盘文件”）。如果列表里找不到，直接把下面的地址粘贴到 &lt;strong&gt;Manually add scopes（手动添加范围）&lt;/strong&gt; 中，点击 &lt;strong&gt;Add to table（添加到表）&lt;/strong&gt;，再点击 &lt;strong&gt;Update（更新）&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;https://www.googleapis.com/auth/drive&lt;/code&gt;&lt;/pre&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figure&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/9ec301365079ddf7b5f38239fec7f012ca291d9dc2f5410cd4a1a55205dd653c.webp&#34; alt=&#34;添加并勾选 auth/drive 权限范围&#34; decoding=&#34;async&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/32a60fdf56b7adacce5b4c410c766404bca94ccb029865c830ddb93df091188a.webp 720w, https://mizore.blog/images/9ec301365079ddf7b5f38239fec7f012ca291d9dc2f5410cd4a1a55205dd653c.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：回到数据访问页面后，一定要点击底部的 &lt;strong&gt;Save（保存）&lt;/strong&gt;。只点上一步的 Update 并不会真正保存。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/6cc1c4e76ebd5f981820b1ea892b01e967e8abeef3454594081d06967ce8b71d.webp&#34; alt=&#34;点击页面底部的 Save 保存权限范围&#34; decoding=&#34;async&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/d921003eacdd0301478aa6a5ea63019d50bc9e7310d2528a9bf597665ce1de39.webp 720w, https://mizore.blog/images/6cc1c4e76ebd5f981820b1ea892b01e967e8abeef3454594081d06967ce8b71d.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;4-创建桌面客户端并下载-json&#34;&gt;4. 创建桌面客户端并下载 JSON&lt;/h3&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;在左侧菜单打开 &lt;strong&gt;Clients（客户端）&lt;/strong&gt;，点击 &lt;strong&gt;Create client（创建客户端）&lt;/strong&gt;。应用类型必须选择 &lt;strong&gt;Desktop app（桌面应用）&lt;/strong&gt;（网页客户端、API Key 和服务账号 JSON 都不能用），名称随意（如 &lt;code&gt;gdirect Desktop&lt;/code&gt;），点击 &lt;strong&gt;Create（创建）&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/7245cd5c4fb7c5fb6e2382604ec69cf7559c64ed80ad7c5ad560907940406c17.webp&#34; alt=&#34;创建 Desktop app 类型的 OAuth 客户端&#34; decoding=&#34;async&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/c2a71a1ebbdc0e129577a8aa2b4a6eeabcfaf85f0dec5be4cc58974d27d8175b.webp 720w, https://mizore.blog/images/7245cd5c4fb7c5fb6e2382604ec69cf7559c64ed80ad7c5ad560907940406c17.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;在弹出的窗口中，&lt;strong&gt;关闭之前&lt;/strong&gt;点击 &lt;strong&gt;Download JSON（下载 JSON）&lt;/strong&gt;。窗口关闭后就无法再次下载这个文件；如果不小心关掉了，重新创建一个客户端即可。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/6ef9a135c4a4fd44828da2070ef67b9953b0147a32b507f9e670179b8659ac1a.webp&#34; alt=&#34;下载客户端 JSON 文件&#34; decoding=&#34;async&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/485f1069d4436e8f7a5fac4f24ccbe6f5be31322ea4372f073b1cbf27d642e4f.webp 720w, https://mizore.blog/images/6ef9a135c4a4fd44828da2070ef67b9953b0147a32b507f9e670179b8659ac1a.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;5-允许你的账号登录解决-7-天过期问题&#34;&gt;5. 允许你的账号登录（解决 7 天过期问题）&lt;/h3&gt;&#xA;&lt;p&gt;新建的应用默认处于 &lt;strong&gt;Testing（测试）&lt;/strong&gt; 状态，只有测试用户列表中的账号可以登录：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;在左侧菜单打开 &lt;strong&gt;Audience（目标对象）&lt;/strong&gt;，在 &lt;strong&gt;Test users（测试用户）&lt;/strong&gt; 下点击 &lt;strong&gt;Add users&lt;/strong&gt;，填入你要在 &lt;code&gt;gdirect&lt;/code&gt; 中使用的 Google 账号邮箱，点击 &lt;strong&gt;Save（保存）&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/3dce73deeb5b0257abdb91f13eb51c98d55b87e301b0b905701731dec6dfeba3.webp&#34; alt=&#34;将自己的 Google 账号添加为测试用户&#34; decoding=&#34;async&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/599f697dd1e4de390d1093d6e2843b18679aea141211a155d72182222bb59be0.webp 720w, https://mizore.blog/images/3dce73deeb5b0257abdb91f13eb51c98d55b87e301b0b905701731dec6dfeba3.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;测试状态下，登录凭证会在 &lt;strong&gt;7 天&lt;/strong&gt;后失效，届时执行 &lt;code&gt;gdirect login --reauth&lt;/code&gt; 重新授权即可。&lt;/p&gt;&#xA;&lt;p&gt;如果不想每周重新登录，可以直接在 &lt;strong&gt;Audience（目标对象）&lt;/strong&gt; 页面顶部点击 &lt;strong&gt;Publish app（发布应用）&lt;/strong&gt;，把状态改为 &lt;strong&gt;In production（正式版）&lt;/strong&gt;。自己使用的应用不需要经过 Google 审核，切到正式版后就不会再有 7 天过期的限制。&lt;/p&gt;&#xA;&lt;h3 id=&#34;6-在终端登录&#34;&gt;6. 在终端登录&lt;/h3&gt;&#xA;&lt;p&gt;在终端执行下面的命令，把路径换成你下载的 JSON 文件：&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;gdirect login --client-secret ~/Downloads/client_secret.json&lt;/code&gt;&lt;/pre&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;span class=&#34;lang&#34;&gt;sh&lt;/span&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figure&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;程序会自动打开浏览器（如果没有自动打开，请把终端打印的链接复制到&lt;strong&gt;同一台电脑&lt;/strong&gt;的浏览器中打开，因为授权完成后要回调本机的临时端口）。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;选择你的 Google 账号，Google 会提示“此应用未经验证”——这是正常的，因为应用是你自己刚刚创建的：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;测试状态&lt;/strong&gt;：点击 &lt;strong&gt;Continue（继续）&lt;/strong&gt;，不要点“返回到安全网页”。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;正式版（In production）&lt;/strong&gt;：点击左下角 &lt;strong&gt;Advanced（高级）&lt;/strong&gt;，再点击 &lt;strong&gt;Go to 你的应用名 (unsafe)（转至……（不安全））&lt;/strong&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/16d0ca2ca0226f0282eaddc0e7523496d63c8f4f108b2c118226665f6e2a94f1.webp&#34; alt=&#34;在未验证应用提示页点击继续&#34; decoding=&#34;async&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/c75581419f26c9fc6343c43a9323ff4ff7227a6261dcf0245a0952b202eeb05e.webp 720w, https://mizore.blog/images/16d0ca2ca0226f0282eaddc0e7523496d63c8f4f108b2c118226665f6e2a94f1.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;核对应用名称和 Google 云端硬盘权限，确认无误后点击 &lt;strong&gt;Continue（继续）&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/2990236874548513a7d3cf18e351ff26d82f5fd485d9d39c70efcdfb38c2885d.webp&#34; alt=&#34;勾选并确认授权访问 Google 云端硬盘&#34; decoding=&#34;async&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;1197&#34; srcset=&#34;https://mizore.blog/images/451c0035c2dd1d54bd2d6fa769390ca0b25378206a7c71a3332a77e5d67675a1.webp 720w, https://mizore.blog/images/2990236874548513a7d3cf18e351ff26d82f5fd485d9d39c70efcdfb38c2885d.webp 1600w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;回到终端，看到 &lt;code&gt;Logged in&lt;/code&gt; 即大功告成。此时可以删掉下载的 JSON 文件（客户端信息已保存在本地 SQLite 中，以后切换账号或重登只需执行 &lt;code&gt;gdirect login --reauth&lt;/code&gt;）。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;无界面服务器（Headless）提示&lt;/strong&gt;：如果在没有浏览器的远程服务器上使用，可以先在桌面电脑上完成登录，或者用 &lt;code&gt;gdirect login --credentials 路径&lt;/code&gt; 导入已有的 &lt;code&gt;authorized_user&lt;/code&gt; 凭据 JSON；写脚本时也可以直接通过环境变量 &lt;code&gt;GDIRECT_ACCESS_TOKEN&lt;/code&gt; 传入访问令牌。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;大陆网络与代理配置指南&#34;&gt;大陆网络与代理配置指南&lt;/h2&gt;&#xA;&lt;p&gt;在大陆使用 &lt;code&gt;gdirect&lt;/code&gt; 时，最容易遇到的困惑是：&lt;strong&gt;我的翻墙软件（sing-box / Mihomo / Clash）明明已经用了 &lt;a href=&#34;https://github.com/DustinWin/ruleset_geodata&#34;&gt;DustinWin/ruleset_geodata&lt;/a&gt; 规则集，并且把 &lt;code&gt;google-cn&lt;/code&gt; 设为了 &lt;code&gt;DIRECT&lt;/code&gt; 直连，为什么跑 &lt;code&gt;gdirect&lt;/code&gt; 下载时流量还是有可能走代理？&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;仔细算一笔账翻墙软件是怎么把直连流量截胡的&#34;&gt;仔细算一笔账：翻墙软件是怎么把直连流量截胡的？&lt;/h3&gt;&#xA;&lt;p&gt;虽然 &lt;code&gt;gdirect&lt;/code&gt; 在应用层禁用了 HTTP/SOCKS 代理环境变量（&lt;code&gt;no_proxy&lt;/code&gt;），但当你开着翻墙软件（尤其是 &lt;strong&gt;TUN 虚拟网卡模式&lt;/strong&gt;）时，所有流量都会先经过翻墙软件的路由引擎。我们顺着翻墙软件的匹配逻辑看一遍：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;我们借的是 &lt;code&gt;google-cn&lt;/code&gt; 的 IP，但发的是 Google Drive 的域名&lt;/strong&gt;：&#xA;&lt;code&gt;gdirect&lt;/code&gt; 在底层把连接目标 IP 换成了国内 &lt;code&gt;google-cn&lt;/code&gt; 节点（比如 &lt;code&gt;183.56.143.147&lt;/code&gt;），但为了让 Google 边缘节点正确路由并通过本机 TLS 证书校验，它在 TLS 握手（&lt;code&gt;ClientHello&lt;/code&gt; 的 SNI）和 HTTP &lt;code&gt;Host&lt;/code&gt; 头里发出的依然是 &lt;code&gt;www.googleapis.com&lt;/code&gt;、&lt;code&gt;drive.usercontent.google.com&lt;/code&gt; 和 &lt;code&gt;*.googleusercontent.com&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;云盘的这些域名，并不在 &lt;code&gt;google-cn&lt;/code&gt; 规则集里！&lt;/strong&gt;&#xA;翻开 &lt;a href=&#34;https://github.com/DustinWin/ruleset_geodata&#34;&gt;DustinWin/ruleset_geodata&lt;/a&gt; 的 &lt;code&gt;google-cn&lt;/code&gt; 规则集可以看到，里面收录的是 &lt;code&gt;dl.google.com&lt;/code&gt;、&lt;code&gt;g.cn&lt;/code&gt;、&lt;code&gt;gstatic.cn&lt;/code&gt;、&lt;code&gt;fonts.googleapis.com&lt;/code&gt;、&lt;code&gt;clientservices.googleapis.com&lt;/code&gt; 等国内本来就能直连的域名；而 &lt;strong&gt;&lt;code&gt;www.googleapis.com&lt;/code&gt;、&lt;code&gt;drive.usercontent.google.com&lt;/code&gt;、&lt;code&gt;drive.google.com&lt;/code&gt;、&lt;code&gt;*.googleusercontent.com&lt;/code&gt; 并不在 &lt;code&gt;google-cn&lt;/code&gt; 里，它们全部躺在需要翻墙的 &lt;code&gt;google&lt;/code&gt; 规则集里&lt;/strong&gt;。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;TUN 流量嗅探（&lt;code&gt;sniff&lt;/code&gt;）触发按域名截胡&lt;/strong&gt;：&#xA;当 &lt;code&gt;gdirect&lt;/code&gt; 向国内 IP &lt;code&gt;183.56.143.147:443&lt;/code&gt; 发起连接时，TUN 网卡拦下数据包，通过 &lt;code&gt;sniff&lt;/code&gt;（TLS 域名嗅探）读出里面的 SNI 是 &lt;code&gt;drive.usercontent.google.com&lt;/code&gt; 或 &lt;code&gt;www.googleapis.com&lt;/code&gt;。随后翻墙软件拿着这个嗅探出来的域名从上往下走规则：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;经过 &lt;code&gt;rule_set: google-cn&lt;/code&gt;（直连）&lt;span class=&#34;math-inline&#34;&gt;&lt;math display=&#34;inline&#34; style=&#34;font-feature-settings: &#39;dtls&#39; off;&#34; xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo&gt;→&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\rightarrow&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt; &lt;strong&gt;未命中&lt;/strong&gt;（因为域名是云盘域名，不在 &lt;code&gt;google-cn&lt;/code&gt; 中）；&lt;/li&gt;&#xA;&lt;li&gt;走到 &lt;code&gt;rule_set: google&lt;/code&gt;（代理）&lt;span class=&#34;math-inline&#34;&gt;&lt;math display=&#34;inline&#34; style=&#34;font-feature-settings: &#39;dtls&#39; off;&#34; xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo&gt;→&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\rightarrow&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt; &lt;strong&gt;直接命中！&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;在主流规则模板里，域名规则优先于 IP 规则（或者当 &lt;code&gt;scan&lt;/code&gt; 尝试用系统 DNS 解析上述云盘域名时，直接在 DNS 层命中 &lt;code&gt;google&lt;/code&gt; 规则被远端代理解析或返回 &lt;code&gt;198.18.x.x&lt;/code&gt; 的 Fake-IP）。结果就是：&lt;strong&gt;这股明明发往国内电信 IP 的连接，因为身上带着 &lt;code&gt;google&lt;/code&gt; 规则集里的域名，被翻墙软件当成翻墙流量重新塞进了代理节点！&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;理清了这个逻辑，大陆用户只需要在下面两种做法中&lt;strong&gt;二选一&lt;/strong&gt;即可彻底解决：&lt;/p&gt;&#xA;&lt;h3 id=&#34;做法一登录好之后scan-和下载时退出翻墙软件或关闭-tun&#34;&gt;做法一：登录好之后，&lt;code&gt;scan&lt;/code&gt; 和下载时退出翻墙软件（或关闭 TUN）&lt;/h3&gt;&#xA;&lt;p&gt;如果你不想改动翻墙软件现有的分流规则，这是最简单、也最干净的做法：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;登录阶段（开着翻墙软件）&lt;/strong&gt;：保持翻墙软件正常开启，执行 &lt;code&gt;gdirect login --client-secret ...&lt;/code&gt; 完成授权（因为浏览器打开 &lt;code&gt;accounts.google.com&lt;/code&gt; 以及终端向 &lt;code&gt;oauth2.googleapis.com&lt;/code&gt; 换取/刷新令牌必须走代理）。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;扫描与下载阶段（退出翻墙软件，或关闭 TUN 模式）&lt;/strong&gt;：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;登录完成后（本地已拿到有效期约 1 小时的短期 &lt;code&gt;access_token&lt;/code&gt;），&lt;strong&gt;直接退出翻墙软件，或者关掉翻墙软件的 TUN 模式&lt;/strong&gt;（如果只开系统代理 System Proxy、不开 TUN，&lt;code&gt;gdirect&lt;/code&gt; 自身会忽略系统代理环境变量直接连出去，但关掉 TUN 最稳妥）。&lt;/li&gt;&#xA;&lt;li&gt;执行 &lt;code&gt;gdirect scan&lt;/code&gt; 扫出当前纯物理宽带下最快的国内 &lt;code&gt;google-cn&lt;/code&gt; 节点 IP。&lt;/li&gt;&#xA;&lt;li&gt;执行 &lt;code&gt;gdirect download ...&lt;/code&gt; 或 &lt;code&gt;gdirect browse&lt;/code&gt; 开始满速直连下载（若遇到宽带运营商对明文 SNI 重置连接，加上 &lt;code&gt;--no-sni&lt;/code&gt; 即可）：&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;gdirect scan&#xA;gdirect download FILE_ID&#xA;&lt;span class=&#34;c1&#34;&gt;# 若遇到连接或 TLS 重置（libaria2 code 1），去掉 SNI 即可：&lt;/span&gt;&#xA;gdirect download FILE_ID --no-sni&lt;/code&gt;&lt;/pre&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;span class=&#34;lang&#34;&gt;sh&lt;/span&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figure&gt;&#xA;&lt;h3 id=&#34;做法二在翻墙软件中配置规则把云盘域名或-gdirect-进程也并入直连&#34;&gt;做法二：在翻墙软件中配置规则，把云盘域名（或 &lt;code&gt;gdirect&lt;/code&gt; 进程）也并入直连&lt;/h3&gt;&#xA;&lt;p&gt;如果你希望翻墙软件和 TUN 模式 24 小时常开，思路其实很简单：&lt;strong&gt;既然翻墙软件是因为嗅探到了云盘域名才把流量分给 &lt;code&gt;google&lt;/code&gt;（代理）的，那我们就在 &lt;code&gt;google&lt;/code&gt; 规则之前，手动把这几个云盘域名（或 &lt;code&gt;gdirect&lt;/code&gt; 进程本身）也像 &lt;code&gt;google-cn&lt;/code&gt; 一样指派给 &lt;code&gt;DIRECT&lt;/code&gt; 直连&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;保留走代理（&lt;code&gt;PROXY&lt;/code&gt;）的域名&lt;/strong&gt;（仅用于登录和令牌自动刷新）：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;accounts.google.com&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;oauth2.googleapis.com&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;需要放行直连（&lt;code&gt;DIRECT&lt;/code&gt;）并关闭 &lt;code&gt;fake-ip&lt;/code&gt; 的传输域名&lt;/strong&gt;：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;drive.usercontent.google.com&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;*.googleusercontent.com&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;drive.google.com&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;www.googleapis.com&lt;/code&gt;、&lt;code&gt;drive.googleapis.com&lt;/code&gt;、&lt;code&gt;content.googleapis.com&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：普通浏览器访问 &lt;code&gt;www.googleapis.com&lt;/code&gt; 时并不会像 &lt;code&gt;gdirect&lt;/code&gt; 这样把底层 IP 替换成 &lt;code&gt;google-cn&lt;/code&gt; 节点，所以如果你把 &lt;code&gt;www.googleapis.com&lt;/code&gt; 全局设为 &lt;code&gt;DIRECT&lt;/code&gt;，可能会影响浏览器里依赖该域名的其他网页。因此在桌面端最推荐配合 &lt;strong&gt;&lt;code&gt;PROCESS-NAME&lt;/code&gt;（进程名分流）&lt;/strong&gt;，或者只对 &lt;code&gt;gdirect&lt;/code&gt; 进程放行直连。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;以 &lt;strong&gt;Mihomo（Clash）&lt;/strong&gt; 为例，在配置文件的 &lt;code&gt;GEOSITE,google&lt;/code&gt; 规则之前加入：&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;nt&#34;&gt;dns&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;  &lt;span class=&#34;nt&#34;&gt;fake-ip-filter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;    - &lt;span class=&#34;s2&#34;&gt;&amp;#34;www.googleapis.com&amp;#34;&lt;/span&gt;&#xA;    - &lt;span class=&#34;s2&#34;&gt;&amp;#34;drive.googleapis.com&amp;#34;&lt;/span&gt;&#xA;    - &lt;span class=&#34;s2&#34;&gt;&amp;#34;content.googleapis.com&amp;#34;&lt;/span&gt;&#xA;    - &lt;span class=&#34;s2&#34;&gt;&amp;#34;drive.usercontent.google.com&amp;#34;&lt;/span&gt;&#xA;    - &lt;span class=&#34;s2&#34;&gt;&amp;#34;drive.google.com&amp;#34;&lt;/span&gt;&#xA;    - &lt;span class=&#34;s2&#34;&gt;&amp;#34;+.googleusercontent.com&amp;#34;&lt;/span&gt;&#xA;&#xA;&lt;span class=&#34;nt&#34;&gt;rules&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;  &lt;span class=&#34;c&#34;&gt;# 1. 登录与刷新令牌走代理&lt;/span&gt;&#xA;  - &lt;span class=&#34;l&#34;&gt;DOMAIN,accounts.google.com,PROXY&lt;/span&gt;&#xA;  - &lt;span class=&#34;l&#34;&gt;DOMAIN,oauth2.googleapis.com,PROXY&lt;/span&gt;&#xA;&#xA;  &lt;span class=&#34;c&#34;&gt;# 2. 推荐：让 gdirect 进程的其余所有流量（扫描、API、下载、上传）强制直连，不被 sniff 域名规则截胡&lt;/span&gt;&#xA;  - &lt;span class=&#34;l&#34;&gt;PROCESS-NAME,gdirect,DIRECT&lt;/span&gt;&#xA;&#xA;  &lt;span class=&#34;c&#34;&gt;# 3. 或者按具体域名直连（放在 GEOSITE,google / RULE-SET,google 之前）：&lt;/span&gt;&#xA;  - &lt;span class=&#34;l&#34;&gt;DOMAIN,drive.usercontent.google.com,DIRECT&lt;/span&gt;&#xA;  - &lt;span class=&#34;l&#34;&gt;DOMAIN-SUFFIX,googleusercontent.com,DIRECT&lt;/span&gt;&#xA;  - &lt;span class=&#34;l&#34;&gt;DOMAIN,www.googleapis.com,DIRECT&lt;/span&gt;&#xA;  - &lt;span class=&#34;l&#34;&gt;DOMAIN,drive.googleapis.com,DIRECT&lt;/span&gt;&#xA;  - &lt;span class=&#34;l&#34;&gt;DOMAIN,content.googleapis.com,DIRECT&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;span class=&#34;lang&#34;&gt;yaml&lt;/span&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figure&gt;&#xA;&lt;p&gt;如果你用的是 &lt;strong&gt;sing-box&lt;/strong&gt;，同样可以在 &lt;code&gt;route.rules&lt;/code&gt; 的 &lt;code&gt;sniff&lt;/code&gt; 或 &lt;code&gt;rule_set: [&amp;quot;google&amp;quot;]&lt;/code&gt; 之前加上一条按进程或域名的直连规则（保留 &lt;code&gt;oauth2.googleapis.com&lt;/code&gt; 走代理），就能在 TUN 常开的情况下随时免流跑满云盘下载。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;日常使用与排障速查&#34;&gt;日常使用与排障速查&lt;/h2&gt;&#xA;&lt;h3 id=&#34;常用命令&#34;&gt;常用命令&lt;/h3&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;gdirect                                   &lt;span class=&#34;c1&#34;&gt;# 等同于 gdirect browse，打开交互式 TUI&lt;/span&gt;&#xA;gdirect browse -o ~/Downloads             &lt;span class=&#34;c1&#34;&gt;# 指定下载目录（默认 ./downloads）&lt;/span&gt;&#xA;gdirect ls root --json                    &lt;span class=&#34;c1&#34;&gt;# 列出目录内容（支持 JSON 输出）&lt;/span&gt;&#xA;gdirect download 文件ID或分享链接          &lt;span class=&#34;c1&#34;&gt;# 下载文件，断流重跑同一命令即可续传&lt;/span&gt;&#xA;gdirect download FILE_ID -o file.zip&#xA;gdirect download DOC_ID --export application/pdf -o doc.pdf&#xA;gdirect upload ./file.zip                 &lt;span class=&#34;c1&#34;&gt;# 32 MiB 分块上传，支持断点续传&lt;/span&gt;&#xA;gdirect upload ./file.zip --parent FOLDER_ID --name backup.zip&#xA;gdirect mount                             &lt;span class=&#34;c1&#34;&gt;# 挂载到 ~/GoogleDrive（macOS 走原生 NFS 免 FUSE）&lt;/span&gt;&#xA;gdirect unmount                           &lt;span class=&#34;c1&#34;&gt;# 卸载并清理临时导出的凭据&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;span class=&#34;lang&#34;&gt;sh&lt;/span&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figure&gt;&#xA;&lt;p&gt;在 &lt;code&gt;gdirect browse&lt;/code&gt; 界面中，可以用键盘完成几乎所有日常操作：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;按键&lt;/th&gt;&#xA;&lt;th&gt;操作&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;j / k&lt;/code&gt;、&lt;code&gt;↑ / ↓&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;上下选择文件或文件夹&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;Enter&lt;/code&gt;、&lt;code&gt;l / →&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;进入文件夹&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;h / ←&lt;/code&gt;、&lt;code&gt;Backspace&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;返回上级目录&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;加入下载队列（Google 文档、表格、幻灯片自动导出为 PDF）&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;+ / -&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;调整后续下载的并发连接数（&lt;code&gt;1–16&lt;/code&gt;），并保存为默认值（初始为 8）&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;r&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;刷新当前目录&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;c&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;取消当前下载并保留已下载部分&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;q&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;退出&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;如果遇到特殊网络环境，&lt;code&gt;download&lt;/code&gt; 和 &lt;code&gt;browse&lt;/code&gt; 还支持手动干预传输层参数：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;--ip IP&lt;/code&gt;：强制使用指定的 Google IP，跳过自动扫描结果；&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;--sni HOST&lt;/code&gt; / &lt;code&gt;--no-sni&lt;/code&gt;：改写 TLS SNI 主机名，或完全不发送 SNI（证书仍按真实 Google 主机名校验）；&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;-j N&lt;/code&gt;：仅对当前命令设置并发连接数（&lt;code&gt;1–16&lt;/code&gt;）；&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;gdirect scan --ips FILE&lt;/code&gt;：探测自定义候选 IP 列表（每行一个公网 IP，&lt;code&gt;#&lt;/code&gt; 后为注释）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;常见报错速查表&#34;&gt;常见报错速查表&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;现象&lt;/th&gt;&#xA;&lt;th&gt;处理方法&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;access_denied&lt;/code&gt; 或提示应用正在测试&lt;/td&gt;&#xA;&lt;td&gt;把当前账号加为测试用户，或发布应用（见配置指南第 5 步）&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;redirect_uri_mismatch&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;下载的 JSON 不是 &lt;strong&gt;Desktop app&lt;/strong&gt; 客户端，按第 4 步重新创建&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;Drive API 403 … insufficient permission&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;在客户端所在的&lt;strong&gt;同一个项目&lt;/strong&gt;中启用 Drive API（第 1 步），并保存 Drive 权限范围（第 3 步底部点 Save）；然后执行 &lt;code&gt;gdirect login --reauth&lt;/code&gt;&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;refresh credential expired or revoked&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;执行 &lt;code&gt;gdirect login --reauth&lt;/code&gt;。如果每 7 天出现一次，把应用从测试状态发布为正式版（第 5 步）&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;技术解析底层是怎么实现的&#34;&gt;技术解析：底层是怎么实现的？&lt;/h2&gt;&#xA;&lt;p&gt;如果你对 &lt;code&gt;gdirect&lt;/code&gt; 内部是如何用 Rust 配合定制 &lt;code&gt;libaria2&lt;/code&gt; 实现上述机制感兴趣，下面结合仓库源码逐一拆解。&lt;/p&gt;&#xA;&lt;h3 id=&#34;1-候选-ip-扫描与双端点校验&#34;&gt;1. 候选 IP 扫描与双端点校验&lt;/h3&gt;&#xA;&lt;p&gt;第一次运行传输类命令时，如果本地 SQLite 里没有 24 小时内验证过的可用 IP，&lt;code&gt;src/scan.rs&lt;/code&gt; 就会启动扫描。候选池由三部分汇总（上限 256 个）：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;内置引导种子 IP &lt;code&gt;183.56.143.147&lt;/code&gt;（来自 Cealing-Host，每次都会重新探测，从不盲目采信）；&lt;/li&gt;&#xA;&lt;li&gt;系统 DNS 对 &lt;code&gt;www.googleapis.com&lt;/code&gt;、&lt;code&gt;drive.usercontent.google.com&lt;/code&gt;、&lt;code&gt;drive.google.com&lt;/code&gt; 解析出的公网 IP；&lt;/li&gt;&#xA;&lt;li&gt;SQLite 中过去 7 天内曾经成功连通的历史 IP（超过 7 天未成功的僵死记录会在扫描前自动清理，避免候选池无限膨胀）。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;在进入探测池前，&lt;code&gt;public(ip)&lt;/code&gt; 会先把私网、环回、链路本地地址以及 &lt;code&gt;198.18.0.0/15&lt;/code&gt;（Clash / Mihomo &lt;code&gt;fake-ip&lt;/code&gt; 默认网段）全部过滤掉。随后启动 8 个工作线程并发执行 &lt;code&gt;probe(ip)&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;src/scan.rs&lt;/b&gt;&lt;span&gt;对单个候选 IP 进行 TLS 与双端点探测&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/gdirect/blob/f4b67efb3f96ba061e89ac956b0b0335a481b007/src/scan.rs#L78-L113&#34;&gt;L78–113 · f4b67ef ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;78&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;fn&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;probe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ip&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;IpAddr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; -&amp;gt; &lt;span class=&#34;nb&#34;&gt;Result&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;i64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Option&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;79&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;client&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Client&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;builder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;80&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;no_proxy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;81&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;redirect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;reqwest&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;redirect&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;Policy&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;none&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;82&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;connect_timeout&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Duration&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;from_secs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;83&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;timeout&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Duration&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;from_secs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;84&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;resolve&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;www.googleapis.com&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SocketAddr&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;443&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;85&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;resolve&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;drive.usercontent.google.com&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SocketAddr&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;443&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;86&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;build&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;87&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;start&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Instant&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;now&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;88&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;client&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;89&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;https://www.googleapis.com/drive/v3/about?fields=user&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;90&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;send&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;91&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map_err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;without_url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;92&lt;/span&gt;    &lt;span class=&#34;fm&#34;&gt;ensure!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;93&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;status&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;reqwest&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;StatusCode&lt;/span&gt;::&lt;span class=&#34;no&#34;&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;94&lt;/span&gt;        &lt;span class=&#34;s&#34;&gt;&amp;#34;unexpected Drive API status {}&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;95&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;status&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;96&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;97&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;serde_json&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;Value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;98&lt;/span&gt;    &lt;span class=&#34;fm&#34;&gt;ensure!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;][&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;401&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;invalid Drive API response&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;99&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;client&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;https://drive.usercontent.google.com/download?id=0B9P1L--7Wd2vU3VUVlFnbTgtS2c&amp;amp;export=download&amp;amp;confirm=t&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;send&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map_err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;without_url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;100&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;status&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;status&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;101&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;warning&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;fm&#34;&gt;matches!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;102&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;status&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;103&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;reqwest&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;StatusCode&lt;/span&gt;::&lt;span class=&#34;no&#34;&gt;FORBIDDEN&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;reqwest&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;StatusCode&lt;/span&gt;::&lt;span class=&#34;no&#34;&gt;NOT_FOUND&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;104&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;105&lt;/span&gt;        &lt;span class=&#34;nb&#34;&gt;Some&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;fm&#34;&gt;format!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;HTTP &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{status}&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;106&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;107&lt;/span&gt;        &lt;span class=&#34;fm&#34;&gt;ensure!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;status&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;is_success&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Drive download probe HTTP {status}&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;108&lt;/span&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;bytes&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Vec&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;109&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;take&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read_to_end&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;bytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;110&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bytes&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;sa&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;spam&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\n&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;then&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;unexpected content&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;into&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;111&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;112&lt;/span&gt;    &lt;span class=&#34;nb&#34;&gt;Ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;start&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;elapsed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_millis&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;i64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;warning&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;113&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;p&gt;这段探测逻辑有三个关键设计：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;.no_proxy()&lt;/code&gt; + &lt;code&gt;.resolve()&lt;/code&gt;&lt;/strong&gt;：强制绕过环境变量代理，在客户端内部把 &lt;code&gt;www.googleapis.com&lt;/code&gt; 和 &lt;code&gt;drive.usercontent.google.com&lt;/code&gt; 直接绑定到待测 &lt;code&gt;ip:443&lt;/code&gt;，同时走完整的 TLS 证书校验（验证证书链及 SAN 域名匹配，杜绝连上运营商拦截页或中间人）。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;无凭证 API 校验&lt;/strong&gt;：不携带任何用户 Token 请求 &lt;code&gt;GET /drive/v3/about?fields=user&lt;/code&gt;。真正的 Google Drive API 节点一定会返回 HTTP &lt;code&gt;401 Unauthorized&lt;/code&gt; 且 JSON &lt;code&gt;error.code == 401&lt;/code&gt;。这样既验证了应用层路由，又不会在探测未知 IP 时泄露用户的 Bearer Token。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;下载链路探针&lt;/strong&gt;：接着向 &lt;code&gt;drive.usercontent.google.com&lt;/code&gt; 请求一个公开的小测试文件。如果测试文件本身因 Google 限流返回 403/404，只要前一步 Drive API 校验通过，该 IP 依然会被标记为可用并打印警告，避免因为公开测试文件失效而把正常 IP 全部误杀。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;如何自己寻找更多候选直连 IP？&lt;/strong&gt;&#xA;如果内置种子 IP &lt;code&gt;183.56.143.147&lt;/code&gt; 在你所在地区的运营商网络下延迟较高，你可以从 &lt;a href=&#34;https://github.com/DustinWin/ruleset_geodata&#34;&gt;DustinWin/ruleset_geodata&lt;/a&gt; 的 &lt;code&gt;google-cn&lt;/code&gt; 列表里挑几个域名（如 &lt;code&gt;dl.google.com&lt;/code&gt;、&lt;code&gt;g.cn&lt;/code&gt;、&lt;code&gt;fonts.googleapis.com&lt;/code&gt;、&lt;code&gt;gstatic.cn&lt;/code&gt;），用国内 DNS（如 &lt;code&gt;dig @223.5.5.5 dl.google.com +short&lt;/code&gt;）解析出一批你当地运营商的 Google 边缘 IP，每行一个存成 &lt;code&gt;ips.txt&lt;/code&gt;，再执行 &lt;code&gt;gdirect scan --ips ips.txt&lt;/code&gt;。探测仪会自动帮你筛出其中持有合法证书且能跑通 Drive API 的最快节点。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h3 id=&#34;2-定制-libaria2为什么必须给-c-源码打补丁&#34;&gt;2. 定制 libaria2：为什么必须给 C++ 源码打补丁&lt;/h3&gt;&#xA;&lt;p&gt;对于 &lt;code&gt;ls&lt;/code&gt; 和 &lt;code&gt;upload&lt;/code&gt;，Rust 层的 &lt;code&gt;reqwest&lt;/code&gt; 配合 &lt;code&gt;.resolve()&lt;/code&gt; 就够用了；但下载大文件需要多连接分段并发和断点续传，&lt;code&gt;gdirect&lt;/code&gt; 选择把 &lt;code&gt;aria2 1.37.0&lt;/code&gt; 裁剪成纯 HTTP/HTTPS + OpenSSL 3 的静态库 &lt;code&gt;libaria2&lt;/code&gt; 嵌进二进制。&lt;/p&gt;&#xA;&lt;p&gt;问题在于：&lt;strong&gt;Google Drive 的下载请求经常会发生跨域名 302 重定向&lt;/strong&gt;（例如从 &lt;code&gt;www.googleapis.com&lt;/code&gt; 或 &lt;code&gt;drive.google.com&lt;/code&gt; 重定向到 &lt;code&gt;drive.usercontent.google.com&lt;/code&gt; 乃至动态生成的 &lt;code&gt;*.googleusercontent.com&lt;/code&gt;）。在原版 &lt;code&gt;aria2&lt;/code&gt; 里，如果你只是修改初始 URL，一旦跟随 302 跳转到新域名，&lt;code&gt;aria2&lt;/code&gt; 又会去查系统 DNS；而如果你把 URL 里的域名硬改成 IP，TLS 证书校验又会立刻因为主机名不匹配而失败。&lt;/p&gt;&#xA;&lt;p&gt;为此，&lt;code&gt;gdirect&lt;/code&gt; 在构建 &lt;code&gt;libaria2&lt;/code&gt; 时打了一个不到 90 行的补丁（&lt;code&gt;vendor/aria2-1.37.0.patch&lt;/code&gt; 配合 &lt;code&gt;vendor/gdirect_route.h&lt;/code&gt;），在 C++ 底层做了四处改动：&lt;/p&gt;&#xA;&lt;h4 id=&#34;1google-drive-传输域名白名单&#34;&gt;（1）Google Drive 传输域名白名单&lt;/h4&gt;&#xA;&lt;p&gt;先在 &lt;code&gt;vendor/gdirect_route.h&lt;/code&gt; 中收拢所有受控的 Google Drive 域名：&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;vendor/gdirect_route.h&lt;/b&gt;&lt;span&gt;Google Drive 域名白名单&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/gdirect/blob/f4b67efb3f96ba061e89ac956b0b0335a481b007/vendor/gdirect_route.h#L1-L12&#34;&gt;L1–12 · f4b67ef ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// gdirect&amp;#39;s narrow routing hook for the pinned aria2 1.37.0 build.&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;#pragma once&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;#include&lt;/span&gt; &lt;span class=&#34;cpf&#34;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;extern&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;gdirect_ip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;extern&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;gdirect_sni&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;inline&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;gdirect_host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;std&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;h&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;7&lt;/span&gt;  &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;std&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;suffix&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;.googleusercontent.com&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;8&lt;/span&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;h&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;drive.google.com&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;h&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;drive.usercontent.google.com&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;9&lt;/span&gt;         &lt;span class=&#34;n&#34;&gt;h&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;www.googleapis.com&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;h&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;content.googleapis.com&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;10&lt;/span&gt;         &lt;span class=&#34;n&#34;&gt;h&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;drive.googleapis.com&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;11&lt;/span&gt;         &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;h&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;suffix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;h&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;compare&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;h&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;suffix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;suffix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;suffix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;12&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;h4 id=&#34;2在-socketcorecc-拦截-getaddrinfo-与-tls-sni&#34;&gt;（2）在 &lt;code&gt;SocketCore.cc&lt;/code&gt; 拦截 &lt;code&gt;getaddrinfo&lt;/code&gt; 与 TLS SNI&lt;/h4&gt;&#xA;&lt;p&gt;上层 HTTP 请求保持原始 URL 不变（因此 HTTP &lt;code&gt;Host&lt;/code&gt; 头与 OpenSSL 证书校验使用的目标域名完全不受影响）。只有当 &lt;code&gt;SocketCore&lt;/code&gt; 真正调用 &lt;code&gt;getaddrinfo&lt;/code&gt; 建立连接时，才把命中 &lt;code&gt;gdirect_host(host)&lt;/code&gt; 的主机名替换成扫描出的 &lt;code&gt;gdirect_ip&lt;/code&gt;，并加上 &lt;code&gt;AI_NUMERICHOST&lt;/code&gt; 彻底跳过 DNS 解析。同一处还接管了 &lt;code&gt;tlsSession_-&amp;gt;setSNIHostname()&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;vendor/aria2-1.37.0.patch&lt;/b&gt;&lt;span&gt;在 SocketCore.cc 中接管 TLS SNI 与 getaddrinfo&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/gdirect/blob/f4b67efb3f96ba061e89ac956b0b0335a481b007/vendor/aria2-1.37.0.patch#L22-L42&#34;&gt;L22–42 · f4b67ef ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;22&lt;/span&gt;&lt;span class=&#34;gu&#34;&gt;@@ -936,7 +941,9 @@&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;23&lt;/span&gt;     // &amp;#34;localhost&amp;#34; will produce TLS alert with GNUTLS.&#xA;&lt;span class=&#34;ln&#34;&gt;24&lt;/span&gt;     if (tlsctx-&amp;gt;getSide() == TLS_CLIENT &amp;amp;&amp;amp; !util::isNumericHost(hostname) &amp;amp;&amp;amp;&#xA;&lt;span class=&#34;ln&#34;&gt;25&lt;/span&gt;         hostname.find(&amp;#34;.&amp;#34;) != std::string::npos) {&#xA;&lt;span class=&#34;ln&#34;&gt;26&lt;/span&gt;&lt;span class=&#34;gd&#34;&gt;-      rv = tlsSession_-&amp;gt;setSNIHostname(hostname);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;27&lt;/span&gt;&lt;span class=&#34;gi&#34;&gt;+      const char* sni = gdirect_host(hostname) ? gdirect_sni : nullptr;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;28&lt;/span&gt;&lt;span class=&#34;gi&#34;&gt;+      rv = (sni &amp;amp;&amp;amp; !*sni) ? TLS_ERR_OK :&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;29&lt;/span&gt;&lt;span class=&#34;gi&#34;&gt;+          tlsSession_-&amp;gt;setSNIHostname(sni ? std::string(sni) : hostname);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;30&lt;/span&gt;       if (rv != TLS_ERR_OK) {&#xA;&lt;span class=&#34;ln&#34;&gt;31&lt;/span&gt;         throw DL_ABORT_EX(fmt(EX_SSL_INIT_FAILURE,&#xA;&lt;span class=&#34;ln&#34;&gt;32&lt;/span&gt;                               tlsSession_-&amp;gt;getLastErrorString().c_str()));&#xA;&lt;span class=&#34;ln&#34;&gt;33&lt;/span&gt;&lt;span class=&#34;gu&#34;&gt;@@ -1450,6 +1457,10 @@&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;34&lt;/span&gt;   hints.ai_flags = getDefaultAIFlags();&#xA;&lt;span class=&#34;ln&#34;&gt;35&lt;/span&gt;   hints.ai_flags |= flags;&#xA;&lt;span class=&#34;ln&#34;&gt;36&lt;/span&gt;   hints.ai_protocol = protocol;&#xA;&lt;span class=&#34;ln&#34;&gt;37&lt;/span&gt;&lt;span class=&#34;gi&#34;&gt;+  if (host &amp;amp;&amp;amp; gdirect_ip &amp;amp;&amp;amp; *gdirect_ip &amp;amp;&amp;amp; gdirect_host(host)) {&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;38&lt;/span&gt;&lt;span class=&#34;gi&#34;&gt;+    host = gdirect_ip;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;39&lt;/span&gt;&lt;span class=&#34;gi&#34;&gt;+    hints.ai_flags |= AI_NUMERICHOST;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;40&lt;/span&gt;&lt;span class=&#34;gi&#34;&gt;+  }&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;41&lt;/span&gt;   return getaddrinfo(host, service, &amp;amp;hints, resPtr);&#xA;&lt;span class=&#34;ln&#34;&gt;42&lt;/span&gt; }&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;当 &lt;code&gt;gdirect_sni&lt;/code&gt; 为 &lt;code&gt;nullptr&lt;/code&gt;（默认）时，正常发送原域名作为 SNI；&lt;/li&gt;&#xA;&lt;li&gt;当传入 &lt;code&gt;--sni HOST&lt;/code&gt; 时，发送伪装的 SNI 主机名；&lt;/li&gt;&#xA;&lt;li&gt;当传入 &lt;code&gt;--no-sni&lt;/code&gt; 时（&lt;code&gt;gdirect_sni&lt;/code&gt; 为空字符串 &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt;），直接返回 &lt;code&gt;TLS_ERR_OK&lt;/code&gt; 跳过设置，让 OpenSSL 发出不带 SNI 扩展的 &lt;code&gt;ClientHello&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h4 id=&#34;3跨域重定向自动剥离-authorization-头与重定向白名单&#34;&gt;（3）跨域重定向自动剥离 &lt;code&gt;Authorization&lt;/code&gt; 头与重定向白名单&lt;/h4&gt;&#xA;&lt;p&gt;下载私有文件时，初始请求发往 &lt;code&gt;https://www.googleapis.com/drive/v3/files/{id}?alt=media&lt;/code&gt; 并携带 &lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt;。一旦 302 跳转到 &lt;code&gt;*.googleusercontent.com&lt;/code&gt; 的临时签名 URL，如果继续携带 &lt;code&gt;Authorization&lt;/code&gt; 头，Google 内容节点会因为同时收到两套认证信息而报错，也有跨域名泄露令牌的风险。因此补丁在 &lt;code&gt;HttpRequest.cc&lt;/code&gt; 中规定只有当 &lt;code&gt;getHost() == &amp;quot;www.googleapis.com&amp;quot;&lt;/code&gt; 时才输出 &lt;code&gt;Authorization:&lt;/code&gt; 头；并在 &lt;code&gt;Request.cc&lt;/code&gt; 中强制限制所有重定向目标都必须是 &lt;code&gt;https&lt;/code&gt; 且满足 &lt;code&gt;gdirect_host()&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;h4 id=&#34;4去掉-openssl-3-legacy-provider-依赖&#34;&gt;（4）去掉 OpenSSL 3 Legacy Provider 依赖&lt;/h4&gt;&#xA;&lt;p&gt;原版 &lt;code&gt;aria2&lt;/code&gt; 在 OpenSSL 3 下启动时会无条件调用 &lt;code&gt;OSSL_PROVIDER_load(nullptr, &amp;quot;legacy&amp;quot;)&lt;/code&gt;（为了 BitTorrent 加密里的 RC4）。在没有安装 &lt;code&gt;legacy&lt;/code&gt; 模块的系统上会导致启动直接崩溃。补丁用 &lt;code&gt;#ifdef ENABLE_BITTORRENT&lt;/code&gt; 把它围了起来，使纯 HTTP 静态构建不再依赖任何外部 OpenSSL 模块。&lt;/p&gt;&#xA;&lt;h3 id=&#34;3-下载防呆大文件病毒扫描确认页与-md5-校验&#34;&gt;3. 下载防呆、大文件病毒扫描确认页与 MD5 校验&lt;/h3&gt;&#xA;&lt;p&gt;在 Rust 侧的 &lt;code&gt;src/download.rs&lt;/code&gt; 中，围绕 &lt;code&gt;libaria2&lt;/code&gt; 还有三层保障：&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;src/download.rs&lt;/b&gt;&lt;span&gt;断点控制文件判定、HTML 确认页自动跳转与 MD5 校验&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/gdirect/blob/f4b67efb3f96ba061e89ac956b0b0335a481b007/src/download.rs#L191-L248&#34;&gt;L191–248 · f4b67ef ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;191&lt;/span&gt;    &lt;span class=&#34;c1&#34;&gt;// Never treat an unrelated existing file as a resumable download.&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;192&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;control&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;PathBuf&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;fm&#34;&gt;format!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{}&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;.aria2&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;output&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()));&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;193&lt;/span&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;output&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;exists&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;control&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;exists&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;194&lt;/span&gt;        &lt;span class=&#34;fm&#34;&gt;ensure!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;195&lt;/span&gt;            &lt;span class=&#34;n&#34;&gt;matches_md5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;output&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;md5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_deref&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;196&lt;/span&gt;            &lt;span class=&#34;s&#34;&gt;&amp;#34;output already exists; choose another -o path&amp;#34;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;197&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;198&lt;/span&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;progress&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;is_none&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;199&lt;/span&gt;            &lt;span class=&#34;fm&#34;&gt;println!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Already downloaded &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{}&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;output&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;());&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;200&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;201&lt;/span&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(());&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;202&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;203&lt;/span&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;progress&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;is_none&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;204&lt;/span&gt;        &lt;span class=&#34;fm&#34;&gt;eprintln!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;205&lt;/span&gt;            &lt;span class=&#34;s&#34;&gt;&amp;#34;libaria2 1.37.0 · IP=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{}&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; · SNI=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{}&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; · certificate verification ON&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;206&lt;/span&gt;            &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ip&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;207&lt;/span&gt;                &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ip&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;to_string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;208&lt;/span&gt;                &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;unwrap_or_else&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;system DNS&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;into&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()),&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;209&lt;/span&gt;            &lt;span class=&#34;k&#34;&gt;match&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sni&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_deref&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;210&lt;/span&gt;                &lt;span class=&#34;nb&#34;&gt;None&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;original&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;211&lt;/span&gt;                &lt;span class=&#34;nb&#34;&gt;Some&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;none&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;212&lt;/span&gt;                &lt;span class=&#34;nb&#34;&gt;Some&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sni&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sni&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;213&lt;/span&gt;            &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;214&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;215&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;216&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;progress&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;progress&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;217&lt;/span&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;attempt&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;..&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;218&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;download&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_deref&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;progress&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_deref_mut&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;219&lt;/span&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;prefix&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Vec&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;220&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;fs&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;File&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;output&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;221&lt;/span&gt;            &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;take&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1024&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1024&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;222&lt;/span&gt;            &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read_to_end&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;prefix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;223&lt;/span&gt;        &lt;span class=&#34;c1&#34;&gt;// Public downloads can return the virus-scan confirmation form with HTTP 200.&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;224&lt;/span&gt;        &lt;span class=&#34;c1&#34;&gt;// Only Drive&amp;#39;s own HTML pages are inspected; other files stay untouched.&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;225&lt;/span&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;is_none&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;html_page&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;prefix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;226&lt;/span&gt;            &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;text&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;from_utf8_lossy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;prefix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;227&lt;/span&gt;            &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;contains&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;download-form&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;228&lt;/span&gt;                &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;next&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;drive&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;confirmation&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;229&lt;/span&gt;                &lt;span class=&#34;fm&#34;&gt;ensure!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;attempt&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Drive confirmation loop&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;230&lt;/span&gt;                &lt;span class=&#34;n&#34;&gt;fs&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;remove_file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;output&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;231&lt;/span&gt;                &lt;span class=&#34;n&#34;&gt;url&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;232&lt;/span&gt;                &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;233&lt;/span&gt;            &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;234&lt;/span&gt;            &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;contains&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;accounts.google.com/ServiceLogin&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;235&lt;/span&gt;                &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;contains&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Google Drive - Quota exceeded&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;236&lt;/span&gt;                &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;contains&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Google Drive - Access denied&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;237&lt;/span&gt;            &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;238&lt;/span&gt;                &lt;span class=&#34;fm&#34;&gt;bail!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;239&lt;/span&gt;                    &lt;span class=&#34;s&#34;&gt;&amp;#34;Drive returned a login/quota error page; saved for inspection at {}&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;240&lt;/span&gt;                    &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;output&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;241&lt;/span&gt;                &lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;242&lt;/span&gt;            &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;243&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;244&lt;/span&gt;        &lt;span class=&#34;fm&#34;&gt;ensure!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;245&lt;/span&gt;            &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;md5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;is_none&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;matches_md5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;output&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;md5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_deref&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;246&lt;/span&gt;            &lt;span class=&#34;s&#34;&gt;&amp;#34;MD5 does not match Drive&amp;#39;s checksum; the file is kept at {} for inspection&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;247&lt;/span&gt;            &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;output&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;248&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;已存在文件的安全检查&lt;/strong&gt;：如果目标文件已存在且没有对应的 &lt;code&gt;.aria2&lt;/code&gt; 断点文件，先算一次本地 MD5 与云端 &lt;code&gt;md5Checksum&lt;/code&gt; 比对——匹配则直接提示 &lt;code&gt;Already downloaded&lt;/code&gt;，不匹配则拒绝覆盖。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;公开大文件的病毒扫描确认页拆解&lt;/strong&gt;：未登录状态下通过分享链接下载大文件时，Google Drive 会返回 HTTP 200 的 HTML 确认页（包含 &lt;code&gt;&amp;lt;form id=&amp;quot;download-form&amp;quot;&amp;gt;&lt;/code&gt;）。&lt;code&gt;gdirect&lt;/code&gt; 只在文件头部确实是 &lt;code&gt;&amp;lt;!doctype html&lt;/code&gt; 或 &lt;code&gt;&amp;lt;html&lt;/code&gt; 时解析该表单，拼出带 &lt;code&gt;confirm&lt;/code&gt; / &lt;code&gt;uuid&lt;/code&gt; 的真实下载地址自动重下，绝不会误伤内容里恰好带有 &lt;code&gt;download-form&lt;/code&gt; 字样的普通压缩包或源码文件。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;落盘后 MD5 校验&lt;/strong&gt;：下载完成后再次流式计算整个文件的 MD5 并与 Drive 元数据核对，确保多连接切片拼接毫无差错。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;4-32-mib-分块可恢复上传&#34;&gt;4. 32 MiB 分块可恢复上传&lt;/h3&gt;&#xA;&lt;p&gt;上传模块（&lt;code&gt;src/upload.rs&lt;/code&gt;）基于 Google Drive v3 的 Resumable Upload 协议。考虑到跨国直连链路的 RTT 较高，分块过小会导致大量时间浪费在等待每个块的确认上，因此将单次分块大小设为 &lt;code&gt;32 MiB&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;每次开始上传时，程序以 &lt;code&gt;(本地绝对路径, 目标父目录 ID, 云端文件名)&lt;/code&gt; 为键查询本地 SQLite：&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;src/upload.rs&lt;/b&gt;&lt;span&gt;向云端探测已有上传会话的已接收偏移量&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/gdirect/blob/f4b67efb3f96ba061e89ac956b0b0335a481b007/src/upload.rs#L85-L105&#34;&gt;L85–105 · f4b67ef ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;85&lt;/span&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;session&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;saved&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;size&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;size&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;modified&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;modified&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;86&lt;/span&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Some&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;session&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;87&lt;/span&gt;            &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;88&lt;/span&gt;                &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;client&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;89&lt;/span&gt;                &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;put&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;session_url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;90&lt;/span&gt;                &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bearer_auth&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;91&lt;/span&gt;                &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;header&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Content-Range&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;fm&#34;&gt;format!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;bytes */&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{size}&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;92&lt;/span&gt;                &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;header&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Content-Length&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;93&lt;/span&gt;                &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;send&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;94&lt;/span&gt;                &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map_err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;without_url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;95&lt;/span&gt;            &lt;span class=&#34;k&#34;&gt;match&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;status&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;96&lt;/span&gt;                &lt;span class=&#34;n&#34;&gt;StatusCode&lt;/span&gt;::&lt;span class=&#34;no&#34;&gt;OK&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;StatusCode&lt;/span&gt;::&lt;span class=&#34;no&#34;&gt;CREATED&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;97&lt;/span&gt;                    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;98&lt;/span&gt;                    &lt;span class=&#34;n&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;unset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;99&lt;/span&gt;                    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;100&lt;/span&gt;                &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;101&lt;/span&gt;                &lt;span class=&#34;n&#34;&gt;StatusCode&lt;/span&gt;::&lt;span class=&#34;no&#34;&gt;PERMANENT_REDIRECT&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;start&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;offset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;102&lt;/span&gt;                &lt;span class=&#34;n&#34;&gt;StatusCode&lt;/span&gt;::&lt;span class=&#34;no&#34;&gt;NOT_FOUND&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;StatusCode&lt;/span&gt;::&lt;span class=&#34;no&#34;&gt;GONE&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;session&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;103&lt;/span&gt;                &lt;span class=&#34;n&#34;&gt;_&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;failure&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)),&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;104&lt;/span&gt;            &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;105&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;p&gt;只要本地文件的大小和纳秒级修改时间没有变化，中断后重跑同一条命令就会先发送 &lt;code&gt;Content-Range: bytes */{size}&lt;/code&gt; 探测云端进度，从 &lt;code&gt;308 Permanent Redirect&lt;/code&gt; 返回的 &lt;code&gt;Range&lt;/code&gt; 头里拿到断点偏移量继续上传。&lt;/p&gt;&#xA;&lt;h3 id=&#34;5-本机-loopback--pkce-授权流&#34;&gt;5. 本机 Loopback + PKCE 授权流&lt;/h3&gt;&#xA;&lt;p&gt;在 &lt;code&gt;src/auth.rs&lt;/code&gt; 中，&lt;code&gt;gdirect login&lt;/code&gt; 实现了无需外部 SDK 的 &lt;strong&gt;OAuth 2.0 Loopback + PKCE（RFC 7636 &lt;code&gt;S256&lt;/code&gt;）&lt;/strong&gt; 流程：&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;src/auth.rs&lt;/b&gt;&lt;span&gt;本机随机端口监听与 PKCE 授权 URL 构造&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/gdirect/blob/f4b67efb3f96ba061e89ac956b0b0335a481b007/src/auth.rs#L194-L213&#34;&gt;L194–213 · f4b67ef ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;194&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;listener&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TcpListener&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;bind&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;127.0.0.1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;195&lt;/span&gt;    &lt;span class=&#34;n&#34;&gt;listener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;set_nonblocking&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;196&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;redirect&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;fm&#34;&gt;format!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;197&lt;/span&gt;        &lt;span class=&#34;s&#34;&gt;&amp;#34;http://127.0.0.1:&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{}&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;/callback&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;198&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;listener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;local_addr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;port&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;199&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;200&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;state&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;random_string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;201&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;verifier&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;random_string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;202&lt;/span&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;url&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;url&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;Url&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;parse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;https://accounts.google.com/o/oauth2/v2/auth&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;203&lt;/span&gt;    &lt;span class=&#34;n&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;query_pairs_mut&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;extend_pairs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;204&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;client_id&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;client_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;205&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;redirect_uri&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;redirect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()),&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;206&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;response_type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;207&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;SCOPE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;208&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;access_type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;offline&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;209&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;prompt&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;consent select_account&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;210&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;state&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()),&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;211&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;code_challenge&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;challenge&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;verifier&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()),&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;212&lt;/span&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;code_challenge_method&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;S256&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;213&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;]);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;p&gt;它在 &lt;code&gt;127.0.0.1:0&lt;/code&gt; 上开启一个临时的非阻塞 TCP 监听，校验浏览器回调的 &lt;code&gt;state&lt;/code&gt; 参数防 CSRF，再用授权码和 &lt;code&gt;code_verifier&lt;/code&gt; 向 &lt;code&gt;https://oauth2.googleapis.com/token&lt;/code&gt; 换取 &lt;code&gt;refresh_token&lt;/code&gt;。落盘的 SQLite 数据库和临时导出的 &lt;code&gt;rclone&lt;/code&gt; 凭据文件在创建时即强制设为 &lt;code&gt;0600&lt;/code&gt; 权限，&lt;code&gt;unmount&lt;/code&gt; 或挂载失败时也会自动清理导出的临时凭据文件。&lt;/p&gt;&#xA;</description>
      <category>技术</category>
      <pubDate>Sun, 25 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://mizore.blog/posts/gdirect/</guid>
    </item>
  </channel>
</rss>