Skip to main content

zenith_http1/
client.rs

1//! HTTP/1.1 客户端编解码(反向代理上游转发专用)
2//!
3//! 本模块提供反向代理转发闭环的最小客户端能力:
4//! - [`encode_request`]:将规范化请求编码为 HTTP/1.1 线格式(固定 `Connection: close`,
5//!   v1 不做上游 keep-alive 复用)
6//! - [`parse_response`]:**增量式**响应解析——数据不足返回 `Ok(None)`,
7//!   完整响应返回 `Ok(Some((响应, 消费字节数)))`
8//! - [`parse_response_eof`]:connection-close 语义兜底——无 `Content-Length` 且无
9//!   `Transfer-Encoding` 时,由调用方读到 EOF 后调用,剩余字节全部视为 body
10//!
11//! 支持的三种 body 分帧(严格 RFC 7230 §3.3.3,与服务端 parser 同一套语义):
12//! 1. `Transfer-Encoding: chunked` → 完整 chunked 解码(十六进制块大小、CRLF、
13//!    零块终止、trailer 忽略)
14//! 2. `Content-Length: N` → 恰好 N 字节
15//! 3. 两者皆无 → connection-close(读到 EOF,由 [`parse_response_eof`] 兜底)
16//!
17//! # 安全保证(fail-closed)
18//! - method/path/host/头名/头值含 `\r` 或 `\n` → [`encode_request`] 返回 `None`
19//!   (CRLF 注入防护,绝不静默放行)
20//! - 响应头区超过 [`MAX_HEADER_SECTION`](64 KiB,无论终止标记是否已找到)→ Err
21//! - 响应头条数 / 单名 / 单值超 [`MAX_HEADER_COUNT`] / [`MAX_HEADER_NAME_LEN`] /
22//!   [`MAX_HEADER_VALUE_LEN`] → Err(与服务端 parser 防线对称,M-4)
23//! - Content-Length 与 Transfer-Encoding 共存 / 多个不同的 Content-Length → Err
24//!   (请求走私防护)
25//! - 非法状态行 / 非法头部 → Err,绝不截断放行
26
27use std::fmt;
28
29use crate::chunked::ChunkedDecoder;
30use crate::types::Http1Error;
31
32/// 响应头区硬上限(64 KiB,含 `\r\n\r\n` 终止标记),超限 fail-closed
33pub const MAX_HEADER_SECTION: usize = 64 * 1024;
34
35/// 响应头条数硬上限(与服务端 parser `max_header_count` 默认 256 对称,M-4)
36pub const MAX_HEADER_COUNT: usize = 256;
37
38/// 单个响应头名硬上限(与服务端 parser `max_header_name_len` 默认 64 对称,M-4)
39pub const MAX_HEADER_NAME_LEN: usize = 64;
40
41/// 单个响应头值硬上限(与服务端 parser `max_header_value_len` 默认 8192 对称,M-4)
42pub const MAX_HEADER_VALUE_LEN: usize = 8192;
43
44/// 上游响应 body 硬上限(64 MiB),超限 fail-closed
45///
46/// 反向代理场景下恶意上游可声明超大 Content-Length 或无限 chunked 流,
47/// 无上限将导致内存耗尽(OOM)。调用方如需更小上限应在读取层先行截断
48/// (如 zenith-proxy 的 `ForwardConfig::max_response_bytes`)。
49pub const MAX_BODY_BYTES: u64 = 64 * 1024 * 1024;
50
51/// 上游响应(代理解析结果)
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct ClientResponse {
54    /// 状态码(三位数字)
55    pub status: u16,
56    /// 响应头(保留原始大小写与顺序)
57    pub headers: Vec<(String, String)>,
58    /// 响应体(chunked 已解码)
59    pub body: Vec<u8>,
60}
61
62/// 代理客户端错误
63#[derive(Debug)]
64pub enum ClientError {
65    /// 状态行格式非法
66    InvalidStatusLine,
67    /// 头部格式非法
68    InvalidHeader(String),
69    /// 头区超过 64 KiB 上限
70    HeaderTooLarge,
71    /// body 超过上限
72    BodyTooLarge,
73    /// chunked 编码错误
74    InvalidChunked(String),
75    /// 协议不一致(CL/TE 共存、多个不同 CL 等,请求走私前兆)
76    ProtocolInconsistency(String),
77    /// 响应不完整(EOF 先于完整响应到达,截断响应 fail-closed)
78    Truncated,
79}
80
81impl fmt::Display for ClientError {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            Self::InvalidStatusLine => write!(f, "upstream status line invalid"),
85            Self::InvalidHeader(m) => write!(f, "upstream header invalid: {m}"),
86            Self::HeaderTooLarge => write!(f, "upstream header section too large"),
87            Self::BodyTooLarge => write!(f, "upstream body too large"),
88            Self::InvalidChunked(m) => write!(f, "upstream chunked encoding error: {m}"),
89            Self::ProtocolInconsistency(m) => write!(f, "upstream protocol inconsistency: {m}"),
90            Self::Truncated => write!(f, "upstream response truncated"),
91        }
92    }
93}
94
95impl std::error::Error for ClientError {}
96
97/// 头名字符合法性(RFC 7230 token)
98#[inline]
99fn is_valid_token(s: &str) -> bool {
100    !s.is_empty()
101        && s.bytes().all(|b| {
102            b.is_ascii_alphanumeric()
103                || matches!(
104                    b,
105                    b'!' | b'#'
106                        | b'$'
107                        | b'%'
108                        | b'&'
109                        | b'\''
110                        | b'*'
111                        | b'+'
112                        | b'-'
113                        | b'.'
114                        | b'^'
115                        | b'_'
116                        | b'`'
117                        | b'|'
118                        | b'~'
119                )
120        })
121}
122
123/// 是否含 CR/LF(CRLF 注入判定)
124#[inline]
125fn contains_crlf(s: &str) -> bool {
126    s.contains('\r') || s.contains('\n')
127}
128
129/// 编码 HTTP/1.1 请求(代理转发上游)
130///
131/// 输出布局:请求行 + `Host` + 透传头 + `Content-Length` + `Connection: close` + 空行 + body。
132///
133/// # 参数
134/// - `method`:请求方法(RFC 7230 token,如 `GET`)
135/// - `path_with_query`:路径 + 可选查询串(`?` 已拼接)
136/// - `host`:Host 头值(透传原始 Host)
137/// - `headers`:透传头;调用方负责剔除 `host` / `connection` / `content-length` 旧值
138/// - `body`:请求体(始终写 `Content-Length`,即使为 0)
139///
140/// # 返回
141/// - `Some(bytes)`:编码后的线格式字节
142/// - `None`:method/path/host/任一头名或头值含 `\r` / `\n`,或 method 非 token、
143///   path/host 为空——**fail-closed 整体拒绝,绝不静默放行或改写**
144pub fn encode_request(
145    method: &str,
146    path_with_query: &str,
147    host: &str,
148    headers: &[(String, String)],
149    body: &[u8],
150) -> Option<Vec<u8>> {
151    // CRLF 注入防护:请求行三要素任一含 CR/LF 即整体拒绝(fail-closed)
152    if !is_valid_token(method)
153        || path_with_query.is_empty()
154        || contains_crlf(path_with_query)
155        || path_with_query.contains(' ')
156        || host.is_empty()
157        || contains_crlf(host)
158    {
159        return None;
160    }
161    // 透传头逐一校验:任一头名/头值含 CR/LF 即整体拒绝(不放行、不跳过、不改写)
162    for (name, value) in headers {
163        if !is_valid_token(name) || contains_crlf(value) {
164            return None;
165        }
166    }
167
168    // 预分配:请求行 + 头 + body 的估计容量,避免热路径反复扩容
169    let mut out = Vec::with_capacity(method.len() + path_with_query.len() + host.len() + body.len() + 256);
170    out.extend_from_slice(method.as_bytes());
171    out.push(b' ');
172    out.extend_from_slice(path_with_query.as_bytes());
173    out.extend_from_slice(b" HTTP/1.1\r\nHost: ");
174    out.extend_from_slice(host.as_bytes());
175    out.extend_from_slice(b"\r\n");
176
177    for (name, value) in headers {
178        out.extend_from_slice(name.as_bytes());
179        out.extend_from_slice(b": ");
180        out.extend_from_slice(value.as_bytes());
181        out.extend_from_slice(b"\r\n");
182    }
183
184    out.extend_from_slice(b"Content-Length: ");
185    out.extend_from_slice(body.len().to_string().as_bytes());
186    // v1 不做上游 keep-alive:固定 close,响应读到 EOF 即连接终止
187    out.extend_from_slice(b"\r\nConnection: close\r\n\r\n");
188    out.extend_from_slice(body);
189    Some(out)
190}
191
192/// 在缓冲区中查找头区结束标记 `\r\n\r\n`,返回其起始下标
193#[inline]
194fn find_header_end(buf: &[u8]) -> Option<usize> {
195    buf.windows(4).position(|w| w == b"\r\n\r\n")
196}
197
198/// 解析状态行 `HTTP/1.x <status> <reason>`(reason 可空)
199fn parse_status_line(line: &[u8]) -> Result<u16, ClientError> {
200    let s = std::str::from_utf8(line).map_err(|_| ClientError::InvalidStatusLine)?;
201    let mut parts = s.splitn(3, ' ');
202    let version = parts.next().ok_or(ClientError::InvalidStatusLine)?;
203    // 仅接受 HTTP/1.x(本客户端只发 HTTP/1.1 请求,2/3 响应视为格式错误)
204    let ver_ok = version.len() == 8
205        && version.starts_with("HTTP/1.")
206        && version.as_bytes()[7].is_ascii_digit();
207    if !ver_ok {
208        return Err(ClientError::InvalidStatusLine);
209    }
210    let status_str = parts.next().ok_or(ClientError::InvalidStatusLine)?;
211    if status_str.len() != 3 || !status_str.bytes().all(|b| b.is_ascii_digit()) {
212        return Err(ClientError::InvalidStatusLine);
213    }
214    status_str
215        .parse::<u16>()
216        .map_err(|_| ClientError::InvalidStatusLine)
217}
218
219/// 解析头区为 (status, headers)
220///
221/// H-4 修复:按 `\n` 切行后,非最后一行必须以 `\r` 结尾——否则为裸 LF(协议违规)。
222/// 在代理转发场景下,恶意上游利用裸 LF 可注入额外头部;严格拒绝以阻断注入。
223/// 最后一行无需 `\r`:head 截取自 `\r\n\r\n` 终止符之前,最后一段的 `\r` 属于终止符。
224fn parse_head(head: &[u8]) -> Result<(u16, Vec<(String, String)>), ClientError> {
225    let lines: Vec<&[u8]> = head.split(|&b| b == b'\n').collect();
226    let total = lines.len();
227    if total == 0 {
228        return Err(ClientError::InvalidStatusLine);
229    }
230    let status_line = lines[0];
231    let status_line = if total > 1 {
232        match status_line.last() {
233            Some(b'\r') => &status_line[..status_line.len() - 1],
234            _ => {
235                return Err(ClientError::InvalidHeader(
236                    "bare LF in status line (protocol violation)".into(),
237                ))
238            }
239        }
240    } else {
241        match status_line.last() {
242            Some(b'\r') => &status_line[..status_line.len() - 1],
243            _ => status_line,
244        }
245    };
246    let status = parse_status_line(status_line)?;
247
248    let mut headers: Vec<(String, String)> = Vec::new();
249    for (i, raw) in lines.iter().enumerate().skip(1) {
250        let line = if i < total - 1 {
251            match raw.last() {
252                Some(b'\r') => &raw[..raw.len() - 1],
253                _ => {
254                    return Err(ClientError::InvalidHeader(
255                        "bare LF in header line (protocol violation)".into(),
256                    ))
257                }
258            }
259        } else {
260            match raw.last() {
261                Some(b'\r') => &raw[..raw.len() - 1],
262                _ => raw,
263            }
264        };
265        if line.is_empty() {
266            continue;
267        }
268        let colon = line
269            .iter()
270            .position(|&b| b == b':')
271            .ok_or_else(|| ClientError::InvalidHeader("missing colon".into()))?;
272        let name = std::str::from_utf8(&line[..colon])
273            .map_err(|_| ClientError::InvalidHeader("name not UTF-8".into()))?;
274        if !is_valid_token(name) {
275            return Err(ClientError::InvalidHeader(format!(
276                "invalid header name: {name:?}"
277            )));
278        }
279        // M-4:单名长度上限(与服务端 parser 防线对称,fail-closed)
280        if name.len() > MAX_HEADER_NAME_LEN {
281            return Err(ClientError::HeaderTooLarge);
282        }
283        let value_raw = &line[colon + 1..];
284        // 去除首尾 OWS(空格 / 水平制表符)
285        let mut start = 0;
286        let mut end = value_raw.len();
287        while start < end && matches!(value_raw[start], b' ' | b'\t') {
288            start += 1;
289        }
290        while end > start && matches!(value_raw[end - 1], b' ' | b'\t') {
291            end -= 1;
292        }
293        // M-4:单值长度上限(与服务端 parser 防线对称,fail-closed)
294        if end - start > MAX_HEADER_VALUE_LEN {
295            return Err(ClientError::HeaderTooLarge);
296        }
297        let value = std::str::from_utf8(&value_raw[start..end])
298            .map_err(|_| ClientError::InvalidHeader("value not UTF-8".into()))?;
299        headers.push((name.to_string(), value.to_string()));
300        // M-4:条数上限(恶意上游海量头部耗尽内存的防线)
301        if headers.len() > MAX_HEADER_COUNT {
302            return Err(ClientError::HeaderTooLarge);
303        }
304    }
305    Ok((status, headers))
306}
307
308/// body 分帧形态(RFC 7230 §3.3.3)
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
310enum BodyFraming {
311    /// 无 body(1xx / 204 / 304 状态码禁止携带 body)
312    Bodiless,
313    /// Transfer-Encoding: chunked
314    Chunked,
315    /// Content-Length: N
316    Length(usize),
317    /// 无长度头:connection-close 语义(读到 EOF)
318    UntilEof,
319}
320
321/// 判定 body 分帧形态(含请求走私防护)
322fn determine_framing(
323    status: u16,
324    headers: &[(String, String)],
325) -> Result<BodyFraming, ClientError> {
326    // 1xx / 204 / 304 MUST NOT 携带 body(RFC 7230 §3.3.1/§3.3.2)
327    if matches!(status, 100..=199) || status == 204 || status == 304 {
328        return Ok(BodyFraming::Bodiless);
329    }
330
331    let mut content_lengths: Vec<usize> = Vec::new();
332    let mut chunked = false;
333    for (name, value) in headers {
334        if name.eq_ignore_ascii_case("content-length") {
335            // u64 解析后转 usize,32/64 位平台均 fail-closed(拒绝超平台范围的值)
336            let n: u64 = value
337                .trim()
338                .parse()
339                .map_err(|_| ClientError::InvalidHeader("content-length not a number".into()))?;
340            let n = usize::try_from(n)
341                .map_err(|_| ClientError::BodyTooLarge)?;
342            content_lengths.push(n);
343        } else if name.eq_ignore_ascii_case("transfer-encoding") {
344            // 逐 token 匹配(如 "gzip, chunked")
345            for token in value.split(',') {
346                if token.trim().eq_ignore_ascii_case("chunked") {
347                    chunked = true;
348                }
349            }
350        }
351    }
352
353    // CL/TE 共存 = 请求走私经典前兆,fail-closed
354    if chunked && !content_lengths.is_empty() {
355        return Err(ClientError::ProtocolInconsistency(
356            "content-length with transfer-encoding".into(),
357        ));
358    }
359    // 多个不同的 Content-Length 同样拒绝(相同值的重复头按 RFC 7230 §3.3.2 允许)
360    if let Some(first) = content_lengths.first()
361        && content_lengths.iter().any(|n| n != first)
362    {
363        return Err(ClientError::ProtocolInconsistency(
364            "conflicting content-length headers".into(),
365        ));
366    }
367
368    if chunked {
369        Ok(BodyFraming::Chunked)
370    } else if let Some(&len) = content_lengths.first() {
371        Ok(BodyFraming::Length(len))
372    } else {
373        Ok(BodyFraming::UntilEof)
374    }
375}
376
377/// 增量解析 HTTP/1.1 上游响应
378///
379/// # 返回
380/// - `Ok(Some((response, consumed)))`:完整响应 + 消费字节数(调用方可据此处理粘包)
381/// - `Ok(None)`:数据不足,调用方应继续读取后重试
382/// - `Err(_)`:格式非法 / 超限 / 走私前兆,fail-closed
383///
384/// # 分帧语义
385/// - `Content-Length` / `chunked`:数据齐全即完成,无需等待 EOF
386/// - 无长度头:始终返回 `Ok(None)`,由调用方读到 EOF 后调用 [`parse_response_eof`]
387pub fn parse_response(buf: &[u8]) -> Result<Option<(ClientResponse, usize)>, ClientError> {
388    let head_end = match find_header_end(buf) {
389        Some(pos) => pos,
390        None => {
391            // 头区未齐:超过硬上限 fail-closed,否则等待更多数据
392            if buf.len() > MAX_HEADER_SECTION {
393                return Err(ClientError::HeaderTooLarge);
394            }
395            return Ok(None);
396        }
397    };
398    // M-4:找到终止标记后同样强制头区总字节上限(含 4 字节终止标记)
399    if head_end.saturating_add(4) > MAX_HEADER_SECTION {
400        return Err(ClientError::HeaderTooLarge);
401    }
402    let (status, headers) = parse_head(&buf[..head_end])?;
403    let body_start = head_end.saturating_add(4);
404    let body_bytes = &buf[body_start..];
405
406    match determine_framing(status, &headers)? {
407        BodyFraming::Bodiless => Ok(Some((
408            ClientResponse {
409                status,
410                headers,
411                body: Vec::new(),
412            },
413            body_start,
414        ))),
415        BodyFraming::Length(len) => {
416            // body 硬上限:恶意上游声明超大 Content-Length → fail-closed(防 OOM)
417            if len as u64 > MAX_BODY_BYTES {
418                return Err(ClientError::BodyTooLarge);
419            }
420            // checked 算术:head_end + 4 + len 溢出按超限处理(fail-closed)
421            let need = body_start.checked_add(len).ok_or(ClientError::BodyTooLarge)?;
422            if buf.len() < need {
423                return Ok(None);
424            }
425            Ok(Some((
426                ClientResponse {
427                    status,
428                    headers,
429                    body: buf[body_start..need].to_vec(),
430                },
431                need,
432            )))
433        }
434        BodyFraming::Chunked => {
435            // 复用服务端同款 ChunkedDecoder(语义一致);
436            // 每次调用全量重喂 body 前缀(无状态增量解析,v1 以正确性优先)
437            // 上限 MAX_BODY_BYTES:恶意上游无限 chunked 流 → fail-closed(防 OOM)
438            let mut dec = ChunkedDecoder::new(MAX_BODY_BYTES);
439            let (out, consumed) = dec.feed(body_bytes).map_err(|e| match e {
440                Http1Error::BodyTooLarge => ClientError::BodyTooLarge,
441                other => ClientError::InvalidChunked(other.to_string()),
442            })?;
443            if dec.is_done() {
444                Ok(Some((
445                    ClientResponse {
446                        status,
447                        headers,
448                        body: out,
449                    },
450                    body_start.saturating_add(consumed),
451                )))
452            } else {
453                Ok(None)
454            }
455        }
456        // connection-close 语义:增量解析无法判定完成,等待 EOF 兜底
457        BodyFraming::UntilEof => Ok(None),
458    }
459}
460
461/// 有状态增量响应解析器
462///
463/// 解决 [`parse_response`] 在 chunked 分帧下 O(n²) 的性能问题:
464/// 维持 `ChunkedDecoder` 跨调用,仅处理新增字节,总复杂度 O(n)。
465///
466/// # 用法
467///
468/// ```ignore
469/// let mut parser = ResponseParser::new();
470/// loop {
471///     // 读取数据追加到 buf...
472///     match parser.feed(&buf)? {
473///         Some((resp, consumed)) => { /* 完整响应 */ break; }
474///         None => { /* 等待更多数据 */ }
475///     }
476/// }
477/// ```
478#[derive(Debug)]
479pub struct ResponseParser {
480    /// 头部解析状态(仅首次调用时解析,之后复用)
481    head: Option<ParsedHead>,
482    /// Chunked decoder(仅 chunked 分帧时初始化)
483    chunked_decoder: Option<ChunkedDecoder>,
484    /// 已处理的 body 字节数(跳过已解析部分)
485    body_processed: usize,
486    /// 累积的 chunked 解码输出
487    chunked_output: Vec<u8>,
488}
489
490#[derive(Debug, Clone)]
491struct ParsedHead {
492    status: u16,
493    headers: Vec<(String, String)>,
494    body_start: usize,
495    framing: BodyFraming,
496}
497
498impl ResponseParser {
499    /// 创建解析器
500    #[inline]
501    pub fn new() -> Self {
502        Self {
503            head: None,
504            chunked_decoder: None,
505            body_processed: 0,
506            chunked_output: Vec::new(),
507        }
508    }
509
510    /// 增量解析:输入当前全量缓冲,返回已完成的响应或等待更多数据
511    ///
512    /// 与 [`parse_response`] API 兼容:返回 `Ok(Some((resp, consumed)))` 表示
513    /// 完整响应已就绪(`consumed` 为从头开始的消费字节数),`Ok(None)` 表示
514    /// 等待更多数据。
515    pub fn feed(&mut self, buf: &[u8]) -> Result<Option<(ClientResponse, usize)>, ClientError> {
516        // Phase 1: 解析头部(仅首次调用)
517        if self.head.is_none() {
518            let head_end = match find_header_end(buf) {
519                Some(pos) => pos,
520                None => {
521                    if buf.len() > MAX_HEADER_SECTION {
522                        return Err(ClientError::HeaderTooLarge);
523                    }
524                    return Ok(None);
525                }
526            };
527            if head_end.saturating_add(4) > MAX_HEADER_SECTION {
528                return Err(ClientError::HeaderTooLarge);
529            }
530            let (status, headers) = parse_head(&buf[..head_end])?;
531            let body_start = head_end.saturating_add(4);
532            let framing = determine_framing(status, &headers)?;
533            self.head = Some(ParsedHead {
534                status,
535                headers,
536                body_start,
537                framing,
538            });
539        }
540
541        let head = self.head.as_ref().expect("head just parsed");
542        let body_bytes = &buf[head.body_start..];
543
544        match head.framing {
545            BodyFraming::Bodiless => Ok(Some((
546                ClientResponse {
547                    status: head.status,
548                    headers: head.headers.clone(),
549                    body: Vec::new(),
550                },
551                head.body_start,
552            ))),
553            BodyFraming::Length(len) => {
554                if len as u64 > MAX_BODY_BYTES {
555                    return Err(ClientError::BodyTooLarge);
556                }
557                let need = head
558                    .body_start
559                    .checked_add(len)
560                    .ok_or(ClientError::BodyTooLarge)?;
561                if buf.len() < need {
562                    return Ok(None);
563                }
564                Ok(Some((
565                    ClientResponse {
566                        status: head.status,
567                        headers: head.headers.clone(),
568                        body: buf[head.body_start..need].to_vec(),
569                    },
570                    need,
571                )))
572            }
573            BodyFraming::Chunked => {
574                // 初始化 decoder(仅首次)
575                if self.chunked_decoder.is_none() {
576                    self.chunked_decoder = Some(ChunkedDecoder::new(MAX_BODY_BYTES));
577                }
578                let decoder = self.chunked_decoder.as_mut().expect("just initialized");
579
580                // 仅处理新增字节(从 body_processed 开始)
581                let new_data = &body_bytes[self.body_processed..];
582                if !new_data.is_empty() {
583                    let (out, consumed) = decoder
584                        .feed(new_data)
585                        .map_err(|e| match e {
586                            Http1Error::BodyTooLarge => ClientError::BodyTooLarge,
587                            other => ClientError::InvalidChunked(other.to_string()),
588                        })?;
589                    self.body_processed += consumed;
590                    self.chunked_output.extend_from_slice(&out);
591
592                    if decoder.is_done() {
593                        let total_consumed = head.body_start + self.body_processed;
594                        let body = std::mem::take(&mut self.chunked_output);
595                        return Ok(Some((
596                            ClientResponse {
597                                status: head.status,
598                                headers: head.headers.clone(),
599                                body,
600                            },
601                            total_consumed,
602                        )));
603                    }
604                }
605                Ok(None)
606            }
607            BodyFraming::UntilEof => Ok(None),
608        }
609    }
610}
611
612impl Default for ResponseParser {
613    #[inline]
614    fn default() -> Self {
615        Self::new()
616    }
617}
618
619/// EOF 兜底解析(connection-close 语义)
620///
621/// 调用方读到 EOF 后调用:
622/// - 无长度头:头区之后的全部字节视为 body
623/// - `Content-Length` / `chunked`:委托 [`parse_response`],数据不齐 → [`ClientError::Truncated`]
624///
625/// # 错误
626/// 头区未齐即 EOF、或声明的长度未读满即 EOF → Err(截断响应 fail-closed,绝不放行)
627pub fn parse_response_eof(buf: &[u8]) -> Result<ClientResponse, ClientError> {
628    let head_end = find_header_end(buf).ok_or(ClientError::Truncated)?;
629    let (status, headers) = parse_head(&buf[..head_end])?;
630    let body_start = head_end.saturating_add(4);
631
632    match determine_framing(status, &headers)? {
633        BodyFraming::UntilEof => Ok(ClientResponse {
634            status,
635            headers,
636            body: buf[body_start..].to_vec(),
637        }),
638        // 有明确分帧的响应必须完整到达,否则视为截断
639        _ => match parse_response(buf)? {
640            Some((resp, _)) => Ok(resp),
641            None => Err(ClientError::Truncated),
642        },
643    }
644}
645
646#[cfg(test)]
647mod tests {
648    use super::*;
649
650    // ───────── encode_request 测试 ─────────
651
652    #[test]
653    fn encode_basic_get() {
654        let out = encode_request("GET", "/api/x?a=1", "127.0.0.1:8080", &[], b"");
655        let out = match out {
656            Some(v) => v,
657            None => panic!("encode should succeed"),
658        };
659        let s = String::from_utf8_lossy(&out);
660        assert!(s.starts_with("GET /api/x?a=1 HTTP/1.1\r\n"));
661        assert!(s.contains("Host: 127.0.0.1:8080\r\n"));
662        assert!(s.contains("Content-Length: 0\r\n"));
663        assert!(s.contains("Connection: close\r\n"));
664        assert!(s.ends_with("\r\n\r\n"));
665    }
666
667    #[test]
668    fn encode_post_with_body_and_headers() {
669        let headers = vec![
670            ("content-type".to_string(), "application/json".to_string()),
671            ("x-token".to_string(), "abc".to_string()),
672        ];
673        let out = encode_request("POST", "/echo", "up:9000", &headers, b"hello");
674        let out = match out {
675            Some(v) => v,
676            None => panic!("encode should succeed"),
677        };
678        let s = String::from_utf8_lossy(&out);
679        assert!(s.starts_with("POST /echo HTTP/1.1\r\n"));
680        assert!(s.contains("content-type: application/json\r\n"));
681        assert!(s.contains("x-token: abc\r\n"));
682        assert!(s.contains("Content-Length: 5\r\n"));
683        assert!(s.ends_with("\r\n\r\nhello"));
684    }
685
686    #[test]
687    fn encode_rejects_crlf_injection_fail_closed() {
688        // method/path/host 含 CR/LF → None(绝不静默放行)
689        assert!(encode_request("GET\r\nEvil: x", "/", "h", &[], b"").is_none());
690        assert!(encode_request("GET", "/a\r\nb", "h", &[], b"").is_none());
691        assert!(encode_request("GET", "/", "ho\r\nst", &[], b"").is_none());
692        assert!(encode_request("GET", "/a\nb", "h", &[], b"").is_none());
693        // 头名/头值含 CR/LF → None
694        let bad_value = vec![("x-bad".to_string(), "evil\r\nInjected: yes".to_string())];
695        assert!(encode_request("GET", "/", "h", &bad_value, b"").is_none());
696        let bad_name = vec![("x-bad\r\nInjected".to_string(), "v".to_string())];
697        assert!(encode_request("GET", "/", "h", &bad_name, b"").is_none());
698        // 非法 method / 空 path / 空 host / 空头名 → None
699        assert!(encode_request("GE T", "/", "h", &[], b"").is_none());
700        assert!(encode_request("", "/", "h", &[], b"").is_none());
701        assert!(encode_request("GET", "", "h", &[], b"").is_none());
702        assert!(encode_request("GET", "/", "", &[], b"").is_none());
703        let empty_name = vec![("".to_string(), "v".to_string())];
704        assert!(encode_request("GET", "/", "h", &empty_name, b"").is_none());
705    }
706
707    // ───────── parse_response:Content-Length 分帧 ─────────
708
709    #[test]
710    fn parse_content_length_response() {
711        let raw = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\nhello";
712        let r = parse_response(raw);
713        let (resp, consumed) = match r {
714            Ok(Some(v)) => v,
715            other => panic!("expected complete response, got {other:?}"),
716        };
717        assert_eq!(resp.status, 200);
718        assert_eq!(resp.body, b"hello");
719        assert_eq!(consumed, raw.len());
720        assert_eq!(
721            resp.headers,
722            vec![
723                ("Content-Type".to_string(), "text/plain".to_string()),
724                ("Content-Length".to_string(), "5".to_string()),
725            ]
726        );
727    }
728
729    #[test]
730    fn parse_content_length_incremental_feed() {
731        // 逐字节喂入:完整前必须 Ok(None),完整后立即 Some
732        let raw = b"HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nhello world";
733        for i in 0..raw.len() {
734            match parse_response(&raw[..i]) {
735                Ok(None) => {}
736                other => panic!("prefix {i} should be incomplete, got {other:?}"),
737            }
738        }
739        match parse_response(raw) {
740            Ok(Some((resp, consumed))) => {
741                assert_eq!(resp.body, b"hello world");
742                assert_eq!(consumed, raw.len());
743            }
744            other => panic!("full input should complete, got {other:?}"),
745        }
746    }
747
748    #[test]
749    fn parse_content_length_sticky_packet_consumed() {
750        // 粘包:响应后紧跟多余字节,consumed 必须精确指向响应末尾
751        let raw = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhiNEXT-REQUEST-BYTES";
752        match parse_response(raw) {
753            Ok(Some((resp, consumed))) => {
754                assert_eq!(resp.body, b"hi");
755                assert_eq!(&raw[consumed..], b"NEXT-REQUEST-BYTES");
756            }
757            other => panic!("expected complete, got {other:?}"),
758        }
759    }
760
761    #[test]
762    fn parse_status_without_reason_phrase() {
763        let raw = b"HTTP/1.1 200\r\nContent-Length: 0\r\n\r\n";
764        match parse_response(raw) {
765            Ok(Some((resp, _))) => assert_eq!(resp.status, 200),
766            other => panic!("expected complete, got {other:?}"),
767        }
768    }
769
770    #[test]
771    fn parse_http10_response() {
772        let raw = b"HTTP/1.0 302 Found\r\nContent-Length: 2\r\n\r\nok";
773        match parse_response(raw) {
774            Ok(Some((resp, _))) => {
775                assert_eq!(resp.status, 302);
776                assert_eq!(resp.body, b"ok");
777            }
778            other => panic!("expected complete, got {other:?}"),
779        }
780    }
781
782    #[test]
783    fn parse_bodiless_status_complete_without_body() {
784        // 204 无长度头也必须立即完成(禁止 body),不能等 EOF
785        let raw = b"HTTP/1.1 204 No Content\r\nX-A: b\r\n\r\n";
786        match parse_response(raw) {
787            Ok(Some((resp, consumed))) => {
788                assert_eq!(resp.status, 204);
789                assert!(resp.body.is_empty());
790                assert_eq!(consumed, raw.len());
791            }
792            other => panic!("204 should complete immediately, got {other:?}"),
793        }
794        // 304 同理
795        let raw304 = b"HTTP/1.1 304 Not Modified\r\n\r\n";
796        match parse_response(raw304) {
797            Ok(Some((resp, _))) => assert_eq!(resp.status, 304),
798            other => panic!("304 should complete immediately, got {other:?}"),
799        }
800    }
801
802    #[test]
803    fn parse_duplicate_identical_content_length_ok() {
804        let raw = b"HTTP/1.1 200 OK\r\nContent-Length: 3\r\nContent-Length: 3\r\n\r\nabc";
805        match parse_response(raw) {
806            Ok(Some((resp, _))) => assert_eq!(resp.body, b"abc"),
807            other => panic!("expected complete, got {other:?}"),
808        }
809    }
810
811    // ───────── parse_response:chunked 分帧 ─────────
812
813    #[test]
814    fn parse_chunked_response() {
815        let raw = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n";
816        match parse_response(raw) {
817            Ok(Some((resp, consumed))) => {
818                assert_eq!(resp.status, 200);
819                assert_eq!(resp.body, b"hello world");
820                assert_eq!(consumed, raw.len());
821            }
822            other => panic!("expected complete, got {other:?}"),
823        }
824    }
825
826    #[test]
827    fn parse_chunked_with_trailer_ignored() {
828        let raw = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n2\r\nhi\r\n0\r\nX-Trailer: v\r\n\r\n";
829        match parse_response(raw) {
830            Ok(Some((resp, _))) => assert_eq!(resp.body, b"hi"),
831            other => panic!("expected complete, got {other:?}"),
832        }
833    }
834
835    #[test]
836    fn parse_chunked_incremental_across_chunks() {
837        // chunked 跨块增量喂入:零块未到时一律 Ok(None)
838        let raw = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n";
839        let mut saw_complete_before_full = false;
840        for i in 0..raw.len() {
841            if let Ok(Some(_)) = parse_response(&raw[..i]) {
842                saw_complete_before_full = true;
843            }
844        }
845        assert!(!saw_complete_before_full, "chunked must not complete before terminator");
846        match parse_response(raw) {
847            Ok(Some((resp, _))) => assert_eq!(resp.body, b"hello world"),
848            other => panic!("expected complete, got {other:?}"),
849        }
850    }
851
852    #[test]
853    fn parse_chunked_uppercase_hex_and_extension() {
854        let raw = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nA;ext=1\r\n0123456789\r\n0\r\n\r\n";
855        match parse_response(raw) {
856            Ok(Some((resp, _))) => assert_eq!(resp.body, b"0123456789"),
857            other => panic!("expected complete, got {other:?}"),
858        }
859    }
860
861    // ───────── parse_response:connection-close 分帧 + EOF 兜底 ─────────
862
863    #[test]
864    fn parse_until_eof_waits_then_eof_completes() {
865        // 无 CL 无 TE:增量解析始终 Ok(None),EOF 兜底取全部剩余字节
866        let raw = b"HTTP/1.1 200 OK\r\nX-A: b\r\n\r\nstreamed-body-until-close";
867        match parse_response(raw) {
868            Ok(None) => {}
869            other => panic!("until-eof framing must wait for EOF, got {other:?}"),
870        }
871        let resp = match parse_response_eof(raw) {
872            Ok(r) => r,
873            Err(e) => panic!("eof parse should succeed: {e}"),
874        };
875        assert_eq!(resp.status, 200);
876        assert_eq!(resp.body, b"streamed-body-until-close");
877    }
878
879    #[test]
880    fn parse_response_eof_rejects_truncated() {
881        // 头区未齐即 EOF
882        assert!(matches!(
883            parse_response_eof(b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n"),
884            Err(ClientError::Truncated)
885        ));
886        // CL 声明 10 字节只到 5 字节
887        assert!(matches!(
888            parse_response_eof(b"HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\nshort"),
889            Err(ClientError::Truncated)
890        ));
891        // chunked 未见零块
892        assert!(matches!(
893            parse_response_eof(b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhel"),
894            Err(ClientError::Truncated)
895        ));
896        // 完全空缓冲
897        assert!(matches!(parse_response_eof(b""), Err(ClientError::Truncated)));
898    }
899
900    // ───────── parse_response:非法输入 fail-closed ─────────
901
902    #[test]
903    fn parse_rejects_bad_status_line() {
904        for raw in [
905            &b"NOTHTTP 200 OK\r\n\r\n"[..],
906            b"HTTP/2 200 OK\r\n\r\n",
907            b"HTTP/1.1 20 OK\r\n\r\n",
908            b"HTTP/1.1 abc OK\r\n\r\n",
909            b"HTTP/1.1 \r\n\r\n",
910        ] {
911            match parse_response(raw) {
912                Err(ClientError::InvalidStatusLine) => {}
913                other => panic!(
914                    "should reject {:?}, got {other:?}",
915                    String::from_utf8_lossy(raw)
916                ),
917            }
918        }
919    }
920
921    #[test]
922    fn parse_rejects_header_without_colon() {
923        let raw = b"HTTP/1.1 200 OK\r\nBadHeaderLine\r\n\r\n";
924        assert!(matches!(
925            parse_response(raw),
926            Err(ClientError::InvalidHeader(_))
927        ));
928    }
929
930    #[test]
931    fn parse_rejects_invalid_header_name() {
932        let raw = b"HTTP/1.1 200 OK\r\nBad Name: v\r\n\r\n";
933        assert!(matches!(
934            parse_response(raw),
935            Err(ClientError::InvalidHeader(_))
936        ));
937    }
938
939    #[test]
940    fn parse_rejects_bare_lf_in_status_line() {
941        // H-4: malicious upstream uses bare LF to inject headers
942        let raw = b"HTTP/1.1 200 OK\nInjected: evil\r\nContent-Length: 0\r\n\r\n";
943        assert!(matches!(
944            parse_response(raw),
945            Err(ClientError::InvalidHeader(_))
946        ));
947    }
948
949    #[test]
950    fn parse_rejects_bare_lf_in_header_line() {
951        // H-4: header line uses bare LF (non-last line missing \r)
952        let raw = b"HTTP/1.1 200 OK\r\nX-A: b\nInjected: evil\r\nContent-Length: 0\r\n\r\n";
953        assert!(matches!(
954            parse_response(raw),
955            Err(ClientError::InvalidHeader(_))
956        ));
957    }
958
959    #[test]
960    fn parse_rejects_cl_and_te_coexist() {
961        let raw = b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n";
962        assert!(matches!(
963            parse_response(raw),
964            Err(ClientError::ProtocolInconsistency(_))
965        ));
966    }
967
968    #[test]
969    fn parse_rejects_conflicting_content_length() {
970        let raw = b"HTTP/1.1 200 OK\r\nContent-Length: 3\r\nContent-Length: 4\r\n\r\nabcd";
971        assert!(matches!(
972            parse_response(raw),
973            Err(ClientError::ProtocolInconsistency(_))
974        ));
975    }
976
977    #[test]
978    fn parse_rejects_invalid_chunk_size() {
979        let raw = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nZZ\r\nx\r\n0\r\n\r\n";
980        assert!(matches!(
981            parse_response(raw),
982            Err(ClientError::InvalidChunked(_))
983        ));
984    }
985
986    #[test]
987    fn parse_rejects_oversized_header_section() {
988        let mut raw = Vec::new();
989        raw.extend_from_slice(b"HTTP/1.1 200 OK\r\n");
990        // 构造超过 64 KiB 的头区(无 \r\n\r\n 终止)
991        while raw.len() <= MAX_HEADER_SECTION {
992            raw.extend_from_slice(b"X-Pad: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n");
993        }
994        assert!(matches!(
995            parse_response(&raw),
996            Err(ClientError::HeaderTooLarge)
997        ));
998    }
999
1000    // ───────── M-4:客户端响应头防线(与服务端 parser 对称) ─────────
1001
1002    #[test]
1003    fn parse_rejects_oversized_header_section_with_terminator() {
1004        // M-4:终止标记已找到、头区总字节仍超 64 KiB 时也必须拒绝
1005        // (修复前仅未找到 \r\n\r\n 时才检查上限,终止后可绕过)
1006        let mut raw = Vec::new();
1007        raw.extend_from_slice(b"HTTP/1.1 200 OK\r\n");
1008        while raw.len() <= MAX_HEADER_SECTION {
1009            raw.extend_from_slice(b"X-Pad: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n");
1010        }
1011        raw.extend_from_slice(b"\r\n"); // 补上终止标记
1012        raw.extend_from_slice(b"ok");
1013        assert!(matches!(
1014            parse_response(&raw),
1015            Err(ClientError::HeaderTooLarge)
1016        ));
1017        // EOF 兜底路径同样拒绝
1018        assert!(matches!(
1019            parse_response_eof(&raw),
1020            Err(ClientError::HeaderTooLarge)
1021        ));
1022    }
1023
1024    #[test]
1025    fn parse_rejects_too_many_headers() {
1026        // M-4:条数上限(> MAX_HEADER_COUNT)
1027        let mut raw = Vec::new();
1028        raw.extend_from_slice(b"HTTP/1.1 200 OK\r\n");
1029        for i in 0..=MAX_HEADER_COUNT {
1030            raw.extend_from_slice(format!("X-H{i}: v\r\n").as_bytes());
1031        }
1032        raw.extend_from_slice(b"Content-Length: 0\r\n\r\n");
1033        assert!(matches!(
1034            parse_response(&raw),
1035            Err(ClientError::HeaderTooLarge)
1036        ));
1037    }
1038
1039    #[test]
1040    fn parse_accepts_header_count_at_limit() {
1041        // 边界:恰好 MAX_HEADER_COUNT 条放行
1042        let mut raw = Vec::new();
1043        raw.extend_from_slice(b"HTTP/1.1 200 OK\r\n");
1044        for i in 1..MAX_HEADER_COUNT {
1045            raw.extend_from_slice(format!("X-H{i}: v\r\n").as_bytes());
1046        }
1047        raw.extend_from_slice(b"Content-Length: 0\r\n\r\n");
1048        assert!(
1049            matches!(parse_response(&raw), Ok(Some(_))),
1050            "恰在上限内的条数必须放行"
1051        );
1052    }
1053
1054    #[test]
1055    fn parse_rejects_oversized_header_name() {
1056        // M-4:单名长度上限(> MAX_HEADER_NAME_LEN)
1057        let name = format!("X-{}", "A".repeat(MAX_HEADER_NAME_LEN));
1058        let raw = format!("HTTP/1.1 200 OK\r\n{name}: v\r\nContent-Length: 0\r\n\r\n");
1059        assert!(matches!(
1060            parse_response(raw.as_bytes()),
1061            Err(ClientError::HeaderTooLarge)
1062        ));
1063    }
1064
1065    #[test]
1066    fn parse_rejects_oversized_header_value() {
1067        // M-4:单值长度上限(> MAX_HEADER_VALUE_LEN)
1068        let value = "v".repeat(MAX_HEADER_VALUE_LEN + 1);
1069        let raw = format!("HTTP/1.1 200 OK\r\nX-Pad: {value}\r\nContent-Length: 0\r\n\r\n");
1070        assert!(matches!(
1071            parse_response(raw.as_bytes()),
1072            Err(ClientError::HeaderTooLarge)
1073        ));
1074    }
1075
1076    #[test]
1077    fn parse_header_ows_trimmed() {
1078        let raw = b"HTTP/1.1 200 OK\r\nX-Pad:   value  \r\nContent-Length: 1\r\n\r\nx";
1079        match parse_response(raw) {
1080            Ok(Some((resp, _))) => {
1081                assert_eq!(resp.headers[0], ("X-Pad".to_string(), "value".to_string()))
1082            }
1083            other => panic!("expected complete, got {other:?}"),
1084        }
1085    }
1086
1087    // ───────── 错误类型 ─────────
1088
1089    #[test]
1090    fn error_display_impl() {
1091        let e = ClientError::InvalidStatusLine;
1092        assert!(!format!("{e}").is_empty());
1093        let e = ClientError::Truncated;
1094        assert!(format!("{e}").contains("truncated"));
1095        // Error trait 可用
1096        let _: &dyn std::error::Error = &ClientError::HeaderTooLarge;
1097    }
1098}