//! CPIO archive parsing for PBZX payloads.
//!
//! PBZX archives contain CPIO archives as their payload. This module provides
//! functionality to parse, list, and extract files from CPIO archives.
//!
//! # Supported Formats
//!
//! - newc (070701): SVR4 portable format with no CRC
//! - crc (070702): SVR4 portable format with CRC
//! - odc (070707): POSIX.1 portable format
//!
//! # Example
//!
//! ```no_run
//! use pbzx::cpio::CpioReader;
//! use std::io::Cursor;
//!
//! let data = vec![/* CPIO data */];
//! let mut reader = CpioReader::new(Cursor::new(data));
//!
//! for entry in reader.entries().unwrap() {
//!     let entry = entry.unwrap();
//!     println!("{}: {} bytes", entry.path, entry.size);
//! }
//! ```

use std::io::{Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};

use crate::error::{PbzxError, Result};
use crate::format::{
    CPIO_MAGIC_CRC, CPIO_MAGIC_NEWC, CPIO_MAGIC_ODC, CpioFormat, CpioHeader, FileEntry,
};

/// Statistics returned after extraction.
#[derive(Debug, Clone, Default)]
pub struct ExtractStats {
    /// Number of regular files extracted
    pub files: u64,
    /// Number of directories created
    pub dirs: u64,
    /// Number of symlinks skipped (symlinks are never created during extraction)
    pub symlinks_skipped: u64,
    /// Total bytes written to disk
    pub bytes: u64,
}

/// Normalize a CPIO path: strip leading "./" or "/" and represent root as "".
fn normalize_cpio_path(path: &str) -> String {
    let p = path.strip_prefix("./").unwrap_or(path);
    let p = p.strip_prefix('/').unwrap_or(p);
    if p == "." || p.is_empty() {
        String::new()
    } else {
        p.to_string()
    }
}

/// A reader for CPIO archives.
pub struct CpioReader<R> {
    reader: R,
    position: u64,
}

impl<R: Read> CpioReader<R> {
    /// Create a new CPIO reader.
    pub fn new(reader: R) -> Self {
        Self {
            reader,
            position: 0,
        }
    }

