1 use std
::collections
::HashMap
;
3 use anyhow
::{bail, Error, format_err}
;
4 use rfc822_like
::de
::Deserializer
;
5 use serde
::Deserialize
;
13 //Version Control System (VCS) fields
19 //Standards-Version (mandatory)
23 //Package-List (recommended)
25 //Checksums-Sha1 and Checksums-Sha256 (mandatory)
31 #[derive(Debug, Deserialize)]
32 #[serde(rename_all = "PascalCase")]
33 pub struct SourcesFileRaw
{
36 pub binary
: Option
<Vec
<String
>>,
38 pub section
: Option
<String
>,
39 pub priority
: Option
<String
>,
40 pub maintainer
: String
,
41 pub uploaders
: Option
<String
>,
42 pub architecture
: Option
<String
>,
43 pub directory
: String
,
45 #[serde(rename = "Checksums-Sha256")]
46 pub sha256
: Option
<String
>,
47 #[serde(rename = "Checksums-Sha512")]
48 pub sha512
: Option
<String
>,
50 pub extra_fields
: HashMap
<String
, Value
>,
53 #[derive(Debug, PartialEq, Eq)]
54 pub struct SourcePackageEntry
{
57 pub binary
: Option
<Vec
<String
>>,
59 pub architecture
: Option
<String
>,
60 pub section
: Option
<String
>,
61 pub priority
: Option
<String
>,
62 pub maintainer
: String
,
63 pub uploaders
: Option
<String
>,
64 pub directory
: String
,
65 pub files
: HashMap
<String
, SourcePackageFileReference
>,
68 #[derive(Debug, PartialEq, Eq)]
69 pub struct SourcePackageFileReference
{
72 pub checksums
: CheckSums
,
75 impl SourcePackageEntry
{
76 pub fn size(&self) -> usize {
77 self.files
.values().map(|f
| f
.size
).sum()
81 #[derive(Debug, Default, PartialEq, Eq)]
82 /// A parsed representation of a Release file
83 pub struct SourcesFile
{
84 pub source_packages
: Vec
<SourcePackageEntry
>,
87 impl TryFrom
<SourcesFileRaw
> for SourcePackageEntry
{
90 fn try_from(value
: SourcesFileRaw
) -> Result
<Self, Self::Error
> {
91 let mut parsed
= SourcePackageEntry
{
92 package
: value
.package
,
94 version
: value
.version
,
95 architecture
: value
.architecture
,
96 files
: HashMap
::new(),
98 section
: value
.section
,
99 priority
: value
.priority
,
100 maintainer
: value
.maintainer
,
101 uploaders
: value
.uploaders
,
102 directory
: value
.directory
,
105 for file_reference
in value
.files
.lines() {
106 let (file_name
, size
, md5
) = parse_file_reference(file_reference
, 16)?
;
107 let entry
= parsed
.files
.entry(file_name
.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()}
);
108 entry
.checksums
.md5
= Some(md5
.try_into().map_err(|_
|format_err
!("unexpected checksum length"))?
);
109 if entry
.size
!= size
{
110 bail
!("Size mismatch: {} != {}", entry
.size
, size
);
114 if let Some(sha256
) = value
.sha256
{
115 for line
in sha256
.lines() {
116 let (file_name
, size
, sha256
) = parse_file_reference(line
, 32)?
;
117 let entry
= parsed
.files
.entry(file_name
.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()}
);
118 entry
.checksums
.sha256
= Some(sha256
.try_into().map_err(|_
|format_err
!("unexpected checksum length"))?
);
119 if entry
.size
!= size
{
120 bail
!("Size mismatch: {} != {}", entry
.size
, size
);
125 if let Some(sha512
) = value
.sha512
{
126 for line
in sha512
.lines() {
127 let (file_name
, size
, sha512
) = parse_file_reference(line
, 64)?
;
128 let entry
= parsed
.files
.entry(file_name
.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()}
);
129 entry
.checksums
.sha512
= Some(sha512
.try_into().map_err(|_
|format_err
!("unexpected checksum length"))?
);
130 if entry
.size
!= size
{
131 bail
!("Size mismatch: {} != {}", entry
.size
, size
);
136 for (file_name
, reference
) in &parsed
.files
{
137 if !reference
.checksums
.is_secure() {
139 "no strong checksum found for source entry '{}'",
149 impl TryFrom
<String
> for SourcesFile
{
152 fn try_from(value
: String
) -> Result
<Self, Self::Error
> {
153 value
.as_bytes().try_into()
157 impl TryFrom
<&[u8]> for SourcesFile
{
160 fn try_from(value
: &[u8]) -> Result
<Self, Self::Error
> {
161 let deserialized
= <Vec
<SourcesFileRaw
>>::deserialize(Deserializer
::new(value
))?
;
162 deserialized
.try_into()
166 impl TryFrom
<Vec
<SourcesFileRaw
>> for SourcesFile
{
169 fn try_from(value
: Vec
<SourcesFileRaw
>) -> Result
<Self, Self::Error
> {
170 let mut source_packages
= Vec
::with_capacity(value
.len());
172 let entry
: SourcePackageEntry
= entry
.try_into()?
;
173 source_packages
.push(entry
);
176 Ok(Self { source_packages }
)
180 fn parse_file_reference(
183 ) -> Result
<(String
, usize, Vec
<u8>), Error
> {
184 let mut split
= line
.split_ascii_whitespace();
188 .ok_or_else(|| format_err
!("Missing 'checksum' field."))?
;
189 if checksum
.len() > csum_len
* 2 {
191 "invalid checksum length: '{}', expected {} bytes",
197 let checksum
= hex
::decode(checksum
)?
;
201 .ok_or_else(|| format_err
!("Missing 'size' field."))?
206 .ok_or_else(|| format_err
!("Missing 'file name' field."))?
209 Ok((file
, size
, checksum
))
213 pub fn test_deb_packages_file() {
214 // NOTE: test is over an excerpt from packages starting with 0-9, a, b & z using:
215 // http://snapshot.debian.org/archive/debian/20221017T212657Z/dists/bullseye/main/source/Sources.xz
216 let input
= include_str
!(concat
!(
217 env
!("CARGO_MANIFEST_DIR"),
218 "/tests/deb822/sources/deb.debian.org_debian_dists_bullseye_main_source_Sources"
222 <Vec
<SourcesFileRaw
>>::deserialize(Deserializer
::new(input
.as_bytes())).unwrap();
223 assert_eq
!(deserialized
.len(), 1558);
225 let parsed
: SourcesFile
= deserialized
.try_into().unwrap();
227 assert_eq
!(parsed
.source_packages
.len(), 1558);
229 let found
= parsed
.source_packages
.iter().find(|source
| source
.package
== "base-files").expect("test file contains 'base-files' entry");
230 assert_eq
!(found
.package
, "base-files");
231 assert_eq
!(found
.format
, "3.0 (native)");
232 assert_eq
!(found
.architecture
.as_deref(), Some("any"));
233 assert_eq
!(found
.directory
, "pool/main/b/base-files");
234 assert_eq
!(found
.section
.as_deref(), Some("admin"));
235 assert_eq
!(found
.version
, "11.1+deb11u5");
237 let binary_packages
= found
.binary
.as_ref().expect("base-files source package builds base-files binary package");
238 assert_eq
!(binary_packages
.len(), 1);
239 assert_eq
!(binary_packages
[0], "base-files");
241 let references
= &found
.files
;
242 assert_eq
!(references
.len(), 2);
244 let dsc_file
= "base-files_11.1+deb11u5.dsc";
245 let dsc
= references
.get(dsc_file
).expect("base-files source package contains 'dsc' reference");
246 assert_eq
!(dsc
.file
, dsc_file
);
247 assert_eq
!(dsc
.size
, 1110);
248 assert_eq
!(dsc
.checksums
.md5
.expect("dsc has md5 checksum"), hex
::decode("741c34ac0151262a03de8d5a07bc4271").unwrap()[..]);
249 assert_eq
!(dsc
.checksums
.sha256
.expect("dsc has sha256 checksum"), hex
::decode("c41a7f00d57759f27e6068240d1ea7ad80a9a752e4fb43850f7e86e967422bd3").unwrap()[..]);
251 let tar_file
= "base-files_11.1+deb11u5.tar.xz";
252 let tar
= references
.get(tar_file
).expect("base-files source package contains 'tar' reference");
253 assert_eq
!(tar
.file
, tar_file
);
254 assert_eq
!(tar
.size
, 65612);
255 assert_eq
!(tar
.checksums
.md5
.expect("tar has md5 checksum"), hex
::decode("995df33642118b566a4026410e1c6aac").unwrap()[..]);
256 assert_eq
!(tar
.checksums
.sha256
.expect("tar has sha256 checksum"), hex
::decode("31c9e5745845a73f3d5c8a7868c379d77aaca42b81194679d7ab40cc28e3a0e9").unwrap()[..]);