hns_browser_loopback_proxy/chromium/
metadata.rs1use crate::event::{ObservedHost, ObservedMethod};
11use crate::response::InternalResponseMetadata;
12use std::fmt;
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum ProxyMetadataObservationKind {
18 OriginResponse,
19 WebPkiConnectDecision,
20}
21
22#[derive(Clone, Eq, PartialEq)]
29pub struct ProxyResponseMetadataObservation {
30 generation: u64,
31 host: ObservedHost,
32 method: ObservedMethod,
33 status_code: u16,
34 likely_main_frame: bool,
35 observation_id: Option<u64>,
36 kind: ProxyMetadataObservationKind,
37 port: Option<u16>,
38 correlation_epoch: Option<u64>,
39 metadata: InternalResponseMetadata,
40}
41
42impl ProxyResponseMetadataObservation {
43 #[allow(clippy::too_many_arguments)]
47 pub(crate) fn new(
48 generation: u64,
49 host: ObservedHost,
50 method: ObservedMethod,
51 status_code: u16,
52 likely_main_frame: bool,
53 observation_id: Option<u64>,
54 kind: ProxyMetadataObservationKind,
55 port: Option<u16>,
56 correlation_epoch: Option<u64>,
57 metadata: InternalResponseMetadata,
58 ) -> Self {
59 Self {
60 generation,
61 host,
62 method,
63 status_code,
64 likely_main_frame,
65 observation_id,
66 kind,
67 port,
68 correlation_epoch,
69 metadata,
70 }
71 }
72
73 pub fn generation(&self) -> u64 {
75 self.generation
76 }
77
78 pub fn host(&self) -> &ObservedHost {
80 &self.host
81 }
82
83 pub fn method(&self) -> ObservedMethod {
85 self.method
86 }
87
88 pub fn status_code(&self) -> u16 {
92 self.status_code
93 }
94
95 pub fn is_likely_main_frame(&self) -> bool {
98 self.likely_main_frame
99 }
100
101 pub fn observation_id(&self) -> Option<u64> {
104 self.observation_id
105 }
106
107 pub const fn kind(&self) -> ProxyMetadataObservationKind {
108 self.kind
109 }
110
111 pub const fn port(&self) -> Option<u16> {
112 self.port
113 }
114
115 pub const fn correlation_epoch(&self) -> Option<u64> {
116 self.correlation_epoch
117 }
118
119 pub fn metadata(&self) -> &InternalResponseMetadata {
122 &self.metadata
123 }
124}
125
126impl fmt::Debug for ProxyResponseMetadataObservation {
127 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
128 formatter
129 .debug_struct("ProxyResponseMetadataObservation")
130 .field("generation", &self.generation)
131 .field("host", &self.host)
132 .field("method", &self.method)
133 .field("status_code", &self.status_code)
134 .field("likely_main_frame", &self.likely_main_frame)
135 .field("observation_id_present", &self.observation_id.is_some())
136 .field("kind", &self.kind)
137 .field("port", &self.port)
138 .field("correlation_epoch", &self.correlation_epoch)
139 .field("metadata", &self.metadata)
140 .finish()
141 }
142}
143
144pub trait ProxyResponseMetadataObserver: Send + Sync + 'static {
146 fn observe(&self, observation: &ProxyResponseMetadataObservation);
150}
151
152impl<F> ProxyResponseMetadataObserver for F
153where
154 F: Fn(&ProxyResponseMetadataObservation) + Send + Sync + 'static,
155{
156 fn observe(&self, observation: &ProxyResponseMetadataObservation) {
157 self(observation);
158 }
159}
160
161#[derive(Clone, Copy, Debug, Default)]
164pub struct NoopProxyResponseMetadataObserver;
165
166impl ProxyResponseMetadataObserver for NoopProxyResponseMetadataObserver {
167 fn observe(&self, _observation: &ProxyResponseMetadataObservation) {}
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use crate::sanitize_response_headers;
174
175 #[test]
176 fn observation_debug_redacts_sensitive_metadata_values() {
177 let headers = sanitize_response_headers(&[
178 (
179 "X-HNS-Resolution-Trace".to_owned(),
180 "private?token=secret".to_owned(),
181 ),
182 ("X-HNS-Security-Path".to_owned(), "dane".to_owned()),
183 ])
184 .unwrap();
185 let observation = ProxyResponseMetadataObservation::new(
186 7,
187 ObservedHost::new("welcome").unwrap(),
188 ObservedMethod::Get,
189 200,
190 true,
191 Some(9),
192 ProxyMetadataObservationKind::OriginResponse,
193 None,
194 None,
195 headers.metadata().clone(),
196 );
197
198 assert_eq!(observation.generation(), 7);
199 assert_eq!(observation.host().as_str(), "welcome");
200 assert_eq!(observation.method(), ObservedMethod::Get);
201 assert_eq!(observation.status_code(), 200);
202 assert!(observation.is_likely_main_frame());
203 assert_eq!(observation.observation_id(), Some(9));
204 assert_eq!(
205 observation.metadata().get("X-HNS-Security-Path"),
206 Some("dane")
207 );
208 let diagnostic = format!("{observation:?}");
209 assert!(!diagnostic.contains("private"));
210 assert!(!diagnostic.contains("token"));
211 assert!(!diagnostic.contains("secret"));
212 assert!(!diagnostic.contains("path"));
213 assert!(!diagnostic.contains("query"));
214 assert!(!diagnostic.contains("body"));
215 }
216}