Repository: seanmonstar/reqwest (Rust)

Issue:
Client Builder option for HTTPS/TLS only connections
In some situations using reqwest for API connections, it would be a valuable hardening option in the client builder to only allow HTTPS outbound connections. 

I would imagine something roughly like:

```
let client = reqwest::Client::builder()
    .https_only(true)
    .build()?;

let res = client.get("https://www.rust-lang.org").send().await?;  // OK!

let res = client.get("http://www.rust-lang.org").send().await?;  // Error!
```

This would be very useful before an authentication for example, as post-auth you can rely on the auth bearer header not being sent due to header_sensitive, but pre-auth to get the token you want to enforce that the connection really is TLS protected. 


Discussion:
That's an interesting idea! I was curious if other libraries provide something like this, and found [curl's CURLOPT_PROTOCOLS](https://curl.haxx.se/libcurl/c/CURLOPT_PROTOCOLS.html).
Edited comment so the example is correct, sorry about that.



Patch:
diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs
index 3b3ef8587..523053332 100644
--- a/src/async_impl/client.rs
+++ b/src/async_impl/client.rs
@@ -106,6 +106,7 @@ struct Config {
     cookie_store: Option<cookie::CookieStore>,
     trust_dns: bool,
     error: Option<crate::Error>,
+    https_only: bool,
 }
 
 impl Default for ClientBuilder {
@@ -157,6 +158,7 @@ impl ClientBuilder {
                 trust_dns: cfg!(feature = "trust-dns"),
                 #[cfg(feature = "cookies")]
                 cookie_store: None,
+                https_only: false,
             },
         }
     }
@@ -349,6 +351,7 @@ impl ClientBuilder {
                 request_timeout: config.timeout,
                 proxies,
                 proxies_maybe_http_auth,
+                https_only: config.https_only,
             }),
         })
     }
@@ -917,6 +920,14 @@ impl ClientBuilder {
             self
         }
     }
+
+    /// Restrict the Client to be used with HTTPS only requests.
+    /// 
+    /// Defaults to false.
+    pub fn https_only(mut self, enabled: bool) -> ClientBuilder {
+        self.config.https_only = enabled;
+        self
+    }
 }
 
 type HyperClient = hyper::Client<Connector, super::body::ImplStream>;
@@ -1040,6 +1051,11 @@ impl Client {
             return Pending::new_err(error::url_bad_scheme(url));
         }
 
+        // check if we're in https_only mode and check the scheme of the current URL
+        if self.inner.https_only && url.scheme() != "https" {
+            return Pending::new_err(error::url_bad_scheme(url));
+        }
+
         // insert default headers in the request headers
         // without overwriting already appended headers.
         for (key, value) in &self.inner.headers {
@@ -1238,6 +1254,7 @@ struct ClientRef {
     request_timeout: Option<Duration>,
     proxies: Arc<Vec<Proxy>>,
     proxies_maybe_http_auth: bool,
+    https_only: bool,
 }
 
 impl ClientRef {
diff --git a/src/blocking/client.rs b/src/blocking/client.rs
index 60f6992ca..d1fbe4159 100644
--- a/src/blocking/client.rs
+++ b/src/blocking/client.rs
@@ -573,6 +573,13 @@ impl ClientBuilder {
         self.with_inner(|inner| inner.no_trust_dns())
     }
 
+    /// Restrict the Client to be used with HTTPS only requests.
+    /// 
+    /// Defaults to false.
+    pub fn https_only(self, enabled: bool) -> ClientBuilder {
+        self.with_inner(|inner| inner.https_only(enabled))
+    }
+
     // private
 
     fn with_inner<F>(mut self, func: F) -> ClientBuilder


Test patch:
diff --git a/tests/blocking.rs b/tests/blocking.rs
index ccb5bdf79..aa1c2a204 100644
--- a/tests/blocking.rs
+++ b/tests/blocking.rs
@@ -288,3 +288,25 @@ fn test_blocking_inside_a_runtime() {
         let _should_panic = reqwest::blocking::get(&url);
     });
 }
+
+#[cfg(feature = "default-tls")]
+#[test]
+fn test_allowed_methods_blocking() {
+    let resp = reqwest::blocking::Client::builder()
+        .https_only(true)
+        .build()
+        .expect("client builder")
+        .get("https://google.com")
+        .send();
+
+    assert_eq!(resp.is_err(), false);
+
+    let resp = reqwest::blocking::Client::builder()
+        .https_only(true)
+        .build()
+        .expect("client builder")
+        .get("http://google.com")
+        .send();
+
+    assert_eq!(resp.is_err(), true);
+}
diff --git a/tests/client.rs b/tests/client.rs
index 40febef31..19e90d684 100644
--- a/tests/client.rs
+++ b/tests/client.rs
@@ -199,3 +199,27 @@ fn use_preconfigured_rustls_default() {
         .build()
         .expect("preconfigured rustls tls");
 }
+
+#[cfg(feature = "default-tls")]
+#[tokio::test]
+async fn test_allowed_methods() {
+    let resp = reqwest::Client::builder()
+        .https_only(true)
+        .build()
+        .expect("client builder")
+        .get("https://google.com")
+        .send()
+        .await;
+
+    assert_eq!(resp.is_err(), false);
+
+    let resp = reqwest::Client::builder()
+        .https_only(true)
+        .build()
+        .expect("client builder")
+        .get("http://google.com")
+        .send()
+        .await;
+
+    assert_eq!(resp.is_err(), true);
+}