mirror of
https://github.com/nushell/nushell.git
synced 2024-12-25 00:19:39 +01:00
Deprecate hash base64
, extend decode
and add encode
commands (#5863)
* feat: deprecate `hash base64` command * feat: extend `decode` and `encode` command families This commit - Adds `encode` command family - Backports `hash base64` features to `encode base64` and `decode base64` subcommands. - Refactors code a bit and extends tests for encodings - `decode base64` returns a binary `Value` (that may be decoded into a string using `decode` command) * feat: add `--binary(-b)` flag to `decode base64` Default output type is now string, but binary can be requested using this new flag.
This commit is contained in:
parent
f2989bf704
commit
173d60d59d
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2621,6 +2621,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
|
"rstest",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -132,3 +132,4 @@ hamcrest2 = "0.3.0"
|
|||||||
dirs-next = "2.0.0"
|
dirs-next = "2.0.0"
|
||||||
quickcheck = "1.0.3"
|
quickcheck = "1.0.3"
|
||||||
quickcheck_macros = "1.0.0"
|
quickcheck_macros = "1.0.0"
|
||||||
|
rstest = "0.12.0"
|
||||||
|
@ -170,6 +170,9 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
|||||||
BuildString,
|
BuildString,
|
||||||
Char,
|
Char,
|
||||||
Decode,
|
Decode,
|
||||||
|
Encode,
|
||||||
|
DecodeBase64,
|
||||||
|
EncodeBase64,
|
||||||
DetectColumns,
|
DetectColumns,
|
||||||
Format,
|
Format,
|
||||||
FileSize,
|
FileSize,
|
||||||
@ -382,7 +385,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
|||||||
Hash,
|
Hash,
|
||||||
HashMd5::default(),
|
HashMd5::default(),
|
||||||
HashSha256::default(),
|
HashSha256::default(),
|
||||||
Base64,
|
HashBase64,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Experimental
|
// Experimental
|
||||||
|
34
crates/nu-command/src/deprecated/hash_base64.rs
Normal file
34
crates/nu-command/src/deprecated/hash_base64.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{Category, PipelineData, ShellError, Signature};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HashBase64;
|
||||||
|
|
||||||
|
impl Command for HashBase64 {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"hash base64"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name()).category(Category::Deprecated)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Deprecated command"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_: &EngineState,
|
||||||
|
_: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Err(nu_protocol::ShellError::DeprecatedCommand(
|
||||||
|
self.name().to_string(),
|
||||||
|
"encode base64".to_owned(),
|
||||||
|
call.head,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
mod hash_base64;
|
||||||
mod keep_;
|
mod keep_;
|
||||||
mod keep_until;
|
mod keep_until;
|
||||||
mod keep_while;
|
mod keep_while;
|
||||||
@ -10,6 +11,7 @@ mod str_find_replace;
|
|||||||
mod str_int;
|
mod str_int;
|
||||||
mod unalias;
|
mod unalias;
|
||||||
|
|
||||||
|
pub use hash_base64::HashBase64;
|
||||||
pub use keep_::KeepDeprecated;
|
pub use keep_::KeepDeprecated;
|
||||||
pub use keep_until::KeepUntilDeprecated;
|
pub use keep_until::KeepUntilDeprecated;
|
||||||
pub use keep_while::KeepWhileDeprecated;
|
pub use keep_while::KeepWhileDeprecated;
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
mod base64;
|
|
||||||
mod generic_digest;
|
mod generic_digest;
|
||||||
mod hash_;
|
mod hash_;
|
||||||
mod md5;
|
mod md5;
|
||||||
mod sha256;
|
mod sha256;
|
||||||
|
|
||||||
pub use self::base64::Base64;
|
|
||||||
pub use self::hash_::Hash;
|
pub use self::hash_::Hash;
|
||||||
pub use self::md5::HashMd5;
|
pub use self::md5::HashMd5;
|
||||||
pub use self::sha256::HashSha256;
|
pub use self::sha256::HashSha256;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
use base64::{decode_config, encode_config};
|
use base64::{decode_config, encode_config};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::{Call, CellPath};
|
use nu_protocol::ast::{Call, CellPath};
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{PipelineData, ShellError, Span, Spanned, Value};
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
|
||||||
};
|
pub const CHARACTER_SET_DESC: &str = "specify the character rules for encoding the input.\n\
|
||||||
|
\tValid values are 'standard', 'standard-no-padding', 'url-safe', 'url-safe-no-padding',\
|
||||||
|
'binhex', 'bcrypt', 'crypt'";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Base64Config {
|
pub struct Base64Config {
|
||||||
@ -18,106 +20,19 @@ pub enum ActionType {
|
|||||||
Decode,
|
Decode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
pub fn operate(
|
||||||
pub struct Base64;
|
action_type: ActionType,
|
||||||
|
|
||||||
impl Command for Base64 {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"hash base64"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("hash base64")
|
|
||||||
.named(
|
|
||||||
"character-set",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"specify the character rules for encoding the input.\n\
|
|
||||||
\tValid values are 'standard', 'standard-no-padding', 'url-safe', 'url-safe-no-padding',\
|
|
||||||
'binhex', 'bcrypt', 'crypt'",
|
|
||||||
Some('c'),
|
|
||||||
)
|
|
||||||
.switch(
|
|
||||||
"encode",
|
|
||||||
"encode the input as base64. This is the default behavior if not specified.",
|
|
||||||
Some('e')
|
|
||||||
)
|
|
||||||
.switch(
|
|
||||||
"decode",
|
|
||||||
"decode the input from base64",
|
|
||||||
Some('d'))
|
|
||||||
.rest(
|
|
||||||
"rest",
|
|
||||||
SyntaxShape::CellPath,
|
|
||||||
"optionally base64 encode / decode data by column paths",
|
|
||||||
)
|
|
||||||
.category(Category::Hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"base64 encode or decode a value"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "Base64 encode a string with default settings",
|
|
||||||
example: "echo 'username:password' | hash base64",
|
|
||||||
result: Some(Value::string("dXNlcm5hbWU6cGFzc3dvcmQ=", Span::test_data())),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Base64 encode a string with the binhex character set",
|
|
||||||
example: "echo 'username:password' | hash base64 --character-set binhex --encode",
|
|
||||||
result: Some(Value::string("F@0NEPjJD97kE'&bEhFZEP3", Span::test_data())),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Base64 decode a value",
|
|
||||||
example: "echo 'dXNlcm5hbWU6cGFzc3dvcmQ=' | hash base64 --decode",
|
|
||||||
result: Some(Value::string("username:password", Span::test_data())),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
operate(engine_state, stack, call, input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operate(
|
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let encode = call.has_flag("encode");
|
|
||||||
let decode = call.has_flag("decode");
|
|
||||||
let character_set: Option<Spanned<String>> =
|
let character_set: Option<Spanned<String>> =
|
||||||
call.get_flag(engine_state, stack, "character-set")?;
|
call.get_flag(engine_state, stack, "character-set")?;
|
||||||
|
let binary = call.has_flag("binary");
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
if encode && decode {
|
|
||||||
return Err(ShellError::GenericError(
|
|
||||||
"only one of --decode and --encode flags can be used".to_string(),
|
|
||||||
"conflicting flags".to_string(),
|
|
||||||
Some(head),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default the action to be encoding if no flags are specified.
|
|
||||||
let action_type = if decode {
|
|
||||||
ActionType::Decode
|
|
||||||
} else {
|
|
||||||
ActionType::Encode
|
|
||||||
};
|
|
||||||
|
|
||||||
// Default the character set to standard if the argument is not specified.
|
// Default the character set to standard if the argument is not specified.
|
||||||
let character_set = match character_set {
|
let character_set = match character_set {
|
||||||
Some(inner_tag) => inner_tag,
|
Some(inner_tag) => inner_tag,
|
||||||
@ -135,7 +50,7 @@ fn operate(
|
|||||||
input.map(
|
input.map(
|
||||||
move |v| {
|
move |v| {
|
||||||
if column_paths.is_empty() {
|
if column_paths.is_empty() {
|
||||||
match action(&v, &encoding_config, &head) {
|
match action(&v, binary, &encoding_config, &head) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => Value::Error { error: e },
|
Err(e) => Value::Error { error: e },
|
||||||
}
|
}
|
||||||
@ -147,7 +62,7 @@ fn operate(
|
|||||||
|
|
||||||
let r = ret.update_cell_path(
|
let r = ret.update_cell_path(
|
||||||
&path.members,
|
&path.members,
|
||||||
Box::new(move |old| match action(old, &config, &head) {
|
Box::new(move |old| match action(old, binary, &config, &head) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => Value::Error { error: e },
|
Err(e) => Value::Error { error: e },
|
||||||
}),
|
}),
|
||||||
@ -166,6 +81,8 @@ fn operate(
|
|||||||
|
|
||||||
fn action(
|
fn action(
|
||||||
input: &Value,
|
input: &Value,
|
||||||
|
// only used for `decode` action
|
||||||
|
output_binary: bool,
|
||||||
base64_config: &Base64Config,
|
base64_config: &Base64Config,
|
||||||
command_span: &Span,
|
command_span: &Span,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
@ -200,7 +117,10 @@ fn action(
|
|||||||
*command_span,
|
*command_span,
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Value::String { val, .. } => {
|
Value::String {
|
||||||
|
val,
|
||||||
|
span: value_span,
|
||||||
|
} => {
|
||||||
match base64_config.action_type {
|
match base64_config.action_type {
|
||||||
ActionType::Encode => Ok(Value::string(
|
ActionType::Encode => Ok(Value::string(
|
||||||
encode_config(&val, base64_config_enum),
|
encode_config(&val, base64_config_enum),
|
||||||
@ -211,13 +131,26 @@ fn action(
|
|||||||
// for decode, input val may contains invalid new line character, which is ok to omitted them by default.
|
// for decode, input val may contains invalid new line character, which is ok to omitted them by default.
|
||||||
let val = val.clone();
|
let val = val.clone();
|
||||||
let val = val.replace("\r\n", "").replace('\n', "");
|
let val = val.replace("\r\n", "").replace('\n', "");
|
||||||
let decode_result = decode_config(&val, base64_config_enum);
|
|
||||||
|
|
||||||
match decode_result {
|
match decode_config(&val, base64_config_enum) {
|
||||||
Ok(decoded_value) => Ok(Value::string(
|
Ok(decoded_value) => {
|
||||||
std::string::String::from_utf8_lossy(&decoded_value),
|
if output_binary {
|
||||||
*command_span,
|
Ok(Value::binary(decoded_value, *command_span))
|
||||||
|
} else {
|
||||||
|
match String::from_utf8(decoded_value) {
|
||||||
|
Ok(string_value) => {
|
||||||
|
Ok(Value::string(string_value, *command_span))
|
||||||
|
}
|
||||||
|
Err(e) => Err(ShellError::GenericError(
|
||||||
|
"base64 payload isn't a valid utf-8 sequence".to_owned(),
|
||||||
|
e.to_string(),
|
||||||
|
Some(*value_span),
|
||||||
|
Some("consider using the `--binary` flag".to_owned()),
|
||||||
|
Vec::new(),
|
||||||
)),
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(_) => Err(ShellError::GenericError(
|
Err(_) => Err(ShellError::GenericError(
|
||||||
"value could not be base64 decoded".to_string(),
|
"value could not be base64 decoded".to_string(),
|
||||||
format!(
|
format!(
|
||||||
@ -241,22 +174,17 @@ fn action(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{action, ActionType, Base64, Base64Config};
|
use super::{action, ActionType, Base64Config};
|
||||||
use nu_protocol::{Span, Spanned, Value};
|
use nu_protocol::{Span, Spanned, Value};
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
use crate::test_examples;
|
|
||||||
test_examples(Base64 {})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn base64_encode_standard() {
|
fn base64_encode_standard() {
|
||||||
let word = Value::string("username:password", Span::test_data());
|
let word = Value::string("Some Data Padding", Span::test_data());
|
||||||
let expected = Value::string("dXNlcm5hbWU6cGFzc3dvcmQ=", Span::test_data());
|
let expected = Value::string("U29tZSBEYXRhIFBhZGRpbmc=", Span::test_data());
|
||||||
|
|
||||||
let actual = action(
|
let actual = action(
|
||||||
&word,
|
&word,
|
||||||
|
true,
|
||||||
&Base64Config {
|
&Base64Config {
|
||||||
character_set: Spanned {
|
character_set: Spanned {
|
||||||
item: "standard".to_string(),
|
item: "standard".to_string(),
|
||||||
@ -272,11 +200,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn base64_encode_standard_no_padding() {
|
fn base64_encode_standard_no_padding() {
|
||||||
let word = Value::string("username:password", Span::test_data());
|
let word = Value::string("Some Data Padding", Span::test_data());
|
||||||
let expected = Value::string("dXNlcm5hbWU6cGFzc3dvcmQ", Span::test_data());
|
let expected = Value::string("U29tZSBEYXRhIFBhZGRpbmc", Span::test_data());
|
||||||
|
|
||||||
let actual = action(
|
let actual = action(
|
||||||
&word,
|
&word,
|
||||||
|
true,
|
||||||
&Base64Config {
|
&Base64Config {
|
||||||
character_set: Spanned {
|
character_set: Spanned {
|
||||||
item: "standard-no-padding".to_string(),
|
item: "standard-no-padding".to_string(),
|
||||||
@ -297,6 +226,7 @@ mod tests {
|
|||||||
|
|
||||||
let actual = action(
|
let actual = action(
|
||||||
&word,
|
&word,
|
||||||
|
true,
|
||||||
&Base64Config {
|
&Base64Config {
|
||||||
character_set: Spanned {
|
character_set: Spanned {
|
||||||
item: "url-safe".to_string(),
|
item: "url-safe".to_string(),
|
||||||
@ -313,10 +243,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn base64_decode_binhex() {
|
fn base64_decode_binhex() {
|
||||||
let word = Value::string("A5\"KC9jRB@IIF'8bF!", Span::test_data());
|
let word = Value::string("A5\"KC9jRB@IIF'8bF!", Span::test_data());
|
||||||
let expected = Value::string("a binhex test", Span::test_data());
|
let expected = Value::binary(b"a binhex test".as_slice(), Span::test_data());
|
||||||
|
|
||||||
let actual = action(
|
let actual = action(
|
||||||
&word,
|
&word,
|
||||||
|
true,
|
||||||
&Base64Config {
|
&Base64Config {
|
||||||
character_set: Spanned {
|
character_set: Spanned {
|
||||||
item: "binhex".to_string(),
|
item: "binhex".to_string(),
|
||||||
@ -333,10 +264,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn base64_decode_binhex_with_new_line_input() {
|
fn base64_decode_binhex_with_new_line_input() {
|
||||||
let word = Value::string("A5\"KC9jRB\n@IIF'8bF!", Span::test_data());
|
let word = Value::string("A5\"KC9jRB\n@IIF'8bF!", Span::test_data());
|
||||||
let expected = Value::string("a binhex test", Span::test_data());
|
let expected = Value::binary(b"a binhex test".as_slice(), Span::test_data());
|
||||||
|
|
||||||
let actual = action(
|
let actual = action(
|
||||||
&word,
|
&word,
|
||||||
|
true,
|
||||||
&Base64Config {
|
&Base64Config {
|
||||||
character_set: Spanned {
|
character_set: Spanned {
|
||||||
item: "binhex".to_string(),
|
item: "binhex".to_string(),
|
||||||
@ -360,6 +292,7 @@ mod tests {
|
|||||||
|
|
||||||
let actual = action(
|
let actual = action(
|
||||||
&word,
|
&word,
|
||||||
|
true,
|
||||||
&Base64Config {
|
&Base64Config {
|
||||||
character_set: Spanned {
|
character_set: Spanned {
|
||||||
item: "standard".to_string(),
|
item: "standard".to_string(),
|
||||||
@ -382,6 +315,7 @@ mod tests {
|
|||||||
|
|
||||||
let actual = action(
|
let actual = action(
|
||||||
&word,
|
&word,
|
||||||
|
true,
|
||||||
&Base64Config {
|
&Base64Config {
|
||||||
character_set: Spanned {
|
character_set: Spanned {
|
||||||
item: "standard".to_string(),
|
item: "standard".to_string(),
|
@ -1,10 +1,9 @@
|
|||||||
use encoding_rs::Encoding;
|
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
Value,
|
SyntaxShape, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -38,11 +37,21 @@ documentation link at https://docs.rs/encoding_rs/0.8.28/encoding_rs/#statics"#
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
|
Example {
|
||||||
description: "Decode the output of an external command",
|
description: "Decode the output of an external command",
|
||||||
example: "cat myfile.q | decode utf-8",
|
example: "^cat myfile.q | decode utf-8",
|
||||||
result: None,
|
result: None,
|
||||||
}]
|
},
|
||||||
|
Example {
|
||||||
|
description: "Decode an UTF-16 string into nushell UTF-8 string",
|
||||||
|
example: r#"0x[00 53 00 6F 00 6D 00 65 00 20 00 44 00 61 00 74 00 61] | decode utf-16be"#,
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "Some Data".to_owned(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -62,51 +71,10 @@ documentation link at https://docs.rs/encoding_rs/0.8.28/encoding_rs/#statics"#
|
|||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let bytes: Vec<u8> = stream.into_bytes()?.item;
|
let bytes: Vec<u8> = stream.into_bytes()?.item;
|
||||||
|
super::encoding::decode(head, encoding, &bytes).map(|val| val.into_pipeline_data())
|
||||||
let encoding = match Encoding::for_label(encoding.item.as_bytes()) {
|
|
||||||
None => Err(ShellError::GenericError(
|
|
||||||
format!(
|
|
||||||
r#"{} is not a valid encoding, refer to https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics for a valid list of encodings"#,
|
|
||||||
encoding.item
|
|
||||||
),
|
|
||||||
"invalid encoding".into(),
|
|
||||||
Some(encoding.span),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
Some(encoding) => Ok(encoding),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let result = encoding.decode(&bytes);
|
|
||||||
|
|
||||||
Ok(Value::String {
|
|
||||||
val: result.0.to_string(),
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
.into_pipeline_data())
|
|
||||||
}
|
}
|
||||||
PipelineData::Value(Value::Binary { val: bytes, .. }, ..) => {
|
PipelineData::Value(Value::Binary { val: bytes, .. }, ..) => {
|
||||||
let encoding = match Encoding::for_label(encoding.item.as_bytes()) {
|
super::encoding::decode(head, encoding, &bytes).map(|val| val.into_pipeline_data())
|
||||||
None => Err(ShellError::GenericError(
|
|
||||||
format!(
|
|
||||||
r#"{} is not a valid encoding, refer to https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics for a valid list of encodings"#,
|
|
||||||
encoding.item
|
|
||||||
),
|
|
||||||
"invalid encoding".into(),
|
|
||||||
Some(encoding.span),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
Some(encoding) => Ok(encoding),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let result = encoding.decode(&bytes);
|
|
||||||
|
|
||||||
Ok(Value::String {
|
|
||||||
val: result.0.to_string(),
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
.into_pipeline_data())
|
|
||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedInput(
|
_ => Err(ShellError::UnsupportedInput(
|
||||||
"non-binary input".into(),
|
"non-binary input".into(),
|
90
crates/nu-command/src/strings/encode_decode/decode_base64.rs
Normal file
90
crates/nu-command/src/strings/encode_decode/decode_base64.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
use super::base64::{operate, ActionType, CHARACTER_SET_DESC};
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DecodeBase64;
|
||||||
|
|
||||||
|
impl Command for DecodeBase64 {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"decode base64"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("decode base64")
|
||||||
|
.named(
|
||||||
|
"character-set",
|
||||||
|
SyntaxShape::String,
|
||||||
|
CHARACTER_SET_DESC,
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"binary",
|
||||||
|
"do not decode payload as UTF-8 and output binary",
|
||||||
|
Some('b'),
|
||||||
|
)
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"optionally base64 decode data by column paths",
|
||||||
|
)
|
||||||
|
.category(Category::Hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"base64 decode a value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"Will attempt to decode binary payload as an UTF-8 string by default. Use the `--binary(-b)` argument to force binary output."#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Base64 decode a value and output as UTF-8 string",
|
||||||
|
example: "echo 'U29tZSBEYXRh' | decode base64",
|
||||||
|
result: Some(Value::string("Some Data", Span::test_data())),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Base64 decode a value and output as binary",
|
||||||
|
example: "echo 'U29tZSBEYXRh' | decode base64 --binary",
|
||||||
|
result: Some(Value::binary(
|
||||||
|
[0x53, 0x6f, 0x6d, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61],
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
operate(ActionType::Decode, engine_state, stack, call, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
crate::test_examples(DecodeBase64)
|
||||||
|
}
|
||||||
|
}
|
95
crates/nu-command/src/strings/encode_decode/encode.rs
Normal file
95
crates/nu-command/src/strings/encode_decode/encode.rs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
|
SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Encode;
|
||||||
|
|
||||||
|
impl Command for Encode {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"encode"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Encode an UTF-8 string into other kind of representations."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["text", "encoding", "decoding"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("encode")
|
||||||
|
.required("encoding", SyntaxShape::String, "the text encoding to use")
|
||||||
|
.category(Category::Strings)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"Multiple encodings are supported, here is an example of a few:
|
||||||
|
big5, euc-jp, euc-kr, gbk, iso-8859-1, cp1252, latin5
|
||||||
|
|
||||||
|
Note that since the Encoding Standard doesn't specify encoders for utf-16le and utf-16be, these are not yet supported.
|
||||||
|
|
||||||
|
For a more complete list of encodings please refer to the encoding_rs
|
||||||
|
documentation link at https://docs.rs/encoding_rs/0.8.28/encoding_rs/#statics"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Encode an UTF-8 string into Shift-JIS",
|
||||||
|
example: r#"echo "負けると知って戦うのが、遥かに美しいのだ" | encode shift-jis"#,
|
||||||
|
result: Some(Value::Binary {
|
||||||
|
val: vec![
|
||||||
|
0x95, 0x89, 0x82, 0xaf, 0x82, 0xe9, 0x82, 0xc6, 0x92, 0x6d, 0x82, 0xc1, 0x82,
|
||||||
|
0xc4, 0x90, 0xed, 0x82, 0xa4, 0x82, 0xcc, 0x82, 0xaa, 0x81, 0x41, 0x97, 0x79,
|
||||||
|
0x82, 0xa9, 0x82, 0xc9, 0x94, 0xfc, 0x82, 0xb5, 0x82, 0xa2, 0x82, 0xcc, 0x82,
|
||||||
|
0xbe,
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let encoding: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
match input {
|
||||||
|
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::new(call.head)),
|
||||||
|
PipelineData::ExternalStream {
|
||||||
|
stdout: Some(stream),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let s = stream.into_string()?.item;
|
||||||
|
super::encoding::encode(head, encoding, &s).map(|val| val.into_pipeline_data())
|
||||||
|
}
|
||||||
|
PipelineData::Value(Value::String { val: s, .. }, ..) => {
|
||||||
|
super::encoding::encode(head, encoding, &s).map(|val| val.into_pipeline_data())
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::UnsupportedInput(
|
||||||
|
"non-string input".into(),
|
||||||
|
head,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
crate::test_examples(Encode)
|
||||||
|
}
|
||||||
|
}
|
78
crates/nu-command/src/strings/encode_decode/encode_base64.rs
Normal file
78
crates/nu-command/src/strings/encode_decode/encode_base64.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
use super::base64::{operate, ActionType, CHARACTER_SET_DESC};
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct EncodeBase64;
|
||||||
|
|
||||||
|
impl Command for EncodeBase64 {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"encode base64"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("encode base64")
|
||||||
|
.named(
|
||||||
|
"character-set",
|
||||||
|
SyntaxShape::String,
|
||||||
|
CHARACTER_SET_DESC,
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"optionally base64 encode data by column paths",
|
||||||
|
)
|
||||||
|
.category(Category::Hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"base64 encode a value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Base64 encode a string with default settings",
|
||||||
|
example: "echo 'Some Data' | encode base64",
|
||||||
|
result: Some(Value::string("U29tZSBEYXRh", Span::test_data())),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Base64 encode a string with the binhex character set",
|
||||||
|
example: "echo 'Some Data' | encode base64 --character-set binhex",
|
||||||
|
result: Some(Value::string(r#"7epXB5"%A@4J"#, Span::test_data())),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
operate(ActionType::Encode, engine_state, stack, call, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
crate::test_examples(EncodeBase64)
|
||||||
|
}
|
||||||
|
}
|
67
crates/nu-command/src/strings/encode_decode/encoding.rs
Normal file
67
crates/nu-command/src/strings/encode_decode/encoding.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
use encoding_rs::Encoding;
|
||||||
|
use nu_protocol::{ShellError, Span, Spanned, Value};
|
||||||
|
|
||||||
|
pub fn decode(head: Span, encoding: Spanned<String>, bytes: &[u8]) -> Result<Value, ShellError> {
|
||||||
|
let encoding = parse_encoding(encoding.span, &encoding.item)?;
|
||||||
|
let (result, ..) = encoding.decode(bytes);
|
||||||
|
Ok(Value::String {
|
||||||
|
val: result.into_owned(),
|
||||||
|
span: head,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode(head: Span, encoding: Spanned<String>, s: &str) -> Result<Value, ShellError> {
|
||||||
|
let encoding = parse_encoding(encoding.span, &encoding.item)?;
|
||||||
|
let (result, ..) = encoding.encode(s);
|
||||||
|
Ok(Value::Binary {
|
||||||
|
val: result.into_owned(),
|
||||||
|
span: head,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_encoding(span: Span, label: &str) -> Result<&'static Encoding, ShellError> {
|
||||||
|
match Encoding::for_label_no_replacement(label.as_bytes()) {
|
||||||
|
None => Err(ShellError::GenericError(
|
||||||
|
format!(
|
||||||
|
r#"{} is not a valid encoding, refer to https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics for a valid list of encodings"#,
|
||||||
|
label
|
||||||
|
),
|
||||||
|
"invalid encoding".into(),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
)),
|
||||||
|
Some(encoding) => Ok(encoding),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case::big5("big5", "簡体字")]
|
||||||
|
#[case::shift_jis("shift-jis", "何だと?……無駄な努力だ?……百も承知だ!")]
|
||||||
|
#[case::euc_jp("euc-jp", "だがな、勝つ望みがある時ばかり、戦うのとは訳が違うぞ!")]
|
||||||
|
#[case::euc_kr("euc-kr", "가셨어요?")]
|
||||||
|
#[case::gbk("gbk", "簡体字")]
|
||||||
|
#[case::iso_8859_1("iso-8859-1", "Some ¼½¿ Data µ¶·¸¹º")]
|
||||||
|
#[case::cp1252("cp1252", "Some ¼½¿ Data")]
|
||||||
|
#[case::latin5("latin5", "Some ¼½¿ Data µ¶·¸¹º")]
|
||||||
|
fn smoke(#[case] encoding: String, #[case] expected: &str) {
|
||||||
|
let test_span = Span::test_data();
|
||||||
|
let encoding = Spanned {
|
||||||
|
item: encoding,
|
||||||
|
span: test_span,
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded = encode(test_span, encoding.clone(), expected).unwrap();
|
||||||
|
let encoded = encoded.as_binary().unwrap();
|
||||||
|
|
||||||
|
let decoded = decode(test_span, encoding, encoded).unwrap();
|
||||||
|
let decoded = decoded.as_string().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(decoded, expected);
|
||||||
|
}
|
||||||
|
}
|
11
crates/nu-command/src/strings/encode_decode/mod.rs
Normal file
11
crates/nu-command/src/strings/encode_decode/mod.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
mod base64;
|
||||||
|
mod decode;
|
||||||
|
mod decode_base64;
|
||||||
|
mod encode;
|
||||||
|
mod encode_base64;
|
||||||
|
mod encoding;
|
||||||
|
|
||||||
|
pub use self::decode::Decode;
|
||||||
|
pub use self::decode_base64::DecodeBase64;
|
||||||
|
pub use self::encode::Encode;
|
||||||
|
pub use self::encode_base64::EncodeBase64;
|
@ -1,7 +1,7 @@
|
|||||||
mod build_string;
|
mod build_string;
|
||||||
mod char_;
|
mod char_;
|
||||||
mod decode;
|
|
||||||
mod detect_columns;
|
mod detect_columns;
|
||||||
|
mod encode_decode;
|
||||||
mod format;
|
mod format;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod size;
|
mod size;
|
||||||
@ -10,8 +10,8 @@ mod str_;
|
|||||||
|
|
||||||
pub use build_string::BuildString;
|
pub use build_string::BuildString;
|
||||||
pub use char_::Char;
|
pub use char_::Char;
|
||||||
pub use decode::*;
|
|
||||||
pub use detect_columns::*;
|
pub use detect_columns::*;
|
||||||
|
pub use encode_decode::*;
|
||||||
pub use format::*;
|
pub use format::*;
|
||||||
pub use parse::*;
|
pub use parse::*;
|
||||||
pub use size::Size;
|
pub use size::Size;
|
||||||
|
@ -5,7 +5,7 @@ fn base64_defaults_to_encoding_with_standard_character_type() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".", pipeline(
|
cwd: ".", pipeline(
|
||||||
r#"
|
r#"
|
||||||
echo 'username:password' | hash base64
|
echo 'username:password' | encode base64
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -18,7 +18,7 @@ fn base64_encode_characterset_binhex() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".", pipeline(
|
cwd: ".", pipeline(
|
||||||
r#"
|
r#"
|
||||||
echo 'username:password' | hash base64 --character-set binhex --encode
|
echo 'username:password' | encode base64 --character-set binhex
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -31,7 +31,7 @@ fn error_when_invalid_character_set_given() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".", pipeline(
|
cwd: ".", pipeline(
|
||||||
r#"
|
r#"
|
||||||
echo 'username:password' | hash base64 --character-set 'this is invalid' --encode
|
echo 'username:password' | encode base64 --character-set 'this is invalid'
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -46,7 +46,7 @@ fn base64_decode_characterset_binhex() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".", pipeline(
|
cwd: ".", pipeline(
|
||||||
r#"
|
r#"
|
||||||
echo "F@0NEPjJD97kE'&bEhFZEP3" | hash base64 --character-set binhex --decode
|
echo "F@0NEPjJD97kE'&bEhFZEP3" | decode base64 --character-set binhex --binary | decode utf-8
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -59,7 +59,7 @@ fn error_invalid_decode_value() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".", pipeline(
|
cwd: ".", pipeline(
|
||||||
r#"
|
r#"
|
||||||
echo "this should not be a valid encoded value" | hash base64 --character-set url-safe --decode
|
echo "this should not be a valid encoded value" | decode base64 --character-set url-safe
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -69,21 +69,6 @@ fn error_invalid_decode_value() {
|
|||||||
.contains("invalid base64 input for character set url-safe"));
|
.contains("invalid base64 input for character set url-safe"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_use_both_flags() {
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: ".", pipeline(
|
|
||||||
r#"
|
|
||||||
echo 'username:password' | hash base64 --encode --decode
|
|
||||||
"#
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(actual
|
|
||||||
.err
|
|
||||||
.contains("only one of --decode and --encode flags can be used"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn md5_works_with_file() {
|
fn md5_works_with_file() {
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
|
@ -1035,6 +1035,13 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn binary(val: impl Into<Vec<u8>>, span: Span) -> Value {
|
||||||
|
Value::Binary {
|
||||||
|
val: val.into(),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn int(val: i64, span: Span) -> Value {
|
pub fn int(val: i64, span: Span) -> Value {
|
||||||
Value::Int { val, span }
|
Value::Int { val, span }
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user