Skip to main content

provenant/parsers/debian/
tarball.rs

1// SPDX-FileCopyrightText: Provenant contributors
2// SPDX-License-Identifier: Apache-2.0
3
4use std::path::Path;
5
6use crate::models::{DatasourceId, PackageData, PackageType};
7use crate::parsers::utils::truncate_field;
8
9use super::utils::build_debian_purl;
10use super::{PACKAGE_TYPE, default_package_data};
11use crate::parsers::PackageParser;
12
13/// Parser for Debian original source tarballs (*.orig.tar.*)
14pub struct DebianOrigTarParser;
15
16impl PackageParser for DebianOrigTarParser {
17    const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
18
19    fn is_match(path: &Path) -> bool {
20        path.file_name()
21            .and_then(|n| n.to_str())
22            .map(|name| name.contains(".orig.tar."))
23            .unwrap_or(false)
24    }
25
26    fn extract_packages(path: &Path) -> Vec<PackageData> {
27        let filename = match path.file_name().and_then(|n| n.to_str()) {
28            Some(f) => f,
29            None => {
30                return vec![default_package_data(
31                    DatasourceId::DebianOriginalSourceTarball,
32                )];
33            }
34        };
35
36        vec![parse_source_tarball_filename(
37            filename,
38            DatasourceId::DebianOriginalSourceTarball,
39        )]
40    }
41}
42
43crate::register_parser!(
44    "Debian original source tarball",
45    &["**/*.orig.tar.*"],
46    "deb",
47    "",
48    Some("https://www.debian.org/doc/debian-policy/ch-source.html"),
49);
50
51/// Parser for Debian source package metadata tarballs (*.debian.tar.*)
52pub struct DebianDebianTarParser;
53
54impl PackageParser for DebianDebianTarParser {
55    const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
56
57    fn is_match(path: &Path) -> bool {
58        path.file_name()
59            .and_then(|n| n.to_str())
60            .map(|name| name.contains(".debian.tar."))
61            .unwrap_or(false)
62    }
63
64    fn extract_packages(path: &Path) -> Vec<PackageData> {
65        let filename = match path.file_name().and_then(|n| n.to_str()) {
66            Some(f) => f,
67            None => {
68                return vec![default_package_data(
69                    DatasourceId::DebianSourceMetadataTarball,
70                )];
71            }
72        };
73
74        vec![parse_source_tarball_filename(
75            filename,
76            DatasourceId::DebianSourceMetadataTarball,
77        )]
78    }
79}
80
81crate::register_parser!(
82    "Debian source metadata tarball",
83    &["**/*.debian.tar.*"],
84    "deb",
85    "",
86    Some("https://www.debian.org/doc/debian-policy/ch-source.html"),
87);
88
89fn parse_source_tarball_filename(filename: &str, datasource_id: DatasourceId) -> PackageData {
90    let without_tar_ext = filename
91        .trim_end_matches(".gz")
92        .trim_end_matches(".xz")
93        .trim_end_matches(".bz2")
94        .trim_end_matches(".tar");
95
96    let parts: Vec<&str> = without_tar_ext.splitn(2, '_').collect();
97    if parts.len() < 2 {
98        return default_package_data(datasource_id);
99    }
100
101    let name = truncate_field(parts[0].to_string());
102    let version_with_suffix = parts[1];
103
104    let version = version_with_suffix
105        .trim_end_matches(".orig")
106        .trim_end_matches(".debian")
107        .to_string();
108    let version = truncate_field(version);
109
110    let namespace = Some("debian".to_string());
111
112    PackageData {
113        datasource_id: Some(datasource_id),
114        package_type: Some(PACKAGE_TYPE),
115        namespace: namespace.clone(),
116        name: Some(name.clone()),
117        version: Some(version.clone()),
118        purl: build_debian_purl(&name, Some(&version), namespace.as_deref(), None),
119        ..Default::default()
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use crate::models::DatasourceId;
127    use std::path::PathBuf;
128
129    #[test]
130    fn test_orig_tar_parser_is_match() {
131        assert!(DebianOrigTarParser::is_match(&PathBuf::from(
132            "package_1.0.orig.tar.gz"
133        )));
134        assert!(DebianOrigTarParser::is_match(&PathBuf::from(
135            "abseil_0~20200923.3.orig.tar.xz"
136        )));
137        assert!(!DebianOrigTarParser::is_match(&PathBuf::from(
138            "package.debian.tar.gz"
139        )));
140        assert!(!DebianOrigTarParser::is_match(&PathBuf::from("control")));
141    }
142
143    #[test]
144    fn test_debian_tar_parser_is_match() {
145        assert!(DebianDebianTarParser::is_match(&PathBuf::from(
146            "package_1.0-1.debian.tar.xz"
147        )));
148        assert!(DebianDebianTarParser::is_match(&PathBuf::from(
149            "abseil_20220623.1-1.debian.tar.gz"
150        )));
151        assert!(!DebianDebianTarParser::is_match(&PathBuf::from(
152            "package.orig.tar.gz"
153        )));
154        assert!(!DebianDebianTarParser::is_match(&PathBuf::from("control")));
155    }
156
157    #[test]
158    fn test_parse_orig_tar_filename() {
159        let pkg = parse_source_tarball_filename(
160            "abseil_0~20200923.3.orig.tar.gz",
161            DatasourceId::DebianOriginalSourceTarball,
162        );
163        assert_eq!(pkg.name, Some("abseil".to_string()));
164        assert_eq!(pkg.version, Some("0~20200923.3".to_string()));
165        assert_eq!(pkg.namespace, Some("debian".to_string()));
166        assert_eq!(
167            pkg.purl,
168            Some("pkg:deb/debian/abseil@0~20200923.3".to_string())
169        );
170        assert_eq!(
171            pkg.datasource_id,
172            Some(DatasourceId::DebianOriginalSourceTarball)
173        );
174    }
175
176    #[test]
177    fn test_parse_debian_tar_filename() {
178        let pkg = parse_source_tarball_filename(
179            "abseil_20220623.1-1.debian.tar.xz",
180            DatasourceId::DebianSourceMetadataTarball,
181        );
182        assert_eq!(pkg.name, Some("abseil".to_string()));
183        assert_eq!(pkg.version, Some("20220623.1-1".to_string()));
184        assert_eq!(pkg.namespace, Some("debian".to_string()));
185        assert_eq!(
186            pkg.purl,
187            Some("pkg:deb/debian/abseil@20220623.1-1".to_string())
188        );
189    }
190
191    #[test]
192    fn test_parse_source_tarball_various_compressions() {
193        let pkg_gz = parse_source_tarball_filename(
194            "test_1.0.orig.tar.gz",
195            DatasourceId::DebianOriginalSourceTarball,
196        );
197        let pkg_xz = parse_source_tarball_filename(
198            "test_1.0.orig.tar.xz",
199            DatasourceId::DebianOriginalSourceTarball,
200        );
201        let pkg_bz2 = parse_source_tarball_filename(
202            "test_1.0.orig.tar.bz2",
203            DatasourceId::DebianOriginalSourceTarball,
204        );
205
206        assert_eq!(pkg_gz.version, Some("1.0".to_string()));
207        assert_eq!(pkg_xz.version, Some("1.0".to_string()));
208        assert_eq!(pkg_bz2.version, Some("1.0".to_string()));
209    }
210
211    #[test]
212    fn test_parse_source_tarball_invalid_format() {
213        let pkg = parse_source_tarball_filename(
214            "invalid-no-underscore.tar.gz",
215            DatasourceId::DebianOriginalSourceTarball,
216        );
217        assert!(pkg.name.is_none());
218        assert!(pkg.version.is_none());
219    }
220}