//! Minimal read-only ISO9660 lookup. Given an uploaded ISO file and an //! in-ISO path (e.g. `/casper/vmlinuz`), locate the file and return a //! `(start_byte, length_bytes)` pair so the HTTP handler can stream just //! that range from the on-disk ISO without full extraction. //! //! We only implement what we need: the Primary Volume Descriptor and Rock //! Ridge / Joliet extensions are ignored. Paths are matched case-insensitive //! against plain ISO9660 filenames (uppercase, `;1` version suffix stripped). //! This is sufficient for the kernel/initrd and wimboot files we serve; //! if a requested path isn't found, the handler returns 404 and the user //! can still download the whole ISO via `/iso/.iso`. use std::io::{Read, Seek, SeekFrom}; use std::path::Path; const SECTOR: u64 = 2048; #[derive(Debug, Clone)] pub struct FileLocation { pub offset: u64, pub length: u64, } /// Look up `in_iso_path` (leading slash optional, case-insensitive) in the /// ISO at `iso_path`. Returns None on any parsing or IO failure. pub fn lookup(iso_path: &Path, in_iso_path: &str) -> Option { let mut f = std::fs::File::open(iso_path).ok()?; let root = read_root_directory(&mut f)?; let components: Vec<&str> = in_iso_path .trim_start_matches('/') .split('/') .filter(|c| !c.is_empty()) .collect(); if components.is_empty() { return None; } walk(&mut f, root.offset, root.length, &components) } fn read_root_directory(f: &mut std::fs::File) -> Option { // Primary Volume Descriptor at LBA 16. let mut pvd = [0u8; 2048]; f.seek(SeekFrom::Start(16 * SECTOR)).ok()?; f.read_exact(&mut pvd).ok()?; if pvd[0] != 0x01 || &pvd[1..6] != b"CD001" { return None; } // Root directory record is at offset 156, length 34. let rec = &pvd[156..156 + 34]; let (offset, length) = parse_dir_record_ext(rec)?; Some(FileLocation { offset: offset * SECTOR, length, }) } /// Walk components down the directory tree starting at `dir_offset`. fn walk( f: &mut std::fs::File, dir_offset: u64, dir_len: u64, components: &[&str], ) -> Option { let mut dir = vec![0u8; dir_len as usize]; f.seek(SeekFrom::Start(dir_offset)).ok()?; f.read_exact(&mut dir).ok()?; let target = components[0]; let rest = &components[1..]; let mut i = 0; while i < dir.len() { let len = dir[i] as usize; if len == 0 { // Padding to sector boundary. let next = (i / SECTOR as usize + 1) * SECTOR as usize; if next <= i { break; } i = next; continue; } if i + len > dir.len() { break; } let rec = &dir[i..i + len]; let name = dir_record_name(rec); let is_dir = (rec.get(25).copied().unwrap_or(0) & 0x02) != 0; // Skip "." (0x00) and ".." (0x01) pseudo-entries. let is_pseudo = matches!(rec.get(32).copied(), Some(1)) && rec.get(33).copied() == Some(0x00) || matches!(rec.get(32).copied(), Some(1)) && rec.get(33).copied() == Some(0x01); if !is_pseudo && name.eq_ignore_ascii_case(target) { let (child_off, child_len) = parse_dir_record_ext(rec)?; if rest.is_empty() && !is_dir { return Some(FileLocation { offset: child_off * SECTOR, length: child_len, }); } else if !rest.is_empty() && is_dir { return walk(f, child_off * SECTOR, child_len, rest); } } i += len; } None } /// Extract (extent LBA, data length in bytes) from a directory record. /// Layout per ISO9660: bytes 2..10 extent LBA (LE+BE duplicate), 10..18 /// data length (LE+BE duplicate). We trust the little-endian copy. fn parse_dir_record_ext(rec: &[u8]) -> Option<(u64, u64)> { if rec.len() < 34 { return None; } let lba = u32::from_le_bytes(rec[2..6].try_into().ok()?) as u64; let len = u32::from_le_bytes(rec[10..14].try_into().ok()?) as u64; Some((lba, len)) } /// Extract the identifier from a directory record, stripping ISO9660's /// `;1` version suffix. fn dir_record_name(rec: &[u8]) -> String { let name_len = *rec.get(32).unwrap_or(&0) as usize; if name_len == 0 || rec.len() < 33 + name_len { return String::new(); } let raw = &rec[33..33 + name_len]; let s = String::from_utf8_lossy(raw).to_string(); // Strip `;N` version suffix. if let Some(i) = s.rfind(';') { s[..i].to_string() } else { s } }