Patch-Source: https://github.com/ducaale/xh/pull/281
--
From 36d7d7d2f98e367ff0c5b3d86dbea62b690d29b8 Mon Sep 17 00:00:00 2001
From: Jakub Jirutka <jakub@jirutka.cz>
Date: Mon, 26 Sep 2022 00:37:43 +0200
Subject: [PATCH] allow to build with native-tls only

Bundling (static linking) TLS library is a very bad idea from the
security perspective and Linux distributions try to avoid it. This
will allow to build xh with native-tls (i.e. dynamically linked
system-provided TLS library) only, without rustls.
---
 Cargo.toml   |  5 +++--
 build.rs     | 15 +++++++++++----
 src/cli.rs   |  6 +++++-
 src/main.rs  | 53 +++++++++++++++++++++++++++++++---------------------
 tests/cli.rs |  3 ++-
 5 files changed, 53 insertions(+), 29 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index b0ebbb3f..1d8662ce 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -49,7 +49,7 @@ url = "2.2.2"
 [dependencies.reqwest]
 version = "0.11.10"
 default-features = false
-features = ["rustls-tls", "rustls-tls-webpki-roots", "rustls-tls-native-roots", "json", "multipart", "blocking", "socks", "cookies"]
+features = ["json", "multipart", "blocking", "socks", "cookies"]
 
 [dependencies.syntect]
 version = "4.4"
@@ -69,9 +69,10 @@ tempfile = "3.2.0"
 syntect = { version = "4.4", default-features = false }
 
 [features]
-default = ["online-tests"]
+default = ["online-tests", "rustls"]
 online-tests = []
 native-tls = ["reqwest/native-tls", "reqwest/native-tls-alpn"]
+rustls = ["reqwest/rustls-tls", "reqwest/rustls-tls-webpki-roots", "reqwest/rustls-tls-native-roots"]
 
 [package.metadata.deb]
 features = []
diff --git a/build.rs b/build.rs
index b58bfc9e..daf2151e 100644
--- a/build.rs
+++ b/build.rs
@@ -14,13 +14,16 @@ fn build_syntax(dir: &str, out: &str) {
     dump_to_file(&ss, Path::new(&out_dir).join(out)).unwrap();
 }
 
