stackforge_core/layer/http/
mod.rs1pub mod builder;
39pub mod detection;
40pub mod request;
41pub mod response;
42
43pub use builder::{HttpRequestBuilder, HttpResponseBuilder};
44pub use request::HttpRequest;
45pub use response::HttpResponse;
46
47use crate::layer::field::{FieldError, FieldValue};
48use crate::layer::{Layer, LayerIndex, LayerKind};
49
50pub const HTTP_FIELD_NAMES: &[&str] = &[
56 "method",
57 "uri",
58 "version",
59 "status_code",
60 "reason",
61 "headers",
62 "body",
63];
64
65#[derive(Debug, Clone)]
74pub struct HttpLayer {
75 pub index: LayerIndex,
78}
79
80impl HttpLayer {
81 #[must_use]
83 pub fn new(index: LayerIndex) -> Self {
84 Self { index }
85 }
86
87 #[inline]
93 fn slice<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
94 self.index.slice(buf)
95 }
96
97 #[must_use]
103 pub fn is_request(&self, buf: &[u8]) -> bool {
104 detection::is_http_request(self.slice(buf))
105 }
106
107 #[must_use]
109 pub fn is_response(&self, buf: &[u8]) -> bool {
110 detection::is_http_response(self.slice(buf))
111 }
112
113 #[must_use]
119 pub fn first_line<'a>(&self, buf: &'a [u8]) -> Option<&'a [u8]> {
120 let slice = self.slice(buf);
121 let crlf_pos = slice.windows(2).position(|w| w == b"\r\n")?;
122 Some(&slice[..crlf_pos])
123 }
124
125 #[must_use]
129 pub fn headers_end(buf: &[u8], start: usize) -> Option<usize> {
130 let search_area = buf.get(start..)?;
131 search_area
132 .windows(4)
133 .position(|w| w == b"\r\n\r\n")
134 .map(|rel| start + rel + 4)
135 }
136
137 #[must_use]
145 pub fn method<'a>(&self, buf: &'a [u8]) -> Option<&'a str> {
146 if !self.is_request(buf) {
147 return None;
148 }
149 let line = std::str::from_utf8(self.first_line(buf)?).ok()?;
150 line.split(' ').next()
151 }
152
153 #[must_use]
157 pub fn uri<'a>(&self, buf: &'a [u8]) -> Option<&'a str> {
158 if !self.is_request(buf) {
159 return None;
160 }
161 let slice = self.slice(buf);
162 let text = std::str::from_utf8(slice).ok()?;
163 let line_end = text.find("\r\n")?;
164 let request_line = &text[..line_end];
165 let mut parts = request_line.splitn(3, ' ');
166 parts.next(); parts.next() }
169
170 #[must_use]
173 pub fn http_version<'a>(&self, buf: &'a [u8]) -> Option<&'a str> {
174 let slice = self.slice(buf);
175 let text = std::str::from_utf8(slice).ok()?;
176 let line_end = text.find("\r\n")?;
177 let first_line = &text[..line_end];
178
179 if self.is_response(buf) {
180 first_line.split(' ').next()
182 } else {
183 first_line.rsplit(' ').next()
185 }
186 }
187
188 #[must_use]
196 pub fn status_code(&self, buf: &[u8]) -> Option<u16> {
197 if !self.is_response(buf) {
198 return None;
199 }
200 let slice = self.slice(buf);
201 let text = std::str::from_utf8(slice).ok()?;
202 let line_end = text.find("\r\n")?;
203 let status_line = &text[..line_end];
204 let mut parts = status_line.splitn(3, ' ');
205 parts.next(); parts.next()?.parse().ok()
207 }
208
209 #[must_use]
213 pub fn reason<'a>(&self, buf: &'a [u8]) -> Option<&'a str> {
214 if !self.is_response(buf) {
215 return None;
216 }
217 let slice = self.slice(buf);
218 let text = std::str::from_utf8(slice).ok()?;
219 let line_end = text.find("\r\n")?;
220 let status_line = &text[..line_end];
221 let mut parts = status_line.splitn(3, ' ');
222 parts.next(); parts.next(); parts.next()
225 }
226
227 #[must_use]
235 pub fn header_value<'a>(&self, buf: &'a [u8], name: &str) -> Option<&'a str> {
236 let slice = self.slice(buf);
237 let text = std::str::from_utf8(slice).ok()?;
238
239 let first_crlf = text.find("\r\n")?;
241 let after_first = first_crlf + 2;
242 let search_start = first_crlf; let headers_end = text[search_start..]
246 .find("\r\n\r\n")
247 .map(|rel| search_start + rel)?;
248
249 if after_first > headers_end {
250 return None; }
252 let header_section = &text[after_first..headers_end];
253 for line in header_section.split("\r\n") {
254 if let Some(colon) = line.find(':') {
255 let hdr_name = &line[..colon];
256 if hdr_name.eq_ignore_ascii_case(name) {
257 return Some(line[colon + 1..].trim());
258 }
259 }
260 }
261 None
262 }
263
264 #[must_use]
269 pub fn body_offset(&self, buf: &[u8]) -> Option<usize> {
270 Self::headers_end(buf, self.index.start)
271 }
272
273 #[must_use]
277 pub fn content_length(&self, buf: &[u8]) -> Option<usize> {
278 self.header_value(buf, "content-length")
279 .and_then(|v| v.parse().ok())
280 }
281
282 #[must_use]
284 pub fn is_chunked(&self, buf: &[u8]) -> bool {
285 self.header_value(buf, "transfer-encoding")
286 .is_some_and(|v| v.eq_ignore_ascii_case("chunked"))
287 }
288
289 #[must_use]
297 pub fn summary_str(&self, buf: &[u8]) -> String {
298 if self.is_request(buf) {
299 let method = self.method(buf).unwrap_or("?");
300 let uri = self.uri(buf).unwrap_or("?");
301 let version = self.http_version(buf).unwrap_or("HTTP/?");
302 format!("HTTP {method} {uri} {version}")
303 } else if self.is_response(buf) {
304 let code = self.status_code(buf).unwrap_or(0);
305 let reason = self.reason(buf).unwrap_or("?");
306 format!("HTTP {code} {reason}")
307 } else {
308 "HTTP".to_owned()
309 }
310 }
311
312 #[must_use]
317 pub fn http_header_len(&self, buf: &[u8]) -> usize {
318 match self.body_offset(buf) {
319 Some(body_start) => body_start.saturating_sub(self.index.start),
320 None => 0,
321 }
322 }
323
324 #[must_use]
330 pub fn field_names(&self) -> &'static [&'static str] {
331 HTTP_FIELD_NAMES
332 }
333
334 #[must_use]
347 pub fn get_field(&self, buf: &[u8], name: &str) -> Option<Result<FieldValue, FieldError>> {
348 match name {
349 "method" => {
350 let v = self.method(buf)?;
351 Some(Ok(FieldValue::Bytes(v.as_bytes().to_vec())))
352 },
353 "uri" => {
354 let v = self.uri(buf)?;
355 Some(Ok(FieldValue::Bytes(v.as_bytes().to_vec())))
356 },
357 "version" => {
358 let v = self.http_version(buf)?;
359 Some(Ok(FieldValue::Bytes(v.as_bytes().to_vec())))
360 },
361 "status_code" => {
362 let v = self.status_code(buf)?;
363 Some(Ok(FieldValue::U16(v)))
364 },
365 "reason" => {
366 let v = self.reason(buf)?;
367 Some(Ok(FieldValue::Bytes(v.as_bytes().to_vec())))
368 },
369 _ => None,
370 }
371 }
372}
373
374impl Layer for HttpLayer {
379 fn kind(&self) -> LayerKind {
380 LayerKind::Http
381 }
382
383 fn summary(&self, data: &[u8]) -> String {
384 self.summary_str(data)
385 }
386
387 fn header_len(&self, data: &[u8]) -> usize {
391 self.http_header_len(data)
392 }
393
394 fn hashret(&self, _data: &[u8]) -> Vec<u8> {
395 vec![]
397 }
398
399 fn answers(&self, _data: &[u8], _other: &Self, _other_data: &[u8]) -> bool {
400 false
401 }
402
403 fn extract_padding<'a>(&self, data: &'a [u8]) -> (&'a [u8], &'a [u8]) {
404 let slice = self.index.slice(data);
408 (slice, &[])
409 }
410
411 fn field_names(&self) -> &'static [&'static str] {
412 HTTP_FIELD_NAMES
413 }
414}
415
416#[cfg(test)]
421mod tests {
422 use super::*;
423
424 fn make_layer(buf: &[u8]) -> HttpLayer {
425 HttpLayer {
426 index: LayerIndex::new(LayerKind::Http, 0, buf.len()),
427 }
428 }
429
430 #[test]
433 fn test_request_is_request() {
434 let raw = b"GET / HTTP/1.1\r\nHost: h\r\n\r\n";
435 let layer = make_layer(raw);
436 assert!(layer.is_request(raw));
437 assert!(!layer.is_response(raw));
438 }
439
440 #[test]
441 fn test_request_method() {
442 let raw = b"POST /upload HTTP/1.1\r\n\r\n";
443 let layer = make_layer(raw);
444 assert_eq!(layer.method(raw), Some("POST"));
445 }
446
447 #[test]
448 fn test_request_uri() {
449 let raw = b"GET /path/to/resource HTTP/1.1\r\n\r\n";
450 let layer = make_layer(raw);
451 assert_eq!(layer.uri(raw), Some("/path/to/resource"));
452 }
453
454 #[test]
455 fn test_request_version() {
456 let raw = b"GET / HTTP/1.0\r\n\r\n";
457 let layer = make_layer(raw);
458 assert_eq!(layer.http_version(raw), Some("HTTP/1.0"));
459 }
460
461 #[test]
462 fn test_request_header_value() {
463 let raw = b"GET / HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n";
464 let layer = make_layer(raw);
465 assert_eq!(layer.header_value(raw, "host"), Some("example.com"));
466 assert_eq!(layer.header_value(raw, "Host"), Some("example.com"));
467 assert_eq!(layer.header_value(raw, "Accept"), Some("*/*"));
468 assert_eq!(layer.header_value(raw, "X-Missing"), None);
469 }
470
471 #[test]
472 fn test_request_body_offset() {
473 let raw = b"GET / HTTP/1.1\r\nHost: h\r\n\r\nbody data";
474 let layer = make_layer(raw);
475 let offset = layer.body_offset(raw).unwrap();
476 assert_eq!(&raw[offset..], b"body data");
477 }
478
479 #[test]
480 fn test_request_content_length() {
481 let raw = b"POST / HTTP/1.1\r\nContent-Length: 42\r\n\r\n";
482 let layer = make_layer(raw);
483 assert_eq!(layer.content_length(raw), Some(42));
484 }
485
486 #[test]
487 fn test_request_is_chunked() {
488 let raw = b"POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n";
489 let layer = make_layer(raw);
490 assert!(layer.is_chunked(raw));
491 }
492
493 #[test]
494 fn test_request_not_chunked() {
495 let raw = b"POST / HTTP/1.1\r\nContent-Length: 5\r\n\r\n";
496 let layer = make_layer(raw);
497 assert!(!layer.is_chunked(raw));
498 }
499
500 #[test]
501 fn test_request_summary() {
502 let raw = b"DELETE /res HTTP/1.1\r\n\r\n";
503 let layer = make_layer(raw);
504 assert_eq!(layer.summary_str(raw), "HTTP DELETE /res HTTP/1.1");
505 }
506
507 #[test]
508 fn test_request_header_len() {
509 let headers = b"GET / HTTP/1.1\r\nHost: h\r\n\r\n";
510 let body = b"BODY";
511 let mut raw = headers.to_vec();
512 raw.extend_from_slice(body);
513 let layer = make_layer(&raw);
514 assert_eq!(layer.http_header_len(&raw), headers.len());
516 }
517
518 #[test]
519 fn test_request_get_field_method() {
520 let raw = b"PUT /x HTTP/1.1\r\n\r\n";
521 let layer = make_layer(raw);
522 if let Some(Ok(FieldValue::Bytes(v))) = layer.get_field(raw, "method") {
523 assert_eq!(v, b"PUT");
524 } else {
525 panic!("expected Bytes value for method");
526 }
527 }
528
529 #[test]
530 fn test_request_get_field_uri() {
531 let raw = b"GET /hello HTTP/1.1\r\n\r\n";
532 let layer = make_layer(raw);
533 if let Some(Ok(FieldValue::Bytes(v))) = layer.get_field(raw, "uri") {
534 assert_eq!(v, b"/hello");
535 } else {
536 panic!("expected Bytes value for uri");
537 }
538 }
539
540 #[test]
541 fn test_request_get_field_version() {
542 let raw = b"GET / HTTP/1.0\r\n\r\n";
543 let layer = make_layer(raw);
544 if let Some(Ok(FieldValue::Bytes(v))) = layer.get_field(raw, "version") {
545 assert_eq!(v, b"HTTP/1.0");
546 } else {
547 panic!("expected Bytes value for version");
548 }
549 }
550
551 #[test]
554 fn test_response_is_response() {
555 let raw = b"HTTP/1.1 200 OK\r\n\r\n";
556 let layer = make_layer(raw);
557 assert!(layer.is_response(raw));
558 assert!(!layer.is_request(raw));
559 }
560
561 #[test]
562 fn test_response_status_code() {
563 let raw = b"HTTP/1.1 404 Not Found\r\n\r\n";
564 let layer = make_layer(raw);
565 assert_eq!(layer.status_code(raw), Some(404));
566 }
567
568 #[test]
569 fn test_response_reason() {
570 let raw = b"HTTP/1.1 301 Moved Permanently\r\n\r\n";
571 let layer = make_layer(raw);
572 assert_eq!(layer.reason(raw), Some("Moved Permanently"));
573 }
574
575 #[test]
576 fn test_response_version() {
577 let raw = b"HTTP/1.0 200 OK\r\n\r\n";
578 let layer = make_layer(raw);
579 assert_eq!(layer.http_version(raw), Some("HTTP/1.0"));
580 }
581
582 #[test]
583 fn test_response_header_value() {
584 let raw = b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n";
585 let layer = make_layer(raw);
586 assert_eq!(
587 layer.header_value(raw, "content-type"),
588 Some("application/json")
589 );
590 }
591
592 #[test]
593 fn test_response_body_offset() {
594 let raw = b"HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest";
595 let layer = make_layer(raw);
596 let offset = layer.body_offset(raw).unwrap();
597 assert_eq!(&raw[offset..], b"test");
598 }
599
600 #[test]
601 fn test_response_summary() {
602 let raw = b"HTTP/1.1 500 Internal Server Error\r\n\r\n";
603 let layer = make_layer(raw);
604 assert_eq!(layer.summary_str(raw), "HTTP 500 Internal Server Error");
605 }
606
607 #[test]
608 fn test_response_get_field_status_code() {
609 let raw = b"HTTP/1.1 200 OK\r\n\r\n";
610 let layer = make_layer(raw);
611 if let Some(Ok(FieldValue::U16(code))) = layer.get_field(raw, "status_code") {
612 assert_eq!(code, 200);
613 } else {
614 panic!("expected U16 for status_code");
615 }
616 }
617
618 #[test]
619 fn test_response_get_field_reason() {
620 let raw = b"HTTP/1.1 200 OK\r\n\r\n";
621 let layer = make_layer(raw);
622 if let Some(Ok(FieldValue::Bytes(v))) = layer.get_field(raw, "reason") {
623 assert_eq!(v, b"OK");
624 } else {
625 panic!("expected Bytes for reason");
626 }
627 }
628
629 #[test]
632 fn test_layer_kind() {
633 let raw = b"GET / HTTP/1.1\r\n\r\n";
634 let layer = make_layer(raw);
635 assert_eq!(layer.kind(), LayerKind::Http);
636 }
637
638 #[test]
639 fn test_layer_field_names() {
640 let raw = b"GET / HTTP/1.1\r\n\r\n";
641 let layer = make_layer(raw);
642 assert_eq!(layer.field_names(), HTTP_FIELD_NAMES);
643 }
644
645 #[test]
648 fn test_builder_and_layer_roundtrip_request() {
649 let raw = HttpRequestBuilder::new()
650 .method("PATCH")
651 .uri("/resource/1")
652 .header("Authorization", "Bearer token123")
653 .header("Content-Type", "application/json")
654 .body(b"{\"key\":\"val\"}".to_vec())
655 .build();
656
657 let layer = make_layer(&raw);
658 assert_eq!(layer.method(&raw), Some("PATCH"));
659 assert_eq!(layer.uri(&raw), Some("/resource/1"));
660 assert_eq!(layer.http_version(&raw), Some("HTTP/1.1"));
661 assert_eq!(
662 layer.header_value(&raw, "authorization"),
663 Some("Bearer token123")
664 );
665 }
666
667 #[test]
668 fn test_builder_and_layer_roundtrip_response() {
669 let raw = HttpResponseBuilder::new()
670 .status(201, "Created")
671 .header("Location", "/resource/1")
672 .body(b"{}".to_vec())
673 .build();
674
675 let layer = make_layer(&raw);
676 assert_eq!(layer.status_code(&raw), Some(201));
677 assert_eq!(layer.reason(&raw), Some("Created"));
678 assert_eq!(layer.header_value(&raw, "Location"), Some("/resource/1"));
679 let body = &raw[layer.body_offset(&raw).unwrap()..];
680 assert_eq!(body, b"{}");
681 }
682}