Skip to main content

provenant/parsers/nuget/
packages_lock.rs

1// SPDX-FileCopyrightText: nexB Inc. and others
2// SPDX-FileCopyrightText: Provenant contributors
3// SPDX-License-Identifier: Apache-2.0
4// Derived from ScanCode Toolkit (Apache-2.0); modified. See NOTICE.
5
6use std::path::Path;
7
8use crate::models::{DatasourceId, Dependency, PackageData, PackageType};
9use crate::parser_warn as warn;
10use packageurl::PackageUrl;
11
12use super::super::PackageParser;
13use super::super::utils::{MAX_ITERATION_COUNT, read_file_to_string};
14use super::default_package_data;
15
16pub struct PackagesLockParser;
17
18impl PackageParser for PackagesLockParser {
19    const PACKAGE_TYPE: PackageType = PackageType::Nuget;
20
21    fn is_match(path: &Path) -> bool {
22        path.file_name()
23            .and_then(|name| name.to_str())
24            .is_some_and(|name| name.ends_with("packages.lock.json"))
25    }
26
27    fn extract_packages(path: &Path) -> Vec<PackageData> {
28        let content = match read_file_to_string(path, None) {
29            Ok(c) => c,
30            Err(e) => {
31                warn!("Failed to read packages.lock.json at {:?}: {}", path, e);
32                return vec![default_package_data(Some(DatasourceId::NugetPackagesLock))];
33            }
34        };
35
36        let parsed: serde_json::Value = match serde_json::from_str(&content) {
37            Ok(v) => v,
38            Err(e) => {
39                warn!("Failed to parse packages.lock.json at {:?}: {}", path, e);
40                return vec![default_package_data(Some(DatasourceId::NugetPackagesLock))];
41            }
42        };
43
44        let mut dependencies = Vec::new();
45        let mut iteration_count: usize = 0;
46
47        if let Some(deps_obj) = parsed.get("dependencies").and_then(|v| v.as_object()) {
48            for (target_framework, packages) in deps_obj.iter().take(MAX_ITERATION_COUNT) {
49                if let Some(packages_obj) = packages.as_object() {
50                    for (package_name, package_info) in
51                        packages_obj.iter().take(MAX_ITERATION_COUNT)
52                    {
53                        iteration_count += 1;
54                        if iteration_count > MAX_ITERATION_COUNT {
55                            warn!(
56                                "Iteration limit exceeded in packages.lock.json at {:?}; stopping at {} dependencies",
57                                path, MAX_ITERATION_COUNT
58                            );
59                            break;
60                        }
61                        if let Some(info_obj) = package_info.as_object() {
62                            let version = info_obj
63                                .get("resolved")
64                                .and_then(|v| v.as_str())
65                                .map(|s| s.to_string());
66
67                            let requested = info_obj
68                                .get("requested")
69                                .and_then(|v| v.as_str())
70                                .map(|s| s.to_string());
71
72                            let package_type = info_obj.get("type").and_then(|v| v.as_str());
73
74                            let is_direct = match package_type {
75                                Some("Direct") => Some(true),
76                                Some("Transitive") => Some(false),
77                                _ => None,
78                            };
79
80                            let purl = version.as_ref().and_then(|v| {
81                                PackageUrl::new("nuget", package_name).ok().map(|mut p| {
82                                    let _ = p.with_version(v);
83                                    p.to_string()
84                                })
85                            });
86
87                            let mut extra_data = serde_json::Map::new();
88                            extra_data.insert(
89                                "target_framework".to_string(),
90                                serde_json::Value::String(target_framework.clone()),
91                            );
92
93                            if let Some(content_hash) =
94                                info_obj.get("contentHash").and_then(|v| v.as_str())
95                            {
96                                extra_data.insert(
97                                    "content_hash".to_string(),
98                                    serde_json::Value::String(content_hash.to_string()),
99                                );
100                            }
101
102                            dependencies.push(Dependency {
103                                purl,
104                                extracted_requirement: requested.or(version),
105                                scope: Some(target_framework.clone()),
106                                is_runtime: Some(true),
107                                is_optional: Some(false),
108                                is_pinned: Some(true),
109                                is_direct,
110                                resolved_package: None,
111                                extra_data: if extra_data.is_empty() {
112                                    None
113                                } else {
114                                    Some(extra_data.into_iter().collect())
115                                },
116                            });
117                        }
118                    }
119                }
120            }
121        }
122
123        vec![PackageData {
124            datasource_id: Some(DatasourceId::NugetPackagesLock),
125            package_type: Some(Self::PACKAGE_TYPE),
126            dependencies,
127            ..default_package_data(Some(DatasourceId::NugetPackagesLock))
128        }]
129    }
130
131    fn metadata() -> Vec<super::super::metadata::ParserMetadata> {
132        vec![super::super::metadata::ParserMetadata {
133            description: ".NET packages.lock.json lockfile",
134            file_patterns: &["**/packages.lock.json"],
135            package_type: "nuget",
136            primary_language: "C#",
137            documentation_url: Some(
138                "https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#locking-dependencies",
139            ),
140        }]
141    }
142}