From: "Fabian Grünbichler" <f.gruenbichler@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH proxmox-apt 2/2] deb822: source index support
Date: Tue, 18 Oct 2022 11:20:36 +0200 [thread overview]
Message-ID: <20221018092040.860121-3-f.gruenbichler@proxmox.com> (raw)
In-Reply-To: <20221018092040.860121-1-f.gruenbichler@proxmox.com>
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
the test file needs to be downloaded from the referenced URL and
uncompressed (it's too big to send as patch).
its SHA256sum is e7777c1d305f5e0a31bcf2fe26e955436986edb5c211c03a362c7d557c899349
src/deb822/mod.rs | 3 +
src/deb822/release_file.rs | 2 +-
src/deb822/sources_file.rs | 255 +
..._debian_dists_bullseye_main_source_Sources | 858657 +++++++++++++++
4 files changed, 858916 insertions(+), 1 deletion(-)
create mode 100644 src/deb822/sources_file.rs
create mode 100644 tests/deb822/sources/deb.debian.org_debian_dists_bullseye_main_source_Sources
diff --git a/src/deb822/mod.rs b/src/deb822/mod.rs
index 7a1bb0e..59e7c21 100644
--- a/src/deb822/mod.rs
+++ b/src/deb822/mod.rs
@@ -5,6 +5,9 @@ pub use release_file::{CompressionType, FileReference, FileReferenceType, Releas
mod packages_file;
pub use packages_file::PackagesFile;
+mod sources_file;
+pub use sources_file::SourcesFile;
+
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct CheckSums {
pub md5: Option<[u8; 16]>,
diff --git a/src/deb822/release_file.rs b/src/deb822/release_file.rs
index c50c095..85d3436 100644
--- a/src/deb822/release_file.rs
+++ b/src/deb822/release_file.rs
@@ -245,7 +245,7 @@ impl FileReferenceType {
}
pub fn is_package_index(&self) -> bool {
- matches!(self, FileReferenceType::Packages(_, _))
+ matches!(self, FileReferenceType::Packages(_, _) | FileReferenceType::Sources(_))
}
}
diff --git a/src/deb822/sources_file.rs b/src/deb822/sources_file.rs
new file mode 100644
index 0000000..a13d84f
--- /dev/null
+++ b/src/deb822/sources_file.rs
@@ -0,0 +1,255 @@
+use std::collections::HashMap;
+
+use anyhow::{bail, Error, format_err};
+use rfc822_like::de::Deserializer;
+use serde::Deserialize;
+use serde_json::Value;
+
+use super::CheckSums;
+//Uploaders
+//
+//Homepage
+//
+//Version Control System (VCS) fields
+//
+//Testsuite
+//
+//Dgit
+//
+//Standards-Version (mandatory)
+//
+//Build-Depends et al
+//
+//Package-List (recommended)
+//
+//Checksums-Sha1 and Checksums-Sha256 (mandatory)
+//
+//Files (mandatory)
+
+
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+pub struct SourcesFileRaw {
+ pub format: String,
+ pub package: String,
+ pub binary: Option<Vec<String>>,
+ pub version: String,
+ pub section: Option<String>,
+ pub priority: Option<String>,
+ pub maintainer: String,
+ pub uploaders: Option<String>,
+ pub architecture: Option<String>,
+ pub directory: String,
+ pub files: String,
+ #[serde(rename = "Checksums-Sha256")]
+ pub sha256: Option<String>,
+ #[serde(rename = "Checksums-Sha512")]
+ pub sha512: Option<String>,
+ #[serde(flatten)]
+ pub extra_fields: HashMap<String, Value>,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct SourcePackageEntry {
+ pub format: String,
+ pub package: String,
+ pub binary: Option<Vec<String>>,
+ pub version: String,
+ pub architecture: Option<String>,
+ pub section: Option<String>,
+ pub priority: Option<String>,
+ pub maintainer: String,
+ pub uploaders: Option<String>,
+ pub directory: String,
+ pub files: HashMap<String, SourcePackageFileReference>,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct SourcePackageFileReference {
+ pub file: String,
+ pub size: usize,
+ pub checksums: CheckSums,
+}
+
+impl SourcePackageEntry {
+ pub fn size(&self) -> usize {
+ self.files.values().map(|f| f.size).sum()
+ }
+}
+
+#[derive(Debug, Default, PartialEq, Eq)]
+/// A parsed representation of a Release file
+pub struct SourcesFile {
+ pub source_packages: Vec<SourcePackageEntry>,
+}
+
+impl TryFrom<SourcesFileRaw> for SourcePackageEntry {
+ type Error = Error;
+
+ fn try_from(value: SourcesFileRaw) -> Result<Self, Self::Error> {
+ let mut parsed = SourcePackageEntry {
+ package: value.package,
+ binary: value.binary,
+ version: value.version,
+ architecture: value.architecture,
+ files: HashMap::new(),
+ format: value.format,
+ section: value.section,
+ priority: value.priority,
+ maintainer: value.maintainer,
+ uploaders: value.uploaders,
+ directory: value.directory,
+ };
+
+ for file_reference in value.files.lines() {
+ let (file_name, size, md5) = parse_file_reference(file_reference, 16)?;
+ let entry = parsed.files.entry(file_name.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()});
+ entry.checksums.md5 = Some(md5.try_into().map_err(|_|format_err!("unexpected checksum length"))?);
+ if entry.size != size {
+ bail!("Size mismatch: {} != {}", entry.size, size);
+ }
+ }
+
+ if let Some(sha256) = value.sha256 {
+ for line in sha256.lines() {
+ let (file_name, size, sha256) = parse_file_reference(line, 32)?;
+ let entry = parsed.files.entry(file_name.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()});
+ entry.checksums.sha256 = Some(sha256.try_into().map_err(|_|format_err!("unexpected checksum length"))?);
+ if entry.size != size {
+ bail!("Size mismatch: {} != {}", entry.size, size);
+ }
+ }
+ };
+
+ if let Some(sha512) = value.sha512 {
+ for line in sha512.lines() {
+ let (file_name, size, sha512) = parse_file_reference(line, 64)?;
+ let entry = parsed.files.entry(file_name.clone()).or_insert_with(|| SourcePackageFileReference { file: file_name, size, checksums: CheckSums::default()});
+ entry.checksums.sha512 = Some(sha512.try_into().map_err(|_|format_err!("unexpected checksum length"))?);
+ if entry.size != size {
+ bail!("Size mismatch: {} != {}", entry.size, size);
+ }
+ }
+ };
+
+ for (file_name, reference) in &parsed.files {
+ if !reference.checksums.is_secure() {
+ bail!(
+ "no strong checksum found for source entry '{}'",
+ file_name
+ );
+ }
+ }
+
+ Ok(parsed)
+ }
+}
+
+impl TryFrom<String> for SourcesFile {
+ type Error = Error;
+
+ fn try_from(value: String) -> Result<Self, Self::Error> {
+ value.as_bytes().try_into()
+ }
+}
+
+impl TryFrom<&[u8]> for SourcesFile {
+ type Error = Error;
+
+ fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
+ let deserialized = <Vec<SourcesFileRaw>>::deserialize(Deserializer::new(value))?;
+ deserialized.try_into()
+ }
+}
+
+impl TryFrom<Vec<SourcesFileRaw>> for SourcesFile {
+ type Error = Error;
+
+ fn try_from(value: Vec<SourcesFileRaw>) -> Result<Self, Self::Error> {
+ let mut source_packages = Vec::with_capacity(value.len());
+ for entry in value {
+ let entry: SourcePackageEntry = entry.try_into()?;
+ source_packages.push(entry);
+ }
+
+ Ok(Self { source_packages })
+ }
+}
+
+fn parse_file_reference(
+ line: &str,
+ csum_len: usize,
+) -> Result<(String, usize, Vec<u8>), Error> {
+ let mut split = line.split_ascii_whitespace();
+
+ let checksum = split
+ .next()
+ .ok_or_else(|| format_err!("Missing 'checksum' field."))?;
+ if checksum.len() > csum_len * 2 {
+ bail!(
+ "invalid checksum length: '{}', expected {} bytes",
+ checksum,
+ csum_len
+ );
+ }
+
+ let checksum = hex::decode(checksum)?;
+
+ let size = split
+ .next()
+ .ok_or_else(|| format_err!("Missing 'size' field."))?
+ .parse::<usize>()?;
+
+ let file = split
+ .next()
+ .ok_or_else(|| format_err!("Missing 'file name' field."))?
+ .to_string();
+
+ Ok((file, size, checksum))
+}
+
+#[test]
+pub fn test_deb_packages_file() {
+ let input = include_str!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/tests/deb822/sources/deb.debian.org_debian_dists_bullseye_main_source_Sources"
+ ));
+
+ let deserialized =
+ <Vec<SourcesFileRaw>>::deserialize(Deserializer::new(input.as_bytes())).unwrap();
+ assert_eq!(deserialized.len(), 30953);
+
+ let parsed: SourcesFile = deserialized.try_into().unwrap();
+
+ assert_eq!(parsed.source_packages.len(), 30953);
+
+ let found = parsed.source_packages.iter().find(|source| source.package == "base-files").expect("test file contains 'base-files' entry");
+ assert_eq!(found.package, "base-files");
+ assert_eq!(found.format, "3.0 (native)");
+ assert_eq!(found.architecture.as_deref(), Some("any"));
+ assert_eq!(found.directory, "pool/main/b/base-files");
+ assert_eq!(found.section.as_deref(), Some("admin"));
+ assert_eq!(found.version, "11.1+deb11u5");
+
+ let binary_packages = found.binary.as_ref().expect("base-files source package builds base-files binary package");
+ assert_eq!(binary_packages.len(), 1);
+ assert_eq!(binary_packages[0], "base-files");
+
+ let references = &found.files;
+ assert_eq!(references.len(), 2);
+
+ let dsc_file = "base-files_11.1+deb11u5.dsc";
+ let dsc = references.get(dsc_file).expect("base-files source package contains 'dsc' reference");
+ assert_eq!(dsc.file, dsc_file);
+ assert_eq!(dsc.size, 1110);
+ assert_eq!(dsc.checksums.md5.expect("dsc has md5 checksum"), hex::decode("741c34ac0151262a03de8d5a07bc4271").unwrap()[..]);
+ assert_eq!(dsc.checksums.sha256.expect("dsc has sha256 checksum"), hex::decode("c41a7f00d57759f27e6068240d1ea7ad80a9a752e4fb43850f7e86e967422bd3").unwrap()[..]);
+
+ let tar_file = "base-files_11.1+deb11u5.tar.xz";
+ let tar = references.get(tar_file).expect("base-files source package contains 'tar' reference");
+ assert_eq!(tar.file, tar_file);
+ assert_eq!(tar.size, 65612);
+ assert_eq!(tar.checksums.md5.expect("tar has md5 checksum"), hex::decode("995df33642118b566a4026410e1c6aac").unwrap()[..]);
+ assert_eq!(tar.checksums.sha256.expect("tar has sha256 checksum"), hex::decode("31c9e5745845a73f3d5c8a7868c379d77aaca42b81194679d7ab40cc28e3a0e9").unwrap()[..]);
+}
\ No newline at end of file
diff --git a/tests/deb822/sources/deb.debian.org_debian_dists_bullseye_main_source_Sources b/tests/deb822/sources/deb.debian.org_debian_dists_bullseye_main_source_Sources
new file mode 100644
index 0000000..2b8e387
--- /dev/null
+++ b/tests/deb822/sources/deb.debian.org_debian_dists_bullseye_main_source_Sources
@@ -0,0 +1,1 @@
+DOWNLOAD-ME-FROM: http://snapshot.debian.org/archive/debian/20221017T212657Z/dists/bullseye/main/source/Sources.xz
--
2.30.2
next prev parent reply other threads:[~2022-10-18 9:21 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-10-18 9:20 [pve-devel] [PATCH-SERIES 0/6] proxmox-offline-mirror filtering & deb-src support Fabian Grünbichler
2022-10-18 9:20 ` [pve-devel] [PATCH proxmox-apt 1/2] packages file: add section field Fabian Grünbichler
2022-10-18 9:20 ` Fabian Grünbichler [this message]
2022-10-18 9:20 ` [pve-devel] [PATCH proxmox-offline-mirror 1/4] mirror: add exclusion of packages/sections Fabian Grünbichler
2022-10-18 9:20 ` [pve-devel] [PATCH proxmox-offline-mirror 2/4] mirror: implement source packages mirroring Fabian Grünbichler
2022-10-18 9:20 ` [pve-devel] [PATCH proxmox-offline-mirror 3/4] fix #4264: only require either Release or InRelease Fabian Grünbichler
2022-10-18 9:20 ` [pve-devel] [PATCH proxmox-offline-mirror 4/4] mirror: refactor fetch_binary/source_packages Fabian Grünbichler
2022-10-20 12:49 ` [pve-devel] applied-series: [PATCH-SERIES 0/6] proxmox-offline-mirror filtering & deb-src support Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20221018092040.860121-3-f.gruenbichler@proxmox.com \
--to=f.gruenbichler@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.