aboutsummaryrefslogtreecommitdiff
path: root/nix-rust/src/nar.rs
blob: aa05d815b2e1c0330a381389bb2f2f115db0b7f5 (plain)
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
use crate::Error;
use byteorder::{LittleEndian, ReadBytesExt};
use std::convert::TryFrom;
use std::io::Read;

pub fn parse<R: Read>(input: &mut R) -> Result<(), Error> {
    if String::read(input)? != NAR_VERSION_MAGIC {
        return Err(Error::BadNarVersionMagic);
    }

    parse_file(input)
}

const NAR_VERSION_MAGIC: &str = "nix-archive-1";

fn parse_file<R: Read>(input: &mut R) -> Result<(), Error> {
    if String::read(input)? != "(" {
        return Err(Error::MissingNarOpenTag);
    }

    if String::read(input)? != "type" {
        return Err(Error::MissingNarField);
    }

    match String::read(input)?.as_ref() {
        "regular" => {
            let mut executable = false;
            let mut tag = String::read(input)?;
            if tag == "executable" {
                executable = true;
                if String::read(input)? != "" {
                    return Err(Error::BadExecutableField);
                }
                tag = String::read(input)?;
            }
            if tag != "contents" {
                return Err(Error::MissingNarField);
            }
            let contents = Vec::<u8>::read(input)?;
            if String::read(input)? != ")" {
                return Err(Error::MissingNarCloseTag);
            }
        }
        "directory" => loop {
            match String::read(input)?.as_ref() {
                "entry" => {
                    if String::read(input)? != "(" {
                        return Err(Error::MissingNarOpenTag);
                    }
                    if String::read(input)? != "name" {
                        return Err(Error::MissingNarField);
                    }
                    let name = String::read(input)?;
                    if String::read(input)? != "node" {
                        return Err(Error::MissingNarField);
                    }
                    parse_file(input)?;
                    let tag = String::read(input)?;
                    if tag != ")" {
                        return Err(Error::MissingNarCloseTag);
                    }
                }
                ")" => break,
                s => return Err(Error::BadNarField(s.into())),
            }
        },
        "symlink" => {
            if String::read(input)? != "target" {
                return Err(Error::MissingNarField);
            }
            let target = String::read(input)?;
            if String::read(input)? != ")" {
                return Err(Error::MissingNarCloseTag);
            }
        }
        s => return Err(Error::BadNarField(s.into())),
    }

    Ok(())
}

trait Deserialize: Sized {
    fn read<R: Read>(input: &mut R) -> Result<Self, Error>;
}

impl Deserialize for String {
    fn read<R: Read>(input: &mut R) -> Result<Self, Error> {
        let buf = Deserialize::read(input)?;
        Ok(String::from_utf8(buf).map_err(|_| Error::BadNarString)?)
    }
}

impl Deserialize for Vec<u8> {
    fn read<R: Read>(input: &mut R) -> Result<Self, Error> {
        let n: usize = Deserialize::read(input)?;
        let mut buf = vec![0; n];
        input.read_exact(&mut buf)?;
        skip_padding(input, n)?;
        Ok(buf)
    }
}

fn skip_padding<R: Read>(input: &mut R, len: usize) -> Result<(), Error> {
    if len % 8 != 0 {
        let mut buf = [0; 8];
        let buf = &mut buf[0..8 - (len % 8)];
        input.read_exact(buf)?;
        if !buf.iter().all(|b| *b == 0) {
            return Err(Error::BadNarPadding);
        }
    }
    Ok(())
}

impl Deserialize for u64 {
    fn read<R: Read>(input: &mut R) -> Result<Self, Error> {
        Ok(input.read_u64::<LittleEndian>()?)
    }
}

impl Deserialize for usize {
    fn read<R: Read>(input: &mut R) -> Result<Self, Error> {
        let n: u64 = Deserialize::read(input)?;
        Ok(usize::try_from(n).map_err(|_| Error::NarSizeFieldTooBig)?)
    }
}