    /// Get an iterator over all entries in the archive.
    pub fn entries(&mut self) -> Result<CpioEntries<'_, R>> {
        Ok(CpioEntries {
            reader: self,
            finished: false,
        })
    }

    /// Detect CPIO format from magic bytes.
    fn detect_format(magic: &[u8; 6]) -> Option<CpioFormat> {
        if magic == CPIO_MAGIC_NEWC {
            Some(CpioFormat::Newc)
        } else if magic == CPIO_MAGIC_CRC {
            Some(CpioFormat::Crc)
        } else if magic == CPIO_MAGIC_ODC {
            Some(CpioFormat::Odc)
        } else {
            None
        }
    }

    /// Read and parse a CPIO header at the current position.
    fn read_header(&mut self) -> Result<Option<CpioHeader>> {
        let mut magic = [0u8; 6];
        match self.reader.read_exact(&mut magic) {
            Ok(()) => {}
            Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
            Err(e) => return Err(e.into()),
        }
        self.position += 6;

        // Detect format
        let format = Self::detect_format(&magic).ok_or_else(|| {
            PbzxError::InvalidCpio(format!(
                "Invalid CPIO magic at offset {}: {:?}",
                self.position - 6,
                String::from_utf8_lossy(&magic)
            ))
        })?;

        match format {
            CpioFormat::Newc | CpioFormat::Crc => self.read_newc_header(),
            CpioFormat::Odc => self.read_odc_header(),
        }
    }

    /// Read newc/crc format header (after magic has been read).
    fn read_newc_header(&mut self) -> Result<Option<CpioHeader>> {
        // Read the rest of the fixed header (104 bytes after magic)
        let mut header_buf = [0u8; 104];
        self.reader.read_exact(&mut header_buf)?;
        self.position += 104;

        // Parse hex fields (8 chars each)
        let parse_hex = |start: usize, len: usize| -> Result<u32> {
            let s = std::str::from_utf8(&header_buf[start..start + len])
                .map_err(|e| PbzxError::InvalidCpio(format!("Invalid UTF-8 in header: {}", e)))?;
            u32::from_str_radix(s, 16)
                .map_err(|e| PbzxError::InvalidCpio(format!("Invalid hex value '{}': {}", s, e)))
        };

        let ino = parse_hex(0, 8)?;
        let mode = parse_hex(8, 8)?;
        let uid = parse_hex(16, 8)?;
        let gid = parse_hex(24, 8)?;
        let nlink = parse_hex(32, 8)?;
        let mtime = parse_hex(40, 8)?;
        let filesize = parse_hex(48, 8)?;
        let devmajor = parse_hex(56, 8)?;
        let devminor = parse_hex(64, 8)?;
        let rdevmajor = parse_hex(72, 8)?;
        let rdevminor = parse_hex(80, 8)?;
        let namesize = parse_hex(88, 8)?;
        let check = parse_hex(96, 8)?;

        // Read filename
        let mut name_buf = vec![0u8; namesize as usize];
        self.reader.read_exact(&mut name_buf)?;
        self.position += namesize as u64;

        // Remove null terminator if present
        if name_buf.last() == Some(&0) {
            name_buf.pop();
        }

        let name = String::from_utf8(name_buf)
            .map_err(|e| PbzxError::InvalidCpio(format!("Invalid filename: {}", e)))?;

        // Align to 4-byte boundary (header is 110 bytes + namesize)
        let header_total = 110 + namesize as u64;
        let padding = (4 - (header_total % 4)) % 4;
        if padding > 0 {
            let mut pad = vec![0u8; padding as usize];
            self.reader.read_exact(&mut pad)?;
            self.position += padding;
        }

        Ok(Some(CpioHeader {
            ino,
            mode,
            uid,
            gid,
            nlink,
            mtime,
            filesize,
            devmajor,
            devminor,
            rdevmajor,
            rdevminor,
            namesize,
            check,
            name,
        }))
    }

    /// Read odc format header (after magic has been read).
    ///
    /// ODC format structure (76 bytes total including magic):
    /// - 6 bytes: magic "070707" (already read)
    /// - 6 bytes: dev (octal)
    /// - 6 bytes: ino (octal)
    /// - 6 bytes: mode (octal)
    /// - 6 bytes: uid (octal)
    /// - 6 bytes: gid (octal)
    /// - 6 bytes: nlink (octal)
    /// - 6 bytes: rdev (octal)
    /// - 11 bytes: mtime (octal)
    /// - 6 bytes: namesize (octal)
    /// - 11 bytes: filesize (octal)
    fn read_odc_header(&mut self) -> Result<Option<CpioHeader>> {
        // Read the rest of the fixed header (70 bytes after magic)
        let mut header_buf = [0u8; 70];
        self.reader.read_exact(&mut header_buf)?;
        self.position += 70;

        // Parse octal fields
        let parse_octal = |start: usize, len: usize| -> Result<u32> {
            let s = std::str::from_utf8(&header_buf[start..start + len])
                .map_err(|e| PbzxError::InvalidCpio(format!("Invalid UTF-8 in header: {}", e)))?;
            u32::from_str_radix(s.trim(), 8)
                .map_err(|e| PbzxError::InvalidCpio(format!("Invalid octal value '{}': {}", s, e)))
        };

        let parse_octal_u64 = |start: usize, len: usize| -> Result<u64> {
            let s = std::str::from_utf8(&header_buf[start..start + len])
                .map_err(|e| PbzxError::InvalidCpio(format!("Invalid UTF-8 in header: {}", e)))?;
            u64::from_str_radix(s.trim(), 8)
                .map_err(|e| PbzxError::InvalidCpio(format!("Invalid octal value '{}': {}", s, e)))
        };

        let dev = parse_octal(0, 6)?;
        let ino = parse_octal(6, 6)?;
        let mode = parse_octal(12, 6)?;
        let uid = parse_octal(18, 6)?;
        let gid = parse_octal(24, 6)?;
        let nlink = parse_octal(30, 6)?;
        let rdev = parse_octal(36, 6)?;
        let mtime = parse_octal_u64(42, 11)? as u32;
        let namesize = parse_octal(53, 6)?;
        let filesize = parse_octal_u64(59, 11)? as u32;

        // Read filename
        let mut name_buf = vec![0u8; namesize as usize];
        self.reader.read_exact(&mut name_buf)?;
        self.position += namesize as u64;

        // Remove null terminator if present
        if name_buf.last() == Some(&0) {
            name_buf.pop();
        }

        let name = String::from_utf8(name_buf)
            .map_err(|e| PbzxError::InvalidCpio(format!("Invalid filename: {}", e)))?;

        // ODC format has no padding requirement

        Ok(Some(CpioHeader {
            ino,
            mode,
            uid,
            gid,
            nlink,
            mtime,
            filesize,
            devmajor: dev >> 8,
            devminor: dev & 0xff,
            rdevmajor: rdev >> 8,
            rdevminor: rdev & 0xff,
            namesize,
            check: 0,
            name,
        }))
    }

    /// Skip the file data for the current entry (newc format with padding).
    fn skip_data_newc(&mut self, size: u64) -> Result<()> {
        // Read and discard the data
        let mut remaining = size;
        let mut buf = [0u8; 8192];

        while remaining > 0 {
            let to_read = std::cmp::min(remaining, buf.len() as u64) as usize;
            self.reader.read_exact(&mut buf[..to_read])?;
            remaining -= to_read as u64;
        }
        self.position += size;

        // Align to 4-byte boundary
        let padding = (4 - (size % 4)) % 4;
        if padding > 0 {
            let mut pad = vec![0u8; padding as usize];
            self.reader.read_exact(&mut pad)?;
            self.position += padding;
        }

        Ok(())
    }

    /// Skip the file data for the current entry (odc format, no padding).
    fn skip_data_odc(&mut self, size: u64) -> Result<()> {
        let mut remaining = size;
        let mut buf = [0u8; 8192];

        while remaining > 0 {
            let to_read = std::cmp::min(remaining, buf.len() as u64) as usize;
            self.reader.read_exact(&mut buf[..to_read])?;
            remaining -= to_read as u64;
        }
        self.position += size;

        Ok(())
    }

    /// Read the file data for an entry (newc format with padding).
    fn read_data_newc(&mut self, size: u64) -> Result<Vec<u8>> {
        let mut data = vec![0u8; size as usize];
        self.reader.read_exact(&mut data)?;
        self.position += size;

        // Align to 4-byte boundary
        let padding = (4 - (size % 4)) % 4;
        if padding > 0 {
            let mut pad = vec![0u8; padding as usize];
            self.reader.read_exact(&mut pad)?;
            self.position += padding;
        }

        Ok(data)
    }

    /// Read the file data for an entry (odc format, no padding).
    fn read_data_odc(&mut self, size: u64) -> Result<Vec<u8>> {
        let mut data = vec![0u8; size as usize];
        self.reader.read_exact(&mut data)?;
        self.position += size;
        Ok(data)
    }

    /// Internal: Detect format at current position without consuming.
    fn peek_format(&mut self) -> Result<Option<CpioFormat>>
    where
        R: Seek,
    {
        let mut magic = [0u8; 6];
        match self.reader.read_exact(&mut magic) {
            Ok(()) => {}
            Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
            Err(e) => return Err(e.into()),
        }
        // Seek back
        self.reader.seek(SeekFrom::Current(-6))?;
        Ok(Self::detect_format(&magic))
    }
}

