use crate::encode;
use crate::options::RemoveDirectoryIndex;
use crate::QueryFilter;
pub fn remove_duplicate_slashes(path: &str) -> String {
let mut result = String::with_capacity(path.len());
let bytes = path.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'/' && i + 1 < bytes.len() && bytes[i + 1] == b'/' {
if is_preceded_by_scheme(&result) {
result.push('/');
result.push('/');
i += 2;
continue;
}
result.push('/');
while i < bytes.len() && bytes[i] == b'/' {
i += 1;
}
continue;
}
result.push(bytes[i] as char);
i += 1;
}
result
}
fn is_preceded_by_scheme(result: &str) -> bool {
if !result.ends_with(':') {
return false;
}
let without_colon = &result[..result.len() - 1];
let scheme_start = without_colon.rfind('/').map(|i| i + 1).unwrap_or(0);
let scheme = &without_colon[scheme_start..];
if scheme.len() < 2 || scheme.len() > 51 {
return false;
}
let bytes = scheme.as_bytes();
bytes[0].is_ascii_alphabetic()
&& bytes[1..]
.iter()
.all(|b| b.is_ascii_alphanumeric() || *b == b'+' || *b == b'-' || *b == b'.')
}
pub fn decode_pathname(path: &str) -> String {
encode::decode_uri(path)
}
pub fn remove_directory_index(path: &mut String, option: &RemoveDirectoryIndex) {
let filters: Option<Vec<&QueryFilter>> = match option {
RemoveDirectoryIndex::None => None,
RemoveDirectoryIndex::Default => {
let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
if let Some(last) = components.last() {
if last.starts_with("index.")
&& last.len() > 6
&& last[6..].bytes().all(|b| b.is_ascii_lowercase())
{
let new_components = components[..components.len() - 1].to_vec();
if new_components.is_empty() {
*path = "/".to_string();
} else {
*path = format!("/{}/", new_components.join("/"));
}
}
}
return;
}
RemoveDirectoryIndex::List(filters) => Some(filters.iter().collect()),
};
if let Some(filters) = filters {
if filters.is_empty() {
return;
}
let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
if let Some(last) = components.last() {
if filters.iter().any(|f| f.matches(last)) {
let new_components = &components[..components.len() - 1];
if new_components.is_empty() {
*path = "/".to_string();
} else {
*path = format!("/{}/", new_components.join("/"));
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remove_duplicate_slashes() {
assert_eq!(remove_duplicate_slashes("/foo//bar"), "/foo/bar");
assert_eq!(remove_duplicate_slashes("/foo///bar"), "/foo/bar");
assert_eq!(remove_duplicate_slashes("/foo/bar"), "/foo/bar");
}
#[test]
fn test_preserve_embedded_protocol() {
assert_eq!(
remove_duplicate_slashes("/https://bar.com/foo//bar"),
"/https://bar.com/foo/bar"
);
}
#[test]
fn test_decode_pathname() {
assert_eq!(decode_pathname("/%7Efoo/"), "/~foo/");
}
}