#![allow(unused)]
use crate::State;
use regex::Regex;
pub trait MatchTarget<'a> {
fn get_input(&self) -> &'a str;
fn clear_ranges(&mut self);
fn push_range(&mut self, start: usize, end: usize);
}
impl<'a> MatchTarget<'a> for &'a str {
#[inline]
fn get_input(&self) -> &'a str {
self
}
#[inline]
fn clear_ranges(&mut self) {}
#[inline]
fn push_range(&mut self, _start: usize, _end: usize) {}
}
impl<'a> MatchTarget<'a> for &mut State<'a> {
#[inline]
fn get_input(&self) -> &'a str {
self.input
}
#[inline]
fn clear_ranges(&mut self) {
self.ranges.clear();
}
#[inline]
fn push_range(&mut self, start: usize, end: usize) {
self.ranges.push((start, end));
}
}
#[inline]
pub fn has_keyword<'a>(mut target: impl MatchTarget<'a>, keyword: &str, ignore_case: bool) -> bool {
target.clear_ranges();
let input = target.get_input();
let k_len = keyword.len();
if k_len == 0 || input.len() < k_len {
return false;
}
let mut found = false;
if !ignore_case {
let mut start = 0;
while let Some(idx) = input[start..].find(keyword) {
let abs_idx = start + idx;
target.push_range(abs_idx, abs_idx + k_len);
found = true;
start = abs_idx + k_len;
}
} else {
let input_bytes = input.as_bytes();
let keyword_bytes = keyword.as_bytes();
let mut i = 0;
while i <= input_bytes.len() - k_len {
let window = &input_bytes[i..i + k_len];
if window.eq_ignore_ascii_case(keyword_bytes) {
if input.is_char_boundary(i) && input.is_char_boundary(i + k_len) {
target.push_range(i, i + k_len);
found = true;
}
i += k_len;
} else {
i += 1;
}
}
}
found
}
#[inline]
pub fn has_chars<'a>(
mut target: impl MatchTarget<'a>,
chars: &[char],
any: bool,
ignore_case: bool,
) -> bool {
target.clear_ranges();
let input = target.get_input();
if chars.is_empty() || input.is_empty() {
return false;
}
let mut target_map = [false; 256];
let mut target_count = 0;
for &c in chars {
if c.is_ascii() {
let b = if ignore_case {
c.to_ascii_lowercase() as usize
} else {
c as usize
};
if !target_map[b] {
target_map[b] = true;
target_count += 1; }
}
}
if target_count == 0 {
return false;
}
let mut found_map = [false; 256];
let mut distinct_found = 0;
let mut any_found = false;
for (i, b) in input.bytes().enumerate() {
if !b.is_ascii() {
continue;
}
let val = if ignore_case {
b.to_ascii_lowercase() as usize
} else {
b as usize
};
if target_map[val] {
target.push_range(i, i + 1);
any_found = true;
if !any && !found_map[val] {
found_map[val] = true;
distinct_found += 1;
}
}
}
if any {
any_found
} else {
let all_found = distinct_found == target_count;
if !all_found {
target.clear_ranges();
}
all_found
}
}
#[inline]
pub fn has_regex<'a>(mut target: impl MatchTarget<'a>, re: &Regex) -> bool {
target.clear_ranges();
let input = target.get_input();
let mut found = false;
for mat in re.find_iter(input) {
target.push_range(mat.start(), mat.end());
found = true;
}
found
}
#[inline]
pub fn upper_prob(input: &str) -> f64 {
let mut upper_count = 0;
let mut letter_count = 0;
for b in input.bytes() {
if b.is_ascii_uppercase() {
upper_count += 1;
letter_count += 1;
} else if b.is_ascii_lowercase() {
letter_count += 1;
}
}
if letter_count == 0 {
return 0.0;
}
(upper_count as f64) / (letter_count as f64)
}
#[inline]
pub fn has_upper(input: &str) -> bool {
input.bytes().any(|b| b.is_ascii_uppercase())
}
#[inline]
pub fn has_lower(input: &str) -> bool {
input.bytes().any(|b| b.is_ascii_lowercase())
}
pub fn is_base64(s: &str) -> bool {
let len = s.len();
if len <= 8 {
return false;
}
let trimmed = s.trim_end_matches('=');
let padding_count = len - trimmed.len();
if padding_count > 2 {
return false;
}
if trimmed.len() % 4 == 1 {
return false;
}
if trimmed.ends_with('/')
|| trimmed.ends_with('+')
|| trimmed.ends_with('-')
|| trimmed.ends_with('_')
{
return false;
}
let mut upper_count = 0;
let mut letter_count = 0;
let mut has_digit = false;
let mut has_special = padding_count > 0;
let mut consecutive_upper = 0;
let mut consecutive_lower = 0;
for b in trimmed.bytes() {
if b.is_ascii_uppercase() {
upper_count += 1;
letter_count += 1;
consecutive_upper += 1;
consecutive_lower = 0;
if consecutive_upper >= 8 {
return false;
}
} else if b.is_ascii_lowercase() {
letter_count += 1;
consecutive_lower += 1;
consecutive_upper = 0;
if consecutive_lower >= 8 {
return false;
}
} else {
consecutive_upper = 0;
consecutive_lower = 0;
if b.is_ascii_digit() {
has_digit = true;
} else if b == b'+' || b == b'/' || b == b'-' || b == b'_' {
has_special = true;
}
}
}
if !has_digit && !has_special {
return false;
}
if letter_count == 0 {
return false;
}
let prob = (upper_count as f64) / (letter_count as f64);
if prob <= 0.25 || prob >= 0.75 {
return false;
}
true
}
#[inline]
pub fn sort_ascii_counts(input: &str, ignore_case: bool) -> Vec<(char, usize)> {
let mut counts = [0usize; 256];
for b in input.bytes() {
if b.is_ascii() {
let val = if ignore_case {
b.to_ascii_lowercase() as usize
} else {
b as usize
};
counts[val] += 1;
}
}
let mut result: Vec<(char, usize)> = counts
.iter()
.enumerate()
.filter(|&(_, &count)| count > 0)
.map(|(b, &count)| (b as u8 as char, count))
.collect();
result.sort_unstable_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
result
}
pub fn is_hex(input: &str) -> bool {
let mut s = input.trim();
if s.is_empty() || !s.is_ascii() || s.len() <= 10 {
return false;
}
if (s.starts_with("0x") || s.starts_with("0X") || s.starts_with("\\x") || s.starts_with("\\X"))
&& s[2..].find(&s[0..2]).is_none()
{
s = &s[2..];
}
let prefix = if s.starts_with("0x") {
"0x"
} else if s.starts_with("0X") {
"0X"
} else if s.starts_with("\\x") {
"\\x"
} else if s.starts_with("\\X") {
"\\X"
} else if s.starts_with("%") {
"%"
} else {
""
};
let mut idx = prefix.len();
let bytes = s.as_bytes();
if idx + 2 > bytes.len() {
return false;
}
let mut upper_hex = false;
let mut lower_hex = false;
#[inline]
fn check_hex(b: u8, upper: &mut bool, lower: &mut bool) -> bool {
if b.is_ascii_digit() {
return true;
}
if (b'a'..=b'f').contains(&b) {
*lower = true;
return true;
}
if (b'A'..=b'F').contains(&b) {
*upper = true;
return true;
}
false
}
if !check_hex(bytes[idx], &mut upper_hex, &mut lower_hex)
|| !check_hex(bytes[idx + 1], &mut upper_hex, &mut lower_hex)
{
return false;
}
idx += 2;
let separator = if idx == bytes.len() {
""
} else {
if !prefix.is_empty() {
if let Some(next_prefix_idx) = s[idx..].find(prefix) {
&s[idx..idx + next_prefix_idx]
} else {
return false;
}
} else {
let mut sep_len = 0;
while idx + sep_len < bytes.len() {
let b = bytes[idx + sep_len];
if b.is_ascii_hexdigit() {
break;
}
sep_len += 1;
}
&s[idx..idx + sep_len]
}
};
if !separator.is_empty() && separator.bytes().any(|b| b.is_ascii_alphanumeric()) {
return false;
}
let mut curr_idx = 0;
let mut count = 0;
while curr_idx < bytes.len() {
if !s[curr_idx..].starts_with(prefix) {
return false;
}
curr_idx += prefix.len();
if curr_idx + 2 > bytes.len() {
return false;
}
if !check_hex(bytes[curr_idx], &mut upper_hex, &mut lower_hex)
|| !check_hex(bytes[curr_idx + 1], &mut upper_hex, &mut lower_hex)
{
return false;
}
if upper_hex && lower_hex {
return false;
}
curr_idx += 2;
count += 1;
if curr_idx == bytes.len() {
break;
}
if !s[curr_idx..].starts_with(separator) {
return false;
}
curr_idx += separator.len();
if curr_idx == bytes.len() {
return false; }
}
count > 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
has_keyword("", "", true);
has_keyword(&mut State::new(""), "", true);
}
}