1use crate::{
2 case_map::CaseMap,
3 errors::{APTError, MissingKeyError, ParseError},
4 parse_kv,
5};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, PartialEq)]
9pub struct ReleaseHash {
10 pub filename: String,
11 pub hash: String,
12 pub size: u64,
13}
14
15pub struct Release {
16 pub(crate) map: CaseMap,
17 pub architectures: Vec<String>,
18 pub no_support_for_architecture_all: Option<bool>,
19 pub description: Option<String>,
20 pub origin: Option<String>,
21 pub label: Option<String>,
22 pub suite: Option<String>,
23 pub version: Option<String>,
24 pub codename: Option<String>,
25 pub date: Option<String>,
26 pub valid_until: Option<String>,
27 pub components: Vec<String>,
28 pub md5sum: Option<Vec<ReleaseHash>>,
29 pub sha1sum: Option<Vec<ReleaseHash>>,
30 pub sha256sum: Option<Vec<ReleaseHash>>,
31 pub sha512sum: Option<Vec<ReleaseHash>>,
32 pub not_automatic: Option<bool>,
33 pub but_automatic_upgrades: Option<bool>,
34 pub acquire_by_hash: Option<bool>,
35 pub signed_by: Option<String>,
36 pub packages_require_authorization: Option<bool>,
37}
38
39impl Release {
40 pub fn from(data: &str) -> Result<Release, APTError> {
41 let map = match parse_kv(data) {
42 Ok(map) => map,
43 Err(err) => return Err(APTError::KVError(err)),
44 };
45
46 let architectures = match map.get("Architectures") {
47 Some(architectures) => architectures
48 .split_whitespace()
49 .map(|x| x.to_string())
50 .collect(),
51 None => {
52 return Err(APTError::MissingKeyError(MissingKeyError::new(
53 "Architectures",
54 data,
55 )))
56 }
57 };
58
59 let components = match map.get("Components") {
60 Some(components) => components
61 .split_whitespace()
62 .map(|x| x.to_string())
63 .collect(),
64 None => {
65 return Err(APTError::MissingKeyError(MissingKeyError::new(
66 "Components",
67 data,
68 )))
69 }
70 };
71
72 let description = map.get("Description").map(|x| {
73 if x.ends_with('\n') {
74 x.trim_end_matches('\n').to_string()
75 } else {
76 x.to_string()
77 }
78 });
79
80 const HASHES: [&str; 4] = ["MD5Sum", "SHA1", "SHA256", "SHA512"];
81 let mut hash_map = HashMap::<String, Vec<ReleaseHash>>::new();
82
83 for (key, value) in map.iter() {
84 if !HASHES.contains(&key.as_str()) {
85 continue;
86 }
87
88 let chunks = value.split_whitespace().collect::<Vec<&str>>();
89 let chunks = chunks.chunks(3);
90
91 let mut hashes = Vec::new();
92 for chunk in chunks {
93 let chunk = chunk.to_vec();
94 if chunk.len() != 3 {
95 return Err(APTError::ParseError(ParseError));
96 }
97
98 let size = match chunk[1].parse::<u64>() {
99 Ok(size) => size,
100 Err(_) => return Err(APTError::ParseError(ParseError)),
101 };
102
103 hashes.push(ReleaseHash {
104 filename: chunk[2].to_string(),
105 hash: chunk[0].to_string(),
106 size,
107 });
108 }
109
110 hash_map.insert(key.to_string(), hashes);
111 }
112
113 Ok(Release {
114 map: map.clone(),
115 architectures,
116 no_support_for_architecture_all: map
117 .get("No-Support-for-Architecture-all")
118 .map(|x| x == "yes"),
119 description,
120 origin: map.get("Origin").cloned(),
121 label: map.get("Label").cloned(),
122 suite: map.get("Suite").cloned(),
123 version: map.get("Version").cloned(),
124 codename: map.get("Codename").cloned(),
125 date: map.get("Date").cloned(),
126 valid_until: map.get("Valid-Until").cloned(),
127 components,
128 md5sum: hash_map.get("MD5Sum").cloned(),
129 sha1sum: hash_map.get("SHA1").cloned(),
130 sha256sum: hash_map.get("SHA256").cloned(),
131 sha512sum: hash_map.get("SHA512").cloned(),
132 not_automatic: map.get("NotAutomatic").map(|x| x == "yes"),
133 but_automatic_upgrades: map.get("ButAutomaticUpgrades").map(|x| x == "yes"),
134 acquire_by_hash: map.get("Acquire-By-Hash").map(|x| x == "yes"),
135 signed_by: map.get("Signed-By").cloned(),
136 packages_require_authorization: map
137 .get("Packages-Require-Authorization")
138 .map(|x| x == "yes"),
139 })
140 }
141
142 pub fn get(&self, key: &str) -> Option<&str> {
143 self.map.get(key).map(|x| &**x)
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::{Release, ReleaseHash};
150 use std::fs::read_to_string;
151
152 #[test]
153 fn release_chariz() {
154 let file = "./test/chariz.release";
155 let data = match read_to_string(file) {
156 Ok(data) => data,
157 Err(err) => panic!("Failed to read file: {}", err),
158 };
159
160 let release = match Release::from(&data) {
161 Ok(release) => release,
162 Err(err) => panic!("Failed to parse release: {}", err),
163 };
164
165 assert_eq!(release.architectures, vec!["iphoneos-arm"]);
166 assert_eq!(release.no_support_for_architecture_all, None);
167 assert_eq!(
168 release.description,
169 Some(
170 "Check out what’s new and download purchases from the Chariz marketplace!"
171 .to_owned()
172 )
173 );
174 assert_eq!(release.origin, Some("Chariz".to_owned()));
175 assert_eq!(release.label, Some("Chariz".to_owned()));
176 assert_eq!(release.suite, Some("stable".to_owned()));
177 assert_eq!(release.version, Some("0.9".to_owned()));
178 assert_eq!(release.codename, Some("hbang".to_owned()));
179 assert_eq!(
180 release.date,
181 Some("Thu, 13 Jan 2022 07:15:42 +0000".to_owned())
182 );
183 assert_eq!(release.valid_until, None);
184 assert_eq!(release.components, vec!["main"]);
185
186 assert_eq!(
187 release.md5sum,
188 Some(vec![
189 ReleaseHash {
190 filename: "Packages".to_owned(),
191 hash: "e95ba4e016983b6145b3de3b535bf5e9".to_owned(),
192 size: 368031,
193 },
194 ReleaseHash {
195 filename: "Packages.bz2".to_owned(),
196 hash: "1c1be6a4f557dc99335cc03c2d2aec3c".to_owned(),
197 size: 41023,
198 },
199 ReleaseHash {
200 filename: "Packages.lzma".to_owned(),
201 hash: "eb1e7b1c68981be1fe4eeefb7a95f393".to_owned(),
202 size: 39736,
203 },
204 ReleaseHash {
205 filename: "Packages.xz".to_owned(),
206 hash: "10ad7b7937ab117be9db77b47c74eaf4".to_owned(),
207 size: 39360,
208 },
209 ReleaseHash {
210 filename: "Packages.zst".to_owned(),
211 hash: "627771b17cc4b50b130cbf5b85f22965".to_owned(),
212 size: 42508,
213 }
214 ])
215 );
216
217 assert_eq!(release.sha1sum, None);
218 assert_eq!(release.sha256sum, None);
219
220 assert_eq!(
221 release.sha512sum,
222 Some(vec![
223 ReleaseHash {
224 filename: "Packages".to_owned(),
225 hash: "3b7029624379049caff7181a464841fd823c8ce6a7c41c653fcddaeb3215880c5ef5c33347726a44d76c9fed6e74dd3511f9e53e497fa275db04c907c5c44ed0".to_owned(),
226 size: 368031,
227 },
228 ReleaseHash {
229 filename: "Packages.bz2".to_owned(),
230 hash: "45637f123591db0c8c0483671ec7bbd73c87b8b7c4d03f0968f007a8bf413ed371c965224f2a5652054c0b4605b2766496c7d182a6b81107c032d8daf3eb20d4".to_owned(),
231 size: 41023,
232 },
233 ReleaseHash {
234 filename: "Packages.lzma".to_owned(),
235 hash: "5881f263d9d8dcc99eb8aea1cc95a380d02b1f6b6512b61603f2a63b446b596cd5080f67bbe04f05c6c74c69caebc2d988eedd33d7d616d9aec17253752c4ef8".to_owned(),
236 size: 39736,
237 },
238 ReleaseHash {
239 filename: "Packages.xz".to_owned(),
240 hash: "373d79126d59f28c555f4582d84836b2dd66995f6fd3d4d3c737089c1f9226ae29af5923c4ca59c848451bd7ef1b43e828c7bb96dc448482cbd3aa99a456262b".to_owned(),
241 size: 39360,
242 },
243 ReleaseHash {
244 filename: "Packages.zst".to_owned(),
245 hash: "c858de6a346a1e540f426e9b14ce8680f43dcfe3fa754dc6a0c4f1c4cfb025b82819330176b6847773b38080f370868f387e435c6aeda65ced5eaf242ce4a075".to_owned(),
246 size: 42508,
247 }
248 ])
249 );
250
251 assert_eq!(release.not_automatic, None);
252 assert_eq!(release.but_automatic_upgrades, None);
253 assert_eq!(release.acquire_by_hash, None);
254 assert_eq!(release.signed_by, None);
255 assert_eq!(release.packages_require_authorization, None);
256 }
257
258 #[test]
259 fn release_jammy() {
260 let file = "./test/jammy.release";
261 let data = match read_to_string(file) {
262 Ok(data) => data,
263 Err(err) => panic!("Failed to read file: {}", err),
264 };
265
266 let release = match Release::from(&data) {
267 Ok(release) => release,
268 Err(err) => panic!("Failed to parse release: {}", err),
269 };
270
271 assert_eq!(
272 release.architectures,
273 vec!["amd64", "arm64", "armhf", "i386", "ppc64el", "riscv64", "s390x"]
274 );
275 assert_eq!(release.no_support_for_architecture_all, None);
276 assert_eq!(release.description, Some("Ubuntu Jammy 22.04".to_owned()));
277 assert_eq!(release.origin, Some("Ubuntu".to_owned()));
278 assert_eq!(release.label, Some("Ubuntu".to_owned()));
279 assert_eq!(release.suite, Some("jammy".to_owned()));
280 assert_eq!(release.version, Some("22.04".to_owned()));
281 assert_eq!(release.codename, Some("jammy".to_owned()));
282 assert_eq!(
283 release.date,
284 Some("Sat, 15 Jan 2022 22:01:06 UTC".to_owned())
285 );
286 assert_eq!(release.valid_until, None);
287 assert_eq!(
288 release.components,
289 vec!["main", "restricted", "universe", "multiverse"]
290 );
291
292 assert_eq!(release.not_automatic, None);
294 assert_eq!(release.but_automatic_upgrades, None);
295 assert_eq!(release.acquire_by_hash, Some(true));
296 assert_eq!(release.signed_by, None);
297 assert_eq!(release.packages_require_authorization, None);
298 }
299}