#![warn(missing_docs)]
use std::cmp;
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;
use std::fs::{File, Metadata};
use std::io::{
self, BufRead, BufReader, Error, ErrorKind, Read, Result, Seek, SeekFrom,
Write,
};
use std::path::Path;
use std::str;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
fn read_le_u32(r: &mut impl io::Read) -> io::Result<u32> {
let mut buf = [0; 4];
r.read_exact(&mut buf).map(|()| u32::from_le_bytes(buf))
}
fn read_be_u32(r: &mut impl io::Read) -> io::Result<u32> {
let mut buf = [0; 4];
r.read_exact(&mut buf).map(|()| u32::from_be_bytes(buf))
}
const GLOBAL_HEADER_LEN: usize = 8;
const GLOBAL_HEADER: &'static [u8; GLOBAL_HEADER_LEN] = b"!<arch>\n";
const ENTRY_HEADER_LEN: usize = 60;
const BSD_SYMBOL_LOOKUP_TABLE_ID: &[u8] = b"__.SYMDEF";
const BSD_SORTED_SYMBOL_LOOKUP_TABLE_ID: &[u8] = b"__.SYMDEF SORTED";
const GNU_NAME_TABLE_ID: &str = "//";
const GNU_SYMBOL_LOOKUP_TABLE_ID: &[u8] = b"/";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Variant {
Common,
BSD,
GNU,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Header {
identifier: Vec<u8>,
mtime: u64,
uid: u32,
gid: u32,
mode: u32,
size: u64,
}
impl Header {
pub fn new(identifier: Vec<u8>, size: u64) -> Header {
Header { identifier, mtime: 0, uid: 0, gid: 0, mode: 0, size }
}
#[cfg(unix)]
pub fn from_metadata(identifier: Vec<u8>, meta: &Metadata) -> Header {
Header {
identifier,
mtime: meta.mtime() as u64,
uid: meta.uid(),
gid: meta.gid(),
mode: meta.mode(),
size: meta.len(),
}
}
#[cfg(not(unix))]
pub fn from_metadata(identifier: Vec<u8>, meta: &Metadata) -> Header {
Header::new(identifier, meta.len())
}
pub fn identifier(&self) -> &[u8] {
&self.identifier
}
pub fn set_identifier(&mut self, identifier: Vec<u8>) {
self.identifier = identifier;
}
pub fn mtime(&self) -> u64 {
self.mtime
}
pub fn set_mtime(&mut self, mtime: u64) {
self.mtime = mtime;
}
pub fn uid(&self) -> u32 {
self.uid
}
pub fn set_uid(&mut self, uid: u32) {
self.uid = uid;
}
pub fn gid(&self) -> u32 {
self.gid
}
pub fn set_gid(&mut self, gid: u32) {
self.gid = gid;
}
pub fn mode(&self) -> u32 {
self.mode
}
pub fn set_mode(&mut self, mode: u32) {
self.mode = mode;
}
pub fn size(&self) -> u64 {
self.size
}
pub fn set_size(&mut self, size: u64) {
self.size = size;
}
fn read<R>(
reader: &mut R,
variant: &mut Variant,
name_table: &mut Vec<u8>,
) -> Result<Option<(Header, u64)>>
where
R: Read,
{
let mut buffer = [0; 60];
let bytes_read = reader.read(&mut buffer)?;
if bytes_read == 0 {
return Ok(None);
} else if bytes_read < buffer.len() {
if let Err(error) = reader.read_exact(&mut buffer[bytes_read..]) {
if error.kind() == ErrorKind::UnexpectedEof {
let msg = "unexpected EOF in the middle of archive entry \
header";
return Err(Error::new(ErrorKind::UnexpectedEof, msg));
} else {
let msg = "failed to read archive entry header";
return Err(annotate(error, msg));
}
}
}
let mut identifier = buffer[0..16].to_vec();
while identifier.last() == Some(&b' ') {
identifier.pop();
}
let mut size = parse_number("file size", &buffer[48..58], 10)?;
let mut header_len = ENTRY_HEADER_LEN as u64;
if *variant != Variant::BSD && identifier.starts_with(b"/") {
*variant = Variant::GNU;
if identifier == GNU_SYMBOL_LOOKUP_TABLE_ID {
io::copy(&mut reader.by_ref().take(size), &mut io::sink())?;
return Ok(Some((Header::new(identifier, size), header_len)));
} else if identifier == GNU_NAME_TABLE_ID.as_bytes() {
*name_table = vec![0; size as usize];
reader.read_exact(name_table as &mut [u8]).map_err(|err| {
annotate(err, "failed to read name table")
})?;
return Ok(Some((Header::new(identifier, size), header_len)));
}
let start = parse_number("GNU filename index", &buffer[1..16], 10)?
as usize;
let end = match name_table[start..]
.iter()
.position(|&ch| ch == b'/' || ch == b'\x00')
{
Some(len) => start + len,
None => name_table.len(),
};
identifier = name_table[start..end].to_vec();
} else if *variant != Variant::BSD && identifier.ends_with(b"/") {
*variant = Variant::GNU;
identifier.pop();
}
let mtime = parse_number("timestamp", &buffer[16..28], 10)?;
let uid = if *variant == Variant::GNU {
parse_number_permitting_empty("owner ID", &buffer[28..34], 10)?
} else {
parse_number("owner ID", &buffer[28..34], 10)?
} as u32;
let gid = if *variant == Variant::GNU {
parse_number_permitting_empty("group ID", &buffer[34..40], 10)?
} else {
parse_number("group ID", &buffer[34..40], 10)?
} as u32;
let mode = parse_number("file mode", &buffer[40..48], 8)? as u32;
if *variant != Variant::GNU && identifier.starts_with(b"#1/") {
*variant = Variant::BSD;
let padded_length =
parse_number("BSD filename length", &buffer[3..16], 10)?;
if size < padded_length {
let msg = format!(
"Entry size ({}) smaller than extended \
entry identifier length ({})",
size, padded_length
);
return Err(Error::new(ErrorKind::InvalidData, msg));
}
size -= padded_length;
header_len += padded_length;
let mut id_buffer = vec![0; padded_length as usize];
let bytes_read = reader.read(&mut id_buffer)?;
if bytes_read < id_buffer.len() {
if let Err(error) =
reader.read_exact(&mut id_buffer[bytes_read..])
{
if error.kind() == ErrorKind::UnexpectedEof {
let msg = "unexpected EOF in the middle of extended \
entry identifier";
return Err(Error::new(ErrorKind::UnexpectedEof, msg));
} else {
let msg = "failed to read extended entry identifier";
return Err(annotate(error, msg));
}
}
}
while id_buffer.last() == Some(&0) {
id_buffer.pop();
}
identifier = id_buffer;
if identifier == BSD_SYMBOL_LOOKUP_TABLE_ID
|| identifier == BSD_SORTED_SYMBOL_LOOKUP_TABLE_ID
{
io::copy(&mut reader.by_ref().take(size), &mut io::sink())?;
return Ok(Some((Header::new(identifier, size), header_len)));
}
}
Ok(Some((
Header { identifier, mtime, uid, gid, mode, size },
header_len,
)))
}
fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
if self.identifier.len() > 16 || self.identifier.contains(&b' ') {
let padding_length = (4 - self.identifier.len() % 4) % 4;
let padded_length = self.identifier.len() + padding_length;
write!(
writer,
"#1/{:<13}{:<12}{:<6}{:<6}{:<8o}{:<10}`\n",
padded_length,
self.mtime,
self.uid,
self.gid,
self.mode,
self.size + padded_length as u64
)?;
writer.write_all(&self.identifier)?;
writer.write_all(&vec![0; padding_length])?;
} else {
writer.write_all(&self.identifier)?;
writer.write_all(&vec![b' '; 16 - self.identifier.len()])?;
write!(
writer,
"{:<12}{:<6}{:<6}{:<8o}{:<10}`\n",
self.mtime, self.uid, self.gid, self.mode, self.size
)?;
}
Ok(())
}
fn write_gnu<W>(
&self,
writer: &mut W,
names: &HashMap<Vec<u8>, usize>,
) -> Result<()>
where
W: Write,
{
if self.identifier.len() > 15 {
let offset = names[&self.identifier];
write!(writer, "/{:<15}", offset)?;
} else {
writer.write_all(&self.identifier)?;
writer.write_all(b"/")?;
writer.write_all(&vec![b' '; 15 - self.identifier.len()])?;
}
write!(
writer,
"{:<12}{:<6}{:<6}{:<8o}{:<10}`\n",
self.mtime, self.uid, self.gid, self.mode, self.size
)?;
Ok(())
}
}
fn parse_number(field_name: &str, bytes: &[u8], radix: u32) -> Result<u64> {
if let Ok(string) = str::from_utf8(bytes) {
if let Ok(value) = u64::from_str_radix(string.trim_end(), radix) {
return Ok(value);
}
}
let msg = format!(
"Invalid {} field in entry header ({:?})",
field_name,
String::from_utf8_lossy(bytes)
);
Err(Error::new(ErrorKind::InvalidData, msg))
}
fn parse_number_permitting_empty(
field_name: &str,
bytes: &[u8],
radix: u32,
) -> Result<u64> {
if let Ok(string) = str::from_utf8(bytes) {
let trimmed = string.trim_end();
if trimmed.len() == 0 {
return Ok(0);
} else if let Ok(value) = u64::from_str_radix(trimmed, radix) {
return Ok(value);
}
}
let msg = format!(
"Invalid {} field in entry header ({:?})",
field_name,
String::from_utf8_lossy(bytes)
);
Err(Error::new(ErrorKind::InvalidData, msg))
}
struct HeaderAndLocation {
header: Header,
header_start: u64,
data_start: u64,
}
pub struct Archive<R: Read> {
reader: R,
variant: Variant,
name_table: Vec<u8>,
entry_headers: Vec<HeaderAndLocation>,
new_entry_start: u64,
next_entry_index: usize,
symbol_table_header: Option<HeaderAndLocation>,
symbol_table: Option<Vec<(Vec<u8>, u64)>>,
started: bool, padding: bool, scanned: bool, error: bool, }
impl<R: Read> Archive<R> {
pub fn new(reader: R) -> Archive<R> {
Archive {
reader,
variant: Variant::Common,
name_table: Vec::new(),
entry_headers: Vec::new(),
new_entry_start: GLOBAL_HEADER_LEN as u64,
next_entry_index: 0,
symbol_table_header: None,
symbol_table: None,
started: false,
padding: false,
scanned: false,
error: false,
}
}
pub fn variant(&self) -> Variant {
self.variant
}
pub fn into_inner(self) -> Result<R> {
Ok(self.reader)
}
fn is_name_table_id(&self, identifier: &[u8]) -> bool {
self.variant == Variant::GNU
&& identifier == GNU_NAME_TABLE_ID.as_bytes()
}
fn is_symbol_lookup_table_id(&self, identifier: &[u8]) -> bool {
match self.variant {
Variant::Common => false,
Variant::BSD => {
identifier == BSD_SYMBOL_LOOKUP_TABLE_ID
|| identifier == BSD_SORTED_SYMBOL_LOOKUP_TABLE_ID
}
Variant::GNU => identifier == GNU_SYMBOL_LOOKUP_TABLE_ID,
}
}
fn read_global_header_if_necessary(&mut self) -> Result<()> {
if self.started {
return Ok(());
}
let mut buffer = [0; GLOBAL_HEADER_LEN];
match self.reader.read_exact(&mut buffer) {
Ok(()) => {}
Err(error) => {
self.error = true;
return Err(annotate(error, "failed to read global header"));
}
}
if &buffer != GLOBAL_HEADER {
self.error = true;
let msg = "Not an archive file (invalid global header)";
return Err(Error::new(ErrorKind::InvalidData, msg));
}
self.started = true;
Ok(())
}
pub fn next_entry(&mut self) -> Option<Result<Entry<R>>> {
loop {
if self.error {
return None;
}
if self.scanned
&& self.next_entry_index == self.entry_headers.len()
{
return None;
}
match self.read_global_header_if_necessary() {
Ok(()) => {}
Err(error) => return Some(Err(error)),
}
if self.padding {
let mut buffer = [0u8; 1];
match self.reader.read_exact(&mut buffer) {
Ok(()) => {
if buffer[0] != b'\n' {
self.error = true;
let msg = format!(
"invalid padding byte ({})",
buffer[0]
);
let error =
Error::new(ErrorKind::InvalidData, msg);
return Some(Err(error));
}
}
Err(error) => {
if error.kind() != ErrorKind::UnexpectedEof {
self.error = true;
let msg = "failed to read padding byte";
return Some(Err(annotate(error, msg)));
}
}
}
self.padding = false;
}
let header_start = self.new_entry_start;
match Header::read(
&mut self.reader,
&mut self.variant,
&mut self.name_table,
) {
Ok(Some((header, header_len))) => {
let size = header.size();
if size % 2 != 0 {
self.padding = true;
}
if self.next_entry_index == self.entry_headers.len() {
self.new_entry_start += header_len + size + (size % 2);
}
if self.is_name_table_id(header.identifier()) {
continue;
}
if self.is_symbol_lookup_table_id(header.identifier()) {
self.symbol_table_header = Some(HeaderAndLocation {
header,
header_start,
data_start: header_start + header_len,
});
continue;
}
if self.next_entry_index == self.entry_headers.len() {
self.entry_headers.push(HeaderAndLocation {
header,
header_start,
data_start: header_start + header_len,
});
}
let header =
&self.entry_headers[self.next_entry_index].header;
self.next_entry_index += 1;
return Some(Ok(Entry {
header,
reader: self.reader.by_ref(),
length: size,
position: 0,
}));
}
Ok(None) => {
self.scanned = true;
return None;
}
Err(error) => {
self.error = true;
return Some(Err(error));
}
}
}
}
}
impl<R: Read + Seek> Archive<R> {
fn scan_if_necessary(&mut self) -> io::Result<()> {
if self.scanned {
return Ok(());
}
self.read_global_header_if_necessary()?;
loop {
let header_start = self.new_entry_start;
self.reader.seek(SeekFrom::Start(header_start))?;
if let Some((header, header_len)) = Header::read(
&mut self.reader,
&mut self.variant,
&mut self.name_table,
)? {
let size = header.size();
self.new_entry_start += header_len + size + (size % 2);
if self.is_name_table_id(header.identifier()) {
continue;
}
if self.is_symbol_lookup_table_id(header.identifier()) {
self.symbol_table_header = Some(HeaderAndLocation {
header,
header_start,
data_start: header_start + header_len,
});
continue;
}
self.entry_headers.push(HeaderAndLocation {
header,
header_start,
data_start: header_start + header_len,
});
} else {
break;
}
}
if self.next_entry_index < self.entry_headers.len() {
let offset =
self.entry_headers[self.next_entry_index].header_start;
self.reader.seek(SeekFrom::Start(offset))?;
}
self.scanned = true;
Ok(())
}
pub fn count_entries(&mut self) -> io::Result<usize> {
self.scan_if_necessary()?;
Ok(self.entry_headers.len())
}
pub fn jump_to_entry(&mut self, index: usize) -> io::Result<Entry<R>> {
self.scan_if_necessary()?;
if index >= self.entry_headers.len() {
let msg = "Entry index out of bounds";
return Err(Error::new(ErrorKind::InvalidInput, msg));
}
let offset = self.entry_headers[index].data_start;
self.reader.seek(SeekFrom::Start(offset))?;
let header = &self.entry_headers[index].header;
let size = header.size();
if size % 2 != 0 {
self.padding = true;
} else {
self.padding = false;
}
self.next_entry_index = index + 1;
Ok(Entry {
header,
reader: self.reader.by_ref(),
length: size,
position: 0,
})
}
fn parse_symbol_table_if_necessary(&mut self) -> io::Result<()> {
self.scan_if_necessary()?;
if self.symbol_table.is_some() {
return Ok(());
}
if let Some(ref header_and_loc) = self.symbol_table_header {
let offset = header_and_loc.data_start;
self.reader.seek(SeekFrom::Start(offset))?;
let mut reader = BufReader::new(
self.reader.by_ref().take(header_and_loc.header.size()),
);
if self.variant == Variant::GNU {
let num_symbols = read_be_u32(&mut reader)? as usize;
let mut symbol_offsets =
Vec::<u32>::with_capacity(num_symbols);
for _ in 0..num_symbols {
let offset = read_be_u32(&mut reader)?;
symbol_offsets.push(offset);
}
let mut symbol_table = Vec::with_capacity(num_symbols);
for offset in symbol_offsets.into_iter() {
let mut buffer = Vec::<u8>::new();
reader.read_until(0, &mut buffer)?;
if buffer.last() == Some(&0) {
buffer.pop();
}
buffer.shrink_to_fit();
symbol_table.push((buffer, offset as u64));
}
self.symbol_table = Some(symbol_table);
} else {
let num_symbols = (read_le_u32(&mut reader)? / 8) as usize;
let mut symbol_offsets =
Vec::<(u32, u32)>::with_capacity(num_symbols);
for _ in 0..num_symbols {
let str_offset = read_le_u32(&mut reader)?;
let file_offset = read_le_u32(&mut reader)?;
symbol_offsets.push((str_offset, file_offset));
}
let str_table_len = read_le_u32(&mut reader)?;
let mut str_table_data = vec![0u8; str_table_len as usize];
reader.read_exact(&mut str_table_data).map_err(|err| {
annotate(err, "failed to read string table")
})?;
let mut symbol_table = Vec::with_capacity(num_symbols);
for (str_start, file_offset) in symbol_offsets.into_iter() {
let str_start = str_start as usize;
let mut str_end = str_start;
while str_end < str_table_data.len()
&& str_table_data[str_end] != 0u8
{
str_end += 1;
}
let string = &str_table_data[str_start..str_end];
symbol_table.push((string.to_vec(), file_offset as u64));
}
self.symbol_table = Some(symbol_table);
}
}
if self.entry_headers.len() > 0 {
let offset =
self.entry_headers[self.next_entry_index].header_start;
self.reader.seek(SeekFrom::Start(offset))?;
}
Ok(())
}
pub fn symbols(&mut self) -> io::Result<Symbols<R>> {
self.parse_symbol_table_if_necessary()?;
Ok(Symbols { archive: self, index: 0 })
}
}
pub struct Entry<'a, R: 'a + Read> {
header: &'a Header,
reader: &'a mut R,
length: u64,
position: u64,
}
impl<'a, R: 'a + Read> Entry<'a, R> {
pub fn header(&self) -> &Header {
self.header
}
}
impl<'a, R: 'a + Read> Read for Entry<'a, R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
debug_assert!(self.position <= self.length);
if self.position == self.length {
return Ok(0);
}
let max_len =
cmp::min(self.length - self.position, buf.len() as u64) as usize;
let bytes_read = self.reader.read(&mut buf[0..max_len])?;
self.position += bytes_read as u64;
debug_assert!(self.position <= self.length);
Ok(bytes_read)
}
}
impl<'a, R: 'a + Read + Seek> Seek for Entry<'a, R> {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
let delta = match pos {
SeekFrom::Start(offset) => offset as i64 - self.position as i64,
SeekFrom::End(offset) => {
self.length as i64 + offset - self.position as i64
}
SeekFrom::Current(delta) => delta,
};
let new_position = self.position as i64 + delta;
if new_position < 0 {
let msg = format!(
"Invalid seek to negative position ({})",
new_position
);
return Err(Error::new(ErrorKind::InvalidInput, msg));
}
let new_position = new_position as u64;
if new_position > self.length {
let msg = format!(
"Invalid seek to position past end of entry ({} vs. {})",
new_position, self.length
);
return Err(Error::new(ErrorKind::InvalidInput, msg));
}
self.reader.seek(SeekFrom::Current(delta))?;
self.position = new_position;
Ok(self.position)
}
}
impl<'a, R: 'a + Read> Drop for Entry<'a, R> {
fn drop(&mut self) {
if self.position < self.length {
let mut remaining = self.reader.take(self.length - self.position);
let _ = io::copy(&mut remaining, &mut io::sink());
}
}
}
pub struct Symbols<'a, R: 'a + Read> {
archive: &'a Archive<R>,
index: usize,
}
impl<'a, R: Read> Iterator for Symbols<'a, R> {
type Item = &'a [u8];
fn next(&mut self) -> Option<&'a [u8]> {
if let Some(ref table) = self.archive.symbol_table {
if self.index < table.len() {
let next = table[self.index].0.as_slice();
self.index += 1;
return Some(next);
}
}
None
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = if let Some(ref table) = self.archive.symbol_table {
table.len() - self.index
} else {
0
};
(remaining, Some(remaining))
}
}
impl<'a, R: Read> ExactSizeIterator for Symbols<'a, R> {}
pub struct Builder<W: Write> {
writer: W,
started: bool,
}
impl<W: Write> Builder<W> {
pub fn new(writer: W) -> Builder<W> {
Builder { writer, started: false }
}
pub fn into_inner(self) -> Result<W> {
Ok(self.writer)
}
pub fn append<R: Read>(
&mut self,
header: &Header,
mut data: R,
) -> Result<()> {
if !self.started {
self.writer.write_all(GLOBAL_HEADER)?;
self.started = true;
}
header.write(&mut self.writer)?;
let actual_size = io::copy(&mut data, &mut self.writer)?;
if actual_size != header.size() {
let msg = format!(
"Wrong file size (header.size() = {}, actual \
size was {})",
header.size(),
actual_size
);
return Err(Error::new(ErrorKind::InvalidData, msg));
}
if actual_size % 2 != 0 {
self.writer.write_all(&['\n' as u8])?;
}
Ok(())
}
pub fn append_path<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let name: &OsStr = path.as_ref().file_name().ok_or_else(|| {
let msg = "Given path doesn't have a file name";
Error::new(ErrorKind::InvalidInput, msg)
})?;
let identifier = osstr_to_bytes(name)?;
let mut file = File::open(&path)?;
self.append_file_id(identifier, &mut file)
}
pub fn append_file(&mut self, name: &[u8], file: &mut File) -> Result<()> {
self.append_file_id(name.to_vec(), file)
}
fn append_file_id(&mut self, id: Vec<u8>, file: &mut File) -> Result<()> {
let metadata = file.metadata()?;
let header = Header::from_metadata(id, &metadata);
self.append(&header, file)
}
}
pub struct GnuBuilder<W: Write> {
writer: W,
short_names: HashSet<Vec<u8>>,
long_names: HashMap<Vec<u8>, usize>,
name_table_size: usize,
name_table_needs_padding: bool,
started: bool,
}
impl<W: Write> GnuBuilder<W> {
pub fn new(writer: W, identifiers: Vec<Vec<u8>>) -> GnuBuilder<W> {
let mut short_names = HashSet::<Vec<u8>>::new();
let mut long_names = HashMap::<Vec<u8>, usize>::new();
let mut name_table_size: usize = 0;
for identifier in identifiers.into_iter() {
let length = identifier.len();
if length > 15 {
long_names.insert(identifier, name_table_size);
name_table_size += length + 2;
} else {
short_names.insert(identifier);
}
}
let name_table_needs_padding = name_table_size % 2 != 0;
if name_table_needs_padding {
name_table_size += 3; }
GnuBuilder {
writer,
short_names,
long_names,
name_table_size,
name_table_needs_padding,
started: false,
}
}
pub fn into_inner(self) -> Result<W> {
Ok(self.writer)
}
pub fn append<R: Read>(
&mut self,
header: &Header,
mut data: R,
) -> Result<()> {
let is_long_name = header.identifier().len() > 15;
let has_name = if is_long_name {
self.long_names.contains_key(header.identifier())
} else {
self.short_names.contains(header.identifier())
};
if !has_name {
let msg = format!(
"Identifier {:?} was not in the list of \
identifiers passed to GnuBuilder::new()",
String::from_utf8_lossy(header.identifier())
);
return Err(Error::new(ErrorKind::InvalidInput, msg));
}
if !self.started {
self.writer.write_all(GLOBAL_HEADER)?;
if !self.long_names.is_empty() {
write!(
self.writer,
"{:<48}{:<10}`\n",
GNU_NAME_TABLE_ID, self.name_table_size
)?;
let mut entries: Vec<(usize, &[u8])> = self
.long_names
.iter()
.map(|(id, &start)| (start, id.as_slice()))
.collect();
entries.sort();
for (_, id) in entries {
self.writer.write_all(id)?;
self.writer.write_all(b"/\n")?;
}
if self.name_table_needs_padding {
self.writer.write_all(b" /\n")?;
}
}
self.started = true;
}
header.write_gnu(&mut self.writer, &self.long_names)?;
let actual_size = io::copy(&mut data, &mut self.writer)?;
if actual_size != header.size() {
let msg = format!(
"Wrong file size (header.size() = {}, actual \
size was {})",
header.size(),
actual_size
);
return Err(Error::new(ErrorKind::InvalidData, msg));
}
if actual_size % 2 != 0 {
self.writer.write_all(&['\n' as u8])?;
}
Ok(())
}
pub fn append_path<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let name: &OsStr = path.as_ref().file_name().ok_or_else(|| {
let msg = "Given path doesn't have a file name";
Error::new(ErrorKind::InvalidInput, msg)
})?;
let identifier = osstr_to_bytes(name)?;
let mut file = File::open(&path)?;
self.append_file_id(identifier, &mut file)
}
pub fn append_file(&mut self, name: &[u8], file: &mut File) -> Result<()> {
self.append_file_id(name.to_vec(), file)
}
fn append_file_id(&mut self, id: Vec<u8>, file: &mut File) -> Result<()> {
let metadata = file.metadata()?;
let header = Header::from_metadata(id, &metadata);
self.append(&header, file)
}
}
#[cfg(unix)]
fn osstr_to_bytes(string: &OsStr) -> Result<Vec<u8>> {
Ok(string.as_bytes().to_vec())
}
#[cfg(not(unix))]
fn osstr_to_bytes(string: &OsStr) -> Result<Vec<u8>> {
let utf8: &str = string.to_str().ok_or_else(|| {
Error::new(ErrorKind::InvalidData, "Non-UTF8 file name")
})?;
Ok(utf8.as_bytes().to_vec())
}
fn annotate(error: io::Error, msg: &str) -> io::Error {
let kind = error.kind();
if let Some(inner) = error.into_inner() {
io::Error::new(kind, format!("{}: {}", msg, inner))
} else {
io::Error::new(kind, msg)
}
}
#[cfg(test)]
mod tests {
use super::{Archive, Builder, GnuBuilder, Header, Variant};
use std::io::{Cursor, Read, Result, Seek, SeekFrom};
use std::str;
struct SlowReader<'a> {
current_position: usize,
buffer: &'a [u8],
}
impl<'a> Read for SlowReader<'a> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
if self.current_position >= self.buffer.len() {
return Ok(0);
}
buf[0] = self.buffer[self.current_position];
self.current_position += 1;
return Ok(1);
}
}
#[test]
fn build_common_archive() {
let mut builder = Builder::new(Vec::new());
let mut header1 = Header::new(b"foo.txt".to_vec(), 7);
header1.set_mtime(1487552916);
header1.set_uid(501);
header1.set_gid(20);
header1.set_mode(0o100644);
builder.append(&header1, "foobar\n".as_bytes()).unwrap();
let header2 = Header::new(b"baz.txt".to_vec(), 4);
builder.append(&header2, "baz\n".as_bytes()).unwrap();
let actual = builder.into_inner().unwrap();
let expected = "\
!<arch>\n\
foo.txt 1487552916 501 20 100644 7 `\n\
foobar\n\n\
baz.txt 0 0 0 0 4 `\n\
baz\n";
assert_eq!(str::from_utf8(&actual).unwrap(), expected);
}
#[test]
fn build_bsd_archive_with_long_filenames() {
let mut builder = Builder::new(Vec::new());
let mut header1 = Header::new(b"short".to_vec(), 1);
header1.set_identifier(b"this_is_a_very_long_filename.txt".to_vec());
header1.set_mtime(1487552916);
header1.set_uid(501);
header1.set_gid(20);
header1.set_mode(0o100644);
header1.set_size(7);
builder.append(&header1, "foobar\n".as_bytes()).unwrap();
let header2 = Header::new(
b"and_this_is_another_very_long_filename.txt".to_vec(),
4,
);
builder.append(&header2, "baz\n".as_bytes()).unwrap();
let actual = builder.into_inner().unwrap();
let expected = "\
!<arch>\n\
#1/32 1487552916 501 20 100644 39 `\n\
this_is_a_very_long_filename.txtfoobar\n\n\
#1/44 0 0 0 0 48 `\n\
and_this_is_another_very_long_filename.txt\x00\x00baz\n";
assert_eq!(str::from_utf8(&actual).unwrap(), expected);
}
#[test]
fn build_bsd_archive_with_space_in_filename() {
let mut builder = Builder::new(Vec::new());
let header = Header::new(b"foo bar".to_vec(), 4);
builder.append(&header, "baz\n".as_bytes()).unwrap();
let actual = builder.into_inner().unwrap();
let expected = "\
!<arch>\n\
#1/8 0 0 0 0 12 `\n\
foo bar\x00baz\n";
assert_eq!(str::from_utf8(&actual).unwrap(), expected);
}
#[test]
fn build_gnu_archive() {
let names = vec![b"baz.txt".to_vec(), b"foo.txt".to_vec()];
let mut builder = GnuBuilder::new(Vec::new(), names);
let mut header1 = Header::new(b"foo.txt".to_vec(), 7);
header1.set_mtime(1487552916);
header1.set_uid(501);
header1.set_gid(20);
header1.set_mode(0o100644);
builder.append(&header1, "foobar\n".as_bytes()).unwrap();
let header2 = Header::new(b"baz.txt".to_vec(), 4);
builder.append(&header2, "baz\n".as_bytes()).unwrap();
let actual = builder.into_inner().unwrap();
let expected = "\
!<arch>\n\
foo.txt/ 1487552916 501 20 100644 7 `\n\
foobar\n\n\
baz.txt/ 0 0 0 0 4 `\n\
baz\n";
assert_eq!(str::from_utf8(&actual).unwrap(), expected);
}
#[test]
fn build_gnu_archive_with_long_filenames() {
let names = vec![
b"this_is_a_very_long_filename.txt".to_vec(),
b"and_this_is_another_very_long_filename.txt".to_vec(),
];
let mut builder = GnuBuilder::new(Vec::new(), names);
let mut header1 = Header::new(b"short".to_vec(), 1);
header1.set_identifier(b"this_is_a_very_long_filename.txt".to_vec());
header1.set_mtime(1487552916);
header1.set_uid(501);
header1.set_gid(20);
header1.set_mode(0o100644);
header1.set_size(7);
builder.append(&header1, "foobar\n".as_bytes()).unwrap();
let header2 = Header::new(
b"and_this_is_another_very_long_filename.txt".to_vec(),
4,
);
builder.append(&header2, "baz\n".as_bytes()).unwrap();
let actual = builder.into_inner().unwrap();
let expected = "\
!<arch>\n\
// 78 `\n\
this_is_a_very_long_filename.txt/\n\
and_this_is_another_very_long_filename.txt/\n\
/0 1487552916 501 20 100644 7 `\n\
foobar\n\n\
/34 0 0 0 0 4 `\n\
baz\n";
assert_eq!(str::from_utf8(&actual).unwrap(), expected);
}
#[test]
fn build_gnu_archive_with_space_in_filename() {
let names = vec![b"foo bar".to_vec()];
let mut builder = GnuBuilder::new(Vec::new(), names);
let header = Header::new(b"foo bar".to_vec(), 4);
builder.append(&header, "baz\n".as_bytes()).unwrap();
let actual = builder.into_inner().unwrap();
let expected = "\
!<arch>\n\
foo bar/ 0 0 0 0 4 `\n\
baz\n";
assert_eq!(str::from_utf8(&actual).unwrap(), expected);
}
#[test]
#[should_panic(
expected = "Identifier \\\"bar\\\" was not in the list of \
identifiers passed to GnuBuilder::new()"
)]
fn build_gnu_archive_with_unexpected_identifier() {
let names = vec![b"foo".to_vec()];
let mut builder = GnuBuilder::new(Vec::new(), names);
let header = Header::new(b"bar".to_vec(), 4);
builder.append(&header, "baz\n".as_bytes()).unwrap();
}
#[test]
fn read_common_archive() {
let input = "\
!<arch>\n\
foo.txt 1487552916 501 20 100644 7 `\n\
foobar\n\n\
bar.awesome.txt 1487552919 501 20 100644 22 `\n\
This file is awesome!\n\
baz.txt 1487552349 42 12345 100664 4 `\n\
baz\n";
let reader =
SlowReader { current_position: 0, buffer: input.as_bytes() };
let mut archive = Archive::new(reader);
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), b"foo.txt");
assert_eq!(entry.header().mtime(), 1487552916);
assert_eq!(entry.header().uid(), 501);
assert_eq!(entry.header().gid(), 20);
assert_eq!(entry.header().mode(), 0o100644);
assert_eq!(entry.header().size(), 7);
let mut buffer = [0; 4];
entry.read_exact(&mut buffer).unwrap();
assert_eq!(&buffer, "foob".as_bytes());
}
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), b"bar.awesome.txt");
assert_eq!(entry.header().size(), 22);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "This file is awesome!\n".as_bytes());
}
{
let entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), b"baz.txt");
assert_eq!(entry.header().size(), 4);
}
assert!(archive.next_entry().is_none());
assert_eq!(archive.variant(), Variant::Common);
}
#[test]
fn read_bsd_archive_with_long_filenames() {
let input = "\
!<arch>\n\
#1/32 1487552916 501 20 100644 39 `\n\
this_is_a_very_long_filename.txtfoobar\n\n\
#1/44 0 0 0 0 48 `\n\
and_this_is_another_very_long_filename.txt\x00\x00baz\n";
let mut archive = Archive::new(input.as_bytes());
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(
entry.header().identifier(),
"this_is_a_very_long_filename.txt".as_bytes()
);
assert_eq!(entry.header().mtime(), 1487552916);
assert_eq!(entry.header().uid(), 501);
assert_eq!(entry.header().gid(), 20);
assert_eq!(entry.header().mode(), 0o100644);
assert_eq!(entry.header().size(), 7);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
}
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(
entry.header().identifier(),
"and_this_is_another_very_long_filename.txt".as_bytes()
);
assert_eq!(entry.header().size(), 4);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
}
assert!(archive.next_entry().is_none());
assert_eq!(archive.variant(), Variant::BSD);
}
#[test]
fn read_bsd_archive_with_space_in_filename() {
let input = "\
!<arch>\n\
#1/8 0 0 0 0 12 `\n\
foo bar\x00baz\n";
let mut archive = Archive::new(input.as_bytes());
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), "foo bar".as_bytes());
assert_eq!(entry.header().size(), 4);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
}
assert!(archive.next_entry().is_none());
assert_eq!(archive.variant(), Variant::BSD);
}
#[test]
fn read_gnu_archive() {
let input = "\
!<arch>\n\
foo.txt/ 1487552916 501 20 100644 7 `\n\
foobar\n\n\
bar.awesome.txt/1487552919 501 20 100644 22 `\n\
This file is awesome!\n\
baz.txt/ 1487552349 42 12345 100664 4 `\n\
baz\n";
let mut archive = Archive::new(input.as_bytes());
{
let entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), "foo.txt".as_bytes());
assert_eq!(entry.header().size(), 7);
}
{
let entry = archive.next_entry().unwrap().unwrap();
assert_eq!(
entry.header().identifier(),
"bar.awesome.txt".as_bytes()
);
assert_eq!(entry.header().size(), 22);
}
{
let entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), "baz.txt".as_bytes());
assert_eq!(entry.header().size(), 4);
}
assert!(archive.next_entry().is_none());
assert_eq!(archive.variant(), Variant::GNU);
}
#[test]
fn read_gnu_archive_with_long_filenames() {
let input = "\
!<arch>\n\
// 78 `\n\
this_is_a_very_long_filename.txt/\n\
and_this_is_another_very_long_filename.txt/\n\
/0 1487552916 501 20 100644 7 `\n\
foobar\n\n\
/34 0 0 0 0 4 `\n\
baz\n";
let mut archive = Archive::new(input.as_bytes());
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(
entry.header().identifier(),
"this_is_a_very_long_filename.txt".as_bytes()
);
assert_eq!(entry.header().mtime(), 1487552916);
assert_eq!(entry.header().uid(), 501);
assert_eq!(entry.header().gid(), 20);
assert_eq!(entry.header().mode(), 0o100644);
assert_eq!(entry.header().size(), 7);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
}
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(
entry.header().identifier(),
"and_this_is_another_very_long_filename.txt".as_bytes()
);
assert_eq!(entry.header().size(), 4);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
}
assert!(archive.next_entry().is_none());
assert_eq!(archive.variant(), Variant::GNU);
}
#[test]
fn read_ms_archive_with_long_filenames() {
let input = "\
!<arch>\n\
// 76 `\n\
this_is_a_very_long_filename.txt\x00\
and_this_is_another_very_long_filename.txt\x00\
/0 1487552916 100644 7 `\n\
foobar\n\n\
/33 1446790218 100666 4 `\n\
baz\n";
let mut archive = Archive::new(input.as_bytes());
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(
entry.header().identifier(),
"this_is_a_very_long_filename.txt".as_bytes()
);
assert_eq!(entry.header().mtime(), 1487552916);
assert_eq!(entry.header().uid(), 0);
assert_eq!(entry.header().gid(), 0);
assert_eq!(entry.header().mode(), 0o100644);
assert_eq!(entry.header().size(), 7);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
}
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(
entry.header().identifier(),
"and_this_is_another_very_long_filename.txt".as_bytes()
);
assert_eq!(entry.header().size(), 4);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
}
assert!(archive.next_entry().is_none());
assert_eq!(archive.variant(), Variant::GNU);
}
#[test]
fn read_gnu_archive_with_space_in_filename() {
let input = "\
!<arch>\n\
foo bar/ 0 0 0 0 4 `\n\
baz\n";
let mut archive = Archive::new(input.as_bytes());
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), "foo bar".as_bytes());
assert_eq!(entry.header().size(), 4);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
}
assert!(archive.next_entry().is_none());
assert_eq!(archive.variant(), Variant::GNU);
}
#[test]
fn read_gnu_archive_with_symbol_lookup_table() {
let input = b"\
!<arch>\n\
/ 0 0 0 0 15 `\n\
\x00\x00\x00\x01\x00\x00\x00\xb2foobar\x00\n\
// 34 `\n\
this_is_a_very_long_filename.txt/\n\
/0 1487552916 501 20 100644 7 `\n\
foobar\n";
let mut archive = Archive::new(input as &[u8]);
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(
entry.header().identifier(),
"this_is_a_very_long_filename.txt".as_bytes()
);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
}
assert!(archive.next_entry().is_none());
}
#[test]
fn read_archive_with_no_padding_byte_in_final_entry() {
let input = "\
!<arch>\n\
foo.txt 1487552916 501 20 100644 7 `\n\
foobar\n\n\
bar.txt 1487552919 501 20 100644 3 `\n\
foo";
let mut archive = Archive::new(input.as_bytes());
{
let entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), "foo.txt".as_bytes());
assert_eq!(entry.header().size(), 7);
}
{
let entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), "bar.txt".as_bytes());
assert_eq!(entry.header().size(), 3);
}
assert!(archive.next_entry().is_none());
}
#[test]
#[should_panic(expected = "Invalid timestamp field in entry header \
(\\\"helloworld \\\")")]
fn read_archive_with_invalid_mtime() {
let input = "\
!<arch>\n\
foo.txt helloworld 501 20 100644 7 `\n\
foobar\n\n";
let mut archive = Archive::new(input.as_bytes());
archive.next_entry().unwrap().unwrap();
}
#[test]
#[should_panic(expected = "Invalid owner ID field in entry header \
(\\\"foo \\\")")]
fn read_archive_with_invalid_uid() {
let input = "\
!<arch>\n\
foo.txt 1487552916 foo 20 100644 7 `\n\
foobar\n\n";
let mut archive = Archive::new(input.as_bytes());
archive.next_entry().unwrap().unwrap();
}
#[test]
#[should_panic(expected = "Invalid group ID field in entry header \
(\\\"bar \\\")")]
fn read_archive_with_invalid_gid() {
let input = "\
!<arch>\n\
foo.txt 1487552916 501 bar 100644 7 `\n\
foobar\n\n";
let mut archive = Archive::new(input.as_bytes());
archive.next_entry().unwrap().unwrap();
}
#[test]
#[should_panic(expected = "Invalid file mode field in entry header \
(\\\"foobar \\\")")]
fn read_archive_with_invalid_mode() {
let input = "\
!<arch>\n\
foo.txt 1487552916 501 20 foobar 7 `\n\
foobar\n\n";
let mut archive = Archive::new(input.as_bytes());
archive.next_entry().unwrap().unwrap();
}
#[test]
#[should_panic(expected = "Invalid file size field in entry header \
(\\\"whatever \\\")")]
fn read_archive_with_invalid_size() {
let input = "\
!<arch>\n\
foo.txt 1487552916 501 20 100644 whatever `\n\
foobar\n\n";
let mut archive = Archive::new(input.as_bytes());
archive.next_entry().unwrap().unwrap();
}
#[test]
#[should_panic(expected = "Invalid BSD filename length field in entry \
header (\\\"foobar \\\")")]
fn read_bsd_archive_with_invalid_filename_length() {
let input = "\
!<arch>\n\
#1/foobar 1487552916 501 20 100644 39 `\n\
this_is_a_very_long_filename.txtfoobar\n\n";
let mut archive = Archive::new(input.as_bytes());
archive.next_entry().unwrap().unwrap();
}
#[test]
#[should_panic(expected = "Invalid GNU filename index field in entry \
header (\\\"foobar \\\")")]
fn read_gnu_archive_with_invalid_filename_index() {
let input = "\
!<arch>\n\
// 34 `\n\
this_is_a_very_long_filename.txt/\n\
/foobar 1487552916 501 20 100644 7 `\n\
foobar\n\n";
let mut archive = Archive::new(input.as_bytes());
archive.next_entry().unwrap().unwrap();
}
#[test]
fn seek_within_entry() {
let input = "\
!<arch>\n\
foo.txt 1487552916 501 20 100644 31 `\n\
abcdefghij0123456789ABCDEFGHIJ\n\n\
bar.awesome.txt 1487552919 501 20 100644 22 `\n\
This file is awesome!\n";
let mut archive = Archive::new(Cursor::new(input.as_bytes()));
{
let mut entry = archive.next_entry().unwrap().unwrap();
let mut buffer = [0; 5];
entry.seek(SeekFrom::Start(10)).unwrap();
entry.read_exact(&mut buffer).unwrap();
assert_eq!(&buffer, "01234".as_bytes());
entry.seek(SeekFrom::Start(5)).unwrap();
entry.read_exact(&mut buffer).unwrap();
assert_eq!(&buffer, "fghij".as_bytes());
entry.seek(SeekFrom::End(-10)).unwrap();
entry.read_exact(&mut buffer).unwrap();
assert_eq!(&buffer, "BCDEF".as_bytes());
entry.seek(SeekFrom::End(-30)).unwrap();
entry.read_exact(&mut buffer).unwrap();
assert_eq!(&buffer, "bcdef".as_bytes());
entry.seek(SeekFrom::Current(10)).unwrap();
entry.read_exact(&mut buffer).unwrap();
assert_eq!(&buffer, "6789A".as_bytes());
entry.seek(SeekFrom::Current(-8)).unwrap();
entry.read_exact(&mut buffer).unwrap();
assert_eq!(&buffer, "34567".as_bytes());
}
{
let mut entry = archive.next_entry().unwrap().unwrap();
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "This file is awesome!\n".as_bytes());
}
}
#[test]
#[should_panic(expected = "Invalid seek to negative position (-17)")]
fn seek_entry_to_negative_position() {
let input = "\
!<arch>\n\
foo.txt 1487552916 501 20 100644 30 `\n\
abcdefghij0123456789ABCDEFGHIJ";
let mut archive = Archive::new(Cursor::new(input.as_bytes()));
let mut entry = archive.next_entry().unwrap().unwrap();
entry.seek(SeekFrom::End(-47)).unwrap();
}
#[test]
#[should_panic(expected = "Invalid seek to position past end of entry \
(47 vs. 30)")]
fn seek_entry_beyond_end() {
let input = "\
!<arch>\n\
foo.txt 1487552916 501 20 100644 30 `\n\
abcdefghij0123456789ABCDEFGHIJ";
let mut archive = Archive::new(Cursor::new(input.as_bytes()));
let mut entry = archive.next_entry().unwrap().unwrap();
entry.seek(SeekFrom::Start(47)).unwrap();
}
#[test]
fn count_entries_in_bsd_archive() {
let input = b"\
!<arch>\n\
#1/32 1487552916 501 20 100644 39 `\n\
this_is_a_very_long_filename.txtfoobar\n\n\
baz.txt 0 0 0 0 4 `\n\
baz\n";
let mut archive = Archive::new(Cursor::new(input as &[u8]));
assert_eq!(archive.count_entries().unwrap(), 2);
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(
entry.header().identifier(),
"this_is_a_very_long_filename.txt".as_bytes()
);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
}
assert_eq!(archive.count_entries().unwrap(), 2);
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), "baz.txt".as_bytes());
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
}
assert_eq!(archive.count_entries().unwrap(), 2);
}
#[test]
fn count_entries_in_gnu_archive() {
let input = b"\
!<arch>\n\
/ 0 0 0 0 15 `\n\
\x00\x00\x00\x01\x00\x00\x00\xb2foobar\x00\n\
// 34 `\n\
this_is_a_very_long_filename.txt/\n\
/0 1487552916 501 20 100644 7 `\n\
foobar\n\n\
baz.txt/ 1487552349 42 12345 100664 4 `\n\
baz\n";
let mut archive = Archive::new(Cursor::new(input as &[u8]));
assert_eq!(archive.count_entries().unwrap(), 2);
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(
entry.header().identifier(),
"this_is_a_very_long_filename.txt".as_bytes()
);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
}
assert_eq!(archive.count_entries().unwrap(), 2);
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), "baz.txt".as_bytes());
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
}
assert_eq!(archive.count_entries().unwrap(), 2);
}
#[test]
fn jump_to_entry_in_bsd_archive() {
let input = b"\
!<arch>\n\
hello.txt 1487552316 42 12345 100644 14 `\n\
Hello, world!\n\
#1/32 1487552916 501 20 100644 39 `\n\
this_is_a_very_long_filename.txtfoobar\n\n\
baz.txt 1487552349 42 12345 100664 4 `\n\
baz\n";
let mut archive = Archive::new(Cursor::new(input as &[u8]));
{
let mut entry = archive.jump_to_entry(1).unwrap();
assert_eq!(
entry.header().identifier(),
"this_is_a_very_long_filename.txt".as_bytes()
);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
}
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), "baz.txt".as_bytes());
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
}
assert!(archive.next_entry().is_none());
{
let mut entry = archive.jump_to_entry(0).unwrap();
assert_eq!(entry.header().identifier(), "hello.txt".as_bytes());
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "Hello, world!\n".as_bytes());
}
{
let mut entry = archive.jump_to_entry(1).unwrap();
assert_eq!(
entry.header().identifier(),
"this_is_a_very_long_filename.txt".as_bytes()
);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
}
{
let mut entry = archive.jump_to_entry(0).unwrap();
assert_eq!(entry.header().identifier(), "hello.txt".as_bytes());
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "Hello, world!\n".as_bytes());
}
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(
entry.header().identifier(),
"this_is_a_very_long_filename.txt".as_bytes()
);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
}
}
#[test]
fn jump_to_entry_in_gnu_archive() {
let input = b"\
!<arch>\n\
// 34 `\n\
this_is_a_very_long_filename.txt/\n\
hello.txt/ 1487552316 42 12345 100644 14 `\n\
Hello, world!\n\
/0 1487552916 501 20 100644 7 `\n\
foobar\n\n\
baz.txt/ 1487552349 42 12345 100664 4 `\n\
baz\n";
let mut archive = Archive::new(Cursor::new(input as &[u8]));
{
let mut entry = archive.jump_to_entry(1).unwrap();
assert_eq!(
entry.header().identifier(),
"this_is_a_very_long_filename.txt".as_bytes()
);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
}
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(entry.header().identifier(), "baz.txt".as_bytes());
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
}
assert!(archive.next_entry().is_none());
{
let mut entry = archive.jump_to_entry(0).unwrap();
assert_eq!(entry.header().identifier(), "hello.txt".as_bytes());
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "Hello, world!\n".as_bytes());
}
{
let mut entry = archive.next_entry().unwrap().unwrap();
assert_eq!(
entry.header().identifier(),
"this_is_a_very_long_filename.txt".as_bytes()
);
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).unwrap();
assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
}
}
#[test]
fn list_symbols_in_bsd_archive() {
let input = b"\
!<arch>\n\
#1/12 0 0 0 0 60 `\n\
__.SYMDEF\x00\x00\x00\x18\x00\x00\x00\
\x00\x00\x00\x00\x80\x00\x00\x00\
\x07\x00\x00\x00\x80\x00\x00\x00\
\x0b\x00\x00\x00\x80\x00\x00\x00\
\x10\x00\x00\x00foobar\x00baz\x00quux\x00\
foo.o/ 1487552916 501 20 100644 16 `\n\
foobar,baz,quux\n";
let mut archive = Archive::new(Cursor::new(input as &[u8]));
assert_eq!(archive.symbols().unwrap().len(), 3);
assert_eq!(archive.variant(), Variant::BSD);
let symbols = archive.symbols().unwrap().collect::<Vec<&[u8]>>();
let expected: Vec<&[u8]> = vec![b"foobar", b"baz", b"quux"];
assert_eq!(symbols, expected);
}
#[test]
fn list_sorted_symbols_in_bsd_archive() {
let input = b"\
!<arch>\n\
#1/16 0 0 0 0 64 `\n\
__.SYMDEF SORTED\x18\x00\x00\x00\
\x00\x00\x00\x00\x80\x00\x00\x00\
\x04\x00\x00\x00\x80\x00\x00\x00\
\x0b\x00\x00\x00\x80\x00\x00\x00\
\x10\x00\x00\x00baz\x00foobar\x00quux\x00\
foo.o/ 1487552916 501 20 100644 16 `\n\
foobar,baz,quux\n";
let mut archive = Archive::new(Cursor::new(input as &[u8]));
assert_eq!(archive.symbols().unwrap().len(), 3);
assert_eq!(archive.variant(), Variant::BSD);
let symbols = archive.symbols().unwrap().collect::<Vec<&[u8]>>();
let expected: Vec<&[u8]> = vec![b"baz", b"foobar", b"quux"];
assert_eq!(symbols, expected);
}
#[test]
fn list_symbols_in_gnu_archive() {
let input = b"\
!<arch>\n\
/ 0 0 0 0 32 `\n\
\x00\x00\x00\x03\x00\x00\x00\x5c\x00\x00\x00\x5c\x00\x00\x00\x5c\
foobar\x00baz\x00quux\x00\
foo.o/ 1487552916 501 20 100644 16 `\n\
foobar,baz,quux\n";
let mut archive = Archive::new(Cursor::new(input as &[u8]));
assert_eq!(archive.symbols().unwrap().len(), 3);
assert_eq!(archive.variant(), Variant::GNU);
let symbols = archive.symbols().unwrap().collect::<Vec<&[u8]>>();
let expected: Vec<&[u8]> = vec![b"foobar", b"baz", b"quux"];
assert_eq!(symbols, expected);
}
#[test]
fn non_multiple_of_two_long_ident_in_gnu_archive() {
let mut buffer = std::io::Cursor::new(Vec::new());
{
let filenames = vec![
b"rust.metadata.bin".to_vec(),
b"compiler_builtins-78891cf83a7d3547.dummy_name.rcgu.o"
.to_vec(),
];
let mut builder = GnuBuilder::new(&mut buffer, filenames.clone());
for filename in filenames {
builder
.append(&Header::new(filename, 1), &mut (&[b'?'] as &[u8]))
.expect("add file");
}
}
buffer.set_position(0);
let mut archive = Archive::new(buffer);
while let Some(entry) = archive.next_entry() {
entry.unwrap();
}
}
}