provenant/parsers/nuget/
packages_config.rs1use std::fs::File;
2use std::io::BufReader;
3use std::path::Path;
4
5use crate::models::{DatasourceId, Dependency, PackageData, PackageType};
6use crate::parser_warn as warn;
7use packageurl::PackageUrl;
8use quick_xml::Reader;
9use quick_xml::events::Event;
10
11use super::super::PackageParser;
12use super::super::utils::MAX_ITERATION_COUNT;
13use super::{check_file_size, default_package_data};
14
15pub struct PackagesConfigParser;
16
17impl PackageParser for PackagesConfigParser {
18 const PACKAGE_TYPE: PackageType = PackageType::Nuget;
19
20 fn is_match(path: &Path) -> bool {
21 path.file_name()
22 .and_then(|name| name.to_str())
23 .is_some_and(|name| name == "packages.config")
24 }
25
26 fn extract_packages(path: &Path) -> Vec<PackageData> {
27 if let Err(e) = check_file_size(path) {
28 warn!("{}", e);
29 return vec![default_package_data(Some(
30 DatasourceId::NugetPackagesConfig,
31 ))];
32 }
33
34 let file = match File::open(path) {
35 Ok(f) => f,
36 Err(e) => {
37 warn!("Failed to open packages.config at {:?}: {}", path, e);
38 return vec![default_package_data(Some(
39 DatasourceId::NugetPackagesConfig,
40 ))];
41 }
42 };
43
44 let reader = BufReader::new(file);
45 let mut xml_reader = Reader::from_reader(reader);
46 xml_reader.config_mut().trim_text(true);
47
48 let mut dependencies = Vec::new();
49 let mut buf = Vec::new();
50 let mut iteration_count: usize = 0;
51
52 loop {
53 iteration_count += 1;
54 if iteration_count > MAX_ITERATION_COUNT {
55 warn!(
56 "Iteration limit exceeded in packages.config at {:?}; stopping at {} items",
57 path, MAX_ITERATION_COUNT
58 );
59 break;
60 }
61 match xml_reader.read_event_into(&mut buf) {
62 Ok(Event::Empty(e)) if e.name().as_ref() == b"package" => {
63 if let Some(dep) = parse_packages_config_package(&e) {
64 dependencies.push(dep);
65 }
66 }
67 Ok(Event::Eof) => break,
68 Err(e) => {
69 warn!("Error parsing packages.config at {:?}: {}", path, e);
70 return vec![default_package_data(Some(
71 DatasourceId::NugetPackagesConfig,
72 ))];
73 }
74 _ => {}
75 }
76 buf.clear();
77 }
78
79 vec![PackageData {
80 datasource_id: Some(DatasourceId::NugetPackagesConfig),
81 package_type: Some(Self::PACKAGE_TYPE),
82 dependencies,
83 ..default_package_data(Some(DatasourceId::NugetPackagesConfig))
84 }]
85 }
86}
87
88fn parse_packages_config_package(element: &quick_xml::events::BytesStart) -> Option<Dependency> {
89 let mut id = None;
90 let mut version = None;
91 let mut target_framework = None;
92
93 for attr in element.attributes().filter_map(|a| a.ok()) {
94 match attr.key.as_ref() {
95 b"id" => id = String::from_utf8(attr.value.to_vec()).ok(),
96 b"version" => version = String::from_utf8(attr.value.to_vec()).ok(),
97 b"targetFramework" => target_framework = String::from_utf8(attr.value.to_vec()).ok(),
98 _ => {}
99 }
100 }
101
102 let name = id?;
103 let purl = PackageUrl::new("nuget", &name).ok().map(|p| p.to_string());
104
105 Some(Dependency {
106 purl,
107 extracted_requirement: version,
108 scope: target_framework,
109 is_runtime: Some(true),
110 is_optional: Some(false),
111 is_pinned: Some(true),
112 is_direct: Some(true),
113 resolved_package: None,
114 extra_data: None,
115 })
116}
117
118crate::register_parser!(
119 ".NET packages.config manifest",
120 &["**/packages.config"],
121 "nuget",
122 "C#",
123 Some("https://learn.microsoft.com/en-us/nuget/reference/packages-config"),
124);