Skip to main content

huginn_net_http/http/
observable.rs

1use super::common::{HttpCookie, HttpHeader};
2use super::{Header, Version};
3use core::fmt;
4use std::fmt::Formatter;
5
6/// Observed HTTP request characteristics extracted from network traffic.
7///
8/// `huginn-net-http` defines this type so the crate stays independent of any
9/// signature database. `huginn-net-db` re-exports it from here for matching.
10#[derive(Clone, Debug, PartialEq)]
11pub struct HttpRequestObservation {
12    /// HTTP version
13    pub version: Version,
14    /// ordered list of headers that should appear in matching traffic (p0f style).
15    pub horder: Vec<Header>,
16    /// list of headers that must *not* appear in matching traffic (p0f style).
17    pub habsent: Vec<Header>,
18    /// expected substring in 'User-Agent' or 'Server'.
19    pub expsw: String,
20}
21
22/// Observed HTTP response characteristics extracted from network traffic.
23#[derive(Clone, Debug, PartialEq)]
24pub struct HttpResponseObservation {
25    /// HTTP version
26    pub version: Version,
27    /// ordered list of headers that should appear in matching traffic (p0f style).
28    pub horder: Vec<Header>,
29    /// list of headers that must *not* appear in matching traffic (p0f style).
30    pub habsent: Vec<Header>,
31    /// expected substring in 'User-Agent' or 'Server'.
32    pub expsw: String,
33}
34
35/// Public-facing HTTP request observation: includes the matching payload plus
36/// raw signal fields useful to consumers (lang, UA, headers, cookies, …).
37#[derive(Debug, Clone)]
38pub struct ObservableHttpRequest {
39    pub matching: HttpRequestObservation,
40    pub lang: Option<String>,
41    pub user_agent: Option<String>,
42    pub headers: Vec<HttpHeader>,
43    pub cookies: Vec<HttpCookie>,
44    pub referer: Option<String>,
45    pub method: Option<String>,
46    pub uri: Option<String>,
47}
48
49#[derive(Debug, Clone)]
50pub struct ObservableHttpResponse {
51    pub matching: HttpResponseObservation,
52    pub headers: Vec<HttpHeader>,
53    pub status_code: Option<u16>,
54}
55
56/// Trait used to render HTTP signatures in the canonical p0f text form
57/// `version:horder:habsent:expsw`.
58///
59/// `huginn-net-db` implements this trait for its own `http::Signature` and
60/// reuses the same shape, so observations and DB signatures print identically.
61pub trait HttpDisplayFormat {
62    fn get_version(&self) -> Version;
63    fn get_horder(&self) -> &[Header];
64    fn get_habsent(&self) -> &[Header];
65    fn get_expsw(&self) -> &str;
66
67    fn format_http_display(&self, f: &mut Formatter<'_>) -> fmt::Result {
68        write!(f, "{}:", self.get_version())?;
69
70        for (i, h) in self.get_horder().iter().enumerate() {
71            if i > 0 {
72                f.write_str(",")?;
73            }
74            write!(f, "{h}")?;
75        }
76
77        f.write_str(":")?;
78
79        for (i, h) in self.get_habsent().iter().enumerate() {
80            if i > 0 {
81                f.write_str(",")?;
82            }
83            write!(f, "{h}")?;
84        }
85
86        write!(f, ":{}", self.get_expsw())
87    }
88}
89
90impl HttpDisplayFormat for HttpRequestObservation {
91    fn get_version(&self) -> Version {
92        self.version
93    }
94    fn get_horder(&self) -> &[Header] {
95        &self.horder
96    }
97    fn get_habsent(&self) -> &[Header] {
98        &self.habsent
99    }
100    fn get_expsw(&self) -> &str {
101        &self.expsw
102    }
103}
104
105impl HttpDisplayFormat for HttpResponseObservation {
106    fn get_version(&self) -> Version {
107        self.version
108    }
109    fn get_horder(&self) -> &[Header] {
110        &self.horder
111    }
112    fn get_habsent(&self) -> &[Header] {
113        &self.habsent
114    }
115    fn get_expsw(&self) -> &str {
116        &self.expsw
117    }
118}
119
120impl fmt::Display for HttpRequestObservation {
121    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
122        self.format_http_display(f)
123    }
124}
125
126impl fmt::Display for HttpResponseObservation {
127    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
128        self.format_http_display(f)
129    }
130}
131
132impl HttpDisplayFormat for ObservableHttpRequest {
133    fn get_version(&self) -> Version {
134        self.matching.version
135    }
136    fn get_horder(&self) -> &[Header] {
137        &self.matching.horder
138    }
139    fn get_habsent(&self) -> &[Header] {
140        &self.matching.habsent
141    }
142    fn get_expsw(&self) -> &str {
143        &self.matching.expsw
144    }
145}
146
147impl HttpDisplayFormat for ObservableHttpResponse {
148    fn get_version(&self) -> Version {
149        self.matching.version
150    }
151    fn get_horder(&self) -> &[Header] {
152        &self.matching.horder
153    }
154    fn get_habsent(&self) -> &[Header] {
155        &self.matching.habsent
156    }
157    fn get_expsw(&self) -> &str {
158        &self.matching.expsw
159    }
160}
161
162impl fmt::Display for ObservableHttpRequest {
163    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
164        self.format_http_display(f)
165    }
166}
167
168impl fmt::Display for ObservableHttpResponse {
169    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
170        self.format_http_display(f)
171    }
172}
173
174impl fmt::Display for HttpHeader {
175    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
176        if let Some(ref value) = self.value {
177            write!(f, "{}={}", self.name, value)
178        } else {
179            write!(f, "{}", self.name)
180        }
181    }
182}