use regex::Regex;
use std::sync::LazyLock;
static QUOTED_STRING: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r#""(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'"#).expect("Invalid quoted string regex")
});
static PATH_TRAVERSAL: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"(?:^|\s|[/\\])\.\.(?:[/\\]|\s|$)").expect("Invalid path traversal regex")
});
fn strip_quoted_sections(input: &str) -> String {
QUOTED_STRING.replace_all(input, " ").to_string()
}
#[must_use]
pub fn contains_path_traversal(input: &str) -> bool {
let unquoted = strip_quoted_sections(input);
PATH_TRAVERSAL.is_match(&unquoted)
}
#[must_use]
pub fn contains_absolute_path(input: &str) -> bool {
let mut chars = input.chars().peekable();
let mut prev_char = ' ';
while let Some(c) = chars.next() {
if c == '/' {
if (prev_char == ' ' || prev_char == '"' || prev_char == '\'' || prev_char == '=')
&& chars.peek() != Some(&'/')
{
return true;
}
}
prev_char = c;
}
if input.starts_with('/') && !input.starts_with("//") {
return true;
}
if input.contains(":\\") || input.contains(":/") {
return true;
}
if input.contains("\\\\") {
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_traversal_unquoted() {
assert!(contains_path_traversal("../foo"));
assert!(contains_path_traversal("foo/../bar"));
assert!(contains_path_traversal("foo/.."));
assert!(contains_path_traversal("..\\foo")); assert!(contains_path_traversal(".."));
assert!(!contains_path_traversal("foo/bar"));
assert!(!contains_path_traversal("foo.bar")); }
#[test]
fn test_path_traversal_quoted() {
assert!(!contains_path_traversal("sqry query \"..*password\""));
assert!(!contains_path_traversal("sqry query \"module..submodule\""));
assert!(!contains_path_traversal("sqry search '..range'"));
assert!(contains_path_traversal("sqry query \"foo\" ../bar"));
}
#[test]
fn test_strip_quoted_sections() {
assert_eq!(strip_quoted_sections("hello \"world\" foo"), "hello foo");
assert_eq!(strip_quoted_sections("hello 'world' foo"), "hello foo");
}
#[test]
fn test_absolute_unix() {
assert!(contains_absolute_path("/etc/passwd"));
assert!(contains_absolute_path("sqry query \"/etc/passwd\""));
}
#[test]
fn test_absolute_windows() {
assert!(contains_absolute_path("C:\\Windows"));
assert!(contains_absolute_path("D:/Users"));
}
#[test]
fn test_unc_path() {
assert!(contains_absolute_path("\\\\server\\share"));
}
#[test]
fn test_relative_paths_not_flagged_as_absolute() {
assert!(!contains_absolute_path("src/lib.rs"));
assert!(!contains_absolute_path("sqry query \"src/**/*.rs\""));
}
}