# Cloudflare Pages 自定义响应头
# 文档：https://developers.cloudflare.com/pages/configuration/headers/
#
# 这个文件只负责安全头，不动缓存策略（静态资源的缓存交给 Cloudflare 自己的规则）。
# 之前站点只带了 nosniff 和 referrer-policy，审计扣了 4 项。

/*
  # 阻止浏览器猜测 MIME 类型，避免文本被当成脚本执行
  X-Content-Type-Options: nosniff

  # 不允许第三方站点把本站塞进 iframe（点击劫持）
  X-Frame-Options: SAMEORIGIN

  # 跨站跳转只带来源，不带完整 URL
  Referrer-Policy: strict-origin-when-cross-origin

  # 强制 HTTPS。站点整体托管在 Cloudflare，主域和子域都有证书，所以带上 includeSubDomains
  Strict-Transport-Security: max-age=31536000; includeSubDomains

  # 只关掉确实用不到的能力。
  # 注意：camera / microphone / geolocation 一律不能禁 —— 摄像头录制、录音、
  # 天气定位这几个工具真的要用，禁了就是直接把功能弄坏。
  Permissions-Policy: payment=(), usb=(), serial=(), xr-spatial-tracking=()

  # CSP 先上宽松版：
  #   - naive-ui 会写行内样式，style-src 必须留 'unsafe-inline'
  #   - ffmpeg.wasm / tesseract 这类用 WebAssembly，需要 'wasm-unsafe-eval'
  #   - cdn.jsdelivr.net 必须同时进 script-src 和 connect-src：
  #       · script-src：tesseract.js 的 Worker 用 importScripts 从 jsdelivr 加载 worker.min.js
  #         （importScripts 受 script-src 约束，不受 connect-src 约束）——OCR 工具依赖它；
  #         （抠图工具曾从这里加载 ONNX runtime，现已改为本站 /ai/ort/ 自托管。）
  #       · connect-src：上面那些 wasm / 模型 / 汇率等二进制与 JSON 的 fetch 走这里。
  #   - img-src 放 data:/blob: 是因为二维码、图片转换等工具的结果都是本地 blob/DataURL
  #   - connect-src 里额外放行的第三方域名，都是**没有同源首选、只能浏览器直连**的工具：
  #       · open.er-api.com / cdn.jsdelivr.net / api.frankfurter.dev —— 货币汇率换算
  #         （首选已改为同源的 /api/rates，这三个是 Functions 挂掉时的兜底）
  #       · www.schemastore.org —— JSON / YAML 查看器的 schema 自动校验
  #       · rdap.org —— Whois / RDAP 域名查询
  #       · 抠图(remove-background) 已不再需要第三方放行：模型权重与 ONNX runtime
  #         都由本站 /ai/ 路径提供，同源 fetch 不受 connect-src 约束。
  #     全部是只读的公开 JSON / 模型权重，实测响应头带 Access-Control-Allow-Origin: *。
  #       · api.globalping.io —— Ping 多节点测试：把目标交给公共探针网络执行 ICMP，
  #         没有同源替代（浏览器不可能自己发 ICMP，Pages Functions 也不支持），只能直连。
  #       · ipinfo.io —— Ping 工具的目标 IP 增强（PTR / AS / 归属地 / anycast 标记）。
  #       · ipwho.is —— IP 归属地查询工具的唯一数据源。**这条曾漏配**，导致该工具线上
  #         一直被 CSP 拦住（控制台只报一条违规，页面表现为「查询失败」）。别因为
  #         /api/my-ip 存在就误以为所有 IP 查询都走同源。
  #     注意：my-ip 工具的 ipify / ip.sb / cloudflare trace 不在其中——
  #     它已经有同源的 /api/my-ip 作首选，第三方只是兜底，没必要为它放宽 CSP。
  #   - AdSense（2026-10-18 接入）：pagead2 一个域名远远不够。真实加载链路是
  #     pagead2 的脚本再去拉 gpt.js / tpc.googlesyndication.com 的 iframe，
  #     frame-src 不放行 tpc 的话广告位会静默空白，控制台只报一条 CSP 违规。
  #     下面这几条是 Google 官方 CSP 指引里的最小集合，缺一条就有一部分广告加载不出来：
  #       script-src  tpc / googletagservices / adservice / securepubads / partner.googleadservices
  #       connect-src tpc（素材与埋点） + ep1.adtrafficquality.google（流量质量检测）
  #       frame-src   tpc（广告 iframe）+ www.google.com（广告偏好与反馈弹窗）
  #     ⚠ 2026-10-19 补 ep2，别再删：SODAR 链路会以 **script** 方式加载
  #     https://ep2.adtrafficquality.google/sodar/sodar2.js，还会建 ep2 的 frame，
  #     所以 ep2 要同时进 script-src 和 frame-src；ep1 只走 connect-src（XHR）。
  #     漏放 ep2 的后果：每页 1 条 CSP 违规 + 1 条
  #     Uncaught (in promise) undefined，广告还能出但 console 全红、真错误被淹。
  # 部署后逐个工具页看 console 有没有 CSP 报错，确认干净了再往回收紧。
  #       · qt.gtimg.cn —— 高股息选股工具的行情快照，48 只拼成一条 URL、只发一次请求。
  #         必须在 connect-src 里：实测带 Access-Control-Allow-Origin: *，浏览器直连。
  #       · push2.eastmoney.com —— 同一工具的快照备源（腾讯挂了才用），也是浏览器直连。
  #       · K 线**不在**这个清单里：48 只 × 3 周期 = 144 条，浏览器直连必被摁掉
  #         （腾讯 web.ifzq 域名有腾讯云 WAF，超量返 501 且不带 CORS 头；
  #           东财 push2his 按 IP 限流，超量直接断连），所以统一走同源
  #         /api/stock-kline 代理 + D1 缓存，同源请求不受 connect-src 约束，
  #         这两个域名一个都不需要放行。
  #         分红走同源 /api/em-bonus（datacenter 接口没有 CORS 头），同样不需要放行。
  #         批量行情每批上限 100 只，停牌股价格返回 "-"，前端按数值过滤。
  Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval' https://pagead2.googlesyndication.com https://tpc.googlesyndication.com https://www.googletagservices.com https://adservice.google.com https://securepubads.g.doubleclick.net https://partner.googleadservices.com https://www.googletagmanager.com https://static.cloudflareinsights.com https://cdn.jsdelivr.net https://ep1.adtrafficquality.google https://ep2.adtrafficquality.google; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https://pagead2.googlesyndication.com https://googleads.g.doubleclick.net https://tpc.googlesyndication.com https://ep1.adtrafficquality.google https://ep2.adtrafficquality.google https://cloudflareinsights.com https://open.er-api.com https://cdn.jsdelivr.net https://api.frankfurter.dev https://www.schemastore.org https://rdap.org https://api.globalping.io https://ipinfo.io https://ipwho.is https://qt.gtimg.cn https://push2.eastmoney.com; worker-src 'self' blob:; frame-src 'self' https://googleads.g.doubleclick.net https://tpc.googlesyndication.com https://ep2.adtrafficquality.google https://www.google.com; object-src 'none'; base-uri 'self'; form-action 'self'

# sw.js 是唯一必须每次请求都回源验证的文件，这里破例动缓存策略。
# Cloudflare Pages 默认给非 HTML 文件 max-age=14400（4 小时）：老访客浏览器在 4 小时内
# 拿着缓存里的旧 sw.js 直接判定"没有更新"，于是一直用旧 Service Worker —— 旧 SW
# precache 的旧 index.html 引用的旧 chunk 在新部署里已被删除，页面加载失败后
# main.ts 的 vite:preloadError 处理会触发 reload，刷新一次再拿旧 index.html，
# 形成"不停刷新"，且 4 小时内无法自愈。no-cache 语义是"每次用前必须回源验证"，
# SW 更新检查立刻能发现新版本。
/sw.js
  Cache-Control: no-cache


# ---------------------------------------------------------------------------
# /source/ —— GPLv3 第 6 条的源码提供入口，不需要也不打算被搜索引擎收录。
#
# 这里必须用响应头 noindex，**不能**在 robots.txt 里写 Disallow /source/：
# 被 Disallow 的 URL 爬虫不会去取，X-Robots-Tag 就永远读不到；而一旦有外链指向它，
# 反而可能以「无标题 URL」的形式进索引。robots.txt 保持全站放行，靠响应头排除。
# ---------------------------------------------------------------------------
/source
  X-Robots-Tag: noindex, nofollow

/source/
  X-Robots-Tag: noindex, nofollow

/source/*
  X-Robots-Tag: noindex, nofollow
