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