Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ flate2 = "1"
# Base64 encoding
base64 = "0.22"

# Checksums (md5sum, sha256sum, etc.)
md-5 = "0.10"
sha1 = "0.10"
sha2 = "0.10"

# CLI
clap = { version = "4", features = ["derive"] }

Expand Down
5 changes: 5 additions & 0 deletions crates/bashkit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ flate2 = { workspace = true }
# Base64 encoding (for base64 builtin and HTTP basic auth)
base64 = { workspace = true }

# Checksums (for md5sum, sha1sum, sha256sum builtins)
md-5 = { workspace = true }
sha1 = { workspace = true }
sha2 = { workspace = true }

# Logging/tracing (optional)
tracing = { workspace = true, optional = true }

Expand Down
161 changes: 161 additions & 0 deletions crates/bashkit/src/builtins/checksum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
//! Checksum builtins - md5sum, sha1sum, sha256sum

use async_trait::async_trait;
use md5::Md5;
use sha1::Sha1;
use sha2::{Digest, Sha256};

use super::{Builtin, Context};
use crate::error::Result;
use crate::interpreter::ExecResult;

/// md5sum builtin - compute MD5 message digest
pub struct Md5sum;

/// sha1sum builtin - compute SHA-1 message digest
pub struct Sha1sum;

/// sha256sum builtin - compute SHA-256 message digest
pub struct Sha256sum;

#[async_trait]
impl Builtin for Md5sum {
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
checksum_execute::<Md5>(&ctx, "md5sum").await
}
}

#[async_trait]
impl Builtin for Sha1sum {
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
checksum_execute::<Sha1>(&ctx, "sha1sum").await
}
}

#[async_trait]
impl Builtin for Sha256sum {
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
checksum_execute::<Sha256>(&ctx, "sha256sum").await
}
}

async fn checksum_execute<D: Digest>(ctx: &Context<'_>, cmd: &str) -> Result<ExecResult> {
let files: Vec<&String> = ctx.args.iter().filter(|a| !a.starts_with('-')).collect();

let mut output = String::new();

if files.is_empty() {
// Read from stdin
let input = ctx.stdin.unwrap_or("");
let hash = hex_digest::<D>(input.as_bytes());
output.push_str(&hash);
output.push_str(" -\n");
} else {
for file in &files {
let path = if file.starts_with('/') {
std::path::PathBuf::from(file)
} else {
ctx.cwd.join(file)
};

match ctx.fs.read_file(&path).await {
Ok(content) => {
let hash = hex_digest::<D>(&content);
output.push_str(&hash);
output.push_str(" ");
output.push_str(file);
output.push('\n');
}
Err(e) => {
return Ok(ExecResult::err(format!("{}: {}: {}\n", cmd, file, e), 1));
}
}
}
}

Ok(ExecResult::ok(output))
}

