goblin/archive/
mod.rs

1//! Implements a simple parser and extractor for a Unix Archive.
2//!
3//! There are two "common" formats: BSD and SysV
4//!
5//! This crate currently only implements the SysV version, which essentially postfixes all
6//! names in the archive with a / as a sigil for the end of the name, and uses a special symbol
7//! index for looking up symbols faster.
8
9use scroll::{Pread, Pwrite, SizeWith};
10
11use crate::error::{Error, Result};
12use crate::strtab;
13
14use alloc::collections::btree_map::BTreeMap;
15use alloc::vec::Vec;
16use core::usize;
17
18pub const SIZEOF_MAGIC: usize = 8;
19/// The magic number of a Unix Archive
20pub const MAGIC: &[u8; SIZEOF_MAGIC] = b"!<arch>\x0A";
21
22const SIZEOF_FILE_IDENTIFER: usize = 16;
23const SIZEOF_FILE_SIZE: usize = 10;
24
25#[repr(C)]
26#[derive(Debug, Clone, PartialEq, Pread, Pwrite, SizeWith)]
27/// A Unix Archive Header - meta data for the file/byte blob/whatever that follows exactly after.
28/// All data is right-padded with spaces ASCII `0x20`. The Binary layout is as follows:
29///
30/// |Offset|Length|Name                       |Format     |
31/// |:-----|:-----|:--------------------------|:----------|
32/// |0     |16    |File identifier            |ASCII      |
33/// |16    |12    |File modification timestamp|Decimal    |
34/// |28    |6     |Owner ID                   |Decimal    |
35/// |34    |6     |Group ID                   |Decimal    |
36/// |40    |8     |File mode                  |Octal      |
37/// |48    |10    |Filesize in bytes          |Decimal    |
38/// |58    |2     |Ending characters          |`0x60 0x0A`|
39///
40/// Byte alignment is according to the following:
41/// > Each archive file member begins on an even byte boundary; a newline is inserted between files
42/// > if necessary. Nevertheless, the size given reflects the actual size of the file exclusive
43/// > of padding.
44pub struct MemberHeader {
45    /// The identifier, or name for this file/whatever.
46    pub identifier: [u8; 16],
47    /// The timestamp for when this file was last modified. Base 10 number
48    pub timestamp: [u8; 12],
49    /// The file's owner's id. Base 10 string number
50    pub owner_id: [u8; 6],
51    /// The file's group id. Base 10 string number
52    pub group_id: [u8; 6],
53    /// The file's permissions mode. Base 8 number number
54    pub mode: [u8; 8],
55    /// The size of this file. Base 10 string number
56    pub file_size: [u8; 10],
57    /// The file header's terminator, always `0x60 0x0A`
58    pub terminator: [u8; 2],
59}
60
61#[derive(Debug, Clone, Copy, PartialEq)]
62pub struct Header<'a> {
63    pub name: &'a str,
64    pub size: usize,
65}
66
67pub const SIZEOF_HEADER: usize = SIZEOF_FILE_IDENTIFER + 12 + 6 + 6 + 8 + SIZEOF_FILE_SIZE + 2;
68
69impl MemberHeader {
70    pub fn name(&self) -> Result<&str> {
71        Ok(self
72            .identifier
73            .pread_with::<&str>(0, ::scroll::ctx::StrCtx::Length(SIZEOF_FILE_IDENTIFER))?)
74    }
75    pub fn size(&self) -> Result<usize> {
76        match usize::from_str_radix(
77            self.file_size
78                .pread_with::<&str>(0, ::scroll::ctx::StrCtx::Length(self.file_size.len()))?
79                .trim_end(),
80            10,
81        ) {
82            Ok(file_size) => Ok(file_size),
83            Err(err) => Err(Error::Malformed(format!(
84                "{:?} Bad file_size in header: {:?}",
85                err, self
86            ))),
87        }
88    }
89}
90
91#[derive(Debug, Clone, PartialEq)]
92/// Represents a single entry in the archive
93pub struct Member<'a> {
94    /// The entry header
95    pub header: Header<'a>,
96    /// File offset from the start of the archive to where the header begins
97    pub header_offset: u64,
98    /// File offset from the start of the archive to where the file begins
99    pub offset: u64,
100    /// BSD `ar` members store the filename separately
101    bsd_name: Option<&'a str>,
102    /// SysV `ar` members store the filename in a string table, a copy of which we hold here
103    sysv_name: Option<&'a str>,
104}
105
106impl<'a> Member<'a> {
107    /// Tries to parse the header in `R`, as well as the offset in `R.
108    /// **NOTE** the Seek will be pointing at the first byte of whatever the file is, skipping padding.
109    /// This is because just like members in the archive, the data section is 2-byte aligned.
110    pub fn parse(buffer: &'a [u8], offset: &mut usize) -> Result<Member<'a>> {
111        let header_offset = *offset;
112        let name = buffer.pread_with::<&str>(
113            *offset,
114            ::scroll::ctx::StrCtx::Length(SIZEOF_FILE_IDENTIFER),
115        )?;
116        let archive_header = buffer.gread::<MemberHeader>(offset)?;
117        let mut header = Header {
118            name,
119            size: archive_header.size()?,
120        };
121
122        // skip newline padding if we're on an uneven byte boundary
123        if *offset & 1 == 1 {
124            *offset += 1;
125        }
126
127        let bsd_name = if let Some(len) = Self::bsd_filename_length(name) {
128            // there's a filename of length `len` right after the header
129            let name = buffer.pread_with::<&str>(
130                header_offset + SIZEOF_HEADER,
131                ::scroll::ctx::StrCtx::Length(len),
132            )?;
133
134            // adjust the offset and size accordingly
135            if header.size > len {
136                *offset = header_offset + SIZEOF_HEADER + len;
137                header.size -= len;
138
139                // the name may have trailing NULs which we don't really want to keep
140                Some(name.trim_end_matches('\0'))
141            } else {
142                None
143            }
144        } else {
145            None
146        };
147
148        Ok(Member {
149            header,
150            header_offset: header_offset as u64,
151            offset: *offset as u64,
152            bsd_name,
153            sysv_name: None,
154        })
155    }
156
157    /// The size of the Member's content, in bytes. Does **not** include newline padding,
158    /// nor the size of the file header.
159    pub fn size(&self) -> usize {
160        self.header.size
161    }
162
163    /// Parse `#1/123` as `Some(123)`
164    fn bsd_filename_length(name: &str) -> Option<usize> {
165        use core::str::FromStr;
166
167        if let Some(name) = name.strip_prefix("#1/") {
168            let trimmed_name = name.trim_end_matches(' ');
169            if let Ok(len) = usize::from_str(trimmed_name) {
170                Some(len)
171            } else {
172                None
173            }
174        } else {
175            None
176        }
177    }
178
179    /// The member name, accounting for SysV and BSD `ar` filename extensions
180    pub fn extended_name(&self) -> &'a str {
181        if let Some(bsd_name) = self.bsd_name {
182            bsd_name
183        } else if let Some(ref sysv_name) = self.sysv_name {
184            sysv_name
185        } else {
186            self.header.name.trim_end_matches(' ').trim_end_matches('/')
187        }
188    }
189
190    /// The untrimmed raw member name, i.e., includes right-aligned space padding and `'/'` end-of-string
191    /// identifier
192    pub fn raw_name(&self) -> &'a str {
193        self.header.name
194    }
195}
196
197#[derive(Debug, Default)]
198/// The special index member signified by the name `'/'`.
199/// The data element contains a list of symbol indexes and symbol names, giving their offsets
200/// into the archive for a given name.
201pub struct Index<'a> {
202    /// Big Endian number of symbol_indexes and strings
203    pub size: usize,
204    /// Big Endian u32 index into the archive for this symbol (index in array is the index into the string table)
205    pub symbol_indexes: Vec<u32>,
206    /// Set of zero-terminated strings indexed by above. Number of strings = `self.size`
207    pub strtab: Vec<&'a str>,
208}
209
210/// SysV Archive Variant Symbol Lookup Table "Magic" Name
211const INDEX_NAME: &str = "/               ";
212/// SysV Archive Variant Extended Filename String Table Name
213const NAME_INDEX_NAME: &str = "//              ";
214/// BSD symbol definitions
215const BSD_SYMDEF_NAME: &str = "__.SYMDEF";
216const BSD_SYMDEF_SORTED_NAME: &str = "__.SYMDEF SORTED";
217
218impl<'a> Index<'a> {
219    /// Parses the given byte buffer into an Index. NB: the buffer must be the start of the index
220    pub fn parse_sysv_index(buffer: &'a [u8]) -> Result<Self> {
221        let offset = &mut 0;
222        let sizeof_table = buffer.gread_with::<u32>(offset, scroll::BE)? as usize;
223
224        if sizeof_table > buffer.len() / 4 {
225            return Err(Error::BufferTooShort(sizeof_table, "indices"));
226        }
227
228        let mut indexes = Vec::with_capacity(sizeof_table);
229        for _ in 0..sizeof_table {
230            indexes.push(buffer.gread_with::<u32>(offset, scroll::BE)?);
231        }
232        let sizeof_strtab = buffer.len() - ((sizeof_table * 4) + 4);
233        let strtab = strtab::Strtab::parse(buffer, *offset, sizeof_strtab, 0x0)?;
234        Ok(Index {
235            size: sizeof_table,
236            symbol_indexes: indexes,
237            strtab: strtab.to_vec()?, // because i'm lazy
238        })
239    }
240
241    /// Parses the given byte buffer into an Index, in BSD style archives
242    pub fn parse_bsd_symdef(buffer: &'a [u8]) -> Result<Self> {
243        // `llvm-ar` is a suitable reference:
244        //   https://github.com/llvm-mirror/llvm/blob/6ea9891f9310510c621be562d1c5cdfcf5575678/lib/Object/Archive.cpp#L842-L870
245
246        // BSD __.SYMDEF files look like:
247        //
248        //            ┌─────────────┐
249        //  entries:  │   # bytes   │
250        //            ├─────────────┼─────────────┐
251        //            │ name offset │  .o offset  │
252        //            ├─────────────┼─────────────┤
253        //            │ name offset │  .o offset  │
254        //            ├─────────────┼─────────────┤
255        //            │ name offset │  .o offset  │
256        //            ├─────────────┼─────────────┤
257        //            │ name offset │  .o offset  │
258        //            ├─────────────┼─────────────┘
259        //   strings: │   # bytes   │
260        //            ├─────────────┴───────────────────┐
261        //            │  _symbol\0                      │
262        //            ├─────────────────────────────────┴─────────────────────┐
263        //            │  _longer_symbol\0                                     │
264        //            ├────────────────┬──────────────────────────────────────┘
265        //            │  _baz\0        │
266        //            ├────────────────┴───┐
267        //            │  _quxx\0           │
268        //            └────────────────────┘
269        //
270        // All numeric values are u32s. Name offsets are relative to the start of the string table,
271        // and .o offsets are relative to the the start of the archive.
272
273        // Read the number of entries, which is at the start of the symdef (offset 0)
274        let entries_bytes = buffer.pread_with::<u32>(0, scroll::LE)? as usize;
275        let entries = entries_bytes / 8;
276
277        // Set up the string table, the length of which is recorded after the entire entries table,
278        // (`entries_bytes + 4`), and which starts immediately after that (`entries_bytes + 8`).
279        let strtab_bytes = buffer.pread_with::<u32>(entries_bytes + 4, scroll::LE)? as usize;
280        let strtab = strtab::Strtab::parse(buffer, entries_bytes + 8, strtab_bytes, 0x0)?;
281
282        if entries_bytes > buffer.len() {
283            return Err(Error::BufferTooShort(entries, "entries"));
284        }
285
286        // build the index
287        let mut indexes = Vec::with_capacity(entries);
288        let mut strings = Vec::with_capacity(entries);
289        for i in 0..entries {
290            // The entries table starts after the original length value (offset 4), and each entry
291            // has two u32 values, making them 8 bytes long.
292            //
293            // Therefore, the `i`th entry starts at offset `(i*8)+4`. The first u32 is at that
294            // address, and the second u32 follows 4 bytes later.
295            let string_offset: u32 = buffer.pread_with(i * 8 + 4, scroll::LE)?;
296            let archive_member: u32 = buffer.pread_with(i * 8 + 8, scroll::LE)?;
297
298            let string = match strtab.get_at(string_offset as usize) {
299                Some(result) => Ok(result),
300                None => Err(Error::Malformed(format!(
301                    "{} entry {} has string offset {}, which is out of bounds",
302                    BSD_SYMDEF_NAME, i, string_offset
303                ))),
304            }?;
305
306            indexes.push(archive_member);
307            strings.push(string);
308        }
309
310        Ok(Index {
311            size: entries,
312            symbol_indexes: indexes,
313            strtab: strings,
314        })
315    }
316
317    // Parses Windows Second Linker Member:
318    // number of members (m):   4
319    // member offsets:          4 * m
320    // number of symbols (n):   4
321    // symbol member indexes:   2 * n
322    // followed by SysV-style string table
323    // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#first-linker-member
324    pub fn parse_windows_linker_member(buffer: &'a [u8]) -> Result<Self> {
325        let offset = &mut 0;
326        let members = buffer.gread_with::<u32>(offset, scroll::LE)? as usize;
327
328        if members > buffer.len() / 4 {
329            return Err(Error::BufferTooShort(members, "members"));
330        }
331
332        let mut member_offsets = Vec::with_capacity(members);
333        for _ in 0..members {
334            member_offsets.push(buffer.gread_with::<u32>(offset, scroll::LE)?);
335        }
336
337        let symbols = buffer.gread_with::<u32>(offset, scroll::LE)? as usize;
338
339        if symbols > buffer.len() / 2 {
340            return Err(Error::BufferTooShort(symbols, "symbols"));
341        }
342
343        let mut symbol_offsets = Vec::with_capacity(symbols);
344        for _ in 0..symbols {
345            let index = buffer.gread_with::<u16>(offset, scroll::LE)?;
346            // <=0 and ==0 are semantically same in unsigned integer
347            if index <= 0 {
348                return Err(Error::BufferTooShort(members, "members"));
349            }
350            if let Some(symbol_offset) = member_offsets.get(index as usize - 1) {
351                symbol_offsets.push(*symbol_offset);
352            } else {
353                return Err(Error::BufferTooShort(members, "members"));
354            }
355        }
356        let strtab = strtab::Strtab::parse(buffer, *offset, buffer.len() - *offset, 0x0)?;
357        Ok(Index {
358            size: symbols,
359            symbol_indexes: symbol_offsets,
360            strtab: strtab.to_vec()?,
361        })
362    }
363}
364
365/// Member names greater than 16 bytes are indirectly referenced using a `/<idx` schema,
366/// where `idx` is an offset into a newline delimited string table directly following the `//` member
367/// of the archive.
368#[derive(Debug, Default)]
369struct NameIndex<'a> {
370    strtab: strtab::Strtab<'a>,
371}
372
373impl<'a> NameIndex<'a> {
374    pub fn parse(buffer: &'a [u8], offset: &mut usize, size: usize) -> Result<NameIndex<'a>> {
375        // This is a total hack, because strtab returns "" if idx == 0, need to change
376        // but previous behavior might rely on this, as ELF strtab's have "" at 0th index...
377        let hacked_size = size + 1;
378        if hacked_size < 2 {
379            return Err(Error::Malformed(format!(
380                "Size ({hacked_size:#x}) too small"
381            )));
382        }
383        let strtab = strtab::Strtab::parse(buffer, *offset - 1, hacked_size, b'\n')?;
384        // precious time was lost when refactoring because strtab::parse doesn't update the mutable seek...
385        *offset += hacked_size - 2;
386        Ok(NameIndex { strtab })
387    }
388
389    pub fn get(&self, name: &str) -> Result<&'a str> {
390        let idx = name.trim_start_matches('/').trim_end();
391        match usize::from_str_radix(idx, 10) {
392            Ok(idx) => {
393                let name = match self.strtab.get_at(idx + 1) {
394                    Some(result) => Ok(result),
395                    None => Err(Error::Malformed(format!(
396                        "Name {} is out of range in archive NameIndex",
397                        name
398                    ))),
399                }?;
400
401                if name != "" {
402                    Ok(name.trim_end_matches('/'))
403                } else {
404                    Err(Error::Malformed(format!(
405                        "Could not find {:?} in index",
406                        name
407                    )))
408                }
409            }
410            Err(_) => Err(Error::Malformed(format!(
411                "Bad name index {:?} in index",
412                name
413            ))),
414        }
415    }
416}
417
418#[derive(Debug, PartialEq)]
419/// The type of symbol index can be present in an archive. Can serve as an indication of the
420/// archive format.
421pub enum IndexType {
422    /// No symbol index present.
423    None,
424    /// SystemV/GNU style symbol index, used on Windows as well.
425    SysV,
426    /// Windows specific extension of SysV symbol index, so called Second Linker Member. Has the
427    /// same member name as SysV symbol index but different structure.
428    Windows,
429    /// BSD style symbol index.
430    BSD,
431}
432
433// TODO: add pretty printer fmt::Display with number of members, and names of members, along with
434// the values of the index symbols once implemented
435#[derive(Debug)]
436/// An in-memory representation of a parsed Unix Archive
437pub struct Archive<'a> {
438    // The array of members, which are indexed by the members hash and symbol index.
439    // These are in the same order they are found in the file.
440    member_array: Vec<Member<'a>>,
441    // file name -> member
442    members: BTreeMap<&'a str, usize>,
443    // symbol -> member
444    symbol_index: BTreeMap<&'a str, usize>,
445}
446
447impl<'a> Archive<'a> {
448    pub fn parse(buffer: &'a [u8]) -> Result<Archive<'a>> {
449        let mut magic = [0u8; SIZEOF_MAGIC];
450        let offset = &mut 0usize;
451        buffer.gread_inout(offset, &mut magic)?;
452        if &magic != MAGIC {
453            return Err(Error::BadMagic(magic.pread(0)?));
454        }
455        let mut member_array = Vec::new();
456        let mut index = Index::default();
457        let mut index_type = IndexType::None;
458        let mut sysv_name_index = NameIndex::default();
459        while *offset + 1 < buffer.len() {
460            // realign the cursor to a word boundary, if it's not on one already
461            if *offset & 1 == 1 {
462                *offset += 1;
463            }
464
465            let member = Member::parse(buffer, offset)?;
466
467            // advance to the next record
468            *offset = member.offset as usize + member.size() as usize;
469
470            let name = member.raw_name();
471            if name == INDEX_NAME {
472                let data: &[u8] = buffer.pread_with(member.offset as usize, member.size())?;
473                index = match index_type {
474                    IndexType::None => {
475                        index_type = IndexType::SysV;
476                        Index::parse_sysv_index(data)?
477                    }
478                    IndexType::SysV => {
479                        index_type = IndexType::Windows;
480                        // second symbol index is Microsoft's extension of SysV format
481                        Index::parse_windows_linker_member(data)?
482                    }
483                    IndexType::BSD => {
484                        return Err(Error::Malformed("SysV index occurs after BSD index".into()));
485                    }
486                    IndexType::Windows => {
487                        return Err(Error::Malformed(
488                            "More than two Windows Linker members".into(),
489                        ));
490                    }
491                }
492            } else if member.bsd_name == Some(BSD_SYMDEF_NAME)
493                || member.bsd_name == Some(BSD_SYMDEF_SORTED_NAME)
494            {
495                if index_type != IndexType::None {
496                    return Err(Error::Malformed("BSD index occurs after SysV index".into()));
497                }
498                index_type = IndexType::BSD;
499                let data: &[u8] = buffer.pread_with(member.offset as usize, member.size())?;
500                index = Index::parse_bsd_symdef(data)?;
501            } else if name == NAME_INDEX_NAME {
502                let mut name_index_offset: usize = member.offset as usize;
503                sysv_name_index = NameIndex::parse(buffer, &mut name_index_offset, member.size())?;
504            } else {
505                // record this as an archive member
506                member_array.push(member);
507            }
508        }
509
510        // preprocess member names
511        let mut members = BTreeMap::new();
512        let mut member_index_by_offset: BTreeMap<u32, usize> = BTreeMap::new();
513        for (i, member) in member_array.iter_mut().enumerate() {
514            // copy in any SysV extended names
515            if let Ok(sysv_name) = sysv_name_index.get(member.raw_name()) {
516                member.sysv_name = Some(sysv_name);
517            }
518
519            // build a hashmap by extended name
520            let key = member.extended_name();
521            members.insert(key, i);
522
523            // build a hashmap translating archive offset into member index
524            member_index_by_offset.insert(member.header_offset as u32, i);
525        }
526
527        // build the symbol index, translating symbol names into member indexes
528        let mut symbol_index: BTreeMap<&str, usize> = BTreeMap::new();
529        for (member_offset, name) in index.symbol_indexes.iter().zip(index.strtab.iter()) {
530            let member_index = *member_index_by_offset.get(member_offset).ok_or_else(|| {
531                Error::Malformed(format!(
532                    "Could not get member {:?} at offset: {}",
533                    name, member_offset
534                ))
535            })?;
536            symbol_index.insert(&name, member_index);
537        }
538
539        Ok(Archive {
540            member_array,
541            members,
542            symbol_index,
543        })
544    }
545
546    /// Get the member named `member` in this archive, if any. If there are
547    /// multiple files in the archive with the same name it only returns one
548    /// of them.
549    pub fn get(&self, member: &str) -> Option<&Member<'_>> {
550        if let Some(idx) = self.members.get(member) {
551            Some(&self.member_array[*idx])
552        } else {
553            None
554        }
555    }
556
557    /// Get the member at position `index` in this archive, if any.
558    pub fn get_at(&self, index: usize) -> Option<&Member<'_>> {
559        self.member_array.get(index)
560    }
561
562    /// Return the number of archive members.
563    pub fn len(&self) -> usize {
564        self.member_array.len()
565    }
566
567    /// Returns a slice of the raw bytes for the given `member` in the scrollable `buffer`
568    pub fn extract<'b>(&self, member: &str, buffer: &'b [u8]) -> Result<&'b [u8]> {
569        if let Some(member) = self.get(member) {
570            let bytes = buffer.pread_with(member.offset as usize, member.size())?;
571            Ok(bytes)
572        } else {
573            Err(Error::Malformed(format!(
574                "Cannot extract member {:?}",
575                member
576            )))
577        }
578    }
579
580    /// Gets a summary of this archive, returning a list of membername, the member, and the list of symbols the member contains
581    pub fn summarize(&self) -> Vec<(&str, &Member<'_>, Vec<&'a str>)> {
582        // build a result array, with indexes matching the member indexes
583        let mut result = self
584            .member_array
585            .iter()
586            .map(|ref member| (member.extended_name(), *member, Vec::new()))
587            .collect::<Vec<_>>();
588
589        // walk the symbol index once, adding each symbol to the appropriate result Vec
590        for (symbol_name, member_index) in self.symbol_index.iter() {
591            result[*member_index].2.push(*symbol_name);
592        }
593
594        result
595    }
596
597    /// Get the list of member names in this archive
598    ///
599    /// This returns members in alphabetical order, not in the order they
600    /// occurred in the archive. If there are multiple files with the same
601    /// name, the size of the returned array will be less than the size of
602    /// `len()`.
603    pub fn members(&self) -> Vec<&'a str> {
604        self.members.keys().cloned().collect()
605    }
606
607    /// Returns the member's name which contains the given `symbol`, if it is in the archive
608    pub fn member_of_symbol(&self, symbol: &str) -> Option<&'a str> {
609        if let Some(idx) = self.symbol_index.get(symbol) {
610            Some(self.member_array[*idx].extended_name())
611        } else {
612            None
613        }
614    }
615}
616
617#[cfg(test)]
618mod tests {
619    use super::*;
620    use crate::error;
621
622    #[test]
623    fn test_member_bsd_filename_length() {
624        // non-BSD names should fall through
625        assert_eq!(Member::bsd_filename_length(""), None);
626        assert_eq!(Member::bsd_filename_length("123"), None);
627        assert_eq!(Member::bsd_filename_length("#1"), None);
628        assert_eq!(Member::bsd_filename_length("#1/"), None);
629        assert_eq!(Member::bsd_filename_length("#2/1"), None);
630        assert_eq!(Member::bsd_filename_length(INDEX_NAME), None);
631        assert_eq!(Member::bsd_filename_length(NAME_INDEX_NAME), None);
632        assert_eq!(Member::bsd_filename_length("👺"), None);
633
634        // #1/<len> should be parsed as Some(len), with or without whitespace
635        assert_eq!(Member::bsd_filename_length("#1/1"), Some(1));
636        assert_eq!(Member::bsd_filename_length("#1/22"), Some(22));
637        assert_eq!(Member::bsd_filename_length("#1/333"), Some(333));
638        assert_eq!(Member::bsd_filename_length("#1/1          "), Some(1));
639        assert_eq!(Member::bsd_filename_length("#1/22         "), Some(22));
640        assert_eq!(Member::bsd_filename_length("#1/333      "), Some(333));
641
642        // #!/<len><trailing garbage> should be None
643        assert_eq!(Member::bsd_filename_length("#1/1A"), None);
644        assert_eq!(Member::bsd_filename_length("#1/1 A"), None);
645    }
646
647    /// https://github.com/m4b/goblin/issues/450
648    const MALFORMED_ARCHIVE_INDEX_TOO_SMALL: [u8; 132] = [
649        0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A, 0x55, 0x52, 0x09, 0x5C, 0x09, 0x09, 0x10,
650        0x27, 0x2B, 0x09, 0x0A, 0x53, 0x54, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
651        0x09, 0x09, 0x09, 0x09, 0x2A, 0x29, 0x2A, 0x09, 0xF7, 0x08, 0x09, 0x09, 0x00, 0x01, 0x01,
652        0x01, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x01, 0x00, 0x31, 0x20, 0x20, 0x20,
653        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x08, 0x2F, 0x2F, 0x20, 0x20, 0x20,
654        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x09,
655        0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x23, 0x42, 0x21, 0x09, 0xF7, 0x08, 0x20, 0x20, 0x00,
656        0x3C, 0x20, 0x20, 0x20, 0x00, 0x20, 0x20, 0x20, 0x20, 0x09, 0x09, 0x01, 0x01, 0x30, 0x0D,
657        0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x00, 0x00, 0x27, 0x55,
658    ];
659
660    /// https://github.com/m4b/goblin/issues/450
661    const MALFORMED_ARCHIVE: [u8; 212] = [
662        0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A, 0x2F, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
663        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0F, 0x01, 0x00, 0x00, 0x31, 0x31,
664        0x31, 0x0E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x03, 0x30, 0x34, 0x30, 0x30, 0x30, 0x71,
665        0x30, 0x30, 0x17, 0xE8, 0x33, 0x5A, 0x31, 0x30, 0x30, 0x30, 0x30, 0x35, 0x31, 0x20, 0x20,
666        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
667        0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
668        0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
669        0x38, 0x38, 0x37, 0x37, 0x37, 0x35, 0x37, 0x21, 0x3E, 0x61, 0x6D, 0x66, 0x76, 0x3B, 0x0A,
670        0x2F, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
671        0x20, 0x0F, 0x01, 0x00, 0x00, 0x31, 0x31, 0x31, 0x00, 0x0E, 0x30, 0x30, 0x30, 0x30, 0x30,
672        0x30, 0x03, 0x30, 0x38, 0x30, 0x30, 0x30, 0x72, 0x30, 0x30, 0x17, 0xE8, 0x36, 0x4E, 0x31,
673        0x30, 0x30, 0x30, 0x30, 0x33, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
674        0x01, 0x00, 0x00, 0x00, 0x01, 0x34, 0x37, 0x35, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36,
675        0x37, 0x33, 0x37, 0x34, 0x33, 0x32, 0x38, 0x35, 0x32, 0x34, 0x33, 0x38, 0x32, 0x35, 0x33,
676        0x14, 0x34,
677    ];
678
679    #[test]
680    fn parse_name_index_too_small() {
681        let res = Archive::parse(&MALFORMED_ARCHIVE_INDEX_TOO_SMALL);
682        assert_eq!(res.is_err(), true);
683        if let Err(Error::Malformed(msg)) = res {
684            assert_eq!(msg, "Size (0x1) too small");
685        } else {
686            panic!("Expected a Malformed error but got {:?}", res);
687        }
688    }
689
690    #[test]
691    fn parse_malformed_archive() {
692        let res = Archive::parse(&MALFORMED_ARCHIVE);
693        assert_eq!(res.is_err(), true);
694        match res {
695            Err(error::Error::BufferTooShort(num_member, msg)) => {
696                assert_eq!(num_member, 1);
697                assert_eq!(msg, "members");
698            }
699            _ => panic!("Expected a BufferTooShort error but got {:?}", res),
700        }
701    }
702}