"""本地转发器的无状态报文解析与改写函数。""" from urllib.parse import urlsplit HTTP_METHODS = ( b"CONNECT ", b"GET ", b"POST ", b"PUT ", b"DELETE ", b"HEAD ", b"OPTIONS ", b"PATCH ", ) def starts_with_http_method(data: bytes) -> bool: return any(data.startswith(method) for method in HTTP_METHODS) def inject_proxy_auth(data: bytes, basic_auth: str) -> bytes: """为完整 HTTP 代理请求注入上游 Basic Authorization。""" if not starts_with_http_method(data): return data end = data.find(b"\r\n\r\n") if end == -1: return data head = data[:end] first_end = head.find(b"\r\n") request_line = head if first_end == -1 else head[:first_end] headers = b"" if first_end == -1 else head[first_end + 2:] for line in headers.split(b"\r\n"): if b":" not in line: continue name = line[:line.index(b":")].strip().lower() if name == b"authorization": return data auth_line = f"Authorization: Basic {basic_auth}\r\n".encode() return request_line + b"\r\n" + auth_line + headers + data[end:] def parse_connect_authority(first_line: str): """从 CONNECT 首行提取 ``(host, port)``,失败时返回 ``None``。""" parts = first_line.split(" ") if len(parts) < 2: return None authority = parts[1] if authority.startswith("[") and "]" in authority: try: host = authority[1:authority.index("]")] tail = authority[authority.index("]") + 1:] if tail.startswith(":") and tail[1:].isdigit(): return host, int(tail[1:]) except ValueError: pass if ":" not in authority: return None host, _, port_s = authority.rpartition(":") if not host or not port_s.isdigit(): return None return host, int(port_s) def parse_http_absolute(first_line: str): """从明文 HTTP 代理首行提取 ``(host, port)``,失败时返回 ``None``。""" parts = first_line.split(" ") if len(parts) < 2 or not parts[1].lower().startswith(("http://", "https://")): return None parsed = urlsplit(parts[1]) if not parsed.hostname: return None port = parsed.port or (443 if parsed.scheme == "https" else 80) return parsed.hostname, port def force_http_close(buffer: bytes) -> bytes: """直连明文 HTTP 时强制 Connection: close,避免错复用连接。""" end = buffer.find(b"\r\n\r\n") if end == -1: return buffer head, tail = buffer[:end], buffer[end:] lines = head.split(b"\r\n") out = [lines[0]] conn_seen = False for line in lines[1:]: low = line.lower() if low.startswith(b"connection:"): out.append(b"Connection: close") conn_seen = True elif low.startswith(b"proxy-connection:"): continue else: out.append(line) if not conn_seen: out.append(b"Connection: close") return b"\r\n".join(out) + tail