//! A library for encoding/decoding Unix archive files.
//!
//! This library provides utilities necessary to manage [Unix archive
//! files](https://en.wikipedia.org/wiki/Ar_(Unix)) (as generated by the
//! standard `ar` command line utility) abstracted over a reader or writer.
//! This library provides a streaming interface that avoids having to ever load
//! a full archive entry into memory.
//!
//! The API of this crate is meant to be similar to that of the
//! [`tar`](https://crates.io/crates/tar) crate.
//!
//! # Format variants
//!
//! Unix archive files come in several variants, of which three are the most
//! common:
//!
//! * The *common variant*, used for Debian package (`.deb`) files among other
//!   things, which only supports filenames up to 16 characters.
//! * The *BSD variant*, used by the `ar` utility on BSD systems (including Mac
//!   OS X), which is backwards-compatible with the common variant, but extends
//!   it to support longer filenames and filenames containing spaces.
//! * The *GNU variant*, used by the `ar` utility on GNU and many other systems
//!   (including Windows), which is similar to the common format, but which
//!   stores filenames in a slightly different, incompatible way, and has its
//!   own strategy for supporting long filenames.
//!
//! This crate supports reading and writing all three of these variants.
//!
//! # Example usage
//!
//! Writing an archive:
//!
//! ```no_run
//! use ar::Builder;
//! use std::fs::File;
//! // Create a new archive that will be written to foo.a:
//! let mut builder = Builder::new(File::create("foo.a").unwrap());
//! // Add foo/bar.txt to the archive, under the name "bar.txt":
//! builder.append_path("foo/bar.txt").unwrap();
//! // Add foo/baz.txt to the archive, under the name "hello.txt":
//! let mut file = File::open("foo/baz.txt").unwrap();
//! builder.append_file(b"hello.txt", &mut file).unwrap();
//! ```
//!
//! Reading an archive:
//!
//! ```no_run
//! use ar::Archive;
//! use std::fs::File;
//! use std::io;
//! use std::str;
//! // Read an archive from the file foo.a:
//! let mut archive = Archive::new(File::open("foo.a").unwrap());
//! // Iterate over all entries in the archive:
//! while let Some(entry_result) = archive.next_entry() {
//!     let mut entry = entry_result.unwrap();
//!     // Create a new file with the same name as the archive entry:
//!     let mut file = File::create(
//!         str::from_utf8(entry.header().identifier()).unwrap(),
//!     ).unwrap();
//!     // The Entry object also acts as an io::Read, so we can easily copy the
//!     // contents of the archive entry into the file:
//!     io::copy(&mut entry, &mut file).unwrap();
//! }
//! ```

#![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"/";

// ========================================================================= //

/// Variants of the Unix archive format.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Variant {
    /// Used by Debian package files; allows only short filenames.
    Common,
    /// Used by BSD `ar` (and OS X); backwards-compatible with common variant.
    BSD,
    /// Used by GNU `ar` (and Windows); incompatible with common variant.
    GNU,
}

// ========================================================================= //

/// Representation of an archive entry header.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Header {
    identifier: Vec<u8>,
    mtime: u64,
    uid: u32,
    gid: u32,
    mode: u32,
    size: u64,
}

impl Header {
    /// Creates a header with the given file identifier and size, and all
    /// other fields set to zero.
    pub fn new(identifier: Vec<u8>, size: u64) -> Header {
        Header { identifier, mtime: 0, uid: 0, gid: 0, mode: 0, size }
    }

