package_parser/pkgs/dotnet/
csproj.rs1use packageurl::PackageUrl;
2
3use serde::Deserialize;
4use serde_xml_rs::Deserializer;
5
6use crate::error::SourcePkgError;
7use crate::pkgs::common::model::{DependentPackage, Package, PackageManifest};
8
9use std::path::Path;
10
11const INVALID_CHAR: &str = "\u{feff}";
12
13#[derive(Debug, Deserialize, PartialEq, Eq)]
14pub struct PackageReference {
15 #[serde(alias = "Include", default)]
16 pub name: String,
17
18 #[serde(alias = "Version", default)]
19 pub version: String,
20}
21
22#[derive(Debug, Deserialize, PartialEq)]
23struct ItemGroup {
24 #[serde(alias = "PackageReference", default)]
25 pub dependencies: Vec<PackageReference>,
26}
27
28#[derive(Debug, Deserialize, PartialEq)]
29struct Project {
30 #[serde(rename = "ItemGroup", default)]
31 pub item_groups: Vec<ItemGroup>,
32}
33
34#[derive(Debug, Clone, Deserialize)]
35#[serde(rename_all = "camelCase")]
36struct PackagesConfigItem {
37 id: String,
38 version: String,
39 target_framework: String,
40 development_dependency: Option<String>,
41}
42
43#[derive(Debug, Clone, Deserialize)]
44struct PackagesConfig {
45 package: Vec<PackagesConfigItem>,
46}
47
48fn parse(path: impl AsRef<Path>) -> Result<Package, SourcePkgError> {
49 let path = path.as_ref();
50
51 let mut deps = vec![];
52
53 let mut packages_config_path = path.to_owned();
55 packages_config_path.pop();
56 packages_config_path.push("packages.config");
57 if packages_config_path.exists() {
58 let content = std::fs::read_to_string(packages_config_path)?
59 .trim_start_matches(INVALID_CHAR)
60 .to_string();
61 let mut de =
62 Deserializer::new_from_reader(content.as_bytes()).non_contiguous_seq_elements(true);
63
64 match PackagesConfig::deserialize(&mut de) {
65 Ok(parsed) => {
66 for pkg in parsed.package {
67 let dep = DependentPackage {
68 purl: PackageUrl::new("nuget", pkg.id)
69 .expect("purl arguments are invalid")
70 .to_string(),
71 requirement: pkg.version,
72 scope: pkg.target_framework,
73 is_resolved: true,
74 is_runtime: if let Some(t) = pkg.development_dependency {
75 t == "false"
76 } else {
77 true
78 },
79 ..Default::default()
80 };
81 deps.push(dep);
82 }
83 }
84 Err(e) => {
85 log::warn!("Failed to parse packages.config: {}", e);
86 }
87 }
88 }
89
90 let content = std::fs::read_to_string(path)?
91 .trim_start_matches(INVALID_CHAR)
92 .to_string();
93
94 let mut de =
95 Deserializer::new_from_reader(content.as_bytes()).non_contiguous_seq_elements(true);
96 let parsed =
97 Project::deserialize(&mut de).map_err(|e| SourcePkgError::GenericsError2(e.to_string()))?;
98
99 for item_group in parsed.item_groups {
100 deps.extend(item_group.dependencies.into_iter().map(|dependency| {
101 let locked_version = match semver::Version::parse(&dependency.version) {
104 Ok(version) => version.to_string(),
105 Err(_) => "".into(),
106 };
107
108 DependentPackage {
109 purl: PackageUrl::new("nuget", dependency.name)
110 .expect("purl arguments are invalid")
111 .to_string(),
112 requirement: locked_version.clone(),
113 is_resolved: !locked_version.is_empty(),
114 ..Default::default()
115 }
116 }));
117 }
118
119 let package = Package {
120 dependencies: deps,
121 ..Default::default()
122 };
123
124 Ok(package)
125}
126
127pub struct CSharpCsproj {}
128
129impl CSharpCsproj {
130 pub fn new() -> Self {
131 Self {}
132 }
133}
134
135#[async_trait::async_trait]
136impl PackageManifest for CSharpCsproj {
137 fn get_name(&self) -> String {
138 "nuget".into()
139 }
140
141 async fn recognize(&self, path: &Path) -> Result<Package, SourcePkgError> {
142 parse(path)
143 }
144
145 fn file_name_patterns(&self) -> &'static [&'static str] {
146 &["*.csproj"]
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_csproj() {
156 let filepath = Path::new(concat!(
157 env!("CARGO_MANIFEST_DIR"),
158 "/testdata/csharp/ICSharpCode.Decompiler.csproj"
159 ));
160
161 let p = parse(filepath).unwrap();
162 println!("{:?}", p);
163 }
164
165 #[test]
166 fn test_csproj_with_packages() {
167 let filepath = Path::new(concat!(
168 env!("CARGO_MANIFEST_DIR"),
169 "/testdata/csharp_with_package/Snyk.Common.csproj"
170 ));
171
172 let p = parse(filepath).unwrap();
173 println!("{:?}", p);
174 }
175}