use super::*;
pub(crate) fn check_secret_version_idempotency(
versions: &BTreeMap<String, SecretVersion>,
version_id: &str,
existing_plaintext: Option<String>,
secret_string: &Option<String>,
secret_binary: &Option<Vec<u8>>,
) -> VersionIdempotency {
let Some(existing) = versions.get(version_id) else {
return VersionIdempotency::NotFound;
};
if &existing_plaintext == secret_string && &existing.secret_binary == secret_binary {
VersionIdempotency::Match
} else {
VersionIdempotency::Conflict
}
}
pub(crate) fn is_mutating_action(action: &str) -> bool {
matches!(
action,
"CreateSecret"
| "PutSecretValue"
| "UpdateSecret"
| "DeleteSecret"
| "RestoreSecret"
| "TagResource"
| "UntagResource"
| "RotateSecret"
| "CancelRotateSecret"
| "UpdateSecretVersionStage"
| "PutResourcePolicy"
| "DeleteResourcePolicy"
| "ReplicateSecretToRegions"
| "RemoveRegionsFromReplication"
| "StopReplicationToReplica"
)
}
pub(crate) fn require_secret_id(body: &Value) -> Result<String, AwsServiceError> {
let id = body["SecretId"].as_str().ok_or_else(|| {
AwsServiceError::aws_error(
StatusCode::BAD_REQUEST,
"InvalidParameterException",
"SecretId is required",
)
})?;
validate_string_length("secretId", id, 1, 2048)?;
Ok(id.to_string())
}
pub(crate) fn parse_tags(tags_val: &Value) -> Vec<(String, String)> {
tags_val
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|t| {
let key = t["Key"].as_str()?;
let value = t["Value"].as_str()?;
Some((key.to_string(), value.to_string()))
})
.collect()
})
.unwrap_or_default()
}
pub(crate) fn tags_to_json(tags: &[(String, String)]) -> Vec<Value> {
tags.iter()
.map(|(k, v)| json!({"Key": k, "Value": v}))
.collect()
}
pub(crate) fn split_words(text: &str) -> Vec<String> {
let mut all_words = Vec::new();
for space_part in text.split_whitespace() {
all_words.extend(split_words_no_space(space_part));
}
all_words
}
pub(crate) fn split_words_no_space(text: &str) -> Vec<String> {
let special_chars = ['/', '-', '_', '+', '=', '.', '@'];
if text.len() == 1 && special_chars.contains(&text.chars().next().unwrap_or(' ')) {
return vec![];
}
let present: Vec<char> = special_chars
.iter()
.filter(|&&c| text.contains(c))
.copied()
.collect();
if present.len() > 1 {
return vec![text.to_string()];
}
if present.len() == 1 {
let ch = present[0];
let parts: Vec<&str> = text.split(ch).filter(|s| !s.is_empty()).collect();
let mut result = Vec::new();
for part in parts {
result.extend(split_by_uppercase(part));
}
return result;
}
split_by_uppercase(text)
}
pub(crate) fn split_by_uppercase(text: &str) -> Vec<String> {
let chars: Vec<char> = text.chars().collect();
let mut words = Vec::new();
let mut last_end = 0;
let mut i = 0;
while i < chars.len() {
if !chars[i].is_ascii_lowercase()
&& i + 1 < chars.len()
&& chars[i + 1].is_ascii_lowercase()
{
if i > last_end {
let between: String = chars[last_end..i].iter().collect();
let trimmed = between.trim().to_string();
if !trimmed.is_empty() {
words.push(trimmed);
}
}
let start = i;
i += 2;
while i < chars.len() && chars[i].is_ascii_lowercase() {
i += 1;
}
let word: String = chars[start..i].iter().collect();
let trimmed = word.trim().to_string();
if !trimmed.is_empty() {
words.push(trimmed);
}
last_end = i;
} else {
i += 1;
}
}
if last_end < chars.len() {
let after: String = chars[last_end..].iter().collect();
let trimmed = after.trim().to_string();
if !trimmed.is_empty() {
words.push(trimmed);
}
}
words
}
pub(crate) fn match_pattern(
pattern: &str,
value: &str,
match_prefix: bool,
case_sensitive: bool,
) -> bool {
if match_prefix {
if case_sensitive {
value.starts_with(pattern)
} else {
value.to_lowercase().starts_with(&pattern.to_lowercase())
}
} else {
let mut pattern_words = split_words(pattern);
if pattern_words.is_empty() {
return false;
}
let mut value_words = split_words(value);
if !case_sensitive {
pattern_words = pattern_words.iter().map(|w| w.to_lowercase()).collect();
value_words = value_words.iter().map(|w| w.to_lowercase()).collect();
}
for pw in &pattern_words {
if !value_words.iter().any(|vw| vw.starts_with(pw.as_str())) {
return false;
}
}
true
}
}
pub(crate) fn matcher(
patterns: &[&str],
strings: &[&str],
match_prefix: bool,
case_sensitive: bool,
) -> bool {
for pattern in patterns.iter().filter(|p| p.starts_with('!')) {
let inner = &pattern[1..];
for s in strings {
if !match_pattern(inner, s, match_prefix, case_sensitive) {
return true;
}
}
}
for pattern in patterns.iter().filter(|p| !p.starts_with('!')) {
for s in strings {
if match_pattern(pattern, s, match_prefix, case_sensitive) {
return true;
}
}
}
false
}
pub(crate) fn filter_name(secret: &Secret, values: &[&str]) -> bool {
matcher(values, &[secret.name.as_str()], true, true)
}
pub(crate) fn filter_description(secret: &Secret, values: &[&str]) -> bool {
match secret.description.as_deref() {
Some(desc) if !desc.is_empty() => matcher(values, &[desc], false, false),
_ => false,
}
}
pub(crate) fn filter_tag_key(secret: &Secret, values: &[&str]) -> bool {
if secret.tags.is_empty() {
return false;
}
let keys: Vec<&str> = secret.tags.iter().map(|(k, _)| k.as_str()).collect();
matcher(values, &keys, true, true)
}
pub(crate) fn filter_tag_value(secret: &Secret, values: &[&str]) -> bool {
if secret.tags.is_empty() {
return false;
}
let vals: Vec<&str> = secret.tags.iter().map(|(_, v)| v.as_str()).collect();
matcher(values, &vals, true, true)
}
pub(crate) fn filter_all(secret: &Secret, values: &[&str]) -> bool {
let mut attributes: Vec<&str> = vec![secret.name.as_str()];
if let Some(ref desc) = secret.description {
if !desc.is_empty() {
attributes.push(desc.as_str());
}
}
for (k, v) in &secret.tags {
attributes.push(k.as_str());
attributes.push(v.as_str());
}
matcher(values, &attributes, false, false)
}
pub(crate) fn simple_random() -> usize {
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
let s = RandomState::new();
let mut hasher = s.build_hasher();
hasher.write_usize(0);
hasher.finish() as usize
}
pub(crate) fn base64_decode(input: &str) -> Option<Vec<u8>> {
let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut buf = Vec::new();
let mut bits: u32 = 0;
let mut count = 0;
for &b in input.as_bytes() {
if b == b'=' || b == b'\n' || b == b'\r' {
continue;
}
let val = table.iter().position(|&c| c == b)? as u32;
bits = (bits << 6) | val;
count += 1;
if count == 4 {
buf.push((bits >> 16) as u8);
buf.push((bits >> 8) as u8);
buf.push(bits as u8);
bits = 0;
count = 0;
}
}
match count {
2 => {
bits <<= 12;
buf.push((bits >> 16) as u8);
}
3 => {
bits <<= 6;
buf.push((bits >> 16) as u8);
buf.push((bits >> 8) as u8);
}
_ => {}
}
Some(buf)
}
pub(crate) fn base64_encode(input: &[u8]) -> String {
let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut result = String::new();
for chunk in input.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
let triple = (b0 << 16) | (b1 << 8) | b2;
result.push(table[((triple >> 18) & 0x3F) as usize] as char);
result.push(table[((triple >> 12) & 0x3F) as usize] as char);
if chunk.len() > 1 {
result.push(table[((triple >> 6) & 0x3F) as usize] as char);
} else {
result.push('=');
}
if chunk.len() > 2 {
result.push(table[(triple & 0x3F) as usize] as char);
} else {
result.push('=');
}
}
result
}