diff --git a/ngrok/src/config/common.rs b/ngrok/src/config/common.rs index 1df328a..9d5f8be 100644 --- a/ngrok/src/config/common.rs +++ b/ngrok/src/config/common.rs @@ -112,8 +112,7 @@ macro_rules! impl_builder { impl ForwarderBuilder for $name { async fn listen_and_forward(&self, to_url: Url) -> Result, RpcError> { let mut cfg = self.clone(); - let url_str: &str = to_url.as_ref(); - cfg.forwards_to(url_str); + cfg.for_forwarding_to(&to_url).await; let tunnel = cfg.listen().await?; let info = tunnel.make_info(); $crate::forwarder::forward(tunnel, info, to_url) @@ -224,6 +223,11 @@ impl CommonOpts { (!self.user_agent_filter.allow.is_empty() || !self.user_agent_filter.deny.is_empty()) .then_some(self.user_agent_filter.clone().into()) } + + pub(crate) fn for_forwarding_to(&mut self, to_url: &Url) -> &mut Self { + self.forwards_to = Some(to_url.as_str().into()); + self + } } // transform into the wire protocol format diff --git a/ngrok/src/config/headers.rs b/ngrok/src/config/headers.rs index d0a4c1a..ba6726c 100644 --- a/ngrok/src/config/headers.rs +++ b/ngrok/src/config/headers.rs @@ -13,10 +13,10 @@ pub(crate) struct Headers { impl Headers { pub(crate) fn add(&mut self, name: impl Into, value: impl Into) { - self.added.insert(name.into(), value.into()); + self.added.insert(name.into().to_lowercase(), value.into()); } pub(crate) fn remove(&mut self, name: impl Into) { - self.removed.push(name.into()); + self.removed.push(name.into().to_lowercase()); } pub(crate) fn has_entries(&self) -> bool { !self.added.is_empty() || !self.removed.is_empty() diff --git a/ngrok/src/config/http.rs b/ngrok/src/config/http.rs index 9f921c2..fa45f0e 100644 --- a/ngrok/src/config/http.rs +++ b/ngrok/src/config/http.rs @@ -9,6 +9,7 @@ use bytes::{ Bytes, }; use thiserror::Error; +use url::Url; use super::common::ProxyProto; // These are used for doc comment links. @@ -84,6 +85,7 @@ struct HttpOptions { pub(crate) circuit_breaker: f64, pub(crate) request_headers: Headers, pub(crate) response_headers: Headers, + pub(crate) rewrite_host: bool, pub(crate) basic_auth: Vec<(String, String)>, pub(crate) oauth: Option, pub(crate) oidc: Option, @@ -238,6 +240,17 @@ impl HttpTunnelBuilder { self } + /// Automatically rewrite the host header to the one in the provided URL + /// when calling [ForwarderBuilder::listen_and_forward]. Does nothing if + /// using [TunnelBuilder::listen]. Defaults to `false`. + /// + /// If you need to set the host header to a specific value, use + /// `cfg.request_header("host", "some.host.com")` instead. + pub fn host_header_rewrite(&mut self, rewrite: bool) -> &mut Self { + self.options.rewrite_host = rewrite; + self + } + /// Adds a header to all requests to this edge. pub fn request_header( &mut self, @@ -315,6 +328,14 @@ impl HttpTunnelBuilder { self.options.common_opts.user_agent_filter.deny(regex); self } + + pub(crate) async fn for_forwarding_to(&mut self, to_url: &Url) -> &mut Self { + self.options.common_opts.for_forwarding_to(to_url); + if let Some(host) = to_url.host_str().filter(|_| self.options.rewrite_host) { + self.request_header("host", host); + } + self + } } #[cfg(test)] @@ -412,12 +433,12 @@ mod test { assert_eq!(0.5f64, endpoint.circuit_breaker.unwrap().error_threshold); let request_headers = endpoint.request_headers.unwrap(); - assert_eq!(["X-Req-Yup:true"].to_vec(), request_headers.add); - assert_eq!(["X-Req-Nope"].to_vec(), request_headers.remove); + assert_eq!(["x-req-yup:true"].to_vec(), request_headers.add); + assert_eq!(["x-req-nope"].to_vec(), request_headers.remove); let response_headers = endpoint.response_headers.unwrap(); - assert_eq!(["X-Res-Yup:true"].to_vec(), response_headers.add); - assert_eq!(["X-Res-Nope"].to_vec(), response_headers.remove); + assert_eq!(["x-res-yup:true"].to_vec(), response_headers.add); + assert_eq!(["x-res-nope"].to_vec(), response_headers.remove); let webhook = endpoint.webhook_verification.unwrap(); assert_eq!("twilio", webhook.provider); diff --git a/ngrok/src/config/labeled.rs b/ngrok/src/config/labeled.rs index 2ce375e..47ccd5a 100644 --- a/ngrok/src/config/labeled.rs +++ b/ngrok/src/config/labeled.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use url::Url; + // These are used for doc comment links. #[allow(unused_imports)] use crate::config::{ @@ -81,6 +83,11 @@ impl LabeledTunnelBuilder { self.options.common_opts.forwards_to = forwards_to.into().into(); self } + + pub(crate) async fn for_forwarding_to(&mut self, to_url: &Url) -> &mut Self { + self.options.common_opts.for_forwarding_to(to_url); + self + } } #[cfg(test)] diff --git a/ngrok/src/config/tcp.rs b/ngrok/src/config/tcp.rs index e4d03aa..71b6222 100644 --- a/ngrok/src/config/tcp.rs +++ b/ngrok/src/config/tcp.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use url::Url; + use super::common::ProxyProto; // These are used for doc comment links. #[allow(unused_imports)] @@ -106,6 +108,11 @@ impl TcpTunnelBuilder { self.options.remote_addr = Some(remote_addr.into()); self } + + pub(crate) async fn for_forwarding_to(&mut self, to_url: &Url) -> &mut Self { + self.options.common_opts.for_forwarding_to(to_url); + self + } } #[cfg(test)] diff --git a/ngrok/src/config/tls.rs b/ngrok/src/config/tls.rs index 89c6fce..2a45995 100644 --- a/ngrok/src/config/tls.rs +++ b/ngrok/src/config/tls.rs @@ -4,6 +4,7 @@ use bytes::{ self, Bytes, }; +use url::Url; use super::common::ProxyProto; // These are used for doc comment links. @@ -146,6 +147,11 @@ impl TlsTunnelBuilder { self.options.cert_pem = Some(cert_pem); self } + + pub(crate) async fn for_forwarding_to(&mut self, to_url: &Url) -> &mut Self { + self.options.common_opts.for_forwarding_to(to_url); + self + } } #[cfg(test)]