From 40d7814cc4191f3df25c5262afe1395ca173148c Mon Sep 17 00:00:00 2001 From: Damien Voreiter Date: Thu, 21 Sep 2023 16:28:10 +0200 Subject: [PATCH 1/8] Update rustls 0.21.7 and adapt code --- Cargo.lock | 54 ++++++++++++++++++++++++++++++++++------------------ Cargo.toml | 7 ++++--- src/error.rs | 9 +++++++++ src/tls.rs | 38 ++++++++++++++++++------------------ 4 files changed, 67 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7508fdc..b748aff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "base64" -version = "0.13.1" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "bitflags" @@ -105,6 +105,7 @@ version = "0.9.3" dependencies = [ "native-tls", "rustls", + "rustls-pemfile", "unicase", "webpki", "webpki-roots", @@ -261,9 +262,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.13" +version = "0.38.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" dependencies = [ "bitflags 2.4.0", "errno", @@ -274,15 +275,33 @@ dependencies = [ [[package]] name = "rustls" -version = "0.19.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ - "base64", "log", "ring", + "rustls-webpki", "sct", - "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" +dependencies = [ + "ring", + "untrusted", ] [[package]] @@ -296,9 +315,9 @@ dependencies = [ [[package]] name = "sct" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", "untrusted", @@ -335,9 +354,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "syn" -version = "2.0.36" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e02e55d62894af2a08aca894c6577281f76769ba47c94d5756bec8ac6e7373" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -456,9 +475,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.21.4" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" dependencies = [ "ring", "untrusted", @@ -466,12 +485,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.21.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki", -] +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "winapi" diff --git a/Cargo.toml b/Cargo.toml index f2ad32a..e2a089d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["http", "client", "request"] edition = "2021" [dependencies] +rustls-pemfile = "^1" unicase = "^2.6" [features] @@ -22,13 +23,13 @@ version = "^0.2" optional = true [dependencies.rustls] -version = "^0.19" +version = "^0.21" optional = true [dependencies.webpki] -version = "^0.21" +version = "^0" optional = true [dependencies.webpki-roots] -version = "^0.21" +version = "^0" optional = true diff --git a/src/error.rs b/src/error.rs index 55bf752..8bb27b0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,7 @@ use std::{error, fmt, io, num, str}; pub enum ParseErr { Utf8(str::Utf8Error), Int(num::ParseIntError), + Rustls(rustls::Error), StatusErr, HeadersErr, UriErr, @@ -19,6 +20,7 @@ impl error::Error for ParseErr { match self { Utf8(e) => Some(e), Int(e) => Some(e), + Rustls(e) => Some(e), StatusErr | HeadersErr | UriErr | Invalid | Empty => None, } } @@ -31,6 +33,7 @@ impl fmt::Display for ParseErr { let err = match self { Utf8(_) => "invalid character", Int(_) => "cannot parse number", + Rustls(_) => "rustls error", Invalid => "invalid value", Empty => "nothing to parse", StatusErr => "status line contains invalid values", @@ -41,6 +44,12 @@ impl fmt::Display for ParseErr { } } +impl From for ParseErr { + fn from(e: rustls::Error) -> Self { + ParseErr::Rustls(e) + } +} + impl From for ParseErr { fn from(e: num::ParseIntError) -> Self { ParseErr::Int(e) diff --git a/src/tls.rs b/src/tls.rs index ebb5b91..00d81d6 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -23,7 +23,7 @@ pub struct Conn { stream: native_tls::TlsStream, #[cfg(feature = "rust-tls")] - stream: rustls::StreamOwned, + stream: rustls::StreamOwned, } impl io::Read for Conn { @@ -63,7 +63,7 @@ pub struct Config { #[cfg(feature = "native-tls")] extra_root_certs: Vec, #[cfg(feature = "rust-tls")] - client_config: std::sync::Arc, + root_certs: std::sync::Arc, } impl Default for Config { @@ -76,13 +76,16 @@ impl Default for Config { #[cfg(feature = "rust-tls")] fn default() -> Self { - let mut config = rustls::ClientConfig::new(); - config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - + let mut root_store = rustls::RootCertStore::empty(); + root_store.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| { + rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject, + ta.spki, + ta.name_constraints, + ) + })); Config { - client_config: std::sync::Arc::new(config), + root_certs: std::sync::Arc::new(root_store), } } } @@ -127,11 +130,8 @@ impl Config { pub fn add_root_cert_file_pem(&mut self, file_path: &Path) -> Result<&mut Self, HttpError> { let f = File::open(file_path)?; let mut f = BufReader::new(f); - let config = std::sync::Arc::make_mut(&mut self.client_config); - let _ = config - .root_store - .add_pem_file(&mut f) - .map_err(|_| HttpError::from(ParseErr::Invalid))?; + let root_certs = std::sync::Arc::make_mut(&mut self.root_certs); + root_certs.add_parsable_certificates(&rustls_pemfile::certs(&mut f)?); Ok(self) } @@ -141,13 +141,13 @@ impl Config { H: AsRef, S: io::Read + io::Write, { - use rustls::{ClientSession, StreamOwned}; + use rustls::{ClientConnection, StreamOwned}; - let session = ClientSession::new( - &self.client_config, - webpki::DNSNameRef::try_from_ascii_str(hostname.as_ref()) - .map_err(|_| HttpError::Tls)?, - ); + let client_config = rustls::ClientConfig::builder().with_safe_defaults().with_root_certificates(self.root_certs.clone()).with_no_client_auth(); + let session = ClientConnection::new( + std::sync::Arc::new(client_config), + hostname.as_ref().try_into().map_err(|_| HttpError::Tls)?, + ).map_err(|e| ParseErr::Rustls(e))?; let stream = StreamOwned::new(session, stream); Ok(Conn { stream }) From 626fe4b3e87608338e0218d3d5cf5e4fce295a8e Mon Sep 17 00:00:00 2001 From: jayjamesjay Date: Thu, 21 Sep 2023 22:10:44 +0200 Subject: [PATCH 2/8] add config flags --- Cargo.toml | 4 ++-- src/error.rs | 8 ++++++-- src/tls.rs | 8 ++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e2a089d..f4f71f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,9 +27,9 @@ version = "^0.21" optional = true [dependencies.webpki] -version = "^0" +version = "^0.22" optional = true [dependencies.webpki-roots] -version = "^0" +version = "^0.25" optional = true diff --git a/src/error.rs b/src/error.rs index 8bb27b0..477d4bd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,12 +5,13 @@ use std::{error, fmt, io, num, str}; pub enum ParseErr { Utf8(str::Utf8Error), Int(num::ParseIntError), - Rustls(rustls::Error), StatusErr, HeadersErr, UriErr, Invalid, Empty, + #[cfg(feature = "rust-tls")] + Rustls(rustls::Error), } impl error::Error for ParseErr { @@ -20,6 +21,7 @@ impl error::Error for ParseErr { match self { Utf8(e) => Some(e), Int(e) => Some(e), + #[cfg(feature = "rust-tls")] Rustls(e) => Some(e), StatusErr | HeadersErr | UriErr | Invalid | Empty => None, } @@ -33,17 +35,19 @@ impl fmt::Display for ParseErr { let err = match self { Utf8(_) => "invalid character", Int(_) => "cannot parse number", - Rustls(_) => "rustls error", Invalid => "invalid value", Empty => "nothing to parse", StatusErr => "status line contains invalid values", HeadersErr => "headers contain invalid values", UriErr => "uri contains invalid characters", + #[cfg(feature = "rust-tls")] + Rustls(_) => "rustls error", }; write!(f, "ParseErr: {}", err) } } +#[cfg(feature = "rust-tls")] impl From for ParseErr { fn from(e: rustls::Error) -> Self { ParseErr::Rustls(e) diff --git a/src/tls.rs b/src/tls.rs index 00d81d6..5d6a713 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -143,11 +143,15 @@ impl Config { { use rustls::{ClientConnection, StreamOwned}; - let client_config = rustls::ClientConfig::builder().with_safe_defaults().with_root_certificates(self.root_certs.clone()).with_no_client_auth(); + let client_config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(self.root_certs.clone()) + .with_no_client_auth(); let session = ClientConnection::new( std::sync::Arc::new(client_config), hostname.as_ref().try_into().map_err(|_| HttpError::Tls)?, - ).map_err(|e| ParseErr::Rustls(e))?; + ) + .map_err(|e| ParseErr::Rustls(e))?; let stream = StreamOwned::new(session, stream); Ok(Conn { stream }) From 9b7c55209236fa3ea06d629e25ca32cf805b931b Mon Sep 17 00:00:00 2001 From: jayjamesjay Date: Fri, 22 Sep 2023 08:19:31 +0200 Subject: [PATCH 3/8] set rustls-pemfile as optional dependency --- Cargo.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f4f71f8..6810584 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,11 @@ keywords = ["http", "client", "request"] edition = "2021" [dependencies] -rustls-pemfile = "^1" unicase = "^2.6" [features] default = ["native-tls"] -rust-tls = ["rustls", "webpki", "webpki-roots"] +rust-tls = ["rustls", "webpki", "webpki-roots", "rustls-pemfile"] [dependencies.native-tls] version = "^0.2" @@ -26,6 +25,10 @@ optional = true version = "^0.21" optional = true +[dependencies.rustls-pemfile] +version = "^1.0" +optional = true + [dependencies.webpki] version = "^0.22" optional = true From 7a1a4099890f2d31542d7fbfcd659b2feccdd2b3 Mon Sep 17 00:00:00 2001 From: jayjamesjay Date: Sat, 23 Sep 2023 12:09:37 +0200 Subject: [PATCH 4/8] improve examples --- examples/get.rs | 12 ++++++++---- examples/head.rs | 4 +++- examples/post.rs | 16 +++++++++++----- examples/request_builder_get.rs | 13 +++++++------ 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/examples/get.rs b/examples/get.rs index 1fbf882..a14c8a7 100644 --- a/examples/get.rs +++ b/examples/get.rs @@ -1,10 +1,14 @@ use http_req::request; fn main() { - let mut writer = Vec::new(); //container for body of a response - let res = request::get("https://www.rust-lang.org/learn", &mut writer).unwrap(); + //Container for body of a response. + let mut body = Vec::new(); + //Sends a HTTP GET request and processes the response. Saves body of the response to `body` variable. + let res = request::get("https://www.rust-lang.org/learn", &mut body).unwrap(); + + //Prints details about the response. println!("Status: {} {}", res.status_code(), res.reason()); - println!("Headers {}", res.headers()); - //println!("{}", String::from_utf8_lossy(&writer)); + println!("Headers: {}", res.headers()); + //println!("{}", String::from_utf8_lossy(&body)); } diff --git a/examples/head.rs b/examples/head.rs index eb2f816..922e278 100644 --- a/examples/head.rs +++ b/examples/head.rs @@ -1,8 +1,10 @@ use http_req::request; fn main() { + //Sends a HTTP HEAD request and processes the response. let res = request::head("https://www.rust-lang.org/learn").unwrap(); + //Prints details about the response. println!("Status: {} {}", res.status_code(), res.reason()); - println!("{:?}", res.headers()); + println!("Headers: {}", res.headers()); } diff --git a/examples/post.rs b/examples/post.rs index 982fd32..fadfb9d 100644 --- a/examples/post.rs +++ b/examples/post.rs @@ -1,11 +1,17 @@ use http_req::request; fn main() { - let mut writer = Vec::new(); //container for body of a response - const BODY: &[u8; 27] = b"field1=value1&field2=value2"; - let res = request::post("https://httpbin.org/post", BODY, &mut writer).unwrap(); + //Container for body of a response. + let mut res_body = Vec::new(); + //Body of a request. + const REQ_BODY: &[u8; 27] = b"field1=value1&field2=value2"; + + //Sends a HTTP POST request and processes the response. + let res = request::post("https://httpbin.org/post", REQ_BODY, &mut res_body).unwrap(); + + //Prints details about the response. println!("Status: {} {}", res.status_code(), res.reason()); - println!("Headers {}", res.headers()); - //println!("{}", String::from_utf8_lossy(&writer)); + println!("Headers: {}", res.headers()); + println!("{}", String::from_utf8_lossy(&res_body)); } diff --git a/examples/request_builder_get.rs b/examples/request_builder_get.rs index 52457d7..7c237d5 100644 --- a/examples/request_builder_get.rs +++ b/examples/request_builder_get.rs @@ -2,26 +2,27 @@ use http_req::{request::RequestBuilder, tls, uri::Uri}; use std::{convert::TryFrom, net::TcpStream}; fn main() { - //Parse uri and assign it to variable `addr` - let addr: Uri = Uri::try_from("https://doc.rust-lang.org/").unwrap(); + //Parses a URI and assigns it to a variable `addr`. + let addr: Uri = Uri::try_from("https://www.rust-lang.org/learn").unwrap(); - //Connect to remote host + //Connects to a remote host. Uses information from `addr`. let stream = TcpStream::connect((addr.host().unwrap(), addr.corr_port())).unwrap(); - //Open secure connection over TlsStream, because of `addr` (https) + //Opens a secure connection over TlsStream. This is required due to use of `https` protocol. let mut stream = tls::Config::default() .connect(addr.host().unwrap_or(""), stream) .unwrap(); - //Container for response's body + //Container for a response's body. let mut writer = Vec::new(); - //Add header `Connection: Close` + //Adds a header `Connection: Close`. let response = RequestBuilder::new(&addr) .header("Connection", "Close") .send(&mut stream, &mut writer) .unwrap(); println!("Status: {} {}", response.status_code(), response.reason()); + println!("Headers: {}", response.headers()); //println!("{}", String::from_utf8_lossy(&writer)); } From 84c7030456a688bf75da87ed4bd5591cdba48699 Mon Sep 17 00:00:00 2001 From: jayjamesjay Date: Sat, 23 Sep 2023 12:11:52 +0200 Subject: [PATCH 5/8] update readme --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- README.md | 16 +++++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b748aff..4038d1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,7 +101,7 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "http_req" -version = "0.9.3" +version = "0.10.0" dependencies = [ "native-tls", "rustls", diff --git a/Cargo.toml b/Cargo.toml index 6810584..adb0af1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "http_req" -version = "0.9.3" +version = "0.10.0" license = "MIT" description = "simple and lightweight HTTP client with built-in HTTPS support" repository = "https://github.com/jayjamesjay/http_req" @@ -11,7 +11,7 @@ keywords = ["http", "client", "request"] edition = "2021" [dependencies] -unicase = "^2.6" +unicase = "^2.7" [features] default = ["native-tls"] diff --git a/README.md b/README.md index 52f01b7..00a6163 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,35 @@ # http_req [![Rust](https://github.com/jayjamesjay/http_req/actions/workflows/rust.yml/badge.svg)](https://github.com/jayjamesjay/http_req/actions/workflows/rust.yml) -[![Crates.io](https://img.shields.io/badge/crates.io-v0.9.3-orange.svg?longCache=true)](https://crates.io/crates/http_req) -[![Docs.rs](https://docs.rs/http_req/badge.svg)](https://docs.rs/http_req/0.9.3/http_req/) +[![Crates.io](https://img.shields.io/badge/crates.io-v0.10.0-orange.svg?longCache=true)](https://crates.io/crates/http_req) +[![Docs.rs](https://docs.rs/http_req/badge.svg)](https://docs.rs/http_req/0.10.0/http_req/) Simple and lightweight HTTP client with built-in HTTPS support. ## Requirements http_req by default uses [rust-native-tls](https://github.com/sfackler/rust-native-tls), -which uses TLS framework provided by OS on Windows and macOS, and OpenSSL +which relies on TLS framework provided by OS on Windows and macOS, and OpenSSL on all other platforms. But it also supports [rus-tls](https://crates.io/crates/rustls). ## Example -Basic GET request +Basic HTTP GET request ```rust use http_req::request; fn main() { - let mut writer = Vec::new(); //container for body of a response - let res = request::get("https://doc.rust-lang.org/", &mut writer).unwrap(); + let mut body = Vec::new(); //Container for body of a response. + let res = request::get("https://doc.rust-lang.org/", &mut body).unwrap(); println!("Status: {} {}", res.status_code(), res.reason()); } ``` +Take a look at [more examples](https://github.com/jayjamesjay/http_req/tree/master/examples) + ## How to use with `rustls`: In order to use `http_req` with `rustls` in your project, add following lines to `Cargo.toml`: ```toml [dependencies] -http_req = {version="^0.9", default-features = false, features = ["rust-tls"]} +http_req = {version="^0.10", default-features = false, features = ["rust-tls"]} ``` ## License From 07772307ab82243c7315078432418ffe76337ec8 Mon Sep 17 00:00:00 2001 From: jayjamesjay Date: Sun, 24 Sep 2023 21:47:55 +0200 Subject: [PATCH 6/8] update Uri parser --- src/uri.rs | 182 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 112 insertions(+), 70 deletions(-) diff --git a/src/uri.rs b/src/uri.rs index 378a602..b7d549f 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -229,16 +229,10 @@ impl<'a> Uri<'a> { ///assert_eq!(uri.resource(), "/bar/baz?query#fragment"); ///``` pub fn resource(&self) -> &str { - let mut result = "/"; - - for v in &[self.path, self.query, self.fragment] { - if let Some(r) = v { - result = &self.inner[r.start..]; - break; - } + match self.path { + Some(p) => &self.inner[p.start..], + None => "/", } - - result } } @@ -265,30 +259,38 @@ impl<'a> TryFrom<&'a str> for Uri<'a> { fn try_from(s: &'a str) -> Result { let (scheme, mut uri_part) = get_chunks(&s, Some(RangeC::new(0, s.len())), ":"); let scheme = scheme.ok_or(ParseErr::UriErr)?; + let (mut authority, mut query, mut fragment) = (None, None, None); - let mut authority = None; - - if let Some(u) = &uri_part { - if s[*u].contains("//") { + if let Some(u) = uri_part { + if s[u].contains("//") { let (auth, part) = get_chunks(&s, Some(RangeC::new(u.start + 2, u.end)), "/"); - authority = if let Some(a) = auth { - Some(Authority::try_from(&s[a])?) - } else { - None + if let Some(a) = auth { + authority = Some(Authority::try_from(&s[a])?) }; uri_part = part; } } - let (mut path, uri_part) = get_chunks(&s, uri_part, "?"); - - if authority.is_some() || &s[scheme] == "file" { - path = path.map(|p| RangeC::new(p.start - 1, p.end)); + if let Some(u) = uri_part { + if &s[u.start - 1..u.start] == "/" { + uri_part = Some(RangeC::new(u.start - 1, u.end)); + } } - let (query, fragment) = get_chunks(&s, uri_part, "#"); + let mut path = uri_part; + + if let Some(u) = uri_part { + if s[u].contains("?") && s[u].contains("#") { + (path, uri_part) = get_chunks(&s, uri_part, "?"); + (query, fragment) = get_chunks(&s, uri_part, "#"); + } else if s[u].contains("?") { + (path, query) = get_chunks(&s, uri_part, "?"); + } else if s[u].contains("#") { + (path, fragment) = get_chunks(&s, uri_part, "#"); + } + } Ok(Uri { inner: s, @@ -405,17 +407,14 @@ impl<'a> TryFrom<&'a str> for Authority<'a> { let uri_part = if s.contains('@') { let (info, part) = get_chunks(&s, Some(RangeC::new(0, s.len())), "@"); - let (name, pass) = get_chunks(&s, info, ":"); - - username = name; - password = pass; + (username, password) = get_chunks(&s, info, ":"); part } else { Some(RangeC::new(0, s.len())) }; - let split_by = if s.contains(']') && s.contains('[') { + let split_by = if s.contains('[') && s.contains(']') { "]:" } else { ":" @@ -498,12 +497,14 @@ fn get_chunks<'a>( mod tests { use super::*; - const TEST_URIS: [&str; 5] = [ + const TEST_URIS: [&str; 7] = [ "https://user:info@foo.com:12/bar/baz?query#fragment", "file:///C:/Users/User/Pictures/screenshot.png", "https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol", "mailto:John.Doe@example.com", "https://[4b10:bbb0:0:d0::ba7:8001]:443/", + "http://example.com/?query=val", + "https://example.com/#fragment", ]; const TEST_AUTH: [&str; 4] = [ @@ -552,12 +553,11 @@ mod tests { .iter() .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); + const RESULT: [&str; 7] = ["https", "file", "https", "mailto", "https", "http", "https"]; - assert_eq!(uris[0].scheme(), "https"); - assert_eq!(uris[1].scheme(), "file"); - assert_eq!(uris[2].scheme(), "https"); - assert_eq!(uris[3].scheme(), "mailto"); - assert_eq!(uris[4].scheme(), "https"); + for i in 0..RESULT.len() { + assert_eq!(uris[i].scheme(), RESULT[i]); + } } #[test] @@ -566,12 +566,11 @@ mod tests { .iter() .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); + const RESULT: [Option<&str>; 7] = [Some("user:info"), None, None, None, None, None, None]; - assert_eq!(uris[0].user_info(), Some("user:info")); - assert_eq!(uris[1].user_info(), None); - assert_eq!(uris[2].user_info(), None); - assert_eq!(uris[3].user_info(), None); - assert_eq!(uris[4].user_info(), None); + for i in 0..RESULT.len() { + assert_eq!(uris[i].user_info(), RESULT[i]); + } } #[test] @@ -581,11 +580,19 @@ mod tests { .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); - assert_eq!(uris[0].host(), Some("foo.com")); - assert_eq!(uris[1].host(), None); - assert_eq!(uris[2].host(), Some("en.wikipedia.org")); - assert_eq!(uris[3].host(), None); - assert_eq!(uris[4].host(), Some("[4b10:bbb0:0:d0::ba7:8001]")); + const RESULT: [Option<&str>; 7] = [ + Some("foo.com"), + None, + Some("en.wikipedia.org"), + None, + Some("[4b10:bbb0:0:d0::ba7:8001]"), + Some("example.com"), + Some("example.com"), + ]; + + for i in 0..RESULT.len() { + assert_eq!(uris[i].host(), RESULT[i]); + } } #[test] @@ -612,7 +619,11 @@ mod tests { assert_eq!(uris[0].port(), Some(12)); assert_eq!(uris[4].port(), Some(443)); - for i in 1..3 { + for i in 1..4 { + assert_eq!(uris[i].port(), None); + } + + for i in 5..7 { assert_eq!(uris[i].port(), None); } } @@ -624,11 +635,13 @@ mod tests { .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); - assert_eq!(uris[0].corr_port(), 12); - assert_eq!(uris[1].corr_port(), HTTP_PORT); - assert_eq!(uris[2].corr_port(), HTTPS_PORT); - assert_eq!(uris[3].corr_port(), HTTP_PORT); - assert_eq!(uris[4].corr_port(), HTTPS_PORT); + const RESULT: [u16; 7] = [ + 12, HTTP_PORT, HTTPS_PORT, HTTP_PORT, HTTPS_PORT, HTTP_PORT, HTTPS_PORT, + ]; + + for i in 0..RESULT.len() { + assert_eq!(uris[i].corr_port(), RESULT[i]); + } } #[test] @@ -638,14 +651,19 @@ mod tests { .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); - assert_eq!(uris[0].path(), Some("/bar/baz")); - assert_eq!( - uris[1].path(), - Some("/C:/Users/User/Pictures/screenshot.png") - ); - assert_eq!(uris[2].path(), Some("/wiki/Hypertext_Transfer_Protocol")); - assert_eq!(uris[3].path(), Some("John.Doe@example.com")); - assert_eq!(uris[4].path(), None); + const RESULT: [Option<&str>; 7] = [ + Some("/bar/baz"), + Some("/C:/Users/User/Pictures/screenshot.png"), + Some("/wiki/Hypertext_Transfer_Protocol"), + Some("John.Doe@example.com"), + None, + Some("/"), + Some("/"), + ]; + + for i in 0..RESULT.len() { + assert_eq!(uris[i].path(), RESULT[i]); + } } #[test] @@ -655,10 +673,18 @@ mod tests { .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); - assert_eq!(uris[0].query(), Some("query")); - - for i in 1..4 { - assert_eq!(uris[i].query(), None); + const RESULT: [Option<&str>; 7] = [ + Some("query"), + None, + None, + None, + None, + Some("query=val"), + None, + ]; + + for i in 0..RESULT.len() { + assert_eq!(uris[i].query(), RESULT[i]); } } @@ -669,10 +695,18 @@ mod tests { .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); - assert_eq!(uris[0].fragment(), Some("fragment")); - - for i in 1..4 { - assert_eq!(uris[i].fragment(), None); + const RESULT: [Option<&str>; 7] = [ + Some("fragment"), + None, + None, + None, + None, + None, + Some("fragment"), + ]; + + for i in 0..RESULT.len() { + assert_eq!(uris[i].fragment(), RESULT[i]); } } @@ -683,11 +717,19 @@ mod tests { .map(|uri| Uri::try_from(*uri).unwrap()) .collect(); - assert_eq!(uris[0].resource(), "/bar/baz?query#fragment"); - assert_eq!(uris[1].resource(), "/C:/Users/User/Pictures/screenshot.png"); - assert_eq!(uris[2].resource(), "/wiki/Hypertext_Transfer_Protocol"); - assert_eq!(uris[3].resource(), "John.Doe@example.com"); - assert_eq!(uris[4].resource(), "/"); + const RESULT: [&str; 7] = [ + "/bar/baz?query#fragment", + "/C:/Users/User/Pictures/screenshot.png", + "/wiki/Hypertext_Transfer_Protocol", + "John.Doe@example.com", + "/", + "/?query=val", + "/#fragment" + ]; + + for i in 0..RESULT.len() { + assert_eq!(uris[i].resource(), RESULT[i]); + } } #[test] From 55bb7c06058a592e33d98a38dc48852ed62d8391 Mon Sep 17 00:00:00 2001 From: jayjamesjay Date: Mon, 25 Sep 2023 19:44:28 +0200 Subject: [PATCH 7/8] refactor uri --- src/error.rs | 2 +- src/lib.rs | 5 +++-- src/uri.rs | 60 ++++++++++++++++++++++------------------------------ 3 files changed, 29 insertions(+), 38 deletions(-) diff --git a/src/error.rs b/src/error.rs index 477d4bd..5c16e68 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,9 +21,9 @@ impl error::Error for ParseErr { match self { Utf8(e) => Some(e), Int(e) => Some(e), + StatusErr | HeadersErr | UriErr | Invalid | Empty => None, #[cfg(feature = "rust-tls")] Rustls(e) => Some(e), - StatusErr | HeadersErr | UriErr | Invalid | Empty => None, } } } diff --git a/src/lib.rs b/src/lib.rs index 27bd641..d07d71f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,9 @@ //!use http_req::request; //! //!fn main() { -//! let mut writer = Vec::new(); //container for body of a response -//! let res = request::get("https://doc.rust-lang.org/", &mut writer).unwrap(); +//! //Container for body of a response +//! let mut body = Vec::new(); +//! let res = request::get("https://doc.rust-lang.org/", &mut body).unwrap(); //! //! println!("Status: {} {}", res.status_code(), res.reason()); //!} diff --git a/src/uri.rs b/src/uri.rs index b7d549f..a3e9301 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -238,16 +238,14 @@ impl<'a> Uri<'a> { impl<'a> fmt::Display for Uri<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let uri = if let Some(auth) = &self.authority { - let mut uri = self.inner.to_string(); + let mut uri = self.inner.to_string(); + + if let Some(auth) = &self.authority { let auth = auth.to_string(); let start = self.scheme.end + 3; uri.replace_range(start..(start + auth.len()), &auth); - uri - } else { - self.inner.to_string() - }; + } write!(f, "{}", uri) } @@ -402,8 +400,7 @@ impl<'a> TryFrom<&'a str> for Authority<'a> { type Error = ParseErr; fn try_from(s: &'a str) -> Result { - let mut username = None; - let mut password = None; + let (mut username, mut password) = (None, None); let uri_part = if s.contains('@') { let (info, part) = get_chunks(&s, Some(RangeC::new(0, s.len())), "@"); @@ -440,17 +437,14 @@ impl<'a> TryFrom<&'a str> for Authority<'a> { impl<'a> fmt::Display for Authority<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let auth = if let Some(pass) = self.password { - let range = Range::from(pass); + let mut auth = self.inner.to_string(); + if let Some(pass) = self.password { + let range = Range::from(pass); let hidden_pass = "*".repeat(range.len()); - let mut auth = self.inner.to_string(); - auth.replace_range(range, &hidden_pass); - auth - } else { - self.inner.to_string() - }; + auth.replace_range(range, &hidden_pass); + } write!(f, "{}", auth) } @@ -469,28 +463,24 @@ fn get_chunks<'a>( range: Option, separator: &'a str, ) -> (Option, Option) { - if let Some(r) = range { - let range = Range::from(r); + let (mut before, mut after) = (None, None); - match s[range.clone()].find(separator) { + if let Some(range) = range { + match s[range].find(separator) { Some(i) => { - let mid = r.start + i + separator.len(); - let before = Some(RangeC::new(r.start, mid - 1)).filter(|r| r.start != r.end); - let after = Some(RangeC::new(mid, r.end)).filter(|r| r.start != r.end); - - (before, after) + let mid = range.start + i + separator.len(); + before = Some(RangeC::new(range.start, mid - 1)).filter(|r| r.start != r.end); + after = Some(RangeC::new(mid, range.end)).filter(|r| r.start != r.end); } None => { if !s[range].is_empty() { - (Some(r), None) - } else { - (None, None) + before = Some(range); } } } - } else { - (None, None) } + + (before, after) } #[cfg(test)] @@ -718,13 +708,13 @@ mod tests { .collect(); const RESULT: [&str; 7] = [ - "/bar/baz?query#fragment", - "/C:/Users/User/Pictures/screenshot.png", + "/bar/baz?query#fragment", + "/C:/Users/User/Pictures/screenshot.png", "/wiki/Hypertext_Transfer_Protocol", - "John.Doe@example.com", - "/", - "/?query=val", - "/#fragment" + "John.Doe@example.com", + "/", + "/?query=val", + "/#fragment", ]; for i in 0..RESULT.len() { From 33e79124e5a428f46bfb815cd08e768cd10fbb0b Mon Sep 17 00:00:00 2001 From: jayjamesjay Date: Mon, 2 Oct 2023 19:33:56 +0200 Subject: [PATCH 8/8] update dependencies --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4038d1a..03afe1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,9 +59,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "errno" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" dependencies = [ "errno-dragonfly", "libc", @@ -80,9 +80,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "foreign-types" @@ -134,9 +134,9 @@ checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" [[package]] name = "log" @@ -262,9 +262,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.14" +version = "0.38.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +checksum = "d2f9da0cbd88f9f09e7814e388301c8414c51c62aa6ce1e4b5c551d49d96e531" dependencies = [ "bitflags 2.4.0", "errno", @@ -296,9 +296,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.5" +version = "0.101.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" dependencies = [ "ring", "untrusted", @@ -475,9 +475,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" +checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f" dependencies = [ "ring", "untrusted",