-fn feature_status(feature: &str) -> String {
-    if env::var_os(format!(
+fn feature_enabled(feature: &str) -> bool {
+    env::var_os(format!(
         "CARGO_FEATURE_{}",
         feature.to_uppercase().replace('-', "_")
     ))
     .is_some()
-    {
+}
+
+fn feature_status(feature: &str) -> String {
+    if feature_enabled(feature) {
         format!("+{}", feature)
     } else {
         format!("-{}", feature)
@@ -28,10 +31,14 @@ fn feature_status(feature: &str) -> String {
 }
 
 fn features() -> String {
-    feature_status("native-tls")
+    format!("{} {}", &feature_status("native-tls"), &feature_status("rustls"))
 }
 
 fn main() {
+    if !feature_enabled("native-tls") && !feature_enabled("rustls") {
+        panic!("Either native-tls or rustls feature must be enabled!");
+    }
+
     for dir in &["assets", "assets/basic", "assets/large"] {
         println!("cargo:rerun-if-changed={}", dir);
         for entry in read_dir(dir).unwrap() {
diff --git a/src/cli.rs b/src/cli.rs
index d897fab7..29986328 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -250,7 +250,7 @@ pub struct Cli {
     pub ssl: Option<std::option::Option<tls::Version>>,
 
     /// Use the system TLS library instead of rustls (if enabled at compile time).
-    #[clap(long)]
+    #[clap(long, hide = cfg!(not(all(feature = "native-tls", feature = "rustls"))))]
     pub native_tls: bool,
 
     /// The default scheme to use if not specified in the URL.
@@ -454,6 +454,10 @@ impl Cli {
             )
         })?;
 
+        if cfg!(not(feature = "rustls")) {
+            cli.native_tls = true;
+        }
+
         Ok(cli)
     }
 
diff --git a/src/main.rs b/src/main.rs
index 3b93ab15..2885234c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -158,7 +158,6 @@ fn run(args: Cli) -> Result<i32> {
 
     let mut client = Client::builder()
         .http1_title_case_headers()
-        .use_rustls_tls()
         .http2_adaptive_window(true)
         .redirect(reqwest::redirect::Policy::none())
         .timeout(timeout)
@@ -166,6 +165,11 @@ fn run(args: Cli) -> Result<i32> {
         .no_deflate()
         .no_brotli();
 
+    #[cfg(feature = "rustls")]
+    if !args.native_tls {
+        client = client.use_rustls_tls();
+    }
+
     if let Some(Some(tls_version)) = args.ssl {
         client = client
             .min_tls_version(tls_version)
@@ -249,30 +253,37 @@ fn run(args: Cli) -> Result<i32> {
             if args.native_tls {
                 // Unlike the --verify case this is advertised to not work, so it's
                 // not an outright bug, but it's still imaginable that it'll start working
-                warn("Client certificates are not supported for native-tls");
+                if cfg!(feature = "rustls") {
+                    warn("Client certificates are not supported for native-tls");
+                } else {
+                    warn("Client certificates are not supported for native-tls and this binary was built without rustls support");
+                }
             }
 
-            let mut buffer = Vec::new();
-            let mut file = File::open(&cert)
-                .with_context(|| format!("Failed to open the cert file: {}", cert.display()))?;
-            file.read_to_end(&mut buffer)
-                .with_context(|| format!("Failed to read the cert file: {}", cert.display()))?;
-
-            if let Some(cert_key) = args.cert_key {
-                buffer.push(b'\n');
+            #[cfg(feature = "rustls")]
+            {
+                let mut buffer = Vec::new();
+                let mut file = File::open(&cert)
+                    .with_context(|| format!("Failed to open the cert file: {}", cert.display()))?;
+                file.read_to_end(&mut buffer)
+                    .with_context(|| format!("Failed to read the cert file: {}", cert.display()))?;
+
+                if let Some(cert_key) = args.cert_key {
+                    buffer.push(b'\n');
+
+                    let mut file = File::open(&cert_key).with_context(|| {
+                        format!("Failed to open the cert key file: {}", cert_key.display())
+                    })?;
+                    file.read_to_end(&mut buffer).with_context(|| {
+                        format!("Failed to read the cert key file: {}", cert_key.display())
+                    })?;
+                }
 
-                let mut file = File::open(&cert_key).with_context(|| {
-                    format!("Failed to open the cert key file: {}", cert_key.display())
-                })?;
-                file.read_to_end(&mut buffer).with_context(|| {
-                    format!("Failed to read the cert key file: {}", cert_key.display())
-                })?;
+                // We may fail here if we can't parse it but also if we don't have the key
+                let identity = reqwest::Identity::from_pem(&buffer)
+                    .context("Failed to load the cert/cert key files")?;
+                client = client.identity(identity);
             }
-
-            // We may fail here if we can't parse it but also if we don't have the key
-            let identity = reqwest::Identity::from_pem(&buffer)
-                .context("Failed to load the cert/cert key files")?;
-            client = client.identity(identity);
         };
     }
 
diff --git a/tests/cli.rs b/tests/cli.rs
index 24d80dae..30ffe761 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -1136,7 +1136,7 @@ fn improved_https_ip_error_with_support() {
         .stderr(contains("using the --native-tls flag"));
 }
 
-#[cfg(feature = "native-tls")]
+#[cfg(all(feature = "native-tls", feature = "rustls"))]
 #[test]
 fn auto_nativetls() {
     get_command()
@@ -1200,6 +1200,7 @@ fn unsupported_tls_version_nativetls() {
         .stderr(contains("running without the --native-tls"));
 }
 
+#[cfg(feature = "rustls")]
 #[test]
 fn unsupported_tls_version_rustls() {
     #[cfg(feature = "native-tls")]
