pub(crate) fn looks_like_pure_identifier(credential: &str) -> bool {
let bytes = credential.as_bytes();
if bytes.is_empty() {
return false;
}
let mut underscore_count = 0usize;
let mut hyphen_count = 0usize;
let mut has_digit = false;
let mut has_upper = false;
let mut has_lower = false;
let mut alpha_count = 0usize;
for &b in bytes {
if b == b'_' {
underscore_count += 1;
} else if b == b'-' {
hyphen_count += 1;
} else if b.is_ascii_digit() {
has_digit = true;
} else if b.is_ascii_uppercase() {
has_upper = true;
alpha_count += 1;
} else if b.is_ascii_lowercase() {
has_lower = true;
alpha_count += 1;
} else {
return false;
}
}
if has_digit {
return false;
}
if underscore_count >= 2 {
return true;
}
if (underscore_count + hyphen_count) <= 1
&& (8..=40).contains(&alpha_count)
&& (has_upper || has_lower)
{
return true;
}
false
}
pub(crate) fn looks_like_word_separated_identifier(value: &str) -> bool {
if value.len() < 8 || value.len() > 50 {
return false;
}
if !value
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-')
{
return false;
}
let sep_count = value.bytes().filter(|&b| b == b'_' || b == b'-').count();
if sep_count == 0 {
return false;
}
let words: Vec<&str> = value.split(['_', '-']).collect();
if words.iter().any(|w| w.is_empty()) {
return false;
}
if !words
.iter()
.all(|w| w.bytes().any(|b| b.is_ascii_alphabetic()))
{
return false;
}
let max_word_len = words.iter().map(|w| w.len()).max().unwrap_or(0);
if max_word_len > 10 {
return false;
}
true
}
pub(crate) fn looks_like_scheme_prefixed_uri(value: &str) -> bool {
let bytes = value.as_bytes();
if bytes.len() < 6 {
return false;
}
let Some(colon_idx) = bytes.iter().position(|&b| b == b':') else {
return false;
};
if !(3..=15).contains(&colon_idx) {
return false;
}
let scheme = &bytes[..colon_idx];
if !scheme.iter().all(|&b| b.is_ascii_alphabetic() || b == b'-') {
return false;
}
if !scheme.iter().any(|b| b.is_ascii_alphabetic()) {
return false;
}
let after = &bytes[colon_idx + 1..];
if after.starts_with(b"//") {
return true;
}
if after.contains(&b':') {
return true;
}
if scheme.contains(&b'-') {
return true;
}
let scheme_str = std::str::from_utf8(scheme).unwrap_or("");
if matches!(
scheme_str,
"sha256" | "sha512" | "sha1" | "md5" | "blake3" | "blake2"
) {
return true;
}
if bytes.len() <= 20
&& after.iter().all(|&b| b.is_ascii_alphabetic())
&& !after.is_empty()
&& after.len() <= 10
{
return true;
}
false
}
pub(crate) fn looks_like_url_or_path_segment(value: &str) -> bool {
if !value.contains('/') {
return false;
}
let segments: Vec<&str> = value.split('/').filter(|s| !s.is_empty()).collect();
if segments.len() < 2 {
return false;
}
segments.iter().all(|s| {
s.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b'.')
&& s.bytes().any(|b| b.is_ascii_alphabetic())
})
}
pub(crate) fn looks_like_regex_literal_tail(value: &str) -> bool {
const REGEX_SIGIL_SUFFIXES: &[&str] = &[
")/g", ")/g,", ")/gi", ")/gi,", ")/i", ")/i,", ")/m", ")/m,", ")\\b", "})\\b", "})\\\\b", "]+", "]*",
"]?", "]+/", "]+\\b", "*/g", "+/g", "+/i", ")*", ")+", ")?", ")?$", ")$",
];
REGEX_SIGIL_SUFFIXES.iter().any(|sig| value.ends_with(sig))
}
pub(crate) fn looks_like_email_address(value: &str) -> bool {
let bytes = value.as_bytes();
if bytes.len() < 5 || bytes.len() > 64 {
return false;
}
let at = match bytes.iter().position(|&b| b == b'@') {
Some(idx) => idx,
None => return false,
};
if bytes.iter().skip(at + 1).any(|&b| b == b'@') {
return false;
}
let local = &bytes[..at];
let domain = &bytes[at + 1..];
if local.is_empty() || domain.is_empty() {
return false;
}
if !local
.iter()
.all(|&b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b'.' || b == b'+')
{
return false;
}
if !domain.contains(&b'.') {
return false;
}
domain
.iter()
.all(|&b| b.is_ascii_alphanumeric() || b == b'-' || b == b'.')
}
pub(crate) fn contains_uuid_v4_substring(value: &str) -> bool {
let bytes = value.as_bytes();
if bytes.len() < 36 {
return false;
}
let mut i = 0;
while i + 36 <= bytes.len() {
let slice = &bytes[i..i + 36];
if slice[8] == b'-' && slice[13] == b'-' && slice[18] == b'-' && slice[23] == b'-' {
let all_hex_or_dash = slice.iter().enumerate().all(|(j, &c)| match j {
8 | 13 | 18 | 23 => c == b'-',
_ => c.is_ascii_hexdigit(),
});
if all_hex_or_dash {
return true;
}
}
i += 1;
}
false
}
pub fn looks_like_syntactic_punctuation_marker(value: &str) -> bool {
if value.is_empty() {
return false;
}
let bytes = value.as_bytes();
let starts_with_double_dash = bytes.starts_with(b"--") && bytes.len() >= 3 && bytes[2] != b'-';
if starts_with_double_dash {
return true;
}
if matches!(bytes[0], b'&' | b'@' | b'$') {
let rest = &bytes[1..];
let pure_ident_tail =
!rest.is_empty() && rest.iter().all(|&b| b.is_ascii_alphanumeric() || b == b'_');
if pure_ident_tail {
return true;
}
}
let last = bytes[bytes.len() - 1];
if last == b':' {
let prefix = &bytes[..bytes.len() - 1];
if !prefix.is_empty() && prefix.iter().all(|&b| b.is_ascii_alphabetic()) {
return true;
}
}
false
}
pub fn looks_like_credential_colliding_punctuation(value: &str) -> bool {
if value.is_empty() {
return false;
}
let bytes = value.as_bytes();
bytes[0] == b'!' || bytes[0] == b'/' || looks_like_ts_non_null_identifier(bytes)
}
fn looks_like_ts_non_null_identifier(bytes: &[u8]) -> bool {
if !bytes.ends_with(b"!") || bytes.len() < 9 {
return false;
}
let body = &bytes[..bytes.len() - 1];
if !body.iter().all(|&b| b.is_ascii_alphanumeric() || b == b'_') {
return false;
}
if body.iter().any(|b| b.is_ascii_digit()) {
return false;
}
let has_camel_transition = body
.windows(2)
.any(|w| w[0].is_ascii_lowercase() && w[1].is_ascii_uppercase());
if !has_camel_transition {
return false;
}
[
b"token".as_slice(),
b"secret".as_slice(),
b"key".as_slice(),
b"password".as_slice(),
b"passwd".as_slice(),
b"auth".as_slice(),
b"credential".as_slice(),
]
.iter()
.any(|needle| crate::ascii_ci::ci_find(body, needle))
}
pub fn looks_like_punctuation_decorated_identifier(value: &str) -> bool {
looks_like_syntactic_punctuation_marker(value)
|| looks_like_credential_colliding_punctuation(value)
}