use super::*;
use std::fmt;
#[non_exhaustive]
pub enum EntryContent<'a> {
File(EntryDataReader<'a>),
Directory,
SymbolicLink(EntryReference),
HardLink(EntryReference),
Unknown(DataKind, EntryDataReader<'a>),
}
impl fmt::Debug for EntryContent<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::File(_) => f.debug_tuple("File").finish(),
Self::Directory => f.write_str("Directory"),
Self::SymbolicLink(r) => f.debug_tuple("SymbolicLink").field(r).finish(),
Self::HardLink(r) => f.debug_tuple("HardLink").field(r).finish(),
Self::Unknown(kind, _) => f.debug_tuple("Unknown").field(kind).finish(),
}
}
}
fn read_reference(reader: EntryDataReader<'_>) -> io::Result<EntryReference> {
let target = io::read_to_string(reader)?;
Ok(EntryReference::from_utf8_preserve_root(&target))
}
impl<T: AsRef<[u8]>> NormalEntry<T> {
#[inline]
pub fn content(&self, option: impl ReadOption) -> io::Result<EntryContent<'_>> {
match self.header.data_kind {
DataKind::FILE => Ok(EntryContent::File(self.reader(option)?)),
DataKind::DIRECTORY => Ok(EntryContent::Directory),
DataKind::SYMBOLIC_LINK => Ok(EntryContent::SymbolicLink(read_reference(
self.reader(option)?,
)?)),
DataKind::HARD_LINK => Ok(EntryContent::HardLink(read_reference(
self.reader(option)?,
)?)),
kind => Ok(EntryContent::Unknown(kind, self.reader(option)?)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::TryIntoInner;
use std::io::Write;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use wasm_bindgen_test::wasm_bindgen_test as test;
fn read_options() -> ReadOptions {
ReadOptions::builder().build()
}
#[test]
fn file_reads_contents() {
let mut builder = FileEntryBuilder::new("f.txt".into()).unwrap();
builder.write_all(b"abc").unwrap();
let entry = builder.build().unwrap();
let EntryContent::File(reader) = entry.content(read_options()).unwrap() else {
panic!("expected File");
};
assert_eq!("abc", io::read_to_string(reader).unwrap());
}
#[test]
fn empty_file_reads_empty() {
let builder = FileEntryBuilder::new("f.txt".into()).unwrap();
let entry = builder.build().unwrap();
let EntryContent::File(reader) = entry.content(read_options()).unwrap() else {
panic!("expected File");
};
assert_eq!("", io::read_to_string(reader).unwrap());
}
#[test]
fn directory_has_no_content() {
let entry = DirEntryBuilder::new("d".into()).build().unwrap();
assert!(matches!(
entry.content(read_options()).unwrap(),
EntryContent::Directory
));
}
#[test]
fn symlink_target_roundtrips() {
let entry = SymlinkEntryBuilder::new(
"l".into(),
EntryReference::from_utf8_preserve_root("target/path"),
)
.unwrap()
.build()
.unwrap();
let EntryContent::SymbolicLink(target) = entry.content(read_options()).unwrap() else {
panic!("expected SymbolicLink");
};
assert_eq!("target/path", target);
}
#[test]
fn absolute_symlink_target_is_preserved() {
let entry = SymlinkEntryBuilder::new(
"l".into(),
EntryReference::from_utf8_preserve_root("/usr/bin/env"),
)
.unwrap()
.build()
.unwrap();
let EntryContent::SymbolicLink(target) = entry.content(read_options()).unwrap() else {
panic!("expected SymbolicLink");
};
assert_eq!("/usr/bin/env", target);
}
#[test]
fn empty_symlink_target_roundtrips() {
let entry =
SymlinkEntryBuilder::new("l".into(), EntryReference::from_utf8_preserve_root(""))
.unwrap()
.build()
.unwrap();
let EntryContent::SymbolicLink(target) = entry.content(read_options()).unwrap() else {
panic!("expected SymbolicLink");
};
assert_eq!("", target);
}
#[test]
fn hard_link_target_roundtrips() {
let entry = HardLinkEntryBuilder::new(
"l".into(),
EntryReference::from_utf8_preserve_root("target"),
)
.unwrap()
.build()
.unwrap();
let EntryContent::HardLink(target) = entry.content(read_options()).unwrap() else {
panic!("expected HardLink");
};
assert_eq!("target", target);
}
#[test]
fn directory_with_stray_data_is_still_directory() {
let mut entry = DirEntryBuilder::new("d".into()).build().unwrap();
entry.data = vec![b"junk".to_vec()];
assert!(matches!(
entry.content(read_options()).unwrap(),
EntryContent::Directory
));
}
#[test]
fn non_utf8_symlink_target_is_invalid_data() {
let mut entry =
SymlinkEntryBuilder::new("l".into(), EntryReference::from_utf8_preserve_root("t"))
.unwrap()
.build()
.unwrap();
entry.data = vec![vec![0xFF, 0xFE, 0xFD]];
let err = entry.content(read_options()).unwrap_err();
assert_eq!(io::ErrorKind::InvalidData, err.kind());
}
#[test]
fn non_utf8_hard_link_target_is_invalid_data() {
let mut entry =
HardLinkEntryBuilder::new("l".into(), EntryReference::from_utf8_preserve_root("t"))
.unwrap()
.build()
.unwrap();
entry.data = vec![vec![0xFF, 0xFE, 0xFD]];
let err = entry.content(read_options()).unwrap_err();
assert_eq!(io::ErrorKind::InvalidData, err.kind());
}
#[test]
fn reserved_kind_yields_unknown_with_raw_bytes() {
let mut builder = FileEntryBuilder::new("f".into()).unwrap();
builder.write_all(b"raw").unwrap();
let mut entry = builder.build().unwrap();
entry.header.data_kind = DataKind::from_byte(42);
let EntryContent::Unknown(kind, reader) = entry.content(read_options()).unwrap() else {
panic!("expected Unknown");
};
assert_eq!(DataKind::from_byte(42), kind);
assert_eq!("raw", io::read_to_string(reader).unwrap());
}
#[test]
fn private_kind_yields_unknown_with_raw_bytes() {
let mut builder = FileEntryBuilder::new("f".into()).unwrap();
builder.write_all(b"raw").unwrap();
let mut entry = builder.build().unwrap();
entry.header.data_kind = DataKind::from_byte(200);
let EntryContent::Unknown(kind, reader) = entry.content(read_options()).unwrap() else {
panic!("expected Unknown");
};
assert_eq!(DataKind::from_byte(200), kind);
assert_eq!("raw", io::read_to_string(reader).unwrap());
}
fn encrypted_symlink_entry(target: &str, password: &str) -> NormalEntry {
let options = WriteOptions::builder()
.encryption(Encryption::AES)
.password(Some(password))
.try_build()
.unwrap();
let context = get_writer_context(&options).unwrap();
let mut writer = get_writer(crate::io::FlattenWriter::new(), &context).unwrap();
writer.write_all(target.as_bytes()).unwrap();
let mut data = writer
.try_into_inner()
.unwrap()
.try_into_inner()
.unwrap()
.inner;
let (iv, phsf) = match context.cipher {
None => (None, None),
Some(WriteCipher { context: c, .. }) => (Some(c.iv), Some(c.phsf)),
};
if let Some(iv) = iv {
data.insert(0, iv);
}
let mut entry =
SymlinkEntryBuilder::new("l".into(), EntryReference::from_utf8_preserve_root(target))
.unwrap()
.build()
.unwrap();
entry.header.encryption = options.encryption();
entry.header.cipher_mode = options.cipher_mode();
entry.phsf = phsf;
entry.data = data;
entry
}
#[test]
fn encrypted_symlink_with_password_decodes_target() {
let entry = encrypted_symlink_entry("enc/target", "password");
let EntryContent::SymbolicLink(target) = entry
.content(ReadOptions::with_password(Some("password")))
.unwrap()
else {
panic!("expected SymbolicLink");
};
assert_eq!("enc/target", target);
}
#[test]
fn encrypted_symlink_without_password_errors() {
let entry = encrypted_symlink_entry("enc/target", "password");
assert!(entry.content(read_options()).is_err());
}
#[test]
fn encrypted_directory_decodes_without_password() {
let mut entry = DirEntryBuilder::new("d".into()).build().unwrap();
entry.header.encryption = Encryption::AES;
entry.header.cipher_mode = CipherMode::CTR;
assert!(matches!(
entry.content(read_options()).unwrap(),
EntryContent::Directory
));
}
}