diff --git a/CHANGELOG.md b/CHANGELOG.md index eac2ff0..b275238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). All versions prior to 1.0.0 are beta releases. +## Unreleased +- MSRV has been increased to 1.56.0 +- Bumped `secrecy` crate to 0.10 + ## [0.5.1] - 2024-08-31 ### Fixed - Client requests are now correctly percent-encoded when necessary. diff --git a/Cargo.lock b/Cargo.lock index f679124..9247569 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "secrecy" -version = "0.8.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" dependencies = [ "zeroize", ] @@ -88,6 +88,6 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.5.7" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 342c88e..f23079d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,13 @@ readme = "README.md" keywords = ["passphrase", "password"] categories = ["api-bindings", "command-line-interface"] license = "MIT OR Apache-2.0" -edition = "2018" +edition = "2021" +rust-version = "1.60" [dependencies] log = "0.4" nom = { version = "7", default-features = false } percent-encoding = "2.1" -secrecy = "0.8" +secrecy = "0.10" which = { version = "4", default-features = false } zeroize = "1" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a0d7144..47fd09e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.51.0" +channel = "1.60.0" components = ["clippy", "rustfmt"] diff --git a/src/assuan.rs b/src/assuan.rs index d9f4545..884737c 100644 --- a/src/assuan.rs +++ b/src/assuan.rs @@ -134,7 +134,7 @@ impl Connection { debug!("< OK {}", info); } line.zeroize(); - return Ok(data.map(SecretString::new)); + return Ok(data); } Response::Err { code, description } => { line.zeroize(); @@ -148,7 +148,20 @@ impl Connection { let buf = data.take(); let data_line_decoded = percent_decode_str(data_line.expose_secret()).decode_utf8()?; - data = Some(buf.unwrap_or_else(String::new) + &data_line_decoded); + + // Concatenate into a new buffer so we can control allocations. + let mut s = String::with_capacity( + buf.as_ref() + .map(|buf| buf.expose_secret().len()) + .unwrap_or(0) + + data_line_decoded.len(), + ); + if let Some(buf) = buf { + s.push_str(buf.expose_secret()); + } + s.push_str(data_line_decoded.as_ref()); + data = Some(s.into()); + if let Cow::Owned(mut data_line_decoded) = data_line_decoded { data_line_decoded.zeroize(); } @@ -174,13 +187,12 @@ mod read { sequence::{pair, preceded, terminated}, IResult, }; - use secrecy::SecretString; use super::Response; fn gpg_error_code(input: &str) -> IResult<&str, u16> { - map(digit1, |code| { - let full = u32::from_str_radix(code, 10).expect("have decimal digits"); + map(digit1, |code: &str| { + let full = code.parse::().expect("have decimal digits"); // gpg uses the lowest 16 bits for error codes. full as u16 })(input) @@ -224,7 +236,7 @@ mod read { preceded( tag("D "), map(is_not("\r\n"), |data: &str| { - Response::DataLine(SecretString::new(data.to_owned())) + Response::DataLine(data.to_owned().into()) }), ), preceded( diff --git a/src/lib.rs b/src/lib.rs index 383bcb3..0d24c27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ //! .interact() //! } else { //! // Fall back to some other passphrase entry method. -//! Ok(SecretString::new("a better passphrase than this".to_owned())) +//! Ok("a better passphrase than this".to_owned().into()) //! }?; //! # Ok::<(), pinentry::Error>(()) //! ``` @@ -50,7 +50,7 @@ //! ``` // Catch documentation errors caused by code changes. -#![deny(broken_intra_doc_links)] +#![deny(rustdoc::broken_intra_doc_links)] #![deny(missing_docs)] use secrecy::SecretString; @@ -84,7 +84,7 @@ impl<'a> PassphraseInput<'a> { /// /// Returns `None` if `pinentry` cannot be found in `PATH`. pub fn with_default_binary() -> Option { - Self::with_binary("pinentry".to_owned()) + Self::with_binary("pinentry") } /// Creates a new PassphraseInput using the given path to, or name of, a `pinentry` @@ -239,7 +239,7 @@ impl<'a> PassphraseInput<'a> { loop { match (pinentry.send_request("GETPIN", None)?, self.required) { // If the user provides an empty passphrase, GETPIN returns no data. - (None, None) => return Ok(SecretString::new(String::new())), + (None, None) => return Ok(String::new().into()), (Some(passphrase), _) => return Ok(passphrase), (_, Some(empty_error)) => { // SETERROR is cleared by GETPIN, so we reset it on each loop. @@ -265,7 +265,7 @@ impl<'a> ConfirmationDialog<'a> { /// /// Returns `None` if `pinentry` cannot be found in `PATH`. pub fn with_default_binary() -> Option { - Self::with_binary("pinentry".to_owned()) + Self::with_binary("pinentry") } /// Creates a new ConfirmationDialog using the given path to, or name of, a `pinentry` @@ -390,7 +390,7 @@ impl<'a> MessageDialog<'a> { /// /// Returns `None` if `pinentry` cannot be found in `PATH`. pub fn with_default_binary() -> Option { - Self::with_binary("pinentry".to_owned()) + Self::with_binary("pinentry") } /// Creates a new MessageDialog using the given path to, or name of, a `pinentry`