impl<R: Read + Seek> CpioReader<R> {
    /// List all files in the archive.
    pub fn list(&mut self) -> Result<Vec<FileEntry>> {
        self.reader.seek(SeekFrom::Start(0))?;
        self.position = 0;

        let mut entries = Vec::new();

        // Detect format from first header
        let format = match self.peek_format()? {
            Some(f) => f,
            None => return Ok(entries),
        };

        while let Some(header) = self.read_header()? {
            if header.is_trailer() {
                break;
            }

            // Read symlink target if applicable
            let link_target = if header.is_symlink() && header.filesize > 0 {
                let data = match format {
                    CpioFormat::Odc => self.read_data_odc(header.filesize as u64)?,
                    _ => self.read_data_newc(header.filesize as u64)?,
                };
                Some(String::from_utf8(data).map_err(|e| {
                    PbzxError::InvalidCpio(format!("Invalid symlink target: {}", e))
                })?)
            } else {
                match format {
                    CpioFormat::Odc => self.skip_data_odc(header.filesize as u64)?,
                    _ => self.skip_data_newc(header.filesize as u64)?,
                }
                None
            };

            entries.push(FileEntry {
                path: header.name.clone(),
                size: header.filesize as u64,
                mode: header.mode,
                mtime: header.mtime,
                uid: header.uid,
                gid: header.gid,
                is_dir: header.is_directory(),
                is_symlink: header.is_symlink(),
                link_target,
            });
        }

        Ok(entries)
    }

