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