Repository: seanmonstar/reqwest (language: rust)

Issue:
Unrecognized schemas interpreted as HTTP
Steps to reproduce:

1. Parse a URL containing an invalid schema, e.g. "hxxp".
2. Request that URL.

Expected result:

Receive an error.

Actual result:

A request is made to the hostname and path from that URL, via HTTP.

This is a bit of a safety problem, since someone could typo the https schema as "htttps:", and the requests would silently be downgraded to HTTP.

```
use reqwest::{blocking, Url};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = Url::parse("hxxp://example.com").unwrap();
    let mut resp = blocking::get(url)?;
    resp.copy_to(&mut std::io::stdout())?;
    Ok(())
}
```


Discussion / hints:
Spent some time poking at this. It looks like `hyper` requires that URLs have the "http:" scheme, but reqwest turns that off with enforce_http(false):

https://github.com/seanmonstar/reqwest/blob/d879d6f6c2f261524632b0d135a05b0bae14c7fd/src/connect.rs#L189

https://github.com/seanmonstar/reqwest/blob/d879d6f6c2f261524632b0d135a05b0bae14c7fd/src/connect.rs#L214

I tried removing those lines in a local checkout and confirmed that I got errors on unrecognized schemes. Of course, this causes `hyper` to reject "https:" URLs because it doesn't recognize them. It seems like reqwest needs some extra logic to enforce valid schemes, since it turns off `hyper`'s validation.

Fix patch:
diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs
index f08859973..f96398a98 100644
--- a/src/async_impl/client.rs
+++ b/src/async_impl/client.rs
@@ -35,6 +35,7 @@ use super::Body;
 use crate::connect::{Connector, HttpConnector};
 #[cfg(feature = "cookies")]
 use crate::cookie;
+use crate::error;
 use crate::into_url::{expect_uri, try_uri};
 use crate::redirect::{self, remove_sensitive_headers};
 #[cfg(feature = "__tls")]
@@ -995,6 +996,9 @@ impl Client {
 
     pub(super) fn execute_request(&self, req: Request) -> Pending {
         let (method, url, mut headers, body, timeout) = req.pieces();
+        if url.scheme() != "http" && url.scheme() != "https" {
+            return Pending::new_err(error::url_bad_scheme(url));
+        }
 
         // insert default headers in the request headers
         // without overwriting already appended headers.
@@ -1496,3 +1500,18 @@ fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &cookie::CookieStore
         );
     }
 }
+
+#[cfg(test)]
+mod tests {
+    #[tokio::test]
+    async fn execute_request_rejects_invald_urls() {
+        let url_str = "hxxps://www.rust-lang.org/";
+        let url = url::Url::parse(url_str).unwrap();
+        let result = crate::get(url.clone()).await;
+
+        assert!(result.is_err());
+        let err = result.err().unwrap();
+        assert!(err.is_builder());
+        assert_eq!(url_str, err.url().unwrap().as_str());
+    }
+}


Test patch: