(self)
| 828 | |
| 829 | # ---- HTTP CONNECT 隧道:Anthropic 域名 fast-fail、其余透传(修 #3) ---- |
| 830 | def do_CONNECT(self): |
| 831 | # operon 用 https_proxy 走到这里;self.path 形如 "host:port"。 |
| 832 | # 【为何不走 _auth_ok】CONNECT 把目标放在请求行、没有可嵌 path-secret 的位置, |
| 833 | # operon 的 https_proxy 也带不上 secret。此处不鉴权的实际风险面很小: |
| 834 | # - 只监听回环(127.0.0.1),本机进程本就能自行外连,隧道不给它任何新能力; |
| 835 | # - 隧道是裸 TCP 转发,不注入上游 key、不经推理端点(那两条仍受 secret 保护)。 |
| 836 | # 即 path-secret 真正守护的边界(第三方 key + 推理端点)未被削弱。 |
| 837 | # 进一步收紧可让 launch 把 secret 放进 https_proxy 的 userinfo 再校验 |
| 838 | # Proxy-Authorization,但需先实测 operon 是否会带该头(否则误伤透传),留待整链联调。 |
| 839 | target = self.path |
| 840 | host = target.rsplit(":", 1)[0].strip("[]").lower() |
| 841 | if _is_blocked_host(host): |
| 842 | # 401(未登录)而非 403(禁止):让 operon 判 logged-out 秒过,而非当组织问题反复重试。 |
| 843 | log(f"CONNECT {target} -> 401 未登录(Anthropic 域名 fast-fail)") |
| 844 | self._connect_reply(401) |
| 845 | return |
| 846 | try: |
| 847 | port = int(target.rsplit(":", 1)[1]) |
| 848 | except (ValueError, IndexError): |
| 849 | self._connect_reply(400) |
| 850 | return |
| 851 | try: |
| 852 | upstream = socket.create_connection((host, port), timeout=10) |
| 853 | except Exception as e: |
| 854 | log(f"CONNECT {target} -> 502 上游连不上: {e}") |
| 855 | self._connect_reply(502) |
| 856 | return |
| 857 | self.send_response(200, "Connection Established") |
| 858 | self.end_headers() |
| 859 | try: |
| 860 | self.wfile.flush() |
| 861 | except Exception: |
| 862 | pass |
| 863 | log(f"CONNECT {target} -> 隧道建立,透传") |
| 864 | try: |
| 865 | self._tunnel(self.connection, upstream) |
| 866 | finally: |
| 867 | try: |
| 868 | upstream.close() |
| 869 | except Exception: |
| 870 | pass |
| 871 | self.close_connection = True |
| 872 | |
| 873 | def _connect_reply(self, code): |
| 874 | """CONNECT 的短响应(拒绝/错误):空体 + 主动关连接。""" |
nothing calls this directly
no test coverage detected