fn hex_digest<D: Digest>(data: &[u8]) -> String {
let result = D::digest(data);
result.iter().map(|b| format!("{:02x}", b)).collect()
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;

use crate::fs::InMemoryFs;

async fn run_checksum<B: Builtin>(
builtin: &B,
args: &[&str],
stdin: Option<&str>,
) -> ExecResult {
let fs = Arc::new(InMemoryFs::new());
let mut variables = HashMap::new();
let env = HashMap::new();
let mut cwd = PathBuf::from("/");

let args: Vec<String> = args.iter().map(|s| s.to_string()).collect();
let ctx = Context {
args: &args,
env: &env,
variables: &mut variables,
cwd: &mut cwd,
fs,
stdin,
#[cfg(feature = "http_client")]
http_client: None,
#[cfg(feature = "git")]
git_client: None,
};

builtin.execute(ctx).await.unwrap()
}

#[tokio::test]
async fn test_md5sum_stdin() {
let result = run_checksum(&Md5sum, &[], Some("hello\n")).await;
assert_eq!(result.exit_code, 0);
// md5("hello\n") = b1946ac92492d2347c6235b4d2611184
assert!(result
.stdout
.starts_with("b1946ac92492d2347c6235b4d2611184"));
assert!(result.stdout.contains(" -"));
}

#[tokio::test]
async fn test_sha256sum_stdin() {
let result = run_checksum(&Sha256sum, &[], Some("hello\n")).await;
assert_eq!(result.exit_code, 0);
// sha256("hello\n") = 5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
assert!(result
.stdout
.starts_with("5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03"));
}

#[tokio::test]
async fn test_sha1sum_stdin() {
let result = run_checksum(&Sha1sum, &[], Some("hello\n")).await;
assert_eq!(result.exit_code, 0);
// sha1("hello\n") = f572d396fae9206628714fb2ce00f72e94f2258f
assert!(result
.stdout
.starts_with("f572d396fae9206628714fb2ce00f72e94f2258f"));
}

#[tokio::test]
async fn test_md5sum_empty() {
let result = run_checksum(&Md5sum, &[], Some("")).await;
assert_eq!(result.exit_code, 0);
// md5("") = d41d8cd98f00b204e9800998ecf8427e
assert!(result
.stdout
.starts_with("d41d8cd98f00b204e9800998ecf8427e"));
}
}
2 changes: 2 additions & 0 deletions crates/bashkit/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod archive;
mod awk;
mod base64;
mod cat;
mod checksum;
mod column;
mod comm;
mod curl;
Expand Down Expand Up @@ -78,6 +79,7 @@ pub use archive::{Gunzip, Gzip, Tar};
pub use awk::Awk;
pub use base64::Base64;
pub use cat::Cat;
pub use checksum::{Md5sum, Sha1sum, Sha256sum};
pub use column::Column;
pub use comm::Comm;
pub use curl::{Curl, Wget};
Expand Down
3 changes: 3 additions & 0 deletions crates/bashkit/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ impl Interpreter {
builtins.insert("xxd".to_string(), Box::new(builtins::Xxd));
builtins.insert("hexdump".to_string(), Box::new(builtins::Hexdump));
builtins.insert("base64".to_string(), Box::new(builtins::Base64));
builtins.insert("md5sum".to_string(), Box::new(builtins::Md5sum));
builtins.insert("sha1sum".to_string(), Box::new(builtins::Sha1sum));
builtins.insert("sha256sum".to_string(), Box::new(builtins::Sha256sum));
builtins.insert("seq".to_string(), Box::new(builtins::Seq));
builtins.insert("tac".to_string(), Box::new(builtins::Tac));
builtins.insert("rev".to_string(), Box::new(builtins::Rev));
Expand Down
75 changes: 75 additions & 0 deletions crates/bashkit/tests/spec_cases/bash/checksum.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
### md5sum_stdin
# md5sum from stdin
echo -n "hello" | md5sum
### expect
5d41402abc4b2a76b9719d911017c592 -
### end

### sha1sum_stdin
# sha1sum from stdin
echo -n "hello" | sha1sum
### expect
aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d -
### end

### sha256sum_stdin
# sha256sum from stdin
echo -n "hello" | sha256sum
### expect
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 -
### end

### md5sum_empty
# md5sum of empty string
echo -n "" | md5sum
### expect
d41d8cd98f00b204e9800998ecf8427e -
### end

### sha256sum_newline
# sha256sum with trailing newline
echo "hello" | sha256sum
### expect
5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03 -
### end

### md5sum_file
# md5sum of a file
echo -n "test" > /tmp/checkfile.txt
md5sum /tmp/checkfile.txt
### expect
098f6bcd4621d373cade4e832627b4f6 /tmp/checkfile.txt
### end

### sha1sum_file
# sha1sum of a file
echo -n "test" > /tmp/checkfile.txt
sha1sum /tmp/checkfile.txt
### expect
a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 /tmp/checkfile.txt
### end

### sha256sum_file
# sha256sum of a file
echo -n "test" > /tmp/checkfile.txt
sha256sum /tmp/checkfile.txt
### expect
9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 /tmp/checkfile.txt
### end

### md5sum_multiple_files
# md5sum of multiple files
echo -n "aaa" > /tmp/a.txt
echo -n "bbb" > /tmp/b.txt
md5sum /tmp/a.txt /tmp/b.txt
### expect
47bce5c74f589f4867dbd57e9ca9f808 /tmp/a.txt
08f8e0260c64418510cefb2b06eee5cd /tmp/b.txt
### end

### checksum_missing_file
# checksum of non-existent file
md5sum /tmp/nonexistent.txt
### expect_exit_code
1
### end
32 changes: 32 additions & 0 deletions supply-chain/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ criteria = "safe-to-run"
version = "2.11.0"
criteria = "safe-to-deploy"

[[exemptions.block-buffer]]
version = "0.10.4"
criteria = "safe-to-deploy"

[[exemptions.bstr]]
version = "1.12.1"
criteria = "safe-to-deploy"
Expand Down Expand Up @@ -246,6 +250,10 @@ criteria = "safe-to-deploy"
version = "0.8.7"
criteria = "safe-to-deploy"

[[exemptions.cpufeatures]]
version = "0.2.17"
criteria = "safe-to-deploy"

[[exemptions.crc32fast]]
version = "1.5.0"
criteria = "safe-to-deploy"
Expand Down Expand Up @@ -278,6 +286,10 @@ criteria = "safe-to-run"
version = "0.2.4"
criteria = "safe-to-run"

[[exemptions.crypto-common]]
version = "0.1.7"
criteria = "safe-to-deploy"

[[exemptions.derive-where]]
version = "1.6.0"
criteria = "safe-to-deploy"
Expand All @@ -286,6 +298,10 @@ criteria = "safe-to-deploy"
version = "0.1.13"
criteria = "safe-to-run"

[[exemptions.digest]]
version = "0.10.7"
criteria = "safe-to-deploy"

[[exemptions.displaydoc]]
version = "0.2.5"
criteria = "safe-to-deploy"
Expand Down Expand Up @@ -398,6 +414,10 @@ criteria = "safe-to-deploy"
version = "0.3.32"
criteria = "safe-to-deploy"

[[exemptions.generic-array]]
version = "0.14.7"
criteria = "safe-to-deploy"

[[exemptions.get-size-derive2]]
version = "0.7.4"
criteria = "safe-to-deploy"
Expand Down Expand Up @@ -658,6 +678,10 @@ criteria = "safe-to-deploy"
version = "0.3.10"
criteria = "safe-to-deploy"

[[exemptions.md-5]]
version = "0.10.6"
criteria = "safe-to-deploy"

[[exemptions.memchr]]
version = "2.8.0"
criteria = "safe-to-deploy"
Expand Down Expand Up @@ -1090,6 +1114,14 @@ criteria = "safe-to-run"
version = "3.4.0"
criteria = "safe-to-run"

[[exemptions.sha1]]
version = "0.10.6"
criteria = "safe-to-deploy"

[[exemptions.sha2]]
version = "0.10.9"
criteria = "safe-to-deploy"

[[exemptions.shlex]]
version = "1.3.0"
criteria = "safe-to-deploy"
Expand Down
Loading