Skip to main content

provenant/parsers/nuget/
packages_config.rs

1use 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);