Files
lou af43b07dc0 fix: 清 Pylance 类型检查提示(老板标注)
- client.py: 删未使用导入 TYPE_AUTH_OK/TYPE_DNS_RESP/TYPE_HTTP_RESP
- dnsmsg.py: flags 解包后未用 -> 补 QR=1 响应拒绝(符合'非 query 返回 None'语义)
- httpmsg.py: headers default_factory=list 推断 Unknown -> lambda: [] 显式类型
- smoke_channel.py: 删未使用导入 json/os/socket/FrameReader/pack_frame; cfg 显式 dict[str, object]
- analyze_capture.py: proto Counter() 无类型参数 -> Counter[int]
2026-09-09 16:32:31 +08:00

107 lines
3.2 KiB
Python

"""HTTP 请求解析与重组(共享: 本地代理解析, 云端套壳重组)。
保持最小够用集: 请求行 + headers(保留顺序/重复) + Content-Length body。
chunked 请求体与 obs-fold 不做(第一版), 遇到按无 body 处理。
"""
from __future__ import annotations
from dataclasses import dataclass, field
_HEAD_END = b"\r\n\r\n"
# hop-by-hop 头, 套壳重组时剔除(不跨连接转发)
_HOP_BY_HOP = {
"connection",
"keep-alive",
"proxy-connection",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailer",
"transfer-encoding",
"upgrade",
}
@dataclass
class HttpRequest:
method: str
path: str
host: str
headers: list[tuple[str, str]] = field(default_factory=lambda: [])
body: bytes = b""
def header(self, name: str) -> str | None:
low = name.lower()
for key, value in self.headers:
if key.lower() == low:
return value
return None
def without_hop_by_hop(self) -> list[tuple[str, str]]:
out: list[tuple[str, str]] = []
for key, value in self.headers:
if key.lower() not in _HOP_BY_HOP and key.lower() != "host":
out.append((key, value))
return out
def parse_request_head(data: bytes) -> tuple[HttpRequest, int] | None:
"""解析请求头部区, 返回 (请求, 头部区字节数); 数据不足/畸形返回 None。"""
idx = data.find(_HEAD_END)
if idx < 0:
return None
head = data[:idx]
lines = head.split(b"\r\n")
if not lines or len(lines[0].split(b" ")) != 3:
return None
method_b, path_b, _ver_b = lines[0].split(b" ", 2)
headers: list[tuple[str, str]] = []
for line in lines[1:]:
if not line:
continue
if b":" not in line:
continue
name_b, value_b = line.split(b":", 1)
name = name_b.decode("latin-1").strip()
value = value_b.decode("latin-1").strip()
headers.append((name, value))
try:
method = method_b.decode("latin-1")
path = path_b.decode("latin-1")
except UnicodeDecodeError:
return None
host = ""
body_len = 0
for key, value in headers:
low = key.lower()
if low == "host" and not host:
host = value
elif low == "content-length":
try:
body_len = int(value)
except ValueError:
body_len = 0
if body_len < 0 or body_len > 64 * 1024 * 1024:
return None
return HttpRequest(method=method, path=path, host=host, headers=headers), idx + 4
def parse_request(data: bytes) -> HttpRequest | None:
"""解析完整请求(头部 + body), body 不足返回 None。"""
parsed = parse_request_head(data)
if parsed is None:
return None
req, head_len = parsed
content_length = 0
for key, value in req.headers:
if key.lower() == "content-length":
try:
content_length = int(value)
except ValueError:
content_length = 0
if len(data) < head_len + content_length:
return None
req.body = data[head_len : head_len + content_length]
return req