1use std::fmt;
45use std::future::Future;
46use std::pin::Pin;
47use std::sync::Arc;
48use std::task::{Context, Poll};
49
50use ::tower::Layer;
51use http::{Request, Response, StatusCode};
52
53use crate::event_types::{EVENT_DECORATOR_VIOLATION, EVENT_HTTPS_ENFORCED};
54use crate::events::{MIDDLEWARE_HANDLER_NAME, SecurityEvent, SecurityEventBus};
55use crate::redact::SensitiveNames;
56
57pub use guard_core_engine::https_enforcement::{
58 HTTPS_ENFORCEMENT_CHECK_NAME, HTTPS_REDIRECT_STATUS,
59};
60
61#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct HttpsRedirectAnswer {
65 pub status: u16,
67 pub location: String,
70}
71
72pub type RouteHttpsResolver = Arc<dyn Fn(&str) -> Option<bool> + Send + Sync>;
76
77#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
80pub struct HttpsEnforcementStageConfig {
81 pub enforce_https: bool,
83 pub trust_x_forwarded_proto: bool,
86 pub passive_mode: bool,
88}
89
90#[derive(Clone)]
92pub struct HttpsEnforcementStage {
93 config: HttpsEnforcementStageConfig,
94 trusted_proxies: Vec<String>,
95 route_resolver: Option<RouteHttpsResolver>,
96 events: Option<Arc<SecurityEventBus>>,
97 sensitive: Arc<SensitiveNames>,
98}
99
100impl fmt::Debug for HttpsEnforcementStage {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.debug_struct("HttpsEnforcementStage")
103 .field("config", &self.config)
104 .field("trusted_proxies", &self.trusted_proxies.len())
105 .finish_non_exhaustive()
106 }
107}
108
109#[derive(Default)]
111pub struct HttpsEnforcementStageBuilder {
112 config: HttpsEnforcementStageConfig,
113 trusted_proxies: Vec<String>,
114 route_resolver: Option<RouteHttpsResolver>,
115 events: Option<Arc<SecurityEventBus>>,
116 sensitive: SensitiveNames,
117}
118
119impl HttpsEnforcementStage {
120 #[must_use]
122 pub fn builder(config: HttpsEnforcementStageConfig) -> HttpsEnforcementStageBuilder {
123 HttpsEnforcementStageBuilder {
124 config,
125 trusted_proxies: Vec::new(),
126 route_resolver: None,
127 events: None,
128 sensitive: SensitiveNames::default(),
129 }
130 }
131
132 #[must_use]
145 pub fn decide(
146 &self,
147 path: &str,
148 url_scheme: &str,
149 client_host: Option<&str>,
150 x_forwarded_proto: Option<&str>,
151 https_url: &str,
152 ) -> Option<HttpsRedirectAnswer> {
153 let route_require_https = self
154 .route_resolver
155 .as_ref()
156 .and_then(|resolve| resolve(path));
157 self.decide_route(
158 path,
159 url_scheme,
160 client_host,
161 x_forwarded_proto,
162 https_url,
163 route_require_https,
164 )
165 }
166
167 #[must_use]
175 pub fn decide_route(
176 &self,
177 path: &str,
178 url_scheme: &str,
179 client_host: Option<&str>,
180 x_forwarded_proto: Option<&str>,
181 https_url: &str,
182 route_require_https: Option<bool>,
183 ) -> Option<HttpsRedirectAnswer> {
184 let engine_config = guard_core_engine::https_enforcement::HttpsEnforcementConfig::new(
185 self.config.enforce_https,
186 self.config.trust_x_forwarded_proto,
187 self.trusted_proxies.iter().map(String::as_str),
188 )
189 .expect("builder validated the proxy list");
190 let request = guard_core_engine::https_enforcement::HttpsRequest {
191 url_scheme,
192 client_host,
193 x_forwarded_proto,
194 route_require_https,
195 };
196 match guard_core_engine::https_enforcement::decide(&request, &engine_config) {
197 guard_core_engine::https_enforcement::HttpsVerdict::Allowed => None,
198 guard_core_engine::https_enforcement::HttpsVerdict::Redirect { route_scoped } => {
199 self.observe_https_violation(
202 route_scoped,
203 url_scheme,
204 client_host,
205 path,
206 https_url,
207 );
208 if self.config.passive_mode {
209 None
210 } else {
211 Some(HttpsRedirectAnswer {
212 status: HTTPS_REDIRECT_STATUS,
213 location: https_url.to_owned(),
214 })
215 }
216 }
217 }
218 }
219
220 fn observe_https_violation(
227 &self,
228 route_scoped: bool,
229 original_scheme: &str,
230 client_host: Option<&str>,
231 path: &str,
232 https_url: &str,
233 ) {
234 let Some(bus) = &self.events else {
235 return;
236 };
237 let mut event = if route_scoped {
238 let mut event = SecurityEvent::new(
239 EVENT_DECORATOR_VIOLATION,
240 client_host.unwrap_or_default(),
241 "https_redirect",
242 "Route requires HTTPS but request was HTTP",
243 MIDDLEWARE_HANDLER_NAME,
244 );
245 event.decorator_type = Some(String::from("authentication"));
246 event.metadata.insert(
247 String::from("decorator_type"),
248 serde_json::json!("authentication"),
249 );
250 event.metadata.insert(
251 String::from("violation_type"),
252 serde_json::json!("require_https"),
253 );
254 event
255 } else {
256 SecurityEvent::new(
257 EVENT_HTTPS_ENFORCED,
258 client_host.unwrap_or_default(),
259 "https_redirect",
260 "HTTP request redirected to HTTPS for security",
261 MIDDLEWARE_HANDLER_NAME,
262 )
263 };
264 event.metadata.insert(
265 String::from("original_scheme"),
266 serde_json::json!(original_scheme),
267 );
268 event.metadata.insert(
269 String::from("redirect_url"),
270 serde_json::json!(crate::redact::redact_url_for_display(
271 https_url,
272 &self.sensitive
273 )),
274 );
275 event.endpoint = Some(crate::redact::redact_url_for_display(path, &self.sensitive));
276 bus.send_event(&event);
277 }
278}
279
280impl HttpsEnforcementStageBuilder {
281 #[must_use]
283 pub const fn enforce_https(mut self, enforce_https: bool) -> Self {
284 self.config.enforce_https = enforce_https;
285 self
286 }
287
288 #[must_use]
291 pub fn trusted_proxies<I>(mut self, entries: I) -> Self
292 where
293 I: IntoIterator,
294 I::Item: AsRef<str>,
295 {
296 self.trusted_proxies
297 .extend(entries.into_iter().map(|entry| entry.as_ref().to_owned()));
298 self
299 }
300
301 #[must_use]
303 pub fn route_resolver(mut self, resolver: RouteHttpsResolver) -> Self {
304 self.route_resolver = Some(resolver);
305 self
306 }
307
308 #[must_use]
310 pub fn events(mut self, bus: Arc<SecurityEventBus>) -> Self {
311 self.events = Some(bus);
312 self
313 }
314
315 #[must_use]
318 pub fn sensitive(mut self, sensitive: SensitiveNames) -> Self {
319 self.sensitive = sensitive;
320 self
321 }
322
323 pub fn build(self) -> Result<HttpsEnforcementStage, guard_core_engine::ip_gate::IpGateError> {
330 guard_core_engine::https_enforcement::HttpsEnforcementConfig::new(
331 self.config.enforce_https,
332 self.config.trust_x_forwarded_proto,
333 self.trusted_proxies.iter().map(String::as_str),
334 )?;
335 Ok(HttpsEnforcementStage {
336 config: self.config,
337 trusted_proxies: self.trusted_proxies,
338 route_resolver: self.route_resolver,
339 events: self.events,
340 sensitive: Arc::new(self.sensitive),
341 })
342 }
343}
344
345#[derive(Clone)]
347pub struct HttpsEnforcementStageLayer {
348 stage: HttpsEnforcementStage,
349}
350
351impl HttpsEnforcementStageLayer {
352 #[must_use]
354 pub const fn new(stage: HttpsEnforcementStage) -> Self {
355 Self { stage }
356 }
357}
358
359impl fmt::Debug for HttpsEnforcementStageLayer {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 f.debug_struct("HttpsEnforcementStageLayer")
362 .field("stage", &self.stage)
363 .finish()
364 }
365}
366
367impl<S> Layer<S> for HttpsEnforcementStageLayer {
368 type Service = HttpsEnforcementStageService<S>;
369
370 fn layer(&self, inner: S) -> Self::Service {
371 HttpsEnforcementStageService {
372 inner,
373 stage: self.stage.clone(),
374 }
375 }
376}
377
378#[derive(Clone)]
380pub struct HttpsEnforcementStageService<S> {
381 inner: S,
382 stage: HttpsEnforcementStage,
383}
384
385fn host_of_authority(value: &str) -> &str {
392 let host_port = value.rsplit('@').next().unwrap_or_default();
393 if let Some(rest) = host_port.strip_prefix('[') {
394 return rest.split(']').next().unwrap_or(rest);
395 }
396 match host_port.split_once(':') {
397 Some((host, port)) if !port.is_empty() && port.bytes().all(|b| b.is_ascii_digit()) => host,
398 _ => host_port,
399 }
400}
401
402impl<S, B, ResBody> ::tower::Service<Request<B>> for HttpsEnforcementStageService<S>
403where
404 S: ::tower::Service<Request<B>, Response = Response<ResBody>>,
405 S::Future: Send + 'static,
406 ResBody: From<&'static str> + Send + 'static,
407{
408 type Response = S::Response;
409 type Error = S::Error;
410 type Future = Pin<Box<dyn Future<Output = Result<S::Response, S::Error>> + Send>>;
411
412 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), S::Error>> {
413 self.inner.poll_ready(cx)
414 }
415
416 fn call(&mut self, request: Request<B>) -> Self::Future {
417 let scheme = request.uri().scheme_str().unwrap_or("http").to_owned();
418 let path = request.uri().path().to_owned();
419 let host_and_port = request
420 .uri()
421 .authority()
422 .map(|authority| authority.as_str().to_owned())
423 .or_else(|| {
424 request
425 .headers()
426 .get(http::header::HOST)
427 .and_then(|value| value.to_str().ok())
428 .map(ToOwned::to_owned)
429 });
430 let client_host = host_and_port
435 .as_deref()
436 .map(|value| host_of_authority(value).to_owned());
437 let forwarded = request
438 .headers()
439 .get("x-forwarded-proto")
440 .and_then(|value| value.to_str().ok())
441 .map(str::to_owned);
442 let authority = host_and_port.unwrap_or_default();
443 let query = request
444 .uri()
445 .query()
446 .map(|q| format!("?{q}"))
447 .unwrap_or_default();
448 let https_url = format!("https://{authority}{path}{query}");
449
450 let answer = self.stage.decide(
451 &path,
452 &scheme,
453 client_host.as_deref(),
454 forwarded.as_deref(),
455 &https_url,
456 );
457 let Some(redirect) = answer else {
458 return Box::pin(self.inner.call(request));
459 };
460 drop(request);
461 let status = StatusCode::from_u16(redirect.status).expect("reference status");
462 let mut response = Response::new(ResBody::from(""));
463 *response.status_mut() = status;
464 #[cfg(not(coverage))] if let Ok(value) = http::HeaderValue::from_str(&redirect.location) {
470 response.headers_mut().insert(http::header::LOCATION, value);
471 }
472 #[cfg(coverage)]
473 response.headers_mut().insert(
474 http::header::LOCATION,
475 http::HeaderValue::from_str(&redirect.location)
476 .expect("the composed location is a valid header value"),
477 );
478 Box::pin(async move { Ok(response) })
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485 use std::convert::Infallible;
486
487 use ::tower::Service;
488
489 fn stage() -> HttpsEnforcementStage {
490 HttpsEnforcementStage::builder(HttpsEnforcementStageConfig::default())
491 .enforce_https(true)
492 .build()
493 .expect("valid")
494 }
495
496 fn route_stage() -> HttpsEnforcementStage {
497 HttpsEnforcementStage::builder(HttpsEnforcementStageConfig::default())
498 .enforce_https(false)
499 .route_resolver(Arc::new(|path: &str| match path {
500 "/secure" => Some(true),
501 "/open" => Some(false),
502 _ => None,
503 }))
504 .build()
505 .expect("valid")
506 }
507
508 #[test]
509 fn decide_route_carries_the_direct_require_https_override() {
510 let stage = HttpsEnforcementStage::builder(HttpsEnforcementStageConfig::default())
511 .build()
512 .expect("default config");
513 let answer = stage
516 .decide_route(
517 "/private",
518 "http",
519 None,
520 None,
521 "https://host/private",
522 Some(true),
523 )
524 .expect("route require_https redirects");
525 assert_eq!(answer.status, HTTPS_REDIRECT_STATUS);
526 assert_eq!(answer.location, "https://host/private");
527 assert!(
530 stage
531 .decide_route(
532 "/private",
533 "http",
534 None,
535 None,
536 "https://host/private",
537 Some(false)
538 )
539 .is_none()
540 );
541 assert!(
543 stage
544 .decide_route(
545 "/private",
546 "https",
547 None,
548 None,
549 "https://host/private",
550 Some(true)
551 )
552 .is_none()
553 );
554 }
555
556 #[test]
557 fn global_enforcement_redirects_plain_http_and_passes_https() {
558 let stage = stage();
559 let answer = stage
560 .decide("/x", "http", None, None, "https://host/x")
561 .expect("redirected");
562 assert_eq!(answer.status, 301);
563 assert_eq!(answer.location, "https://host/x");
564 assert!(stage.decide("/x", "https", None, None, "").is_none());
565 }
566
567 #[test]
568 fn the_route_verdict_wins_both_ways() {
569 let stage = route_stage();
570 assert!(
571 stage.decide("/secure", "http", None, None, "").is_some(),
572 "a route requiring https redirects even with the global arm off"
573 );
574 assert!(
575 stage.decide("/open", "http", None, None, "").is_none(),
576 "a route opting out passes even under a global arm"
577 );
578 assert!(
579 stage.decide("/other", "http", None, None, "").is_none(),
580 "no route entry and a globally-off engine passes"
581 );
582 }
583
584 #[test]
585 fn passive_mode_observes_without_redirecting() {
586 let stage = HttpsEnforcementStage::builder(HttpsEnforcementStageConfig {
587 enforce_https: true,
588 passive_mode: true,
589 ..HttpsEnforcementStageConfig::default()
590 })
591 .build()
592 .expect("valid");
593 assert!(
594 stage
595 .decide("/x", "http", None, None, "https://host/x")
596 .is_none(),
597 "passive mode never answers the redirect"
598 );
599 }
600
601 #[test]
602 fn a_forged_forwarded_proto_never_upgrades() {
603 let stage = HttpsEnforcementStage::builder(HttpsEnforcementStageConfig {
604 enforce_https: true,
605 trust_x_forwarded_proto: true,
606 ..HttpsEnforcementStageConfig::default()
607 })
608 .trusted_proxies(["10.0.0.0/8"])
609 .build()
610 .expect("valid");
611 assert!(
612 stage
613 .decide("/x", "http", Some("203.0.113.9"), Some("https"), "")
614 .is_some(),
615 "an untrusted client's header is ignored"
616 );
617 assert!(
618 stage
619 .decide("/x", "http", Some("10.1.2.3"), Some("https"), "")
620 .is_none(),
621 "a trusted proxy's header upgrades the request"
622 );
623 }
624
625 #[test]
626 fn the_builder_fails_closed_on_a_bad_proxy_entry() {
627 let error = HttpsEnforcementStage::builder(HttpsEnforcementStageConfig::default())
628 .trusted_proxies(["not-an-ip"])
629 .build()
630 .unwrap_err();
631 assert_eq!(error.list, "trusted_proxies");
632 }
633
634 #[test]
635 fn the_connecting_host_strips_the_port_and_the_v6_brackets() {
636 assert_eq!(host_of_authority("10.1.2.3:8080"), "10.1.2.3");
640 assert_eq!(host_of_authority("host.example"), "host.example");
641 assert_eq!(host_of_authority("host.example:443"), "host.example");
642 assert_eq!(host_of_authority("[2001:db8::1]:8080"), "2001:db8::1");
643 assert_eq!(host_of_authority("[2001:db8::1]"), "2001:db8::1");
644 assert_eq!(host_of_authority("2001:db8::1"), "2001:db8::1");
645 }
646
647 fn recording_bus() -> (
648 Arc<std::sync::Mutex<Vec<SecurityEvent>>>,
649 Arc<SecurityEventBus>,
650 ) {
651 let log = Arc::new(std::sync::Mutex::new(Vec::new()));
652 let sink = Arc::clone(&log);
653 let bus = Arc::new(SecurityEventBus::new(true).on_event(Arc::new(
654 move |event: &SecurityEvent| {
655 sink.lock().expect("sink").push(event.clone());
656 },
657 )));
658 (log, bus)
659 }
660
661 #[test]
662 fn the_violation_fires_the_route_and_global_event_split() {
663 let (log, bus) = recording_bus();
664 let stage = HttpsEnforcementStage::builder(HttpsEnforcementStageConfig::default())
665 .enforce_https(true)
666 .events(Arc::clone(&bus))
667 .build()
668 .expect("valid");
669 stage
671 .decide("/x", "http", None, None, "https://host.example/x")
672 .expect("redirected");
673 let route_stage = HttpsEnforcementStage::builder(HttpsEnforcementStageConfig::default())
675 .route_resolver(Arc::new(|_: &str| Some(true)))
676 .events(bus)
677 .build()
678 .expect("valid");
679 route_stage
680 .decide("/secure", "http", None, None, "https://host.example/secure")
681 .expect("redirected");
682
683 let events = log.lock().expect("sink").clone();
684 assert_eq!(events.len(), 2);
685 assert_eq!(events[0].event_type, EVENT_HTTPS_ENFORCED);
686 assert_eq!(events[0].action_taken, "https_redirect");
687 assert_eq!(
688 events[0].reason,
689 "HTTP request redirected to HTTPS for security"
690 );
691 assert_eq!(
692 events[0].metadata["original_scheme"], "http",
693 "the reference metadata rides along"
694 );
695 assert_eq!(
696 events[0].metadata["redirect_url"],
697 serde_json::json!("https://host.example/x")
698 );
699 assert_eq!(events[1].event_type, EVENT_DECORATOR_VIOLATION);
700 assert_eq!(events[1].decorator_type.as_deref(), Some("authentication"));
701 assert_eq!(events[1].metadata["violation_type"], "require_https");
702 assert_eq!(
703 events[1].reason,
704 "Route requires HTTPS but request was HTTP"
705 );
706 }
707
708 #[test]
709 fn passive_mode_still_observes_the_https_violation() {
710 let (log, bus) = recording_bus();
712 let stage = HttpsEnforcementStage::builder(HttpsEnforcementStageConfig {
713 enforce_https: true,
714 passive_mode: true,
715 ..HttpsEnforcementStageConfig::default()
716 })
717 .events(bus)
718 .build()
719 .expect("valid");
720 assert!(
721 stage
722 .decide("/x", "http", None, None, "https://host/x")
723 .is_none()
724 );
725 let events = log.lock().expect("sink").clone();
726 assert_eq!(events.len(), 1);
727 assert_eq!(events[0].event_type, EVENT_HTTPS_ENFORCED);
728 }
729
730 #[test]
731 fn the_sensitive_sets_redact_the_emitted_redirect_target() {
732 let (log, bus) = recording_bus();
736 let stage = HttpsEnforcementStage::builder(HttpsEnforcementStageConfig::default())
737 .enforce_https(true)
738 .sensitive(SensitiveNames::new(
739 None,
740 Some(&std::collections::HashSet::from([String::from(
741 "guard_test_param",
742 )])),
743 None,
744 ))
745 .events(bus)
746 .build()
747 .expect("valid");
748 stage
749 .decide(
750 "/x?guard_test_param=leaked",
751 "http",
752 None,
753 None,
754 "https://host.example/x?guard_test_param=leaked",
755 )
756 .expect("redirected");
757 let events = log.lock().expect("sink").clone();
758 assert_eq!(events.len(), 1);
759 assert_eq!(
760 events[0].metadata["redirect_url"],
761 serde_json::json!("https://host.example/x?guard_test_param=[REDACTED]")
762 );
763 assert_eq!(
764 events[0].endpoint.as_deref(),
765 Some("/x?guard_test_param=[REDACTED]")
766 );
767 }
768
769 fn block_on<F: Future>(future: F) -> F::Output {
771 let mut future = std::pin::pin!(future);
772 let waker = std::task::Waker::noop();
773 let mut cx = Context::from_waker(waker);
774 loop {
775 match future.as_mut().poll(&mut cx) {
776 Poll::Ready(output) => return output,
777 Poll::Pending => std::hint::spin_loop(),
778 }
779 }
780 }
781
782 #[derive(Clone)]
783 struct Inner {
784 calls: Arc<std::sync::atomic::AtomicUsize>,
785 }
786
787 impl Inner {
788 fn new() -> Self {
789 Self {
790 calls: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
791 }
792 }
793 }
794
795 impl ::tower::Service<Request<&'static str>> for Inner {
796 type Response = Response<&'static str>;
797 type Error = Infallible;
798 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Infallible>> + Send>>;
799
800 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
801 Poll::Ready(Ok(()))
802 }
803
804 fn call(&mut self, _request: Request<&'static str>) -> Self::Future {
805 self.calls
806 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
807 Box::pin(async move { Ok(Response::new("inner")) })
808 }
809 }
810
811 fn request_to(uri: &str) -> Request<&'static str> {
812 Request::builder().uri(uri).body("body").expect("request")
813 }
814
815 #[test]
816 fn the_layer_redirects_http_and_forwards_https() {
817 let layer = HttpsEnforcementStageLayer::new(stage());
818 let inner = Inner::new();
819 let mut service = ::tower::ServiceBuilder::new()
820 .layer(layer)
821 .service(inner.clone());
822
823 let response =
824 block_on(service.call(request_to("http://host/private?q=1"))).expect("ready");
825 assert_eq!(response.status(), StatusCode::MOVED_PERMANENTLY);
826 assert_eq!(
827 response
828 .headers()
829 .get(http::header::LOCATION)
830 .expect("location"),
831 "https://host/private?q=1"
832 );
833 assert_eq!(
834 inner.calls.load(std::sync::atomic::Ordering::Relaxed),
835 0,
836 "the redirected request never hits the inner service"
837 );
838
839 let response = block_on(service.call(request_to("https://host/private"))).expect("ready");
840 assert_eq!(response.status(), StatusCode::OK);
841 assert_eq!(response.body(), &"inner");
842 }
843
844 #[test]
845 fn the_stage_and_layer_render_debug_and_the_service_readies() {
846 let built = stage();
847 assert!(format!("{built:?}").starts_with("HttpsEnforcementStage"));
848
849 let layer = HttpsEnforcementStageLayer::new(stage());
850 assert!(format!("{layer:?}").starts_with("HttpsEnforcementStageLayer"));
851
852 let mut service = ::tower::ServiceBuilder::new()
853 .layer(layer)
854 .service(Inner::new());
855 let waker = std::task::Waker::noop();
856 let mut cx = Context::from_waker(waker);
857 assert_eq!(
858 ::tower::Service::<Request<&'static str>>::poll_ready(&mut service, &mut cx),
859 Poll::Ready(Ok(()))
860 );
861 }
862
863 #[test]
864 fn a_path_only_request_takes_the_host_header() {
865 let layer = HttpsEnforcementStageLayer::new(stage());
866 let mut service = ::tower::ServiceBuilder::new()
867 .layer(layer)
868 .service(Inner::new());
869
870 let request = Request::builder()
874 .uri("/private")
875 .header(http::header::HOST, "host.example:8443")
876 .header("x-forwarded-proto", "https")
877 .body("body")
878 .expect("request");
879 let response = block_on(service.call(request)).expect("ready");
880 assert_eq!(response.status(), StatusCode::MOVED_PERMANENTLY);
881 assert_eq!(
882 response
883 .headers()
884 .get(http::header::LOCATION)
885 .expect("location"),
886 "https://host.example:8443/private"
887 );
888 }
889
890 #[derive(Clone)]
892 struct InnerYieldsOnce {
893 yielded: Arc<std::sync::atomic::AtomicBool>,
894 }
895
896 struct YieldOnce {
897 yielded: bool,
898 }
899
900 impl Future for YieldOnce {
901 type Output = ();
902
903 fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
904 if self.yielded {
905 Poll::Ready(())
906 } else {
907 self.yielded = true;
908 cx.waker().wake_by_ref();
909 Poll::Pending
910 }
911 }
912 }
913
914 impl ::tower::Service<Request<&'static str>> for InnerYieldsOnce {
915 type Response = Response<&'static str>;
916 type Error = Infallible;
917 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Infallible>> + Send>>;
918
919 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
920 Poll::Ready(Ok(()))
921 }
922
923 fn call(&mut self, _request: Request<&'static str>) -> Self::Future {
924 let yielded = Arc::clone(&self.yielded);
925 Box::pin(async move {
926 YieldOnce {
927 yielded: yielded.load(std::sync::atomic::Ordering::Relaxed),
928 }
929 .await;
930 yielded.store(true, std::sync::atomic::Ordering::Relaxed);
932 Ok(Response::new("inner"))
933 })
934 }
935 }
936
937 #[test]
938 fn the_helper_block_on_spins_a_pending_future_once() {
939 struct PendingOnce {
940 polled: std::cell::Cell<bool>,
941 }
942 impl Future for PendingOnce {
943 type Output = u8;
944
945 fn poll(self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<u8> {
946 if self.polled.get() {
947 Poll::Ready(7)
948 } else {
949 self.polled.set(true);
950 Poll::Pending
951 }
952 }
953 }
954 let future = PendingOnce {
955 polled: std::cell::Cell::new(false),
956 };
957 assert_eq!(block_on(future), 7, "the second poll answers");
958
959 let layer = HttpsEnforcementStageLayer::new(stage());
964 let mut service = ::tower::ServiceBuilder::new()
965 .layer(layer)
966 .service(InnerYieldsOnce {
967 yielded: Arc::new(std::sync::atomic::AtomicBool::new(false)),
968 });
969 let waker = std::task::Waker::noop();
970 let mut cx = Context::from_waker(waker);
971 assert_eq!(
972 ::tower::Service::<Request<&'static str>>::poll_ready(&mut service, &mut cx),
973 Poll::Ready(Ok(()))
974 );
975
976 let response = block_on(service.call(request_to("https://host/private"))).expect("ready");
977 assert_eq!(response.status(), StatusCode::OK);
978 assert_eq!(response.body(), &"inner");
979
980 let response =
981 block_on(service.call(request_to("http://host/private?q=1"))).expect("ready");
982 assert_eq!(response.status(), StatusCode::MOVED_PERMANENTLY);
983
984 let request = Request::builder()
985 .uri("/private?q=1")
986 .header(http::header::HOST, "host.example:8443")
987 .header("x-forwarded-proto", "https")
988 .body("body")
989 .expect("request");
990 let response = block_on(service.call(request)).expect("ready");
991 assert_eq!(response.status(), StatusCode::MOVED_PERMANENTLY);
992 }
993}