From 111477aa748e431b340be939afa618267eb7f5b7 Mon Sep 17 00:00:00 2001 From: Peter Cunderlik Date: Sun, 25 Jul 2021 20:08:08 +0100 Subject: [PATCH] Add sha256 to the hash command (#3836) Hashers now uses on Rust Crypto Digest trait which makes it trivial to implement additional hash functions. The original `md5` crate does not implement the Digest trait and was replaced by `md-5` crate which does. Sha256 uses already included `sha2` crate. --- Cargo.lock | 14 ++- crates/nu-command/Cargo.toml | 3 +- .../generators/hash_/generic_digest.rs | 58 +++++++++++++ .../src/commands/generators/hash_/md5_.rs | 62 +++---------- .../src/commands/generators/hash_/mod.rs | 3 + .../src/commands/generators/hash_/sha256_.rs | 86 +++++++++++++++++++ crates/nu-command/src/default_context.rs | 1 + 7 files changed, 173 insertions(+), 54 deletions(-) create mode 100644 crates/nu-command/src/commands/generators/hash_/generic_digest.rs create mode 100644 crates/nu-command/src/commands/generators/hash_/sha256_.rs diff --git a/Cargo.lock b/Cargo.lock index 92ff51e6de..9a424ddd30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2830,6 +2830,17 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "md5" version = "0.6.1" @@ -3226,6 +3237,7 @@ dependencies = [ "csv", "ctrlc", "derive-new", + "digest 0.9.0", "directories-next", "dirs-next", "dtparse", @@ -3244,7 +3256,7 @@ dependencies = [ "itertools", "lazy_static 1.4.0", "log", - "md5 0.7.0", + "md-5", "meval", "minus", "nu-ansi-term", diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 29b9933a87..ad3c43c2bf 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -58,7 +58,7 @@ indexmap = { version="1.7", features=["serde-1"] } itertools = "0.10.0" lazy_static = "1.*" log = "0.4.14" -md5 = "0.7.0" +md-5 = "0.9.1" meval = "0.2.0" minus = { version="3.4.0", optional=true, features=["async_std_lib", "search"] } num-bigint = { version="0.3.1", features=["serde"] } @@ -96,6 +96,7 @@ url = "2.2.0" uuid_crate = { package="uuid", version="0.8.2", features=["v4"], optional=true } which = { version="4.1.0", optional=true } zip = { version="0.5.9", optional=true } +digest = "0.9.0" [dependencies.polars] version = "0.14.7" diff --git a/crates/nu-command/src/commands/generators/hash_/generic_digest.rs b/crates/nu-command/src/commands/generators/hash_/generic_digest.rs new file mode 100644 index 0000000000..542197b7c8 --- /dev/null +++ b/crates/nu-command/src/commands/generators/hash_/generic_digest.rs @@ -0,0 +1,58 @@ +use crate::prelude::*; +use nu_errors::ShellError; +use nu_protocol::ShellTypeName; +use nu_protocol::{ColumnPath, Primitive, UntaggedValue, Value}; +use nu_source::Tag; + +pub fn run(args: CommandArgs) -> Result +where + D: digest::Digest, + digest::Output: core::fmt::LowerHex, +{ + let column_paths: Vec = args.rest(0)?; + + Ok(args + .input + .map(move |v| { + if column_paths.is_empty() { + action::(&v, v.tag()) + } else { + let mut ret = v; + + for path in &column_paths { + ret = ret.swap_data_by_column_path( + path, + Box::new(move |old| action::(old, old.tag())), + )?; + } + + Ok(ret) + } + }) + .into_input_stream()) +} + +pub fn action(input: &Value, tag: Tag) -> Result +where + D: digest::Digest, + digest::Output: core::fmt::LowerHex, +{ + match &input.value { + UntaggedValue::Primitive(Primitive::String(s)) => { + let digest_result = D::digest(s.as_bytes()); + Ok(UntaggedValue::string(&format!("{:x}", digest_result)).into_value(tag)) + } + UntaggedValue::Primitive(Primitive::Binary(bytes)) => { + let digest_result = D::digest(bytes); + Ok(UntaggedValue::string(&format!("{:x}", digest_result)).into_value(tag)) + } + other => { + let got = format!("got {}", other.type_name()); + Err(ShellError::labeled_error( + "value is not supported for hashing", + got, + tag.span, + )) + } + } +} diff --git a/crates/nu-command/src/commands/generators/hash_/md5_.rs b/crates/nu-command/src/commands/generators/hash_/md5_.rs index d4c0fc6190..33365b497f 100644 --- a/crates/nu-command/src/commands/generators/hash_/md5_.rs +++ b/crates/nu-command/src/commands/generators/hash_/md5_.rs @@ -1,9 +1,10 @@ use crate::prelude::*; +use md5::Md5; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; -use nu_protocol::ShellTypeName; -use nu_protocol::{ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value}; -use nu_source::Tag; +use nu_protocol::{Signature, SyntaxShape, UntaggedValue}; + +use super::generic_digest; pub struct SubCommand; @@ -24,7 +25,7 @@ impl WholeStreamCommand for SubCommand { } fn run(&self, args: CommandArgs) -> Result { - operate(args) + generic_digest::run::(args) } fn examples(&self) -> Vec { @@ -49,65 +50,22 @@ impl WholeStreamCommand for SubCommand { } } -fn operate(args: CommandArgs) -> Result { - let column_paths: Vec = args.rest(0)?; - - Ok(args - .input - .map(move |v| { - if column_paths.is_empty() { - action(&v, v.tag()) - } else { - let mut ret = v; - - for path in &column_paths { - ret = ret.swap_data_by_column_path( - path, - Box::new(move |old| action(old, old.tag())), - )?; - } - - Ok(ret) - } - }) - .into_input_stream()) -} - -fn action(input: &Value, tag: impl Into) -> Result { - match &input.value { - UntaggedValue::Primitive(Primitive::String(s)) => { - let md5_digest = md5::compute(s.as_bytes()); - Ok(UntaggedValue::string(&format!("{:x}", md5_digest)).into_value(tag)) - } - UntaggedValue::Primitive(Primitive::Binary(bytes)) => { - let md5_digest = md5::compute(bytes); - Ok(UntaggedValue::string(&format!("{:x}", md5_digest)).into_value(tag)) - } - other => { - let got = format!("got {}", other.type_name()); - Err(ShellError::labeled_error( - "value is not supported for hashing as md5", - got, - tag.into().span, - )) - } - } -} - #[cfg(test)] mod tests { - use super::action; + use md5::Md5; use nu_protocol::{Primitive, UntaggedValue}; use nu_source::Tag; use nu_test_support::value::string; + use crate::commands::generators::hash_::generic_digest::action; + #[test] fn md5_encode_string() { let word = string("abcdefghijklmnopqrstuvwxyz"); let expected = UntaggedValue::string("c3fcd3d76192e4007dfb496cca67e13b").into_untagged_value(); - let actual = action(&word, Tag::unknown()).unwrap(); + let actual = action::(&word, Tag::unknown()).unwrap(); assert_eq!(actual, expected); } @@ -118,7 +76,7 @@ mod tests { let expected = UntaggedValue::string("5f80e231382769b0102b1164cf722d83").into_untagged_value(); - let actual = action(&binary, Tag::unknown()).unwrap(); + let actual = action::(&binary, Tag::unknown()).unwrap(); assert_eq!(actual, expected); } } diff --git a/crates/nu-command/src/commands/generators/hash_/mod.rs b/crates/nu-command/src/commands/generators/hash_/mod.rs index 30dcacf3f2..0a76ae094a 100644 --- a/crates/nu-command/src/commands/generators/hash_/mod.rs +++ b/crates/nu-command/src/commands/generators/hash_/mod.rs @@ -1,7 +1,10 @@ mod base64_; mod command; +mod generic_digest; mod md5_; +mod sha256_; pub use base64_::SubCommand as HashBase64; pub use command::Command as Hash; pub use md5_::SubCommand as HashMd5; +pub use sha256_::SubCommand as HashSha256; diff --git a/crates/nu-command/src/commands/generators/hash_/sha256_.rs b/crates/nu-command/src/commands/generators/hash_/sha256_.rs new file mode 100644 index 0000000000..666afde88d --- /dev/null +++ b/crates/nu-command/src/commands/generators/hash_/sha256_.rs @@ -0,0 +1,86 @@ +use crate::prelude::*; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{Signature, SyntaxShape, UntaggedValue}; +use sha2::Sha256; + +use super::generic_digest; + +pub struct SubCommand; + +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "hash sha256" + } + + fn signature(&self) -> Signature { + Signature::build("hash sha256").rest( + SyntaxShape::ColumnPath, + "optionally sha256 encode data by column paths", + ) + } + + fn usage(&self) -> &str { + "sha256 encode a value" + } + + fn run(&self, args: CommandArgs) -> Result { + generic_digest::run::(args) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "sha256 encode a string", + example: "echo 'abcdefghijklmnopqrstuvwxyz' | hash sha256", + result: Some(vec![UntaggedValue::string( + "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73", + ) + .into_untagged_value()]), + }, + Example { + description: "sha256 encode a file", + example: "open ./nu_0_24_1_windows.zip | hash sha256", + result: Some(vec![UntaggedValue::string( + "c47a10dc272b1221f0380a2ae0f7d7fa830b3e378f2f5309bbf13f61ad211913", + ) + .into_untagged_value()]), + }, + ] + } +} + +#[cfg(test)] +mod tests { + use nu_protocol::{Primitive, UntaggedValue}; + use nu_source::Tag; + use nu_test_support::value::string; + use sha2::Sha256; + + use crate::commands::generators::hash_::generic_digest::action; + + #[test] + fn md5_encode_string() { + let word = string("abcdefghijklmnopqrstuvwxyz"); + let expected = UntaggedValue::string( + "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73", + ) + .into_untagged_value(); + + let actual = action::(&word, Tag::unknown()).unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn md5_encode_bytes() { + let bytes = vec![0xC0, 0xFF, 0xEE]; + let binary = UntaggedValue::Primitive(Primitive::Binary(bytes)).into_untagged_value(); + let expected = UntaggedValue::string( + "c47a10dc272b1221f0380a2ae0f7d7fa830b3e378f2f5309bbf13f61ad211913", + ) + .into_untagged_value(); + + let actual = action::(&binary, Tag::unknown()).unwrap(); + assert_eq!(actual, expected); + } +} diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 4873399a84..61917cf2cf 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -85,6 +85,7 @@ pub fn create_default_context(interactive: bool) -> Result