use md5::Md5;
use sha1::Sha1;
use sha2::{Digest, Sha224, Sha256, Sha384, Sha512};
use crate::{
error::{EvalError, EvalResult, InterpreterError},
value::Value,
};
const ALGOS: &[&str] = &["md5", "sha1", "sha224", "sha256", "sha384", "sha512"];
pub fn has_function(name: &str) -> bool {
name == "new" || ALGOS.contains(&name)
}
pub fn call(func: &str, args: &[Value]) -> EvalResult {
if func == "new" {
let Some(Value::String(algo)) = args.first() else {
return Err(
InterpreterError::TypeError("new() argument 'name' must be str".into()).into()
);
};
let algo = algo.to_string();
if !ALGOS.contains(&algo.as_str()) {
return Err(EvalError::Exception(crate::value::ExceptionValue::new(
"ValueError",
format!("unsupported hash type {algo}"),
)));
}
let input = match args.get(1) {
None => Vec::new(),
Some(_) => arg_bytes("new", &args[1..])?,
};
return Ok(Value::HashDigest { algo, bytes: input });
}
if !ALGOS.contains(&func) {
return Err(InterpreterError::AttributeError(format!(
"module 'hashlib' has no attribute '{func}'"
))
.into());
}
let input = match args.first() {
None => Vec::new(),
Some(_) => arg_bytes(func, args)?,
};
Ok(Value::HashDigest { algo: func.to_string(), bytes: input })
}
fn compute_digest(algo: &str, input: &[u8]) -> Vec<u8> {
match algo {
"md5" => Md5::digest(input).to_vec(),
"sha1" => Sha1::digest(input).to_vec(),
"sha224" => Sha224::digest(input).to_vec(),
"sha256" => Sha256::digest(input).to_vec(),
"sha384" => Sha384::digest(input).to_vec(),
_ => Sha512::digest(input).to_vec(),
}
}
fn sizes(algo: &str) -> (i64, i64) {
match algo {
"md5" => (16, 64),
"sha1" => (20, 64),
"sha224" => (28, 64),
"sha256" => (32, 64),
"sha384" => (48, 128),
_ => (64, 128),
}
}
pub fn dispatch_hash_method(
algo: &str,
input: &[u8],
method: &str,
_args: &[Value],
kwargs: &indexmap::IndexMap<String, Value>,
) -> EvalResult {
crate::eval::functions::reject_kwargs(method, kwargs)?;
match method {
"hexdigest" => Ok(Value::String(hex::encode(compute_digest(algo, input)).into())),
"digest" => Ok(Value::Bytes(compute_digest(algo, input))),
"name" => Ok(Value::String(algo.into())),
"digest_size" => Ok(Value::Int(sizes(algo).0)),
"block_size" => Ok(Value::Int(sizes(algo).1)),
_ => Err(InterpreterError::AttributeError(format!(
"'{algo} HASH' object has no attribute '{method}'"
))
.into()),
}
}
pub fn hash_attribute(algo: &str, _input: &[u8], name: &str) -> EvalResult {
match name {
"name" => Ok(Value::String(algo.into())),
"digest_size" => Ok(Value::Int(sizes(algo).0)),
"block_size" => Ok(Value::Int(sizes(algo).1)),
_ => Err(InterpreterError::AttributeError(format!(
"'{algo} HASH' object has no attribute '{name}'"
))
.into()),
}
}
fn arg_bytes(func: &str, args: &[Value]) -> Result<Vec<u8>, EvalError> {
match args.first() {
Some(Value::Bytes(b)) => Ok(b.clone()),
Some(Value::ByteArray(b)) => Ok(b.lock().clone()),
_ => Err(InterpreterError::TypeError(format!(
"{func}() argument must be a bytes-like object"
))
.into()),
}
}
pub struct HashlibModule;
#[async_trait::async_trait]
impl crate::eval::modules::Module for HashlibModule {
fn name(&self) -> &'static str {
"hashlib"
}
fn has_function(&self, name: &str) -> bool {
has_function(name)
}
async fn call(
&self,
_state: &mut crate::state::InterpreterState,
func: &str,
args: &[Value],
_kwargs: &indexmap::IndexMap<String, Value>,
_tools: &crate::tools::Tools,
) -> EvalResult {
call(func, args)
}
}