diff --git a/Cargo.lock b/Cargo.lock index 80ece1a46c..46d1ace3eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,9 +93,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.48" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e1f47f7dc0422027a4e370dd4548d4d66b26782e513e98dca1e689e058a80e" +checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" [[package]] name = "arrayref" @@ -200,9 +200,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.51" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2", "quote", @@ -282,6 +282,15 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block-buffer" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "brotli" version = "3.3.2" @@ -443,9 +452,9 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c01c1c607d25c71bbaa67c113d6c6b36c434744b4fd66691d711b5b1bc0c8b" +checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" dependencies = [ "chrono", "chrono-tz-build", @@ -500,10 +509,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] -name = "crc32fast" -version = "1.2.2" +name = "cpufeatures" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" dependencies = [ "cfg-if", ] @@ -584,6 +602,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567569e659735adb39ff2d4c20600f7cd78be5471f8c58ab162bce3c03fdbc5f" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "cstr_core" version = "0.2.4" @@ -666,6 +693,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "digest" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8549e6bfdecd113b7e221fe60b433087f6957387a20f8118ebca9b12af19143d" +dependencies = [ + "block-buffer", + "crypto-common", + "generic-array 0.14.4", +] + [[package]] name = "dirs" version = "1.0.5" @@ -772,9 +810,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.29" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746" +checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" dependencies = [ "cfg-if", ] @@ -1020,9 +1058,9 @@ checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[package]] name = "git2" -version = "0.13.24" +version = "0.13.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "845e007a28f1fcac035715988a234e8ec5458fd825b20a20c7dec74237ef341f" +checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" dependencies = [ "bitflags", "libc", @@ -1173,9 +1211,9 @@ checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" [[package]] name = "itertools" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] @@ -1285,15 +1323,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.108" +version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" +checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01" [[package]] name = "libgit2-sys" -version = "0.12.25+1.3.0" +version = "0.12.26+1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68169ef08d6519b2fe133ecc637408d933c0174b23b80bb2f79828966fbaab" +checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" dependencies = [ "cc", "libc", @@ -1393,6 +1431,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "md-5" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6a38fc55c8bbc10058782919516f88826e70320db6d206aebc49611d24216ae" +dependencies = [ + "digest", +] + [[package]] name = "memchr" version = "2.4.1" @@ -1410,9 +1457,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] @@ -1594,14 +1641,16 @@ name = "nu-command" version = "0.1.0" dependencies = [ "Inflector", + "base64", "bytesize", "calamine", "chrono", "chrono-humanize", - "chrono-tz 0.6.0", + "chrono-tz 0.6.1", "crossterm", "csv", "dialoguer", + "digest", "dtparse", "eml-parser", "glob", @@ -1611,6 +1660,7 @@ dependencies = [ "itertools", "lazy_static", "lscolors", + "md-5", "meval", "nu-ansi-term 0.39.0", "nu-engine", @@ -1633,6 +1683,7 @@ dependencies = [ "serde_ini", "serde_urlencoded", "serde_yaml", + "sha2", "strip-ansi-escapes", "sysinfo", "terminal_size", @@ -2013,9 +2064,9 @@ dependencies = [ [[package]] name = "parquet2" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41051fae4c0fab9040e291b360c6c8037d09d482aa83e94e37f3d080a32a58c3" +checksum = "57e98d7da0076cead49c49580cc5771dfe0ba8a93cadff9b47c1681a4a78e1f9" dependencies = [ "async-stream", "bitpacking", @@ -2106,9 +2157,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" +checksum = "d1a3ea4f0dd7f1f3e512cf97bf100819aa547f36a6eccac8dbaae839eb92363e" [[package]] name = "polars" @@ -2262,9 +2313,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" dependencies = [ "unicode-xid", ] @@ -2539,9 +2590,9 @@ checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" [[package]] name = "same-file" @@ -2578,18 +2629,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.130" +version = "1.0.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.130" +version = "1.0.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2" dependencies = [ "proc-macro2", "quote", @@ -2609,9 +2660,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" +checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" dependencies = [ "indexmap", "itoa", @@ -2621,9 +2672,9 @@ dependencies = [ [[package]] name = "serde_test" -version = "1.0.130" +version = "1.0.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82178225dbdeae2d5d190e8649287db6a3a32c6d24da22ae3146325aa353e4c" +checksum = "dfaa01d46254ba300fb5920e781e9eae209daac68b68b26cdd678da7bd4fc5c4" dependencies = [ "serde", ] @@ -2653,10 +2704,21 @@ dependencies = [ ] [[package]] -name = "signal-hook" -version = "0.3.10" +name = "sha2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" +checksum = "900d964dd36bb15bcf2f2b35694c072feab74969a54f2bbeec7a2d725d2bdcb6" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c35dfd12afb7828318348b8c408383cf5071a086c1d4ab1c0f9840ec92dbb922" dependencies = [ "libc", "signal-hook-registry", @@ -2800,9 +2862,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" dependencies = [ "proc-macro2", "quote", diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 93368e57ad..2a9749c7fd 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -57,8 +57,12 @@ lazy_static = "1.4.0" strip-ansi-escapes = "0.1.1" crossterm = "0.22.1" quick-xml = "0.22" +digest = "0.10.0" +md5 = { package = "md-5", version = "0.10.0" } +sha2 = "0.10.0" +base64 = "0.13.0" -num = {version="0.4.0", optional=true} +num = { version = "0.4.0", optional = true } [dependencies.polars] version = "0.18.0" diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 0ecbff13f9..d83e106f3c 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -9,10 +9,7 @@ pub fn create_default_context() -> EngineState { let mut working_set = StateWorkingSet::new(&engine_state); macro_rules! bind_command { - ( $command:expr ) => { - working_set.add_decl(Box::new($command)); - }; - ( $( $command:expr ),* ) => { + ( $( $command:expr ),* $(,)? ) => { $( working_set.add_decl(Box::new($command)); )* }; } @@ -175,7 +172,11 @@ pub fn create_default_context() -> EngineState { Where, WithEnv, Wrap, - Zip + Zip, + // Hash + Hash, + HashMd5::default(), + HashSha256::default(), ); #[cfg(feature = "plugin")] diff --git a/crates/nu-command/src/hash/command.rs b/crates/nu-command/src/hash/command.rs new file mode 100644 index 0000000000..a0859dde2d --- /dev/null +++ b/crates/nu-command/src/hash/command.rs @@ -0,0 +1,35 @@ +use nu_engine::get_full_help; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, IntoPipelineData, PipelineData, ShellError, Signature, Value}; + +#[derive(Clone)] +pub struct Hash; + +impl Command for Hash { + fn name(&self) -> &str { + "hash" + } + + fn signature(&self) -> Signature { + Signature::build("hash").category(Category::Hash) + } + + fn usage(&self) -> &str { + "Apply hash function." + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(Value::String { + val: get_full_help(&Self.signature(), &Self.examples(), engine_state), + span: call.head, + } + .into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/hash/generic_digest.rs b/crates/nu-command/src/hash/generic_digest.rs new file mode 100644 index 0000000000..ac7205f8ff --- /dev/null +++ b/crates/nu-command/src/hash/generic_digest.rs @@ -0,0 +1,108 @@ +use nu_engine::CallExt; +use nu_protocol::ast::{Call, CellPath}; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value}; +use std::marker::PhantomData; + +pub trait HashDigest: digest::Digest + Clone { + fn name() -> &'static str; + fn examples() -> Vec; +} + +#[derive(Clone)] +pub struct GenericDigest { + name: String, + usage: String, + phantom: PhantomData, +} + +impl Default for GenericDigest { + fn default() -> Self { + Self { + name: format!("hash {}", D::name()), + usage: format!("hash a value using the {} hash algorithm", D::name()), + phantom: PhantomData, + } + } +} + +impl Command for GenericDigest +where + D: HashDigest + Send + Sync + 'static, + digest::Output: core::fmt::LowerHex, +{ + fn name(&self) -> &str { + &self.name + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).rest( + "rest", + SyntaxShape::CellPath, + format!("optionally {} hash data by cell path", D::name()), + ) + } + + fn usage(&self) -> &str { + &self.usage + } + + fn examples(&self) -> Vec { + D::examples() + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let cell_paths: Vec = call.rest(engine_state, stack, 0)?; + + input.map( + move |v| { + if cell_paths.is_empty() { + action::(&v) + } else { + let mut v = v; + for path in &cell_paths { + let ret = v + .update_cell_path(&path.members, Box::new(move |old| action::(old))); + if let Err(error) = ret { + return Value::Error { error }; + } + } + v + } + }, + engine_state.ctrlc.clone(), + ) + } +} + +pub fn action(input: &Value) -> Value +where + D: HashDigest, + digest::Output: core::fmt::LowerHex, +{ + let (bytes, span) = match input { + Value::String { val, span } => (val.as_bytes(), *span), + Value::Binary { val, span } => (val.as_slice(), *span), + other => { + return Value::Error { + error: ShellError::UnsupportedInput( + format!( + "Type `{}` is not supported for {} hashing input", + other.get_type(), + D::name() + ), + input.span().unwrap_or_else(|_| Span::unknown()), + ), + }; + } + }; + + let val = format!("{:x}", D::digest(bytes)); + Value::String { val, span } +} diff --git a/crates/nu-command/src/hash/md5.rs b/crates/nu-command/src/hash/md5.rs new file mode 100644 index 0000000000..bfaa8f8e1e --- /dev/null +++ b/crates/nu-command/src/hash/md5.rs @@ -0,0 +1,68 @@ +use super::generic_digest::{GenericDigest, HashDigest}; +use ::md5::Md5; +use nu_protocol::{Example, Span, Value}; + +pub type HashMd5 = GenericDigest; + +impl HashDigest for Md5 { + fn name() -> &'static str { + "md5" + } + + fn examples() -> Vec { + vec![ + Example { + description: "md5 encode a string", + example: "echo 'abcdefghijklmnopqrstuvwxyz' | hash md5", + result: Some(Value::String { + val: "c3fcd3d76192e4007dfb496cca67e13b".to_owned(), + span: Span::unknown(), + }), + }, + Example { + description: "md5 encode a file", + example: "open ./nu_0_24_1_windows.zip | hash md5", + result: None, + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::hash::generic_digest; + + #[test] + fn test_examples() { + crate::test_examples(HashMd5::default()) + } + + #[test] + fn hash_string() { + let binary = Value::String { + val: "abcdefghijklmnopqrstuvwxyz".to_owned(), + span: Span::unknown(), + }; + let expected = Value::String { + val: "c3fcd3d76192e4007dfb496cca67e13b".to_owned(), + span: Span::unknown(), + }; + let actual = generic_digest::action::(&binary); + assert_eq!(actual, expected); + } + + #[test] + fn hash_bytes() { + let binary = Value::Binary { + val: vec![0xC0, 0xFF, 0xEE], + span: Span::unknown(), + }; + let expected = Value::String { + val: "5f80e231382769b0102b1164cf722d83".to_owned(), + span: Span::unknown(), + }; + let actual = generic_digest::action::(&binary); + assert_eq!(actual, expected); + } +} diff --git a/crates/nu-command/src/hash/mod.rs b/crates/nu-command/src/hash/mod.rs new file mode 100644 index 0000000000..e3aca085fa --- /dev/null +++ b/crates/nu-command/src/hash/mod.rs @@ -0,0 +1,8 @@ +mod command; +mod generic_digest; +mod md5; +mod sha256; + +pub use self::command::Hash; +pub use self::md5::HashMd5; +pub use self::sha256::HashSha256; diff --git a/crates/nu-command/src/hash/sha256.rs b/crates/nu-command/src/hash/sha256.rs new file mode 100644 index 0000000000..7a63e90d8e --- /dev/null +++ b/crates/nu-command/src/hash/sha256.rs @@ -0,0 +1,69 @@ +use super::generic_digest::{GenericDigest, HashDigest}; +use ::sha2::Sha256; +use nu_protocol::{Example, Span, Value}; + +pub type HashSha256 = GenericDigest; + +impl HashDigest for Sha256 { + fn name() -> &'static str { + "sha256" + } + + fn examples() -> Vec { + vec![ + Example { + description: "sha256 encode a string", + example: "echo 'abcdefghijklmnopqrstuvwxyz' | hash sha256", + result: Some(Value::String { + val: "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73" + .to_owned(), + span: Span::unknown(), + }), + }, + Example { + description: "sha256 encode a file", + example: "open ./nu_0_24_1_windows.zip | hash sha256", + result: None, + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::hash::generic_digest; + + #[test] + fn test_examples() { + crate::test_examples(HashSha256::default()) + } + + #[test] + fn hash_string() { + let binary = Value::String { + val: "abcdefghijklmnopqrstuvwxyz".to_owned(), + span: Span::unknown(), + }; + let expected = Value::String { + val: "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73".to_owned(), + span: Span::unknown(), + }; + let actual = generic_digest::action::(&binary); + assert_eq!(actual, expected); + } + + #[test] + fn hash_bytes() { + let binary = Value::Binary { + val: vec![0xC0, 0xFF, 0xEE], + span: Span::unknown(), + }; + let expected = Value::String { + val: "c47a10dc272b1221f0380a2ae0f7d7fa830b3e378f2f5309bbf13f61ad211913".to_owned(), + span: Span::unknown(), + }; + let actual = generic_digest::action::(&binary); + assert_eq!(actual, expected); + } +} diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 14a97fe741..dc547aa894 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -9,6 +9,7 @@ mod experimental; mod filesystem; mod filters; mod formats; +mod hash; mod math; mod network; mod platform; @@ -29,6 +30,7 @@ pub use experimental::*; pub use filesystem::*; pub use filters::*; pub use formats::*; +pub use hash::*; pub use math::*; pub use network::*; pub use platform::*; diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 4a33766b5f..5121718dbf 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -49,6 +49,7 @@ pub enum Category { Strings, System, Viewers, + Hash, Custom(String), } @@ -72,6 +73,7 @@ impl std::fmt::Display for Category { Category::Strings => "strings", Category::System => "system", Category::Viewers => "viewers", + Category::Hash => "hash", Category::Custom(name) => name, };