    /// Creates a header with the given file identifier and all other fields
    /// set from the given filesystem metadata.
    #[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())
    }

    /// Returns the file identifier.
    pub fn identifier(&self) -> &[u8] {
        &self.identifier
    }

    /// Sets the file identifier.
    pub fn set_identifier(&mut self, identifier: Vec<u8>) {
        self.identifier = identifier;
    }

    /// Returns the last modification time in Unix time format.
    pub fn mtime(&self) -> u64 {
        self.mtime
    }

    /// Sets the last modification time in Unix time format.
    pub fn set_mtime(&mut self, mtime: u64) {
        self.mtime = mtime;
    }

    /// Returns the value of the owner's user ID field.
    pub fn uid(&self) -> u32 {
        self.uid
    }

    /// Sets the value of the owner's user ID field.
    pub fn set_uid(&mut self, uid: u32) {
        self.uid = uid;
    }

    /// Returns the value of the group's user ID field.
    pub fn gid(&self) -> u32 {
        self.gid
    }

    /// Returns the value of the group's user ID field.
    pub fn set_gid(&mut self, gid: u32) {
        self.gid = gid;
    }

    /// Returns the mode bits for this file.
    pub fn mode(&self) -> u32 {
        self.mode
    }

    /// Sets the mode bits for this file.
    pub fn set_mode(&mut self, mode: u32) {
        self.mode = mode;
    }

    /// Returns the length of the file, in bytes.
    pub fn size(&self) -> u64 {
        self.size
    }

    /// Sets the length of the file, in bytes.
    pub fn set_size(&mut self, size: u64) {
        self.size = size;
    }

    /// Parses and returns the next header and its length.  Returns `Ok(None)`
    /// if we are at EOF.
    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))
}

/*
 * Equivalent to parse_number() except for the case of bytes being
 * all spaces (eg all 0x20) as MS tools emit for UID/GID
 */
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,
}

// ========================================================================= //

/// A structure for reading archives.
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, // True if we've read past the global header.
    padding: bool, // True if there's a padding byte before the next entry.
    scanned: bool, // True if entry_headers is complete.
    error: bool,   // True if we have encountered an error.
}

impl<R: Read> Archive<R> {
    /// Create a new archive reader with the underlying reader object as the
    /// source of all data read.
    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,
        }
    }

    /// Returns which format variant this archive appears to be so far.
    ///
    /// Note that this may not be accurate before the archive has been fully
    /// read (i.e. before the `next_entry()` method returns `None`).  In
    /// particular, a new `Archive` object that hasn't yet read any data at all
    /// will always return `Variant::Common`.
    pub fn variant(&self) -> Variant {
        self.variant
    }

    /// Unwrap this archive reader, returning the underlying reader object.
    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(())
    }

    /// Reads the next entry from the archive, or returns None if there are no
    /// more.
    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;
            }
        }
        // Resume our previous position in the file.
        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(())
    }

    /// Scans the archive and returns the total number of entries in the
    /// archive (not counting special entries, such as the GNU archive name
    /// table or symbol table, that are not returned by `next_entry()`).
    pub fn count_entries(&mut self) -> io::Result<usize> {
        self.scan_if_necessary()?;
        Ok(self.entry_headers.len())
    }

    /// Scans the archive and jumps to the entry at the given index.  Returns
    /// an error if the index is not less than the result of `count_entries()`.
    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);
            }
        }
        // Resume our previous position in the file.
        if self.entry_headers.len() > 0 {
            let offset =
                self.entry_headers[self.next_entry_index].header_start;
            self.reader.seek(SeekFrom::Start(offset))?;
        }
        Ok(())
    }

    /// Scans the archive and returns an iterator over the symbols in the
    /// archive's symbol table.  If the archive doesn't have a symbol table,
    /// this method will still succeed, but the iterator won't produce any
    /// values.
    pub fn symbols(&mut self) -> io::Result<Symbols<R>> {
        self.parse_symbol_table_if_necessary()?;
        Ok(Symbols { archive: self, index: 0 })
    }
}

// ========================================================================= //

/// Representation of an archive entry.
///
/// `Entry` objects implement the `Read` trait, and can be used to extract the
/// data from this archive entry.  If the underlying reader supports the `Seek`
/// trait, then the `Entry` object supports `Seek` as well.
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> {
    /// Returns the header for this archive entry.
    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 {
            // Consume the rest of the data in this entry.
            let mut remaining = self.reader.take(self.length - self.position);
            let _ = io::copy(&mut remaining, &mut io::sink());
        }
    }
}

// ========================================================================= //

/// An iterator over the symbols in the symbol table of an archive.
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> {}

// ========================================================================= //

/// A structure for building Common or BSD-variant archives (the archive format
/// typically used on e.g. BSD and Mac OS X systems).
///
/// This structure has methods for building up an archive from scratch into any
/// arbitrary writer.
pub struct Builder<W: Write> {
    writer: W,
    started: bool,
}

impl<W: Write> Builder<W> {
    /// Create a new archive builder with the underlying writer object as the
    /// destination of all data written.
    pub fn new(writer: W) -> Builder<W> {
        Builder { writer, started: false }
    }

