use crate::error::{PolicyError, Result};
use crate::MAX_RESOURCE_PATTERN_LENGTH;
use alloc::string::String;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PathPattern {
pattern: String,
}
impl PathPattern {
pub fn new(pattern: impl Into<String>) -> Result<Self> {
let pattern = pattern.into();
if pattern.len() > MAX_RESOURCE_PATTERN_LENGTH {
return Err(PolicyError::PatternTooLong {
max: MAX_RESOURCE_PATTERN_LENGTH,
length: pattern.len(),
});
}
Ok(Self { pattern })
}
#[must_use]
pub(crate) fn new_unchecked(pattern: impl Into<String>) -> Self {
Self {
pattern: pattern.into(),
}
}
#[must_use]
pub fn matches(&self, path: &str) -> bool {
wildcard_match(&self.pattern, path)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.pattern
}
}
fn wildcard_match(pattern: &str, path: &str) -> bool {
let pattern_bytes = pattern.as_bytes();
let path_bytes = path.as_bytes();
let mut p_idx = 0;
let mut s_idx = 0;
let mut star_idx: Option<usize> = None;
let mut match_idx = 0;
while s_idx < path_bytes.len() {
let Some(path_char) = path_bytes.get(s_idx).copied() else {
break;
};
if let Some(pattern_char) = pattern_bytes.get(p_idx).copied() {
match pattern_char {
b'?' => {
p_idx += 1;
s_idx += 1;
}
b'*' => {
while pattern_bytes.get(p_idx).copied() == Some(b'*') {
p_idx += 1;
}
star_idx = Some(p_idx);
match_idx = s_idx;
}
c if c == path_char => {
p_idx += 1;
s_idx += 1;
}
_ => {
if let Some(star_pos) = star_idx {
if path_bytes.get(match_idx).copied() == Some(b'/') {
return false;
}
p_idx = star_pos;
match_idx += 1;
s_idx = match_idx;
} else {
return false;
}
}
}
} else {
if let Some(star_pos) = star_idx {
if match_idx >= path_bytes.len() || path_bytes.get(match_idx).copied() == Some(b'/')
{
return false;
}
p_idx = star_pos;
match_idx += 1;
s_idx = match_idx;
} else {
return false;
}
}
}
while pattern_bytes.get(p_idx).copied() == Some(b'*') {
if path_bytes.is_empty() {
return false;
}
p_idx += 1;
}
p_idx == pattern_bytes.len()
}
impl TryFrom<String> for PathPattern {
type Error = PolicyError;
fn try_from(s: String) -> Result<Self> {
Self::new(s)
}
}
impl TryFrom<&str> for PathPattern {
type Error = PolicyError;
fn try_from(s: &str) -> Result<Self> {
Self::new(s)
}
}