1use super::error::HttpError;
2use nexus_net::buf::ReadBuf;
3
4pub struct Response<'a> {
6 pub status: u16,
8 pub reason: &'a str,
10 pub version: u8,
12 data: &'a [u8],
13 header_offsets: &'a [(usize, usize, usize, usize)],
14}
15
16impl<'a> Response<'a> {
17 pub fn header(&self, name: &str) -> Option<&'a str> {
22 for &(ns, nl, vs, vl) in self.header_offsets {
23 let hname = &self.data[ns..ns + nl];
24 if hname.eq_ignore_ascii_case(name.as_bytes()) {
25 return std::str::from_utf8(&self.data[vs..vs + vl]).ok();
26 }
27 }
28 None
29 }
30
31 pub fn header_bytes(&self, name: &str) -> Option<&'a [u8]> {
35 for &(ns, nl, vs, vl) in self.header_offsets {
36 let hname = &self.data[ns..ns + nl];
37 if hname.eq_ignore_ascii_case(name.as_bytes()) {
38 return Some(&self.data[vs..vs + vl]);
39 }
40 }
41 None
42 }
43
44 pub fn headers(&self) -> impl Iterator<Item = (&'a str, &'a str)> {
49 self.header_offsets.iter().filter_map(|&(ns, nl, vs, vl)| {
50 let name = std::str::from_utf8(&self.data[ns..ns + nl]).ok()?;
51 let value = std::str::from_utf8(&self.data[vs..vs + vl]).ok()?;
52 Some((name, value))
53 })
54 }
55
56 pub fn header_count(&self) -> usize {
58 self.header_offsets.len()
59 }
60}
61
62impl std::fmt::Debug for Response<'_> {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 f.debug_struct("Response")
65 .field("status", &self.status)
66 .field("reason", &self.reason)
67 .field("version", &self.version)
68 .field("headers", &self.header_count())
69 .finish()
70 }
71}
72
73pub struct ResponseReader {
87 buf: ReadBuf,
88 max_headers: usize,
89 max_head_size: usize,
90 max_body_size: usize,
91 head_len: Option<usize>,
92 header_offsets: Vec<(usize, usize, usize, usize)>,
93 status: u16,
94 reason_start: usize,
95 reason_end: usize,
96 version: u8,
97 cached_content_length: Option<Result<usize, ()>>,
99 cached_is_chunked: bool,
100 last_raw_body_bytes: usize,
103}
104
105impl ResponseReader {
106 #[must_use]
108 pub fn new(capacity: usize) -> Self {
109 Self {
110 buf: ReadBuf::with_capacity(capacity),
111 max_headers: 64,
112 max_head_size: 8192,
113 max_body_size: 0,
114 head_len: None,
115 header_offsets: Vec::with_capacity(16),
116 status: 0,
117 reason_start: 0,
118 reason_end: 0,
119 version: 1,
120 cached_content_length: None,
121 cached_is_chunked: false,
122 last_raw_body_bytes: 0,
123 }
124 }
125
126 #[must_use]
128 pub fn max_headers(mut self, n: usize) -> Self {
129 self.max_headers = n;
130 self
131 }
132
133 #[must_use]
135 pub fn max_head_size(mut self, n: usize) -> Self {
136 self.max_head_size = n;
137 self
138 }
139
140 #[must_use]
145 pub fn max_body_size(mut self, n: usize) -> Self {
146 self.max_body_size = n;
147 self
148 }
149
150 pub fn max_body_size_limit(&self) -> usize {
152 self.max_body_size
153 }
154
155 pub fn read(&mut self, src: &[u8]) -> Result<(), HttpError> {
157 let spare = self.buf.spare();
158 if src.len() > spare.len() {
159 self.buf.compact();
160 let spare = self.buf.spare();
161 if src.len() > spare.len() {
162 return Err(HttpError::BufferFull {
163 needed: src.len(),
164 available: spare.len(),
165 });
166 }
167 }
168 let spare = self.buf.spare();
169 spare[..src.len()].copy_from_slice(src);
170 self.buf.filled(src.len());
171 Ok(())
172 }
173
174 #[inline]
179 pub fn spare(&mut self) -> &mut [u8] {
180 self.buf.spare()
181 }
182
183 #[inline]
185 pub fn filled(&mut self, n: usize) {
186 self.buf.filled(n);
187 }
188
189 pub fn read_from<R: std::io::Read>(&mut self, src: &mut R) -> std::io::Result<usize> {
193 let spare = self.buf.spare();
194 if spare.is_empty() {
195 self.buf.compact();
196 }
197 let spare = self.buf.spare();
198 if spare.is_empty() {
199 return Err(std::io::Error::other("response buffer full"));
200 }
201 let n = src.read(spare)?;
202 self.buf.filled(n);
203 Ok(n)
204 }
205
206 pub fn body_remaining(&self) -> usize {
209 self.head_len
210 .map_or(0, |n| self.buf.data().len().saturating_sub(n))
211 }
212
213 pub fn header(&self, name: &str) -> Option<&str> {
218 self.head_len?;
219 let data = self.buf.data();
220 for &(ns, nl, vs, vl) in &self.header_offsets {
221 if data[ns..ns + nl].eq_ignore_ascii_case(name.as_bytes()) {
222 return std::str::from_utf8(&data[vs..vs + vl]).ok();
223 }
224 }
225 None
226 }
227
228 #[allow(clippy::should_implement_trait)]
230 pub fn next(&mut self) -> Result<Option<Response<'_>>, HttpError> {
231 if self.head_len.is_none() {
232 self.try_parse()?;
233 }
234
235 if self.head_len.is_none() {
236 return Ok(None);
237 }
238
239 let data = self.buf.data();
240 if self.reason_end > data.len() || self.reason_start > self.reason_end {
241 return Err(HttpError::Malformed("reason phrase out of bounds"));
242 }
243 let reason = std::str::from_utf8(&data[self.reason_start..self.reason_end])
244 .map_err(|_| HttpError::Malformed("invalid UTF-8 in reason phrase"))?;
245
246 Ok(Some(Response {
247 status: self.status,
248 reason,
249 version: self.version,
250 data,
251 header_offsets: &self.header_offsets,
252 }))
253 }
254
255 pub fn remainder(&self) -> &[u8] {
257 match self.head_len {
258 Some(n) => &self.buf.data()[n..],
259 None => &[],
260 }
261 }
262
263 pub fn status(&self) -> u16 {
265 self.status
266 }
267
268 pub fn header_count(&self) -> usize {
270 self.header_offsets.len()
271 }
272
273 pub fn content_length(&self) -> Option<Result<usize, ()>> {
276 self.cached_content_length
277 }
278
279 pub fn is_chunked(&self) -> bool {
281 self.cached_is_chunked
282 }
283
284 pub fn set_body_consumed(&mut self, raw_bytes: usize) {
292 self.last_raw_body_bytes = raw_bytes;
293 }
294
295 pub fn consume_response(&mut self) {
301 if let Some(head_len) = self.head_len {
302 let consumed = head_len + self.last_raw_body_bytes;
303 if consumed <= self.buf.data().len() {
304 self.buf.advance(consumed);
305 } else {
306 self.buf.clear();
307 }
308 }
309 self.head_len = None;
310 self.header_offsets.clear();
311 self.cached_content_length = None;
312 self.cached_is_chunked = false;
313 self.last_raw_body_bytes = 0;
314 }
315
316 pub fn reset(&mut self) {
318 self.buf.clear();
319 self.head_len = None;
320 self.header_offsets.clear();
321 self.cached_content_length = None;
322 self.cached_is_chunked = false;
323 self.last_raw_body_bytes = 0;
324 }
325
326 fn try_parse(&mut self) -> Result<(), HttpError> {
327 let data = self.buf.data();
328 if data.is_empty() {
329 return Ok(());
330 }
331 if data.len() > self.max_head_size {
332 return Err(HttpError::HeadTooLarge {
333 max: self.max_head_size,
334 });
335 }
336
337 let mut stack_headers = [httparse::EMPTY_HEADER; 64];
338 let mut heap_headers;
339 let headers: &mut [httparse::Header<'_>] = if self.max_headers <= 64 {
340 &mut stack_headers[..self.max_headers]
341 } else {
342 heap_headers = vec![httparse::EMPTY_HEADER; self.max_headers];
343 &mut heap_headers
344 };
345 let mut resp = httparse::Response::new(headers);
346
347 match resp.parse(data) {
348 Ok(httparse::Status::Complete(head_len)) => {
349 let status = resp
350 .code
351 .ok_or(HttpError::Malformed("missing status code"))?;
352 let reason = resp
353 .reason
354 .ok_or(HttpError::Malformed("missing reason phrase"))?;
355 let version = resp
356 .version
357 .ok_or(HttpError::Malformed("missing HTTP version"))?;
358
359 let data_ptr = data.as_ptr();
360 self.status = status;
361 self.reason_start = unsafe { reason.as_ptr().offset_from(data_ptr) } as usize;
363 self.reason_end = self.reason_start + reason.len();
364 self.version = version;
365
366 self.header_offsets.clear();
367 self.cached_content_length = None;
368 self.cached_is_chunked = false;
369
370 for h in resp.headers.iter() {
371 let ns = unsafe { h.name.as_ptr().offset_from(data_ptr) } as usize;
373 let nl = h.name.len();
374 let vs = unsafe { h.value.as_ptr().offset_from(data_ptr) } as usize;
376 let vl = h.value.len();
377 debug_assert!(ns + nl <= data.len(), "header name offset out of bounds");
378 debug_assert!(vs + vl <= data.len(), "header value offset out of bounds");
379 self.header_offsets.push((ns, nl, vs, vl));
380
381 if h.name.eq_ignore_ascii_case("Content-Length") {
384 self.cached_content_length = Some(
385 std::str::from_utf8(h.value)
386 .ok()
387 .and_then(|v| v.trim().parse::<usize>().ok())
388 .ok_or(()),
389 );
390 } else if h.name.eq_ignore_ascii_case("Transfer-Encoding")
391 && let Ok(te) = std::str::from_utf8(h.value)
392 {
393 self.cached_is_chunked = te
394 .split(',')
395 .any(|t| t.trim().eq_ignore_ascii_case("chunked"));
396 }
397 }
398
399 self.head_len = Some(head_len);
400 Ok(())
401 }
402 Ok(httparse::Status::Partial) => Ok(()),
403 Err(httparse::Error::TooManyHeaders) => Err(HttpError::TooManyHeaders),
404 Err(_) => Err(HttpError::Malformed("httparse rejected response")),
405 }
406 }
407}
408
409impl crate::ParserSink for ResponseReader {
413 #[inline]
414 fn spare(&mut self) -> &mut [u8] {
415 ResponseReader::spare(self)
416 }
417
418 #[inline]
419 fn filled(&mut self, n: usize) {
420 ResponseReader::filled(self, n);
421 }
422}
423
424fn validate_header_value(s: &str) -> Result<(), super::error::HttpError> {
426 if s.bytes().any(|b| b == b'\r' || b == b'\n') {
427 return Err(super::error::HttpError::InvalidHeaderValue);
428 }
429 Ok(())
430}
431
432fn copy_to(dst: &mut [u8], offset: usize, src: &[u8]) -> Result<usize, super::error::HttpError> {
433 let end = offset + src.len();
434 if end > dst.len() {
435 return Err(super::error::HttpError::BufferTooSmall {
436 needed: end,
437 available: dst.len(),
438 });
439 }
440 dst[offset..end].copy_from_slice(src);
441 Ok(src.len())
442}
443
444fn write_u16(dst: &mut [u8], offset: usize, val: u16) -> Result<usize, super::error::HttpError> {
445 debug_assert!(
446 val >= 100 && val <= 999,
447 "HTTP status must be 3 digits: {val}"
448 );
449 if offset + 3 > dst.len() {
450 return Err(super::error::HttpError::BufferTooSmall {
451 needed: offset + 3,
452 available: dst.len(),
453 });
454 }
455 dst[offset] = (val / 100) as u8 + b'0';
456 dst[offset + 1] = ((val / 10) % 10) as u8 + b'0';
457 dst[offset + 2] = (val % 10) as u8 + b'0';
458 Ok(3)
459}
460
461#[must_use]
463pub fn request_size(method: &str, path: &str, headers: &[(&str, &str)]) -> usize {
464 let mut size = method.len() + 1 + path.len() + 11; for &(name, value) in headers {
466 size += name.len() + 2 + value.len() + 2; }
468 size + 2 }
470
471#[must_use]
473pub fn response_size(reason: &str, headers: &[(&str, &str)]) -> usize {
474 let mut size = 9 + 3 + 1 + reason.len() + 2; for &(name, value) in headers {
476 size += name.len() + 2 + value.len() + 2;
477 }
478 size + 2
479}
480
481pub fn write_request(
486 method: &str,
487 path: &str,
488 headers: &[(&str, &str)],
489 dst: &mut [u8],
490) -> Result<usize, super::error::HttpError> {
491 let mut offset = 0;
492 offset += copy_to(dst, offset, method.as_bytes())?;
493 offset += copy_to(dst, offset, b" ")?;
494 offset += copy_to(dst, offset, path.as_bytes())?;
495 offset += copy_to(dst, offset, b" HTTP/1.1\r\n")?;
496 for &(name, value) in headers {
497 validate_header_value(name)?;
498 validate_header_value(value)?;
499 offset += copy_to(dst, offset, name.as_bytes())?;
500 offset += copy_to(dst, offset, b": ")?;
501 offset += copy_to(dst, offset, value.as_bytes())?;
502 offset += copy_to(dst, offset, b"\r\n")?;
503 }
504 offset += copy_to(dst, offset, b"\r\n")?;
505 Ok(offset)
506}
507
508pub fn write_response(
513 status: u16,
514 reason: &str,
515 headers: &[(&str, &str)],
516 dst: &mut [u8],
517) -> Result<usize, super::error::HttpError> {
518 let mut offset = 0;
519 offset += copy_to(dst, offset, b"HTTP/1.1 ")?;
520 offset += write_u16(dst, offset, status)?;
521 offset += copy_to(dst, offset, b" ")?;
522 offset += copy_to(dst, offset, reason.as_bytes())?;
523 offset += copy_to(dst, offset, b"\r\n")?;
524 for &(name, value) in headers {
525 validate_header_value(name)?;
526 validate_header_value(value)?;
527 offset += copy_to(dst, offset, name.as_bytes())?;
528 offset += copy_to(dst, offset, b": ")?;
529 offset += copy_to(dst, offset, value.as_bytes())?;
530 offset += copy_to(dst, offset, b"\r\n")?;
531 }
532 offset += copy_to(dst, offset, b"\r\n")?;
533 Ok(offset)
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539
540 #[test]
541 fn basic_101_response() {
542 let mut r = ResponseReader::new(4096);
543 r.read(b"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n").unwrap();
544 let resp = r.next().unwrap().unwrap();
545 assert_eq!(resp.status, 101);
546 assert_eq!(resp.reason, "Switching Protocols");
547 assert_eq!(resp.version, 1);
548 assert_eq!(resp.header("Upgrade"), Some("websocket"));
549 assert_eq!(resp.header("Connection"), Some("Upgrade"));
550 }
551
552 #[test]
553 fn basic_200_response() {
554 let mut r = ResponseReader::new(4096);
555 r.read(b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello")
556 .unwrap();
557 let resp = r.next().unwrap().unwrap();
558 assert_eq!(resp.status, 200);
559 assert_eq!(resp.reason, "OK");
560 assert_eq!(resp.header("Content-Length"), Some("5"));
561 }
562
563 #[test]
564 fn response_remainder() {
565 let mut r = ResponseReader::new(4096);
566 r.read(b"HTTP/1.1 200 OK\r\n\r\nbody data").unwrap();
567 let _resp = r.next().unwrap().unwrap();
568 assert_eq!(r.remainder(), b"body data");
569 }
570
571 #[test]
572 fn partial_response() {
573 let mut r = ResponseReader::new(4096);
574 r.read(b"HTTP/1.1 200 OK\r\nHost: ").unwrap();
575 assert!(r.next().unwrap().is_none());
576 r.read(b"example.com\r\n\r\n").unwrap();
577 let resp = r.next().unwrap().unwrap();
578 assert_eq!(resp.status, 200);
579 assert_eq!(resp.header("Host"), Some("example.com"));
580 }
581
582 #[test]
583 fn write_request_round_trip() {
584 use crate::http::RequestReader;
585 let mut dst = [0u8; 256];
586 let n = write_request(
587 "GET",
588 "/ws",
589 &[("Host", "localhost:8080"), ("Upgrade", "websocket")],
590 &mut dst,
591 )
592 .unwrap();
593
594 let mut r = RequestReader::new(4096);
595 r.read(&dst[..n]).unwrap();
596 let req = r.next().unwrap().unwrap();
597 assert_eq!(req.method, "GET");
598 assert_eq!(req.path, "/ws");
599 assert_eq!(req.header("Upgrade"), Some("websocket"));
600 }
601
602 #[test]
603 fn write_response_round_trip() {
604 let mut dst = [0u8; 256];
605 let n = write_response(
606 101,
607 "Switching Protocols",
608 &[("Upgrade", "websocket"), ("Connection", "Upgrade")],
609 &mut dst,
610 )
611 .unwrap();
612
613 let mut r = ResponseReader::new(4096);
614 r.read(&dst[..n]).unwrap();
615 let resp = r.next().unwrap().unwrap();
616 assert_eq!(resp.status, 101);
617 assert_eq!(resp.header("Connection"), Some("Upgrade"));
618 }
619
620 #[test]
621 fn reset_then_reuse() {
622 let mut r = ResponseReader::new(4096);
623 r.read(b"HTTP/1.1 200 OK\r\n\r\n").unwrap();
624 let _ = r.next().unwrap().unwrap();
625 r.reset();
626 r.read(b"HTTP/1.1 404 Not Found\r\n\r\n").unwrap();
627 let resp = r.next().unwrap().unwrap();
628 assert_eq!(resp.status, 404);
629 }
630}