atmos/kernel/
http_body.rs1pub fn body_start(raw: &[u8]) -> Option<usize> {
16 raw.windows(4).position(|w| w == b"\r\n\r\n").map(|i| i + 4)
17}
18
19pub fn content_length(raw: &[u8]) -> Option<usize> {
21 let end = body_start(raw)?;
22 const KEY: &[u8] = b"content-length:";
25 let head = &raw[..end];
26 let mut i = 0usize;
27 while i + KEY.len() <= head.len() {
28 let hit = head[i..i + KEY.len()]
29 .iter()
30 .zip(KEY.iter())
31 .all(|(a, b)| a.to_ascii_lowercase() == *b);
32 if hit {
33 let mut j = i + KEY.len();
35 let mut v: usize = 0;
36 let mut seen = false;
37 while j < head.len() && head[j] != 13 {
38 match head[j] {
39 d @ 48..=57 => {
40 v = v.checked_mul(10)?.checked_add((d - 48) as usize)?;
41 seen = true;
42 }
43 32 | 9 if !seen => {}
44 _ => break,
45 }
46 j += 1;
47 }
48 return if seen { Some(v) } else { None };
49 }
50 i += 1;
51 }
52 None
53}
54
55pub fn body_range(raw: &[u8]) -> Option<(usize, usize)> {
60 let start = body_start(raw)?;
61 let len = content_length(raw)?;
62 let end = start.checked_add(len)?;
63 if raw.len() >= end {
64 Some((start, end))
65 } else {
66 None
67 }
68}
69
70pub fn looks_truncated(raw: &[u8]) -> bool {
79 let Some(start) = body_start(raw) else {
80 return false;
81 };
82 let Some(len) = content_length(raw) else {
83 return false;
84 };
85 raw.len().saturating_sub(start) < len
86}
87
88fn header_value<'a>(raw: &'a [u8], name: &[u8]) -> Option<&'a [u8]> {
90 let end = body_start(raw)?;
91 let head = raw.get(..end)?;
92 for line in head.split(|&b| b == b'\n') {
93 let line = line.strip_suffix(b"\r").unwrap_or(line);
94 if line.len() > name.len()
95 && line
96 .iter()
97 .zip(name.iter())
98 .all(|(a, b)| a.to_ascii_lowercase() == *b)
99 {
100 let v = line.get(name.len()..)?;
101 let start = v.iter().position(|b| !b.is_ascii_whitespace()).unwrap_or(v.len());
102 return v.get(start..);
103 }
104 }
105 None
106}
107
108pub fn resume_offset(raw: &[u8]) -> Option<usize> {
116 if !looks_truncated(raw) {
117 return None;
118 }
119 let ar = header_value(raw, b"accept-ranges:")?;
120 if !ar.eq_ignore_ascii_case(b"bytes") {
121 return None;
122 }
123 let got = raw.len().checked_sub(body_start(raw)?)?;
124 (got > 0).then_some(got)
125}
126
127pub fn merge_range_response(partial: &[u8], off: usize, resp: &[u8]) -> Option<alloc::vec::Vec<u8>> {
134 let status_ok = resp.starts_with(b"HTTP/1.1 206") || resp.starts_with(b"HTTP/1.0 206");
135 if !status_ok {
136 return None;
137 }
138 let total = content_length(partial)?;
139 let cr = header_value(resp, b"content-range:")?;
140 let cr = cr.strip_prefix(b"bytes ")?;
141 let dash = cr.iter().position(|&b| b == b'-')?;
142 let slash = cr.iter().position(|&b| b == b'/')?;
143 let parse = |s: &[u8]| -> Option<usize> { core::str::from_utf8(s).ok()?.trim().parse().ok() };
144 let start = parse(cr.get(..dash)?)?;
145 let whole = parse(cr.get(slash + 1..)?)?;
146 if start != off || whole != total {
147 return None;
148 }
149 let body = resp.get(body_start(resp)?..)?;
150 let pstart = body_start(partial)?;
151 if partial.len().checked_sub(pstart)? != off {
152 return None;
153 }
154 let mut out = alloc::vec::Vec::with_capacity(partial.len() + body.len());
155 out.extend_from_slice(partial);
156 out.extend_from_slice(body);
157 Some(out)
158}