Skip to main content

hns_browser_loopback_proxy/chromium/
server.rs

1//! Authenticated, scoped HTTP/1 loopback proxy lifecycle.
2
3use crate::auth::ProxyAuthorization;
4use crate::backend::{
5    BackendError, CancellationToken, ProxyBackend, ProxyConnectOpen, ProxyConnectTunnel,
6    ProxyHeader, ProxyRequest, ProxyRequestBody, ProxyResponse, ProxyResponseBody, ProxyTunnel,
7    ProxyTunnelOpen,
8};
9use crate::certificate::LocalTlsIdentityStore;
10use crate::config::{ProxyConfig, ProxyLimits, ProxyRoutingMode, ProxyTimeouts};
11use crate::endpoint::ProxyEndpoint;
12use crate::event::{
13    BackendFailureKind, ClientRejectionReason, LifecycleEvent, ObservedHost, ObservedMethod,
14    ProxyEvent, ProxyObserver, RequestPhase, RequestRejectionReason, StopReason,
15};
16use crate::host::HostScopeError;
17use crate::http1::{
18    AbsoluteTarget, Authority, BodyFraming, Http1Error, RequestHead, RequestTarget, Scheme,
19    determine_body_framing, read_request_body, read_request_head, sanitize_forward_headers,
20    sanitize_upgrade_forward_headers,
21};
22use crate::listener::{
23    ClientHandler, ListenerExitHandler, ListenerExitPhase, ListenerExitReason, OwnedListener,
24    RejectionHandler,
25};
26use crate::metadata::{
27    NoopProxyResponseMetadataObserver, ProxyMetadataObservationKind,
28    ProxyResponseMetadataObservation, ProxyResponseMetadataObserver,
29};
30use crate::rate_limit::{
31    RateLimitConfig, RateLimitConfigError, RateLimitDecision, RateLimitScope, RequestRateLimiter,
32};
33use crate::response::{
34    encode_response_head, encode_upgrade_response_head, sanitize_response_headers,
35};
36use crate::tls::{TlsStream, accept_local_tls};
37use hns_core::network_policy::{is_browser_blocked_port, is_browser_special_use_host};
38use std::fmt;
39use std::io::{self, Read, Write};
40use std::net::{Shutdown, SocketAddr, TcpStream};
41use std::panic::{AssertUnwindSafe, catch_unwind};
42use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
43use std::sync::{Arc, Condvar, Mutex as StdMutex};
44use std::thread::{self, ThreadId};
45use std::time::{Duration, Instant};
46use thiserror::Error;
47
48const RESPONSE_COPY_BUFFER_BYTES: usize = 16 * 1024;
49const TUNNEL_COPY_BUFFER_BYTES: usize = 16 * 1024;
50const TUNNEL_CLIENT_POLL_INTERVAL: Duration = Duration::from_millis(25);
51const CONNECT_TUNNEL_IO_TIMEOUT: Duration = Duration::from_millis(250);
52const CONNECT_ESTABLISHED_RESPONSE: &[u8] = b"HTTP/1.1 200 Connection Established\r\n\r\n";
53
54/// Failure to start a proxy generation. No listener or credential is retained
55/// after this error is returned.
56#[derive(Debug, Error)]
57pub enum ProxyError {
58    #[error("unable to generate mandatory loopback proxy credentials")]
59    Authorization(#[source] crate::AuthorizationGenerationError),
60    #[error("invalid loopback proxy rate-limit configuration")]
61    RateLimit(#[source] RateLimitConfigError),
62    #[error("unable to start the IPv4 loopback proxy listener")]
63    Listener(#[source] io::Error),
64}
65
66/// One owned proxy generation and all of its listener/client work.
67pub struct RunningProxy {
68    endpoint: ProxyEndpoint,
69    listener: OwnedListener,
70    tls_identities: Arc<LocalTlsIdentityStore>,
71    observer: Arc<dyn ProxyObserver>,
72    generation: u64,
73    stop_state: Arc<ProxyStopState>,
74}
75
76impl RunningProxy {
77    /// Starts a fresh authenticated proxy on an operating-system-selected
78    /// IPv4 loopback port.
79    pub fn start(
80        config: ProxyConfig,
81        backend: Arc<dyn ProxyBackend>,
82        observer: Arc<dyn ProxyObserver>,
83    ) -> Result<Self, ProxyError> {
84        Self::start_with_metadata_observer(
85            config,
86            backend,
87            observer,
88            Arc::new(NoopProxyResponseMetadataObserver),
89        )
90    }
91
92    /// Starts a fresh authenticated proxy with an independent observer for
93    /// trusted metadata removed from validated browser response heads.
94    pub fn start_with_metadata_observer(
95        config: ProxyConfig,
96        backend: Arc<dyn ProxyBackend>,
97        observer: Arc<dyn ProxyObserver>,
98        metadata_observer: Arc<dyn ProxyResponseMetadataObserver>,
99    ) -> Result<Self, ProxyError> {
100        let authorization =
101            Arc::new(ProxyAuthorization::generate().map_err(ProxyError::Authorization)?);
102        let limits = config.limits();
103        let timeouts = config.timeouts();
104        let generation = config.instance().generation();
105        let rate_limiter =
106            RequestRateLimiter::new(rate_limit_config(limits)).map_err(ProxyError::RateLimit)?;
107        let cancellation = CancellationToken::new();
108        let tls_identities = Arc::new(LocalTlsIdentityStore::with_certificate_authority(
109            config.instance().clone(),
110            config.local_certificate_authority().cloned(),
111        ));
112        let stop_state = Arc::new(ProxyStopState::new());
113        let context = Arc::new(ServerContext {
114            authorization: Arc::clone(&authorization),
115            hns_scope: config.hns_scope().cloned(),
116            routing_mode: config.routing_mode(),
117            limits,
118            timeouts,
119            backend,
120            observer: Arc::clone(&observer),
121            metadata_observer,
122            generation,
123            rate_limiter,
124            tls_identities: Arc::clone(&tls_identities),
125        });
126
127        let client_context = Arc::clone(&context);
128        let client_handler: Arc<ClientHandler> = Arc::new(move |stream, peer, token| {
129            handle_client(stream, peer, token, &client_context);
130        });
131        let rejection_observer = Arc::clone(&observer);
132        let rejection_handler: Arc<RejectionHandler> = Arc::new(move |mut stream, _, _| {
133            let _result = stream.set_write_timeout(Some(timeouts.socket_timeout()));
134            observe(
135                rejection_observer.as_ref(),
136                ProxyEvent::ClientRejected {
137                    generation,
138                    reason: ClientRejectionReason::ActiveClientLimit,
139                },
140            );
141            let _result = write_error_response(
142                &mut stream,
143                429,
144                "Too Many Requests",
145                &[("Retry-After", "1".to_owned())],
146            );
147            let _result = stream.shutdown(Shutdown::Both);
148        });
149        let exit_handler = listener_exit_handler(
150            Arc::clone(&tls_identities),
151            Arc::clone(&observer),
152            generation,
153            Arc::clone(&stop_state),
154        );
155
156        let listener = OwnedListener::start(
157            SocketAddr::V4(config.bind().socket_addr()),
158            limits.max_active_clients(),
159            cancellation,
160            client_handler,
161            rejection_handler,
162            exit_handler,
163        )
164        .map_err(ProxyError::Listener)?;
165        let endpoint = ProxyEndpoint::new(
166            config.instance().clone(),
167            listener.local_addr(),
168            authorization,
169        );
170        observe(
171            observer.as_ref(),
172            ProxyEvent::Lifecycle {
173                generation,
174                event: LifecycleEvent::Listening {
175                    port: endpoint.port(),
176                },
177            },
178        );
179        stop_state.publish_listening();
180
181        Ok(Self {
182            endpoint,
183            listener,
184            tls_identities,
185            observer,
186            generation,
187            stop_state,
188        })
189    }
190
191    pub fn endpoint(&self) -> &ProxyEndpoint {
192        &self.endpoint
193    }
194
195    pub fn active_clients(&self) -> usize {
196        self.listener.active_clients()
197    }
198
199    pub fn is_stopped(&self) -> bool {
200        self.stop_state.completed.load(Ordering::Acquire)
201    }
202
203    pub fn is_stop_requested(&self) -> bool {
204        self.stop_state.requested.load(Ordering::Acquire)
205    }
206
207    /// Matches a platform proxy-authentication challenge only while this
208    /// generation remains live and unrevoked.
209    pub fn matches_authentication_challenge(&self, host: &str, port: u16, realm: &str) -> bool {
210        !self.is_stop_requested()
211            && self.endpoint.matches_challenge(host, port, realm)
212            && !self.is_stop_requested()
213    }
214
215    /// Returns informational, generation-bound pin metadata after an
216    /// authenticated CONNECT has prepared this exact canonical host. The pin
217    /// cannot authorize certificate DER; native trust hooks must call
218    /// [`Self::matches_local_certificate`] through this live proxy handle.
219    pub fn local_certificate_pin(&self, host: &str) -> Option<crate::LocalCertificatePin> {
220        if self.stop_state.requested.load(Ordering::Acquire) {
221            return None;
222        }
223        let host = crate::NormalizedHost::parse(host).ok()?;
224        self.tls_identities.pin(&host)
225    }
226
227    /// Verifies browser challenge DER against the active generation's exact
228    /// host identity. Unknown, malformed, oversized, or stopped lookups fail.
229    pub fn matches_local_certificate(&self, host: &str, certificate_der: &[u8]) -> bool {
230        if self.stop_state.requested.load(Ordering::Acquire) {
231            return false;
232        }
233        let Ok(host) = crate::NormalizedHost::parse(host) else {
234            return false;
235        };
236        self.tls_identities.matches_der(&host, certificate_der)
237            && !self.stop_state.requested.load(Ordering::Acquire)
238    }
239
240    /// Cancels socket/backend work and, from an external control thread, joins
241    /// the listener and every client worker. Repeated and concurrent calls are
242    /// safe. A reentrant observer callback requests cancellation; the next
243    /// external call completes the join.
244    pub fn request_stop(&self) {
245        self.stop_state.requested.store(true, Ordering::Release);
246        self.listener.request_stop();
247        self.tls_identities.deactivate();
248    }
249
250    /// Requests immediate revocation and cancellation, then joins the listener
251    /// and every client worker. Platform UI threads may call
252    /// [`Self::request_stop`] first and complete this blocking join on a
253    /// lifecycle worker.
254    pub fn stop(&self) {
255        self.request_stop();
256        if !self
257            .stop_state
258            .announcement
259            .announce(self.observer.as_ref(), self.generation)
260        {
261            return;
262        }
263        let joined = self.listener.stop();
264        if joined {
265            self.stop_state.complete(
266                self.observer.as_ref(),
267                self.generation,
268                StopReason::Requested,
269            );
270        }
271    }
272}
273
274struct ProxyStopState {
275    requested: AtomicBool,
276    completed: AtomicBool,
277    announcement: StopAnnouncement,
278    listening_published: StdMutex<bool>,
279    listening_changed: Condvar,
280}
281
282impl ProxyStopState {
283    fn new() -> Self {
284        Self {
285            requested: AtomicBool::new(false),
286            completed: AtomicBool::new(false),
287            announcement: StopAnnouncement::new(),
288            listening_published: StdMutex::new(false),
289            listening_changed: Condvar::new(),
290        }
291    }
292
293    fn publish_listening(&self) {
294        let mut published = self
295            .listening_published
296            .lock()
297            .unwrap_or_else(|poisoned| poisoned.into_inner());
298        *published = true;
299        self.listening_changed.notify_all();
300    }
301
302    fn wait_for_listening(&self) {
303        let mut published = self
304            .listening_published
305            .lock()
306            .unwrap_or_else(|poisoned| poisoned.into_inner());
307        while !*published {
308            published = self
309                .listening_changed
310                .wait(published)
311                .unwrap_or_else(|poisoned| poisoned.into_inner());
312        }
313    }
314
315    fn complete(&self, observer: &dyn ProxyObserver, generation: u64, reason: StopReason) {
316        if !self.completed.swap(true, Ordering::AcqRel) {
317            observe(
318                observer,
319                ProxyEvent::Lifecycle {
320                    generation,
321                    event: LifecycleEvent::Stopped { reason },
322                },
323            );
324        }
325    }
326}
327
328struct StopAnnouncement {
329    state: StdMutex<StopAnnouncementState>,
330    emitted: Condvar,
331}
332
333impl StopAnnouncement {
334    fn new() -> Self {
335        Self {
336            state: StdMutex::new(StopAnnouncementState::Pending),
337            emitted: Condvar::new(),
338        }
339    }
340
341    /// Emits `Stopping` exactly once before any concurrent caller can join and
342    /// emit `Stopped`. A reentrant call from that observer returns after
343    /// revocation instead of waiting on its own announcement.
344    fn announce(&self, observer: &dyn ProxyObserver, generation: u64) -> bool {
345        let current = std::thread::current().id();
346        let mut state = self
347            .state
348            .lock()
349            .unwrap_or_else(|poisoned| poisoned.into_inner());
350        loop {
351            match &*state {
352                StopAnnouncementState::Pending => {
353                    *state = StopAnnouncementState::Emitting(current);
354                    drop(state);
355                    observe(
356                        observer,
357                        ProxyEvent::Lifecycle {
358                            generation,
359                            event: LifecycleEvent::Stopping,
360                        },
361                    );
362                    let mut state = self
363                        .state
364                        .lock()
365                        .unwrap_or_else(|poisoned| poisoned.into_inner());
366                    *state = StopAnnouncementState::Emitted;
367                    self.emitted.notify_all();
368                    return true;
369                }
370                StopAnnouncementState::Emitting(owner) if *owner == current => return false,
371                StopAnnouncementState::Emitting(_) => {
372                    state = self
373                        .emitted
374                        .wait(state)
375                        .unwrap_or_else(|poisoned| poisoned.into_inner());
376                }
377                StopAnnouncementState::Emitted => return true,
378            }
379        }
380    }
381}
382
383fn listener_exit_handler(
384    tls_identities: Arc<LocalTlsIdentityStore>,
385    observer: Arc<dyn ProxyObserver>,
386    generation: u64,
387    stop_state: Arc<ProxyStopState>,
388) -> Arc<ListenerExitHandler> {
389    Arc::new(move |phase| match phase {
390        ListenerExitPhase::FailureDetected => {
391            stop_state.requested.store(true, Ordering::Release);
392            tls_identities.deactivate();
393            stop_state.wait_for_listening();
394            let _announced = stop_state
395                .announcement
396                .announce(observer.as_ref(), generation);
397        }
398        ListenerExitPhase::Quiesced(reason) => {
399            stop_state.requested.store(true, Ordering::Release);
400            tls_identities.deactivate();
401            stop_state.wait_for_listening();
402            let _announced = stop_state
403                .announcement
404                .announce(observer.as_ref(), generation);
405            let reason = match reason {
406                ListenerExitReason::Requested => StopReason::Requested,
407                ListenerExitReason::ListenerFailure => StopReason::ListenerFailure,
408            };
409            stop_state.complete(observer.as_ref(), generation, reason);
410        }
411    })
412}
413
414#[derive(Debug)]
415enum StopAnnouncementState {
416    Pending,
417    Emitting(ThreadId),
418    Emitted,
419}
420
421impl fmt::Debug for RunningProxy {
422    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
423        formatter
424            .debug_struct("RunningProxy")
425            .field("endpoint", &self.endpoint)
426            .field("active_clients", &self.active_clients())
427            .field(
428                "stop_requested",
429                &self.stop_state.requested.load(Ordering::Acquire),
430            )
431            .field("stop_completed", &self.is_stopped())
432            .finish()
433    }
434}
435
436impl Drop for RunningProxy {
437    fn drop(&mut self) {
438        self.stop();
439    }
440}
441
442struct ServerContext {
443    authorization: Arc<ProxyAuthorization>,
444    hns_scope: Option<crate::HostScope>,
445    routing_mode: ProxyRoutingMode,
446    limits: ProxyLimits,
447    timeouts: ProxyTimeouts,
448    backend: Arc<dyn ProxyBackend>,
449    observer: Arc<dyn ProxyObserver>,
450    metadata_observer: Arc<dyn ProxyResponseMetadataObserver>,
451    generation: u64,
452    rate_limiter: RequestRateLimiter,
453    tls_identities: Arc<LocalTlsIdentityStore>,
454}
455
456fn handle_client(
457    mut stream: TcpStream,
458    peer: SocketAddr,
459    cancellation: CancellationToken,
460    context: &ServerContext,
461) {
462    if !peer.ip().is_loopback() {
463        observe_client_rejection(context, ClientRejectionReason::InvalidPeer);
464        let _result = stream.shutdown(Shutdown::Both);
465        return;
466    }
467    if cancellation.is_cancelled() {
468        observe_client_rejection(context, ClientRejectionReason::Cancelled);
469        let _result = stream.shutdown(Shutdown::Both);
470        return;
471    }
472
473    let _result = stream.set_nodelay(true);
474    if stream
475        .set_write_timeout(Some(context.timeouts.socket_timeout()))
476        .is_err()
477    {
478        observe_client_rejection(context, ClientRejectionReason::InvalidRequest);
479        return;
480    }
481
482    let head = {
483        let mut reader =
484            DeadlineReader::new(&mut stream, context.timeouts.request_header_timeout());
485        match read_request_head(&mut reader, context.limits.max_header_bytes()) {
486            Ok(head) => head,
487            Err(error) => {
488                observe_client_rejection(context, ClientRejectionReason::InvalidRequest);
489                let (status, reason) = request_error_status(&error);
490                let _result = write_error_response(&mut stream, status, reason, &[]);
491                return;
492            }
493        }
494    };
495
496    // Authentication is evaluated before the request target is interpreted,
497    // classified, scoped, rate-limited, or sent to the backend.
498    if !context
499        .authorization
500        .verify_header_values(head.header_values(crate::PROXY_AUTHORIZATION_HEADER))
501    {
502        observe_client_rejection(context, ClientRejectionReason::AuthenticationRequired);
503        let _result = write_error_response(
504            &mut stream,
505            407,
506            "Proxy Authentication Required",
507            &[(
508                crate::PROXY_AUTHENTICATE_HEADER,
509                context.authorization.challenge_header_value(),
510            )],
511        );
512        return;
513    }
514
515    let target = match head.validated_target(None) {
516        Ok(target) => target,
517        Err(error) => {
518            observe_client_rejection(context, ClientRejectionReason::InvalidRequest);
519            let _result =
520                write_error_response(&mut stream, error.status_code(), error.reason_phrase(), &[]);
521            return;
522        }
523    };
524    let method = ObservedMethod::from_token(head.method());
525    let canonical_host = match admit_target_host(context, target.authority().host()) {
526        Ok(host) => host,
527        Err(error) => {
528            let (status_reason, rejection) = route_rejection(error);
529            if let Ok(host) = crate::NormalizedHost::parse(target.authority().host()) {
530                observe_request(
531                    context,
532                    &host,
533                    method,
534                    RequestPhase::Rejected { reason: rejection },
535                );
536            }
537            let _result = write_error_response(&mut stream, 403, status_reason, &[]);
538            return;
539        }
540    };
541    observe_request(
542        context,
543        canonical_host.as_str(),
544        method,
545        RequestPhase::Accepted,
546    );
547
548    match target {
549        RequestTarget::Absolute(absolute) => handle_direct_http(
550            &mut stream,
551            &head,
552            &absolute,
553            &canonical_host,
554            method,
555            &cancellation,
556            context,
557        ),
558        RequestTarget::Connect(authority) => handle_connect(
559            stream,
560            &head,
561            authority,
562            canonical_host,
563            method,
564            &cancellation,
565            context,
566        ),
567        RequestTarget::Origin(_) => reject_routed_request(
568            &mut stream,
569            context,
570            canonical_host.as_str(),
571            method,
572            400,
573            "Bad Request",
574            RequestRejectionReason::InvalidRequest,
575            false,
576        ),
577    }
578}
579
580#[derive(Clone, Copy, Debug, Eq, PartialEq)]
581enum RouteRejection {
582    Scope(HostScopeError),
583    SpecialUse,
584    InvalidClass,
585}
586
587fn admit_target_host(
588    context: &ServerContext,
589    raw_host: &str,
590) -> Result<crate::NormalizedHost, RouteRejection> {
591    authorize_native_gateway_host(context, raw_host)
592}
593
594fn authorize_native_gateway_host(
595    context: &ServerContext,
596    raw_host: &str,
597) -> Result<crate::NormalizedHost, RouteRejection> {
598    if context.routing_mode == ProxyRoutingMode::DaneBrowser {
599        let host = crate::NormalizedHost::parse(raw_host)
600            .map_err(|error| RouteRejection::Scope(HostScopeError::InvalidHost(error)))?;
601        if is_browser_special_use_host(host.as_str()) {
602            return Err(RouteRejection::SpecialUse);
603        }
604        // Namespace selection is intentionally absent here. Every ordinary
605        // DNS name reaches the native gateway, which resolves the complete
606        // host through both HNS and ICANN before selecting a root.
607        return Ok(host);
608    }
609
610    context
611        .hns_scope
612        .as_ref()
613        .ok_or(RouteRejection::InvalidClass)?
614        .authorize(raw_host)
615        .map_err(RouteRejection::Scope)
616}
617
618#[derive(Clone, Copy)]
619struct HttpRoute<'a> {
620    scheme: Scheme,
621    authority: &'a Authority,
622    path_and_query: &'a str,
623}
624
625#[allow(clippy::too_many_arguments)]
626fn handle_direct_http(
627    stream: &mut TcpStream,
628    head: &RequestHead,
629    absolute: &AbsoluteTarget,
630    canonical_host: &crate::NormalizedHost,
631    method: ObservedMethod,
632    cancellation: &CancellationToken,
633    context: &ServerContext,
634) {
635    let route = HttpRoute {
636        scheme: absolute.scheme(),
637        authority: absolute.authority(),
638        path_and_query: absolute.path_and_query(),
639    };
640    let is_upgrade =
641        matches!(absolute.scheme(), Scheme::Ws | Scheme::Wss) || requests_upgrade(head);
642    let likely_main_frame =
643        !is_upgrade && is_likely_main_frame_navigation(head, route.path_and_query);
644    if !admit_rate(stream, context, canonical_host, method, likely_main_frame) {
645        return;
646    }
647
648    if is_upgrade {
649        handle_admitted_upgrade(
650            stream,
651            head,
652            route,
653            canonical_host,
654            method,
655            cancellation,
656            context,
657        );
658    } else {
659        handle_admitted_http(
660            stream,
661            head,
662            route,
663            canonical_host,
664            method,
665            cancellation,
666            context,
667        );
668    }
669}
670#[allow(clippy::too_many_arguments)]
671fn handle_connect(
672    mut stream: TcpStream,
673    head: &RequestHead,
674    authority: Authority,
675    canonical_host: crate::NormalizedHost,
676    method: ObservedMethod,
677    cancellation: &CancellationToken,
678    context: &ServerContext,
679) {
680    if requests_upgrade(head) {
681        reject_scoped_request(
682            &mut stream,
683            context,
684            &canonical_host,
685            method,
686            501,
687            "HNS Protocol Upgrade Unsupported",
688            RequestRejectionReason::InvalidRequest,
689        );
690        return;
691    }
692    if head.header_values("expect").next().is_some() {
693        reject_scoped_request(
694            &mut stream,
695            context,
696            &canonical_host,
697            method,
698            417,
699            "Expectation Failed",
700            RequestRejectionReason::InvalidRequest,
701        );
702        return;
703    }
704    let framing = match determine_body_framing(head.headers()) {
705        Ok(framing) => framing,
706        Err(error) => {
707            reject_http_request(&mut stream, context, &canonical_host, method, &error);
708            return;
709        }
710    };
711    if !matches!(framing, BodyFraming::None | BodyFraming::ContentLength(0)) {
712        reject_scoped_request(
713            &mut stream,
714            context,
715            &canonical_host,
716            method,
717            400,
718            "HNS CONNECT Body Unsupported",
719            RequestRejectionReason::InvalidRequest,
720        );
721        return;
722    }
723    if is_browser_blocked_port(authority.port()) {
724        reject_scoped_request(
725            &mut stream,
726            context,
727            &canonical_host,
728            method,
729            403,
730            "Destination Port Denied",
731            RequestRejectionReason::InvalidRequest,
732        );
733        return;
734    }
735    if cancellation.is_cancelled() {
736        observe_request(
737            context,
738            &canonical_host,
739            method,
740            RequestPhase::Rejected {
741                reason: RequestRejectionReason::Cancelled,
742            },
743        );
744        return;
745    }
746    if !admit_rate(&mut stream, context, &canonical_host, method, false) {
747        return;
748    }
749
750    let connect_started = Instant::now();
751    let connect = catch_unwind(AssertUnwindSafe(|| {
752        context.backend.open_connect(
753            ProxyRequest {
754                method: "CONNECT".to_owned(),
755                scheme: "https".to_owned(),
756                host: canonical_host.as_str().to_owned(),
757                port: authority.port(),
758                path_and_query: "/".to_owned(),
759                headers: Vec::new(),
760                body: ProxyRequestBody::Empty,
761            },
762            cancellation,
763        )
764    }))
765    .unwrap_or(Err(BackendError::Internal));
766    let connect = match connect {
767        Ok(connect) => connect,
768        Err(BackendError::Cancelled) => return,
769        Err(error) => {
770            let (status, reason) = connect_backend_error_status(error);
771            observe_request(
772                context,
773                &canonical_host,
774                method,
775                RequestPhase::BackendFailed {
776                    kind: backend_failure_kind(error),
777                    elapsed: connect_started.elapsed(),
778                },
779            );
780            observe_proxy_generated_response(context, &canonical_host, method, status, false);
781            let _result = write_error_response(&mut stream, status, reason, &[]);
782            return;
783        }
784    };
785    if matches!(&connect, ProxyConnectOpen::WebPkiUnavailable) {
786        reject_scoped_request(
787            &mut stream,
788            context,
789            &canonical_host,
790            method,
791            502,
792            "ICANN WebPKI Origin Unavailable",
793            RequestRejectionReason::InvalidRequest,
794        );
795        return;
796    }
797    if let ProxyConnectOpen::WebPkiPassthrough(ProxyConnectTunnel {
798        reader: origin_reader,
799        writer: origin_writer,
800        shutdown: origin_shutdown,
801        observation_id,
802        correlation_epoch,
803        publication_permit,
804    }) = connect
805    {
806        if cancellation.is_cancelled()
807            || publication_permit
808                .publish(|| {
809                    if cancellation.is_cancelled() {
810                        return Err(io::Error::new(
811                            io::ErrorKind::Interrupted,
812                            "proxy generation was cancelled before CONNECT publication",
813                        ));
814                    }
815                    stream.write_all(CONNECT_ESTABLISHED_RESPONSE)?;
816                    stream.flush()
817                })
818                .is_err()
819        {
820            origin_shutdown.shutdown();
821            let _result = stream.shutdown(Shutdown::Both);
822            return;
823        }
824        observe_connect_security_decision(
825            context,
826            &canonical_host,
827            authority.port(),
828            observation_id,
829            correlation_epoch,
830        );
831        match pump_connect_tunnel(
832            stream,
833            origin_reader,
834            origin_writer,
835            origin_shutdown,
836            cancellation,
837        ) {
838            TunnelPumpOutcome::Completed => observe_request(
839                context,
840                &canonical_host,
841                method,
842                RequestPhase::Completed {
843                    status_code: 200,
844                    elapsed: connect_started.elapsed(),
845                },
846            ),
847            TunnelPumpOutcome::OriginIo => observe_request(
848                context,
849                &canonical_host,
850                method,
851                RequestPhase::BackendFailed {
852                    kind: BackendFailureKind::Upstream,
853                    elapsed: connect_started.elapsed(),
854                },
855            ),
856            TunnelPumpOutcome::ClientIo | TunnelPumpOutcome::Cancelled => {}
857        }
858        return;
859    }
860
861    let identity = match context.tls_identities.prepare(&canonical_host) {
862        Ok(identity) => identity,
863        Err(_error) => {
864            reject_scoped_request(
865                &mut stream,
866                context,
867                &canonical_host,
868                method,
869                503,
870                "HNS Local TLS Unavailable",
871                RequestRejectionReason::InvalidRequest,
872            );
873            return;
874        }
875    };
876    debug_assert_eq!(identity.pin().host(), &canonical_host);
877    let started = Instant::now();
878    if stream.write_all(CONNECT_ESTABLISHED_RESPONSE).is_err() || stream.flush().is_err() {
879        return;
880    }
881
882    let mut tls = match accept_local_tls(
883        stream,
884        &identity,
885        &canonical_host,
886        context.timeouts.socket_timeout(),
887        cancellation,
888    ) {
889        Ok(tls) => tls,
890        Err(_error) => {
891            let reason = if cancellation.is_cancelled() {
892                RequestRejectionReason::Cancelled
893            } else {
894                RequestRejectionReason::InvalidRequest
895            };
896            observe_request(
897                context,
898                &canonical_host,
899                method,
900                RequestPhase::Rejected { reason },
901            );
902            return;
903        }
904    };
905    observe_request(
906        context,
907        &canonical_host,
908        method,
909        RequestPhase::Completed {
910            status_code: 200,
911            elapsed: started.elapsed(),
912        },
913    );
914    handle_connected_http(&mut tls, &authority, &canonical_host, cancellation, context);
915    tls.conn.send_close_notify();
916    let _result = tls.flush();
917}
918
919fn handle_connected_http(
920    stream: &mut TlsStream,
921    connected_to: &Authority,
922    outer_host: &crate::NormalizedHost,
923    cancellation: &CancellationToken,
924    context: &ServerContext,
925) {
926    let head = {
927        let mut reader = DeadlineReader::new(stream, context.timeouts.request_header_timeout());
928        match read_request_head(&mut reader, context.limits.max_header_bytes()) {
929            Ok(head) => head,
930            Err(error) => {
931                reject_http_request(stream, context, outer_host, ObservedMethod::Other, &error);
932                return;
933            }
934        }
935    };
936    let method = ObservedMethod::from_token(head.method());
937    let target = match head.validated_target(Some(connected_to)) {
938        Ok(target) => target,
939        Err(error) => {
940            reject_http_request(stream, context, outer_host, method, &error);
941            return;
942        }
943    };
944    let canonical_host = match authorize_native_gateway_host(context, target.authority().host())
945        .ok()
946    {
947        Some(host) if host == *outer_host && target.authority().port() == connected_to.port() => {
948            host
949        }
950        Some(_) | None => {
951            reject_scoped_request(
952                stream,
953                context,
954                outer_host,
955                method,
956                403,
957                "HNS Proxy Scope Denied",
958                RequestRejectionReason::HostOutsideScope,
959            );
960            return;
961        }
962    };
963    observe_request(context, &canonical_host, method, RequestPhase::Accepted);
964
965    match target {
966        RequestTarget::Origin(origin) => {
967            let route = HttpRoute {
968                scheme: Scheme::Https,
969                authority: origin.authority(),
970                path_and_query: origin.path_and_query(),
971            };
972            if requests_upgrade(&head) {
973                handle_admitted_upgrade(
974                    stream,
975                    &head,
976                    route,
977                    &canonical_host,
978                    method,
979                    cancellation,
980                    context,
981                );
982            } else {
983                handle_admitted_http(
984                    stream,
985                    &head,
986                    route,
987                    &canonical_host,
988                    method,
989                    cancellation,
990                    context,
991                );
992            }
993        }
994        RequestTarget::Absolute(absolute)
995            if matches!(absolute.scheme(), Scheme::Https | Scheme::Wss) =>
996        {
997            let route = HttpRoute {
998                scheme: absolute.scheme(),
999                authority: absolute.authority(),
1000                path_and_query: absolute.path_and_query(),
1001            };
1002            if absolute.scheme() == Scheme::Wss || requests_upgrade(&head) {
1003                handle_admitted_upgrade(
1004                    stream,
1005                    &head,
1006                    route,
1007                    &canonical_host,
1008                    method,
1009                    cancellation,
1010                    context,
1011                );
1012            } else {
1013                handle_admitted_http(
1014                    stream,
1015                    &head,
1016                    route,
1017                    &canonical_host,
1018                    method,
1019                    cancellation,
1020                    context,
1021                );
1022            }
1023        }
1024        RequestTarget::Absolute(_) | RequestTarget::Connect(_) => reject_scoped_request(
1025            stream,
1026            context,
1027            &canonical_host,
1028            method,
1029            501,
1030            "HNS Protocol Upgrade Unsupported",
1031            RequestRejectionReason::InvalidRequest,
1032        ),
1033    }
1034}
1035
1036#[allow(clippy::too_many_arguments)]
1037fn handle_admitted_upgrade<S: ClientIo + ?Sized>(
1038    stream: &mut S,
1039    head: &RequestHead,
1040    route: HttpRoute<'_>,
1041    canonical_host: &crate::NormalizedHost,
1042    method: ObservedMethod,
1043    cancellation: &CancellationToken,
1044    context: &ServerContext,
1045) {
1046    let framing = match determine_body_framing(head.headers()) {
1047        Ok(framing) => framing,
1048        Err(error) => {
1049            reject_http_request(stream, context, canonical_host, method, &error);
1050            return;
1051        }
1052    };
1053    if !matches!(framing, BodyFraming::None | BodyFraming::ContentLength(0)) {
1054        reject_scoped_request(
1055            stream,
1056            context,
1057            canonical_host,
1058            method,
1059            400,
1060            "HNS Protocol Upgrade Body Unsupported",
1061            RequestRejectionReason::InvalidRequest,
1062        );
1063        return;
1064    }
1065    if head.header_values("expect").next().is_some() {
1066        reject_scoped_request(
1067            stream,
1068            context,
1069            canonical_host,
1070            method,
1071            417,
1072            "Expectation Failed",
1073            RequestRejectionReason::InvalidRequest,
1074        );
1075        return;
1076    }
1077    let headers = match build_upgrade_forward_headers(head, route, canonical_host) {
1078        Ok(headers) => headers,
1079        Err(error) => {
1080            reject_http_request(stream, context, canonical_host, method, &error);
1081            return;
1082        }
1083    };
1084    if stream.clear_client_read_deadline().is_err() {
1085        observe_request(
1086            context,
1087            canonical_host,
1088            method,
1089            RequestPhase::Rejected {
1090                reason: RequestRejectionReason::InvalidRequest,
1091            },
1092        );
1093        return;
1094    }
1095    if cancellation.is_cancelled() {
1096        observe_request(
1097            context,
1098            canonical_host,
1099            method,
1100            RequestPhase::Rejected {
1101                reason: RequestRejectionReason::Cancelled,
1102            },
1103        );
1104        return;
1105    }
1106
1107    execute_backend_tunnel(
1108        stream,
1109        context,
1110        canonical_host,
1111        method,
1112        ProxyRequest {
1113            method: head.method().to_owned(),
1114            scheme: route.scheme.as_str().to_owned(),
1115            host: canonical_host.as_str().to_owned(),
1116            port: route.authority.port(),
1117            path_and_query: route.path_and_query.to_owned(),
1118            headers,
1119            body: ProxyRequestBody::Empty,
1120        },
1121        cancellation,
1122    );
1123}
1124
1125fn admit_rate<W: Write + ?Sized>(
1126    stream: &mut W,
1127    context: &ServerContext,
1128    canonical_host: &str,
1129    method: ObservedMethod,
1130    likely_main_frame: bool,
1131) -> bool {
1132    match context.rate_limiter.check(canonical_host, Instant::now()) {
1133        RateLimitDecision::Allowed => true,
1134        RateLimitDecision::Limited { scope, retry_after } => {
1135            let reason = match scope {
1136                RateLimitScope::Global => RequestRejectionReason::GlobalRateLimit,
1137                RateLimitScope::Host => RequestRejectionReason::HostRateLimit,
1138            };
1139            observe_request(
1140                context,
1141                canonical_host,
1142                method,
1143                RequestPhase::Rejected { reason },
1144            );
1145            observe_proxy_generated_response(
1146                context,
1147                canonical_host,
1148                method,
1149                429,
1150                likely_main_frame,
1151            );
1152            let _result = write_error_response(
1153                stream,
1154                429,
1155                "Too Many Requests",
1156                &[("Retry-After", retry_after_seconds(retry_after).to_string())],
1157            );
1158            false
1159        }
1160    }
1161}
1162
1163#[allow(clippy::too_many_arguments)]
1164fn handle_admitted_http<S: ClientIo + ?Sized>(
1165    stream: &mut S,
1166    head: &RequestHead,
1167    route: HttpRoute<'_>,
1168    canonical_host: &crate::NormalizedHost,
1169    method: ObservedMethod,
1170    cancellation: &CancellationToken,
1171    context: &ServerContext,
1172) {
1173    let likely_main_frame = is_likely_main_frame_navigation(head, route.path_and_query);
1174    let framing = match determine_body_framing(head.headers()) {
1175        Ok(framing) => framing,
1176        Err(error) => {
1177            reject_http_request_with_status(
1178                stream,
1179                context,
1180                canonical_host,
1181                method,
1182                &error,
1183                likely_main_frame,
1184            );
1185            return;
1186        }
1187    };
1188    if matches!(
1189        framing,
1190        BodyFraming::ContentLength(length) if length > context.limits.max_body_bytes()
1191    ) {
1192        reject_http_request_with_status(
1193            stream,
1194            context,
1195            canonical_host,
1196            method,
1197            &Http1Error::BodyTooLarge,
1198            likely_main_frame,
1199        );
1200        return;
1201    }
1202    let expects_continue = match expects_continue(head) {
1203        Ok(expects) => expects,
1204        Err(()) => {
1205            reject_scoped_request_with_status(
1206                stream,
1207                context,
1208                canonical_host,
1209                method,
1210                417,
1211                "Expectation Failed",
1212                RequestRejectionReason::InvalidRequest,
1213                likely_main_frame,
1214            );
1215            return;
1216        }
1217    };
1218    let forward_headers = match build_forward_headers(head, route, canonical_host) {
1219        Ok(headers) => headers,
1220        Err(error) => {
1221            reject_http_request_with_status(
1222                stream,
1223                context,
1224                canonical_host,
1225                method,
1226                &error,
1227                likely_main_frame,
1228            );
1229            return;
1230        }
1231    };
1232
1233    if expects_continue
1234        && !matches!(framing, BodyFraming::None)
1235        && (stream.write_all(b"HTTP/1.1 100 Continue\r\n\r\n").is_err() || stream.flush().is_err())
1236    {
1237        return;
1238    }
1239    let body = {
1240        let mut reader = DeadlineReader::new(stream, context.timeouts.socket_timeout());
1241        match read_request_body(&mut reader, framing, context.limits.max_body_bytes()) {
1242            Ok(bytes) => bytes,
1243            Err(error) => {
1244                reject_http_request_with_status(
1245                    stream,
1246                    context,
1247                    canonical_host,
1248                    method,
1249                    &error,
1250                    likely_main_frame,
1251                );
1252                return;
1253            }
1254        }
1255    };
1256    if stream.clear_client_read_deadline().is_err() {
1257        observe_request(
1258            context,
1259            canonical_host,
1260            method,
1261            RequestPhase::Rejected {
1262                reason: RequestRejectionReason::InvalidRequest,
1263            },
1264        );
1265        return;
1266    }
1267    if cancellation.is_cancelled() {
1268        observe_request(
1269            context,
1270            canonical_host,
1271            method,
1272            RequestPhase::Rejected {
1273                reason: RequestRejectionReason::Cancelled,
1274            },
1275        );
1276        return;
1277    }
1278
1279    let request = ProxyRequest {
1280        method: head.method().to_owned(),
1281        scheme: route.scheme.as_str().to_owned(),
1282        host: canonical_host.as_str().to_owned(),
1283        port: route.authority.port(),
1284        path_and_query: route.path_and_query.to_owned(),
1285        headers: forward_headers,
1286        body: if body.is_empty() {
1287            ProxyRequestBody::Empty
1288        } else {
1289            ProxyRequestBody::Bytes(body)
1290        },
1291    };
1292    execute_backend(
1293        stream,
1294        context,
1295        canonical_host,
1296        method,
1297        likely_main_frame,
1298        head.method(),
1299        request,
1300        cancellation,
1301    );
1302}
1303
1304#[allow(clippy::too_many_arguments)]
1305fn execute_backend<W: Write + ?Sized>(
1306    stream: &mut W,
1307    context: &ServerContext,
1308    host: &crate::NormalizedHost,
1309    method: ObservedMethod,
1310    likely_main_frame: bool,
1311    request_method: &str,
1312    request: ProxyRequest,
1313    cancellation: &CancellationToken,
1314) {
1315    let started = Instant::now();
1316    let response = catch_unwind(AssertUnwindSafe(|| {
1317        context.backend.execute(request, cancellation)
1318    }))
1319    .unwrap_or(Err(BackendError::Internal));
1320    let response = match response {
1321        Ok(response) => response,
1322        Err(error) => {
1323            observe_request(
1324                context,
1325                host,
1326                method,
1327                RequestPhase::BackendFailed {
1328                    kind: backend_failure_kind(error),
1329                    elapsed: started.elapsed(),
1330                },
1331            );
1332            if error != BackendError::Cancelled && !cancellation.is_cancelled() {
1333                let (status, reason) = backend_error_status(error);
1334                observe_proxy_generated_response(context, host, method, status, likely_main_frame);
1335                let _result = write_error_response(stream, status, reason, &[]);
1336            }
1337            return;
1338        }
1339    };
1340    if cancellation.is_cancelled() {
1341        return;
1342    }
1343    let status = response.head.status_code;
1344    let observation_id = response.head.observation_id;
1345    match write_backend_response(stream, request_method, response, cancellation, |metadata| {
1346        observe_response_metadata(
1347            context,
1348            host,
1349            method,
1350            status,
1351            likely_main_frame,
1352            observation_id,
1353            metadata,
1354        );
1355    }) {
1356        Ok(()) => observe_request(
1357            context,
1358            host,
1359            method,
1360            RequestPhase::Completed {
1361                status_code: status,
1362                elapsed: started.elapsed(),
1363            },
1364        ),
1365        Err(WriteBackendError::InvalidBeforeHead) => {
1366            observe_invalid_response(context, host, method, started.elapsed());
1367            if !cancellation.is_cancelled() {
1368                observe_proxy_generated_response(context, host, method, 502, likely_main_frame);
1369                let _result = write_error_response(stream, 502, "Invalid Upstream Response", &[]);
1370            }
1371        }
1372        Err(WriteBackendError::InvalidAfterHead) => {
1373            observe_invalid_response(context, host, method, started.elapsed());
1374        }
1375        Err(WriteBackendError::Io) => {}
1376    }
1377}
1378
1379#[allow(clippy::too_many_arguments)]
1380fn execute_backend_tunnel<S: ClientIo + ?Sized>(
1381    client: &mut S,
1382    context: &ServerContext,
1383    host: &crate::NormalizedHost,
1384    method: ObservedMethod,
1385    request: ProxyRequest,
1386    cancellation: &CancellationToken,
1387) {
1388    let started = Instant::now();
1389    let request_method = request.method.clone();
1390    let tunnel = catch_unwind(AssertUnwindSafe(|| {
1391        context.backend.open_tunnel(request, cancellation)
1392    }))
1393    .unwrap_or(Err(BackendError::Internal));
1394    let opened = match tunnel {
1395        Ok(opened) => opened,
1396        Err(error) => {
1397            observe_request(
1398                context,
1399                host,
1400                method,
1401                RequestPhase::BackendFailed {
1402                    kind: backend_failure_kind(error),
1403                    elapsed: started.elapsed(),
1404                },
1405            );
1406            if error != BackendError::Cancelled && !cancellation.is_cancelled() {
1407                let (status, reason) = backend_error_status(error);
1408                let _result = write_error_response(client, status, reason, &[]);
1409            }
1410            return;
1411        }
1412    };
1413    if cancellation.is_cancelled() {
1414        return;
1415    }
1416    let ProxyTunnel {
1417        head,
1418        stream: mut origin,
1419        publication_permit,
1420    } = match opened {
1421        ProxyTunnelOpen::Tunnel(tunnel) => tunnel,
1422        ProxyTunnelOpen::Response(response) => {
1423            let status = response.head.status_code;
1424            let observation_id = response.head.observation_id;
1425            match write_backend_response(
1426                client,
1427                &request_method,
1428                response,
1429                cancellation,
1430                |metadata| {
1431                    observe_response_metadata(
1432                        context,
1433                        host,
1434                        method,
1435                        status,
1436                        false,
1437                        observation_id,
1438                        metadata,
1439                    );
1440                },
1441            ) {
1442                Ok(()) => observe_request(
1443                    context,
1444                    host,
1445                    method,
1446                    RequestPhase::Completed {
1447                        status_code: status,
1448                        elapsed: started.elapsed(),
1449                    },
1450                ),
1451                Err(WriteBackendError::InvalidBeforeHead) => {
1452                    observe_invalid_response(context, host, method, started.elapsed());
1453                    if !cancellation.is_cancelled() {
1454                        let _result =
1455                            write_error_response(client, 502, "Invalid Upstream Response", &[]);
1456                    }
1457                }
1458                Err(WriteBackendError::InvalidAfterHead) => {
1459                    observe_invalid_response(context, host, method, started.elapsed());
1460                }
1461                Err(WriteBackendError::Io) => {}
1462            }
1463            return;
1464        }
1465    };
1466    let header_pairs: Vec<_> = head
1467        .headers
1468        .into_iter()
1469        .map(|header| (header.name, header.value))
1470        .collect();
1471    let sanitized = match sanitize_response_headers(&header_pairs) {
1472        Ok(sanitized) => sanitized,
1473        Err(_error) => {
1474            observe_invalid_response(context, host, method, started.elapsed());
1475            if !cancellation.is_cancelled() {
1476                let _result = write_error_response(client, 502, "Invalid Upstream Response", &[]);
1477            }
1478            return;
1479        }
1480    };
1481    let encoded =
1482        match encode_upgrade_response_head(head.status_code, &head.reason_phrase, &header_pairs) {
1483            Ok(encoded) => encoded,
1484            Err(_error) => {
1485                observe_invalid_response(context, host, method, started.elapsed());
1486                if !cancellation.is_cancelled() {
1487                    let _result =
1488                        write_error_response(client, 502, "Invalid Upstream Response", &[]);
1489                }
1490                return;
1491            }
1492        };
1493    if cancellation.is_cancelled()
1494        || publication_permit
1495            .publish(|| {
1496                if cancellation.is_cancelled() {
1497                    return Err(io::Error::new(
1498                        io::ErrorKind::Interrupted,
1499                        "proxy generation was cancelled before publication",
1500                    ));
1501                }
1502                client.write_all(encoded.as_bytes())?;
1503                client.flush()
1504            })
1505            .is_err()
1506    {
1507        return;
1508    }
1509    observe_response_metadata(
1510        context,
1511        host,
1512        method,
1513        101,
1514        false,
1515        head.observation_id,
1516        sanitized.metadata(),
1517    );
1518
1519    match pump_tunnel(client, origin.as_mut(), cancellation) {
1520        TunnelPumpOutcome::Completed => observe_request(
1521            context,
1522            host,
1523            method,
1524            RequestPhase::Completed {
1525                status_code: 101,
1526                elapsed: started.elapsed(),
1527            },
1528        ),
1529        TunnelPumpOutcome::OriginIo => observe_request(
1530            context,
1531            host,
1532            method,
1533            RequestPhase::BackendFailed {
1534                kind: BackendFailureKind::Upstream,
1535                elapsed: started.elapsed(),
1536            },
1537        ),
1538        TunnelPumpOutcome::ClientIo | TunnelPumpOutcome::Cancelled => {}
1539    }
1540}
1541
1542#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1543enum TunnelPumpOutcome {
1544    Completed,
1545    ClientIo,
1546    OriginIo,
1547    Cancelled,
1548}
1549
1550fn pump_tunnel<S: ClientIo + ?Sized>(
1551    client: &mut S,
1552    origin: &mut dyn crate::ProxyTunnelIo,
1553    cancellation: &CancellationToken,
1554) -> TunnelPumpOutcome {
1555    let mut client_buffer = [0_u8; TUNNEL_COPY_BUFFER_BYTES];
1556    let mut origin_buffer = [0_u8; TUNNEL_COPY_BUFFER_BYTES];
1557    loop {
1558        if cancellation.is_cancelled() {
1559            return TunnelPumpOutcome::Cancelled;
1560        }
1561
1562        let now = Instant::now();
1563        let deadline = now.checked_add(TUNNEL_CLIENT_POLL_INTERVAL).unwrap_or(now);
1564        if client.set_client_read_deadline(deadline).is_err() {
1565            return TunnelPumpOutcome::ClientIo;
1566        }
1567        let client_read = client.read(&mut client_buffer);
1568        if client.clear_client_read_deadline().is_err() {
1569            return TunnelPumpOutcome::ClientIo;
1570        }
1571        match client_read {
1572            Ok(0) => return TunnelPumpOutcome::Completed,
1573            Ok(count) if count > client_buffer.len() => return TunnelPumpOutcome::ClientIo,
1574            Ok(count) => {
1575                let write = catch_unwind(AssertUnwindSafe(|| {
1576                    origin.write_all(&client_buffer[..count])?;
1577                    origin.flush()
1578                }));
1579                match write {
1580                    Ok(Ok(())) => {}
1581                    Ok(Err(_)) | Err(_) => return TunnelPumpOutcome::OriginIo,
1582                }
1583            }
1584            Err(error) if retryable_tunnel_read(&error) => {}
1585            Err(_error) => return TunnelPumpOutcome::ClientIo,
1586        }
1587
1588        if cancellation.is_cancelled() {
1589            return TunnelPumpOutcome::Cancelled;
1590        }
1591        let origin_read = catch_unwind(AssertUnwindSafe(|| origin.read(&mut origin_buffer)));
1592        match origin_read {
1593            Ok(Ok(0)) => return TunnelPumpOutcome::Completed,
1594            Ok(Ok(count)) if count > origin_buffer.len() => return TunnelPumpOutcome::OriginIo,
1595            Ok(Ok(count)) => {
1596                if client.write_all(&origin_buffer[..count]).is_err() || client.flush().is_err() {
1597                    return TunnelPumpOutcome::ClientIo;
1598                }
1599            }
1600            Ok(Err(error)) if retryable_tunnel_read(&error) => {}
1601            Ok(Err(_)) | Err(_) => return TunnelPumpOutcome::OriginIo,
1602        }
1603    }
1604}
1605
1606fn retryable_tunnel_read(error: &io::Error) -> bool {
1607    matches!(
1608        error.kind(),
1609        io::ErrorKind::Interrupted | io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock
1610    )
1611}
1612
1613fn pump_connect_tunnel(
1614    client: TcpStream,
1615    origin_reader: Box<dyn Read + Send>,
1616    origin_writer: Box<dyn Write + Send>,
1617    origin_shutdown: Arc<dyn crate::ProxyConnectShutdown>,
1618    cancellation: &CancellationToken,
1619) -> TunnelPumpOutcome {
1620    let client_reader = match client.try_clone() {
1621        Ok(stream) => stream,
1622        Err(_) => {
1623            origin_shutdown.shutdown();
1624            let _result = client.shutdown(Shutdown::Both);
1625            return TunnelPumpOutcome::ClientIo;
1626        }
1627    };
1628    let client_shutdown = match client.try_clone() {
1629        Ok(stream) => stream,
1630        Err(_) => {
1631            origin_shutdown.shutdown();
1632            let _result = client.shutdown(Shutdown::Both);
1633            return TunnelPumpOutcome::ClientIo;
1634        }
1635    };
1636    if client_reader
1637        .set_read_timeout(Some(CONNECT_TUNNEL_IO_TIMEOUT))
1638        .is_err()
1639        || client
1640            .set_write_timeout(Some(CONNECT_TUNNEL_IO_TIMEOUT))
1641            .is_err()
1642    {
1643        origin_shutdown.shutdown();
1644        let _result = client.shutdown(Shutdown::Both);
1645        return TunnelPumpOutcome::ClientIo;
1646    }
1647
1648    // Each direction owns its socket half. A quiet browser-upload direction
1649    // therefore cannot serialize a large origin download behind a polling
1650    // read, while bounded waits still make cancellation and revocation joinable.
1651    let first_outcome = Arc::new(AtomicU8::new(0));
1652    let upload_outcome = Arc::clone(&first_outcome);
1653    let upload_cancellation = cancellation.clone();
1654    let upload_origin_shutdown = Arc::clone(&origin_shutdown);
1655    let upload_client_shutdown = match client_shutdown.try_clone() {
1656        Ok(stream) => stream,
1657        Err(_) => {
1658            origin_shutdown.shutdown();
1659            let _result = client.shutdown(Shutdown::Both);
1660            return TunnelPumpOutcome::ClientIo;
1661        }
1662    };
1663    let upload = match thread::Builder::new()
1664        .name("hns-proxy-connect-upload".to_owned())
1665        .spawn(move || {
1666            pump_connect_upload(
1667                client_reader,
1668                origin_writer,
1669                &upload_cancellation,
1670                &upload_outcome,
1671                upload_client_shutdown,
1672                upload_origin_shutdown,
1673            );
1674        }) {
1675        Ok(upload) => upload,
1676        Err(_) => {
1677            origin_shutdown.shutdown();
1678            let _result = client.shutdown(Shutdown::Both);
1679            return TunnelPumpOutcome::OriginIo;
1680        }
1681    };
1682
1683    pump_connect_download(
1684        client,
1685        origin_reader,
1686        cancellation,
1687        &first_outcome,
1688        client_shutdown,
1689        Arc::clone(&origin_shutdown),
1690    );
1691    if upload.join().is_err() {
1692        record_connect_outcome(&first_outcome, TunnelPumpOutcome::OriginIo);
1693        origin_shutdown.shutdown();
1694    }
1695    connect_outcome(&first_outcome)
1696}
1697
1698fn pump_connect_upload(
1699    mut client: TcpStream,
1700    mut origin: Box<dyn Write + Send>,
1701    cancellation: &CancellationToken,
1702    first_outcome: &AtomicU8,
1703    client_shutdown: TcpStream,
1704    origin_shutdown: Arc<dyn crate::ProxyConnectShutdown>,
1705) {
1706    let mut buffer = [0_u8; TUNNEL_COPY_BUFFER_BYTES];
1707    let outcome = loop {
1708        if cancellation.is_cancelled() {
1709            break TunnelPumpOutcome::Cancelled;
1710        }
1711        if first_outcome.load(Ordering::Acquire) != 0 {
1712            return;
1713        }
1714        match client.read(&mut buffer) {
1715            Ok(0) => break TunnelPumpOutcome::Completed,
1716            Ok(count) if count > buffer.len() => break TunnelPumpOutcome::ClientIo,
1717            Ok(count) => {
1718                let write = catch_unwind(AssertUnwindSafe(|| {
1719                    origin.write_all(&buffer[..count])?;
1720                    origin.flush()
1721                }));
1722                if !matches!(write, Ok(Ok(()))) {
1723                    break if cancellation.is_cancelled() {
1724                        TunnelPumpOutcome::Cancelled
1725                    } else {
1726                        TunnelPumpOutcome::OriginIo
1727                    };
1728                }
1729            }
1730            Err(error) if retryable_tunnel_read(&error) => {}
1731            Err(_) => {
1732                break if cancellation.is_cancelled() {
1733                    TunnelPumpOutcome::Cancelled
1734                } else {
1735                    TunnelPumpOutcome::ClientIo
1736                };
1737            }
1738        }
1739    };
1740    record_connect_outcome(first_outcome, outcome);
1741    origin_shutdown.shutdown();
1742    let _result = client_shutdown.shutdown(Shutdown::Both);
1743}
1744
1745fn pump_connect_download(
1746    mut client: TcpStream,
1747    mut origin: Box<dyn Read + Send>,
1748    cancellation: &CancellationToken,
1749    first_outcome: &AtomicU8,
1750    client_shutdown: TcpStream,
1751    origin_shutdown: Arc<dyn crate::ProxyConnectShutdown>,
1752) {
1753    let mut buffer = [0_u8; TUNNEL_COPY_BUFFER_BYTES];
1754    let outcome = loop {
1755        if cancellation.is_cancelled() {
1756            break TunnelPumpOutcome::Cancelled;
1757        }
1758        if first_outcome.load(Ordering::Acquire) != 0 {
1759            return;
1760        }
1761        let read = catch_unwind(AssertUnwindSafe(|| origin.read(&mut buffer)));
1762        match read {
1763            Ok(Ok(0)) => break TunnelPumpOutcome::Completed,
1764            Ok(Ok(count)) if count > buffer.len() => break TunnelPumpOutcome::OriginIo,
1765            Ok(Ok(count)) => {
1766                if client.write_all(&buffer[..count]).is_err() || client.flush().is_err() {
1767                    break if cancellation.is_cancelled() {
1768                        TunnelPumpOutcome::Cancelled
1769                    } else {
1770                        TunnelPumpOutcome::ClientIo
1771                    };
1772                }
1773            }
1774            Ok(Err(error)) if retryable_tunnel_read(&error) => {}
1775            Ok(Err(_)) | Err(_) => {
1776                break if cancellation.is_cancelled() {
1777                    TunnelPumpOutcome::Cancelled
1778                } else {
1779                    TunnelPumpOutcome::OriginIo
1780                };
1781            }
1782        }
1783    };
1784    record_connect_outcome(first_outcome, outcome);
1785    origin_shutdown.shutdown();
1786    let _result = client_shutdown.shutdown(Shutdown::Both);
1787}
1788
1789fn record_connect_outcome(first_outcome: &AtomicU8, outcome: TunnelPumpOutcome) {
1790    let _result = first_outcome.compare_exchange(
1791        0,
1792        connect_outcome_code(outcome),
1793        Ordering::AcqRel,
1794        Ordering::Acquire,
1795    );
1796}
1797
1798fn connect_outcome(first_outcome: &AtomicU8) -> TunnelPumpOutcome {
1799    match first_outcome.load(Ordering::Acquire) {
1800        1 => TunnelPumpOutcome::Completed,
1801        2 => TunnelPumpOutcome::ClientIo,
1802        3 => TunnelPumpOutcome::OriginIo,
1803        4 => TunnelPumpOutcome::Cancelled,
1804        _ => TunnelPumpOutcome::OriginIo,
1805    }
1806}
1807
1808const fn connect_outcome_code(outcome: TunnelPumpOutcome) -> u8 {
1809    match outcome {
1810        TunnelPumpOutcome::Completed => 1,
1811        TunnelPumpOutcome::ClientIo => 2,
1812        TunnelPumpOutcome::OriginIo => 3,
1813        TunnelPumpOutcome::Cancelled => 4,
1814    }
1815}
1816
1817fn observe_invalid_response(
1818    context: &ServerContext,
1819    host: &crate::NormalizedHost,
1820    method: ObservedMethod,
1821    elapsed: Duration,
1822) {
1823    observe_request(
1824        context,
1825        host,
1826        method,
1827        RequestPhase::BackendFailed {
1828            kind: BackendFailureKind::InvalidResponse,
1829            elapsed,
1830        },
1831    );
1832}
1833
1834fn write_backend_response<W, F>(
1835    stream: &mut W,
1836    request_method: &str,
1837    response: ProxyResponse,
1838    cancellation: &CancellationToken,
1839    observe_metadata: F,
1840) -> Result<(), WriteBackendError>
1841where
1842    W: Write + ?Sized,
1843    F: FnOnce(&crate::InternalResponseMetadata),
1844{
1845    let ProxyResponse {
1846        head,
1847        body,
1848        publication_permit,
1849    } = response;
1850    let header_pairs: Vec<_> = head
1851        .headers
1852        .into_iter()
1853        .map(|header| (header.name, header.value))
1854        .collect();
1855    let headers = sanitize_response_headers(&header_pairs)
1856        .map_err(|_error| WriteBackendError::InvalidBeforeHead)?;
1857    let body_len = body.expected_len();
1858    let encoded = encode_response_head(
1859        request_method,
1860        head.status_code,
1861        &head.reason_phrase,
1862        &headers,
1863        body_len,
1864    )
1865    .map_err(|_error| WriteBackendError::InvalidBeforeHead)?;
1866    let body_allowed = encoded.body_allowed();
1867    if cancellation.is_cancelled() {
1868        return Err(WriteBackendError::Io);
1869    }
1870    publication_permit
1871        .publish(|| {
1872            if cancellation.is_cancelled() {
1873                return Err(io::Error::new(
1874                    io::ErrorKind::Interrupted,
1875                    "proxy generation was cancelled before publication",
1876                ));
1877            }
1878            stream.write_all(encoded.as_bytes())?;
1879            stream.flush()
1880        })
1881        .map_err(|_error| WriteBackendError::Io)?;
1882    observe_metadata(headers.metadata());
1883    if !body_allowed {
1884        return Ok(());
1885    }
1886    write_response_body(stream, body, cancellation)
1887}
1888
1889fn write_response_body<W: Write + ?Sized>(
1890    stream: &mut W,
1891    body: ProxyResponseBody,
1892    cancellation: &CancellationToken,
1893) -> Result<(), WriteBackendError> {
1894    match body {
1895        ProxyResponseBody::Bytes(bytes) => {
1896            if cancellation.is_cancelled() {
1897                return Err(WriteBackendError::Io);
1898            }
1899            stream
1900                .write_all(&bytes)
1901                .map_err(|_error| WriteBackendError::Io)
1902        }
1903        ProxyResponseBody::Stream {
1904            expected_len,
1905            mut reader,
1906        } => copy_exact_response(stream, reader.as_mut(), expected_len, cancellation),
1907    }
1908}
1909
1910fn copy_exact_response<W: Write + ?Sized>(
1911    stream: &mut W,
1912    reader: &mut dyn Read,
1913    mut remaining: u64,
1914    cancellation: &CancellationToken,
1915) -> Result<(), WriteBackendError> {
1916    let mut buffer = [0_u8; RESPONSE_COPY_BUFFER_BYTES];
1917    while remaining != 0 {
1918        if cancellation.is_cancelled() {
1919            return Err(WriteBackendError::Io);
1920        }
1921        let limit = usize::try_from(remaining.min(buffer.len() as u64))
1922            .map_err(|_error| WriteBackendError::InvalidAfterHead)?;
1923        let count = catch_unwind(AssertUnwindSafe(|| reader.read(&mut buffer[..limit])))
1924            .map_err(|_panic| WriteBackendError::InvalidAfterHead)?
1925            .map_err(|_error| WriteBackendError::InvalidAfterHead)?;
1926        if count == 0 || count > limit {
1927            return Err(WriteBackendError::InvalidAfterHead);
1928        }
1929        stream
1930            .write_all(&buffer[..count])
1931            .map_err(|_error| WriteBackendError::Io)?;
1932        remaining = remaining
1933            .checked_sub(count as u64)
1934            .ok_or(WriteBackendError::InvalidAfterHead)?;
1935    }
1936    Ok(())
1937}
1938
1939#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1940enum WriteBackendError {
1941    InvalidBeforeHead,
1942    InvalidAfterHead,
1943    Io,
1944}
1945
1946fn build_forward_headers(
1947    head: &RequestHead,
1948    route: HttpRoute<'_>,
1949    host: &crate::NormalizedHost,
1950) -> Result<Vec<ProxyHeader>, Http1Error> {
1951    let mut headers: Vec<_> = sanitize_forward_headers(head.headers())?
1952        .into_iter()
1953        .filter(|header| {
1954            !header.name().eq_ignore_ascii_case("host")
1955                && !header.name().eq_ignore_ascii_case("content-length")
1956                && !header.name().eq_ignore_ascii_case("expect")
1957        })
1958        .map(|header| ProxyHeader::new(header.name(), header.value()))
1959        .collect();
1960    let default_port = route.scheme.default_port();
1961    let host_value = if route.authority.port() == default_port {
1962        host.as_str().to_owned()
1963    } else {
1964        format!("{}:{}", host.as_str(), route.authority.port())
1965    };
1966    headers.push(ProxyHeader::new("Host", host_value));
1967    Ok(headers)
1968}
1969
1970fn build_upgrade_forward_headers(
1971    head: &RequestHead,
1972    route: HttpRoute<'_>,
1973    host: &crate::NormalizedHost,
1974) -> Result<Vec<ProxyHeader>, Http1Error> {
1975    let mut headers: Vec<_> = sanitize_upgrade_forward_headers(head.headers())?
1976        .into_iter()
1977        .filter(|header| {
1978            !header.name().eq_ignore_ascii_case("host")
1979                && !header.name().eq_ignore_ascii_case("content-length")
1980                && !header.name().eq_ignore_ascii_case("expect")
1981        })
1982        .map(|header| ProxyHeader::new(header.name(), header.value()))
1983        .collect();
1984    let default_port = route.scheme.default_port();
1985    let host_value = if route.authority.port() == default_port {
1986        host.as_str().to_owned()
1987    } else {
1988        format!("{}:{}", host.as_str(), route.authority.port())
1989    };
1990    headers.push(ProxyHeader::new("Host", host_value));
1991    Ok(headers)
1992}
1993
1994fn expects_continue(head: &RequestHead) -> Result<bool, ()> {
1995    let mut values = head.header_values("expect");
1996    let Some(first) = values.next() else {
1997        return Ok(false);
1998    };
1999    if values.next().is_some() || !first.eq_ignore_ascii_case("100-continue") {
2000        return Err(());
2001    }
2002    Ok(true)
2003}
2004
2005/// Mirrors the Android shell's conservative navigation heuristic without
2006/// retaining the request target in an observation.
2007fn is_likely_main_frame_navigation(head: &RequestHead, path_and_query: &str) -> bool {
2008    if !head.method().eq_ignore_ascii_case("GET") && !head.method().eq_ignore_ascii_case("HEAD") {
2009        return false;
2010    }
2011
2012    // Sec-Fetch-Dest is authoritative when the browser supplies it. In
2013    // particular, nested navigations (`iframe`) and subresources must not be
2014    // promoted by the compatibility fallbacks below.
2015    if let Some(fetch_destination) = head.header_values("sec-fetch-dest").next() {
2016        return fetch_destination.eq_ignore_ascii_case("document");
2017    }
2018    if head
2019        .header_values("sec-fetch-mode")
2020        .next()
2021        .is_some_and(|mode| mode.eq_ignore_ascii_case("navigate"))
2022    {
2023        return true;
2024    }
2025    if head.header_values("upgrade-insecure-requests").next() == Some("1") {
2026        return true;
2027    }
2028    if head
2029        .header_values("accept")
2030        .next()
2031        .is_some_and(|accept| accept.to_ascii_lowercase().contains("text/html"))
2032    {
2033        return true;
2034    }
2035    path_and_query == "/"
2036}
2037
2038fn requests_upgrade(head: &RequestHead) -> bool {
2039    head.header_values("upgrade").next().is_some()
2040        || head
2041            .header_values("connection")
2042            .flat_map(|value| value.split(','))
2043            .any(|token| token.trim().eq_ignore_ascii_case("upgrade"))
2044}
2045
2046fn reject_http_request<W: Write + ?Sized>(
2047    stream: &mut W,
2048    context: &ServerContext,
2049    host: &crate::NormalizedHost,
2050    method: ObservedMethod,
2051    error: &Http1Error,
2052) {
2053    reject_http_request_with_status(stream, context, host, method, error, false);
2054}
2055
2056fn reject_http_request_with_status<W: Write + ?Sized>(
2057    stream: &mut W,
2058    context: &ServerContext,
2059    host: &crate::NormalizedHost,
2060    method: ObservedMethod,
2061    error: &Http1Error,
2062    likely_main_frame: bool,
2063) {
2064    let rejection = if matches!(error, Http1Error::BodyTooLarge) {
2065        RequestRejectionReason::RequestTooLarge
2066    } else {
2067        RequestRejectionReason::InvalidRequest
2068    };
2069    let (status, status_reason) = request_error_status(error);
2070    reject_scoped_request_with_status(
2071        stream,
2072        context,
2073        host,
2074        method,
2075        status,
2076        status_reason,
2077        rejection,
2078        likely_main_frame,
2079    );
2080}
2081
2082#[allow(clippy::too_many_arguments)]
2083fn reject_routed_request<W: Write + ?Sized>(
2084    stream: &mut W,
2085    context: &ServerContext,
2086    host: &str,
2087    method: ObservedMethod,
2088    status: u16,
2089    reason: &'static str,
2090    rejection: RequestRejectionReason,
2091    likely_main_frame: bool,
2092) {
2093    observe_request(
2094        context,
2095        host,
2096        method,
2097        RequestPhase::Rejected { reason: rejection },
2098    );
2099    observe_proxy_generated_response(context, host, method, status, likely_main_frame);
2100    let _result = write_error_response(stream, status, reason, &[]);
2101}
2102
2103#[allow(clippy::too_many_arguments)]
2104fn reject_scoped_request<W: Write + ?Sized>(
2105    stream: &mut W,
2106    context: &ServerContext,
2107    host: &crate::NormalizedHost,
2108    method: ObservedMethod,
2109    status: u16,
2110    reason: &'static str,
2111    rejection: RequestRejectionReason,
2112) {
2113    reject_scoped_request_with_status(
2114        stream, context, host, method, status, reason, rejection, false,
2115    );
2116}
2117
2118#[allow(clippy::too_many_arguments)]
2119fn reject_scoped_request_with_status<W: Write + ?Sized>(
2120    stream: &mut W,
2121    context: &ServerContext,
2122    host: &crate::NormalizedHost,
2123    method: ObservedMethod,
2124    status: u16,
2125    reason: &'static str,
2126    rejection: RequestRejectionReason,
2127    likely_main_frame: bool,
2128) {
2129    observe_request(
2130        context,
2131        host,
2132        method,
2133        RequestPhase::Rejected { reason: rejection },
2134    );
2135    observe_proxy_generated_response(context, host, method, status, likely_main_frame);
2136    let _result = write_error_response(stream, status, reason, &[]);
2137}
2138
2139fn route_rejection(error: RouteRejection) -> (&'static str, RequestRejectionReason) {
2140    match error {
2141        RouteRejection::Scope(HostScopeError::OutOfScope) => (
2142            "HNS Proxy Scope Denied",
2143            RequestRejectionReason::HostOutsideScope,
2144        ),
2145        RouteRejection::Scope(HostScopeError::NotHns | HostScopeError::InvalidHost(_))
2146        | RouteRejection::SpecialUse
2147        | RouteRejection::InvalidClass => (
2148            "Proxy Scope Denied",
2149            RequestRejectionReason::HostOutsideScope,
2150        ),
2151    }
2152}
2153
2154fn request_error_status(error: &Http1Error) -> (u16, &'static str) {
2155    match error {
2156        Http1Error::Io(source)
2157            if matches!(
2158                source.kind(),
2159                io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock
2160            ) =>
2161        {
2162            (408, "Request Timeout")
2163        }
2164        _ => (error.status_code(), error.reason_phrase()),
2165    }
2166}
2167
2168fn backend_error_status(error: BackendError) -> (u16, &'static str) {
2169    match error {
2170        BackendError::Cancelled => (503, "Proxy Request Cancelled"),
2171        BackendError::InvalidRequest => (400, "Invalid Gateway Request"),
2172        BackendError::PolicyDenied => (403, "Gateway Policy Denied"),
2173        BackendError::ResolutionFailed => (502, "HNS Resolution Failed"),
2174        BackendError::TlsValidationFailed => (502, "HNS TLS Validation Failed"),
2175        BackendError::UpstreamUnavailable => (502, "HNS Upstream Unavailable"),
2176        BackendError::InvalidResponse => (502, "Invalid Upstream Response"),
2177        BackendError::ResponseTooLarge => (502, "Upstream Response Too Large"),
2178        BackendError::UnsupportedUpgrade => (501, "HNS Protocol Upgrade Unsupported"),
2179        BackendError::Internal => (500, "Proxy Internal Error"),
2180    }
2181}
2182
2183fn connect_backend_error_status(error: BackendError) -> (u16, &'static str) {
2184    match error {
2185        BackendError::Cancelled => (503, "Browser Security Request Cancelled"),
2186        BackendError::InvalidRequest => (400, "Invalid Browser Security Request"),
2187        BackendError::PolicyDenied => (403, "Browser Security Policy Denied"),
2188        BackendError::ResolutionFailed => (502, "Namespace Resolution Failed"),
2189        BackendError::TlsValidationFailed => (502, "Origin TLS Validation Failed"),
2190        BackendError::UpstreamUnavailable => (502, "Origin Unavailable"),
2191        BackendError::InvalidResponse => (502, "Invalid Origin Response"),
2192        BackendError::ResponseTooLarge => (502, "Origin Response Too Large"),
2193        BackendError::UnsupportedUpgrade => (501, "Protocol Upgrade Unsupported"),
2194        BackendError::Internal => (503, "Browser Security Runtime Unavailable"),
2195    }
2196}
2197
2198fn backend_failure_kind(error: BackendError) -> BackendFailureKind {
2199    match error {
2200        BackendError::Cancelled => BackendFailureKind::Cancelled,
2201        BackendError::InvalidRequest => BackendFailureKind::InvalidRequest,
2202        BackendError::PolicyDenied => BackendFailureKind::PolicyDenied,
2203        BackendError::ResolutionFailed => BackendFailureKind::Resolution,
2204        BackendError::TlsValidationFailed => BackendFailureKind::TlsValidation,
2205        BackendError::UpstreamUnavailable => BackendFailureKind::Upstream,
2206        BackendError::InvalidResponse => BackendFailureKind::InvalidResponse,
2207        BackendError::ResponseTooLarge => BackendFailureKind::ResponseTooLarge,
2208        BackendError::UnsupportedUpgrade => BackendFailureKind::InvalidRequest,
2209        BackendError::Internal => BackendFailureKind::Internal,
2210    }
2211}
2212
2213fn rate_limit_config(limits: ProxyLimits) -> RateLimitConfig {
2214    RateLimitConfig {
2215        global_requests: limits.max_requests_per_window(),
2216        per_host_requests: limits.max_requests_per_host_per_window(),
2217        window: limits.rate_window(),
2218        max_tracked_hosts: limits.max_tracked_hosts(),
2219    }
2220}
2221
2222fn retry_after_seconds(duration: Duration) -> u64 {
2223    duration.as_secs() + u64::from(duration.subsec_nanos() != 0)
2224}
2225
2226fn write_error_response<W: Write + ?Sized>(
2227    stream: &mut W,
2228    status: u16,
2229    reason: &'static str,
2230    extra_headers: &[(&'static str, String)],
2231) -> io::Result<()> {
2232    let body = reason.as_bytes();
2233    let mut response = format!(
2234        "HTTP/1.1 {status} {reason}\r\nConnection: close\r\nCache-Control: no-store\r\nContent-Type: text/plain; charset=utf-8\r\nX-Content-Type-Options: nosniff\r\n"
2235    )
2236    .into_bytes();
2237    for (name, value) in extra_headers {
2238        response.extend_from_slice(name.as_bytes());
2239        response.extend_from_slice(b": ");
2240        response.extend_from_slice(value.as_bytes());
2241        response.extend_from_slice(b"\r\n");
2242    }
2243    response.extend_from_slice(format!("Content-Length: {}\r\n\r\n", body.len()).as_bytes());
2244    response.extend_from_slice(body);
2245    stream.write_all(&response)?;
2246    stream.flush()
2247}
2248
2249trait ClientIo: Read + Write {
2250    fn set_client_read_deadline(&mut self, deadline: Instant) -> io::Result<()>;
2251
2252    fn clear_client_read_deadline(&mut self) -> io::Result<()> {
2253        Ok(())
2254    }
2255}
2256
2257impl ClientIo for TcpStream {
2258    fn set_client_read_deadline(&mut self, deadline: Instant) -> io::Result<()> {
2259        let remaining = deadline
2260            .checked_duration_since(Instant::now())
2261            .filter(|remaining| !remaining.is_zero())
2262            .ok_or_else(|| io::Error::new(io::ErrorKind::TimedOut, "request deadline elapsed"))?;
2263        TcpStream::set_read_timeout(self, Some(remaining))
2264    }
2265
2266    fn clear_client_read_deadline(&mut self) -> io::Result<()> {
2267        TcpStream::set_read_timeout(self, None)
2268    }
2269}
2270
2271impl ClientIo for TlsStream {
2272    fn set_client_read_deadline(&mut self, deadline: Instant) -> io::Result<()> {
2273        self.sock.set_request_deadline(deadline);
2274        Ok(())
2275    }
2276
2277    fn clear_client_read_deadline(&mut self) -> io::Result<()> {
2278        self.sock.clear_request_deadline()
2279    }
2280}
2281
2282struct DeadlineReader<'a, S: ClientIo + ?Sized> {
2283    stream: &'a mut S,
2284    deadline: Instant,
2285}
2286
2287impl<'a, S: ClientIo + ?Sized> DeadlineReader<'a, S> {
2288    fn new(stream: &'a mut S, timeout: Duration) -> Self {
2289        let now = Instant::now();
2290        let deadline = now.checked_add(timeout).unwrap_or(now);
2291        Self { stream, deadline }
2292    }
2293}
2294
2295impl<S: ClientIo + ?Sized> Read for DeadlineReader<'_, S> {
2296    fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
2297        if Instant::now() >= self.deadline {
2298            return Err(io::Error::new(
2299                io::ErrorKind::TimedOut,
2300                "request deadline elapsed",
2301            ));
2302        }
2303        self.stream.set_client_read_deadline(self.deadline)?;
2304        self.stream.read(buffer)
2305    }
2306}
2307
2308fn observe_client_rejection(context: &ServerContext, reason: ClientRejectionReason) {
2309    observe(
2310        context.observer.as_ref(),
2311        ProxyEvent::ClientRejected {
2312            generation: context.generation,
2313            reason,
2314        },
2315    );
2316}
2317
2318fn observe_request(
2319    context: &ServerContext,
2320    host: &str,
2321    method: ObservedMethod,
2322    phase: RequestPhase,
2323) {
2324    let Ok(host) = ObservedHost::new(host) else {
2325        return;
2326    };
2327    observe(
2328        context.observer.as_ref(),
2329        ProxyEvent::Request {
2330            generation: context.generation,
2331            host,
2332            method,
2333            phase,
2334        },
2335    );
2336}
2337
2338fn observe_response_metadata(
2339    context: &ServerContext,
2340    host: &str,
2341    method: ObservedMethod,
2342    status_code: u16,
2343    likely_main_frame: bool,
2344    observation_id: Option<u64>,
2345    metadata: &crate::InternalResponseMetadata,
2346) {
2347    let Ok(host) = ObservedHost::new(host) else {
2348        return;
2349    };
2350    let observation = ProxyResponseMetadataObservation::new(
2351        context.generation,
2352        host,
2353        method,
2354        status_code,
2355        likely_main_frame,
2356        observation_id,
2357        ProxyMetadataObservationKind::OriginResponse,
2358        None,
2359        None,
2360        metadata.clone(),
2361    );
2362    let _result = catch_unwind(AssertUnwindSafe(|| {
2363        context.metadata_observer.observe(&observation);
2364    }));
2365}
2366
2367fn observe_connect_security_decision(
2368    context: &ServerContext,
2369    host: &str,
2370    port: u16,
2371    observation_id: Option<u64>,
2372    correlation_epoch: u64,
2373) {
2374    let Ok(host) = ObservedHost::new(host) else {
2375        return;
2376    };
2377    let observation = ProxyResponseMetadataObservation::new(
2378        context.generation,
2379        host,
2380        ObservedMethod::Connect,
2381        200,
2382        false,
2383        observation_id,
2384        ProxyMetadataObservationKind::WebPkiConnectDecision,
2385        Some(port),
2386        Some(correlation_epoch),
2387        crate::InternalResponseMetadata::default(),
2388    );
2389    let _result = catch_unwind(AssertUnwindSafe(|| {
2390        context.metadata_observer.observe(&observation);
2391    }));
2392}
2393
2394fn observe_proxy_generated_response(
2395    context: &ServerContext,
2396    host: &str,
2397    method: ObservedMethod,
2398    status_code: u16,
2399    likely_main_frame: bool,
2400) {
2401    if !likely_main_frame {
2402        return;
2403    }
2404    observe_response_metadata(
2405        context,
2406        host,
2407        method,
2408        status_code,
2409        true,
2410        None,
2411        &crate::InternalResponseMetadata::default(),
2412    );
2413}
2414
2415fn observe(observer: &dyn ProxyObserver, event: ProxyEvent) {
2416    let _result = catch_unwind(AssertUnwindSafe(|| observer.observe(&event)));
2417}
2418
2419#[cfg(test)]
2420mod tests {
2421    use super::*;
2422    use crate::{
2423        NoopProxyObserver, ProxyInstanceId, ProxyPublicationAuthority, ProxyPublicationPermit,
2424        ProxyResponseHead, ProxyResponseMetadataObservation, ProxySessionId, ProxyTunnel,
2425    };
2426    use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
2427    use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
2428    use rustls::{
2429        ClientConfig, ClientConnection, DigitallySignedStruct, Error as RustlsError,
2430        SignatureScheme, StreamOwned,
2431    };
2432    use std::collections::VecDeque;
2433    use std::io::Cursor;
2434    use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
2435    use std::sync::{Mutex, mpsc};
2436    use std::thread;
2437
2438    const TEST_TIMEOUT: Duration = Duration::from_secs(2);
2439
2440    fn test_config() -> ProxyConfig {
2441        ProxyConfig::new(
2442            ProxyInstanceId::new(ProxySessionId::generate().unwrap(), 1),
2443            crate::HostScope::new("welcome").unwrap(),
2444        )
2445    }
2446
2447    struct UnusedBackend;
2448
2449    impl ProxyBackend for UnusedBackend {
2450        fn execute(
2451            &self,
2452            _request: ProxyRequest,
2453            _cancellation: &CancellationToken,
2454        ) -> Result<ProxyResponse, BackendError> {
2455            Err(BackendError::Internal)
2456        }
2457    }
2458
2459    #[derive(Clone)]
2460    enum ResponsePlan {
2461        Fixed {
2462            headers: Vec<ProxyHeader>,
2463            body: Vec<u8>,
2464        },
2465        ShortStream {
2466            expected_len: u64,
2467            bytes: Vec<u8>,
2468        },
2469    }
2470
2471    impl ResponsePlan {
2472        fn plain(body: impl Into<Vec<u8>>) -> Self {
2473            Self::Fixed {
2474                headers: vec![ProxyHeader::new("Content-Type", "text/plain")],
2475                body: body.into(),
2476            }
2477        }
2478
2479        fn response(&self) -> ProxyResponse {
2480            let (headers, body) = match self {
2481                Self::Fixed { headers, body } => {
2482                    (headers.clone(), ProxyResponseBody::Bytes(body.clone()))
2483                }
2484                Self::ShortStream {
2485                    expected_len,
2486                    bytes,
2487                } => (
2488                    vec![ProxyHeader::new("Content-Type", "application/octet-stream")],
2489                    ProxyResponseBody::Stream {
2490                        expected_len: *expected_len,
2491                        reader: Box::new(Cursor::new(bytes.clone())),
2492                    },
2493                ),
2494            };
2495            ProxyResponse {
2496                head: ProxyResponseHead {
2497                    status_code: 200,
2498                    reason_phrase: "OK".to_owned(),
2499                    headers,
2500                    observation_id: None,
2501                },
2502                body,
2503                publication_permit: Default::default(),
2504            }
2505        }
2506    }
2507
2508    struct RecordingBackend {
2509        requests: Mutex<Vec<ProxyRequest>>,
2510        response: ResponsePlan,
2511    }
2512
2513    impl RecordingBackend {
2514        fn new(response: ResponsePlan) -> Self {
2515            Self {
2516                requests: Mutex::new(Vec::new()),
2517                response,
2518            }
2519        }
2520
2521        fn request_count(&self) -> usize {
2522            self.requests
2523                .lock()
2524                .unwrap_or_else(|poisoned| poisoned.into_inner())
2525                .len()
2526        }
2527
2528        fn take_requests(&self) -> Vec<ProxyRequest> {
2529            std::mem::take(
2530                &mut *self
2531                    .requests
2532                    .lock()
2533                    .unwrap_or_else(|poisoned| poisoned.into_inner()),
2534            )
2535        }
2536    }
2537
2538    impl ProxyBackend for RecordingBackend {
2539        fn execute(
2540            &self,
2541            request: ProxyRequest,
2542            _cancellation: &CancellationToken,
2543        ) -> Result<ProxyResponse, BackendError> {
2544            self.requests
2545                .lock()
2546                .unwrap_or_else(|poisoned| poisoned.into_inner())
2547                .push(request);
2548            Ok(self.response.response())
2549        }
2550    }
2551
2552    struct GatedPublicationAuthority {
2553        revoked: AtomicBool,
2554        entered: Mutex<Option<mpsc::Sender<()>>>,
2555        release: Mutex<mpsc::Receiver<()>>,
2556    }
2557
2558    impl ProxyPublicationAuthority for GatedPublicationAuthority {
2559        fn publish(&self, operation: &mut dyn FnMut() -> io::Result<()>) -> io::Result<()> {
2560            if let Some(entered) = self
2561                .entered
2562                .lock()
2563                .unwrap_or_else(|poisoned| poisoned.into_inner())
2564                .take()
2565            {
2566                let _result = entered.send(());
2567            }
2568            self.release
2569                .lock()
2570                .unwrap_or_else(|poisoned| poisoned.into_inner())
2571                .recv_timeout(TEST_TIMEOUT)
2572                .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "test permit timed out"))?;
2573            if self.revoked.load(AtomicOrdering::Acquire) {
2574                return Err(io::Error::new(
2575                    io::ErrorKind::ConnectionAborted,
2576                    "test authority was revoked",
2577                ));
2578            }
2579            operation()
2580        }
2581    }
2582
2583    struct PublicationPermitBackend {
2584        permit: ProxyPublicationPermit,
2585    }
2586
2587    impl ProxyBackend for PublicationPermitBackend {
2588        fn execute(
2589            &self,
2590            _request: ProxyRequest,
2591            _cancellation: &CancellationToken,
2592        ) -> Result<ProxyResponse, BackendError> {
2593            let mut response = ResponsePlan::plain("must-not-publish").response();
2594            response.publication_permit = self.permit.clone();
2595            Ok(response)
2596        }
2597    }
2598
2599    struct PublicationPermitTunnelBackend {
2600        permit: ProxyPublicationPermit,
2601    }
2602
2603    impl ProxyBackend for PublicationPermitTunnelBackend {
2604        fn execute(
2605            &self,
2606            _request: ProxyRequest,
2607            _cancellation: &CancellationToken,
2608        ) -> Result<ProxyResponse, BackendError> {
2609            Err(BackendError::Internal)
2610        }
2611
2612        fn open_tunnel(
2613            &self,
2614            _request: ProxyRequest,
2615            _cancellation: &CancellationToken,
2616        ) -> Result<ProxyTunnelOpen, BackendError> {
2617            Ok(ProxyTunnelOpen::Tunnel(ProxyTunnel {
2618                head: ProxyResponseHead {
2619                    status_code: 101,
2620                    reason_phrase: "Switching Protocols".to_owned(),
2621                    headers: vec![
2622                        ProxyHeader::new("Connection", "Upgrade"),
2623                        ProxyHeader::new("Upgrade", "websocket"),
2624                        ProxyHeader::new("Sec-WebSocket-Accept", "accepted"),
2625                    ],
2626                    observation_id: None,
2627                },
2628                stream: Box::new(EchoTunnelStream {
2629                    pending: VecDeque::new(),
2630                }),
2631                publication_permit: self.permit.clone(),
2632            }))
2633        }
2634    }
2635
2636    struct EchoTunnelBackend {
2637        requests: Mutex<Vec<ProxyRequest>>,
2638        head: ProxyResponseHead,
2639        initial_origin_bytes: Vec<u8>,
2640    }
2641
2642    impl EchoTunnelBackend {
2643        fn websocket(initial_origin_bytes: impl Into<Vec<u8>>) -> Self {
2644            Self {
2645                requests: Mutex::new(Vec::new()),
2646                head: ProxyResponseHead {
2647                    status_code: 101,
2648                    reason_phrase: "Switching Protocols".to_owned(),
2649                    headers: vec![
2650                        ProxyHeader::new("Connection", "keep-alive, Upgrade, X-Origin-Hop"),
2651                        ProxyHeader::new("Upgrade", "websocket"),
2652                        ProxyHeader::new("Sec-WebSocket-Accept", "accepted"),
2653                        ProxyHeader::new("X-Origin-Hop", "secret"),
2654                        ProxyHeader::new("X-HNS-Security-Path", "secret"),
2655                    ],
2656                    observation_id: None,
2657                },
2658                initial_origin_bytes: initial_origin_bytes.into(),
2659            }
2660        }
2661
2662        fn with_head(head: ProxyResponseHead) -> Self {
2663            Self {
2664                requests: Mutex::new(Vec::new()),
2665                head,
2666                initial_origin_bytes: Vec::new(),
2667            }
2668        }
2669
2670        fn take_requests(&self) -> Vec<ProxyRequest> {
2671            std::mem::take(
2672                &mut *self
2673                    .requests
2674                    .lock()
2675                    .unwrap_or_else(|poisoned| poisoned.into_inner()),
2676            )
2677        }
2678    }
2679
2680    impl ProxyBackend for EchoTunnelBackend {
2681        fn execute(
2682            &self,
2683            _request: ProxyRequest,
2684            _cancellation: &CancellationToken,
2685        ) -> Result<ProxyResponse, BackendError> {
2686            Err(BackendError::Internal)
2687        }
2688
2689        fn open_tunnel(
2690            &self,
2691            request: ProxyRequest,
2692            _cancellation: &CancellationToken,
2693        ) -> Result<ProxyTunnelOpen, BackendError> {
2694            self.requests
2695                .lock()
2696                .unwrap_or_else(|poisoned| poisoned.into_inner())
2697                .push(request);
2698            Ok(ProxyTunnelOpen::Tunnel(ProxyTunnel {
2699                head: self.head.clone(),
2700                stream: Box::new(EchoTunnelStream {
2701                    pending: self.initial_origin_bytes.iter().copied().collect(),
2702                }),
2703                publication_permit: Default::default(),
2704            }))
2705        }
2706    }
2707
2708    struct EchoTunnelStream {
2709        pending: VecDeque<u8>,
2710    }
2711
2712    impl Read for EchoTunnelStream {
2713        fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
2714            if self.pending.is_empty() {
2715                thread::sleep(Duration::from_millis(5));
2716                return Err(io::Error::new(io::ErrorKind::TimedOut, "idle test tunnel"));
2717            }
2718            let count = buffer.len().min(self.pending.len());
2719            for output in &mut buffer[..count] {
2720                *output = self.pending.pop_front().unwrap();
2721            }
2722            Ok(count)
2723        }
2724    }
2725
2726    impl Write for EchoTunnelStream {
2727        fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
2728            self.pending.extend(buffer.iter().copied());
2729            Ok(buffer.len())
2730        }
2731
2732        fn flush(&mut self) -> io::Result<()> {
2733            Ok(())
2734        }
2735    }
2736
2737    struct ConnectPassthroughBackend {
2738        requests: Mutex<Vec<ProxyRequest>>,
2739    }
2740
2741    struct ConnectEchoState {
2742        pending: Mutex<VecDeque<u8>>,
2743        shutdown: AtomicBool,
2744    }
2745
2746    struct ConnectEchoReader(Arc<ConnectEchoState>);
2747
2748    impl Read for ConnectEchoReader {
2749        fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
2750            let mut pending = self
2751                .0
2752                .pending
2753                .lock()
2754                .unwrap_or_else(|poisoned| poisoned.into_inner());
2755            if pending.is_empty() {
2756                if self.0.shutdown.load(AtomicOrdering::Acquire) {
2757                    return Ok(0);
2758                }
2759                drop(pending);
2760                thread::sleep(Duration::from_millis(5));
2761                return Err(io::Error::new(io::ErrorKind::TimedOut, "idle test tunnel"));
2762            }
2763            let count = buffer.len().min(pending.len());
2764            for output in &mut buffer[..count] {
2765                *output = pending.pop_front().unwrap();
2766            }
2767            Ok(count)
2768        }
2769    }
2770
2771    struct ConnectEchoWriter(Arc<ConnectEchoState>);
2772
2773    impl Write for ConnectEchoWriter {
2774        fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
2775            if self.0.shutdown.load(AtomicOrdering::Acquire) {
2776                return Err(io::Error::new(
2777                    io::ErrorKind::BrokenPipe,
2778                    "test tunnel was shut down",
2779                ));
2780            }
2781            self.0
2782                .pending
2783                .lock()
2784                .unwrap_or_else(|poisoned| poisoned.into_inner())
2785                .extend(buffer.iter().copied());
2786            Ok(buffer.len())
2787        }
2788
2789        fn flush(&mut self) -> io::Result<()> {
2790            Ok(())
2791        }
2792    }
2793
2794    struct ConnectEchoShutdown(Arc<ConnectEchoState>);
2795
2796    impl crate::ProxyConnectShutdown for ConnectEchoShutdown {
2797        fn shutdown(&self) {
2798            self.0.shutdown.store(true, AtomicOrdering::Release);
2799        }
2800    }
2801
2802    struct NoopConnectShutdown;
2803
2804    impl crate::ProxyConnectShutdown for NoopConnectShutdown {
2805        fn shutdown(&self) {}
2806    }
2807
2808    struct BulkDownloadBackend {
2809        bytes: Vec<u8>,
2810    }
2811
2812    impl ProxyBackend for BulkDownloadBackend {
2813        fn open_connect(
2814            &self,
2815            _request: ProxyRequest,
2816            _cancellation: &CancellationToken,
2817        ) -> Result<ProxyConnectOpen, BackendError> {
2818            Ok(ProxyConnectOpen::WebPkiPassthrough(ProxyConnectTunnel {
2819                reader: Box::new(Cursor::new(self.bytes.clone())),
2820                writer: Box::new(io::sink()),
2821                shutdown: Arc::new(NoopConnectShutdown),
2822                observation_id: None,
2823                correlation_epoch: 1,
2824                publication_permit: ProxyPublicationPermit::default(),
2825            }))
2826        }
2827
2828        fn execute(
2829            &self,
2830            _request: ProxyRequest,
2831            _cancellation: &CancellationToken,
2832        ) -> Result<ProxyResponse, BackendError> {
2833            Err(BackendError::Internal)
2834        }
2835    }
2836
2837    struct BulkUploadState {
2838        received: std::sync::atomic::AtomicUsize,
2839        shutdown: AtomicBool,
2840    }
2841
2842    struct IdleOriginReader(Arc<BulkUploadState>);
2843
2844    impl Read for IdleOriginReader {
2845        fn read(&mut self, _buffer: &mut [u8]) -> io::Result<usize> {
2846            if self.0.shutdown.load(AtomicOrdering::Acquire) {
2847                return Ok(0);
2848            }
2849            thread::sleep(Duration::from_millis(25));
2850            Err(io::Error::new(
2851                io::ErrorKind::TimedOut,
2852                "idle origin test reader",
2853            ))
2854        }
2855    }
2856
2857    struct CountingOriginWriter(Arc<BulkUploadState>);
2858
2859    impl Write for CountingOriginWriter {
2860        fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
2861            self.0
2862                .received
2863                .fetch_add(buffer.len(), AtomicOrdering::AcqRel);
2864            Ok(buffer.len())
2865        }
2866
2867        fn flush(&mut self) -> io::Result<()> {
2868            Ok(())
2869        }
2870    }
2871
2872    struct BulkUploadShutdown(Arc<BulkUploadState>);
2873
2874    impl crate::ProxyConnectShutdown for BulkUploadShutdown {
2875        fn shutdown(&self) {
2876            self.0.shutdown.store(true, AtomicOrdering::Release);
2877        }
2878    }
2879
2880    struct BulkUploadBackend {
2881        state: Arc<BulkUploadState>,
2882    }
2883
2884    impl ProxyBackend for BulkUploadBackend {
2885        fn open_connect(
2886            &self,
2887            _request: ProxyRequest,
2888            _cancellation: &CancellationToken,
2889        ) -> Result<ProxyConnectOpen, BackendError> {
2890            Ok(ProxyConnectOpen::WebPkiPassthrough(ProxyConnectTunnel {
2891                reader: Box::new(IdleOriginReader(Arc::clone(&self.state))),
2892                writer: Box::new(CountingOriginWriter(Arc::clone(&self.state))),
2893                shutdown: Arc::new(BulkUploadShutdown(Arc::clone(&self.state))),
2894                observation_id: None,
2895                correlation_epoch: 1,
2896                publication_permit: ProxyPublicationPermit::default(),
2897            }))
2898        }
2899
2900        fn execute(
2901            &self,
2902            _request: ProxyRequest,
2903            _cancellation: &CancellationToken,
2904        ) -> Result<ProxyResponse, BackendError> {
2905            Err(BackendError::Internal)
2906        }
2907    }
2908
2909    struct WebPkiUnavailableBackend;
2910
2911    impl ProxyBackend for WebPkiUnavailableBackend {
2912        fn open_connect(
2913            &self,
2914            _request: ProxyRequest,
2915            _cancellation: &CancellationToken,
2916        ) -> Result<ProxyConnectOpen, BackendError> {
2917            Ok(ProxyConnectOpen::WebPkiUnavailable)
2918        }
2919
2920        fn execute(
2921            &self,
2922            _request: ProxyRequest,
2923            _cancellation: &CancellationToken,
2924        ) -> Result<ProxyResponse, BackendError> {
2925            Err(BackendError::Internal)
2926        }
2927    }
2928
2929    struct UnexpectedConnectFailureBackend {
2930        panic: bool,
2931    }
2932
2933    impl ProxyBackend for UnexpectedConnectFailureBackend {
2934        fn open_connect(
2935            &self,
2936            _request: ProxyRequest,
2937            _cancellation: &CancellationToken,
2938        ) -> Result<ProxyConnectOpen, BackendError> {
2939            if self.panic {
2940                panic!("test CONNECT backend panic");
2941            }
2942            Err(BackendError::Internal)
2943        }
2944
2945        fn execute(
2946            &self,
2947            _request: ProxyRequest,
2948            _cancellation: &CancellationToken,
2949        ) -> Result<ProxyResponse, BackendError> {
2950            Err(BackendError::Internal)
2951        }
2952    }
2953
2954    impl ProxyBackend for ConnectPassthroughBackend {
2955        fn open_connect(
2956            &self,
2957            request: ProxyRequest,
2958            _cancellation: &CancellationToken,
2959        ) -> Result<ProxyConnectOpen, BackendError> {
2960            self.requests
2961                .lock()
2962                .unwrap_or_else(|poisoned| poisoned.into_inner())
2963                .push(request);
2964            let state = Arc::new(ConnectEchoState {
2965                pending: Mutex::new(b"origin".iter().copied().collect()),
2966                shutdown: AtomicBool::new(false),
2967            });
2968            Ok(ProxyConnectOpen::WebPkiPassthrough(ProxyConnectTunnel {
2969                reader: Box::new(ConnectEchoReader(Arc::clone(&state))),
2970                writer: Box::new(ConnectEchoWriter(Arc::clone(&state))),
2971                shutdown: Arc::new(ConnectEchoShutdown(state)),
2972                observation_id: Some(17),
2973                correlation_epoch: 9,
2974                publication_permit: ProxyPublicationPermit::default(),
2975            }))
2976        }
2977
2978        fn execute(
2979            &self,
2980            _request: ProxyRequest,
2981            _cancellation: &CancellationToken,
2982        ) -> Result<ProxyResponse, BackendError> {
2983            Err(BackendError::Internal)
2984        }
2985    }
2986
2987    struct HttpRejectingTunnelBackend;
2988
2989    impl ProxyBackend for HttpRejectingTunnelBackend {
2990        fn execute(
2991            &self,
2992            _request: ProxyRequest,
2993            _cancellation: &CancellationToken,
2994        ) -> Result<ProxyResponse, BackendError> {
2995            Err(BackendError::Internal)
2996        }
2997
2998        fn open_tunnel(
2999            &self,
3000            _request: ProxyRequest,
3001            _cancellation: &CancellationToken,
3002        ) -> Result<ProxyTunnelOpen, BackendError> {
3003            Ok(ProxyTunnelOpen::Response(ProxyResponse {
3004                head: ProxyResponseHead {
3005                    status_code: 404,
3006                    reason_phrase: "HNS Name Not Found".to_owned(),
3007                    headers: vec![
3008                        ProxyHeader::new("Content-Type", "text/plain"),
3009                        ProxyHeader::new("X-HNS-Security-Path", "verified-non-inclusion"),
3010                    ],
3011                    observation_id: None,
3012                },
3013                body: ProxyResponseBody::Bytes(b"verified non-inclusion".to_vec()),
3014                publication_permit: Default::default(),
3015            }))
3016        }
3017    }
3018
3019    struct CooperativeStreamBackend {
3020        read_started: Mutex<Option<mpsc::Sender<()>>>,
3021    }
3022
3023    impl ProxyBackend for CooperativeStreamBackend {
3024        fn execute(
3025            &self,
3026            _request: ProxyRequest,
3027            cancellation: &CancellationToken,
3028        ) -> Result<ProxyResponse, BackendError> {
3029            let read_started = self
3030                .read_started
3031                .lock()
3032                .unwrap_or_else(|poisoned| poisoned.into_inner())
3033                .take();
3034            Ok(ProxyResponse {
3035                head: ProxyResponseHead {
3036                    status_code: 200,
3037                    reason_phrase: "OK".to_owned(),
3038                    headers: vec![],
3039                    observation_id: None,
3040                },
3041                body: ProxyResponseBody::Stream {
3042                    expected_len: 1,
3043                    reader: Box::new(CooperativeCancellationReader {
3044                        cancellation: cancellation.clone(),
3045                        read_started,
3046                    }),
3047                },
3048                publication_permit: Default::default(),
3049            })
3050        }
3051    }
3052
3053    struct CooperativeCancellationReader {
3054        cancellation: CancellationToken,
3055        read_started: Option<mpsc::Sender<()>>,
3056    }
3057
3058    impl Read for CooperativeCancellationReader {
3059        fn read(&mut self, _buffer: &mut [u8]) -> io::Result<usize> {
3060            if let Some(read_started) = self.read_started.take() {
3061                let _result = read_started.send(());
3062            }
3063            if self
3064                .cancellation
3065                .wait_cancelled_timeout(Duration::from_secs(5))
3066            {
3067                Err(io::Error::new(
3068                    io::ErrorKind::Interrupted,
3069                    "proxy generation cancelled",
3070                ))
3071            } else {
3072                Err(io::Error::new(
3073                    io::ErrorKind::TimedOut,
3074                    "test cancellation did not arrive",
3075                ))
3076            }
3077        }
3078    }
3079
3080    struct AcceptedObserver(mpsc::Sender<()>);
3081
3082    impl ProxyObserver for AcceptedObserver {
3083        fn observe(&self, event: &ProxyEvent) {
3084            if matches!(
3085                event,
3086                ProxyEvent::Request {
3087                    phase: RequestPhase::Accepted,
3088                    ..
3089                }
3090            ) {
3091                let _result = self.0.send(());
3092            }
3093        }
3094    }
3095
3096    fn dane_browser_config() -> ProxyConfig {
3097        ProxyConfig::dane_browser(ProxyInstanceId::new(ProxySessionId::generate().unwrap(), 1))
3098    }
3099
3100    fn start_recording_proxy(backend: Arc<RecordingBackend>) -> RunningProxy {
3101        RunningProxy::start(test_config(), backend, Arc::new(NoopProxyObserver)).unwrap()
3102    }
3103
3104    fn start_recording_proxy_with_metadata(
3105        backend: Arc<RecordingBackend>,
3106        metadata_observer: Arc<dyn ProxyResponseMetadataObserver>,
3107    ) -> RunningProxy {
3108        RunningProxy::start_with_metadata_observer(
3109            test_config(),
3110            backend,
3111            Arc::new(NoopProxyObserver),
3112            metadata_observer,
3113        )
3114        .unwrap()
3115    }
3116
3117    fn send_raw(proxy: &RunningProxy, request: &[u8]) -> Vec<u8> {
3118        let mut stream = TcpStream::connect(proxy.endpoint().address()).unwrap();
3119        stream.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
3120        stream.set_write_timeout(Some(TEST_TIMEOUT)).unwrap();
3121        stream.write_all(request).unwrap();
3122        stream.shutdown(Shutdown::Write).unwrap();
3123        let mut response = Vec::new();
3124        match stream.read_to_end(&mut response) {
3125            Ok(_) => {}
3126            Err(error) if error.kind() == io::ErrorKind::ConnectionReset => {}
3127            Err(error) => panic!("unable to read proxy response: {error}"),
3128        }
3129        response
3130    }
3131
3132    fn response_status(response: &[u8]) -> u16 {
3133        let line_end = response
3134            .windows(2)
3135            .position(|pair| pair == b"\r\n")
3136            .expect("response status line ends");
3137        std::str::from_utf8(&response[..line_end])
3138            .unwrap()
3139            .split_whitespace()
3140            .nth(1)
3141            .unwrap()
3142            .parse()
3143            .unwrap()
3144    }
3145
3146    fn response_parts(response: &[u8]) -> (&str, &[u8]) {
3147        let head_end = response
3148            .windows(4)
3149            .position(|window| window == b"\r\n\r\n")
3150            .expect("response head terminates");
3151        (
3152            std::str::from_utf8(&response[..head_end + 4]).unwrap(),
3153            &response[head_end + 4..],
3154        )
3155    }
3156
3157    fn auth_header(proxy: &RunningProxy) -> String {
3158        format!(
3159            "Proxy-Authorization: {}\r\n",
3160            proxy.endpoint().authorization_header_value()
3161        )
3162    }
3163
3164    #[derive(Debug)]
3165    struct CapturingCertificateVerifier {
3166        certificate_der: Arc<Mutex<Option<Vec<u8>>>>,
3167    }
3168
3169    impl ServerCertVerifier for CapturingCertificateVerifier {
3170        fn verify_server_cert(
3171            &self,
3172            end_entity: &CertificateDer<'_>,
3173            _intermediates: &[CertificateDer<'_>],
3174            _server_name: &ServerName<'_>,
3175            _ocsp_response: &[u8],
3176            _now: UnixTime,
3177        ) -> Result<ServerCertVerified, RustlsError> {
3178            *self
3179                .certificate_der
3180                .lock()
3181                .unwrap_or_else(|poisoned| poisoned.into_inner()) =
3182                Some(end_entity.as_ref().to_vec());
3183            Ok(ServerCertVerified::assertion())
3184        }
3185
3186        fn verify_tls12_signature(
3187            &self,
3188            message: &[u8],
3189            certificate: &CertificateDer<'_>,
3190            signature: &DigitallySignedStruct,
3191        ) -> Result<HandshakeSignatureValid, RustlsError> {
3192            rustls::crypto::verify_tls12_signature(
3193                message,
3194                certificate,
3195                signature,
3196                &rustls::crypto::ring::default_provider().signature_verification_algorithms,
3197            )
3198        }
3199
3200        fn verify_tls13_signature(
3201            &self,
3202            message: &[u8],
3203            certificate: &CertificateDer<'_>,
3204            signature: &DigitallySignedStruct,
3205        ) -> Result<HandshakeSignatureValid, RustlsError> {
3206            rustls::crypto::verify_tls13_signature(
3207                message,
3208                certificate,
3209                signature,
3210                &rustls::crypto::ring::default_provider().signature_verification_algorithms,
3211            )
3212        }
3213
3214        fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
3215            rustls::crypto::ring::default_provider()
3216                .signature_verification_algorithms
3217                .supported_schemes()
3218        }
3219    }
3220
3221    fn tls_client_config(
3222        certificate_der: Arc<Mutex<Option<Vec<u8>>>>,
3223        alpn_protocols: &[&[u8]],
3224        enable_sni: bool,
3225    ) -> Arc<ClientConfig> {
3226        let mut config =
3227            ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
3228                .with_safe_default_protocol_versions()
3229                .unwrap()
3230                .dangerous()
3231                .with_custom_certificate_verifier(Arc::new(CapturingCertificateVerifier {
3232                    certificate_der,
3233                }))
3234                .with_no_client_auth();
3235        config.enable_sni = enable_sni;
3236        config.alpn_protocols = alpn_protocols
3237            .iter()
3238            .map(|protocol| protocol.to_vec())
3239            .collect();
3240        Arc::new(config)
3241    }
3242
3243    fn read_response_head(input: &mut impl Read) -> io::Result<Vec<u8>> {
3244        let mut response = Vec::new();
3245        let mut byte = [0_u8; 1];
3246        while response.len() < ProxyLimits::DEFAULT_MAX_HEADER_BYTES {
3247            if input.read(&mut byte)? == 0 {
3248                return Err(io::Error::new(
3249                    io::ErrorKind::UnexpectedEof,
3250                    "response ended before its head",
3251                ));
3252            }
3253            response.push(byte[0]);
3254            if response.ends_with(b"\r\n\r\n") {
3255                return Ok(response);
3256            }
3257        }
3258        Err(io::Error::new(
3259            io::ErrorKind::InvalidData,
3260            "response head exceeded the test limit",
3261        ))
3262    }
3263
3264    fn begin_authenticated_connect(proxy: &RunningProxy, authority: &str) -> TcpStream {
3265        let mut stream = TcpStream::connect(proxy.endpoint().address()).unwrap();
3266        stream.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
3267        stream.set_write_timeout(Some(TEST_TIMEOUT)).unwrap();
3268        let request = format!(
3269            "CONNECT {authority} HTTP/1.1\r\nHost: {authority}\r\n{}\r\n",
3270            auth_header(proxy)
3271        );
3272        stream.write_all(request.as_bytes()).unwrap();
3273        stream.flush().unwrap();
3274        let response = read_response_head(&mut stream).unwrap();
3275        assert_eq!(response_status(&response), 200, "{response:?}");
3276        stream
3277    }
3278
3279    fn complete_tls_handshake(
3280        stream: TcpStream,
3281        server_name: &str,
3282        enable_sni: bool,
3283        alpn_protocols: &[&[u8]],
3284    ) -> io::Result<(StreamOwned<ClientConnection, TcpStream>, Vec<u8>)> {
3285        let certificate_der = Arc::new(Mutex::new(None));
3286        let config = tls_client_config(Arc::clone(&certificate_der), alpn_protocols, enable_sni);
3287        let server_name = ServerName::try_from(server_name.to_owned())
3288            .map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error.to_string()))?;
3289        let connection = ClientConnection::new(config, server_name)
3290            .map_err(|error| io::Error::other(error.to_string()))?;
3291        let mut tls = StreamOwned::new(connection, stream);
3292        while tls.conn.is_handshaking() {
3293            tls.conn.complete_io(&mut tls.sock)?;
3294        }
3295        let certificate_der = certificate_der
3296            .lock()
3297            .unwrap_or_else(|poisoned| poisoned.into_inner())
3298            .clone()
3299            .ok_or_else(|| io::Error::other("server did not present a certificate"))?;
3300        Ok((tls, certificate_der))
3301    }
3302
3303    fn send_tls_request(
3304        proxy: &RunningProxy,
3305        authority: &str,
3306        certificate_host: &str,
3307        request: &[u8],
3308    ) -> (Vec<u8>, Vec<u8>) {
3309        let stream = begin_authenticated_connect(proxy, authority);
3310        assert!(proxy.local_certificate_pin(certificate_host).is_some());
3311        let (mut tls, certificate_der) =
3312            complete_tls_handshake(stream, certificate_host, true, &[b"h2", b"http/1.1"]).unwrap();
3313        assert_eq!(tls.conn.alpn_protocol(), Some(b"http/1.1".as_slice()));
3314        tls.write_all(request).unwrap();
3315        tls.flush().unwrap();
3316        let mut response = Vec::new();
3317        tls.read_to_end(&mut response).unwrap();
3318        (response, certificate_der)
3319    }
3320
3321    fn wait_for_active_clients(proxy: &RunningProxy, expected: usize) {
3322        let deadline = Instant::now() + TEST_TIMEOUT;
3323        while proxy.active_clients() != expected && Instant::now() < deadline {
3324            thread::yield_now();
3325        }
3326        assert_eq!(proxy.active_clients(), expected);
3327    }
3328
3329    fn assert_connection_closed(mut stream: TcpStream) {
3330        stream.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
3331        let mut byte = [0_u8; 1];
3332        match stream.read(&mut byte) {
3333            Ok(0) => {}
3334            Err(error)
3335                if matches!(
3336                    error.kind(),
3337                    io::ErrorKind::ConnectionReset
3338                        | io::ErrorKind::NotConnected
3339                        | io::ErrorKind::UnexpectedEof
3340                ) => {}
3341            result => panic!("expected a closed proxy connection, got {result:?}"),
3342        }
3343    }
3344
3345    fn parsed_test_head(method: &str, headers: &[(&str, &str)]) -> RequestHead {
3346        let mut request = format!("{method} http://welcome/ HTTP/1.1\r\nHost: welcome\r\n");
3347        for (name, value) in headers {
3348            request.push_str(name);
3349            request.push_str(": ");
3350            request.push_str(value);
3351            request.push_str("\r\n");
3352        }
3353        request.push_str("\r\n");
3354        crate::parse_request_head(request.as_bytes()).unwrap()
3355    }
3356
3357    #[test]
3358    fn likely_main_frame_classification_treats_fetch_destination_as_authoritative() {
3359        let classify = |method, path, headers: &[(&str, &str)]| {
3360            is_likely_main_frame_navigation(&parsed_test_head(method, headers), path)
3361        };
3362
3363        assert!(classify(
3364            "GET",
3365            "/asset.js",
3366            &[("Sec-Fetch-Dest", "document")]
3367        ));
3368        assert!(!classify(
3369            "HEAD",
3370            "/asset.js",
3371            &[
3372                ("Sec-Fetch-Dest", "IFRAME"),
3373                ("Sec-Fetch-Mode", "navigate"),
3374                ("Accept", "text/html"),
3375            ]
3376        ));
3377        assert!(!classify(
3378            "GET",
3379            "/",
3380            &[
3381                ("Sec-Fetch-Dest", "script"),
3382                ("Sec-Fetch-Mode", "navigate"),
3383                ("Accept", "text/html"),
3384            ],
3385        ));
3386        assert!(!classify(
3387            "GET",
3388            "/",
3389            &[
3390                ("Sec-Fetch-Dest", "future-subresource"),
3391                ("Sec-Fetch-Mode", "navigate"),
3392                ("Upgrade-Insecure-Requests", "1"),
3393                ("Accept", "text/html"),
3394            ],
3395        ));
3396        assert!(classify(
3397            "GET",
3398            "/asset.js",
3399            &[("Sec-Fetch-Mode", "navigate")],
3400        ));
3401        assert!(classify(
3402            "GET",
3403            "/asset.js",
3404            &[("Upgrade-Insecure-Requests", "1")],
3405        ));
3406        assert!(classify(
3407            "GET",
3408            "/asset.js",
3409            &[("Accept", "application/xhtml+xml, TEXT/HTML;q=0.9")],
3410        ));
3411        assert!(classify("GET", "/", &[]));
3412        assert!(!classify("GET", "/?private=1", &[]));
3413        assert!(!classify("GET", "/asset.css", &[]));
3414        assert!(!classify("POST", "/", &[("Sec-Fetch-Dest", "document")],));
3415    }
3416
3417    #[test]
3418    fn starts_on_ephemeral_loopback_with_fresh_redacted_credentials() {
3419        let first = RunningProxy::start(
3420            test_config(),
3421            Arc::new(UnusedBackend),
3422            Arc::new(NoopProxyObserver),
3423        )
3424        .unwrap();
3425        let second = RunningProxy::start(
3426            test_config(),
3427            Arc::new(UnusedBackend),
3428            Arc::new(NoopProxyObserver),
3429        )
3430        .unwrap();
3431
3432        assert!(first.endpoint().address().ip().is_loopback());
3433        assert_ne!(first.endpoint().port(), 0);
3434        assert_ne!(first.endpoint().realm(), second.endpoint().realm());
3435        assert_ne!(first.endpoint().password(), second.endpoint().password());
3436        let debug = format!("{first:?}");
3437        assert!(!debug.contains(first.endpoint().realm()));
3438        assert!(!debug.contains(first.endpoint().password()));
3439        first.stop();
3440        first.stop();
3441        second.stop();
3442        assert!(first.is_stopped());
3443    }
3444
3445    #[test]
3446    fn request_stop_revokes_admission_before_the_blocking_join() {
3447        let proxy = RunningProxy::start(
3448            test_config(),
3449            Arc::new(UnusedBackend),
3450            Arc::new(NoopProxyObserver),
3451        )
3452        .unwrap();
3453
3454        proxy.request_stop();
3455        assert!(proxy.local_certificate_pin("welcome").is_none());
3456        assert!(!proxy.matches_local_certificate("welcome", b"certificate"));
3457
3458        proxy.stop();
3459        assert!(proxy.is_stopped());
3460    }
3461
3462    #[test]
3463    fn concurrent_stop_emits_lifecycle_events_once_and_in_order() {
3464        let events = Arc::new(Mutex::new(Vec::new()));
3465        let event_sink = Arc::clone(&events);
3466        let observer = move |event: &ProxyEvent| {
3467            if let ProxyEvent::Lifecycle { event, .. } = event {
3468                event_sink
3469                    .lock()
3470                    .unwrap_or_else(|poisoned| poisoned.into_inner())
3471                    .push(*event);
3472            }
3473        };
3474        let proxy = Arc::new(
3475            RunningProxy::start(test_config(), Arc::new(UnusedBackend), Arc::new(observer))
3476                .unwrap(),
3477        );
3478        let barrier = Arc::new(std::sync::Barrier::new(9));
3479        let callers: Vec<_> = (0..8)
3480            .map(|_| {
3481                let proxy = Arc::clone(&proxy);
3482                let barrier = Arc::clone(&barrier);
3483                thread::spawn(move || {
3484                    barrier.wait();
3485                    proxy.stop();
3486                })
3487            })
3488            .collect();
3489        barrier.wait();
3490        for caller in callers {
3491            caller.join().unwrap();
3492        }
3493
3494        assert!(proxy.is_stopped());
3495        assert_eq!(
3496            *events
3497                .lock()
3498                .unwrap_or_else(|poisoned| poisoned.into_inner()),
3499            vec![
3500                LifecycleEvent::Listening {
3501                    port: proxy.endpoint().port(),
3502                },
3503                LifecycleEvent::Stopping,
3504                LifecycleEvent::Stopped {
3505                    reason: StopReason::Requested,
3506                },
3507            ]
3508        );
3509    }
3510
3511    #[test]
3512    fn stopping_observer_can_reenter_running_proxy_stop() {
3513        let proxy_slot = Arc::new(Mutex::new(None::<std::sync::Weak<RunningProxy>>));
3514        let observer_slot = Arc::clone(&proxy_slot);
3515        let observer = move |event: &ProxyEvent| {
3516            if matches!(
3517                event,
3518                ProxyEvent::Lifecycle {
3519                    event: LifecycleEvent::Stopping,
3520                    ..
3521                }
3522            ) {
3523                let proxy = observer_slot
3524                    .lock()
3525                    .unwrap_or_else(|poisoned| poisoned.into_inner())
3526                    .as_ref()
3527                    .and_then(std::sync::Weak::upgrade);
3528                if let Some(proxy) = proxy {
3529                    proxy.stop();
3530                }
3531            }
3532        };
3533        let proxy = Arc::new(
3534            RunningProxy::start(test_config(), Arc::new(UnusedBackend), Arc::new(observer))
3535                .unwrap(),
3536        );
3537        *proxy_slot
3538            .lock()
3539            .unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(Arc::downgrade(&proxy));
3540
3541        proxy.stop();
3542
3543        assert!(proxy.is_stopped());
3544        assert_eq!(proxy.active_clients(), 0);
3545    }
3546
3547    #[test]
3548    fn final_running_proxy_owner_dropped_from_callback_is_reaped_and_stopped() {
3549        let proxy_slot = Arc::new(Mutex::new(None::<std::sync::Weak<RunningProxy>>));
3550        let observer_slot = Arc::clone(&proxy_slot);
3551        let (upgraded_tx, upgraded_rx) = mpsc::channel();
3552        let (release_tx, release_rx) = mpsc::channel();
3553        let release_rx = Mutex::new(release_rx);
3554        let (released_tx, released_rx) = mpsc::channel();
3555        let (stopped_tx, stopped_rx) = mpsc::channel();
3556        let events = Arc::new(Mutex::new(Vec::new()));
3557        let event_sink = Arc::clone(&events);
3558        let observer = move |event: &ProxyEvent| {
3559            if let ProxyEvent::Lifecycle { event, .. } = event {
3560                event_sink
3561                    .lock()
3562                    .unwrap_or_else(|poisoned| poisoned.into_inner())
3563                    .push(*event);
3564                if matches!(event, LifecycleEvent::Stopped { .. }) {
3565                    let _result = stopped_tx.send(());
3566                }
3567                return;
3568            }
3569            if matches!(
3570                event,
3571                ProxyEvent::Request {
3572                    phase: RequestPhase::Accepted,
3573                    ..
3574                }
3575            ) {
3576                let proxy = observer_slot
3577                    .lock()
3578                    .unwrap_or_else(|poisoned| poisoned.into_inner())
3579                    .as_ref()
3580                    .and_then(std::sync::Weak::upgrade)
3581                    .unwrap();
3582                upgraded_tx.send(()).unwrap();
3583                release_rx
3584                    .lock()
3585                    .unwrap_or_else(|poisoned| poisoned.into_inner())
3586                    .recv_timeout(TEST_TIMEOUT)
3587                    .unwrap();
3588                drop(proxy);
3589                released_tx.send(()).unwrap();
3590            }
3591        };
3592        let proxy = Arc::new(
3593            RunningProxy::start(test_config(), Arc::new(UnusedBackend), Arc::new(observer))
3594                .unwrap(),
3595        );
3596        *proxy_slot
3597            .lock()
3598            .unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(Arc::downgrade(&proxy));
3599        let address = proxy.endpoint().address();
3600        let request = format!(
3601            "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
3602            auth_header(&proxy)
3603        );
3604        let mut client = TcpStream::connect(address).unwrap();
3605        client.write_all(request.as_bytes()).unwrap();
3606        client.flush().unwrap();
3607        upgraded_rx.recv_timeout(TEST_TIMEOUT).unwrap();
3608
3609        drop(proxy);
3610        release_tx.send(()).unwrap();
3611        released_rx.recv_timeout(TEST_TIMEOUT).unwrap();
3612        drop(client);
3613        stopped_rx.recv_timeout(TEST_TIMEOUT).unwrap();
3614        assert_eq!(
3615            *events
3616                .lock()
3617                .unwrap_or_else(|poisoned| poisoned.into_inner()),
3618            vec![
3619                LifecycleEvent::Listening {
3620                    port: address.port(),
3621                },
3622                LifecycleEvent::Stopping,
3623                LifecycleEvent::Stopped {
3624                    reason: StopReason::Requested,
3625                },
3626            ]
3627        );
3628
3629        let deadline = Instant::now() + TEST_TIMEOUT;
3630        loop {
3631            match std::net::TcpListener::bind(address) {
3632                Ok(listener) => {
3633                    drop(listener);
3634                    break;
3635                }
3636                Err(_) => {
3637                    assert!(Instant::now() < deadline);
3638                    thread::sleep(Duration::from_millis(5));
3639                }
3640            }
3641        }
3642    }
3643
3644    #[test]
3645    fn listener_failure_revokes_pins_and_reports_quiescence_once() {
3646        let instance = ProxyInstanceId::new(ProxySessionId::generate().unwrap(), 71);
3647        let identities = Arc::new(LocalTlsIdentityStore::new(instance));
3648        let host = crate::NormalizedHost::parse("welcome").unwrap();
3649        drop(identities.prepare(&host).unwrap());
3650        assert!(identities.pin(&host).is_some());
3651
3652        let events = Arc::new(Mutex::new(Vec::new()));
3653        let event_sink = Arc::clone(&events);
3654        let observer: Arc<dyn ProxyObserver> = Arc::new(move |event: &ProxyEvent| {
3655            if let ProxyEvent::Lifecycle { event, .. } = event {
3656                event_sink
3657                    .lock()
3658                    .unwrap_or_else(|poisoned| poisoned.into_inner())
3659                    .push(*event);
3660            }
3661        });
3662        let stop_state = Arc::new(ProxyStopState::new());
3663        stop_state.publish_listening();
3664        let handler = listener_exit_handler(
3665            Arc::clone(&identities),
3666            Arc::clone(&observer),
3667            71,
3668            Arc::clone(&stop_state),
3669        );
3670
3671        handler(ListenerExitPhase::FailureDetected);
3672        assert!(stop_state.requested.load(Ordering::Acquire));
3673        assert!(!stop_state.completed.load(Ordering::Acquire));
3674        assert!(identities.pin(&host).is_none());
3675        assert_eq!(
3676            *events
3677                .lock()
3678                .unwrap_or_else(|poisoned| poisoned.into_inner()),
3679            vec![LifecycleEvent::Stopping]
3680        );
3681
3682        handler(ListenerExitPhase::Quiesced(
3683            ListenerExitReason::ListenerFailure,
3684        ));
3685        handler(ListenerExitPhase::Quiesced(
3686            ListenerExitReason::ListenerFailure,
3687        ));
3688        assert!(stop_state.completed.load(Ordering::Acquire));
3689        assert_eq!(
3690            *events
3691                .lock()
3692                .unwrap_or_else(|poisoned| poisoned.into_inner()),
3693            vec![
3694                LifecycleEvent::Stopping,
3695                LifecycleEvent::Stopped {
3696                    reason: StopReason::ListenerFailure,
3697                },
3698            ]
3699        );
3700    }
3701
3702    #[test]
3703    fn authentication_precedes_target_validation_and_rejects_duplicates() {
3704        let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
3705        let proxy = start_recording_proxy(Arc::clone(&backend));
3706        let valid = proxy.endpoint().authorization_header_value();
3707        let cases = [
3708            "GET not-an-absolute-target HTTP/1.1\r\nHost: mismatch.example\r\n\r\n"
3709                .to_owned(),
3710            "GET not-an-absolute-target HTTP/1.1\r\nHost: mismatch.example\r\nProxy-Authorization: Basic d3Jvbmc6d3Jvbmc=\r\n\r\n"
3711                .to_owned(),
3712            format!(
3713                "GET not-an-absolute-target HTTP/1.1\r\nHost: mismatch.example\r\nProxy-Authorization: {valid}\r\nProxy-Authorization: {valid}\r\n\r\n"
3714            ),
3715            "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n\r\n".to_owned(),
3716        ];
3717
3718        for request in cases {
3719            let response = send_raw(&proxy, request.as_bytes());
3720            assert_eq!(response_status(&response), 407, "{request:?}");
3721            let (head, _) = response_parts(&response);
3722            assert!(head.contains(&format!(
3723                "Proxy-Authenticate: Basic realm=\"{}\"",
3724                proxy.endpoint().realm()
3725            )));
3726        }
3727        assert_eq!(backend.request_count(), 0);
3728
3729        let request = format!(
3730            "GET not-an-absolute-target HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
3731            auth_header(&proxy)
3732        );
3733        assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 400);
3734        assert_eq!(backend.request_count(), 0);
3735        proxy.stop();
3736    }
3737
3738    #[test]
3739    fn rate_limited_connect_does_not_generate_or_publish_an_identity() {
3740        let limits = ProxyLimits::new(
3741            ProxyLimits::DEFAULT_MAX_HEADER_BYTES,
3742            ProxyLimits::DEFAULT_MAX_BODY_BYTES,
3743            ProxyLimits::DEFAULT_MAX_ACTIVE_CLIENTS,
3744            1,
3745            1,
3746            ProxyLimits::DEFAULT_MAX_TRACKED_HOSTS,
3747            ProxyLimits::DEFAULT_RATE_WINDOW,
3748        )
3749        .unwrap();
3750        let config = ProxyConfig::with_controls(
3751            ProxyInstanceId::new(ProxySessionId::generate().unwrap(), 1),
3752            crate::HostScope::new("welcome").unwrap(),
3753            limits,
3754            ProxyTimeouts::default(),
3755            crate::LoopbackBind::Ipv4,
3756        );
3757        let proxy =
3758            RunningProxy::start(config, Arc::new(UnusedBackend), Arc::new(NoopProxyObserver))
3759                .unwrap();
3760
3761        let admitted = format!(
3762            "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
3763            auth_header(&proxy)
3764        );
3765        assert_eq!(response_status(&send_raw(&proxy, admitted.as_bytes())), 500);
3766        assert!(proxy.local_certificate_pin("welcome").is_none());
3767
3768        let limited = format!(
3769            "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n{}\r\n",
3770            auth_header(&proxy)
3771        );
3772        assert_eq!(response_status(&send_raw(&proxy, limited.as_bytes())), 429);
3773        assert!(proxy.local_certificate_pin("welcome").is_none());
3774        proxy.stop();
3775    }
3776
3777    #[test]
3778    fn canonical_forwarding_and_both_header_boundaries_are_enforced() {
3779        let backend = Arc::new(RecordingBackend::new(ResponsePlan::Fixed {
3780            headers: vec![
3781                ProxyHeader::new("Content-Type", "text/plain"),
3782                ProxyHeader::new("X-Origin-Keep", "yes"),
3783                ProxyHeader::new("X-HNS-Security-Path", "dane"),
3784                ProxyHeader::new("X-HNS-Future", "private"),
3785                ProxyHeader::new("Connection", "X-Origin-Hop"),
3786                ProxyHeader::new("X-Origin-Hop", "remove"),
3787                ProxyHeader::new("Alt-Svc", "h3=\":443\""),
3788                ProxyHeader::new("Proxy-Authentication-Info", "secret"),
3789                ProxyHeader::new("Proxy-Future", "secret"),
3790                ProxyHeader::new("Content-Length", "999"),
3791                ProxyHeader::new("Transfer-Encoding", "chunked"),
3792            ],
3793            body: b"response-body".to_vec(),
3794        }));
3795        let proxy = start_recording_proxy(Arc::clone(&backend));
3796        let body = b"request-body";
3797        let request = format!(
3798            "POST http://Sub.WELCOME.:8080/private?q=s3cr3t HTTP/1.1\r\nHost: sub.welcome:8080\r\n{}Proxy-Future: secret\r\nX-HNS-Forged: secret\r\nConnection: keep-alive, X-Remove\r\nProxy-Connection: X-Proxy-Remove\r\nX-Remove: secret\r\nX-Proxy-Remove: secret\r\nKeep-Alive: timeout=5\r\nTE: trailers\r\nTrailer: X-Later\r\nAuthorization: Bearer origin-secret\r\nX-Keep: yes\r\nContent-Length: {}\r\n\r\n",
3799            auth_header(&proxy),
3800            body.len()
3801        );
3802        let mut bytes = request.into_bytes();
3803        bytes.extend_from_slice(body);
3804
3805        let response = send_raw(&proxy, &bytes);
3806        assert_eq!(response_status(&response), 200);
3807        let (head, response_body) = response_parts(&response);
3808        assert_eq!(response_body, b"response-body");
3809        let lower_head = head.to_ascii_lowercase();
3810        assert!(lower_head.contains("x-origin-keep: yes\r\n"));
3811        assert!(lower_head.contains("connection: close\r\n"));
3812        assert!(lower_head.contains("content-length: 13\r\n"));
3813        for forbidden in [
3814            "x-hns-",
3815            "x-origin-hop",
3816            "alt-svc",
3817            "proxy-authentication-info",
3818            "proxy-future",
3819            "transfer-encoding",
3820            "content-length: 999",
3821        ] {
3822            assert!(
3823                !lower_head.contains(forbidden),
3824                "leaked {forbidden}: {head}"
3825            );
3826        }
3827
3828        let requests = backend.take_requests();
3829        assert_eq!(requests.len(), 1);
3830        let forwarded = &requests[0];
3831        assert_eq!(forwarded.method, "POST");
3832        assert_eq!(forwarded.scheme, "http");
3833        assert_eq!(forwarded.host, "sub.welcome");
3834        assert_eq!(forwarded.port, 8080);
3835        assert_eq!(forwarded.path_and_query, "/private?q=s3cr3t");
3836        assert_eq!(forwarded.body.as_bytes(), body);
3837        let forwarded_names: Vec<_> = forwarded
3838            .headers
3839            .iter()
3840            .map(|header| header.name.to_ascii_lowercase())
3841            .collect();
3842        assert_eq!(
3843            forwarded_names
3844                .iter()
3845                .filter(|name| *name == "host")
3846                .count(),
3847            1
3848        );
3849        assert!(!forwarded_names.iter().any(|name| {
3850            name.starts_with("proxy-")
3851                || name.starts_with("x-hns-")
3852                || matches!(
3853                    name.as_str(),
3854                    "connection"
3855                        | "keep-alive"
3856                        | "te"
3857                        | "trailer"
3858                        | "transfer-encoding"
3859                        | "content-length"
3860                        | "x-remove"
3861                        | "x-proxy-remove"
3862                )
3863        }));
3864        assert!(forwarded.headers.iter().any(|header| {
3865            header.name.eq_ignore_ascii_case("host") && header.value == "sub.welcome:8080"
3866        }));
3867        assert!(forwarded.headers.iter().any(|header| {
3868            header.name.eq_ignore_ascii_case("authorization")
3869                && header.value == "Bearer origin-secret"
3870        }));
3871        assert!(
3872            forwarded.headers.iter().any(|header| {
3873                header.name.eq_ignore_ascii_case("x-keep") && header.value == "yes"
3874            })
3875        );
3876        proxy.stop();
3877    }
3878
3879    #[test]
3880    fn ordinary_response_delivers_only_sanitized_typed_metadata() {
3881        let backend = Arc::new(RecordingBackend::new(ResponsePlan::Fixed {
3882            headers: vec![
3883                ProxyHeader::new("Content-Type", "text/plain"),
3884                ProxyHeader::new("X-HNS-Security-Path", "dane"),
3885                ProxyHeader::new(
3886                    "X-HNS-Resolution-Trace",
3887                    r#"[{"origin_path":"/private?server=secret"}]"#,
3888                ),
3889                ProxyHeader::new("X-HNS-Future", "untrusted"),
3890                ProxyHeader::new("Connection", "X-HNS-Resolver-Policy"),
3891                ProxyHeader::new("X-HNS-Resolver-Policy", "nominated"),
3892            ],
3893            body: b"response-body-secret".to_vec(),
3894        }));
3895        let observations = Arc::new(Mutex::new(Vec::new()));
3896        let sink = Arc::clone(&observations);
3897        let observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
3898            sink.lock()
3899                .unwrap_or_else(|poisoned| poisoned.into_inner())
3900                .push(observation.clone());
3901        });
3902        let proxy = start_recording_proxy_with_metadata(Arc::clone(&backend), observer);
3903        let body = b"request-body-secret";
3904        let request = format!(
3905            "GET http://welcome/private?token=request-secret HTTP/1.1\r\nHost: welcome\r\n{}Sec-Fetch-Dest: document\r\nX-Request-Secret: request-header-secret\r\nContent-Length: {}\r\n\r\n",
3906            auth_header(&proxy),
3907            body.len(),
3908        );
3909        let mut request = request.into_bytes();
3910        request.extend_from_slice(body);
3911
3912        let response = send_raw(&proxy, &request);
3913        assert_eq!(response_status(&response), 200);
3914        let (head, response_body) = response_parts(&response);
3915        assert_eq!(response_body, b"response-body-secret");
3916        assert!(!head.to_ascii_lowercase().contains("x-hns-"));
3917
3918        let observations = observations
3919            .lock()
3920            .unwrap_or_else(|poisoned| poisoned.into_inner());
3921        assert_eq!(observations.len(), 1);
3922        let observation = &observations[0];
3923        assert_eq!(observation.generation(), 1);
3924        assert_eq!(observation.host().as_str(), "welcome");
3925        assert_eq!(observation.method(), ObservedMethod::Get);
3926        assert_eq!(observation.status_code(), 200);
3927        assert!(observation.is_likely_main_frame());
3928        assert_eq!(
3929            observation.metadata().get("X-HNS-Security-Path"),
3930            Some("dane")
3931        );
3932        assert_eq!(
3933            observation.metadata().get("X-HNS-Resolution-Trace"),
3934            Some(r#"[{"origin_path":"/private?server=secret"}]"#)
3935        );
3936        assert_eq!(observation.metadata().get("X-HNS-Future"), None);
3937        assert_eq!(observation.metadata().get("X-HNS-Resolver-Policy"), None);
3938        let diagnostic = format!("{observation:?}");
3939        for secret in [
3940            "private",
3941            "request-secret",
3942            "server=secret",
3943            "request-body-secret",
3944            "request-header-secret",
3945        ] {
3946            assert!(
3947                !diagnostic.contains(secret),
3948                "leaked {secret}: {diagnostic}"
3949            );
3950        }
3951        drop(observations);
3952        proxy.stop();
3953    }
3954
3955    #[test]
3956    fn revocation_at_backend_return_boundary_prevents_response_head_publication() {
3957        let (entered_tx, entered_rx) = mpsc::channel();
3958        let (release_tx, release_rx) = mpsc::channel();
3959        let authority = Arc::new(GatedPublicationAuthority {
3960            revoked: AtomicBool::new(false),
3961            entered: Mutex::new(Some(entered_tx)),
3962            release: Mutex::new(release_rx),
3963        });
3964        let backend = Arc::new(PublicationPermitBackend {
3965            permit: ProxyPublicationPermit::new(authority.clone()),
3966        });
3967        let proxy =
3968            RunningProxy::start(test_config(), backend, Arc::new(NoopProxyObserver)).unwrap();
3969        let mut client = TcpStream::connect(proxy.endpoint().address()).unwrap();
3970        client.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
3971        let request = format!(
3972            "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
3973            auth_header(&proxy)
3974        );
3975        client.write_all(request.as_bytes()).unwrap();
3976        client.flush().unwrap();
3977
3978        entered_rx.recv_timeout(TEST_TIMEOUT).unwrap();
3979        authority.revoked.store(true, AtomicOrdering::Release);
3980        release_tx.send(()).unwrap();
3981
3982        let mut response = Vec::new();
3983        client.read_to_end(&mut response).unwrap();
3984        assert!(
3985            response.is_empty(),
3986            "revoked backend result published bytes: {response:?}"
3987        );
3988        proxy.stop();
3989    }
3990
3991    #[test]
3992    fn revocation_at_backend_return_boundary_prevents_tunnel_head_publication() {
3993        let (entered_tx, entered_rx) = mpsc::channel();
3994        let (release_tx, release_rx) = mpsc::channel();
3995        let authority = Arc::new(GatedPublicationAuthority {
3996            revoked: AtomicBool::new(false),
3997            entered: Mutex::new(Some(entered_tx)),
3998            release: Mutex::new(release_rx),
3999        });
4000        let backend = Arc::new(PublicationPermitTunnelBackend {
4001            permit: ProxyPublicationPermit::new(authority.clone()),
4002        });
4003        let proxy =
4004            RunningProxy::start(test_config(), backend, Arc::new(NoopProxyObserver)).unwrap();
4005        let mut client = TcpStream::connect(proxy.endpoint().address()).unwrap();
4006        client.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
4007        let request = format!(
4008            "GET ws://welcome/socket HTTP/1.1\r\nHost: welcome\r\n{}Connection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key: key\r\nSec-WebSocket-Version: 13\r\n\r\n",
4009            auth_header(&proxy)
4010        );
4011        client.write_all(request.as_bytes()).unwrap();
4012        client.flush().unwrap();
4013
4014        entered_rx.recv_timeout(TEST_TIMEOUT).unwrap();
4015        authority.revoked.store(true, AtomicOrdering::Release);
4016        release_tx.send(()).unwrap();
4017
4018        let mut response = Vec::new();
4019        client.read_to_end(&mut response).unwrap();
4020        assert!(
4021            response.is_empty(),
4022            "revoked tunnel result published bytes: {response:?}"
4023        );
4024        proxy.stop();
4025    }
4026
4027    #[test]
4028    fn iframe_response_status_is_not_marked_as_main_frame() {
4029        let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"iframe")));
4030        let observations = Arc::new(Mutex::new(Vec::new()));
4031        let sink = Arc::clone(&observations);
4032        let observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4033            sink.lock()
4034                .unwrap_or_else(|poisoned| poisoned.into_inner())
4035                .push(observation.clone());
4036        });
4037        let proxy = start_recording_proxy_with_metadata(backend, observer);
4038        let request = format!(
4039            "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}Sec-Fetch-Dest: iframe\r\nSec-Fetch-Mode: navigate\r\nAccept: text/html\r\n\r\n",
4040            auth_header(&proxy)
4041        );
4042
4043        assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 200);
4044        let observations = observations
4045            .lock()
4046            .unwrap_or_else(|poisoned| poisoned.into_inner());
4047        assert_eq!(observations.len(), 1);
4048        assert_eq!(observations[0].status_code(), 200);
4049        assert!(!observations[0].is_likely_main_frame());
4050        drop(observations);
4051        proxy.stop();
4052    }
4053
4054    #[test]
4055    fn invalid_response_head_reports_only_the_proxy_generated_main_frame_error() {
4056        let backend = Arc::new(RecordingBackend::new(ResponsePlan::Fixed {
4057            headers: vec![
4058                ProxyHeader::new("X-HNS-TLS-Policy", "dane"),
4059                ProxyHeader::new("x-hns-tls-policy", "webpki"),
4060            ],
4061            body: Vec::new(),
4062        }));
4063        let observations = Arc::new(Mutex::new(Vec::new()));
4064        let sink = Arc::clone(&observations);
4065        let observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4066            sink.lock()
4067                .unwrap_or_else(|poisoned| poisoned.into_inner())
4068                .push(observation.clone());
4069        });
4070        let proxy = start_recording_proxy_with_metadata(backend, observer);
4071        let request = format!(
4072            "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
4073            auth_header(&proxy)
4074        );
4075
4076        assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 502);
4077        let observations = observations
4078            .lock()
4079            .unwrap_or_else(|poisoned| poisoned.into_inner());
4080        assert_eq!(observations.len(), 1);
4081        assert_eq!(observations[0].status_code(), 502);
4082        assert!(observations[0].is_likely_main_frame());
4083        assert!(observations[0].metadata().is_empty());
4084        drop(observations);
4085        proxy.stop();
4086    }
4087
4088    #[test]
4089    fn backend_failure_reports_the_proxy_generated_main_frame_status() {
4090        let observations = Arc::new(Mutex::new(Vec::new()));
4091        let sink = Arc::clone(&observations);
4092        let observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4093            sink.lock()
4094                .unwrap_or_else(|poisoned| poisoned.into_inner())
4095                .push(observation.clone());
4096        });
4097        let proxy = RunningProxy::start_with_metadata_observer(
4098            test_config(),
4099            Arc::new(UnusedBackend),
4100            Arc::new(NoopProxyObserver),
4101            observer,
4102        )
4103        .unwrap();
4104        let request = format!(
4105            "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
4106            auth_header(&proxy)
4107        );
4108
4109        assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 500);
4110        let observations = observations
4111            .lock()
4112            .unwrap_or_else(|poisoned| poisoned.into_inner());
4113        assert_eq!(observations.len(), 1);
4114        assert_eq!(observations[0].status_code(), 500);
4115        assert!(observations[0].is_likely_main_frame());
4116        assert!(observations[0].metadata().is_empty());
4117        drop(observations);
4118        proxy.stop();
4119    }
4120
4121    #[test]
4122    fn panicking_metadata_observer_does_not_interrupt_response_delivery() {
4123        let backend = Arc::new(RecordingBackend::new(ResponsePlan::Fixed {
4124            headers: vec![ProxyHeader::new("X-HNS-Security-Path", "dane")],
4125            body: b"ok".to_vec(),
4126        }));
4127        let observer = Arc::new(|_observation: &ProxyResponseMetadataObservation| {
4128            panic!("test metadata observer panic");
4129        });
4130        let proxy = start_recording_proxy_with_metadata(Arc::clone(&backend), observer);
4131        let request = format!(
4132            "GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
4133            auth_header(&proxy)
4134        );
4135
4136        for _ in 0..2 {
4137            let response = send_raw(&proxy, request.as_bytes());
4138            assert_eq!(response_status(&response), 200);
4139            assert_eq!(response_parts(&response).1, b"ok");
4140        }
4141        assert_eq!(backend.request_count(), 2);
4142        proxy.stop();
4143    }
4144
4145    #[test]
4146    fn fixed_and_chunked_request_bodies_are_forwarded_without_framing_fields() {
4147        let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"ok")));
4148        let proxy = start_recording_proxy(Arc::clone(&backend));
4149        let fixed = format!(
4150            "POST http://welcome/fixed HTTP/1.1\r\nHost: welcome\r\n{}Content-Length: 5\r\n\r\nfixed",
4151            auth_header(&proxy)
4152        );
4153        assert_eq!(response_status(&send_raw(&proxy, fixed.as_bytes())), 200);
4154
4155        let chunked = format!(
4156            "POST http://welcome/chunked HTTP/1.1\r\nHost: welcome\r\n{}Transfer-Encoding: chunked\r\n\r\n4\r\nWiki\r\n5\r\npedia\r\n0\r\nX-Origin-Trailer: ignored\r\n\r\n",
4157            auth_header(&proxy)
4158        );
4159        assert_eq!(response_status(&send_raw(&proxy, chunked.as_bytes())), 200);
4160
4161        let requests = backend.take_requests();
4162        assert_eq!(requests.len(), 2);
4163        assert_eq!(requests[0].path_and_query, "/fixed");
4164        assert_eq!(requests[0].body.as_bytes(), b"fixed");
4165        assert_eq!(requests[1].path_and_query, "/chunked");
4166        assert_eq!(requests[1].body.as_bytes(), b"Wikipedia");
4167        for request in requests {
4168            assert!(!request.headers.iter().any(|header| {
4169                header.name.eq_ignore_ascii_case("content-length")
4170                    || header.name.eq_ignore_ascii_case("transfer-encoding")
4171                    || header.name.eq_ignore_ascii_case("x-hns-trailer")
4172                    || header.name.eq_ignore_ascii_case("x-origin-trailer")
4173            }));
4174        }
4175        proxy.stop();
4176    }
4177
4178    #[test]
4179    fn oversized_expect_request_is_rejected_before_continue_or_body_read() {
4180        let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4181        let proxy = start_recording_proxy(Arc::clone(&backend));
4182        let request = format!(
4183            "POST http://welcome/upload HTTP/1.1\r\nHost: welcome\r\n{}Expect: 100-continue\r\nContent-Length: {}\r\n\r\n",
4184            auth_header(&proxy),
4185            ProxyLimits::DEFAULT_MAX_BODY_BYTES + 1,
4186        );
4187
4188        let response = send_raw(&proxy, request.as_bytes());
4189        assert_eq!(response_status(&response), 413);
4190        assert!(!response.starts_with(b"HTTP/1.1 100 Continue"));
4191        assert_eq!(backend.request_count(), 0);
4192        proxy.stop();
4193    }
4194
4195    #[test]
4196    fn connect_rejections_before_200_never_publish_a_pin_or_reach_the_backend() {
4197        let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4198        let proxy = start_recording_proxy(Arc::clone(&backend));
4199        let valid = proxy.endpoint().authorization_header_value();
4200        let cases = [
4201            (
4202                "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n\r\n".to_owned(),
4203                407,
4204            ),
4205            (
4206                "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\nProxy-Authorization: Basic d3Jvbmc6d3Jvbmc=\r\n\r\n"
4207                    .to_owned(),
4208                407,
4209            ),
4210            (
4211                format!(
4212                    "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\nProxy-Authorization: {valid}\r\nProxy-Authorization: {valid}\r\n\r\n"
4213                ),
4214                407,
4215            ),
4216            (
4217                format!(
4218                    "CONNECT other:443 HTTP/1.1\r\nHost: other:443\r\n{}\r\n",
4219                    auth_header(&proxy)
4220                ),
4221                403,
4222            ),
4223            (
4224                format!(
4225                    "CONNECT welcome:443 HTTP/1.1\r\nHost: sub.welcome:443\r\n{}\r\n",
4226                    auth_header(&proxy)
4227                ),
4228                400,
4229            ),
4230            (
4231                format!(
4232                    "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n{}Content-Length: 1\r\n\r\nx",
4233                    auth_header(&proxy)
4234                ),
4235                400,
4236            ),
4237            (
4238                format!(
4239                    "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n{}Transfer-Encoding: chunked\r\n\r\n0\r\n\r\n",
4240                    auth_header(&proxy)
4241                ),
4242                400,
4243            ),
4244            (
4245                format!(
4246                    "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n{}Expect: 100-continue\r\nContent-Length: 0\r\n\r\n",
4247                    auth_header(&proxy)
4248                ),
4249                417,
4250            ),
4251        ];
4252
4253        for (request, expected_status) in cases {
4254            let response = send_raw(&proxy, request.as_bytes());
4255            assert_eq!(response_status(&response), expected_status, "{request:?}");
4256            assert!(proxy.local_certificate_pin("welcome").is_none());
4257            assert!(proxy.local_certificate_pin("other").is_none());
4258            assert!(!proxy.matches_local_certificate("welcome", b"not-a-certificate"));
4259        }
4260        assert_eq!(backend.request_count(), 0);
4261        proxy.stop();
4262    }
4263
4264    #[test]
4265    fn exact_sni_get_and_post_use_https_routes_and_sanitize_both_header_boundaries() {
4266        let backend = Arc::new(RecordingBackend::new(ResponsePlan::Fixed {
4267            headers: vec![
4268                ProxyHeader::new("Content-Type", "text/plain"),
4269                ProxyHeader::new("X-Origin-Keep", "yes"),
4270                ProxyHeader::new("X-HNS-Security-Path", "dane"),
4271                ProxyHeader::new("Alt-Svc", "h3=\":443\""),
4272                ProxyHeader::new("Connection", "X-Origin-Hop"),
4273                ProxyHeader::new("X-Origin-Hop", "remove"),
4274            ],
4275            body: b"tls-ok".to_vec(),
4276        }));
4277        let proxy = start_recording_proxy(Arc::clone(&backend));
4278
4279        let get = b"GET /asset?q=1 HTTP/1.1\r\nHost: welcome\r\nProxy-Authorization: forged\r\nProxy-Future: secret\r\nX-HNS-Forged: secret\r\nConnection: close, X-Remove\r\nX-Remove: secret\r\nAuthorization: Bearer origin-secret\r\nX-Keep: yes\r\n\r\n";
4280        let (get_response, get_certificate) =
4281            send_tls_request(&proxy, "welcome:443", "welcome", get);
4282        assert_eq!(response_status(&get_response), 200);
4283        let (get_head, get_body) = response_parts(&get_response);
4284        assert_eq!(get_body, b"tls-ok");
4285        let lower_get_head = get_head.to_ascii_lowercase();
4286        assert!(lower_get_head.contains("x-origin-keep: yes\r\n"));
4287        for forbidden in ["x-hns-", "alt-svc", "x-origin-hop"] {
4288            assert!(!lower_get_head.contains(forbidden), "{get_head}");
4289        }
4290        assert!(proxy.matches_local_certificate("welcome", &get_certificate));
4291        assert!(!proxy.matches_local_certificate("sub.welcome", &get_certificate));
4292
4293        let post_head = b"POST /submit?q=2 HTTP/1.1\r\nHost: sub.welcome:8443\r\nContent-Type: text/plain\r\nContent-Length: 7\r\nProxy-Future: secret\r\nX-HNS-Forged: secret\r\nX-Keep: post\r\n\r\n";
4294        let mut post = post_head.to_vec();
4295        post.extend_from_slice(b"payload");
4296        let (post_response, post_certificate) =
4297            send_tls_request(&proxy, "sub.welcome:8443", "sub.welcome", &post);
4298        assert_eq!(response_status(&post_response), 200);
4299        assert!(proxy.matches_local_certificate("sub.welcome", &post_certificate));
4300        assert!(!proxy.matches_local_certificate("welcome", &post_certificate));
4301
4302        let requests = backend.take_requests();
4303        assert_eq!(requests.len(), 2);
4304        let get_request = &requests[0];
4305        assert_eq!(get_request.method, "GET");
4306        assert_eq!(get_request.scheme, "https");
4307        assert_eq!(get_request.host, "welcome");
4308        assert_eq!(get_request.port, 443);
4309        assert_eq!(get_request.path_and_query, "/asset?q=1");
4310        assert!(get_request.body.is_empty());
4311        assert!(get_request.headers.iter().any(|header| {
4312            header.name.eq_ignore_ascii_case("host") && header.value == "welcome"
4313        }));
4314        assert!(get_request.headers.iter().any(|header| {
4315            header.name.eq_ignore_ascii_case("authorization")
4316                && header.value == "Bearer origin-secret"
4317        }));
4318
4319        let post_request = &requests[1];
4320        assert_eq!(post_request.method, "POST");
4321        assert_eq!(post_request.scheme, "https");
4322        assert_eq!(post_request.host, "sub.welcome");
4323        assert_eq!(post_request.port, 8443);
4324        assert_eq!(post_request.path_and_query, "/submit?q=2");
4325        assert_eq!(post_request.body.as_bytes(), b"payload");
4326        assert!(post_request.headers.iter().any(|header| {
4327            header.name.eq_ignore_ascii_case("host") && header.value == "sub.welcome:8443"
4328        }));
4329
4330        for request in requests {
4331            assert!(!request.headers.iter().any(|header| {
4332                let name = header.name.to_ascii_lowercase();
4333                name.starts_with("proxy-")
4334                    || name.starts_with("x-hns-")
4335                    || matches!(
4336                        name.as_str(),
4337                        "connection" | "content-length" | "transfer-encoding" | "x-remove"
4338                    )
4339            }));
4340        }
4341        proxy.stop();
4342    }
4343
4344    #[test]
4345    fn wrong_or_missing_sni_and_h2_only_never_reach_the_backend() {
4346        let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4347        let proxy = start_recording_proxy(Arc::clone(&backend));
4348        let cases: [(&str, bool, &[&[u8]]); 3] = [
4349            ("other.welcome", true, &[b"http/1.1"]),
4350            ("welcome", false, &[b"http/1.1"]),
4351            ("welcome", true, &[b"h2"]),
4352        ];
4353
4354        for (server_name, enable_sni, alpn_protocols) in cases {
4355            let stream = begin_authenticated_connect(&proxy, "welcome:443");
4356            assert!(proxy.local_certificate_pin("welcome").is_some());
4357            assert!(
4358                complete_tls_handshake(stream, server_name, enable_sni, alpn_protocols).is_err(),
4359                "unexpectedly accepted SNI={server_name:?}, enabled={enable_sni}, ALPN={alpn_protocols:?}"
4360            );
4361        }
4362        assert_eq!(backend.request_count(), 0);
4363        proxy.stop();
4364    }
4365
4366    #[test]
4367    fn invalid_connected_targets_never_reach_the_backend() {
4368        let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4369        let proxy = start_recording_proxy(Arc::clone(&backend));
4370        let cases: [(&str, &str, &[u8], u16); 6] = [
4371            (
4372                "welcome:443",
4373                "welcome",
4374                b"GET / HTTP/1.1\r\nHost: other.welcome\r\n\r\n",
4375                400,
4376            ),
4377            (
4378                "welcome:8443",
4379                "welcome",
4380                b"GET / HTTP/1.1\r\nHost: welcome\r\n\r\n",
4381                400,
4382            ),
4383            (
4384                "welcome:443",
4385                "welcome",
4386                b"GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n\r\n",
4387                400,
4388            ),
4389            (
4390                "welcome:443",
4391                "welcome",
4392                b"CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n\r\n",
4393                400,
4394            ),
4395            (
4396                "welcome:443",
4397                "welcome",
4398                b"GET /socket HTTP/1.1\r\nHost: welcome\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n",
4399                501,
4400            ),
4401            (
4402                "welcome:443",
4403                "welcome",
4404                b"GET wss://welcome/socket HTTP/1.1\r\nHost: welcome\r\n\r\n",
4405                400,
4406            ),
4407        ];
4408
4409        for (authority, server_name, request, expected_status) in cases {
4410            let (response, _certificate) =
4411                send_tls_request(&proxy, authority, server_name, request);
4412            assert_eq!(
4413                response_status(&response),
4414                expected_status,
4415                "authority={authority:?}, request={request:?}"
4416            );
4417        }
4418        assert_eq!(backend.request_count(), 0);
4419        proxy.stop();
4420    }
4421
4422    #[test]
4423    fn certificate_pins_are_generation_bound_and_revoked_on_stop() {
4424        let session = ProxySessionId::generate().unwrap();
4425        let first_backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"first")));
4426        let second_backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"second")));
4427        let first = RunningProxy::start(
4428            ProxyConfig::new(
4429                ProxyInstanceId::new(session.clone(), 1),
4430                crate::HostScope::new("welcome").unwrap(),
4431            ),
4432            first_backend,
4433            Arc::new(NoopProxyObserver),
4434        )
4435        .unwrap();
4436        let second = RunningProxy::start(
4437            ProxyConfig::new(
4438                ProxyInstanceId::new(session, 2),
4439                crate::HostScope::new("welcome").unwrap(),
4440            ),
4441            second_backend,
4442            Arc::new(NoopProxyObserver),
4443        )
4444        .unwrap();
4445        let request = b"GET / HTTP/1.1\r\nHost: welcome\r\n\r\n";
4446
4447        let (first_response, first_certificate) =
4448            send_tls_request(&first, "welcome:443", "welcome", request);
4449        let (second_response, second_certificate) =
4450            send_tls_request(&second, "welcome:443", "welcome", request);
4451        assert_eq!(response_status(&first_response), 200);
4452        assert_eq!(response_status(&second_response), 200);
4453        let first_pin = first.local_certificate_pin("welcome").unwrap();
4454        let second_pin = second.local_certificate_pin("welcome").unwrap();
4455        assert_eq!(first_pin.instance().generation(), 1);
4456        assert_eq!(second_pin.instance().generation(), 2);
4457        assert_ne!(
4458            first_pin.certificate_sha256(),
4459            second_pin.certificate_sha256()
4460        );
4461        assert!(first.matches_local_certificate("welcome", &first_certificate));
4462        assert!(!first.matches_local_certificate("welcome", &second_certificate));
4463        assert!(second.matches_local_certificate("welcome", &second_certificate));
4464        assert!(!second.matches_local_certificate("welcome", &first_certificate));
4465
4466        first.stop();
4467        assert!(first.local_certificate_pin("welcome").is_none());
4468        assert!(!first.matches_local_certificate("welcome", &first_certificate));
4469        assert!(second.local_certificate_pin("welcome").is_some());
4470        assert!(second.matches_local_certificate("welcome", &second_certificate));
4471        second.stop();
4472        assert!(second.local_certificate_pin("welcome").is_none());
4473        assert!(!second.matches_local_certificate("welcome", &second_certificate));
4474    }
4475
4476    #[test]
4477    fn stop_interrupts_a_partial_client_hello_and_revokes_its_pin() {
4478        let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4479        let proxy = start_recording_proxy(Arc::clone(&backend));
4480        let mut stream = begin_authenticated_connect(&proxy, "welcome:443");
4481        assert!(proxy.local_certificate_pin("welcome").is_some());
4482        stream
4483            .write_all(b"\x16\x03\x03\x00\x40\x01\x00\x00")
4484            .unwrap();
4485        stream.flush().unwrap();
4486        wait_for_active_clients(&proxy, 1);
4487
4488        let started = Instant::now();
4489        proxy.stop();
4490        assert!(started.elapsed() < Duration::from_secs(1));
4491        assert!(proxy.is_stopped());
4492        assert_eq!(proxy.active_clients(), 0);
4493        assert_eq!(backend.request_count(), 0);
4494        assert!(proxy.local_certificate_pin("welcome").is_none());
4495        assert!(!proxy.matches_local_certificate("welcome", b"not-a-certificate"));
4496        assert_connection_closed(stream);
4497    }
4498
4499    #[test]
4500    fn stop_joins_tls_clients_stalled_in_inner_headers_and_bodies() {
4501        let header_backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4502        let header_proxy = start_recording_proxy(Arc::clone(&header_backend));
4503        let header_stream = begin_authenticated_connect(&header_proxy, "welcome:443");
4504        let (mut header_tls, _certificate) =
4505            complete_tls_handshake(header_stream, "welcome", true, &[b"http/1.1"]).unwrap();
4506        header_tls
4507            .write_all(b"GET / HTTP/1.1\r\nHost: wel")
4508            .unwrap();
4509        header_tls.flush().unwrap();
4510        wait_for_active_clients(&header_proxy, 1);
4511
4512        let started = Instant::now();
4513        header_proxy.stop();
4514        assert!(started.elapsed() < Duration::from_secs(1));
4515        assert_eq!(header_proxy.active_clients(), 0);
4516        assert_eq!(header_backend.request_count(), 0);
4517        assert!(!matches!(header_tls.read(&mut [0_u8; 1]), Ok(1..)));
4518
4519        let body_backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4520        let (accepted_tx, accepted_rx) = mpsc::channel();
4521        let body_proxy = RunningProxy::start(
4522            test_config(),
4523            body_backend.clone(),
4524            Arc::new(AcceptedObserver(accepted_tx)),
4525        )
4526        .unwrap();
4527        let body_stream = begin_authenticated_connect(&body_proxy, "welcome:443");
4528        let (mut body_tls, _certificate) =
4529            complete_tls_handshake(body_stream, "welcome", true, &[b"http/1.1"]).unwrap();
4530        body_tls
4531            .write_all(b"POST / HTTP/1.1\r\nHost: welcome\r\nContent-Length: 12\r\n\r\nshort")
4532            .unwrap();
4533        body_tls.flush().unwrap();
4534        accepted_rx.recv_timeout(TEST_TIMEOUT).unwrap();
4535        accepted_rx.recv_timeout(TEST_TIMEOUT).unwrap();
4536
4537        let started = Instant::now();
4538        body_proxy.stop();
4539        assert!(started.elapsed() < Duration::from_secs(1));
4540        assert_eq!(body_proxy.active_clients(), 0);
4541        assert_eq!(body_backend.request_count(), 0);
4542        assert!(!matches!(body_tls.read(&mut [0_u8; 1]), Ok(1..)));
4543    }
4544
4545    #[test]
4546    fn direct_websocket_upgrade_is_sanitized_and_copied_bidirectionally() {
4547        let backend = Arc::new(EchoTunnelBackend::websocket(b"origin"));
4548        let observations = Arc::new(Mutex::new(Vec::new()));
4549        let sink = Arc::clone(&observations);
4550        let (observed_tx, observed_rx) = mpsc::channel();
4551        let metadata_observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4552            sink.lock()
4553                .unwrap_or_else(|poisoned| poisoned.into_inner())
4554                .push(observation.clone());
4555            let _result = observed_tx.send(());
4556        });
4557        let proxy = RunningProxy::start_with_metadata_observer(
4558            test_config(),
4559            backend.clone(),
4560            Arc::new(NoopProxyObserver),
4561            metadata_observer,
4562        )
4563        .unwrap();
4564        let mut client = TcpStream::connect(proxy.endpoint().address()).unwrap();
4565        client.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
4566        client.set_write_timeout(Some(TEST_TIMEOUT)).unwrap();
4567        let request = format!(
4568            "GET ws://welcome/socket?q=1 HTTP/1.1\r\nHost: welcome\r\n{}Connection: keep-alive, Upgrade, X-Secret\r\nUpgrade: websocket\r\nSec-WebSocket-Key: key\r\nSec-WebSocket-Version: 13\r\nX-Secret: remove\r\nX-HNS-Client: remove\r\n\r\n",
4569            auth_header(&proxy)
4570        );
4571        client.write_all(request.as_bytes()).unwrap();
4572        client.flush().unwrap();
4573
4574        let response = read_response_head(&mut client).unwrap();
4575        let response_text = std::str::from_utf8(&response).unwrap();
4576        assert_eq!(response_status(&response), 101);
4577        assert!(response_text.contains("Connection: Upgrade\r\n"));
4578        assert!(response_text.contains("Upgrade: websocket\r\n"));
4579        assert!(response_text.contains("Sec-WebSocket-Accept: accepted\r\n"));
4580        assert!(!response_text.contains("keep-alive"));
4581        assert!(!response_text.contains("X-Origin-Hop"));
4582        assert!(!response_text.contains("X-HNS-"));
4583        observed_rx.recv_timeout(TEST_TIMEOUT).unwrap();
4584        {
4585            let observations = observations
4586                .lock()
4587                .unwrap_or_else(|poisoned| poisoned.into_inner());
4588            assert_eq!(observations.len(), 1);
4589            let observation = &observations[0];
4590            assert_eq!(observation.generation(), 1);
4591            assert_eq!(observation.host().as_str(), "welcome");
4592            assert_eq!(observation.method(), ObservedMethod::Get);
4593            assert_eq!(observation.status_code(), 101);
4594            assert!(!observation.is_likely_main_frame());
4595            assert_eq!(
4596                observation.metadata().get("X-HNS-Security-Path"),
4597                Some("secret")
4598            );
4599            let diagnostic = format!("{observation:?}");
4600            assert!(!diagnostic.contains("socket"));
4601            assert!(!diagnostic.contains("q=1"));
4602            assert!(!diagnostic.contains("secret"));
4603        }
4604        let mut from_origin = [0_u8; 6];
4605        client.read_exact(&mut from_origin).unwrap();
4606        assert_eq!(&from_origin, b"origin");
4607        client.write_all(b"client").unwrap();
4608        client.flush().unwrap();
4609        let mut echoed = [0_u8; 6];
4610        client.read_exact(&mut echoed).unwrap();
4611        assert_eq!(&echoed, b"client");
4612        drop(client);
4613
4614        let requests = backend.take_requests();
4615        assert_eq!(requests.len(), 1);
4616        let request = &requests[0];
4617        assert_eq!(request.method, "GET");
4618        assert_eq!(request.scheme, "ws");
4619        assert_eq!(request.host, "welcome");
4620        assert_eq!(request.port, 80);
4621        assert_eq!(request.path_and_query, "/socket?q=1");
4622        assert!(request.body.is_empty());
4623        assert!(request.headers.iter().any(|header| {
4624            header.name.eq_ignore_ascii_case("connection") && header.value == "Upgrade"
4625        }));
4626        assert!(request.headers.iter().any(|header| {
4627            header.name.eq_ignore_ascii_case("upgrade") && header.value == "websocket"
4628        }));
4629        assert!(request.headers.iter().any(|header| {
4630            header.name.eq_ignore_ascii_case("sec-websocket-key") && header.value == "key"
4631        }));
4632        assert!(!request.headers.iter().any(|header| {
4633            header.name.eq_ignore_ascii_case("proxy-authorization")
4634                || header.name.eq_ignore_ascii_case("x-secret")
4635                || header.name.to_ascii_lowercase().starts_with("x-hns-")
4636        }));
4637        proxy.stop();
4638    }
4639
4640    #[test]
4641    fn connect_webpki_passthrough_skips_local_tls_and_copies_bidirectionally() {
4642        let backend = Arc::new(ConnectPassthroughBackend {
4643            requests: Mutex::new(Vec::new()),
4644        });
4645        let observations = Arc::new(Mutex::new(Vec::new()));
4646        let sink = Arc::clone(&observations);
4647        let proxy = RunningProxy::start_with_metadata_observer(
4648            test_config(),
4649            backend.clone(),
4650            Arc::new(NoopProxyObserver),
4651            Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4652                sink.lock()
4653                    .unwrap_or_else(|poisoned| poisoned.into_inner())
4654                    .push((
4655                        observation.kind(),
4656                        observation.port(),
4657                        observation.correlation_epoch(),
4658                        observation.observation_id(),
4659                    ));
4660            }),
4661        )
4662        .unwrap();
4663
4664        let mut client = begin_authenticated_connect(&proxy, "welcome:443");
4665        assert!(proxy.local_certificate_pin("welcome").is_none());
4666        let mut from_origin = [0_u8; 6];
4667        client.read_exact(&mut from_origin).unwrap();
4668        assert_eq!(&from_origin, b"origin");
4669        client.write_all(b"client").unwrap();
4670        client.flush().unwrap();
4671        let mut echoed = [0_u8; 6];
4672        client.read_exact(&mut echoed).unwrap();
4673        assert_eq!(&echoed, b"client");
4674        drop(client);
4675
4676        let requests = backend
4677            .requests
4678            .lock()
4679            .unwrap_or_else(|poisoned| poisoned.into_inner());
4680        assert_eq!(requests.len(), 1);
4681        assert_eq!(requests[0].method, "CONNECT");
4682        assert_eq!(requests[0].scheme, "https");
4683        assert_eq!(requests[0].host, "welcome");
4684        assert_eq!(requests[0].port, 443);
4685        assert_eq!(
4686            *observations
4687                .lock()
4688                .unwrap_or_else(|poisoned| poisoned.into_inner()),
4689            vec![(
4690                ProxyMetadataObservationKind::WebPkiConnectDecision,
4691                Some(443),
4692                Some(9),
4693                Some(17),
4694            )]
4695        );
4696        drop(requests);
4697        proxy.stop();
4698    }
4699
4700    #[test]
4701    fn connect_download_is_not_serialized_behind_idle_client_upload_polls() {
4702        const DOWNLOAD_BYTES: usize = 4 * 1024 * 1024;
4703
4704        let backend = Arc::new(BulkDownloadBackend {
4705            bytes: vec![0x5a; DOWNLOAD_BYTES],
4706        });
4707        let proxy =
4708            RunningProxy::start(test_config(), backend, Arc::new(NoopProxyObserver)).unwrap();
4709        let mut client = begin_authenticated_connect(&proxy, "welcome:443");
4710        client
4711            .set_read_timeout(Some(Duration::from_secs(5)))
4712            .unwrap();
4713        let started = Instant::now();
4714        let mut downloaded = Vec::new();
4715        client.read_to_end(&mut downloaded).unwrap();
4716
4717        assert_eq!(downloaded.len(), DOWNLOAD_BYTES);
4718        assert!(downloaded.iter().all(|byte| *byte == 0x5a));
4719        // The former sequential 16-KiB pump waited 25 ms on the idle upload
4720        // side for every chunk (>6 seconds for this payload).
4721        assert!(started.elapsed() < Duration::from_secs(3));
4722        proxy.stop();
4723    }
4724
4725    #[test]
4726    fn connect_upload_is_not_serialized_behind_idle_origin_download_polls() {
4727        const UPLOAD_BYTES: usize = 4 * 1024 * 1024;
4728
4729        let state = Arc::new(BulkUploadState {
4730            received: std::sync::atomic::AtomicUsize::new(0),
4731            shutdown: AtomicBool::new(false),
4732        });
4733        let proxy = RunningProxy::start(
4734            test_config(),
4735            Arc::new(BulkUploadBackend {
4736                state: Arc::clone(&state),
4737            }),
4738            Arc::new(NoopProxyObserver),
4739        )
4740        .unwrap();
4741        let mut client = begin_authenticated_connect(&proxy, "welcome:443");
4742        client
4743            .set_write_timeout(Some(Duration::from_secs(5)))
4744            .unwrap();
4745        let started = Instant::now();
4746        client.write_all(&vec![0x6b; UPLOAD_BYTES]).unwrap();
4747        client.flush().unwrap();
4748        let deadline = started + Duration::from_secs(5);
4749        while state.received.load(AtomicOrdering::Acquire) != UPLOAD_BYTES
4750            && Instant::now() < deadline
4751        {
4752            thread::yield_now();
4753        }
4754
4755        assert_eq!(state.received.load(AtomicOrdering::Acquire), UPLOAD_BYTES);
4756        assert!(started.elapsed() < Duration::from_secs(3));
4757        drop(client);
4758        proxy.stop();
4759    }
4760
4761    #[test]
4762    fn selected_webpki_connect_failure_never_prepares_a_local_identity() {
4763        let proxy = RunningProxy::start(
4764            test_config(),
4765            Arc::new(WebPkiUnavailableBackend),
4766            Arc::new(NoopProxyObserver),
4767        )
4768        .unwrap();
4769        let request = format!(
4770            "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n{}\r\n",
4771            auth_header(&proxy)
4772        );
4773        let response = send_raw(&proxy, request.as_bytes());
4774
4775        assert_eq!(response_status(&response), 502);
4776        assert!(response.starts_with(b"HTTP/1.1 502 ICANN WebPKI Origin Unavailable\r\n"));
4777        assert!(proxy.local_certificate_pin("welcome").is_none());
4778        proxy.stop();
4779    }
4780
4781    #[test]
4782    fn unexpected_connect_backend_failures_never_fall_back_to_local_tls() {
4783        for panic in [false, true] {
4784            let proxy = RunningProxy::start(
4785                test_config(),
4786                Arc::new(UnexpectedConnectFailureBackend { panic }),
4787                Arc::new(NoopProxyObserver),
4788            )
4789            .unwrap();
4790            let request = format!(
4791                "CONNECT welcome:443 HTTP/1.1\r\nHost: welcome:443\r\n{}\r\n",
4792                auth_header(&proxy)
4793            );
4794            let response = send_raw(&proxy, request.as_bytes());
4795
4796            assert_eq!(response_status(&response), 503);
4797            assert!(response.starts_with(b"HTTP/1.1 503 Browser Security Runtime Unavailable\r\n"));
4798            assert!(proxy.local_certificate_pin("welcome").is_none());
4799            proxy.stop();
4800        }
4801    }
4802
4803    #[test]
4804    fn stop_joins_an_idle_webpki_passthrough_without_detached_copy_workers() {
4805        let backend = Arc::new(ConnectPassthroughBackend {
4806            requests: Mutex::new(Vec::new()),
4807        });
4808        let proxy =
4809            RunningProxy::start(test_config(), backend.clone(), Arc::new(NoopProxyObserver))
4810                .unwrap();
4811        let mut client = begin_authenticated_connect(&proxy, "welcome:443");
4812        let mut initial = [0_u8; 6];
4813        client.read_exact(&mut initial).unwrap();
4814        assert_eq!(&initial, b"origin");
4815        wait_for_active_clients(&proxy, 1);
4816
4817        let started = Instant::now();
4818        proxy.stop();
4819        assert!(started.elapsed() < Duration::from_secs(1));
4820        assert!(proxy.is_stopped());
4821        assert_eq!(proxy.active_clients(), 0);
4822        assert_eq!(
4823            backend
4824                .requests
4825                .lock()
4826                .unwrap_or_else(|poisoned| poisoned.into_inner())
4827                .len(),
4828            1
4829        );
4830        assert_connection_closed(client);
4831    }
4832
4833    #[test]
4834    fn post_connect_wss_upgrade_uses_the_same_rust_tunnel_path() {
4835        let backend = Arc::new(EchoTunnelBackend::websocket(Vec::new()));
4836        let proxy =
4837            RunningProxy::start(test_config(), backend.clone(), Arc::new(NoopProxyObserver))
4838                .unwrap();
4839        let stream = begin_authenticated_connect(&proxy, "welcome:443");
4840        let (mut tls, _certificate) =
4841            complete_tls_handshake(stream, "welcome", true, &[b"http/1.1"]).unwrap();
4842        tls.write_all(
4843            b"GET wss://welcome/socket HTTP/1.1\r\nHost: welcome\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key: key\r\nSec-WebSocket-Version: 13\r\n\r\nping",
4844        )
4845        .unwrap();
4846        tls.flush().unwrap();
4847
4848        let response = read_response_head(&mut tls).unwrap();
4849        assert_eq!(response_status(&response), 101);
4850        assert!(
4851            !std::str::from_utf8(&response)
4852                .unwrap()
4853                .contains("X-HNS-Security-Path")
4854        );
4855        let mut echoed = [0_u8; 4];
4856        tls.read_exact(&mut echoed).unwrap();
4857        assert_eq!(&echoed, b"ping");
4858        drop(tls);
4859
4860        let requests = backend.take_requests();
4861        assert_eq!(requests.len(), 1);
4862        assert_eq!(requests[0].scheme, "wss");
4863        assert_eq!(requests[0].host, "welcome");
4864        assert_eq!(requests[0].port, 443);
4865        assert_eq!(requests[0].path_and_query, "/socket");
4866        proxy.stop();
4867    }
4868
4869    #[test]
4870    fn invalid_upgrade_response_fails_before_switching_protocols() {
4871        let backend = Arc::new(EchoTunnelBackend::with_head(ProxyResponseHead {
4872            status_code: 200,
4873            reason_phrase: "OK".to_owned(),
4874            headers: vec![
4875                ProxyHeader::new("Connection", "Upgrade"),
4876                ProxyHeader::new("Upgrade", "websocket"),
4877                ProxyHeader::new("X-HNS-Security-Path", "must-not-observe"),
4878            ],
4879            observation_id: None,
4880        }));
4881        let observations = Arc::new(Mutex::new(Vec::new()));
4882        let sink = Arc::clone(&observations);
4883        let metadata_observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4884            sink.lock()
4885                .unwrap_or_else(|poisoned| poisoned.into_inner())
4886                .push(observation.clone());
4887        });
4888        let proxy = RunningProxy::start_with_metadata_observer(
4889            test_config(),
4890            backend.clone(),
4891            Arc::new(NoopProxyObserver),
4892            metadata_observer,
4893        )
4894        .unwrap();
4895        let request = format!(
4896            "GET ws://welcome/socket HTTP/1.1\r\nHost: welcome\r\n{}Connection: Upgrade\r\nUpgrade: websocket\r\n\r\n",
4897            auth_header(&proxy)
4898        );
4899
4900        let response = send_raw(&proxy, request.as_bytes());
4901        assert_eq!(response_status(&response), 502);
4902        assert!(!String::from_utf8_lossy(&response).contains("101 Switching Protocols"));
4903        assert_eq!(backend.take_requests().len(), 1);
4904        assert!(
4905            observations
4906                .lock()
4907                .unwrap_or_else(|poisoned| poisoned.into_inner())
4908                .is_empty()
4909        );
4910        proxy.stop();
4911    }
4912
4913    #[test]
4914    fn upgrade_backend_can_fail_with_a_bounded_http_response() {
4915        let observations = Arc::new(Mutex::new(Vec::new()));
4916        let sink = Arc::clone(&observations);
4917        let metadata_observer = Arc::new(move |observation: &ProxyResponseMetadataObservation| {
4918            sink.lock()
4919                .unwrap_or_else(|poisoned| poisoned.into_inner())
4920                .push(observation.clone());
4921        });
4922        let proxy = RunningProxy::start_with_metadata_observer(
4923            test_config(),
4924            Arc::new(HttpRejectingTunnelBackend),
4925            Arc::new(NoopProxyObserver),
4926            metadata_observer,
4927        )
4928        .unwrap();
4929        let request = format!(
4930            "GET ws://welcome/ HTTP/1.1\r\nHost: welcome\r\n{}Connection: Upgrade\r\nUpgrade: websocket\r\nSec-Fetch-Dest: document\r\n\r\n",
4931            auth_header(&proxy)
4932        );
4933
4934        let response = send_raw(&proxy, request.as_bytes());
4935        assert_eq!(response_status(&response), 404);
4936        let (head, body) = response_parts(&response);
4937        assert!(head.contains("Content-Type: text/plain\r\n"));
4938        assert!(head.contains("Content-Length: 22\r\n"));
4939        assert!(!head.contains("Upgrade:"));
4940        assert!(!head.contains("X-HNS-"));
4941        assert_eq!(body, b"verified non-inclusion");
4942        let observations = observations
4943            .lock()
4944            .unwrap_or_else(|poisoned| poisoned.into_inner());
4945        assert_eq!(observations.len(), 1);
4946        assert_eq!(observations[0].status_code(), 404);
4947        assert_eq!(observations[0].method(), ObservedMethod::Get);
4948        assert!(!observations[0].is_likely_main_frame());
4949        assert_eq!(
4950            observations[0].metadata().get("X-HNS-Security-Path"),
4951            Some("verified-non-inclusion")
4952        );
4953        drop(observations);
4954        proxy.stop();
4955    }
4956
4957    #[test]
4958    fn stop_joins_an_idle_upgrade_without_detached_copy_workers() {
4959        let backend = Arc::new(EchoTunnelBackend::websocket(Vec::new()));
4960        let proxy =
4961            RunningProxy::start(test_config(), backend.clone(), Arc::new(NoopProxyObserver))
4962                .unwrap();
4963        let mut client = TcpStream::connect(proxy.endpoint().address()).unwrap();
4964        client.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
4965        let request = format!(
4966            "GET ws://welcome/socket HTTP/1.1\r\nHost: welcome\r\n{}Connection: Upgrade\r\nUpgrade: websocket\r\n\r\n",
4967            auth_header(&proxy)
4968        );
4969        client.write_all(request.as_bytes()).unwrap();
4970        client.flush().unwrap();
4971        assert_eq!(
4972            response_status(&read_response_head(&mut client).unwrap()),
4973            101
4974        );
4975        wait_for_active_clients(&proxy, 1);
4976
4977        let started = Instant::now();
4978        proxy.stop();
4979        assert!(started.elapsed() < Duration::from_secs(1));
4980        assert!(proxy.is_stopped());
4981        assert_eq!(proxy.active_clients(), 0);
4982        assert_eq!(backend.take_requests().len(), 1);
4983        assert_connection_closed(client);
4984    }
4985
4986    #[test]
4987    fn upgrades_and_out_of_scope_targets_never_reach_the_backend() {
4988        let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
4989        let proxy = start_recording_proxy(Arc::clone(&backend));
4990        let auth = auth_header(&proxy);
4991        let cases = [
4992            (
4993                format!("CONNECT other:443 HTTP/1.1\r\nHost: other:443\r\n{auth}\r\n"),
4994                403,
4995            ),
4996            (
4997                format!(
4998                    "GET http://welcome/socket HTTP/1.1\r\nHost: welcome\r\n{auth}Connection: Upgrade\r\nUpgrade: websocket\r\n\r\n"
4999                ),
5000                501,
5001            ),
5002            (
5003                format!("GET ws://welcome/socket HTTP/1.1\r\nHost: welcome\r\n{auth}\r\n"),
5004                400,
5005            ),
5006            (
5007                format!("GET http://other/ HTTP/1.1\r\nHost: other\r\n{auth}\r\n"),
5008                403,
5009            ),
5010            (
5011                format!("GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n{auth}\r\n"),
5012                403,
5013            ),
5014        ];
5015
5016        for (request, expected) in cases {
5017            assert_eq!(
5018                response_status(&send_raw(&proxy, request.as_bytes())),
5019                expected,
5020                "{request:?}"
5021            );
5022        }
5023        assert_eq!(backend.request_count(), 0);
5024        proxy.stop();
5025    }
5026
5027    #[test]
5028    fn premature_response_stream_eof_closes_without_a_second_response() {
5029        let backend = Arc::new(RecordingBackend::new(ResponsePlan::ShortStream {
5030            expected_len: 9,
5031            bytes: b"tiny".to_vec(),
5032        }));
5033        let proxy = start_recording_proxy(Arc::clone(&backend));
5034        let request = format!(
5035            "GET http://welcome/stream HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
5036            auth_header(&proxy)
5037        );
5038
5039        let response = send_raw(&proxy, request.as_bytes());
5040        assert_eq!(response_status(&response), 200);
5041        let (head, body) = response_parts(&response);
5042        assert!(head.contains("Content-Length: 9\r\n"));
5043        assert_eq!(body, b"tiny");
5044        assert_eq!(
5045            response
5046                .windows(b"HTTP/1.1".len())
5047                .filter(|window| *window == b"HTTP/1.1")
5048                .count(),
5049            1
5050        );
5051        assert_eq!(backend.request_count(), 1);
5052        proxy.stop();
5053    }
5054
5055    #[test]
5056    fn stop_closes_clients_stalled_in_partial_headers_and_bodies() {
5057        let header_backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
5058        let header_proxy = start_recording_proxy(Arc::clone(&header_backend));
5059        let mut partial_head = TcpStream::connect(header_proxy.endpoint().address()).unwrap();
5060        partial_head
5061            .write_all(b"GET http://welcome/ HTTP/1.1\r\nHost: welcome\r\n")
5062            .unwrap();
5063        wait_for_active_clients(&header_proxy, 1);
5064        header_proxy.stop();
5065        assert!(header_proxy.is_stopped());
5066        assert_eq!(header_proxy.active_clients(), 0);
5067        assert_eq!(header_backend.request_count(), 0);
5068        assert_connection_closed(partial_head);
5069
5070        let body_backend = Arc::new(RecordingBackend::new(ResponsePlan::plain(b"unused")));
5071        let (accepted_tx, accepted_rx) = mpsc::channel();
5072        let body_proxy = RunningProxy::start(
5073            test_config(),
5074            body_backend.clone(),
5075            Arc::new(AcceptedObserver(accepted_tx)),
5076        )
5077        .unwrap();
5078        let mut partial_body = TcpStream::connect(body_proxy.endpoint().address()).unwrap();
5079        let request = format!(
5080            "POST http://welcome/upload HTTP/1.1\r\nHost: welcome\r\n{}Content-Length: 10\r\n\r\nabc",
5081            auth_header(&body_proxy)
5082        );
5083        partial_body.write_all(request.as_bytes()).unwrap();
5084        accepted_rx.recv_timeout(TEST_TIMEOUT).unwrap();
5085        wait_for_active_clients(&body_proxy, 1);
5086        body_proxy.stop();
5087        assert!(body_proxy.is_stopped());
5088        assert_eq!(body_proxy.active_clients(), 0);
5089        assert_eq!(body_backend.request_count(), 0);
5090        assert_connection_closed(partial_body);
5091    }
5092
5093    #[test]
5094    fn stop_joins_a_cooperative_backend_response_stream_read() {
5095        let (read_started_tx, read_started_rx) = mpsc::channel();
5096        let backend = Arc::new(CooperativeStreamBackend {
5097            read_started: Mutex::new(Some(read_started_tx)),
5098        });
5099        let proxy =
5100            RunningProxy::start(test_config(), backend, Arc::new(NoopProxyObserver)).unwrap();
5101        let mut client = TcpStream::connect(proxy.endpoint().address()).unwrap();
5102        client.set_read_timeout(Some(TEST_TIMEOUT)).unwrap();
5103        client.set_write_timeout(Some(TEST_TIMEOUT)).unwrap();
5104        let request = format!(
5105            "GET http://welcome/stream HTTP/1.1\r\nHost: welcome\r\n{}\r\n",
5106            auth_header(&proxy)
5107        );
5108        client.write_all(request.as_bytes()).unwrap();
5109        client.flush().unwrap();
5110        read_started_rx.recv_timeout(TEST_TIMEOUT).unwrap();
5111        let response_head = read_response_head(&mut client).unwrap();
5112        assert_eq!(response_status(&response_head), 200);
5113        wait_for_active_clients(&proxy, 1);
5114
5115        let started = Instant::now();
5116        proxy.stop();
5117
5118        assert!(started.elapsed() < Duration::from_secs(1));
5119        assert!(proxy.is_stopped());
5120        assert_eq!(proxy.active_clients(), 0);
5121        assert_connection_closed(client);
5122    }
5123
5124    #[test]
5125    fn helper_rounds_retry_after_up_and_maps_backend_failures() {
5126        assert_eq!(retry_after_seconds(Duration::from_nanos(1)), 1);
5127        assert_eq!(retry_after_seconds(Duration::from_secs(2)), 2);
5128        assert_eq!(
5129            backend_error_status(BackendError::TlsValidationFailed),
5130            (502, "HNS TLS Validation Failed")
5131        );
5132    }
5133
5134    #[test]
5135    fn exact_stream_copy_rejects_premature_eof() {
5136        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
5137        let address = listener.local_addr().unwrap();
5138        let client = std::thread::spawn(move || TcpStream::connect(address).unwrap());
5139        let (mut server, _) = listener.accept().unwrap();
5140        let _client = client.join().unwrap();
5141
5142        assert_eq!(
5143            copy_exact_response(
5144                &mut server,
5145                &mut Cursor::new(b"x".to_vec()),
5146                2,
5147                &CancellationToken::new(),
5148            ),
5149            Err(WriteBackendError::InvalidAfterHead)
5150        );
5151    }
5152    #[test]
5153    fn dane_browser_routes_hns_and_icann_through_one_backend_boundary() {
5154        let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain("native")));
5155        let proxy = RunningProxy::start(
5156            dane_browser_config(),
5157            backend.clone(),
5158            Arc::new(NoopProxyObserver),
5159        )
5160        .unwrap();
5161
5162        for host in ["welcome", "sub.unregistered-hns-root", "example.com"] {
5163            let request = format!(
5164                "GET http://{host}/ HTTP/1.1\r\nHost: {host}\r\n{}\r\n",
5165                auth_header(&proxy)
5166            );
5167            assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 200);
5168        }
5169
5170        for host in ["localhost", "example.onion", "127.0.0.1"] {
5171            let request = format!(
5172                "GET http://{host}/ HTTP/1.1\r\nHost: {host}\r\n{}\r\n",
5173                auth_header(&proxy)
5174            );
5175            assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 403);
5176        }
5177        assert_eq!(backend.request_count(), 3);
5178    }
5179
5180    #[test]
5181    fn dane_browser_rejects_named_icann_unsafe_connect_port_before_tls_and_backend() {
5182        let backend = Arc::new(RecordingBackend::new(ResponsePlan::plain("unreachable")));
5183        let proxy = RunningProxy::start(
5184            dane_browser_config(),
5185            backend.clone(),
5186            Arc::new(NoopProxyObserver),
5187        )
5188        .unwrap();
5189        let request = format!(
5190            "CONNECT example.com:22 HTTP/1.1\r\nHost: example.com:22\r\n{}\r\n",
5191            auth_header(&proxy)
5192        );
5193
5194        assert_eq!(response_status(&send_raw(&proxy, request.as_bytes())), 403);
5195        assert_eq!(backend.request_count(), 0);
5196    }
5197    #[test]
5198    fn authentication_challenge_matching_is_revoked_on_stop_request() {
5199        let proxy = RunningProxy::start(
5200            test_config(),
5201            Arc::new(UnusedBackend),
5202            Arc::new(NoopProxyObserver),
5203        )
5204        .unwrap();
5205        assert!(proxy.matches_authentication_challenge(
5206            "127.0.0.1",
5207            proxy.endpoint().port(),
5208            proxy.endpoint().realm(),
5209        ));
5210        assert!(!proxy.matches_authentication_challenge(
5211            "localhost",
5212            proxy.endpoint().port(),
5213            proxy.endpoint().realm(),
5214        ));
5215        proxy.request_stop();
5216        assert!(proxy.is_stop_requested());
5217        assert!(!proxy.matches_authentication_challenge(
5218            "127.0.0.1",
5219            proxy.endpoint().port(),
5220            proxy.endpoint().realm(),
5221        ));
5222        proxy.stop();
5223    }
5224}