tokio_ar/
archive.rs

1use crate::entry::Entry;
2use crate::error::annotate;
3use crate::header::Header;
4use crate::symbols::Symbols;
5use std::io::{Error, ErrorKind, Result, SeekFrom};
6use tokio::io::{
7    AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt,
8    BufReader,
9};
10
11pub(crate) const GLOBAL_HEADER_LEN: usize = 8;
12pub(crate) const GLOBAL_HEADER: &[u8; GLOBAL_HEADER_LEN] = b"!<arch>\n";
13
14pub(crate) const BSD_SYMBOL_LOOKUP_TABLE_ID: &[u8] = b"__.SYMDEF";
15pub(crate) const BSD_SORTED_SYMBOL_LOOKUP_TABLE_ID: &[u8] =
16    b"__.SYMDEF SORTED";
17
18pub(crate) const GNU_NAME_TABLE_ID: &str = "//";
19pub(crate) const GNU_SYMBOL_LOOKUP_TABLE_ID: &[u8] = b"/";
20
21/// Variants of the Unix archive format.
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub enum Variant {
24    /// Used by Debian package files; allows only short filenames.
25    Common,
26    /// Used by BSD `ar` (and OS X); backwards-compatible with common variant.
27    BSD,
28    /// Used by GNU `ar` (and Windows); incompatible with common variant.
29    GNU,
30}
31
32pub(crate) struct HeaderAndLocation {
33    header: Header,
34    header_start: u64,
35    data_start: u64,
36}
37
38/// A structure for reading archives.
39pub struct Archive<R: AsyncRead + Unpin> {
40    reader: R,
41    variant: Variant,
42    name_table: Vec<u8>,
43    entry_headers: Vec<HeaderAndLocation>,
44    new_entry_start: u64,
45    next_entry_index: usize,
46    pub(crate) symbol_table_header: Option<HeaderAndLocation>,
47    pub(crate) symbol_table: Option<Vec<(Vec<u8>, u64)>>,
48    started: bool, // True if we've read past the global header.
49    padding: bool, // True if there's a padding byte before the next entry.
50    scanned: bool, // True if entry_headers is complete.
51    error: bool,   // True if we have encountered an error.
52    unread_entry_data: u64, // Bytes remaining from previous entry.
53}
54
55impl<R: AsyncRead + Unpin> Archive<R> {
56    /// Create a new archive reader with the underlying reader object as the
57    /// source of all data read.
58    pub fn new(reader: R) -> Archive<R> {
59        Archive {
60            reader,
61            variant: Variant::Common,
62            name_table: Vec::new(),
63            entry_headers: Vec::new(),
64            new_entry_start: GLOBAL_HEADER_LEN as u64,
65            next_entry_index: 0,
66            symbol_table_header: None,
67            symbol_table: None,
68            started: false,
69            padding: false,
70            scanned: false,
71            error: false,
72            unread_entry_data: 0,
73        }
74    }
75
76    /// Returns which format variant this archive appears to be so far.
77    ///
78    /// Note that this may not be accurate before the archive has been fully
79    /// read (i.e. before the `next_entry()` method returns `None`).  In
80    /// particular, a new `Archive` object that hasn't yet read any data at all
81    /// will always return `Variant::Common`.
82    pub fn variant(&self) -> Variant {
83        self.variant
84    }
85
86    /// Unwrap this archive reader, returning the underlying reader object.
87    pub fn into_inner(self) -> Result<R> {
88        Ok(self.reader)
89    }
90
91    fn is_name_table_id(&self, identifier: &[u8]) -> bool {
92        self.variant == Variant::GNU
93            && identifier == GNU_NAME_TABLE_ID.as_bytes()
94    }
95
96    fn is_symbol_lookup_table_id(&self, identifier: &[u8]) -> bool {
97        match self.variant {
98            Variant::Common => false,
99            Variant::BSD => {
100                identifier == BSD_SYMBOL_LOOKUP_TABLE_ID
101                    || identifier == BSD_SORTED_SYMBOL_LOOKUP_TABLE_ID
102            }
103            Variant::GNU => identifier == GNU_SYMBOL_LOOKUP_TABLE_ID,
104        }
105    }
106
107    async fn read_global_header_if_necessary(&mut self) -> Result<()> {
108        if self.started {
109            return Ok(());
110        }
111        let mut buffer = [0; GLOBAL_HEADER_LEN];
112        match self.reader.read_exact(&mut buffer).await {
113            Ok(_) => {}
114            Err(error) => {
115                self.error = true;
116                return Err(annotate(error, "failed to read global header"));
117            }
118        }
119        if &buffer != GLOBAL_HEADER {
120            self.error = true;
121            let msg = "Not an archive file (invalid global header)";
122            return Err(Error::new(ErrorKind::InvalidData, msg));
123        }
124        self.started = true;
125        Ok(())
126    }
127
128    /// Reads the next entry from the archive, or returns None if there are no
129    /// more.
130    pub async fn next_entry(&mut self) -> Option<Result<Entry<'_, R>>> {
131        loop {
132            if self.error {
133                return None;
134            }
135            if self.scanned
136                && self.next_entry_index == self.entry_headers.len()
137            {
138                return None;
139            }
140            match self.read_global_header_if_necessary().await {
141                Ok(()) => {}
142                Err(error) => return Some(Err(error)),
143            }
144            // Skip any unread data from the previous entry.
145            if self.unread_entry_data > 0 {
146                match tokio::io::copy(
147                    &mut (&mut self.reader).take(self.unread_entry_data),
148                    &mut tokio::io::sink(),
149                )
150                .await
151                {
152                    Ok(_) => self.unread_entry_data = 0,
153                    Err(error) => {
154                        self.error = true;
155                        return Some(Err(annotate(
156                            error,
157                            "failed to skip unread entry data",
158                        )));
159                    }
160                }
161            }
162            if self.padding {
163                let mut buffer = [0u8; 1];
164                match self.reader.read_exact(&mut buffer).await {
165                    Ok(_) => {
166                        if buffer[0] != b'\n' {
167                            self.error = true;
168                            let msg = format!(
169                                "invalid padding byte ({})",
170                                buffer[0]
171                            );
172                            let error =
173                                Error::new(ErrorKind::InvalidData, msg);
174                            return Some(Err(error));
175                        }
176                    }
177                    Err(error) => {
178                        if error.kind() != ErrorKind::UnexpectedEof {
179                            self.error = true;
180                            let msg = "failed to read padding byte";
181                            return Some(Err(annotate(error, msg)));
182                        }
183                    }
184                }
185                self.padding = false;
186            }
187            let header_start = self.new_entry_start;
188            match Header::read(
189                &mut self.reader,
190                &mut self.variant,
191                &mut self.name_table,
192            )
193            .await
194            {
195                Ok(Some((header, header_len))) => {
196                    let size = header.size();
197                    if size % 2 != 0 {
198                        self.padding = true;
199                    }
200                    if self.next_entry_index == self.entry_headers.len() {
201                        self.new_entry_start += header_len + size + (size % 2);
202                    }
203                    if self.is_name_table_id(header.identifier()) {
204                        continue;
205                    }
206                    if self.is_symbol_lookup_table_id(header.identifier()) {
207                        self.symbol_table_header = Some(HeaderAndLocation {
208                            header,
209                            header_start,
210                            data_start: header_start + header_len,
211                        });
212                        continue;
213                    }
214                    if self.next_entry_index == self.entry_headers.len() {
215                        self.entry_headers.push(HeaderAndLocation {
216                            header,
217                            header_start,
218                            data_start: header_start + header_len,
219                        });
220                    }
221                    let header =
222                        &self.entry_headers[self.next_entry_index].header;
223                    self.next_entry_index += 1;
224                    self.unread_entry_data = size;
225                    return Some(Ok(Entry {
226                        header,
227                        reader: &mut self.reader,
228                        length: size,
229                        position: 0,
230                        unread_counter: &mut self.unread_entry_data,
231                    }));
232                }
233                Ok(None) => {
234                    self.scanned = true;
235                    return None;
236                }
237                Err(error) => {
238                    self.error = true;
239                    return Some(Err(error));
240                }
241            }
242        }
243    }
244}
245
246impl<R: AsyncRead + AsyncSeek + Unpin> Archive<R> {
247    async fn scan_if_necessary(&mut self) -> Result<()> {
248        if self.scanned {
249            return Ok(());
250        }
251        self.read_global_header_if_necessary().await?;
252        loop {
253            let header_start = self.new_entry_start;
254            self.reader.seek(SeekFrom::Start(header_start)).await?;
255            if let Some((header, header_len)) = Header::read(
256                &mut self.reader,
257                &mut self.variant,
258                &mut self.name_table,
259            )
260            .await?
261            {
262                let size = header.size();
263                self.new_entry_start += header_len + size + (size % 2);
264                if self.is_name_table_id(header.identifier()) {
265                    continue;
266                }
267                if self.is_symbol_lookup_table_id(header.identifier()) {
268                    self.symbol_table_header = Some(HeaderAndLocation {
269                        header,
270                        header_start,
271                        data_start: header_start + header_len,
272                    });
273                    continue;
274                }
275                self.entry_headers.push(HeaderAndLocation {
276                    header,
277                    header_start,
278                    data_start: header_start + header_len,
279                });
280            } else {
281                break;
282            }
283        }
284        // Resume our previous position in the file.
285        if self.next_entry_index < self.entry_headers.len() {
286            let offset =
287                self.entry_headers[self.next_entry_index].header_start;
288            self.reader.seek(SeekFrom::Start(offset)).await?;
289        }
290        self.scanned = true;
291        Ok(())
292    }
293
294    /// Scans the archive and returns the total number of entries in the
295    /// archive (not counting special entries, such as the GNU archive name
296    /// table or symbol table, that are not returned by `next_entry()`).
297    pub async fn count_entries(&mut self) -> Result<usize> {
298        self.scan_if_necessary().await?;
299        Ok(self.entry_headers.len())
300    }
301
302    /// Scans the archive and jumps to the entry at the given index.  Returns
303    /// an error if the index is not less than the result of `count_entries()`.
304    pub async fn jump_to_entry(
305        &mut self,
306        index: usize,
307    ) -> Result<Entry<'_, R>> {
308        self.scan_if_necessary().await?;
309        if index >= self.entry_headers.len() {
310            let msg = "Entry index out of bounds";
311            return Err(Error::new(ErrorKind::InvalidInput, msg));
312        }
313        let offset = self.entry_headers[index].data_start;
314        self.reader.seek(SeekFrom::Start(offset)).await?;
315        let header = &self.entry_headers[index].header;
316        let size = header.size();
317        self.padding = !size.is_multiple_of(2);
318        self.next_entry_index = index + 1;
319        self.unread_entry_data = size;
320        Ok(Entry {
321            header,
322            reader: &mut self.reader,
323            length: size,
324            position: 0,
325            unread_counter: &mut self.unread_entry_data,
326        })
327    }
328
329    async fn parse_symbol_table_if_necessary(&mut self) -> Result<()> {
330        self.scan_if_necessary().await?;
331        if self.symbol_table.is_some() {
332            return Ok(());
333        }
334        if let Some(ref header_and_loc) = self.symbol_table_header {
335            let offset = header_and_loc.data_start;
336            self.reader.seek(SeekFrom::Start(offset)).await?;
337            let mut reader = BufReader::new(
338                (&mut self.reader).take(header_and_loc.header.size()),
339            );
340            if self.variant == Variant::GNU {
341                let num_symbols = read_be_u32(&mut reader).await? as usize;
342                let mut symbol_offsets =
343                    Vec::<u32>::with_capacity(num_symbols);
344                for _ in 0..num_symbols {
345                    let offset = read_be_u32(&mut reader).await?;
346                    symbol_offsets.push(offset);
347                }
348                let mut symbol_table = Vec::with_capacity(num_symbols);
349                for offset in symbol_offsets.into_iter() {
350                    let mut buffer = Vec::<u8>::new();
351                    reader.read_until(0, &mut buffer).await?;
352                    if buffer.last() == Some(&0) {
353                        buffer.pop();
354                    }
355                    buffer.shrink_to_fit();
356                    symbol_table.push((buffer, offset as u64));
357                }
358                self.symbol_table = Some(symbol_table);
359            } else {
360                let num_symbols =
361                    (read_le_u32(&mut reader).await? / 8) as usize;
362                let mut symbol_offsets =
363                    Vec::<(u32, u32)>::with_capacity(num_symbols);
364                for _ in 0..num_symbols {
365                    let str_offset = read_le_u32(&mut reader).await?;
366                    let file_offset = read_le_u32(&mut reader).await?;
367                    symbol_offsets.push((str_offset, file_offset));
368                }
369                let str_table_len = read_le_u32(&mut reader).await?;
370                let mut str_table_data = vec![0u8; str_table_len as usize];
371                reader.read_exact(&mut str_table_data).await.map_err(
372                    |err| annotate(err, "failed to read string table"),
373                )?;
374                let mut symbol_table = Vec::with_capacity(num_symbols);
375                for (str_start, file_offset) in symbol_offsets.into_iter() {
376                    let str_start = str_start as usize;
377                    let mut str_end = str_start;
378                    while str_end < str_table_data.len()
379                        && str_table_data[str_end] != 0u8
380                    {
381                        str_end += 1;
382                    }
383                    let string = &str_table_data[str_start..str_end];
384                    symbol_table.push((string.to_vec(), file_offset as u64));
385                }
386                self.symbol_table = Some(symbol_table);
387            }
388        }
389        // Resume our previous position in the file.
390        if !self.entry_headers.is_empty() {
391            let offset =
392                self.entry_headers[self.next_entry_index].header_start;
393            self.reader.seek(SeekFrom::Start(offset)).await?;
394        }
395        Ok(())
396    }
397
398    /// Scans the archive and returns an iterator over the symbols in the
399    /// archive's symbol table.  If the archive doesn't have a symbol table,
400    /// this method will still succeed, but the iterator won't produce any
401    /// values.
402    pub async fn symbols(&mut self) -> Result<Symbols<'_, R>> {
403        self.parse_symbol_table_if_necessary().await?;
404        Ok(Symbols { archive: self, index: 0 })
405    }
406}
407
408async fn read_le_u32(mut r: impl AsyncRead + Unpin) -> Result<u32> {
409    let mut buf = [0; 4];
410    r.read_exact(&mut buf).await?;
411    Ok(u32::from_le_bytes(buf))
412}
413
414async fn read_be_u32(mut r: impl AsyncRead + Unpin) -> Result<u32> {
415    let mut buf = [0; 4];
416    r.read_exact(&mut buf).await?;
417    Ok(u32::from_be_bytes(buf))
418}
419
420#[cfg(test)]
421mod tests {
422    use crate::{Archive, GnuBuilder, Header, Variant};
423    use std::io::{Cursor, SeekFrom};
424    use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt};
425
426    struct SlowReader<'a> {
427        current_position: usize,
428        buffer: &'a [u8],
429    }
430
431    impl AsyncRead for SlowReader<'_> {
432        fn poll_read(
433            self: std::pin::Pin<&mut Self>,
434            _cx: &mut std::task::Context<'_>,
435            buf: &mut tokio::io::ReadBuf<'_>,
436        ) -> std::task::Poll<std::io::Result<()>> {
437            let this = self.get_mut();
438            if this.current_position >= this.buffer.len() {
439                std::task::Poll::Ready(Ok(()))
440            } else {
441                buf.put_slice(&[this.buffer[this.current_position]]);
442                this.current_position += 1;
443                std::task::Poll::Ready(Ok(()))
444            }
445        }
446    }
447
448    #[tokio::test]
449    async fn read_common_archive() {
450        let input = "\
451        !<arch>\n\
452        foo.txt         1487552916  501   20    100644  7         `\n\
453        foobar\n\n\
454        bar.awesome.txt 1487552919  501   20    100644  22        `\n\
455        This file is awesome!\n\
456        baz.txt         1487552349  42    12345 100664  4         `\n\
457        baz\n";
458        let reader =
459            SlowReader { current_position: 0, buffer: input.as_bytes() };
460        let mut archive = Archive::new(reader);
461        {
462            // Parse the first entry and check the header values.
463            let mut entry = archive.next_entry().await.unwrap().unwrap();
464            assert_eq!(entry.header().identifier(), b"foo.txt");
465            assert_eq!(entry.header().mtime(), 1487552916);
466            assert_eq!(entry.header().uid(), 501);
467            assert_eq!(entry.header().gid(), 20);
468            assert_eq!(entry.header().mode(), 0o100644);
469            assert_eq!(entry.header().size(), 7);
470            // Read the first few bytes of the entry data and make sure they're
471            // correct.
472            let mut buffer = [0; 4];
473            entry.read_exact(&mut buffer).await.unwrap();
474            assert_eq!(&buffer, "foob".as_bytes());
475            // The archive reader knows how many bytes have been consumed and
476            // skips the rest of the entry data so that the archive reader is
477            // ready to parse the next entry.
478        }
479        {
480            // Parse the second entry and check a couple header values.
481            let mut entry = archive.next_entry().await.unwrap().unwrap();
482            assert_eq!(entry.header().identifier(), b"bar.awesome.txt");
483            assert_eq!(entry.header().size(), 22);
484            // Read in all the entry data.
485            let mut buffer = Vec::new();
486            entry.read_to_end(&mut buffer).await.unwrap();
487            assert_eq!(&buffer as &[u8], "This file is awesome!\n".as_bytes());
488        }
489        {
490            // Parse the third entry and check a couple header values.
491            let entry = archive.next_entry().await.unwrap().unwrap();
492            assert_eq!(entry.header().identifier(), b"baz.txt");
493            assert_eq!(entry.header().size(), 4);
494        }
495        assert!(archive.next_entry().await.is_none());
496        assert_eq!(archive.variant(), Variant::Common);
497    }
498
499    #[tokio::test]
500    async fn read_bsd_archive_with_long_filenames() {
501        let input = "\
502        !<arch>\n\
503        #1/32           1487552916  501   20    100644  39        `\n\
504        this_is_a_very_long_filename.txtfoobar\n\n\
505        #1/44           0           0     0     0       48        `\n\
506        and_this_is_another_very_long_filename.txt\x00\x00baz\n";
507        let mut archive = Archive::new(input.as_bytes());
508        {
509            // Parse the first entry and check the header values.
510            let mut entry = archive.next_entry().await.unwrap().unwrap();
511            assert_eq!(
512                entry.header().identifier(),
513                "this_is_a_very_long_filename.txt".as_bytes()
514            );
515            assert_eq!(entry.header().mtime(), 1487552916);
516            assert_eq!(entry.header().uid(), 501);
517            assert_eq!(entry.header().gid(), 20);
518            assert_eq!(entry.header().mode(), 0o100644);
519            // We should get the size of the actual file, not including the
520            // filename, even though this is not the value that's in the size
521            // field in the input.
522            assert_eq!(entry.header().size(), 7);
523            // Read in the entry data; we should get only the payload and not
524            // the filename.
525            let mut buffer = Vec::new();
526            entry.read_to_end(&mut buffer).await.unwrap();
527            assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
528        }
529        {
530            // Parse the second entry and check a couple header values.
531            let mut entry = archive.next_entry().await.unwrap().unwrap();
532            assert_eq!(
533                entry.header().identifier(),
534                "and_this_is_another_very_long_filename.txt".as_bytes()
535            );
536            assert_eq!(entry.header().size(), 4);
537            // Read in the entry data; we should get only the payload and not
538            // the filename or the padding bytes.
539            let mut buffer = Vec::new();
540            entry.read_to_end(&mut buffer).await.unwrap();
541            assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
542        }
543        assert!(archive.next_entry().await.is_none());
544        assert_eq!(archive.variant(), Variant::BSD);
545    }
546
547    #[tokio::test]
548    async fn read_bsd_archive_with_space_in_filename() {
549        let input = "\
550        !<arch>\n\
551        #1/8            0           0     0     0       12        `\n\
552        foo bar\x00baz\n";
553        let mut archive = Archive::new(input.as_bytes());
554        {
555            let mut entry = archive.next_entry().await.unwrap().unwrap();
556            assert_eq!(entry.header().identifier(), "foo bar".as_bytes());
557            assert_eq!(entry.header().size(), 4);
558            let mut buffer = Vec::new();
559            entry.read_to_end(&mut buffer).await.unwrap();
560            assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
561        }
562        assert!(archive.next_entry().await.is_none());
563        assert_eq!(archive.variant(), Variant::BSD);
564    }
565
566    #[tokio::test]
567    async fn read_gnu_archive() {
568        let input = "\
569        !<arch>\n\
570        foo.txt/        1487552916  501   20    100644  7         `\n\
571        foobar\n\n\
572        bar.awesome.txt/1487552919  501   20    100644  22        `\n\
573        This file is awesome!\n\
574        baz.txt/        1487552349  42    12345 100664  4         `\n\
575        baz\n";
576        let mut archive = Archive::new(input.as_bytes());
577        {
578            let entry = archive.next_entry().await.unwrap().unwrap();
579            assert_eq!(entry.header().identifier(), "foo.txt".as_bytes());
580            assert_eq!(entry.header().size(), 7);
581        }
582        {
583            let entry = archive.next_entry().await.unwrap().unwrap();
584            assert_eq!(
585                entry.header().identifier(),
586                "bar.awesome.txt".as_bytes()
587            );
588            assert_eq!(entry.header().size(), 22);
589        }
590        {
591            let entry = archive.next_entry().await.unwrap().unwrap();
592            assert_eq!(entry.header().identifier(), "baz.txt".as_bytes());
593            assert_eq!(entry.header().size(), 4);
594        }
595        assert!(archive.next_entry().await.is_none());
596        assert_eq!(archive.variant(), Variant::GNU);
597    }
598
599    #[tokio::test]
600    async fn read_gnu_archive_with_long_filenames() {
601        let input = "\
602        !<arch>\n\
603        //                                              78        `\n\
604        this_is_a_very_long_filename.txt/\n\
605        and_this_is_another_very_long_filename.txt/\n\
606        /0              1487552916  501   20    100644  7         `\n\
607        foobar\n\n\
608        /34             0           0     0     0       4         `\n\
609        baz\n";
610        let mut archive = Archive::new(input.as_bytes());
611        {
612            let mut entry = archive.next_entry().await.unwrap().unwrap();
613            assert_eq!(
614                entry.header().identifier(),
615                "this_is_a_very_long_filename.txt".as_bytes()
616            );
617            assert_eq!(entry.header().mtime(), 1487552916);
618            assert_eq!(entry.header().uid(), 501);
619            assert_eq!(entry.header().gid(), 20);
620            assert_eq!(entry.header().mode(), 0o100644);
621            assert_eq!(entry.header().size(), 7);
622            let mut buffer = Vec::new();
623            entry.read_to_end(&mut buffer).await.unwrap();
624            assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
625        }
626        {
627            let mut entry = archive.next_entry().await.unwrap().unwrap();
628            assert_eq!(
629                entry.header().identifier(),
630                "and_this_is_another_very_long_filename.txt".as_bytes()
631            );
632            assert_eq!(entry.header().size(), 4);
633            let mut buffer = Vec::new();
634            entry.read_to_end(&mut buffer).await.unwrap();
635            assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
636        }
637        assert!(archive.next_entry().await.is_none());
638        assert_eq!(archive.variant(), Variant::GNU);
639    }
640
641    // MS `.lib` files are very similar to GNU `ar` archives, but with a few
642    // tweaks:
643    // * File names in the name table are terminated by null, rather than /\n
644    // * Numeric entries may be all empty string, interpreted as 0, possibly?
645    #[tokio::test]
646    async fn read_ms_archive_with_long_filenames() {
647        let input = "\
648        !<arch>\n\
649        //                                              76        `\n\
650        this_is_a_very_long_filename.txt\x00\
651        and_this_is_another_very_long_filename.txt\x00\
652        /0              1487552916              100644  7         `\n\
653        foobar\n\n\
654        /33             1446790218              100666  4         `\n\
655        baz\n";
656        let mut archive = Archive::new(input.as_bytes());
657        {
658            let mut entry = archive.next_entry().await.unwrap().unwrap();
659            assert_eq!(
660                entry.header().identifier(),
661                "this_is_a_very_long_filename.txt".as_bytes()
662            );
663            assert_eq!(entry.header().mtime(), 1487552916);
664            assert_eq!(entry.header().uid(), 0);
665            assert_eq!(entry.header().gid(), 0);
666            assert_eq!(entry.header().mode(), 0o100644);
667            assert_eq!(entry.header().size(), 7);
668            let mut buffer = Vec::new();
669            entry.read_to_end(&mut buffer).await.unwrap();
670            assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
671        }
672        {
673            let mut entry = archive.next_entry().await.unwrap().unwrap();
674            assert_eq!(
675                entry.header().identifier(),
676                "and_this_is_another_very_long_filename.txt".as_bytes()
677            );
678            assert_eq!(entry.header().size(), 4);
679            let mut buffer = Vec::new();
680            entry.read_to_end(&mut buffer).await.unwrap();
681            assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
682        }
683        assert!(archive.next_entry().await.is_none());
684        assert_eq!(archive.variant(), Variant::GNU);
685    }
686
687    #[tokio::test]
688    async fn read_gnu_archive_with_space_in_filename() {
689        let input = "\
690        !<arch>\n\
691        foo bar/        0           0     0     0       4         `\n\
692        baz\n";
693        let mut archive = Archive::new(input.as_bytes());
694        {
695            let mut entry = archive.next_entry().await.unwrap().unwrap();
696            assert_eq!(entry.header().identifier(), "foo bar".as_bytes());
697            assert_eq!(entry.header().size(), 4);
698            let mut buffer = Vec::new();
699            entry.read_to_end(&mut buffer).await.unwrap();
700            assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
701        }
702        assert!(archive.next_entry().await.is_none());
703        assert_eq!(archive.variant(), Variant::GNU);
704    }
705
706    #[tokio::test]
707    async fn read_gnu_archive_with_symbol_lookup_table() {
708        let input = b"\
709        !<arch>\n\
710        /               0           0     0     0       15        `\n\
711        \x00\x00\x00\x01\x00\x00\x00\xb2foobar\x00\n\
712        //                                              34        `\n\
713        this_is_a_very_long_filename.txt/\n\
714        /0              1487552916  501   20    100644  7         `\n\
715        foobar\n";
716        let mut archive = Archive::new(input as &[u8]);
717        {
718            let mut entry = archive.next_entry().await.unwrap().unwrap();
719            assert_eq!(
720                entry.header().identifier(),
721                "this_is_a_very_long_filename.txt".as_bytes()
722            );
723            let mut buffer = Vec::new();
724            entry.read_to_end(&mut buffer).await.unwrap();
725            assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
726        }
727        assert!(archive.next_entry().await.is_none());
728    }
729
730    #[tokio::test]
731    async fn read_archive_with_no_padding_byte_in_final_entry() {
732        let input = "\
733        !<arch>\n\
734        foo.txt         1487552916  501   20    100644  7         `\n\
735        foobar\n\n\
736        bar.txt         1487552919  501   20    100644  3         `\n\
737        foo";
738        let mut archive = Archive::new(input.as_bytes());
739        {
740            let entry = archive.next_entry().await.unwrap().unwrap();
741            assert_eq!(entry.header().identifier(), "foo.txt".as_bytes());
742            assert_eq!(entry.header().size(), 7);
743        }
744        {
745            let entry = archive.next_entry().await.unwrap().unwrap();
746            assert_eq!(entry.header().identifier(), "bar.txt".as_bytes());
747            assert_eq!(entry.header().size(), 3);
748        }
749        assert!(archive.next_entry().await.is_none());
750    }
751
752    #[tokio::test]
753    #[should_panic(expected = "Invalid timestamp field in entry header \
754                               (\\\"helloworld  \\\")")]
755    async fn read_archive_with_invalid_mtime() {
756        let input = "\
757        !<arch>\n\
758        foo.txt         helloworld  501   20    100644  7         `\n\
759        foobar\n\n";
760        let mut archive = Archive::new(input.as_bytes());
761        archive.next_entry().await.unwrap().unwrap();
762    }
763
764    #[tokio::test]
765    async fn read_archive_with_mtime_minus_one() {
766        let input = "\
767        !<arch>\n\
768        foo.txt         -1          501   20    100644  7         `\n\
769        foobar\n\n";
770        let mut archive = Archive::new(input.as_bytes());
771        archive.next_entry().await.unwrap().unwrap();
772    }
773
774    #[tokio::test]
775    #[should_panic(expected = "Invalid owner ID field in entry header \
776                               (\\\"foo   \\\")")]
777    async fn read_archive_with_invalid_uid() {
778        let input = "\
779        !<arch>\n\
780        foo.txt         1487552916  foo   20    100644  7         `\n\
781        foobar\n\n";
782        let mut archive = Archive::new(input.as_bytes());
783        archive.next_entry().await.unwrap().unwrap();
784    }
785
786    #[tokio::test]
787    #[should_panic(expected = "Invalid group ID field in entry header \
788                               (\\\"bar   \\\")")]
789    async fn read_archive_with_invalid_gid() {
790        let input = "\
791        !<arch>\n\
792        foo.txt         1487552916  501   bar   100644  7         `\n\
793        foobar\n\n";
794        let mut archive = Archive::new(input.as_bytes());
795        archive.next_entry().await.unwrap().unwrap();
796    }
797
798    #[tokio::test]
799    #[should_panic(expected = "Invalid file mode field in entry header \
800                               (\\\"foobar  \\\")")]
801    async fn read_archive_with_invalid_mode() {
802        let input = "\
803        !<arch>\n\
804        foo.txt         1487552916  501   20    foobar  7         `\n\
805        foobar\n\n";
806        let mut archive = Archive::new(input.as_bytes());
807        archive.next_entry().await.unwrap().unwrap();
808    }
809
810    #[tokio::test]
811    #[should_panic(expected = "Invalid file size field in entry header \
812                               (\\\"whatever  \\\")")]
813    async fn read_archive_with_invalid_size() {
814        let input = "\
815        !<arch>\n\
816        foo.txt         1487552916  501   20    100644  whatever  `\n\
817        foobar\n\n";
818        let mut archive = Archive::new(input.as_bytes());
819        archive.next_entry().await.unwrap().unwrap();
820    }
821
822    #[tokio::test]
823    #[should_panic(expected = "Invalid BSD filename length field in entry \
824                               header (\\\"foobar       \\\")")]
825    async fn read_bsd_archive_with_invalid_filename_length() {
826        let input = "\
827        !<arch>\n\
828        #1/foobar       1487552916  501   20    100644  39        `\n\
829        this_is_a_very_long_filename.txtfoobar\n\n";
830        let mut archive = Archive::new(input.as_bytes());
831        archive.next_entry().await.unwrap().unwrap();
832    }
833
834    #[tokio::test]
835    #[should_panic(expected = "Invalid GNU filename index field in entry \
836                               header (\\\"foobar         \\\")")]
837    async fn read_gnu_archive_with_invalid_filename_index() {
838        let input = "\
839        !<arch>\n\
840        //                                              34        `\n\
841        this_is_a_very_long_filename.txt/\n\
842        /foobar         1487552916  501   20    100644  7         `\n\
843        foobar\n\n";
844        let mut archive = Archive::new(input.as_bytes());
845        archive.next_entry().await.unwrap().unwrap();
846    }
847
848    #[tokio::test]
849    async fn seek_within_entry() {
850        let input = "\
851        !<arch>\n\
852        foo.txt         1487552916  501   20    100644  31        `\n\
853        abcdefghij0123456789ABCDEFGHIJ\n\n\
854        bar.awesome.txt 1487552919  501   20    100644  22        `\n\
855        This file is awesome!\n";
856        let mut archive = Archive::new(Cursor::new(input.as_bytes()));
857        {
858            // Parse the first entry, then seek around the entry, performing
859            // different reads.
860            let mut entry = archive.next_entry().await.unwrap().unwrap();
861            let mut buffer = [0; 5];
862            entry.seek(SeekFrom::Start(10)).await.unwrap();
863            entry.read_exact(&mut buffer).await.unwrap();
864            assert_eq!(&buffer, "01234".as_bytes());
865            entry.seek(SeekFrom::Start(5)).await.unwrap();
866            entry.read_exact(&mut buffer).await.unwrap();
867            assert_eq!(&buffer, "fghij".as_bytes());
868            entry.seek(SeekFrom::End(-10)).await.unwrap();
869            entry.read_exact(&mut buffer).await.unwrap();
870            assert_eq!(&buffer, "BCDEF".as_bytes());
871            entry.seek(SeekFrom::End(-30)).await.unwrap();
872            entry.read_exact(&mut buffer).await.unwrap();
873            assert_eq!(&buffer, "bcdef".as_bytes());
874            entry.seek(SeekFrom::Current(10)).await.unwrap();
875            entry.read_exact(&mut buffer).await.unwrap();
876            assert_eq!(&buffer, "6789A".as_bytes());
877            entry.seek(SeekFrom::Current(-8)).await.unwrap();
878            entry.read_exact(&mut buffer).await.unwrap();
879            assert_eq!(&buffer, "34567".as_bytes());
880            // The archive reader knows how many bytes have been consumed and
881            // skips the rest of the entry data so that the archive reader is
882            // ready to parse the next entry.
883        }
884        {
885            // Parse the second entry and read in all the entry data.
886            let mut entry = archive.next_entry().await.unwrap().unwrap();
887            let mut buffer = Vec::new();
888            entry.read_to_end(&mut buffer).await.unwrap();
889            assert_eq!(&buffer as &[u8], "This file is awesome!\n".as_bytes());
890        }
891    }
892
893    #[tokio::test]
894    #[should_panic(expected = "Invalid seek to negative position (-17)")]
895    async fn seek_entry_to_negative_position() {
896        let input = "\
897        !<arch>\n\
898        foo.txt         1487552916  501   20    100644  30        `\n\
899        abcdefghij0123456789ABCDEFGHIJ";
900        let mut archive = Archive::new(Cursor::new(input.as_bytes()));
901        let mut entry = archive.next_entry().await.unwrap().unwrap();
902        entry.seek(SeekFrom::End(-47)).await.unwrap();
903    }
904
905    #[tokio::test]
906    #[should_panic(expected = "Invalid seek to position past end of entry \
907                               (47 vs. 30)")]
908    async fn seek_entry_beyond_end() {
909        let input = "\
910        !<arch>\n\
911        foo.txt         1487552916  501   20    100644  30        `\n\
912        abcdefghij0123456789ABCDEFGHIJ";
913        let mut archive = Archive::new(Cursor::new(input.as_bytes()));
914        let mut entry = archive.next_entry().await.unwrap().unwrap();
915        entry.seek(SeekFrom::Start(47)).await.unwrap();
916    }
917
918    #[tokio::test]
919    async fn count_entries_in_bsd_archive() {
920        let input = b"\
921        !<arch>\n\
922        #1/32           1487552916  501   20    100644  39        `\n\
923        this_is_a_very_long_filename.txtfoobar\n\n\
924        baz.txt         0           0     0     0       4         `\n\
925        baz\n";
926        let mut archive = Archive::new(Cursor::new(input as &[u8]));
927        assert_eq!(archive.count_entries().await.unwrap(), 2);
928        {
929            let mut entry = archive.next_entry().await.unwrap().unwrap();
930            assert_eq!(
931                entry.header().identifier(),
932                "this_is_a_very_long_filename.txt".as_bytes()
933            );
934            let mut buffer = Vec::new();
935            entry.read_to_end(&mut buffer).await.unwrap();
936            assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
937        }
938        assert_eq!(archive.count_entries().await.unwrap(), 2);
939        {
940            let mut entry = archive.next_entry().await.unwrap().unwrap();
941            assert_eq!(entry.header().identifier(), "baz.txt".as_bytes());
942            let mut buffer = Vec::new();
943            entry.read_to_end(&mut buffer).await.unwrap();
944            assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
945        }
946        assert_eq!(archive.count_entries().await.unwrap(), 2);
947    }
948
949    #[tokio::test]
950    async fn count_entries_in_gnu_archive() {
951        let input = b"\
952        !<arch>\n\
953        /               0           0     0     0       15        `\n\
954        \x00\x00\x00\x01\x00\x00\x00\xb2foobar\x00\n\
955        //                                              34        `\n\
956        this_is_a_very_long_filename.txt/\n\
957        /0              1487552916  501   20    100644  7         `\n\
958        foobar\n\n\
959        baz.txt/        1487552349  42    12345 100664  4         `\n\
960        baz\n";
961        let mut archive = Archive::new(Cursor::new(input as &[u8]));
962        assert_eq!(archive.count_entries().await.unwrap(), 2);
963        {
964            let mut entry = archive.next_entry().await.unwrap().unwrap();
965            assert_eq!(
966                entry.header().identifier(),
967                "this_is_a_very_long_filename.txt".as_bytes()
968            );
969            let mut buffer = Vec::new();
970            entry.read_to_end(&mut buffer).await.unwrap();
971            assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
972        }
973        assert_eq!(archive.count_entries().await.unwrap(), 2);
974        {
975            let mut entry = archive.next_entry().await.unwrap().unwrap();
976            assert_eq!(entry.header().identifier(), "baz.txt".as_bytes());
977            let mut buffer = Vec::new();
978            entry.read_to_end(&mut buffer).await.unwrap();
979            assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
980        }
981        assert_eq!(archive.count_entries().await.unwrap(), 2);
982    }
983
984    #[tokio::test]
985    async fn jump_to_entry_in_bsd_archive() {
986        let input = b"\
987        !<arch>\n\
988        hello.txt       1487552316  42    12345 100644  14        `\n\
989        Hello, world!\n\
990        #1/32           1487552916  501   20    100644  39        `\n\
991        this_is_a_very_long_filename.txtfoobar\n\n\
992        baz.txt         1487552349  42    12345 100664  4         `\n\
993        baz\n";
994        let mut archive = Archive::new(Cursor::new(input as &[u8]));
995        {
996            // Jump to the second entry and check its contents.
997            let mut entry = archive.jump_to_entry(1).await.unwrap();
998            assert_eq!(
999                entry.header().identifier(),
1000                "this_is_a_very_long_filename.txt".as_bytes()
1001            );
1002            let mut buffer = Vec::new();
1003            entry.read_to_end(&mut buffer).await.unwrap();
1004            assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
1005        }
1006        {
1007            // Read the next entry, which should be the third one now.
1008            let mut entry = archive.next_entry().await.unwrap().unwrap();
1009            assert_eq!(entry.header().identifier(), "baz.txt".as_bytes());
1010            let mut buffer = Vec::new();
1011            entry.read_to_end(&mut buffer).await.unwrap();
1012            assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
1013        }
1014        // We should be at the end of the archive now.
1015        assert!(archive.next_entry().await.is_none());
1016        {
1017            // Jump back to the first entry and check its contents.
1018            let mut entry = archive.jump_to_entry(0).await.unwrap();
1019            assert_eq!(entry.header().identifier(), "hello.txt".as_bytes());
1020            let mut buffer = Vec::new();
1021            entry.read_to_end(&mut buffer).await.unwrap();
1022            assert_eq!(&buffer as &[u8], "Hello, world!\n".as_bytes());
1023        }
1024        {
1025            // Read the next entry, which should be the second one again.
1026            let mut entry = archive.jump_to_entry(1).await.unwrap();
1027            assert_eq!(
1028                entry.header().identifier(),
1029                "this_is_a_very_long_filename.txt".as_bytes()
1030            );
1031            let mut buffer = Vec::new();
1032            entry.read_to_end(&mut buffer).await.unwrap();
1033            assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
1034        }
1035        {
1036            // Jump back to the first entry and check its contents.
1037            let mut entry = archive.jump_to_entry(0).await.unwrap();
1038            assert_eq!(entry.header().identifier(), "hello.txt".as_bytes());
1039            let mut buffer = Vec::new();
1040            entry.read_to_end(&mut buffer).await.unwrap();
1041            assert_eq!(&buffer as &[u8], "Hello, world!\n".as_bytes());
1042        }
1043        {
1044            // Read the next entry, which should be the second one again.
1045            let mut entry = archive.next_entry().await.unwrap().unwrap();
1046            assert_eq!(
1047                entry.header().identifier(),
1048                "this_is_a_very_long_filename.txt".as_bytes()
1049            );
1050            let mut buffer = Vec::new();
1051            entry.read_to_end(&mut buffer).await.unwrap();
1052            assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
1053        }
1054    }
1055
1056    #[tokio::test]
1057    async fn jump_to_entry_in_gnu_archive() {
1058        let input = b"\
1059        !<arch>\n\
1060        //                                              34        `\n\
1061        this_is_a_very_long_filename.txt/\n\
1062        hello.txt/      1487552316  42    12345 100644  14        `\n\
1063        Hello, world!\n\
1064        /0              1487552916  501   20    100644  7         `\n\
1065        foobar\n\n\
1066        baz.txt/        1487552349  42    12345 100664  4         `\n\
1067        baz\n";
1068        let mut archive = Archive::new(Cursor::new(input as &[u8]));
1069        {
1070            // Jump to the second entry and check its contents.
1071            let mut entry = archive.jump_to_entry(1).await.unwrap();
1072            assert_eq!(
1073                entry.header().identifier(),
1074                "this_is_a_very_long_filename.txt".as_bytes()
1075            );
1076            let mut buffer = Vec::new();
1077            entry.read_to_end(&mut buffer).await.unwrap();
1078            assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
1079        }
1080        {
1081            // Read the next entry, which should be the third one now.
1082            let mut entry = archive.next_entry().await.unwrap().unwrap();
1083            assert_eq!(entry.header().identifier(), "baz.txt".as_bytes());
1084            let mut buffer = Vec::new();
1085            entry.read_to_end(&mut buffer).await.unwrap();
1086            assert_eq!(&buffer as &[u8], "baz\n".as_bytes());
1087        }
1088        // We should be at the end of the archive now.
1089        assert!(archive.next_entry().await.is_none());
1090        {
1091            // Jump back to the first entry and check its contents.
1092            let mut entry = archive.jump_to_entry(0).await.unwrap();
1093            assert_eq!(entry.header().identifier(), "hello.txt".as_bytes());
1094            let mut buffer = Vec::new();
1095            entry.read_to_end(&mut buffer).await.unwrap();
1096            assert_eq!(&buffer as &[u8], "Hello, world!\n".as_bytes());
1097        }
1098        {
1099            // Read the next entry, which should be the second one again.
1100            let mut entry = archive.next_entry().await.unwrap().unwrap();
1101            assert_eq!(
1102                entry.header().identifier(),
1103                "this_is_a_very_long_filename.txt".as_bytes()
1104            );
1105            let mut buffer = Vec::new();
1106            entry.read_to_end(&mut buffer).await.unwrap();
1107            assert_eq!(&buffer as &[u8], "foobar\n".as_bytes());
1108        }
1109    }
1110
1111    #[tokio::test]
1112    async fn list_symbols_in_bsd_archive() {
1113        let input = b"\
1114        !<arch>\n\
1115        #1/12           0           0     0     0       60        `\n\
1116        __.SYMDEF\x00\x00\x00\x18\x00\x00\x00\
1117        \x00\x00\x00\x00\x80\x00\x00\x00\
1118        \x07\x00\x00\x00\x80\x00\x00\x00\
1119        \x0b\x00\x00\x00\x80\x00\x00\x00\
1120        \x10\x00\x00\x00foobar\x00baz\x00quux\x00\
1121        foo.o/          1487552916  501   20    100644  16        `\n\
1122        foobar,baz,quux\n";
1123        let mut archive = Archive::new(Cursor::new(input as &[u8]));
1124        assert_eq!(archive.symbols().await.unwrap().len(), 3);
1125        assert_eq!(archive.variant(), Variant::BSD);
1126        let symbols = archive.symbols().await.unwrap().collect::<Vec<&[u8]>>();
1127        let expected: Vec<&[u8]> = vec![b"foobar", b"baz", b"quux"];
1128        assert_eq!(symbols, expected);
1129    }
1130
1131    #[tokio::test]
1132    async fn list_sorted_symbols_in_bsd_archive() {
1133        let input = b"\
1134        !<arch>\n\
1135        #1/16           0           0     0     0       64        `\n\
1136        __.SYMDEF SORTED\x18\x00\x00\x00\
1137        \x00\x00\x00\x00\x80\x00\x00\x00\
1138        \x04\x00\x00\x00\x80\x00\x00\x00\
1139        \x0b\x00\x00\x00\x80\x00\x00\x00\
1140        \x10\x00\x00\x00baz\x00foobar\x00quux\x00\
1141        foo.o/          1487552916  501   20    100644  16        `\n\
1142        foobar,baz,quux\n";
1143        let mut archive = Archive::new(Cursor::new(input as &[u8]));
1144        assert_eq!(archive.symbols().await.unwrap().len(), 3);
1145        assert_eq!(archive.variant(), Variant::BSD);
1146        let symbols = archive.symbols().await.unwrap().collect::<Vec<&[u8]>>();
1147        let expected: Vec<&[u8]> = vec![b"baz", b"foobar", b"quux"];
1148        assert_eq!(symbols, expected);
1149    }
1150
1151    #[tokio::test]
1152    async fn list_symbols_in_gnu_archive() {
1153        let input = b"\
1154        !<arch>\n\
1155        /               0           0     0     0       32        `\n\
1156        \x00\x00\x00\x03\x00\x00\x00\x5c\x00\x00\x00\x5c\x00\x00\x00\x5c\
1157        foobar\x00baz\x00quux\x00\
1158        foo.o/          1487552916  501   20    100644  16        `\n\
1159        foobar,baz,quux\n";
1160        let mut archive = Archive::new(Cursor::new(input as &[u8]));
1161        assert_eq!(archive.symbols().await.unwrap().len(), 3);
1162        assert_eq!(archive.variant(), Variant::GNU);
1163        let symbols = archive.symbols().await.unwrap().collect::<Vec<&[u8]>>();
1164        let expected: Vec<&[u8]> = vec![b"foobar", b"baz", b"quux"];
1165        assert_eq!(symbols, expected);
1166    }
1167
1168    #[tokio::test]
1169    async fn non_multiple_of_two_long_ident_in_gnu_archive() {
1170        let mut buffer = std::io::Cursor::new(Vec::new());
1171
1172        {
1173            let filenames = vec![
1174                b"rust.metadata.bin".to_vec(),
1175                b"compiler_builtins-78891cf83a7d3547.dummy_name.rcgu.o"
1176                    .to_vec(),
1177            ];
1178            let mut builder = GnuBuilder::new(&mut buffer, filenames.clone());
1179
1180            for filename in filenames {
1181                builder
1182                    .append(&Header::new(filename, 1), &b"?"[..])
1183                    .await
1184                    .expect("add file");
1185            }
1186        }
1187
1188        buffer.set_position(0);
1189
1190        let mut archive = Archive::new(buffer);
1191        while let Some(entry) = archive.next_entry().await {
1192            entry.unwrap();
1193        }
1194    }
1195
1196    /// Regression test for https://github.com/mdsteele/rust-ar/issues/22
1197    #[tokio::test]
1198    #[should_panic(expected = "GNU filename index out of range")]
1199    async fn issue_22() {
1200        let data = &[
1201            33, 60, 97, 114, 99, 104, 62, 10, 99, 104, 60, 159, 149, 33, 62,
1202            10, 219, 87, 219, 219, 219, 96, 48, 48, 48, 48, 48, 48, 48, 48,
1203            48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
1204            48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
1205            48, 48, 48, 48, 48, 48, 47, 48, 48, 48, 48, 48, 48, 48, 48, 48,
1206            48, 49, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 39, 48, 48, 48,
1207            48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
1208            48, 48, 48, 48, 51, 49, 50, 56, 48, 48, 54, 54, 54, 51, 52, 56,
1209            48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
1210            48, 48, 48, 48, 48, 48, 48, 48, 47, 48, 0, 40,
1211        ];
1212        let mut archive = Archive::new(std::io::Cursor::new(data));
1213        let _num_entries = archive.count_entries().await.unwrap();
1214    }
1215
1216    /// Test for entries with octal literal (0o) prefix in the mode field.
1217    #[tokio::test]
1218    async fn read_archive_with_radix_prefixed_mode() {
1219        let input = "\
1220        !<arch>\n\
1221        foo.txt/        1487552916  501   20    0o1006447         `\n\
1222        foobar\n";
1223        let mut archive = Archive::new(input.as_bytes());
1224        {
1225            let entry = archive.next_entry().await.unwrap().unwrap();
1226            assert_eq!(entry.header().identifier(), "foo.txt".as_bytes());
1227            assert_eq!(entry.header().mode(), 0o100644);
1228            assert_eq!(entry.header().size(), 7);
1229        }
1230        assert!(archive.next_entry().await.is_none());
1231    }
1232}