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