Skip to main content

provenant/parsers/nuget/
packages_lock.rs

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