apt_release_file/
lib.rs

1extern crate chrono;
2extern crate deb_architectures;
3#[macro_use]
4extern crate smart_default;
5
6mod entry;
7mod image_size;
8mod time;
9
10pub use self::entry::*;
11pub use self::image_size::*;
12
13use self::time::get_time;
14use chrono::{DateTime, Utc};
15use std::collections::BTreeMap;
16use std::path::Path;
17use std::str::FromStr;
18use std::{fs, io};
19
20/// The dist release file is a file in the apt repository that points to all other dist files in the archive.
21#[derive(Debug, SmartDefault, Clone, PartialEq)]
22pub struct DistRelease {
23    pub architectures: Vec<String>,
24    pub codename: String,
25    pub components: Vec<String>,
26    #[default = "Utc::now()"]
27    pub date: DateTime<Utc>,
28    pub description: String,
29    pub label: String,
30    pub origin: String,
31    pub suite: String,
32    pub version: String,
33    pub sums: BTreeMap<String, EntryComponents>,
34}
35
36impl DistRelease {
37    pub fn from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
38        fs::read_to_string(path).and_then(|string| string.parse::<Self>())
39    }
40}
41
42impl FromStr for DistRelease {
43    type Err = io::Error;
44
45    fn from_str(input: &str) -> Result<Self, Self::Err> {
46        let mut iterator = input.lines();
47
48        let mut release = DistRelease::default();
49
50        #[derive(Copy, Clone)]
51        enum Variant {
52            Archs,
53            Codename,
54            Components,
55            Date,
56            Description,
57            Label,
58            Origin,
59            Suite,
60            Version,
61        }
62
63        let mut entries = vec![
64            ("Architectures:", Variant::Archs),
65            ("Codename:", Variant::Codename),
66            ("Components:", Variant::Components),
67            ("Date:", Variant::Date),
68            ("Description:", Variant::Description),
69            ("Label:", Variant::Label),
70            ("Origin:", Variant::Origin),
71            ("Suite:", Variant::Suite),
72            ("Version:", Variant::Version),
73        ];
74
75        let mut remove = None;
76
77        fn get_string(value: &str) -> String {
78            value.trim().to_owned()
79        }
80
81        fn get_vec(value: &str) -> Vec<String> {
82            value.split_whitespace().map(String::from).collect()
83        }
84
85        let mut sum = None;
86
87        while !entries.is_empty() {
88            let line = iterator.next().unwrap();
89
90            for (id, &(ref key, variant)) in entries.iter().enumerate() {
91                if line.starts_with(key) {
92                    remove = Some(id);
93
94                    let value = &line[key.len()..];
95
96                    match variant {
97                        Variant::Archs => release.architectures = get_vec(value),
98                        Variant::Codename => release.codename = get_string(value),
99                        Variant::Components => release.components = get_vec(value),
100                        Variant::Date => release.date = get_time(value)?,
101                        Variant::Description => release.description = get_string(value),
102                        Variant::Label => release.label = get_string(value),
103                        Variant::Origin => release.origin = get_string(value),
104                        Variant::Suite => release.suite = get_string(value),
105                        Variant::Version => release.version = get_string(value),
106                    }
107                }
108            }
109
110            if let Some(pos) = remove.take() {
111                entries.remove(pos);
112            } else if line.ends_with(':') {
113                sum = Some(line.trim()[..line.len() - 1].to_owned());
114                break
115            } else {
116                return Err(io::Error::new(
117                    io::ErrorKind::InvalidData,
118                    format!("unknown key in release file: {}", line),
119                ));
120            }
121        }
122
123        let mut active_hash = sum.unwrap_or_default();
124        let mut active_components = EntryComponents::default();
125
126        for line in iterator {
127            if line.starts_with(' ') {
128                match line.parse::<ReleaseEntry>() {
129                    Ok(mut entry) => {
130                        let mut path = String::new();
131                        ::std::mem::swap(&mut path, &mut entry.path);
132                        let base = match path.find('.') {
133                            Some(pos) => &path[..pos],
134                            None => &path,
135                        };
136
137                        match path.find('/') {
138                            Some(pos) => {
139                                let component = &path[..pos];
140                                // TODO: Prevent this allocation.
141                                entry.path = path[pos + 1..].to_owned();
142
143                                active_components
144                                    .components
145                                    .entry(component.into())
146                                    .and_modify(|e| {
147                                        e.entry(base.into())
148                                            .and_modify(|e| e.push(entry.to_owned()))
149                                            .or_insert_with(|| vec![entry.to_owned()]);
150                                    })
151                                    .or_insert_with(|| {
152                                        let mut map = BTreeMap::new();
153                                        map.insert(base.into(), vec![entry.to_owned()]);
154                                        map
155                                    });
156                            }
157                            None => {
158                                entry.path = path.clone();
159                                active_components
160                                    .base
161                                    .entry(base.into())
162                                    .and_modify(|e| e.push(entry.to_owned()))
163                                    .or_insert_with(|| vec![entry.to_owned()]);
164                            }
165                        }
166                    }
167                    Err(why) => {
168                        return Err(io::Error::new(
169                            io::ErrorKind::InvalidData,
170                            format!("invalid checksum entry: {}", why),
171                        ))
172                    }
173                }
174            } else {
175                if !active_components.is_empty() {
176                    release
177                        .sums
178                        .insert(active_hash.clone(), active_components.clone());
179                    active_components.clear();
180                }
181
182                active_hash.clear();
183                active_hash.push_str(line.trim());
184                active_hash.pop();
185            }
186        }
187
188        if !active_components.is_empty() {
189            release.sums.insert(active_hash, active_components);
190        }
191
192        Ok(release)
193    }
194}
195
196/// Stores the entries for each component for this checksum method.
197#[derive(Debug, Default, Clone, Hash, PartialEq)]
198pub struct EntryComponents {
199    pub base: BTreeMap<String, Vec<ReleaseEntry>>,
200    pub components: BTreeMap<String, BTreeMap<String, Vec<ReleaseEntry>>>,
201}
202
203impl EntryComponents {
204    pub fn clear(&mut self) {
205        self.base.clear();
206        self.components.clear();
207    }
208
209    pub fn is_empty(&self) -> bool {
210        self.base.is_empty() && self.components.is_empty()
211    }
212}