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 pub fn new(index: LayerIndex) -> Self {
83 Self { index }
84 }
85
86 #[inline]
92 fn slice<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
93 self.index.slice(buf)
94 }
95
96 pub fn is_request(&self, buf: &[u8]) -> bool {
102 detection::is_http_request(self.slice(buf))
103 }
104
105 pub fn is_response(&self, buf: &[u8]) -> bool {
107 detection::is_http_response(self.slice(buf))
108 }
109
110 pub fn first_line<'a>(&self, buf: &'a [u8]) -> Option<&'a [u8]> {
116 let slice = self.slice(buf);
117 let crlf_pos = slice.windows(2).position(|w| w == b"\r\n")?;
118 Some(&slice[..crlf_pos])
119 }
120
121 pub fn headers_end(buf: &[u8], start: usize) -> Option<usize> {
125 let search_area = buf.get(start..)?;
126 search_area
127 .windows(4)
128 .position(|w| w == b"\r\n\r\n")
129 .map(|rel| start + rel + 4)
130 }
131
132 pub fn method<'a>(&self, buf: &'a [u8]) -> Option<&'a str> {
140 if !self.is_request(buf) {
141 return None;
142 }
143 let line = std::str::from_utf8(self.first_line(buf)?).ok()?;
144 line.split(' ').next()
145 }
146
147 pub fn uri<'a>(&self, buf: &'a [u8]) -> Option<&'a str> {
151 if !self.is_request(buf) {
152 return None;
153 }
154 let slice = self.slice(buf);
155 let text = std::str::from_utf8(slice).ok()?;
156 let line_end = text.find("\r\n")?;
157 let request_line = &text[..line_end];
158 let mut parts = request_line.splitn(3, ' ');
159 parts.next(); parts.next() }
162
163 pub fn http_version<'a>(&self, buf: &'a [u8]) -> Option<&'a str> {
166 let slice = self.slice(buf);
167 let text = std::str::from_utf8(slice).ok()?;
168 let line_end = text.find("\r\n")?;
169 let first_line = &text[..line_end];
170
171 if self.is_response(buf) {
172 first_line.split(' ').next()
174 } else {
175 first_line.rsplitn(2, ' ').next()
177 }
178 }
179
180 pub fn status_code(&self, buf: &[u8]) -> Option<u16> {
188 if !self.is_response(buf) {
189 return None;
190 }
191 let slice = self.slice(buf);
192 let text = std::str::from_utf8(slice).ok()?;
193 let line_end = text.find("\r\n")?;
194 let status_line = &text[..line_end];
195 let mut parts = status_line.splitn(3, ' ');
196 parts.next(); parts.next()?.parse().ok()
198 }
199
200 pub fn reason<'a>(&self, buf: &'a [u8]) -> Option<&'a str> {
204 if !self.is_response(buf) {
205 return None;
206 }
207 let slice = self.slice(buf);
208 let text = std::str::from_utf8(slice).ok()?;
209 let line_end = text.find("\r\n")?;
210 let status_line = &text[..line_end];
211 let mut parts = status_line.splitn(3, ' ');
212 parts.next(); parts.next(); parts.next()
215 }
216
217 pub fn header_value<'a>(&self, buf: &'a [u8], name: &str) -> Option<&'a str> {
225 let slice = self.slice(buf);
226 let text = std::str::from_utf8(slice).ok()?;
227
228 let first_crlf = text.find("\r\n")?;
230 let after_first = first_crlf + 2;
231 let search_start = first_crlf; let headers_end = text[search_start..]
235 .find("\r\n\r\n")
236 .map(|rel| search_start + rel)?;
237
238 if after_first > headers_end {
239 return None; }
241 let header_section = &text[after_first..headers_end];
242 for line in header_section.split("\r\n") {
243 if let Some(colon) = line.find(':') {
244 let hdr_name = &line[..colon];
245 if hdr_name.eq_ignore_ascii_case(name) {
246 return Some(line[colon + 1..].trim());
247 }
248 }
249 }
250 None
251 }
252
253 pub fn body_offset(&self, buf: &[u8]) -> Option<usize> {
258 Self::headers_end(buf, self.index.start)
259 }
260
261 pub fn content_length(&self, buf: &[u8]) -> Option<usize> {
265 self.header_value(buf, "content-length")
266 .and_then(|v| v.parse().ok())
267 }
268
269 pub fn is_chunked(&self, buf: &[u8]) -> bool {
271 self.header_value(buf, "transfer-encoding")
272 .map(|v| v.eq_ignore_ascii_case("chunked"))
273 .unwrap_or(false)
274 }
275
276 pub fn summary_str(&self, buf: &[u8]) -> String {
284 if self.is_request(buf) {
285 let method = self.method(buf).unwrap_or("?");
286 let uri = self.uri(buf).unwrap_or("?");
287 let version = self.http_version(buf).unwrap_or("HTTP/?");
288 format!("HTTP {} {} {}", method, uri, version)
289 } else if self.is_response(buf) {
290 let code = self.status_code(buf).unwrap_or(0);
291 let reason = self.reason(buf).unwrap_or("?");
292 format!("HTTP {} {}", code, reason)
293 } else {
294 "HTTP".to_owned()
295 }
296 }
297
298 pub fn http_header_len(&self, buf: &[u8]) -> usize {
303 match self.body_offset(buf) {
304 Some(body_start) => body_start.saturating_sub(self.index.start),
305 None => 0,
306 }
307 }
308
309 pub fn field_names(&self) -> &'static [&'static str] {
315 HTTP_FIELD_NAMES
316 }
317
318 pub fn get_field(&self, buf: &[u8], name: &str) -> Option<Result<FieldValue, FieldError>> {
331 match name {
332 "method" => {
333 let v = self.method(buf)?;
334 Some(Ok(FieldValue::Bytes(v.as_bytes().to_vec())))
335 }
336 "uri" => {
337 let v = self.uri(buf)?;
338 Some(Ok(FieldValue::Bytes(v.as_bytes().to_vec())))
339 }
340 "version" => {
341 let v = self.http_version(buf)?;
342 Some(Ok(FieldValue::Bytes(v.as_bytes().to_vec())))
343 }
344 "status_code" => {
345 let v = self.status_code(buf)?;
346 Some(Ok(FieldValue::U16(v)))
347 }
348 "reason" => {
349 let v = self.reason(buf)?;
350 Some(Ok(FieldValue::Bytes(v.as_bytes().to_vec())))
351 }
352 _ => None,
353 }
354 }
355}
356
357impl Layer for HttpLayer {
362 fn kind(&self) -> LayerKind {
363 LayerKind::Http
364 }
365
366 fn summary(&self, data: &[u8]) -> String {
367 self.summary_str(data)
368 }
369
370 fn header_len(&self, data: &[u8]) -> usize {
374 self.http_header_len(data)
375 }
376
377 fn hashret(&self, _data: &[u8]) -> Vec<u8> {
378 vec![]
380 }
381
382 fn answers(&self, _data: &[u8], _other: &Self, _other_data: &[u8]) -> bool {
383 false
384 }
385
386 fn extract_padding<'a>(&self, data: &'a [u8]) -> (&'a [u8], &'a [u8]) {
387 let slice = self.index.slice(data);
391 (slice, &[])
392 }
393
394 fn field_names(&self) -> &'static [&'static str] {
395 HTTP_FIELD_NAMES
396 }
397}
398
399#[cfg(test)]
404mod tests {
405 use super::*;
406
407 fn make_layer(buf: &[u8]) -> HttpLayer {
408 HttpLayer {
409 index: LayerIndex::new(LayerKind::Http, 0, buf.len()),
410 }
411 }
412
413 #[test]
416 fn test_request_is_request() {
417 let raw = b"GET / HTTP/1.1\r\nHost: h\r\n\r\n";
418 let layer = make_layer(raw);
419 assert!(layer.is_request(raw));
420 assert!(!layer.is_response(raw));
421 }
422
423 #[test]
424 fn test_request_method() {
425 let raw = b"POST /upload HTTP/1.1\r\n\r\n";
426 let layer = make_layer(raw);
427 assert_eq!(layer.method(raw), Some("POST"));
428 }
429
430 #[test]
431 fn test_request_uri() {
432 let raw = b"GET /path/to/resource HTTP/1.1\r\n\r\n";
433 let layer = make_layer(raw);
434 assert_eq!(layer.uri(raw), Some("/path/to/resource"));
435 }
436
437 #[test]
438 fn test_request_version() {
439 let raw = b"GET / HTTP/1.0\r\n\r\n";
440 let layer = make_layer(raw);
441 assert_eq!(layer.http_version(raw), Some("HTTP/1.0"));
442 }
443
444 #[test]
445 fn test_request_header_value() {
446 let raw = b"GET / HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n";
447 let layer = make_layer(raw);
448 assert_eq!(layer.header_value(raw, "host"), Some("example.com"));
449 assert_eq!(layer.header_value(raw, "Host"), Some("example.com"));
450 assert_eq!(layer.header_value(raw, "Accept"), Some("*/*"));
451 assert_eq!(layer.header_value(raw, "X-Missing"), None);
452 }
453
454 #[test]
455 fn test_request_body_offset() {
456 let raw = b"GET / HTTP/1.1\r\nHost: h\r\n\r\nbody data";
457 let layer = make_layer(raw);
458 let offset = layer.body_offset(raw).unwrap();
459 assert_eq!(&raw[offset..], b"body data");
460 }
461
462 #[test]
463 fn test_request_content_length() {
464 let raw = b"POST / HTTP/1.1\r\nContent-Length: 42\r\n\r\n";
465 let layer = make_layer(raw);
466 assert_eq!(layer.content_length(raw), Some(42));
467 }
468
469 #[test]
470 fn test_request_is_chunked() {
471 let raw = b"POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n";
472 let layer = make_layer(raw);
473 assert!(layer.is_chunked(raw));
474 }
475
476 #[test]
477 fn test_request_not_chunked() {
478 let raw = b"POST / HTTP/1.1\r\nContent-Length: 5\r\n\r\n";
479 let layer = make_layer(raw);
480 assert!(!layer.is_chunked(raw));
481 }
482
483 #[test]
484 fn test_request_summary() {
485 let raw = b"DELETE /res HTTP/1.1\r\n\r\n";
486 let layer = make_layer(raw);
487 assert_eq!(layer.summary_str(raw), "HTTP DELETE /res HTTP/1.1");
488 }
489
490 #[test]
491 fn test_request_header_len() {
492 let headers = b"GET / HTTP/1.1\r\nHost: h\r\n\r\n";
493 let body = b"BODY";
494 let mut raw = headers.to_vec();
495 raw.extend_from_slice(body);
496 let layer = make_layer(&raw);
497 assert_eq!(layer.http_header_len(&raw), headers.len());
499 }
500
501 #[test]
502 fn test_request_get_field_method() {
503 let raw = b"PUT /x HTTP/1.1\r\n\r\n";
504 let layer = make_layer(raw);
505 if let Some(Ok(FieldValue::Bytes(v))) = layer.get_field(raw, "method") {
506 assert_eq!(v, b"PUT");
507 } else {
508 panic!("expected Bytes value for method");
509 }
510 }
511
512 #[test]
513 fn test_request_get_field_uri() {
514 let raw = b"GET /hello HTTP/1.1\r\n\r\n";
515 let layer = make_layer(raw);
516 if let Some(Ok(FieldValue::Bytes(v))) = layer.get_field(raw, "uri") {
517 assert_eq!(v, b"/hello");
518 } else {
519 panic!("expected Bytes value for uri");
520 }
521 }
522
523 #[test]
524 fn test_request_get_field_version() {
525 let raw = b"GET / HTTP/1.0\r\n\r\n";
526 let layer = make_layer(raw);
527 if let Some(Ok(FieldValue::Bytes(v))) = layer.get_field(raw, "version") {
528 assert_eq!(v, b"HTTP/1.0");
529 } else {
530 panic!("expected Bytes value for version");
531 }
532 }
533
534 #[test]
537 fn test_response_is_response() {
538 let raw = b"HTTP/1.1 200 OK\r\n\r\n";
539 let layer = make_layer(raw);
540 assert!(layer.is_response(raw));
541 assert!(!layer.is_request(raw));
542 }
543
544 #[test]
545 fn test_response_status_code() {
546 let raw = b"HTTP/1.1 404 Not Found\r\n\r\n";
547 let layer = make_layer(raw);
548 assert_eq!(layer.status_code(raw), Some(404));
549 }
550
551 #[test]
552 fn test_response_reason() {
553 let raw = b"HTTP/1.1 301 Moved Permanently\r\n\r\n";
554 let layer = make_layer(raw);
555 assert_eq!(layer.reason(raw), Some("Moved Permanently"));
556 }
557
558 #[test]
559 fn test_response_version() {
560 let raw = b"HTTP/1.0 200 OK\r\n\r\n";
561 let layer = make_layer(raw);
562 assert_eq!(layer.http_version(raw), Some("HTTP/1.0"));
563 }
564
565 #[test]
566 fn test_response_header_value() {
567 let raw = b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n";
568 let layer = make_layer(raw);
569 assert_eq!(
570 layer.header_value(raw, "content-type"),
571 Some("application/json")
572 );
573 }
574
575 #[test]
576 fn test_response_body_offset() {
577 let raw = b"HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest";
578 let layer = make_layer(raw);
579 let offset = layer.body_offset(raw).unwrap();
580 assert_eq!(&raw[offset..], b"test");
581 }
582
583 #[test]
584 fn test_response_summary() {
585 let raw = b"HTTP/1.1 500 Internal Server Error\r\n\r\n";
586 let layer = make_layer(raw);
587 assert_eq!(layer.summary_str(raw), "HTTP 500 Internal Server Error");
588 }
589
590 #[test]
591 fn test_response_get_field_status_code() {
592 let raw = b"HTTP/1.1 200 OK\r\n\r\n";
593 let layer = make_layer(raw);
594 if let Some(Ok(FieldValue::U16(code))) = layer.get_field(raw, "status_code") {
595 assert_eq!(code, 200);
596 } else {
597 panic!("expected U16 for status_code");
598 }
599 }
600
601 #[test]
602 fn test_response_get_field_reason() {
603 let raw = b"HTTP/1.1 200 OK\r\n\r\n";
604 let layer = make_layer(raw);
605 if let Some(Ok(FieldValue::Bytes(v))) = layer.get_field(raw, "reason") {
606 assert_eq!(v, b"OK");
607 } else {
608 panic!("expected Bytes for reason");
609 }
610 }
611
612 #[test]
615 fn test_layer_kind() {
616 let raw = b"GET / HTTP/1.1\r\n\r\n";
617 let layer = make_layer(raw);
618 assert_eq!(layer.kind(), LayerKind::Http);
619 }
620
621 #[test]
622 fn test_layer_field_names() {
623 let raw = b"GET / HTTP/1.1\r\n\r\n";
624 let layer = make_layer(raw);
625 assert_eq!(layer.field_names(), HTTP_FIELD_NAMES);
626 }
627
628 #[test]
631 fn test_builder_and_layer_roundtrip_request() {
632 let raw = HttpRequestBuilder::new()
633 .method("PATCH")
634 .uri("/resource/1")
635 .header("Authorization", "Bearer token123")
636 .header("Content-Type", "application/json")
637 .body(b"{\"key\":\"val\"}".to_vec())
638 .build();
639
640 let layer = make_layer(&raw);
641 assert_eq!(layer.method(&raw), Some("PATCH"));
642 assert_eq!(layer.uri(&raw), Some("/resource/1"));
643 assert_eq!(layer.http_version(&raw), Some("HTTP/1.1"));
644 assert_eq!(
645 layer.header_value(&raw, "authorization"),
646 Some("Bearer token123")
647 );
648 }
649
650 #[test]
651 fn test_builder_and_layer_roundtrip_response() {
652 let raw = HttpResponseBuilder::new()
653 .status(201, "Created")
654 .header("Location", "/resource/1")
655 .body(b"{}".to_vec())
656 .build();
657
658 let layer = make_layer(&raw);
659 assert_eq!(layer.status_code(&raw), Some(201));
660 assert_eq!(layer.reason(&raw), Some("Created"));
661 assert_eq!(layer.header_value(&raw, "Location"), Some("/resource/1"));
662 let body = &raw[layer.body_offset(&raw).unwrap()..];
663 assert_eq!(body, b"{}");
664 }
665}