Skip to main content

provenant/parsers/
rpm_license_files.rs

1// SPDX-FileCopyrightText: nexB Inc. and others
2// ScanCode is a trademark of nexB Inc.
3// SPDX-FileCopyrightText: Provenant contributors
4// SPDX-License-Identifier: Apache-2.0
5// Derived from ScanCode Toolkit (Apache-2.0); modified. See NOTICE.
6
7//! Parser for RPM license files in /usr/share/licenses/ directories.
8//!
9//! Identifies packages from their license files installed in the standard
10//! /usr/share/licenses/ location, primarily used in Mariner distroless containers.
11
12use crate::models::{DatasourceId, PackageType};
13use std::path::Path;
14
15use crate::models::PackageData;
16use crate::parsers::PackageParser;
17use crate::parsers::utils::truncate_field;
18
19const PACKAGE_TYPE: PackageType = PackageType::Rpm;
20
21/// Parser for RPM license files in /usr/share/licenses/ directories.
22///
23/// Identifies packages from their license files installed in the standard
24/// /usr/share/licenses/ location, primarily used in Mariner distroless containers.
25///
26/// # Supported Formats
27/// - `/usr/share/licenses/*/COPYING*` - COPYING license files
28/// - `/usr/share/licenses/*/LICENSE*` - LICENSE files
29///
30/// # Key Features
31/// - Extracts package name from directory path
32/// - Supports Mariner distroless container convention
33/// - Package URL generation with mariner namespace
34///
35/// # Implementation Notes
36/// - Package name is extracted from the directory between `licenses/` and the filename
37/// - For example: `/usr/share/licenses/openssl/LICENSE` → package name is "openssl"
38/// - Does NOT perform license detection (that's handled by the license scanner)
39/// - datasource_id: "rpm_package_licenses"
40/// - namespace: "mariner"
41pub struct RpmLicenseFilesParser;
42
43impl PackageParser for RpmLicenseFilesParser {
44    const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
45
46    fn is_match(path: &Path) -> bool {
47        let path_str = path.to_string_lossy();
48
49        // Check if path contains usr/share/licenses/
50        if !path_str.contains("usr/share/licenses/") {
51            return false;
52        }
53
54        // Get the filename
55        if let Some(filename) = path.file_name().and_then(|f| f.to_str()) {
56            // Match files starting with COPYING or LICENSE (case-sensitive)
57            filename.starts_with("COPYING") || filename.starts_with("LICENSE")
58        } else {
59            false
60        }
61    }
62
63    fn extract_packages(path: &Path) -> Vec<PackageData> {
64        // Extract package name from path
65        let path_str = path.to_string_lossy();
66
67        // Split by usr/share/licenses/ and get the next path component
68        let name = if let Some(after_licenses) = path_str.split("usr/share/licenses/").nth(1) {
69            // Get the first path component after licenses/ (the package name)
70            after_licenses
71                .split('/')
72                .next()
73                .map(|s| truncate_field(s.to_string()))
74        } else {
75            None
76        };
77
78        // Build package data
79        let mut pkg = PackageData {
80            package_type: Some(PACKAGE_TYPE),
81            datasource_id: Some(DatasourceId::RpmPackageLicenses),
82            namespace: Some("mariner".to_string()),
83            name: name.clone(),
84            ..Default::default()
85        };
86
87        // Build PURL if we have a name
88        if let Some(ref package_name) = name {
89            use packageurl::PackageUrl;
90            if let Ok(mut purl) = PackageUrl::new(PACKAGE_TYPE.as_str(), package_name)
91                && purl.with_namespace("mariner").is_ok()
92            {
93                pkg.purl = Some(truncate_field(purl.to_string()));
94            }
95        }
96
97        vec![pkg]
98    }
99
100    fn metadata() -> Vec<super::metadata::ParserMetadata> {
101        vec![super::metadata::ParserMetadata {
102            description: "RPM mariner distroless package license files",
103            file_patterns: &[
104                "*usr/share/licenses/*/COPYING*",
105                "*usr/share/licenses/*/LICENSE*",
106            ],
107            package_type: "rpm",
108            primary_language: "",
109            documentation_url: Some("https://github.com/microsoft/marinara/"),
110        }]
111    }
112}