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    fn metadata() -> Vec<super::super::metadata::ParserMetadata> {
91        vec![super::super::metadata::ParserMetadata {
92            description: ".NET packages.config manifest",
93            file_patterns: &["**/packages.config"],
94            package_type: "nuget",
95            primary_language: "C#",
96            documentation_url: Some(
97                "https://learn.microsoft.com/en-us/nuget/reference/packages-config",
98            ),
99        }]
100    }
101}
102
103fn parse_packages_config_package(element: &quick_xml::events::BytesStart) -> Option<Dependency> {
104    let mut id = None;
105    let mut version = None;
106    let mut target_framework = None;
107
108    for attr in element.attributes().filter_map(|a| a.ok()) {
109        match attr.key.as_ref() {
110            b"id" => id = String::from_utf8(attr.value.to_vec()).ok(),
111            b"version" => version = String::from_utf8(attr.value.to_vec()).ok(),
112            b"targetFramework" => target_framework = String::from_utf8(attr.value.to_vec()).ok(),
113            _ => {}
114        }
115    }
116
117    let name = id?;
118    let purl = PackageUrl::new("nuget", &name).ok().map(|p| p.to_string());
119
120    Some(Dependency {
121        purl,
122        extracted_requirement: version,
123        scope: target_framework,
124        is_runtime: Some(true),
125        is_optional: Some(false),
126        is_pinned: Some(true),
127        is_direct: Some(true),
128        resolved_package: None,
129        extra_data: None,
130    })
131}