From bd8f133d4233dab5f29707c943095a4845760d12 Mon Sep 17 00:00:00 2001 From: "Jamil Lambert, PhD" Date: Wed, 26 Nov 2025 17:06:55 +0000 Subject: [PATCH 1/7] Run the formatter on master --- verify/src/reexports.rs | 73 ++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/verify/src/reexports.rs b/verify/src/reexports.rs index 1e346514..65f64ed5 100644 --- a/verify/src/reexports.rs +++ b/verify/src/reexports.rs @@ -52,22 +52,19 @@ pub fn check_type_reexports(version: Version) -> Result<()> { }; for type_name in version_defs.keys() { - let exported = export_map.values().any(|info| { - info.source_version == version_name && type_name == &info.source_ident - }); + let exported = export_map + .values() + .any(|info| info.source_version == version_name && type_name == &info.source_ident); if !exported { - missing.push(format!( - "{} defines {} but does not re-export it", - version_name, type_name - )); + missing + .push(format!("{} defines {} but does not re-export it", version_name, type_name)); } } // Checks all auxiliary types are re-exported. for (exported_name, export) in &export_map { - if let Some(deps) = definitions - .get(&export.source_version) - .and_then(|map| map.get(&export.source_ident)) + if let Some(deps) = + definitions.get(&export.source_version).and_then(|map| map.get(&export.source_ident)) { for dep in deps { if !export_map.contains_key(dep) { @@ -108,10 +105,7 @@ fn collect_version_dirs(src_dir: &Path) -> Result> { } /// Parses all versioned source files and records every public struct/enum name. -fn collect_type_files_and_names( - src_dir: &Path, - versions: &[String], -) -> Result { +fn collect_type_files_and_names(src_dir: &Path, versions: &[String]) -> Result { let mut files = Vec::new(); let mut names = HashSet::new(); @@ -162,14 +156,18 @@ fn collect_type_definitions( match item { Item::Struct(item_struct) if is_public(&item_struct.vis) => { let deps = collect_deps_from_fields(&item_struct.fields, known_names); - defs.entry(version.clone()).or_default().insert(item_struct.ident.to_string(), deps); + defs.entry(version.clone()) + .or_default() + .insert(item_struct.ident.to_string(), deps); } Item::Enum(item_enum) if is_public(&item_enum.vis) => { let mut deps = BTreeSet::new(); for variant in item_enum.variants { deps.extend(collect_deps_from_fields(&variant.fields, known_names)); } - defs.entry(version.clone()).or_default().insert(item_enum.ident.to_string(), deps); + defs.entry(version.clone()) + .or_default() + .insert(item_enum.ident.to_string(), deps); } _ => {} } @@ -180,15 +178,12 @@ fn collect_type_definitions( } /// Reads `mod.rs` for the chosen version and lists its public re-exports. -fn collect_exports( - src_dir: &Path, - version: &str, -) -> Result> { +fn collect_exports(src_dir: &Path, version: &str) -> Result> { let mod_path = src_dir.join(version).join("mod.rs"); - let content = fs::read_to_string(&mod_path) - .with_context(|| format!("reading {}", mod_path.display()))?; - let syntax = syn::parse_file(&content) - .with_context(|| format!("parsing {}", mod_path.display()))?; + let content = + fs::read_to_string(&mod_path).with_context(|| format!("reading {}", mod_path.display()))?; + let syntax = + syn::parse_file(&content).with_context(|| format!("parsing {}", mod_path.display()))?; let mut exports = HashMap::new(); for item in syntax.items { @@ -213,16 +208,14 @@ fn collect_exports( fn collect_deps_from_fields(fields: &Fields, known_names: &HashSet) -> BTreeSet { let mut deps = BTreeSet::new(); match fields { - Fields::Named(named) => { + Fields::Named(named) => for field in &named.named { collect_type_dependencies(&field.ty, known_names, &mut deps); - } - } - Fields::Unnamed(unnamed) => { + }, + Fields::Unnamed(unnamed) => for field in &unnamed.unnamed { collect_type_dependencies(&field.ty, known_names, &mut deps); - } - } + }, Fields::Unit => {} } deps @@ -255,11 +248,10 @@ fn collect_type_dependencies( Type::Reference(reference) => collect_type_dependencies(&reference.elem, known_names, deps), Type::Paren(paren) => collect_type_dependencies(&paren.elem, known_names, deps), Type::Group(group) => collect_type_dependencies(&group.elem, known_names, deps), - Type::Tuple(tuple) => { + Type::Tuple(tuple) => for elem in &tuple.elems { collect_type_dependencies(elem, known_names, deps); - } - } + }, Type::Array(array) => collect_type_dependencies(&array.elem, known_names, deps), Type::Slice(slice) => collect_type_dependencies(&slice.elem, known_names, deps), Type::Ptr(ptr) => collect_type_dependencies(&ptr.elem, known_names, deps), @@ -278,21 +270,17 @@ fn flatten_use_tree(prefix: Vec, tree: &UseTree, acc: &mut Vec UseTree::Rename(rename) => { let mut path = prefix; path.push(rename.ident.to_string()); - acc.push(UseEntry { - path, - rename: Some(rename.rename.to_string()), - }); + acc.push(UseEntry { path, rename: Some(rename.rename.to_string()) }); } UseTree::Path(path) => { let mut new_prefix = prefix; new_prefix.push(path.ident.to_string()); flatten_use_tree(new_prefix, &path.tree, acc); } - UseTree::Group(group) => { + UseTree::Group(group) => for item in &group.items { flatten_use_tree(prefix.clone(), item, acc); - } - } + }, UseTree::Glob(_) => {} } } @@ -303,10 +291,7 @@ fn interpret_flat_use(target_version: &str, entry: &UseEntry) -> Option Some(ExportInfo { From bde21bed898842fd9d96569794f5625b234d46a4 Mon Sep 17 00:00:00 2001 From: "Jamil Lambert, PhD" Date: Tue, 25 Nov 2025 16:57:47 +0000 Subject: [PATCH 2/7] Split out char percent encoding Create a separate function that does the percent encoding of a single character for future use in the request URL encoding. --- bitreq/src/http_url.rs | 52 +++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/bitreq/src/http_url.rs b/bitreq/src/http_url.rs index 7f5a3950..38fbda31 100644 --- a/bitreq/src/http_url.rs +++ b/bitreq/src/http_url.rs @@ -116,25 +116,8 @@ impl HttpUrl { | '?' => { resource.push(c); } - // There is probably a simpler way to do this, but this - // method avoids any heap allocations (except extending - // `resource`) - _ => { - // Any UTF-8 character can fit in 4 bytes - let mut utf8_buf = [0u8; 4]; - // Bytes fill buffer from the front - c.encode_utf8(&mut utf8_buf); - // Slice disregards the unused portion of the buffer - utf8_buf[..c.len_utf8()].iter().for_each(|byte| { - // Convert byte to URL escape, e.g. %21 for b'!' - let rem = *byte % 16; - let right_char = to_hex_digit(rem); - let left_char = to_hex_digit((*byte - rem) >> 4); - resource.push('%'); - resource.push(left_char); - resource.push(right_char); - }); - } + // Every other character gets percent-encoded. + _ => percent_encode_char(c, &mut resource), }, } } @@ -200,3 +183,34 @@ fn to_hex_digit(digit: u8) -> char { 10..=255 => (b'A' - 10 + digit) as char, } } + +/// Percent-encodes a char and appends it to `result`. +/// Unreserved characters (0-9, A-Z, a-z, -, ., _, ~) are not encoded. +#[cfg(feature = "urlencoding")] +pub(crate) fn percent_encode_char(c: char, result: &mut String) { + match c { + // All URL-'safe' characters are not encoded + '0'..='9' | 'A'..='Z' | 'a'..='z' | '-' | '.' | '_' | '~' => { + result.push(c); + } + _ => { + // There is probably a simpler way to do this, but this + // method avoids any heap allocations (except extending + // `resource`) + // Any UTF-8 character can fit in 4 bytes + let mut utf8_buf = [0u8; 4]; + // Bytes fill buffer from the front + c.encode_utf8(&mut utf8_buf); + // Slice disregards the unused portion of the buffer + utf8_buf[..c.len_utf8()].iter().for_each(|byte| { + // Convert byte to URL escape, e.g. %21 for b'!' + let rem = *byte % 16; + let right_char = to_hex_digit(rem); + let left_char = to_hex_digit((*byte - rem) >> 4); + result.push('%'); + result.push(left_char); + result.push(right_char); + }); + } + } +} From 77cf5010f144974911716bbb88e77cd1b89b7da0 Mon Sep 17 00:00:00 2001 From: "Jamil Lambert, PhD" Date: Tue, 25 Nov 2025 17:34:44 +0000 Subject: [PATCH 3/7] Refactor percent encoding Functionality is the same. Refactor only. --- bitreq/src/http_url.rs | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/bitreq/src/http_url.rs b/bitreq/src/http_url.rs index 38fbda31..e834874b 100644 --- a/bitreq/src/http_url.rs +++ b/bitreq/src/http_url.rs @@ -174,14 +174,11 @@ impl HttpUrl { } } -// https://github.com/kornelski/rust_urlencoding/blob/a4df8027ab34a86a63f1be727965cf101556403f/src/enc.rs#L130-L136 -// Converts a UTF-8 byte to a single hexadecimal character +/// Returns the `%HH` triplet representing `byte` for percent encoding. #[cfg(feature = "urlencoding")] -fn to_hex_digit(digit: u8) -> char { - match digit { - 0..=9 => (b'0' + digit) as char, - 10..=255 => (b'A' - 10 + digit) as char, - } +fn percent_encoded_triplet(byte: u8) -> [char; 3] { + const HEX: &[u8; 16] = b"0123456789ABCDEF"; + ['%', HEX[(byte >> 4) as usize] as char, HEX[(byte & 0x0F) as usize] as char] } /// Percent-encodes a char and appends it to `result`. @@ -194,22 +191,12 @@ pub(crate) fn percent_encode_char(c: char, result: &mut String) { result.push(c); } _ => { - // There is probably a simpler way to do this, but this - // method avoids any heap allocations (except extending - // `resource`) // Any UTF-8 character can fit in 4 bytes let mut utf8_buf = [0u8; 4]; - // Bytes fill buffer from the front - c.encode_utf8(&mut utf8_buf); - // Slice disregards the unused portion of the buffer - utf8_buf[..c.len_utf8()].iter().for_each(|byte| { - // Convert byte to URL escape, e.g. %21 for b'!' - let rem = *byte % 16; - let right_char = to_hex_digit(rem); - let left_char = to_hex_digit((*byte - rem) >> 4); - result.push('%'); - result.push(left_char); - result.push(right_char); + c.encode_utf8(&mut utf8_buf).as_bytes().iter().for_each(|byte| { + for ch in percent_encoded_triplet(*byte) { + result.push(ch); + } }); } } From 0f07b6b5d839d53a5c5e3f518b1c6b998ac83901 Mon Sep 17 00:00:00 2001 From: "Jamil Lambert, PhD" Date: Wed, 26 Nov 2025 17:44:30 +0000 Subject: [PATCH 4/7] Move std feature gate to individual funcions The whole http_url module was feature gated on std. This prevents using the percent-encoding functions in no_std environments. Remove the feature gate from the module and instead gate only the individual functions that require std. --- bitreq/src/http_url.rs | 6 ++++++ bitreq/src/lib.rs | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/bitreq/src/http_url.rs b/bitreq/src/http_url.rs index e834874b..b3bdf57c 100644 --- a/bitreq/src/http_url.rs +++ b/bitreq/src/http_url.rs @@ -1,7 +1,10 @@ +#[cfg(feature = "std")] use core::fmt::{self, Write}; +#[cfg(feature = "std")] use crate::Error; +#[cfg(feature = "std")] #[derive(Clone, Copy, PartialEq)] pub(crate) enum Port { ImplicitHttp, @@ -9,6 +12,7 @@ pub(crate) enum Port { Explicit(u32), } +#[cfg(feature = "std")] impl Port { pub(crate) fn port(self) -> u32 { match self { @@ -27,6 +31,7 @@ impl Port { /// ```text /// scheme "://" host [ ":" port ] path [ "?" query ] [ "#" fragment ] /// ``` +#[cfg(feature = "std")] #[derive(Clone, PartialEq)] pub(crate) struct HttpUrl { /// If scheme is "https", true, if "http", false. @@ -41,6 +46,7 @@ pub(crate) struct HttpUrl { pub(crate) fragment: Option, } +#[cfg(feature = "std")] impl HttpUrl { pub(crate) fn parse(url: &str, redirected_from: Option<&HttpUrl>) -> Result { enum UrlParseStatus { diff --git a/bitreq/src/lib.rs b/bitreq/src/lib.rs index afa718de..649c54d0 100644 --- a/bitreq/src/lib.rs +++ b/bitreq/src/lib.rs @@ -231,7 +231,6 @@ extern crate alloc; #[cfg(feature = "std")] mod connection; mod error; -#[cfg(feature = "std")] mod http_url; #[cfg(feature = "proxy")] mod proxy; From 332b299eb97a90fa102c00e1f71a25ad8e23021b Mon Sep 17 00:00:00 2001 From: "Jamil Lambert, PhD" Date: Tue, 25 Nov 2025 17:59:31 +0000 Subject: [PATCH 5/7] Remove urlencoding dependence Create a new function percent_encode_string to encode entire strings. Use this instead of urlencoding::encode. --- bitreq/Cargo.toml | 3 +-- bitreq/src/http_url.rs | 10 ++++++++++ bitreq/src/request.rs | 6 ++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/bitreq/Cargo.toml b/bitreq/Cargo.toml index 4f01d65d..a0a097db 100644 --- a/bitreq/Cargo.toml +++ b/bitreq/Cargo.toml @@ -16,8 +16,6 @@ rust-version = "1.75.0" maintenance = { status = "experimental" } [dependencies] -# For the urlencoding feature: -urlencoding = { version = "2.1.0", optional = true } # For the punycode feature: punycode = { version = "0.4.1", optional = true } # For the json-using-serde feature: @@ -53,6 +51,7 @@ json-using-serde = ["serde", "serde_json"] proxy = ["base64", "std"] async = ["tokio", "std"] async-https = ["async", "https-rustls", "tokio-rustls"] +urlencoding = [] [[example]] name = "hello" diff --git a/bitreq/src/http_url.rs b/bitreq/src/http_url.rs index b3bdf57c..9a53baf0 100644 --- a/bitreq/src/http_url.rs +++ b/bitreq/src/http_url.rs @@ -207,3 +207,13 @@ pub(crate) fn percent_encode_char(c: char, result: &mut String) { } } } + +/// Percent-encodes the entire input string and returns the encoded version. +#[cfg(feature = "urlencoding")] +pub(crate) fn percent_encode_string(input: &str) -> String { + let mut encoded = String::with_capacity(input.len()); + for ch in input.chars() { + percent_encode_char(ch, &mut encoded); + } + encoded +} diff --git a/bitreq/src/request.rs b/bitreq/src/request.rs index 0cfbbaea..e9bfef5f 100644 --- a/bitreq/src/request.rs +++ b/bitreq/src/request.rs @@ -7,6 +7,8 @@ use core::fmt::Write; use crate::connection::AsyncConnection; #[cfg(feature = "std")] use crate::connection::Connection; +#[cfg(feature = "urlencoding")] +use crate::http_url::percent_encode_string; #[cfg(feature = "std")] use crate::http_url::{HttpUrl, Port}; #[cfg(feature = "proxy")] @@ -159,10 +161,10 @@ impl Request { pub fn with_param, U: Into>(mut self, key: T, value: U) -> Request { let key = key.into(); #[cfg(feature = "urlencoding")] - let key = urlencoding::encode(&key); + let key = percent_encode_string(&key); let value = value.into(); #[cfg(feature = "urlencoding")] - let value = urlencoding::encode(&value); + let value = percent_encode_string(&value); if !self.params.is_empty() { self.params.push('&'); From de77534bfcccc2f2b4ad40e76b6e8f051c551223 Mon Sep 17 00:00:00 2001 From: "Jamil Lambert, PhD" Date: Tue, 25 Nov 2025 19:30:44 +0000 Subject: [PATCH 6/7] Remove urlencoding feature There is no longer a dependency on the urlencoding crate. Remove the feature and all related feature gates and comments. --- bitreq/Cargo.toml | 1 - bitreq/src/http_url.rs | 6 ------ bitreq/src/lib.rs | 6 ------ bitreq/src/request.rs | 19 ++++--------------- 4 files changed, 4 insertions(+), 28 deletions(-) diff --git a/bitreq/Cargo.toml b/bitreq/Cargo.toml index a0a097db..3616300d 100644 --- a/bitreq/Cargo.toml +++ b/bitreq/Cargo.toml @@ -51,7 +51,6 @@ json-using-serde = ["serde", "serde_json"] proxy = ["base64", "std"] async = ["tokio", "std"] async-https = ["async", "https-rustls", "tokio-rustls"] -urlencoding = [] [[example]] name = "hello" diff --git a/bitreq/src/http_url.rs b/bitreq/src/http_url.rs index 9a53baf0..42257611 100644 --- a/bitreq/src/http_url.rs +++ b/bitreq/src/http_url.rs @@ -102,9 +102,6 @@ impl HttpUrl { path_and_query = Some(resource); resource = String::new(); } - #[cfg(not(feature = "urlencoding"))] - UrlParseStatus::PathAndQuery | UrlParseStatus::Fragment => resource.push(c), - #[cfg(feature = "urlencoding")] UrlParseStatus::PathAndQuery | UrlParseStatus::Fragment => match c { // All URL-'safe' characters, plus URL 'special // characters' like &, #, =, / ,? @@ -181,7 +178,6 @@ impl HttpUrl { } /// Returns the `%HH` triplet representing `byte` for percent encoding. -#[cfg(feature = "urlencoding")] fn percent_encoded_triplet(byte: u8) -> [char; 3] { const HEX: &[u8; 16] = b"0123456789ABCDEF"; ['%', HEX[(byte >> 4) as usize] as char, HEX[(byte & 0x0F) as usize] as char] @@ -189,7 +185,6 @@ fn percent_encoded_triplet(byte: u8) -> [char; 3] { /// Percent-encodes a char and appends it to `result`. /// Unreserved characters (0-9, A-Z, a-z, -, ., _, ~) are not encoded. -#[cfg(feature = "urlencoding")] pub(crate) fn percent_encode_char(c: char, result: &mut String) { match c { // All URL-'safe' characters are not encoded @@ -209,7 +204,6 @@ pub(crate) fn percent_encode_char(c: char, result: &mut String) { } /// Percent-encodes the entire input string and returns the encoded version. -#[cfg(feature = "urlencoding")] pub(crate) fn percent_encode_string(input: &str) -> String { let mut encoded = String::with_capacity(input.len()); for ch in input.chars() { diff --git a/bitreq/src/lib.rs b/bitreq/src/lib.rs index 649c54d0..a1bcde23 100644 --- a/bitreq/src/lib.rs +++ b/bitreq/src/lib.rs @@ -72,12 +72,6 @@ //! //! This feature enables HTTP proxy support. See [Proxy]. //! -//! ## `urlencoding` -//! -//! This feature enables percent-encoding for the URL resource when -//! creating a request and any subsequently added parameters from -//! [`Request::with_param`]. -//! //! # Examples //! //! ## Get diff --git a/bitreq/src/request.rs b/bitreq/src/request.rs index e9bfef5f..eb375a8b 100644 --- a/bitreq/src/request.rs +++ b/bitreq/src/request.rs @@ -7,7 +7,6 @@ use core::fmt::Write; use crate::connection::AsyncConnection; #[cfg(feature = "std")] use crate::connection::Connection; -#[cfg(feature = "urlencoding")] use crate::http_url::percent_encode_string; #[cfg(feature = "std")] use crate::http_url::{HttpUrl, Port}; @@ -100,12 +99,8 @@ impl Request { /// This is only the request's data, it is not sent yet. For /// sending the request, see [`send`](struct.Request.html#method.send). /// - /// If `urlencoding` is not enabled, it is the responsibility of the - /// user to ensure there are no illegal characters in the URL. - /// - /// If `urlencoding` is enabled, the resource part of the URL will be - /// encoded. Any URL special characters (e.g. &, #, =) are not encoded - /// as they are assumed to be meaningful parameters etc. + /// The resource part of the URL will be encoded. Any URL special characters (e.g. &, #, =) are + /// not encoded as they are assumed to be meaningful parameters etc. pub fn new>(method: Method, url: T) -> Request { Request { method, @@ -153,17 +148,11 @@ impl Request { /// Adds given key and value as query parameter to request url /// (resource). /// - /// If `urlencoding` is not enabled, it is the responsibility - /// of the user to ensure there are no illegal characters in the - /// key or value. - /// - /// If `urlencoding` is enabled, the key and value are both encoded. + /// The key and value are both encoded. pub fn with_param, U: Into>(mut self, key: T, value: U) -> Request { let key = key.into(); - #[cfg(feature = "urlencoding")] let key = percent_encode_string(&key); let value = value.into(); - #[cfg(feature = "urlencoding")] let value = percent_encode_string(&value); if !self.params.is_empty() { @@ -612,7 +601,7 @@ mod parsing_tests { } } -#[cfg(all(test, feature = "urlencoding"))] +#[cfg(all(test, feature = "std"))] mod encoding_tests { use super::{get, ParsedRequest}; From 035772742d9bbd32ba11a2d933cf53f1e1f18171 Mon Sep 17 00:00:00 2001 From: "Jamil Lambert, PhD" Date: Wed, 26 Nov 2025 17:06:09 +0000 Subject: [PATCH 7/7] Update lock files --- Cargo-minimal.lock | 7 ------- Cargo-recent.lock | 7 ------- 2 files changed, 14 deletions(-) diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 835e598c..cf7242d3 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -170,7 +170,6 @@ dependencies = [ "tiny_http", "tokio", "tokio-rustls", - "urlencoding", "webpki-roots", ] @@ -850,12 +849,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 0eb54056..eb17c45b 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -170,7 +170,6 @@ dependencies = [ "tiny_http", "tokio", "tokio-rustls", - "urlencoding", "webpki-roots", ] @@ -883,12 +882,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1"