    /// Unwrap this archive builder, returning the underlying writer object.
    pub fn into_inner(self) -> Result<W> {
        Ok(self.writer)
    }

    /// Adds a new entry to this archive.
    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(())
    }

    /// Adds a file on the local filesystem to this archive, using the file
    /// name as its identifier.
    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)
    }

    /// Adds a file to this archive, with the given name as its identifier.
    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)
    }
}

// ========================================================================= //

/// A structure for building GNU-variant archives (the archive format typically
/// used on e.g. GNU/Linux and Windows systems).
///
/// This structure has methods for building up an archive from scratch into any
/// arbitrary writer.
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> {
    /// Create a new archive builder with the underlying writer object as the
    /// destination of all data written.  The `identifiers` parameter must give
    /// the complete list of entry identifiers that will be included in this
    /// archive.
    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; // ` /\n`
        }

        GnuBuilder {
            writer,
            short_names,
            long_names,
            name_table_size,
            name_table_needs_padding,
            started: false,
        }
    }

    /// Unwrap this archive builder, returning the underlying writer object.
    pub fn into_inner(self) -> Result<W> {
        Ok(self.writer)
    }

    /// Adds a new entry to this archive.
    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(())
    }

    /// Adds a file on the local filesystem to this archive, using the file
    /// name as its identifier.
    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)
    }

    /// Adds a file to this archive, with the given name as its identifier.
    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);
        {
            // Parse the first entry and check the header values.
            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);
            // Read the first few bytes of the entry data and make sure they're
            // correct.
            let mut buffer = [0; 4];
            entry.read_exact(&mut buffer).unwrap();
            assert_eq!(&buffer, "foob".as_bytes());
            // Dropping the Entry object should automatically consume the rest
            // of the entry data so that the archive reader is ready to parse
            // the next entry.
        }
        {
            // Parse the second entry and check a couple header values.
            let mut entry = archive.next_entry().unwrap().unwrap();
            assert_eq!(entry.header().identifier(), b"bar.awesome.txt");
            assert_eq!(entry.header().size(), 22);
            // Read in all the entry data.
            let mut buffer = Vec::new();
            entry.read_to_end(&mut buffer).unwrap();
            assert_eq!(&buffer as &[u8], "This file is awesome!\n".as_bytes());
        }
        {
            // Parse the third entry and check a couple header values.
            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());
        {
            // Parse the first entry and check the header values.
            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);
            // We should get the size of the actual file, not including the
            // filename, even though this is not the value that's in the size
            // field in the input.
            assert_eq!(entry.header().size(), 7);
            // Read in the entry data; we should get only the payload and not
            // the filename.
            let mut buffer = Vec::new();
            entry.read_to_end(&mut buffer).unwrap();
            assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
        }
        {
            // Parse the second entry and check a couple header values.
            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);
            // Read in the entry data; we should get only the payload and not
            // the filename or the padding 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());
        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);
    }

    // MS `.lib` files are very similar to GNU `ar` archives, but with a few
    // tweaks:
    // * File names in the name table are terminated by null, rather than /\n
    // * Numeric entries may be all empty string, interpreted as 0, possibly?
    #[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()));
        {
            // Parse the first entry, then seek around the entry, performing
            // different reads.
            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());
            // Dropping the Entry object should automatically consume the rest
            // of the entry data so that the archive reader is ready to parse
            // the next entry.
        }
        {
            // Parse the second entry and read in all the entry data.
            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]));
        {
            // Jump to the second entry and check its contents.
            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());
        }
        {
            // Read the next entry, which should be the third one now.
            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());
        }
        // We should be at the end of the archive now.
        assert!(archive.next_entry().is_none());
        {
            // Jump back to the first entry and check its contents.
            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());
        }
        {
            // Read the next entry, which should be the second one again.
            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());
        }
        {
            // Jump back to the first entry and check its contents.
            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());
        }
        {
            // Read the next entry, which should be the second one again.
            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]));
        {
            // Jump to the second entry and check its contents.
            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());
        }
        {
            // Read the next entry, which should be the third one now.
            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());
        }
        // We should be at the end of the archive now.
        assert!(archive.next_entry().is_none());
        {
            // Jump back to the first entry and check its contents.
            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());
        }
        {
            // Read the next entry, which should be the second one again.
            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();
        }
    }
}

// ========================================================================= //