    /// Extract a specific file by path.
    pub fn extract_file(&mut self, path: &str) -> Result<Vec<u8>> {
        self.reader.seek(SeekFrom::Start(0))?;
        self.position = 0;

        // Detect format
        let format = match self.peek_format()? {
            Some(f) => f,
            None => return Err(PbzxError::FileNotFound(path.to_string())),
        };

        while let Some(header) = self.read_header()? {
            if header.is_trailer() {
                break;
            }

            if header.name == path {
                if header.is_directory() {
                    return Err(PbzxError::InvalidPath(format!("'{}' is a directory", path)));
                }
                return match format {
                    CpioFormat::Odc => self.read_data_odc(header.filesize as u64),
                    _ => self.read_data_newc(header.filesize as u64),
                };
            }

            match format {
                CpioFormat::Odc => self.skip_data_odc(header.filesize as u64)?,
                _ => self.skip_data_newc(header.filesize as u64)?,
            }
        }

        Err(PbzxError::FileNotFound(path.to_string()))
    }

    /// Extract all files to a directory.
    ///
    /// Returns statistics about what was extracted. Symlinks are skipped
    /// (counted in `symlinks_skipped`).
    pub fn extract_all<P: AsRef<Path>>(&mut self, dest: P) -> Result<ExtractStats> {
        self.extract_path("/", dest)
    }

    /// Extract files under `base_path` to a directory.
    ///
    /// Only entries whose normalized path equals `base_path` or starts with
    /// `base_path/` are extracted. The `base_path` prefix is stripped from
    /// output paths so only the relative remainder appears under `dest`.
    /// Pass `"/"` to extract everything (no stripping).
    /// Symlinks are skipped (counted in `symlinks_skipped`).
    pub fn extract_path<P: AsRef<Path>>(
        &mut self,
        base_path: &str,
        dest: P,
    ) -> Result<ExtractStats> {
        let base = normalize_cpio_path(base_path);
        let base = base.trim_end_matches('/');

        let dest = dest.as_ref();
        std::fs::create_dir_all(dest)?;

        self.reader.seek(SeekFrom::Start(0))?;
        self.position = 0;

        // Detect format
        let format = match self.peek_format()? {
            Some(f) => f,
            None => return Ok(ExtractStats::default()),
        };

        let mut stats = ExtractStats::default();

        while let Some(header) = self.read_header()? {
            if header.is_trailer() {
                break;
            }

            let entry_path = normalize_cpio_path(&header.name);

            // Check if this entry matches the base_path filter
            let matches = if base.is_empty() {
                !entry_path.is_empty()
            } else {
                entry_path == base || entry_path.starts_with(&format!("{base}/"))
            };

            if !matches {
                match format {
                    CpioFormat::Odc => self.skip_data_odc(header.filesize as u64)?,
                    _ => self.skip_data_newc(header.filesize as u64)?,
                }
                continue;
            }

            // Strip base_path prefix so only the relative remainder is extracted
            let rel_path = strip_base_prefix(&entry_path, base);

            // The base dir itself maps to dest (already created above)
            if rel_path.is_empty() {
                match format {
                    CpioFormat::Odc => self.skip_data_odc(header.filesize as u64)?,
                    _ => self.skip_data_newc(header.filesize as u64)?,
                }
                if header.is_directory() {
                    stats.dirs += 1;
                }
                continue;
            }

            // Sanitize path to prevent directory traversal
            let clean_path = sanitize_path(rel_path)?;
            let full_path = dest.join(&clean_path);

            if let Some(parent) = full_path.parent() {
                std::fs::create_dir_all(parent)?;
            }

            if header.is_directory() {
                std::fs::create_dir_all(&full_path)?;
                match format {
                    CpioFormat::Odc => self.skip_data_odc(header.filesize as u64)?,
                    _ => self.skip_data_newc(header.filesize as u64)?,
                }
                stats.dirs += 1;
            } else if header.is_symlink() {
                // Skip symlinks — just consume the data
                match format {
                    CpioFormat::Odc => self.skip_data_odc(header.filesize as u64)?,
                    _ => self.skip_data_newc(header.filesize as u64)?,
                }
                stats.symlinks_skipped += 1;
            } else if header.is_file() {
                let data = match format {
                    CpioFormat::Odc => self.read_data_odc(header.filesize as u64)?,
                    _ => self.read_data_newc(header.filesize as u64)?,
                };
                let mut file = std::fs::File::create(&full_path)?;
                file.write_all(&data)?;
                stats.bytes += data.len() as u64;
                stats.files += 1;

                #[cfg(unix)]
                {
                    use std::os::unix::fs::PermissionsExt;
                    let perms = std::fs::Permissions::from_mode(header.mode & 0o7777);
                    std::fs::set_permissions(&full_path, perms)?;
                }
            } else {
                // Skip special files (devices, fifos, etc.)
                match format {
                    CpioFormat::Odc => self.skip_data_odc(header.filesize as u64)?,
                    _ => self.skip_data_newc(header.filesize as u64)?,
                }
            }
        }

        Ok(stats)
    }
}

