diff options
Diffstat (limited to 'ext/fetch/proxy.rs')
-rw-r--r-- | ext/fetch/proxy.rs | 860 |
1 files changed, 0 insertions, 860 deletions
diff --git a/ext/fetch/proxy.rs b/ext/fetch/proxy.rs deleted file mode 100644 index db187c3f6..000000000 --- a/ext/fetch/proxy.rs +++ /dev/null @@ -1,860 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -//! Parts of this module should be able to be replaced with other crates -//! eventually, once generic versions appear in hyper-util, et al. - -use std::env; -use std::future::Future; -use std::net::IpAddr; -use std::pin::Pin; -use std::sync::Arc; -use std::task::Context; -use std::task::Poll; - -use deno_core::futures::TryFutureExt; -use deno_tls::rustls::ClientConfig as TlsConfig; - -use http::header::HeaderValue; -use http::uri::Scheme; -use http::Uri; -use hyper_util::client::legacy::connect::Connected; -use hyper_util::client::legacy::connect::Connection; -use hyper_util::rt::TokioIo; -use ipnet::IpNet; -use tokio::net::TcpStream; -use tokio_rustls::client::TlsStream; -use tokio_rustls::TlsConnector; -use tokio_socks::tcp::Socks5Stream; -use tower_service::Service; - -#[derive(Debug, Clone)] -pub(crate) struct ProxyConnector<C> { - connector: C, - proxies: Arc<Proxies>, - tls: Arc<TlsConfig>, - user_agent: Option<HeaderValue>, -} - -#[derive(Debug)] -pub(crate) struct Proxies { - no: Option<NoProxy>, - intercepts: Vec<Intercept>, -} - -#[derive(Clone)] -pub(crate) struct Intercept { - filter: Filter, - target: Target, -} - -#[derive(Clone)] -enum Target { - Http { - dst: Uri, - auth: Option<HeaderValue>, - }, - Https { - dst: Uri, - auth: Option<HeaderValue>, - }, - Socks { - dst: Uri, - auth: Option<(String, String)>, - }, -} - -#[derive(Debug, Clone, Copy)] -enum Filter { - Http, - Https, - All, -} - -pub(crate) fn from_env() -> Proxies { - let mut intercepts = Vec::new(); - - if let Some(proxy) = parse_env_var("ALL_PROXY", Filter::All) { - intercepts.push(proxy); - } else if let Some(proxy) = parse_env_var("all_proxy", Filter::All) { - intercepts.push(proxy); - } - - if let Some(proxy) = parse_env_var("HTTPS_PROXY", Filter::Https) { - intercepts.push(proxy); - } else if let Some(proxy) = parse_env_var("https_proxy", Filter::Https) { - intercepts.push(proxy); - } - - // In a CGI context, headers become environment variables. So, "Proxy:" becomes HTTP_PROXY. - // To prevent an attacker from injecting a proxy, check if we are in CGI. - if env::var_os("REQUEST_METHOD").is_none() { - if let Some(proxy) = parse_env_var("HTTP_PROXY", Filter::Http) { - intercepts.push(proxy); - } else if let Some(proxy) = parse_env_var("http_proxy", Filter::Https) { - intercepts.push(proxy); - } - } - - let no = NoProxy::from_env(); - - Proxies { intercepts, no } -} - -pub(crate) fn basic_auth(user: &str, pass: &str) -> HeaderValue { - use base64::prelude::BASE64_STANDARD; - use base64::write::EncoderWriter; - use std::io::Write; - - let mut buf = b"Basic ".to_vec(); - { - let mut encoder = EncoderWriter::new(&mut buf, &BASE64_STANDARD); - let _ = write!(encoder, "{user}:{pass}"); - } - let mut header = - HeaderValue::from_bytes(&buf).expect("base64 is always valid HeaderValue"); - header.set_sensitive(true); - header -} - -fn parse_env_var(name: &str, filter: Filter) -> Option<Intercept> { - let val = env::var(name).ok()?; - let target = Target::parse(&val)?; - Some(Intercept { filter, target }) -} - -impl Intercept { - pub(crate) fn all(s: &str) -> Option<Self> { - let target = Target::parse(s)?; - Some(Intercept { - filter: Filter::All, - target, - }) - } - - pub(crate) fn set_auth(&mut self, user: &str, pass: &str) { - match self.target { - Target::Http { ref mut auth, .. } => { - *auth = Some(basic_auth(user, pass)); - } - Target::Https { ref mut auth, .. } => { - *auth = Some(basic_auth(user, pass)); - } - Target::Socks { ref mut auth, .. } => { - *auth = Some((user.into(), pass.into())); - } - } - } -} - -impl std::fmt::Debug for Intercept { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Intercept") - .field("filter", &self.filter) - .finish() - } -} - -impl Target { - fn parse(val: &str) -> Option<Self> { - let uri = val.parse::<Uri>().ok()?; - - let mut builder = Uri::builder(); - let mut is_socks = false; - let mut http_auth = None; - let mut socks_auth = None; - - builder = builder.scheme(match uri.scheme() { - Some(s) => { - if s == &Scheme::HTTP || s == &Scheme::HTTPS { - s.clone() - } else if s.as_str() == "socks5" || s.as_str() == "socks5h" { - is_socks = true; - s.clone() - } else { - // can't use this proxy scheme - return None; - } - } - // if no scheme provided, assume they meant 'http' - None => Scheme::HTTP, - }); - - let authority = uri.authority()?; - - if let Some((userinfo, host_port)) = authority.as_str().split_once('@') { - let (user, pass) = userinfo.split_once(':')?; - if is_socks { - socks_auth = Some((user.into(), pass.into())); - } else { - http_auth = Some(basic_auth(user, pass)); - } - builder = builder.authority(host_port); - } else { - builder = builder.authority(authority.clone()); - } - - // removing any path, but we MUST specify one or the builder errors - builder = builder.path_and_query("/"); - - let dst = builder.build().ok()?; - - let target = match dst.scheme().unwrap().as_str() { - "https" => Target::Https { - dst, - auth: http_auth, - }, - "http" => Target::Http { - dst, - auth: http_auth, - }, - "socks5" | "socks5h" => Target::Socks { - dst, - auth: socks_auth, - }, - // shouldn't happen - _ => return None, - }; - - Some(target) - } -} - -#[derive(Debug)] -struct NoProxy { - domains: DomainMatcher, - ips: IpMatcher, -} - -/// Represents a possible matching entry for an IP address -#[derive(Clone, Debug)] -enum Ip { - Address(IpAddr), - Network(IpNet), -} - -/// A wrapper around a list of IP cidr blocks or addresses with a [IpMatcher::contains] method for -/// checking if an IP address is contained within the matcher -#[derive(Clone, Debug, Default)] -struct IpMatcher(Vec<Ip>); - -/// A wrapper around a list of domains with a [DomainMatcher::contains] method for checking if a -/// domain is contained within the matcher -#[derive(Clone, Debug, Default)] -struct DomainMatcher(Vec<String>); - -impl NoProxy { - /// Returns a new no-proxy configuration based on environment variables (or `None` if no variables are set) - /// see [self::NoProxy::from_string()] for the string format - fn from_env() -> Option<NoProxy> { - let raw = env::var("NO_PROXY") - .or_else(|_| env::var("no_proxy")) - .unwrap_or_default(); - - Self::from_string(&raw) - } - - /// Returns a new no-proxy configuration based on a `no_proxy` string (or `None` if no variables - /// are set) - /// The rules are as follows: - /// * The environment variable `NO_PROXY` is checked, if it is not set, `no_proxy` is checked - /// * If neither environment variable is set, `None` is returned - /// * Entries are expected to be comma-separated (whitespace between entries is ignored) - /// * IP addresses (both IPv4 and IPv6) are allowed, as are optional subnet masks (by adding /size, - /// for example "`192.168.1.0/24`"). - /// * An entry "`*`" matches all hostnames (this is the only wildcard allowed) - /// * Any other entry is considered a domain name (and may contain a leading dot, for example `google.com` - /// and `.google.com` are equivalent) and would match both that domain AND all subdomains. - /// - /// For example, if `"NO_PROXY=google.com, 192.168.1.0/24"` was set, all of the following would match - /// (and therefore would bypass the proxy): - /// * `http://google.com/` - /// * `http://www.google.com/` - /// * `http://192.168.1.42/` - /// - /// The URL `http://notgoogle.com/` would not match. - fn from_string(no_proxy_list: &str) -> Option<Self> { - if no_proxy_list.is_empty() { - return None; - } - let mut ips = Vec::new(); - let mut domains = Vec::new(); - let parts = no_proxy_list.split(',').map(str::trim); - for part in parts { - match part.parse::<IpNet>() { - // If we can parse an IP net or address, then use it, otherwise, assume it is a domain - Ok(ip) => ips.push(Ip::Network(ip)), - Err(_) => match part.parse::<IpAddr>() { - Ok(addr) => ips.push(Ip::Address(addr)), - Err(_) => domains.push(part.to_owned()), - }, - } - } - Some(NoProxy { - ips: IpMatcher(ips), - domains: DomainMatcher(domains), - }) - } - - fn contains(&self, host: &str) -> bool { - // According to RFC3986, raw IPv6 hosts will be wrapped in []. So we need to strip those off - // the end in order to parse correctly - let host = if host.starts_with('[') { - let x: &[_] = &['[', ']']; - host.trim_matches(x) - } else { - host - }; - match host.parse::<IpAddr>() { - // If we can parse an IP addr, then use it, otherwise, assume it is a domain - Ok(ip) => self.ips.contains(ip), - Err(_) => self.domains.contains(host), - } - } -} - -impl IpMatcher { - fn contains(&self, addr: IpAddr) -> bool { - for ip in &self.0 { - match ip { - Ip::Address(address) => { - if &addr == address { - return true; - } - } - Ip::Network(net) => { - if net.contains(&addr) { - return true; - } - } - } - } - false - } -} - -impl DomainMatcher { - // The following links may be useful to understand the origin of these rules: - // * https://curl.se/libcurl/c/CURLOPT_NOPROXY.html - // * https://github.com/curl/curl/issues/1208 - fn contains(&self, domain: &str) -> bool { - let domain_len = domain.len(); - for d in &self.0 { - if d == domain || d.strip_prefix('.') == Some(domain) { - return true; - } else if domain.ends_with(d) { - if d.starts_with('.') { - // If the first character of d is a dot, that means the first character of domain - // must also be a dot, so we are looking at a subdomain of d and that matches - return true; - } else if domain.as_bytes().get(domain_len - d.len() - 1) == Some(&b'.') - { - // Given that d is a prefix of domain, if the prior character in domain is a dot - // then that means we must be matching a subdomain of d, and that matches - return true; - } - } else if d == "*" { - return true; - } - } - false - } -} - -impl<C> ProxyConnector<C> { - pub(crate) fn new( - proxies: Arc<Proxies>, - connector: C, - tls: Arc<TlsConfig>, - ) -> Self { - ProxyConnector { - connector, - proxies, - tls, - user_agent: None, - } - } - - pub(crate) fn user_agent(&mut self, val: HeaderValue) { - self.user_agent = Some(val); - } - - fn intercept(&self, dst: &Uri) -> Option<&Intercept> { - self.proxies.intercept(dst) - } -} - -impl Proxies { - pub(crate) fn prepend(&mut self, intercept: Intercept) { - self.intercepts.insert(0, intercept); - } - - pub(crate) fn http_forward_auth(&self, dst: &Uri) -> Option<&HeaderValue> { - let intercept = self.intercept(dst)?; - match intercept.target { - // Only if the proxy target is http - Target::Http { ref auth, .. } => auth.as_ref(), - _ => None, - } - } - - fn intercept(&self, dst: &Uri) -> Option<&Intercept> { - if let Some(no_proxy) = self.no.as_ref() { - if no_proxy.contains(dst.host()?) { - return None; - } - } - - for intercept in &self.intercepts { - return match ( - intercept.filter, - dst.scheme().map(Scheme::as_str).unwrap_or(""), - ) { - (Filter::All, _) => Some(intercept), - (Filter::Https, "https") => Some(intercept), - (Filter::Http, "http") => Some(intercept), - _ => continue, - }; - } - None - } -} - -type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>; -type BoxError = Box<dyn std::error::Error + Send + Sync>; - -// These variatns are not to be inspected. -pub enum Proxied<T> { - /// Not proxied - PassThrough(T), - /// An HTTP forwarding proxy needed absolute-form - HttpForward(T), - /// Tunneled through HTTP CONNECT - HttpTunneled(Box<TokioIo<TlsStream<TokioIo<T>>>>), - /// Tunneled through SOCKS - Socks(TokioIo<TcpStream>), - /// Tunneled through SOCKS and TLS - SocksTls(TokioIo<TlsStream<TokioIo<TokioIo<TcpStream>>>>), -} - -impl<C> Service<Uri> for ProxyConnector<C> -where - C: Service<Uri>, - C::Response: hyper::rt::Read + hyper::rt::Write + Unpin + Send + 'static, - C::Future: Send + 'static, - C::Error: Into<BoxError> + 'static, -{ - type Response = Proxied<C::Response>; - type Error = BoxError; - type Future = BoxFuture<Result<Self::Response, Self::Error>>; - - fn poll_ready( - &mut self, - cx: &mut Context<'_>, - ) -> Poll<Result<(), Self::Error>> { - self.connector.poll_ready(cx).map_err(Into::into) - } - - fn call(&mut self, orig_dst: Uri) -> Self::Future { - if let Some(intercept) = self.intercept(&orig_dst).cloned() { - let is_https = orig_dst.scheme() == Some(&Scheme::HTTPS); - let user_agent = self.user_agent.clone(); - return match intercept.target { - Target::Http { - dst: proxy_dst, - auth, - } - | Target::Https { - dst: proxy_dst, - auth, - } => { - let connecting = self.connector.call(proxy_dst); - let tls = TlsConnector::from(self.tls.clone()); - Box::pin(async move { - let mut io = connecting.await.map_err(Into::into)?; - - if is_https { - tunnel(&mut io, &orig_dst, user_agent, auth).await?; - let tokio_io = TokioIo::new(io); - let io = tls - .connect( - TryFrom::try_from(orig_dst.host().unwrap().to_owned())?, - tokio_io, - ) - .await?; - Ok(Proxied::HttpTunneled(Box::new(TokioIo::new(io)))) - } else { - Ok(Proxied::HttpForward(io)) - } - }) - } - Target::Socks { - dst: proxy_dst, - auth, - } => { - let tls = TlsConnector::from(self.tls.clone()); - Box::pin(async move { - let socks_addr = ( - proxy_dst.host().unwrap(), - proxy_dst.port().map(|p| p.as_u16()).unwrap_or(1080), - ); - let host = orig_dst.host().ok_or("no host in url")?; - let port = match orig_dst.port() { - Some(p) => p.as_u16(), - None if is_https => 443, - _ => 80, - }; - let io = if let Some((user, pass)) = auth { - Socks5Stream::connect_with_password( - socks_addr, - (host, port), - &user, - &pass, - ) - .await? - } else { - Socks5Stream::connect(socks_addr, (host, port)).await? - }; - let io = TokioIo::new(io.into_inner()); - - if is_https { - let tokio_io = TokioIo::new(io); - let io = tls - .connect(TryFrom::try_from(host.to_owned())?, tokio_io) - .await?; - Ok(Proxied::SocksTls(TokioIo::new(io))) - } else { - Ok(Proxied::Socks(io)) - } - }) - } - }; - } - Box::pin( - self - .connector - .call(orig_dst) - .map_ok(Proxied::PassThrough) - .map_err(Into::into), - ) - } -} - -async fn tunnel<T>( - io: &mut T, - dst: &Uri, - user_agent: Option<HeaderValue>, - auth: Option<HeaderValue>, -) -> Result<(), BoxError> -where - T: hyper::rt::Read + hyper::rt::Write + Unpin, -{ - use tokio::io::AsyncReadExt; - use tokio::io::AsyncWriteExt; - - let host = dst.host().expect("proxy dst has host"); - let port = match dst.port() { - Some(p) => p.as_u16(), - None => match dst.scheme().map(Scheme::as_str).unwrap_or("") { - "https" => 443, - "http" => 80, - _ => return Err("proxy dst unexpected scheme".into()), - }, - }; - - let mut buf = format!( - "\ - CONNECT {host}:{port} HTTP/1.1\r\n\ - Host: {host}:{port}\r\n\ - " - ) - .into_bytes(); - - // user-agent - if let Some(user_agent) = user_agent { - buf.extend_from_slice(b"User-Agent: "); - buf.extend_from_slice(user_agent.as_bytes()); - buf.extend_from_slice(b"\r\n"); - } - - // proxy-authorization - if let Some(value) = auth { - buf.extend_from_slice(b"Proxy-Authorization: "); - buf.extend_from_slice(value.as_bytes()); - buf.extend_from_slice(b"\r\n"); - } - - // headers end - buf.extend_from_slice(b"\r\n"); - - let mut tokio_conn = TokioIo::new(io); - - tokio_conn.write_all(&buf).await?; - - let mut buf = [0; 8192]; - let mut pos = 0; - - loop { - let n = tokio_conn.read(&mut buf[pos..]).await?; - - if n == 0 { - return Err("unexpected eof while tunneling".into()); - } - pos += n; - - let recvd = &buf[..pos]; - if recvd.starts_with(b"HTTP/1.1 200") || recvd.starts_with(b"HTTP/1.0 200") - { - if recvd.ends_with(b"\r\n\r\n") { - return Ok(()); - } - if pos == buf.len() { - return Err("proxy headers too long for tunnel".into()); - } - // else read more - } else if recvd.starts_with(b"HTTP/1.1 407") { - return Err("proxy authentication required".into()); - } else { - return Err("unsuccessful tunnel".into()); - } - } -} - -impl<T> hyper::rt::Read for Proxied<T> -where - T: hyper::rt::Read + hyper::rt::Write + Unpin, -{ - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: hyper::rt::ReadBufCursor<'_>, - ) -> Poll<Result<(), std::io::Error>> { - match *self { - Proxied::PassThrough(ref mut p) => Pin::new(p).poll_read(cx, buf), - Proxied::HttpForward(ref mut p) => Pin::new(p).poll_read(cx, buf), - Proxied::HttpTunneled(ref mut p) => Pin::new(p).poll_read(cx, buf), - Proxied::Socks(ref mut p) => Pin::new(p).poll_read(cx, buf), - Proxied::SocksTls(ref mut p) => Pin::new(p).poll_read(cx, buf), - } - } -} - -impl<T> hyper::rt::Write for Proxied<T> -where - T: hyper::rt::Read + hyper::rt::Write + Unpin, -{ - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll<Result<usize, std::io::Error>> { - match *self { - Proxied::PassThrough(ref mut p) => Pin::new(p).poll_write(cx, buf), - Proxied::HttpForward(ref mut p) => Pin::new(p).poll_write(cx, buf), - Proxied::HttpTunneled(ref mut p) => Pin::new(p).poll_write(cx, buf), - Proxied::Socks(ref mut p) => Pin::new(p).poll_write(cx, buf), - Proxied::SocksTls(ref mut p) => Pin::new(p).poll_write(cx, buf), - } - } - - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll<Result<(), std::io::Error>> { - match *self { - Proxied::PassThrough(ref mut p) => Pin::new(p).poll_flush(cx), - Proxied::HttpForward(ref mut p) => Pin::new(p).poll_flush(cx), - Proxied::HttpTunneled(ref mut p) => Pin::new(p).poll_flush(cx), - Proxied::Socks(ref mut p) => Pin::new(p).poll_flush(cx), - Proxied::SocksTls(ref mut p) => Pin::new(p).poll_flush(cx), - } - } - - fn poll_shutdown( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll<Result<(), std::io::Error>> { - match *self { - Proxied::PassThrough(ref mut p) => Pin::new(p).poll_shutdown(cx), - Proxied::HttpForward(ref mut p) => Pin::new(p).poll_shutdown(cx), - Proxied::HttpTunneled(ref mut p) => Pin::new(p).poll_shutdown(cx), - Proxied::Socks(ref mut p) => Pin::new(p).poll_shutdown(cx), - Proxied::SocksTls(ref mut p) => Pin::new(p).poll_shutdown(cx), - } - } - - fn is_write_vectored(&self) -> bool { - match *self { - Proxied::PassThrough(ref p) => p.is_write_vectored(), - Proxied::HttpForward(ref p) => p.is_write_vectored(), - Proxied::HttpTunneled(ref p) => p.is_write_vectored(), - Proxied::Socks(ref p) => p.is_write_vectored(), - Proxied::SocksTls(ref p) => p.is_write_vectored(), - } - } - - fn poll_write_vectored( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[std::io::IoSlice<'_>], - ) -> Poll<Result<usize, std::io::Error>> { - match *self { - Proxied::PassThrough(ref mut p) => { - Pin::new(p).poll_write_vectored(cx, bufs) - } - Proxied::HttpForward(ref mut p) => { - Pin::new(p).poll_write_vectored(cx, bufs) - } - Proxied::HttpTunneled(ref mut p) => { - Pin::new(p).poll_write_vectored(cx, bufs) - } - Proxied::Socks(ref mut p) => Pin::new(p).poll_write_vectored(cx, bufs), - Proxied::SocksTls(ref mut p) => Pin::new(p).poll_write_vectored(cx, bufs), - } - } -} - -impl<T> Connection for Proxied<T> -where - T: Connection, -{ - fn connected(&self) -> Connected { - match self { - Proxied::PassThrough(ref p) => p.connected(), - Proxied::HttpForward(ref p) => p.connected().proxy(true), - Proxied::HttpTunneled(ref p) => p.inner().get_ref().0.connected(), - Proxied::Socks(ref p) => p.connected(), - Proxied::SocksTls(ref p) => p.inner().get_ref().0.connected(), - } - } -} - -#[test] -fn test_proxy_parse_from_env() { - fn parse(s: &str) -> Target { - Target::parse(s).unwrap() - } - - // normal - match parse("http://127.0.0.1:6666") { - Target::Http { dst, auth } => { - assert_eq!(dst, "http://127.0.0.1:6666"); - assert!(auth.is_none()); - } - _ => panic!("bad target"), - } - - // without scheme - match parse("127.0.0.1:6666") { - Target::Http { dst, auth } => { - assert_eq!(dst, "http://127.0.0.1:6666"); - assert!(auth.is_none()); - } - _ => panic!("bad target"), - } - - // with userinfo - match parse("user:pass@127.0.0.1:6666") { - Target::Http { dst, auth } => { - assert_eq!(dst, "http://127.0.0.1:6666"); - assert!(auth.is_some()); - assert!(auth.unwrap().is_sensitive()); - } - _ => panic!("bad target"), - } - - // socks - match parse("socks5://user:pass@127.0.0.1:6666") { - Target::Socks { dst, auth } => { - assert_eq!(dst, "socks5://127.0.0.1:6666"); - assert!(auth.is_some()); - } - _ => panic!("bad target"), - } - - // socks5h - match parse("socks5h://localhost:6666") { - Target::Socks { dst, auth } => { - assert_eq!(dst, "socks5h://localhost:6666"); - assert!(auth.is_none()); - } - _ => panic!("bad target"), - } -} - -#[test] -fn test_domain_matcher() { - let domains = vec![".foo.bar".into(), "bar.foo".into()]; - let matcher = DomainMatcher(domains); - - // domains match with leading `.` - assert!(matcher.contains("foo.bar")); - // subdomains match with leading `.` - assert!(matcher.contains("www.foo.bar")); - - // domains match with no leading `.` - assert!(matcher.contains("bar.foo")); - // subdomains match with no leading `.` - assert!(matcher.contains("www.bar.foo")); - - // non-subdomain string prefixes don't match - assert!(!matcher.contains("notfoo.bar")); - assert!(!matcher.contains("notbar.foo")); -} - -#[test] -fn test_no_proxy_wildcard() { - let no_proxy = NoProxy::from_string("*").unwrap(); - assert!(no_proxy.contains("any.where")); -} - -#[test] -fn test_no_proxy_ip_ranges() { - let no_proxy = NoProxy::from_string( - ".foo.bar, bar.baz,10.42.1.1/24,::1,10.124.7.8,2001::/17", - ) - .unwrap(); - - let should_not_match = [ - // random url, not in no_proxy - "deno.com", - // make sure that random non-subdomain string prefixes don't match - "notfoo.bar", - // make sure that random non-subdomain string prefixes don't match - "notbar.baz", - // ipv4 address out of range - "10.43.1.1", - // ipv4 address out of range - "10.124.7.7", - // ipv6 address out of range - "[ffff:db8:a0b:12f0::1]", - // ipv6 address out of range - "[2005:db8:a0b:12f0::1]", - ]; - - for host in &should_not_match { - assert!(!no_proxy.contains(host), "should not contain {:?}", host); - } - - let should_match = [ - // make sure subdomains (with leading .) match - "hello.foo.bar", - // make sure exact matches (without leading .) match (also makes sure spaces between entries work) - "bar.baz", - // make sure subdomains (without leading . in no_proxy) match - "foo.bar.baz", - // make sure subdomains (without leading . in no_proxy) match - this differs from cURL - "foo.bar", - // ipv4 address match within range - "10.42.1.100", - // ipv6 address exact match - "[::1]", - // ipv6 address match within range - "[2001:db8:a0b:12f0::1]", - // ipv4 address exact match - "10.124.7.8", - ]; - - for host in &should_match { - assert!(no_proxy.contains(host), "should contain {:?}", host); - } -} |