Skip to main content

repro_env/pkgs/
debian.rs

1use crate::errors::*;
2use crate::pkgs::Pkg;
3use std::io::BufReader;
4use std::io::Read;
5
6pub fn parse_control(control: &str) -> Result<Pkg> {
7    let mut name = None;
8    let mut version = None;
9
10    for line in control.lines() {
11        if let Some(value) = line.strip_prefix("Package: ") {
12            name = Some(value.to_string());
13        }
14
15        if let Some(value) = line.strip_prefix("Version: ") {
16            version = Some(value.to_string());
17        }
18    }
19
20    Ok(Pkg {
21        name: name.context("Failed to find package name in deb control data")?,
22        version: version.context("Failed to find package version in deb control data")?,
23    })
24}
25
26pub fn parse_control_tar<R: Read>(filename: &[u8], reader: R) -> Result<Pkg> {
27    let mut buf = Vec::new();
28    let mut reader = BufReader::new(reader);
29    match filename {
30        b"control.tar.xz" => lzma_rs::xz_decompress(&mut reader, &mut buf)?,
31        _ => bail!("Unsupported compression for control.tar: {filename:?}"),
32    }
33
34    let mut tar = tar::Archive::new(&buf[..]);
35    for entry in tar.entries()? {
36        let mut entry = entry?;
37        let path = entry.path()?;
38        let filename = path
39            .to_str()
40            .with_context(|| anyhow!("Package contains paths with invalid encoding: {:?}", path))?;
41
42        if filename == "./control" {
43            let mut buf = String::new();
44            entry.read_to_string(&mut buf)?;
45            return parse_control(&buf);
46        }
47    }
48
49    bail!("Failed to find control data in control.tar")
50}
51
52pub fn parse<R: Read>(reader: R) -> Result<Pkg> {
53    let mut archive = ar::Archive::new(reader);
54    while let Some(entry) = archive.next_entry() {
55        let mut entry = entry?;
56        let filename = entry.header().identifier();
57        if !filename.starts_with(b"control.tar") {
58            continue;
59        }
60        let filename = filename.to_owned();
61        return parse_control_tar(&filename, &mut entry);
62    }
63
64    bail!("Failed to find control data")
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_parse_control_data() -> Result<()> {
73        let data = "Package: binutils-common\nSource: binutils\nVersion: 2.40-2\nArchitecture: amd64\nMaintainer: Matthias Klose <doko@debian.org>\nInstalled-Size: 15021\nBreaks: binutils (<< 2.38.50.20220527-2), binutils-multiarch (<< 2.38.50.20220527-2)\nReplaces: binutils (<< 2.38.50.20220527-2), binutils-multiarch (<< 2.38.50.20220527-2)\nSection: devel\nPriority: optional\nMulti-Arch: same\nHomepage: https://www.gnu.org/software/binutils/\nDescription: Common files for the GNU assembler, linker and binary utilities\n This package contains the localization files used by binutils packages for\n various target architectures and parts of the binutils documentation. It is\n not useful on its own.\n";
74        let data = parse_control(data)?;
75        assert_eq!(
76            data,
77            Pkg {
78                name: "binutils-common".to_string(),
79                version: "2.40-2".to_string(),
80            }
81        );
82        Ok(())
83    }
84
85    #[test]
86    fn test_parse_deb() -> Result<()> {
87        let tar = {
88            let data = b"Package: binutils-common\nSource: binutils\nVersion: 2.40-2\nArchitecture: amd64\nMaintainer: Matthias Klose <doko@debian.org>\nInstalled-Size: 15021\nBreaks: binutils (<< 2.38.50.20220527-2), binutils-multiarch (<< 2.38.50.20220527-2)\nReplaces: binutils (<< 2.38.50.20220527-2), binutils-multiarch (<< 2.38.50.20220527-2)\nSection: devel\nPriority: optional\nMulti-Arch: same\nHomepage: https://www.gnu.org/software/binutils/\nDescription: Common files for the GNU assembler, linker and binary utilities\n This package contains the localization files used by binutils packages for\n various target architectures and parts of the binutils documentation. It is\n not useful on its own.\n";
89
90            let mut tar = tar::Builder::new(Vec::new());
91
92            // it's non-trivial to make a tar::Header with path set to `./control`, so we parse an existing one
93            let mut header = tar::Header::from_byte_slice(&[
94                0x2e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00,
95                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
96                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
97                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
98                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
99                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
100                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
101                0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x36, 0x34, 0x34, 0x00, 0x30, 0x30, 0x30, 0x30,
102                0x30, 0x30, 0x30, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x30,
103                0x30, 0x30, 0x30, 0x30, 0x31, 0x30, 0x37, 0x30, 0x34, 0x00, 0x31, 0x34, 0x34, 0x31,
104                0x34, 0x37, 0x34, 0x35, 0x31, 0x30, 0x34, 0x00, 0x30, 0x31, 0x31, 0x33, 0x32, 0x32,
105                0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
106                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
107                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
108                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
109                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
110                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
111                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
112                0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00, 0x72,
113                0x6f, 0x6f, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
114                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
115                0x00, 0x00, 0x00, 0x72, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
116                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
117                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
118                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
119                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
120                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
121                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
122                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
123                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
124                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
125                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
126                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
127                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
128                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
129                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
130                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
131            ])
132            .clone();
133
134            header.set_size(data.len() as u64);
135            header.set_cksum();
136            tar.append(&header, &data[..])?;
137
138            tar.into_inner()?
139        };
140        let compressed = {
141            let mut compressed = Vec::new();
142            lzma_rs::xz_compress(&mut &tar[..], &mut compressed)?;
143            compressed
144        };
145        let deb = {
146            let mut ar = ar::Builder::new(Vec::new());
147            let header = ar::Header::new(b"control.tar.xz".to_vec(), compressed.len() as u64);
148            ar.append(&header, &compressed[..])?;
149            ar.into_inner()?
150        };
151
152        let pkg = parse(&deb[..])?;
153        assert_eq!(
154            pkg,
155            Pkg {
156                name: "binutils-common".to_string(),
157                version: "2.40-2".to_string(),
158            }
159        );
160
161        Ok(())
162    }
163}