/// Iterator over CPIO archive entries.
pub struct CpioEntries<'a, R> {
    reader: &'a mut CpioReader<R>,
    finished: bool,
}

impl<'a, R: Read> Iterator for CpioEntries<'a, R> {
    type Item = Result<CpioEntry>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.finished {
            return None;
        }

        match self.reader.read_header() {
            Ok(Some(header)) => {
                if header.is_trailer() {
                    self.finished = true;
                    return None;
                }

                // For streaming iterator, we read data with odc-style (no padding)
                // This is a simplification - in practice you'd need to track format
                let data = if header.filesize > 0 {
                    match self.reader.read_data_odc(header.filesize as u64) {
                        Ok(d) => Some(d),
                        Err(e) => return Some(Err(e)),
                    }
                } else {
                    None
                };

                Some(Ok(CpioEntry {
                    path: header.name.clone(),
                    size: header.filesize as u64,
                    mode: header.mode,
                    mtime: header.mtime,
                    uid: header.uid,
                    gid: header.gid,
                    is_dir: header.is_directory(),
                    is_symlink: header.is_symlink(),
                    data,
                }))
            }
            Ok(None) => {
                self.finished = true;
                None
            }
            Err(e) => {
                self.finished = true;
                Some(Err(e))
            }
        }
    }
}

/// A single entry from a CPIO archive with its data.
#[derive(Debug)]
pub struct CpioEntry {
    /// Path within the archive
    pub path: String,
    /// File size in bytes
    pub size: u64,
    /// File mode/permissions
    pub mode: u32,
    /// Modification time (Unix timestamp)
    pub mtime: u32,
    /// User ID
    pub uid: u32,
    /// Group ID
    pub gid: u32,
    /// Whether this is a directory
    pub is_dir: bool,
    /// Whether this is a symlink
    pub is_symlink: bool,
    /// File data (or symlink target if is_symlink)
    pub data: Option<Vec<u8>>,
}

impl CpioEntry {
    /// Get the data as a string (useful for symlinks and text files).
    pub fn data_as_string(
        &self,
    ) -> Option<std::result::Result<String, std::string::FromUtf8Error>> {
        self.data.clone().map(String::from_utf8)
    }
}

/// Strip a normalized base prefix from a path, returning only the remainder.
///
/// Returns `""` when `path` is the base itself (or base is empty and path is empty),
/// and the relative tail when `path` starts with `base/`.
fn strip_base_prefix<'a>(path: &'a str, base: &str) -> &'a str {
    if base.is_empty() {
        path
    } else if path.len() <= base.len() {
        ""
    } else {
        &path[base.len() + 1..]
    }
}

