Skip to main content

provenant/parsers/nuget/
packages_config.rs

1// SPDX-FileCopyrightText: Provenant contributors
2// SPDX-License-Identifier: Apache-2.0
3
4use 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);