mod streams;
pub use streams::{AltStream, discover_alt_streams, read_alt_stream, write_alt_stream};
pub const ADS_SEPARATOR: char = ':';
pub fn parse_ads_path(archive_path: &str) -> Option<(&str, &str)> {
let path_without_drive = if archive_path.len() > 2 && archive_path.as_bytes()[1] == b':' {
&archive_path[2..]
} else {
archive_path
};
if let Some(pos) = path_without_drive.rfind(ADS_SEPARATOR) {
let abs_pos = if archive_path.len() > path_without_drive.len() {
pos + 2
} else {
pos
};
if abs_pos > 0 && abs_pos < archive_path.len() - 1 {
let base = &archive_path[..abs_pos];
let stream = &archive_path[abs_pos + 1..];
if !stream.is_empty() {
return Some((base, stream));
}
}
}
None
}
pub fn make_ads_path(base_path: &str, stream_name: &str) -> String {
format!("{}{}{}", base_path, ADS_SEPARATOR, stream_name)
}
pub fn is_ads_path(archive_path: &str) -> bool {
parse_ads_path(archive_path).is_some()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_ads_path_simple() {
let result = parse_ads_path("file.txt:Zone.Identifier");
assert_eq!(result, Some(("file.txt", "Zone.Identifier")));
}
#[test]
fn test_parse_ads_path_nested() {
let result = parse_ads_path("path/to/file.txt:custom_stream");
assert_eq!(result, Some(("path/to/file.txt", "custom_stream")));
}
#[test]
fn test_parse_ads_path_no_stream() {
let result = parse_ads_path("regular_file.txt");
assert_eq!(result, None);
}
#[test]
fn test_parse_ads_path_empty_stream() {
let result = parse_ads_path("file.txt:");
assert_eq!(result, None);
}
#[test]
fn test_make_ads_path() {
let path = make_ads_path("document.docx", "Zone.Identifier");
assert_eq!(path, "document.docx:Zone.Identifier");
}
#[test]
fn test_is_ads_path() {
assert!(is_ads_path("file.txt:stream"));
assert!(!is_ads_path("file.txt"));
assert!(!is_ads_path("file.txt:"));
}
}