/// Sanitize a path to prevent directory traversal attacks.
fn sanitize_path(path: &str) -> Result<PathBuf> {
    let path = path.trim_start_matches('/');

    // Check for path traversal
    for component in Path::new(path).components() {
        match component {
            std::path::Component::ParentDir => {
                return Err(PbzxError::InvalidPath(format!(
                    "Path traversal detected: {}",
                    path
                )));
            }
            std::path::Component::Normal(_) | std::path::Component::CurDir => {}
            _ => {
                return Err(PbzxError::InvalidPath(format!(
                    "Invalid path component: {}",
                    path
                )));
            }
        }
    }

    Ok(PathBuf::from(path))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_sanitize_path() {
        assert!(sanitize_path("normal/path/file.txt").is_ok());
        assert!(sanitize_path("/absolute/path").is_ok());
        assert!(sanitize_path("../traversal").is_err());
        assert!(sanitize_path("path/../traversal").is_err());
    }

    #[test]
    fn test_normalize_cpio_path() {
        assert_eq!(normalize_cpio_path("./usr/bin/foo"), "usr/bin/foo");
        assert_eq!(normalize_cpio_path("/usr/bin/foo"), "usr/bin/foo");
        assert_eq!(normalize_cpio_path("usr/bin/foo"), "usr/bin/foo");
        assert_eq!(normalize_cpio_path("."), "");
        assert_eq!(normalize_cpio_path("./"), "");
        assert_eq!(normalize_cpio_path("/"), "");
        assert_eq!(normalize_cpio_path(""), "");
    }

    #[test]
    fn test_extract_path_with_filter() {
        use crate::writer::CpioBuilder;

        let mut builder = CpioBuilder::new();
        builder.add_directory("usr", 0o755);
        builder.add_directory("usr/bin", 0o755);
        builder.add_file("usr/bin/hello", b"hello world", 0o755);
        builder.add_directory("usr/lib", 0o755);
        builder.add_file("usr/lib/libfoo.so", b"lib content", 0o644);
        builder.add_directory("etc", 0o755);
        builder.add_file("etc/config.txt", b"config", 0o644);
        let cpio_data = builder.finish();

        let tmp = tempfile::tempdir().unwrap();

        // Extract only usr/bin — base prefix is stripped from output
        let mut reader = CpioReader::new(std::io::Cursor::new(&cpio_data));
        let stats = reader.extract_path("usr/bin", tmp.path()).unwrap();
        assert_eq!(stats.files, 1);
        assert_eq!(stats.dirs, 1); // usr/bin dir itself (base)
        assert!(tmp.path().join("hello").exists());
        assert!(!tmp.path().join("usr").exists());
        assert!(!tmp.path().join("etc").exists());
    }

    #[test]
    fn test_extract_all_returns_stats() {
        use crate::writer::CpioBuilder;

        let mut builder = CpioBuilder::new();
        builder.add_directory("dir", 0o755);
        builder.add_file("dir/file.txt", b"content", 0o644);
        let cpio_data = builder.finish();

        let tmp = tempfile::tempdir().unwrap();
        let mut reader = CpioReader::new(std::io::Cursor::new(&cpio_data));
        let stats = reader.extract_all(tmp.path()).unwrap();

        assert_eq!(stats.files, 1);
        assert_eq!(stats.dirs, 1);
        assert_eq!(stats.bytes, 7); // "content".len()
        assert_eq!(stats.symlinks_skipped, 0);
    }

    #[test]
    fn test_extract_path_skips_symlinks() {
        use crate::writer::CpioBuilder;

        let mut builder = CpioBuilder::new();
        builder.add_directory("usr", 0o755);
        builder.add_file("usr/real.txt", b"real file", 0o644);
        builder.add_symlink("usr/link.txt", "real.txt", 0o777);
        let cpio_data = builder.finish();

        let tmp = tempfile::tempdir().unwrap();
        let mut reader = CpioReader::new(std::io::Cursor::new(&cpio_data));
        let stats = reader.extract_all(tmp.path()).unwrap();

        assert_eq!(stats.files, 1);
        assert_eq!(stats.symlinks_skipped, 1);
        assert!(tmp.path().join("usr/real.txt").exists());
        // Symlink is NOT created
        assert!(!tmp.path().join("usr/link.txt").exists());
    }

    #[test]
    fn test_extract_path_rejects_traversal() {
        assert!(sanitize_path("../etc/passwd").is_err());
        assert!(sanitize_path("usr/../../etc/passwd").is_err());
    }
}
