1mod about;
5#[cfg(test)]
6mod about_scan_test;
7#[cfg(test)]
8mod about_test;
9mod alpine;
10#[cfg(test)]
11mod alpine_scan_test;
12mod android;
13#[cfg(test)]
14mod android_test;
15mod arch;
16#[cfg(test)]
17mod arch_scan_test;
18#[cfg(test)]
19mod arch_test;
20pub(crate) mod archive;
21mod autotools;
22#[cfg(test)]
23mod autotools_test;
24mod bazel;
25#[cfg(test)]
26mod bazel_module_test;
27#[cfg(test)]
28mod bazel_test;
29mod bitbake;
30#[cfg(test)]
31mod bitbake_scan_test;
32#[cfg(test)]
33mod bitbake_test;
34mod bower;
35#[cfg(test)]
36mod bower_scan_test;
37#[cfg(test)]
38mod bower_test;
39mod buck;
40#[cfg(test)]
41mod buck_test;
42mod bun_lock;
43#[cfg(test)]
44mod bun_lock_test;
45mod bun_lockb;
46#[cfg(test)]
47mod bun_lockb_test;
48mod cargo;
49mod cargo_lock;
50#[cfg(test)]
51mod cargo_lock_test;
52#[cfg(test)]
53mod cargo_scan_test;
54#[cfg(test)]
55mod cargo_test;
56mod carthage;
57#[cfg(test)]
58mod carthage_scan_test;
59#[cfg(test)]
60mod carthage_test;
61mod chef;
62#[cfg(test)]
63mod chef_scan_test;
64#[cfg(test)]
65mod chef_test;
66mod citation;
67#[cfg(test)]
68mod citation_test;
69mod clojure;
70#[cfg(test)]
71mod clojure_scan_test;
72#[cfg(test)]
73mod clojure_test;
74#[cfg(test)]
75mod cocoapods_scan_test;
76pub(crate) mod compiled_binary;
77mod composer;
78#[cfg(test)]
79mod composer_scan_test;
80#[cfg(test)]
81mod composer_test;
82mod conan;
83mod conan_data;
84#[cfg(test)]
85mod conan_data_test;
86#[cfg(test)]
87mod conan_scan_test;
88#[cfg(test)]
89mod conan_test;
90mod conda;
91mod conda_meta_json;
92#[cfg(test)]
93mod conda_meta_json_test;
94#[cfg(test)]
95mod conda_scan_test;
96#[cfg(test)]
97mod conda_test;
98mod cpan;
99mod cpan_dist_ini;
100#[cfg(test)]
101mod cpan_dist_ini_test;
102mod cpan_makefile_pl;
103#[cfg(test)]
104mod cpan_makefile_pl_test;
105#[cfg(test)]
106mod cpan_scan_test;
107#[cfg(test)]
108mod cpan_test;
109mod cran;
110#[cfg(test)]
111mod cran_scan_test;
112#[cfg(test)]
113mod cran_test;
114mod dart;
115#[cfg(test)]
116mod dart_scan_test;
117#[cfg(test)]
118mod dart_test;
119mod debian;
120mod declared_license_aliases;
121mod deno;
122mod deno_lock;
123#[cfg(test)]
124mod deno_lock_test;
125#[cfg(test)]
126mod deno_scan_test;
127#[cfg(test)]
128mod deno_test;
129mod docker;
130#[cfg(test)]
131mod docker_scan_test;
132#[cfg(test)]
133mod docker_test;
134mod erlang_otp;
135#[cfg(test)]
136mod erlang_otp_scan_test;
137#[cfg(test)]
138mod erlang_otp_test;
139mod freebsd;
140#[cfg(test)]
141mod freebsd_scan_test;
142#[cfg(test)]
143mod freebsd_test;
144mod gitmodules;
145#[cfg(test)]
146mod gitmodules_scan_test;
147mod go;
148mod go_mod_graph;
149#[cfg(test)]
150mod go_scan_test;
151#[cfg(test)]
152mod go_test;
153#[cfg(test)]
154mod go_work_test;
155#[cfg(feature = "golden-tests")]
156pub mod golden_test_utils;
157mod gradle;
158mod gradle_lock;
159#[cfg(test)]
160mod gradle_lock_test;
161mod gradle_module;
162#[cfg(test)]
163mod gradle_module_scan_test;
164#[cfg(test)]
165mod gradle_module_test;
166#[cfg(test)]
167mod gradle_scan_test;
168mod gradle_settings;
169mod hackage;
170#[cfg(test)]
171mod hackage_scan_test;
172#[cfg(test)]
173mod hackage_test;
174mod haxe;
175#[cfg(test)]
176mod haxe_scan_test;
177#[cfg(test)]
178mod haxe_test;
179mod helm;
180#[cfg(test)]
181mod helm_scan_test;
182#[cfg(test)]
183mod helm_test;
184mod hex_lock;
185#[cfg(test)]
186mod hex_lock_test;
187mod huggingface;
188#[cfg(test)]
189mod huggingface_scan_test;
190#[cfg(test)]
191mod huggingface_test;
192mod ivy;
193#[cfg(test)]
194mod ivy_scan_test;
195#[cfg(test)]
196mod ivy_test;
197mod julia;
198#[cfg(test)]
199mod julia_test;
200pub(crate) mod license_normalization;
201mod maven;
202mod meson;
203#[cfg(test)]
204mod meson_scan_test;
205#[cfg(test)]
206mod meson_test;
207pub mod metadata;
208mod microsoft_update_manifest;
209#[cfg(test)]
210mod microsoft_update_manifest_test;
211mod misc;
212#[cfg(test)]
213mod misc_test;
214mod mix_exs;
215#[cfg(test)]
216mod mix_exs_scan_test;
217#[cfg(test)]
218mod mix_exs_test;
219mod nix;
220#[cfg(test)]
221mod nix_scan_test;
222#[cfg(test)]
223mod nix_test;
224mod npm;
225mod npm_lock;
226#[cfg(test)]
227mod npm_lock_test;
228#[cfg(test)]
229mod npm_scan_test;
230#[cfg(test)]
231mod npm_test;
232mod npm_workspace;
233#[cfg(test)]
234mod npm_workspace_test;
235mod nuget;
236mod oci;
237#[cfg(test)]
238mod oci_scan_test;
239#[cfg(test)]
240mod oci_test;
241mod opam;
242#[cfg(test)]
243mod opam_scan_test;
244mod os_release;
245#[cfg(test)]
246mod os_release_test;
247mod pep508;
248mod pip_inspect_deplock;
249#[cfg(test)]
250mod pip_inspect_deplock_test;
251mod pipfile_lock;
252#[cfg(test)]
253mod pipfile_lock_test;
254mod pixi;
255#[cfg(test)]
256mod pixi_scan_test;
257#[cfg(test)]
258mod pixi_test;
259mod pnpm_lock;
260#[cfg(test)]
261mod pnpm_lock_test;
262mod podfile;
263mod podfile_lock;
264#[cfg(test)]
265mod podfile_lock_test;
266mod podspec;
267mod podspec_json;
268#[cfg(test)]
269mod podspec_json_test;
270mod poetry_lock;
271#[cfg(test)]
272mod poetry_lock_test;
273mod publiccode;
274#[cfg(test)]
275mod publiccode_test;
276mod pylock_toml;
277#[cfg(test)]
278mod pylock_toml_test;
279mod python;
280mod readme;
281#[cfg(test)]
282mod readme_test;
283mod requirements_txt;
284#[cfg(test)]
285mod requirements_txt_test;
286pub(crate) mod rfc822;
287mod rpm_db;
288mod rpm_db_native;
289#[cfg(test)]
290mod rpm_db_scan_test;
291mod rpm_license_files;
292#[cfg(test)]
293mod rpm_license_files_test;
294mod rpm_mariner_manifest;
295#[cfg(test)]
296mod rpm_mariner_manifest_test;
297mod rpm_parser;
298#[cfg(test)]
299mod rpm_scan_test;
300mod rpm_specfile;
301#[cfg(test)]
302mod rpm_specfile_test;
303mod rpm_yumdb;
304mod ruby;
305#[cfg(test)]
306mod ruby_scan_test;
307#[cfg(test)]
308mod ruby_test;
309mod sbt;
310#[cfg(test)]
311mod sbt_scan_test;
312#[cfg(test)]
313mod sbt_test;
314#[cfg(test)]
315mod scan_test_utils;
316mod starlark_parse;
317mod swift_manifest_json;
318#[cfg(test)]
319mod swift_manifest_json_test;
320mod swift_resolved;
321#[cfg(test)]
322mod swift_resolved_test;
323#[cfg(test)]
324mod swift_scan_test;
325mod swift_show_dependencies;
326#[cfg(test)]
327mod swift_show_dependencies_test;
328pub mod utils;
329mod uv_lock;
330#[cfg(test)]
331mod uv_lock_test;
332mod vcpkg;
333#[cfg(test)]
334mod vcpkg_scan_test;
335#[cfg(test)]
336mod vcpkg_test;
337mod vscode_extension;
338#[cfg(test)]
339mod vscode_extension_scan_test;
340#[cfg(test)]
341mod vscode_extension_test;
342pub(crate) mod windows_executable;
343#[cfg(test)]
344mod windows_executable_golden_test;
345mod yarn_lock;
346#[cfg(test)]
347mod yarn_lock_test;
348mod yarn_pnp;
349#[cfg(test)]
350mod yarn_pnp_test;
351
352use std::cell::RefCell;
353use std::panic::{AssertUnwindSafe, catch_unwind};
354use std::path::Path;
355use std::sync::Arc;
356
357use crate::license_detection::LicenseDetectionEngine;
358use crate::models::{DiagnosticSeverity, PackageData, PackageType, ScanDiagnostic};
359use crate::parsers::license_normalization::{
360 finalize_package_declared_license_references, populate_declared_license_and_holder,
361};
362use crate::parsers::utils::CappedIterExt;
363
364thread_local! {
365 static PARSER_DIAGNOSTIC_STACK: RefCell<Vec<Vec<ScanDiagnostic>>> = const { RefCell::new(Vec::new()) };
366 static PARSER_LICENSE_ENGINE_STACK: RefCell<Vec<Option<Arc<LicenseDetectionEngine>>>> = const { RefCell::new(Vec::new()) };
367 static PARSER_SCAN_ROOT_STACK: RefCell<Vec<Option<std::path::PathBuf>>> = const { RefCell::new(Vec::new()) };
368}
369
370#[derive(Debug, Default)]
371pub struct ParsePackagesResult {
372 pub packages: Vec<PackageData>,
373 pub scan_diagnostics: Vec<ScanDiagnostic>,
374}
375
376fn panic_payload_to_string(payload: &(dyn std::any::Any + Send)) -> String {
377 if let Some(message) = payload.downcast_ref::<&str>() {
378 (*message).to_string()
379 } else if let Some(message) = payload.downcast_ref::<String>() {
380 message.clone()
381 } else {
382 "unknown panic payload".to_string()
383 }
384}
385
386pub(crate) fn capture_parser_diagnostics<F>(
387 extract: F,
388 handler_name: &str,
389 path: &Path,
390 license_engine: Option<Arc<LicenseDetectionEngine>>,
391) -> ParsePackagesResult
392where
393 F: FnOnce() -> Vec<PackageData>,
394{
395 PARSER_DIAGNOSTIC_STACK.with(|stack| {
396 stack.borrow_mut().push(Vec::new());
397 });
398 PARSER_LICENSE_ENGINE_STACK.with(|stack| {
399 stack.borrow_mut().push(license_engine);
400 });
401
402 let extract_result = catch_unwind(AssertUnwindSafe(|| {
403 extract()
404 .into_iter()
405 .map(|mut package| {
406 populate_declared_license_and_holder(&mut package);
407 finalize_package_declared_license_references(&mut package);
408 package
409 })
410 .capped("packages emitted by parser")
411 .collect::<Vec<_>>()
412 }));
413 PARSER_LICENSE_ENGINE_STACK.with(|stack| {
414 stack.borrow_mut().pop();
415 });
416 let mut scan_diagnostics =
417 PARSER_DIAGNOSTIC_STACK.with(|stack| stack.borrow_mut().pop().unwrap_or_default());
418
419 match extract_result {
420 Ok(packages) => ParsePackagesResult {
421 packages,
422 scan_diagnostics,
423 },
424 Err(payload) => {
425 scan_diagnostics.push(ScanDiagnostic::error(format!(
426 "{} panicked while parsing {}: {}",
427 handler_name,
428 path.display(),
429 panic_payload_to_string(payload.as_ref())
430 )));
431 ParsePackagesResult {
432 packages: Vec::new(),
433 scan_diagnostics,
434 }
435 }
436 }
437}
438
439pub(crate) fn active_parser_license_engine() -> Option<Arc<LicenseDetectionEngine>> {
440 PARSER_LICENSE_ENGINE_STACK.with(|stack| stack.borrow().last().cloned().flatten())
441}
442
443pub(crate) fn active_parser_scan_root() -> Option<std::path::PathBuf> {
444 PARSER_SCAN_ROOT_STACK.with(|stack| stack.borrow().last().cloned().flatten())
445}
446
447pub(crate) fn with_parser_scan_root<T>(scan_root: Option<&Path>, f: impl FnOnce() -> T) -> T {
448 PARSER_SCAN_ROOT_STACK.with(|stack| {
449 stack.borrow_mut().push(scan_root.map(Path::to_path_buf));
450 });
451 let result = f();
452 PARSER_SCAN_ROOT_STACK.with(|stack| {
453 stack.borrow_mut().pop();
454 });
455 result
456}
457
458pub(crate) fn with_parser_license_engine<T>(
462 engine: Option<Arc<LicenseDetectionEngine>>,
463 f: impl FnOnce() -> T,
464) -> T {
465 PARSER_LICENSE_ENGINE_STACK.with(|stack| {
466 stack.borrow_mut().push(engine);
467 });
468 let result = f();
469 PARSER_LICENSE_ENGINE_STACK.with(|stack| {
470 stack.borrow_mut().pop();
471 });
472 result
473}
474
475pub(crate) fn record_parser_diagnostic(message: String, severity: DiagnosticSeverity) -> bool {
476 PARSER_DIAGNOSTIC_STACK.with(|stack| {
477 let mut stack = stack.borrow_mut();
478 let Some(active) = stack.last_mut() else {
479 return false;
480 };
481 active.push(ScanDiagnostic { severity, message });
482 true
483 })
484}
485
486#[macro_export]
487macro_rules! parser_warn {
488 ($($arg:tt)*) => {{
489 let message = format!($($arg)*);
490 if !$crate::parsers::record_parser_diagnostic(
491 message.clone(),
492 $crate::models::DiagnosticSeverity::Warning,
493 ) {
494 log::warn!("{message}");
495 }
496 }};
497}
498
499pub trait PackageParser {
543 const PACKAGE_TYPE: PackageType;
545
546 fn extract_packages(path: &Path) -> Vec<PackageData>;
556
557 fn is_match(path: &Path) -> bool;
562
563 fn extract_first_package(path: &Path) -> PackageData {
566 Self::extract_packages(path)
567 .into_iter()
568 .map(|mut package| {
569 populate_declared_license_and_holder(&mut package);
570 finalize_package_declared_license_references(&mut package);
571 package
572 })
573 .next()
574 .unwrap_or_default()
575 }
576
577 fn metadata() -> Vec<metadata::ParserMetadata> {
584 Vec::new()
585 }
586}
587
588pub fn try_parse_rpm_archive_with_license_engine(
589 path: &Path,
590 license_engine: Option<Arc<LicenseDetectionEngine>>,
591) -> Option<ParsePackagesResult> {
592 if !self::rpm_parser::path_looks_like_rpm_archive(path) {
593 return None;
594 }
595
596 if <RpmParser as PackageParser>::is_match(path) {
597 return Some(capture_parser_diagnostics(
598 || self::rpm_parser::extract_rpm_packages(path),
599 stringify!(RpmParser),
600 path,
601 license_engine,
602 ));
603 }
604
605 None
606}
607
608pub fn try_parse_rpm_archive(path: &Path) -> Option<ParsePackagesResult> {
609 try_parse_rpm_archive_with_license_engine(path, None)
610}
611
612#[cfg(feature = "golden-tests")]
613pub fn try_parse_compiled_bytes(bytes: &[u8]) -> Option<ParsePackagesResult> {
614 self::compiled_binary::try_parse_compiled_bytes(bytes)
615}
616
617#[cfg(feature = "golden-tests")]
618pub fn try_parse_windows_executable_bytes(
619 path: &Path,
620 bytes: &[u8],
621) -> Option<ParsePackagesResult> {
622 self::windows_executable::try_parse_windows_executable_bytes(path, bytes)
623}
624
625pub fn path_looks_like_rpm_archive(path: &Path) -> bool {
626 self::rpm_parser::path_looks_like_rpm_archive(path)
627}
628
629pub fn all_metadata() -> Vec<metadata::ParserMetadata> {
634 let mut entries = collect_parser_metadata();
635 entries.extend_from_slice(self::compiled_binary::COMPILED_BINARY_METADATA);
636 entries.extend_from_slice(self::windows_executable::WINDOWS_EXE_METADATA);
637 entries.extend_from_slice(self::misc::RECOGNIZER_METADATA);
638 entries.extend_from_slice(crate::utils::font::FONT_METADATA);
639 entries
640}
641
642pub use self::about::AboutFileParser;
643pub use self::alpine::{AlpineApkParser, AlpineApkbuildParser, AlpineInstalledParser};
644pub use self::android::{
645 AndroidAabParser, AndroidApkParser, AndroidManifestParser, AndroidSoongMetadataParser,
646};
647#[cfg(feature = "golden-tests")]
648pub use self::android::{
649 ProtoItem, ProtoPrimitive, ProtoRawStringValue, ProtoSourcePosition, ProtoStringValue,
650 ProtoXmlAttribute, ProtoXmlElement, ProtoXmlNamespace, ProtoXmlNode, proto_item,
651 proto_primitive, proto_xml_node,
652};
653pub use self::arch::{ArchPkginfoParser, ArchSrcinfoParser};
654pub use self::autotools::AutotoolsConfigureParser;
655pub use self::bazel::{BazelBuildParser, BazelModuleParser};
656pub use self::bitbake::BitbakeRecipeParser;
657pub use self::bower::BowerJsonParser;
658pub use self::buck::{BuckBuildParser, BuckMetadataBzlParser};
659pub use self::bun_lock::BunLockParser;
660pub use self::bun_lockb::BunLockbParser;
661pub use self::cargo::CargoParser;
662#[cfg_attr(not(test), allow(unused_imports))]
663pub use self::cargo_lock::CargoLockParser;
664pub use self::carthage::{CarthageCartfileParser, CarthageCartfileResolvedParser};
665pub use self::chef::{ChefMetadataJsonParser, ChefMetadataRbParser};
666pub use self::citation::CitationCffParser;
667pub use self::clojure::{ClojureDepsEdnParser, ClojureProjectCljParser};
668pub use self::composer::{ComposerJsonParser, ComposerLockParser};
669pub use self::conan::{ConanFilePyParser, ConanLockParser, ConanfileTxtParser};
670pub use self::conan_data::ConanDataParser;
671pub use self::conda::{CondaEnvironmentYmlParser, CondaMetaYamlParser};
672pub use self::conda_meta_json::CondaMetaJsonParser;
673pub use self::cpan::{CpanManifestParser, CpanMetaJsonParser, CpanMetaYmlParser};
674pub use self::cpan_dist_ini::CpanDistIniParser;
675pub use self::cpan_makefile_pl::CpanMakefilePlParser;
676pub use self::cran::CranParser;
677pub use self::dart::{PubspecLockParser, PubspecYamlParser};
678pub use self::debian::{
679 DebianControlInExtractedDebParser, DebianControlParser, DebianCopyrightParser, DebianDebParser,
680 DebianDebianTarParser, DebianDistrolessInstalledParser, DebianDscParser,
681 DebianInstalledListParser, DebianInstalledMd5sumsParser, DebianInstalledParser,
682 DebianMd5sumInPackageParser, DebianOrigTarParser,
683};
684pub use self::deno::DenoParser;
685pub use self::deno_lock::DenoLockParser;
686pub use self::docker::DockerfileParser;
687pub use self::erlang_otp::{ErlangAppSrcParser, RebarConfigParser, RebarLockParser};
688pub use self::freebsd::FreebsdCompactManifestParser;
689pub use self::gitmodules::GitmodulesParser;
690pub use self::go::{GoModParser, GoSumParser, GoWorkParser, GodepsParser};
691pub use self::go_mod_graph::GoModGraphParser;
692pub use self::gradle::GradleParser;
693pub use self::gradle_lock::GradleLockfileParser;
694pub use self::gradle_module::GradleModuleParser;
695pub use self::gradle_settings::GradleSettingsParser;
696pub use self::hackage::{HackageCabalParser, HackageCabalProjectParser, HackageStackYamlParser};
697pub use self::haxe::HaxeParser;
698pub use self::helm::{HelmChartLockParser, HelmChartYamlParser};
699pub use self::hex_lock::HexLockParser;
700pub use self::huggingface::{
701 HuggingfaceConfigParser, HuggingfaceModelCardParser, HuggingfaceModelIndexParser,
702};
703pub use self::ivy::{IvyDependenciesPropertiesParser, IvyXmlParser};
704pub use self::julia::{JuliaManifestTomlParser, JuliaProjectTomlParser};
705pub use self::maven::MavenParser;
706pub use self::meson::MesonParser;
707pub use self::microsoft_update_manifest::MicrosoftUpdateManifestParser;
708pub use self::misc::{
709 AndroidLibraryRecognizer, AppleDmgRecognizer, Axis2MarRecognizer, Axis2ModuleXmlRecognizer,
710 CabArchiveRecognizer, ChromeCrxRecognizer, InstallShieldRecognizer, IosIpaRecognizer,
711 IsoImageRecognizer, JBossSarRecognizer, JBossServiceXmlRecognizer, JavaEarAppXmlRecognizer,
712 JavaEarRecognizer, JavaJarRecognizer, JavaWarRecognizer, JavaWarWebXmlRecognizer,
713 MeteorPackageRecognizer, MozillaXpiRecognizer, NsisRecognizer, SharArchiveRecognizer,
714 SquashfsRecognizer,
715};
716pub use self::mix_exs::MixExsParser;
717pub use self::nix::{NixDefaultParser, NixFlakeLockParser, NixFlakeParser};
718pub use self::npm::NpmParser;
719pub use self::npm_lock::NpmLockParser;
720pub use self::npm_workspace::NpmWorkspaceParser;
721pub(crate) use self::nuget::build_nuget_purl;
724pub use self::nuget::{
725 CentralPackageManagementPropsParser, DirectoryBuildPropsParser, DotNetDepsJsonParser,
726 NupkgParser, NuspecParser, PackageReferenceProjectParser, PackagesConfigParser,
727 PackagesLockParser, ProjectJsonParser, ProjectLockJsonParser,
728};
729pub use self::oci::OciImageLayoutParser;
730pub use self::opam::OpamParser;
731pub use self::os_release::OsReleaseParser;
732pub use self::pip_inspect_deplock::PipInspectDeplockParser;
733pub use self::pipfile_lock::PipfileLockParser;
734pub use self::pixi::{PixiLockParser, PixiTomlParser};
735pub use self::pnpm_lock::PnpmLockParser;
736pub use self::podfile::PodfileParser;
737pub use self::podfile_lock::PodfileLockParser;
738pub use self::podspec::PodspecParser;
739pub use self::podspec_json::PodspecJsonParser;
740pub use self::poetry_lock::PoetryLockParser;
741pub use self::publiccode::PubliccodeParser;
742pub use self::pylock_toml::PylockTomlParser;
743pub use self::python::PythonParser;
744pub use self::readme::ReadmeParser;
745pub use self::requirements_txt::RequirementsTxtParser;
746#[cfg(feature = "rpm-sqlite")]
747pub use self::rpm_db::RpmSqliteDatabaseParser;
748pub use self::rpm_db::{RpmBdbDatabaseParser, RpmNdbDatabaseParser};
749pub use self::rpm_license_files::RpmLicenseFilesParser;
750pub use self::rpm_mariner_manifest::RpmMarinerManifestParser;
751pub use self::rpm_parser::RpmParser;
752pub use self::rpm_specfile::RpmSpecfileParser;
753pub use self::rpm_yumdb::RpmYumdbParser;
754pub use self::ruby::{
755 GemArchiveParser, GemMetadataExtractedParser, GemfileLockParser, GemfileParser, GemspecParser,
756};
757pub use self::sbt::SbtParser;
758pub use self::swift_manifest_json::SwiftManifestJsonParser;
759pub use self::swift_resolved::SwiftPackageResolvedParser;
760pub use self::swift_show_dependencies::SwiftShowDependenciesParser;
761pub use self::uv_lock::UvLockParser;
762pub use self::vcpkg::{
763 VcpkgConfigurationParser, VcpkgControlParser, VcpkgLockParser, VcpkgManifestParser,
764};
765pub use self::vscode_extension::VscodeExtensionManifestParser;
766pub use self::yarn_lock::YarnLockParser;
767pub use self::yarn_pnp::YarnPnpParser;
768
769macro_rules! register_package_handlers {
775 (
776 parsers: [$($(#[$parser_meta:meta])* $parser:ty),* $(,)?],
777 recognizers: [$($recognizer:ty),* $(,)?] $(,)?
778 ) => {
779 pub fn try_parse_file_with_license_engine(
780 path: &Path,
781 license_engine: Option<Arc<LicenseDetectionEngine>>,
782 ) -> Option<ParsePackagesResult> {
783 $(
784 $(#[$parser_meta])*
785 if <$parser>::is_match(path) {
786 return Some(capture_parser_diagnostics(
787 || <$parser>::extract_packages(path),
788 stringify!($parser),
789 path,
790 license_engine.clone(),
791 ));
792 }
793 )*
794 $(
795 if <$recognizer>::is_match(path) {
796 return Some(capture_parser_diagnostics(
797 || <$recognizer>::extract_packages(path),
798 stringify!($recognizer),
799 path,
800 license_engine.clone(),
801 ));
802 }
803 )*
804 None
805 }
806
807 pub fn try_parse_file(path: &Path) -> Option<ParsePackagesResult> {
808 try_parse_file_with_license_engine(path, None)
809 }
810
811 #[allow(dead_code)]
814 pub fn parse_by_type_name(type_name: &str, path: &Path) -> Option<PackageData> {
815 match type_name {
816 $(
817 $(#[$parser_meta])*
818 stringify!($parser) => Some(<$parser>::extract_first_package(path)),
819 )*
820 $(
821 stringify!($recognizer) => Some(<$recognizer>::extract_first_package(path)),
822 )*
823 _ => None
824 }
825 }
826
827 #[allow(dead_code)]
830 pub fn list_parser_types() -> Vec<&'static str> {
831 vec![
832 $(
833 $(#[$parser_meta])*
834 stringify!($parser),
835 )*
836 $(
837 stringify!($recognizer),
838 )*
839 ]
840 }
841
842 pub fn collect_parser_metadata() -> Vec<metadata::ParserMetadata> {
844 let mut entries = Vec::new();
845 $(
846 $(#[$parser_meta])*
847 entries.extend(<$parser>::metadata());
848 )*
849 $(
850 entries.extend(<$recognizer>::metadata());
851 )*
852 entries
853 }
854 };
855}
856
857#[cfg(test)]
858mod tests {
859 use std::collections::HashMap;
860
861 use super::{active_parser_license_engine, capture_parser_diagnostics};
862 use crate::license_detection::LicenseDetectionEngine;
863 use crate::models::PackageData;
864 use crate::parsers::license_normalization::{
865 clear_last_parser_license_engine_ptr, last_parser_license_engine_ptr,
866 };
867 use std::path::Path;
868 use std::sync::Arc;
869
870 #[test]
871 fn test_capture_parser_diagnostics_exposes_active_license_engine() {
872 let engine =
873 Arc::new(LicenseDetectionEngine::from_embedded().expect("embedded engine should load"));
874
875 let result = capture_parser_diagnostics(
876 || {
877 assert!(active_parser_license_engine().is_some());
878 vec![PackageData::default()]
879 },
880 "TestParser",
881 Path::new("testdata/package.json"),
882 Some(engine),
883 );
884
885 assert_eq!(result.packages.len(), 1);
886 assert!(active_parser_license_engine().is_none());
887 }
888
889 #[test]
890 fn test_capture_parser_diagnostics_keeps_active_license_engine_for_finalization() {
891 let engine =
892 Arc::new(LicenseDetectionEngine::from_embedded().expect("embedded engine should load"));
893 clear_last_parser_license_engine_ptr();
894
895 let result = capture_parser_diagnostics(
896 || {
897 vec![PackageData {
898 declared_license_expression: Some("mit".to_string()),
899 declared_license_expression_spdx: Some("MIT".to_string()),
900 extracted_license_statement: Some("MIT".to_string()),
901 extra_data: Some(HashMap::from([(
902 "license_file".to_string(),
903 serde_json::Value::String("LICENSE".to_string()),
904 )])),
905 ..Default::default()
906 }]
907 },
908 "TestParser",
909 Path::new("testdata/package.json"),
910 Some(Arc::clone(&engine)),
911 );
912
913 assert_eq!(result.packages.len(), 1);
914 assert_eq!(
915 last_parser_license_engine_ptr(),
916 Some(Arc::as_ptr(&engine) as usize)
917 );
918 assert_eq!(
919 result.packages[0].license_detections[0].matches[0]
920 .referenced_filenames
921 .as_ref(),
922 Some(&vec!["LICENSE".to_string()])
923 );
924 assert!(active_parser_license_engine().is_none());
925 }
926}
927
928register_package_handlers! {
929 parsers: [
930 AboutFileParser,
931 AndroidAabParser,
932 AndroidApkParser,
933 AndroidManifestParser,
934 AndroidSoongMetadataParser,
935 AlpineApkParser,
936 AlpineApkbuildParser,
937 AlpineInstalledParser,
938 ArchPkginfoParser,
939 ArchSrcinfoParser,
940 AutotoolsConfigureParser,
941 BazelBuildParser,
942 BazelModuleParser,
943 BitbakeRecipeParser,
944 BowerJsonParser,
945 BunLockParser,
946 BunLockbParser,
947 BuckBuildParser,
948 BuckMetadataBzlParser,
949 CargoLockParser,
950 CargoParser,
951 CarthageCartfileParser,
952 CarthageCartfileResolvedParser,
953 ChefMetadataJsonParser,
954 ChefMetadataRbParser,
955 CitationCffParser,
956 ClojureDepsEdnParser,
957 ClojureProjectCljParser,
958 ComposerJsonParser,
959 ComposerLockParser,
960 ConanDataParser,
961 ConanFilePyParser,
962 ConanfileTxtParser,
963 ConanLockParser,
964 CondaEnvironmentYmlParser,
965 CondaMetaJsonParser,
966 CondaMetaYamlParser,
967 CpanDistIniParser,
968 CpanMakefilePlParser,
969 CpanManifestParser,
970 CpanMetaJsonParser,
971 CpanMetaYmlParser,
972 CranParser,
973 DebianControlInExtractedDebParser,
974 DebianControlParser,
975 DebianCopyrightParser,
976 DebianDebianTarParser,
977 DebianDebParser,
978 DebianDistrolessInstalledParser,
979 DebianDscParser,
980 DebianInstalledListParser,
981 DebianInstalledMd5sumsParser,
982 DebianInstalledParser,
983 DebianMd5sumInPackageParser,
984 DebianOrigTarParser,
985 DenoParser,
986 DenoLockParser,
987 DockerfileParser,
988 ErlangAppSrcParser,
989 RebarConfigParser,
990 RebarLockParser,
991 FreebsdCompactManifestParser,
992 GemArchiveParser,
993 GemfileLockParser,
994 GemfileParser,
995 GemMetadataExtractedParser,
996 GemspecParser,
997 GitmodulesParser,
998 GodepsParser,
999 GoModParser,
1000 GoModGraphParser,
1001 GoSumParser,
1002 GoWorkParser,
1003 GradleLockfileParser,
1004 GradleParser,
1005 GradleModuleParser,
1006 GradleSettingsParser,
1007 HackageCabalParser,
1008 HackageCabalProjectParser,
1009 HackageStackYamlParser,
1010 HelmChartYamlParser,
1011 HelmChartLockParser,
1012 HaxeParser,
1013 HexLockParser,
1014 MixExsParser,
1015 HuggingfaceModelCardParser,
1016 HuggingfaceConfigParser,
1017 HuggingfaceModelIndexParser,
1018 IvyDependenciesPropertiesParser,
1019 IvyXmlParser,
1020 JuliaManifestTomlParser,
1021 JuliaProjectTomlParser,
1022 MavenParser,
1023 MesonParser,
1024 MicrosoftUpdateManifestParser,
1025 NixDefaultParser,
1026 NixFlakeLockParser,
1027 NixFlakeParser,
1028 NpmLockParser,
1029 NpmParser,
1030 NpmWorkspaceParser,
1031 DotNetDepsJsonParser,
1032 CentralPackageManagementPropsParser,
1033 DirectoryBuildPropsParser,
1034 NupkgParser,
1035 NuspecParser,
1036 PackageReferenceProjectParser,
1037 OciImageLayoutParser,
1038 OpamParser,
1039 OsReleaseParser,
1040 PackagesConfigParser,
1041 PackagesLockParser,
1042 ProjectJsonParser,
1043 ProjectLockJsonParser,
1044 PipfileLockParser,
1045 PipInspectDeplockParser,
1046 PixiTomlParser,
1047 PixiLockParser,
1048 PnpmLockParser,
1049 PodfileLockParser,
1050 PodfileParser,
1051 PodspecJsonParser,
1052 PodspecParser,
1053 PoetryLockParser,
1054 PubliccodeParser,
1055 PylockTomlParser,
1056 PubspecLockParser,
1057 PubspecYamlParser,
1058 PythonParser,
1059 UvLockParser,
1060 VcpkgConfigurationParser,
1061 VcpkgControlParser,
1062 VcpkgLockParser,
1063 VcpkgManifestParser,
1064 VscodeExtensionManifestParser,
1065 ReadmeParser,
1066 RequirementsTxtParser,
1067 RpmBdbDatabaseParser,
1068 RpmLicenseFilesParser,
1069 RpmMarinerManifestParser,
1070 RpmNdbDatabaseParser,
1071 RpmParser,
1072 RpmSpecfileParser,
1073 #[cfg(feature = "rpm-sqlite")]
1074 RpmSqliteDatabaseParser,
1075 RpmYumdbParser,
1076 SbtParser,
1077 SwiftManifestJsonParser,
1078 SwiftPackageResolvedParser,
1079 SwiftShowDependenciesParser,
1080 YarnLockParser,
1081 YarnPnpParser,
1082 ],
1083 recognizers: [
1084 AndroidLibraryRecognizer,
1085 AppleDmgRecognizer,
1086 Axis2MarRecognizer,
1087 Axis2ModuleXmlRecognizer,
1088 CabArchiveRecognizer,
1089 ChromeCrxRecognizer,
1090 InstallShieldRecognizer,
1091 IosIpaRecognizer,
1092 IsoImageRecognizer,
1093 JavaEarAppXmlRecognizer,
1094 JavaEarRecognizer,
1095 JavaJarRecognizer,
1096 JavaWarRecognizer,
1097 JavaWarWebXmlRecognizer,
1098 JBossSarRecognizer,
1099 JBossServiceXmlRecognizer,
1100 MeteorPackageRecognizer,
1101 MozillaXpiRecognizer,
1102 NsisRecognizer,
1103 SharArchiveRecognizer,
1104 SquashfsRecognizer,
1105 ],
1106}
1107
1108#[cfg(test)]
1109mod panic_isolation_tests {
1110 use super::*;
1111 use crate::models::DiagnosticSeverity;
1112
1113 #[test]
1114 fn capture_parser_diagnostics_turns_panics_into_scan_errors() {
1115 let path = Path::new("fixtures/panic-package.json");
1116 let result = capture_parser_diagnostics(
1117 || -> Vec<PackageData> { panic!("panic boom") },
1118 "PanicParser",
1119 path,
1120 None,
1121 );
1122
1123 assert!(result.packages.is_empty());
1124 assert_eq!(result.scan_diagnostics.len(), 1);
1125 assert_eq!(
1126 result.scan_diagnostics[0].severity,
1127 DiagnosticSeverity::Error
1128 );
1129 assert!(result.scan_diagnostics[0].message.contains("PanicParser"));
1130 assert!(
1131 result.scan_diagnostics[0]
1132 .message
1133 .contains("fixtures/panic-package.json")
1134 );
1135 assert!(result.scan_diagnostics[0].message.contains("panic boom"));
1136 }
1137
1138 #[test]
1139 fn capture_parser_diagnostics_recovers_after_panic() {
1140 let panic_path = Path::new("fixtures/panic-package.json");
1141 let _ = capture_parser_diagnostics(
1142 || -> Vec<PackageData> { panic!("panic boom") },
1143 "PanicParser",
1144 panic_path,
1145 None,
1146 );
1147
1148 let ok_path = Path::new("fixtures/recovered-package.json");
1149 let result = capture_parser_diagnostics(
1150 || {
1151 crate::parser_warn!("recoverable parser warning");
1152 vec![PackageData {
1153 package_type: Some(PackageType::Npm),
1154 ..Default::default()
1155 }]
1156 },
1157 "RecoveringParser",
1158 ok_path,
1159 None,
1160 );
1161
1162 assert_eq!(result.packages.len(), 1);
1163 assert_eq!(result.scan_diagnostics.len(), 1);
1164 assert_eq!(
1165 result.scan_diagnostics[0].message,
1166 "recoverable parser warning"
1167 );
1168 assert_eq!(
1169 result.scan_diagnostics[0].severity,
1170 DiagnosticSeverity::Warning
1171 );
1172 }
1173}