1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! This is a Rust implementation of the [Mozilla Archive (MAR) file format][1]
//! used to deliver automatic updates to Firefox.  It includes both a library and
//! a command-line tool for reading and writing MAR files.
//!
//! This code is subject to the terms of the Mozilla Public License, v. 2.0.
//!
//! [1]: https://wiki.mozilla.org/Software_Update:MAR

#![warn(missing_docs)]

use std::{
    fs::File,
    io::{self, BufReader, Cursor, ErrorKind, Read, Seek, SeekFrom},
    path::Path,
};

use byteorder::{BigEndian, ReadBytesExt};
use compression::CompressedRead;
use read::{get_info, read_next_item};

pub mod compression;
pub mod extract;
pub mod read;

/// Metadata about an entire MAR file.
pub struct MarFileInfo {
    offset_to_index: u32,
    #[allow(dead_code)]
    has_signature_block: bool,
    #[allow(dead_code)]
    num_signatures: u32,
    #[allow(dead_code)]
    has_additional_blocks: bool,
    #[allow(dead_code)]
    offset_additional_blocks: u32,
    #[allow(dead_code)]
    num_additional_blocks: u32,
}

/// An entry in the MAR index.
pub struct MarItem {
    /// Position of the item within the archive file.
    offset: u32,
    /// Length of data in bytes.
    pub length: u32,
    /// File mode bits.
    pub flags: u32,
    /// File path.
    pub name: String,
}

/// A high level interface to read the contents of a mar file.
pub struct Mar<R> {
    info: MarFileInfo,
    buffer: R,
}

impl<R> Mar<R>
where
    R: Read + Seek,
{
    /// Creates a Mar instance from any seekable readable.
    pub fn from_buffer(mut buffer: R) -> io::Result<Mar<R>> {
        let info = get_info(&mut buffer)?;

        Ok(Mar { info, buffer })
    }
}

impl Mar<BufReader<File>> {
    /// Creates a Mar instance from a local file path.
    pub fn from_path<P: AsRef<Path>>(path: P) -> io::Result<Mar<BufReader<File>>> {
        let buffer = BufReader::new(File::open(path)?);
        Self::from_buffer(buffer)
    }
}

impl<R> Mar<R>
where
    R: Read + Seek,
{
    /// Reads the contents of a file from this mar.
    pub fn read<'a>(&'a mut self, item: &MarItem) -> io::Result<CompressedRead<'a, R>> {
        self.buffer.seek(SeekFrom::Start(item.offset as u64))?;
        CompressedRead::new(&mut self.buffer, item.length as u64)
    }

    /// Returns an Iterator to the list of files in this mar.
    pub fn files(&mut self) -> io::Result<Files> {
        self.buffer
            .seek(SeekFrom::Start(self.info.offset_to_index as u64))?;

        // Read the index into memory.
        let size_of_index = self.buffer.read_u32::<BigEndian>()?;
        let mut index = vec![0; size_of_index as usize];
        self.buffer.read_exact(&mut index)?;

        Ok(Files {
            index: Cursor::new(index),
        })
    }
}

/// An iterator over the files in a mar.
pub struct Files {
    index: Cursor<Vec<u8>>,
}

impl Iterator for Files {
    type Item = io::Result<MarItem>;

    fn next(&mut self) -> Option<Self::Item> {
        match read_next_item(&mut self.index) {
            Ok(item) => Some(Ok(item)),
            Err(e) => {
                if e.kind() == ErrorKind::UnexpectedEof {
                    None
                } else {
                    Some(Err(e))
                }
            }
        }
    }
}