hns_browser_loopback_proxy/chromium/
backend.rs1use std::fmt;
4use std::io::{self, Read, Write};
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::{Arc, Condvar, Mutex};
7use std::time::Duration;
8use thiserror::Error;
9
10#[derive(Clone, Default)]
13pub struct CancellationToken {
14 inner: Arc<CancellationState>,
15}
16
17#[derive(Default)]
18struct CancellationState {
19 cancelled: AtomicBool,
20 wait_lock: Mutex<()>,
21 changed: Condvar,
22}
23
24impl CancellationToken {
25 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub(crate) fn cancel(&self) -> bool {
31 let _guard = self
35 .inner
36 .wait_lock
37 .lock()
38 .unwrap_or_else(|poisoned| poisoned.into_inner());
39 if self.inner.cancelled.swap(true, Ordering::AcqRel) {
40 return false;
41 }
42 self.inner.changed.notify_all();
43 true
44 }
45
46 pub fn is_cancelled(&self) -> bool {
47 self.inner.cancelled.load(Ordering::Acquire)
48 }
49
50 pub fn wait_cancelled_timeout(&self, timeout: Duration) -> bool {
53 if self.is_cancelled() {
54 return true;
55 }
56 let guard = self
57 .inner
58 .wait_lock
59 .lock()
60 .unwrap_or_else(|poisoned| poisoned.into_inner());
61 if self.is_cancelled() {
62 return true;
63 }
64 let _guard = self
65 .inner
66 .changed
67 .wait_timeout_while(guard, timeout, |_| !self.is_cancelled())
68 .unwrap_or_else(|poisoned| poisoned.into_inner());
69 self.is_cancelled()
70 }
71}
72
73impl fmt::Debug for CancellationToken {
74 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
75 formatter
76 .debug_struct("CancellationToken")
77 .field("cancelled", &self.is_cancelled())
78 .finish()
79 }
80}
81
82#[derive(Clone, Eq, PartialEq)]
83pub struct ProxyHeader {
84 pub name: String,
85 pub value: String,
86}
87
88impl ProxyHeader {
89 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
90 Self {
91 name: name.into(),
92 value: value.into(),
93 }
94 }
95}
96
97impl fmt::Debug for ProxyHeader {
98 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
99 formatter.write_str("ProxyHeader(<redacted>)")
100 }
101}
102
103#[derive(Clone, Eq, PartialEq)]
104pub enum ProxyRequestBody {
105 Empty,
106 Bytes(Vec<u8>),
107}
108
109impl ProxyRequestBody {
110 pub fn len(&self) -> usize {
111 match self {
112 Self::Empty => 0,
113 Self::Bytes(bytes) => bytes.len(),
114 }
115 }
116
117 pub fn is_empty(&self) -> bool {
118 self.len() == 0
119 }
120
121 pub fn as_bytes(&self) -> &[u8] {
122 match self {
123 Self::Empty => &[],
124 Self::Bytes(bytes) => bytes,
125 }
126 }
127
128 pub fn into_bytes(self) -> Vec<u8> {
129 match self {
130 Self::Empty => Vec::new(),
131 Self::Bytes(bytes) => bytes,
132 }
133 }
134}
135
136impl fmt::Debug for ProxyRequestBody {
137 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
138 formatter
139 .debug_struct("ProxyRequestBody")
140 .field("len", &self.len())
141 .finish()
142 }
143}
144
145#[derive(Clone, Eq, PartialEq)]
146pub struct ProxyRequest {
147 pub method: String,
148 pub scheme: String,
149 pub host: String,
150 pub port: u16,
151 pub path_and_query: String,
152 pub headers: Vec<ProxyHeader>,
153 pub body: ProxyRequestBody,
154}
155
156impl fmt::Debug for ProxyRequest {
157 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
158 formatter
159 .debug_struct("ProxyRequest")
160 .field("port", &self.port)
161 .field("header_count", &self.headers.len())
162 .field("body", &self.body)
163 .finish_non_exhaustive()
164 }
165}
166
167#[derive(Clone, Eq, PartialEq)]
168pub struct ProxyResponseHead {
169 pub status_code: u16,
170 pub reason_phrase: String,
171 pub headers: Vec<ProxyHeader>,
172 pub observation_id: Option<u64>,
175}
176
177impl fmt::Debug for ProxyResponseHead {
178 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
179 formatter
180 .debug_struct("ProxyResponseHead")
181 .field("status_code", &self.status_code)
182 .field("header_count", &self.headers.len())
183 .field("observation_id_present", &self.observation_id.is_some())
184 .finish_non_exhaustive()
185 }
186}
187
188pub enum ProxyResponseBody {
189 Bytes(Vec<u8>),
190 Stream {
195 expected_len: u64,
196 reader: Box<dyn Read + Send>,
197 },
198}
199
200impl ProxyResponseBody {
201 pub fn empty() -> Self {
202 Self::Bytes(Vec::new())
203 }
204
205 pub fn expected_len(&self) -> u64 {
206 match self {
207 Self::Bytes(bytes) => u64::try_from(bytes.len()).unwrap_or(u64::MAX),
208 Self::Stream { expected_len, .. } => *expected_len,
209 }
210 }
211
212 pub fn is_empty(&self) -> bool {
213 self.expected_len() == 0
214 }
215}
216
217impl fmt::Debug for ProxyResponseBody {
218 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
219 formatter
220 .debug_struct("ProxyResponseBody")
221 .field("expected_len", &self.expected_len())
222 .finish()
223 }
224}
225
226pub trait ProxyPublicationAuthority: Send + Sync + 'static {
234 fn publish(&self, operation: &mut dyn FnMut() -> io::Result<()>) -> io::Result<()>;
235}
236
237struct NoopProxyPublicationAuthority;
238
239impl ProxyPublicationAuthority for NoopProxyPublicationAuthority {
240 fn publish(&self, operation: &mut dyn FnMut() -> io::Result<()>) -> io::Result<()> {
241 operation()
242 }
243}
244
245#[derive(Clone)]
248pub struct ProxyPublicationPermit {
249 authority: Arc<dyn ProxyPublicationAuthority>,
250}
251
252impl ProxyPublicationPermit {
253 pub fn new(authority: Arc<dyn ProxyPublicationAuthority>) -> Self {
254 Self { authority }
255 }
256
257 pub fn publish(&self, operation: impl FnOnce() -> io::Result<()>) -> io::Result<()> {
258 let mut operation = Some(operation);
259 self.authority.publish(&mut || {
260 operation
261 .take()
262 .ok_or_else(|| io::Error::other("publication operation was already consumed"))?(
263 )
264 })
265 }
266}
267
268impl Default for ProxyPublicationPermit {
269 fn default() -> Self {
270 Self::new(Arc::new(NoopProxyPublicationAuthority))
271 }
272}
273
274impl fmt::Debug for ProxyPublicationPermit {
275 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
276 formatter.write_str("ProxyPublicationPermit(<redacted authority>)")
277 }
278}
279
280pub struct ProxyResponse {
281 pub head: ProxyResponseHead,
282 pub body: ProxyResponseBody,
283 pub publication_permit: ProxyPublicationPermit,
284}
285
286pub struct ProxyTunnel {
294 pub head: ProxyResponseHead,
295 pub stream: Box<dyn ProxyTunnelIo>,
296 pub publication_permit: ProxyPublicationPermit,
297}
298
299pub enum ProxyConnectOpen {
301 Intercept,
303 WebPkiUnavailable,
306 WebPkiPassthrough(ProxyConnectTunnel),
308}
309
310pub struct ProxyConnectTunnel {
313 pub reader: Box<dyn Read + Send>,
315 pub writer: Box<dyn Write + Send>,
317 pub shutdown: Arc<dyn ProxyConnectShutdown>,
319 pub observation_id: Option<u64>,
320 pub correlation_epoch: u64,
321 pub publication_permit: ProxyPublicationPermit,
322}
323
324pub trait ProxyConnectShutdown: Send + Sync {
328 fn shutdown(&self);
329}
330
331impl fmt::Debug for ProxyConnectOpen {
332 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
333 match self {
334 Self::Intercept => formatter.write_str("Intercept"),
335 Self::WebPkiUnavailable => formatter.write_str("WebPkiUnavailable"),
336 Self::WebPkiPassthrough(tunnel) => formatter
337 .debug_tuple("WebPkiPassthrough")
338 .field(tunnel)
339 .finish(),
340 }
341 }
342}
343
344impl fmt::Debug for ProxyConnectTunnel {
345 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
346 formatter
347 .debug_struct("ProxyConnectTunnel")
348 .field("transport", &"<redacted split duplex stream>")
349 .field("observation_id_present", &self.observation_id.is_some())
350 .field("correlation_epoch", &self.correlation_epoch)
351 .finish()
352 }
353}
354
355pub enum ProxyTunnelOpen {
359 Tunnel(ProxyTunnel),
360 Response(ProxyResponse),
361}
362
363impl fmt::Debug for ProxyTunnelOpen {
364 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
365 match self {
366 Self::Tunnel(tunnel) => formatter.debug_tuple("Tunnel").field(tunnel).finish(),
367 Self::Response(response) => formatter.debug_tuple("Response").field(response).finish(),
368 }
369 }
370}
371
372impl fmt::Debug for ProxyTunnel {
373 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
374 formatter
375 .debug_struct("ProxyTunnel")
376 .field("head", &self.head)
377 .field("stream", &"<redacted duplex stream>")
378 .finish()
379 }
380}
381
382pub trait ProxyTunnelIo: Read + Write + Send {}
383
384impl<T: Read + Write + Send> ProxyTunnelIo for T {}
385
386impl fmt::Debug for ProxyResponse {
387 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
388 formatter
389 .debug_struct("ProxyResponse")
390 .field("head", &self.head)
391 .field("body", &self.body)
392 .finish()
393 }
394}
395
396#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
397pub enum BackendError {
398 #[error("backend operation was cancelled")]
399 Cancelled,
400 #[error("backend rejected an invalid request")]
401 InvalidRequest,
402 #[error("backend policy denied the request")]
403 PolicyDenied,
404 #[error("name resolution failed")]
405 ResolutionFailed,
406 #[error("upstream TLS validation failed")]
407 TlsValidationFailed,
408 #[error("upstream service is unavailable")]
409 UpstreamUnavailable,
410 #[error("upstream returned an invalid response")]
411 InvalidResponse,
412 #[error("upstream response exceeds the configured limit")]
413 ResponseTooLarge,
414 #[error("backend does not support HTTP Upgrade tunnelling")]
415 UnsupportedUpgrade,
416 #[error("internal backend failure")]
417 Internal,
418}
419
420pub trait ProxyBackend: Send + Sync + 'static {
421 fn open_connect(
426 &self,
427 _request: ProxyRequest,
428 _cancellation: &CancellationToken,
429 ) -> Result<ProxyConnectOpen, BackendError> {
430 Ok(ProxyConnectOpen::Intercept)
431 }
432
433 fn execute(
438 &self,
439 request: ProxyRequest,
440 cancellation: &CancellationToken,
441 ) -> Result<ProxyResponse, BackendError>;
442
443 fn open_tunnel(
449 &self,
450 _request: ProxyRequest,
451 _cancellation: &CancellationToken,
452 ) -> Result<ProxyTunnelOpen, BackendError> {
453 Err(BackendError::UnsupportedUpgrade)
454 }
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460 use std::io::Cursor;
461 use std::thread;
462 use std::time::Instant;
463
464 #[test]
465 fn cancellation_is_shared_idempotent_and_wakes_waiters() {
466 let token = CancellationToken::new();
467 let waiter = token.clone();
468 let handle = thread::spawn(move || waiter.wait_cancelled_timeout(Duration::from_secs(2)));
469 assert!(token.cancel());
470 assert!(!token.cancel());
471 assert!(handle.join().expect("waiter joins"));
472 }
473
474 #[test]
475 fn uncancelled_wait_times_out() {
476 let token = CancellationToken::new();
477 let started = Instant::now();
478 assert!(!token.wait_cancelled_timeout(Duration::from_millis(5)));
479 assert!(started.elapsed() >= Duration::from_millis(5));
480 }
481
482 #[test]
483 fn diagnostics_redact_request_secrets_and_payloads() {
484 let request = ProxyRequest {
485 method: "GET".to_owned(),
486 scheme: "https".to_owned(),
487 host: "user:secret@example".to_owned(),
488 port: 443,
489 path_and_query: "/private?token=secret".to_owned(),
490 headers: vec![ProxyHeader::new("Authorization", "secret")],
491 body: ProxyRequestBody::Bytes(b"secret body".to_vec()),
492 };
493 let diagnostic = format!("{request:?}");
494 for secret in [
495 "user",
496 "secret",
497 "example",
498 "private",
499 "token",
500 "Authorization",
501 ] {
502 assert!(!diagnostic.contains(secret));
503 }
504 assert!(diagnostic.contains("header_count"));
505 assert!(diagnostic.contains("len"));
506 }
507
508 #[test]
509 fn response_stream_declares_length_without_debugging_contents() {
510 let body = ProxyResponseBody::Stream {
511 expected_len: 12,
512 reader: Box::new(Cursor::new(b"secret bytes".to_vec())),
513 };
514 assert_eq!(body.expected_len(), 12);
515 assert_eq!(
516 format!("{body:?}"),
517 "ProxyResponseBody { expected_len: 12 }"
518 );
519 }
520}