Skip to main content

provenant/parsers/
misc.rs

1// SPDX-FileCopyrightText: nexB Inc. and others
2// SPDX-FileCopyrightText: Provenant contributors
3// SPDX-License-Identifier: Apache-2.0
4// Derived from ScanCode Toolkit (Apache-2.0); modified. See NOTICE.
5
6//! File type recognizers for various package archives and binary formats.
7//!
8//! This module contains simple file-type recognizers that identify packages by
9//! their file extensions or path patterns. These recognizers do NOT parse file
10//! contents - they only tag files with the appropriate package_type and datasource_id.
11//!
12//! # Implementation Notes
13//!
14//! - All recognizers use the `file_recognizer!` macro to reduce boilerplate
15//! - Recognizers return minimal PackageData with only package_type and datasource_id set
16//! - These correspond to Python's misc.py NonAssemblableDatafileHandler classes
17//! - No actual parsing is performed (Python also has `# TODO: parse me!!!`)
18//! - Some recognizers use magic byte detection for disambiguation (Squashfs, NSIS, InstallShield)
19
20use std::path::Path;
21
22use super::PackageParser;
23use crate::models::{DatasourceId, PackageData, PackageType};
24use crate::utils::magic;
25
26/// Helper macro to define file-type recognizers with minimal boilerplate.
27///
28/// Each recognizer matches specific file patterns and returns a minimal
29/// PackageData structure with only package_type and datasource_id populated.
30///
31/// # Arguments
32///
33/// * `$name` - Struct name for the recognizer
34/// * `$pkg_type` - Package type string (e.g., "jar", "war", "meteor")
35/// * `$datasource` - Datasource ID string (e.g., "java_jar", "meteor_package")
36/// * `$match_fn` - Closure that takes a &Path and returns bool for matching
37macro_rules! file_recognizer {
38    ($name:ident, $pkg_type:expr, $datasource:expr, $match_fn:expr) => {
39        pub struct $name;
40
41        impl PackageParser for $name {
42            const PACKAGE_TYPE: PackageType = $pkg_type;
43
44            fn is_match(path: &Path) -> bool {
45                ($match_fn)(path)
46            }
47
48            fn extract_packages(path: &Path) -> Vec<PackageData> {
49                let _ = path;
50                vec![PackageData {
51                    package_type: Some($pkg_type),
52                    datasource_id: Some($datasource),
53                    ..Default::default()
54                }]
55            }
56        }
57    };
58}
59
60// Java Archives
61
62file_recognizer!(
63    JavaJarRecognizer,
64    PackageType::Jar,
65    DatasourceId::JavaJar,
66    |path: &Path| path.extension().and_then(|e| e.to_str()) == Some("jar")
67);
68
69file_recognizer!(
70    IvyXmlRecognizer,
71    PackageType::Ivy,
72    DatasourceId::AntIvyXml,
73    |path: &Path| path.to_str().is_some_and(|p| p.ends_with("/ivy.xml"))
74);
75
76file_recognizer!(
77    JavaWarRecognizer,
78    PackageType::War,
79    DatasourceId::JavaWarArchive,
80    |path: &Path| path.extension().and_then(|e| e.to_str()) == Some("war")
81);
82
83file_recognizer!(
84    JavaWarWebXmlRecognizer,
85    PackageType::War,
86    DatasourceId::JavaWarWebXml,
87    |path: &Path| path
88        .to_str()
89        .is_some_and(|p| p.ends_with("/WEB-INF/web.xml") || p.ends_with("WEB-INF/web.xml"))
90);
91
92file_recognizer!(
93    JavaEarRecognizer,
94    PackageType::Ear,
95    DatasourceId::JavaEarArchive,
96    |path: &Path| path.extension().and_then(|e| e.to_str()) == Some("ear")
97);
98
99file_recognizer!(
100    JavaEarAppXmlRecognizer,
101    PackageType::Ear,
102    DatasourceId::JavaEarApplicationXml,
103    |path: &Path| path.to_str().is_some_and(
104        |p| p.ends_with("/META-INF/application.xml") || p.ends_with("META-INF/application.xml")
105    )
106);
107
108// Apache Axis2
109
110file_recognizer!(
111    Axis2ModuleXmlRecognizer,
112    PackageType::Axis2,
113    DatasourceId::Axis2ModuleXml,
114    |path: &Path| {
115        path.to_str().is_some_and(|p| {
116            let lower = p.to_lowercase();
117            lower.ends_with("/meta-inf/module.xml") || lower.ends_with("meta-inf/module.xml")
118        })
119    }
120);
121
122file_recognizer!(
123    Axis2MarRecognizer,
124    PackageType::Axis2,
125    DatasourceId::Axis2Mar,
126    |path: &Path| path.extension().and_then(|e| e.to_str()) == Some("mar")
127);
128
129// JBoss
130
131file_recognizer!(
132    JBossSarRecognizer,
133    PackageType::JbossService,
134    DatasourceId::JbossSar,
135    |path: &Path| path.extension().and_then(|e| e.to_str()) == Some("sar")
136);
137
138file_recognizer!(
139    JBossServiceXmlRecognizer,
140    PackageType::JbossService,
141    DatasourceId::JbossServiceXml,
142    |path: &Path| {
143        path.to_str().is_some_and(|p| {
144            let lower = p.to_lowercase();
145            lower.ends_with("/meta-inf/jboss-service.xml")
146                || lower.ends_with("meta-inf/jboss-service.xml")
147        })
148    }
149);
150
151// Meteor
152
153file_recognizer!(
154    MeteorPackageRecognizer,
155    PackageType::Meteor,
156    DatasourceId::MeteorPackage,
157    |path: &Path| path.to_str().is_some_and(|p| p.ends_with("/package.js"))
158);
159
160// Mobile Apps
161
162file_recognizer!(
163    AndroidLibraryRecognizer,
164    PackageType::AndroidLib,
165    DatasourceId::AndroidAarLibrary,
166    |path: &Path| path.extension().and_then(|e| e.to_str()) == Some("aar")
167);
168
169file_recognizer!(
170    MozillaXpiRecognizer,
171    PackageType::Mozilla,
172    DatasourceId::MozillaXpi,
173    |path: &Path| path.extension().and_then(|e| e.to_str()) == Some("xpi")
174);
175
176file_recognizer!(
177    ChromeCrxRecognizer,
178    PackageType::Chrome,
179    DatasourceId::ChromeCrx,
180    |path: &Path| path.extension().and_then(|e| e.to_str()) == Some("crx")
181);
182
183file_recognizer!(
184    IosIpaRecognizer,
185    PackageType::Ios,
186    DatasourceId::IosIpa,
187    |path: &Path| path.extension().and_then(|e| e.to_str()) == Some("ipa")
188);
189
190// Archives
191
192file_recognizer!(
193    CabArchiveRecognizer,
194    PackageType::Cab,
195    DatasourceId::MicrosoftCabinet,
196    |path: &Path| path.extension().and_then(|e| e.to_str()) == Some("cab")
197);
198
199file_recognizer!(
200    SharArchiveRecognizer,
201    PackageType::Shar,
202    DatasourceId::SharShellArchive,
203    |path: &Path| path.extension().and_then(|e| e.to_str()) == Some("shar")
204);
205
206// Disk Images
207
208file_recognizer!(
209    AppleDmgRecognizer,
210    PackageType::Dmg,
211    DatasourceId::AppleDmg,
212    |path: &Path| {
213        path.extension()
214            .and_then(|e| e.to_str())
215            .is_some_and(|ext| ext == "dmg" || ext == "sparseimage")
216    }
217);
218
219file_recognizer!(
220    IsoImageRecognizer,
221    PackageType::Iso,
222    DatasourceId::IsoDiskImage,
223    |path: &Path| {
224        path.extension()
225            .and_then(|e| e.to_str())
226            .is_some_and(|ext| ext == "iso" || ext == "udf" || ext == "img")
227    }
228);
229
230// Installers and Binary Formats (require magic byte detection)
231
232file_recognizer!(
233    SquashfsRecognizer,
234    PackageType::Squashfs,
235    DatasourceId::SquashfsDiskImage,
236    |path: &Path| magic::is_squashfs(path)
237);
238
239file_recognizer!(
240    NsisRecognizer,
241    PackageType::Nsis,
242    DatasourceId::NsisInstaller,
243    |path: &Path| {
244        path.extension()
245            .and_then(|e| e.to_str())
246            .is_some_and(|ext| ext == "exe")
247            && magic::is_nsis_installer(path)
248    }
249);
250
251file_recognizer!(
252    InstallShieldRecognizer,
253    PackageType::Installshield,
254    DatasourceId::InstallshieldInstaller,
255    |path: &Path| {
256        path.extension()
257            .and_then(|e| e.to_str())
258            .is_some_and(|ext| ext == "exe")
259            && magic::is_zip(path)
260    }
261);
262
263pub(crate) static RECOGNIZER_METADATA: &[super::metadata::ParserMetadata] = &[
264    super::metadata::ParserMetadata {
265        description: "Misc file type recognizers (JAR, WAR, EAR, AAR, iOS, Chrome, Mozilla, installers, disk images, etc.)",
266        file_patterns: &[
267            "**/*.jar",
268            "**/ivy.xml",
269            "**/*.war",
270            "**/WEB-INF/web.xml",
271            "**/*.ear",
272            "**/META-INF/application.xml",
273            "**/meta-inf/module.xml",
274            "**/*.mar",
275            "**/*.sar",
276            "**/meta-inf/jboss-service.xml",
277            "**/package.js",
278            "**/*.aar",
279            "**/*.xpi",
280            "**/*.crx",
281            "**/*.ipa",
282            "**/*.cab",
283            "**/*.shar",
284            "**/*.dmg",
285            "**/*.sparseimage",
286            "**/*.iso",
287            "**/*.udf",
288            "**/*.img",
289            "**/*.exe",
290        ],
291        package_type: "",
292        primary_language: "",
293        documentation_url: None,
294    },
295];