package_parser/pkgs/java/
maven.rs1use maplit::hashset;
2use packageurl::PackageUrl;
3use quick_xml::Reader;
4use crate::types::LockMavenParam;
5use crate::types::Relation;
6use serde::{Deserialize, Serialize};
7
8use crate::error::SourcePkgError;
9use crate::pkgs::common::model::{DependentPackage, Package, PackageManifest};
10
11use std::collections::HashMap;
12use std::io::BufRead;
13use std::path::Path;
14
15
16#[derive(Clone, Debug, Serialize, Deserialize)]
17#[serde(rename = "metadata")]
18pub struct Metadata {
19 #[serde(rename = "groupId")]
20 group_id: String,
21 #[serde(rename = "artifactId")]
22 artifact_id: String,
23 versioning: Versioning,
24}
25
26#[derive(Default, Debug, Clone, Serialize, Deserialize)]
27#[serde(rename = "versioning")]
28pub struct Versioning {
29 latest: String,
30 release: String,
31 versions: Versions,
32 #[serde(rename = "lastUpdated")]
33 last_updated: String,
34}
35
36#[derive(Default, Debug, Clone, Serialize, Deserialize)]
37#[serde(rename = "versions")]
38pub struct Versions {
39 #[serde(rename = "version", default)]
40 versions: Vec<String>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, Default)]
44#[serde(rename = "dependencies")]
45pub struct Dependencies {
46 #[serde(default)]
47 pub dependency: Vec<Dependency>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct Dependency {
53 pub group_id: String,
54 pub artifact_id: String,
55 pub version: Option<String>,
56 pub scope: Option<String>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60#[serde(rename_all = "camelCase")]
61pub struct Parent {
62 pub group_id: Option<String>,
63 pub artifact_id: Option<String>,
64 pub version: Option<String>,
65 pub relative_path: Option<String>,
66}
67
68#[derive(Default, Debug, Serialize, Deserialize)]
70#[serde(rename = "project", rename_all = "camelCase")]
71pub struct MavenPom {
72 group_id: Option<String>,
74 artifact_id: Option<String>,
75 version: Option<String>,
76
77 parent: Option<Parent>,
78 name: Option<String>,
79 #[serde(default)]
80 dependencies: Dependencies,
81}
82
83#[allow(dead_code)]
84#[derive(Debug, Deserialize)]
85struct ToolError {
86 problems: Vec<String>,
87}
88
89fn parse_properties<R: BufRead>(
90 reader: &mut Reader<R>,
91) -> Result<HashMap<String, String>, SourcePkgError> {
92 use quick_xml::events::Event;
93
94 let mut buffer = Vec::new();
95
96 let mut properties = HashMap::new();
97
98 let mut current_key = None;
99 let mut current_value = String::new();
100
101 loop {
102 match reader.read_event_into(&mut buffer)? {
103 Event::Start(element) => {
104 current_key = Some(String::from_utf8(element.name().as_ref().to_vec())?);
105 }
106 Event::Text(text) => {
107 let text = text.unescape()?;
108 current_value.push_str(&text);
109 }
110 Event::End(element) => {
111 let name = element.name();
112 match current_key.take() {
113 Some(key) => {
114 if key.as_bytes() == name.as_ref() {
115 properties
116 .insert(format!("${{{key}}}"), current_value.trim().to_string());
117 current_value.clear();
118 } else {
119 return Err(SourcePkgError::GenericsError(
120 "Failed to parse properties, tags mismatch",
121 ));
122 }
123 }
124 None => {
125 if name.as_ref() == b"properties" {
126 return Ok(properties);
127 } else {
128 return Err(SourcePkgError::GenericsError(
129 "Failed to parse properties, tags mismatch",
130 ));
131 }
132 }
133 }
134 }
135 _ => {}
136 }
137 buffer.clear();
139 }
140}
141
142#[allow(dead_code)]
143pub struct JavaMavenPom {
144}
145
146impl JavaMavenPom {
147 pub fn new() -> Self {
148 Self {}
149 }
150
151 async fn parse_fallback(
152 &self,
153 path: impl AsRef<Path>,
154 ) -> Result<Vec<LockMavenParam>, SourcePkgError> {
155 use quick_xml::events::Event;
156
157 let path = path.as_ref();
158 let file = tokio::fs::read(path).await?;
159 let file_str = crate::pkgs::common::decode_string(&file)?;
160
161 let mut reader = quick_xml::Reader::from_str(&file_str);
162 let mut buffer = Vec::new();
163
164 let mut properties = loop {
165 match reader.read_event_into(&mut buffer)? {
166 Event::Eof => break HashMap::new(),
167 Event::Start(e) => {
168 if e.name().as_ref() == b"properties" {
169 break parse_properties(&mut reader)?;
170 }
171 }
172 _ => {}
173 }
174 buffer.clear();
176 };
177
178 let root = quick_xml::de::from_str::<MavenPom>(&file_str)?;
179
180 if let Some(ref s) = root.artifact_id {
181 properties.insert("${project.artifactId}".to_string(), s.clone());
182 }
183 if let Some(ref s) = root.group_id {
184 properties.insert("${project.groupId}".to_string(), s.clone());
185 }
186
187 let process_properties = |version: &str| -> String {
188 if version.contains("${") {
189 let mut version = version.to_string();
190 for (key, value) in &properties {
191 version = version.replace(key, value);
192 }
193 version
194 } else {
195 version.to_string()
196 }
197 };
198
199 let requirements = root
200 .dependencies
201 .dependency
202 .into_iter()
203 .map(|dep| LockMavenParam {
204 group_id: process_properties(&dep.group_id),
205 artifact_id: process_properties(&dep.artifact_id),
206 version: process_properties(&dep.version.unwrap_or_default()),
207 scope: dep.scope,
208 is_optional: false,
209 })
210 .collect();
211
212 Ok(requirements)
213 }
214
215 async fn parse(&self, path: impl AsRef<Path>) -> Result<Package, SourcePkgError> {
216 let path = path.as_ref();
217
218 let dependencies = self.parse_fallback(path).await?;
219
220 fn convert_to_package(dep: LockMavenParam) -> DependentPackage {
221 let scope = dep.scope.unwrap_or_else(|| "compile".into());
222 let is_runtime = matches!(scope.as_str(), "compile" | "runtime" | "provided");
223
224 DependentPackage {
225 purl: PackageUrl::new("maven", dep.artifact_id)
226 .unwrap()
227 .with_namespace(dep.group_id)
228 .to_string(),
229 requirement: dep.version,
230 scope,
231 is_runtime,
232 is_optional: false,
233 is_resolved: false,
234 relation: hashset! {Relation::Direct},
235 ..Default::default()
236 }
237 }
238
239 let dependent_packages = dependencies
240 .into_iter()
241 .map(convert_to_package)
242 .collect();
243
244 let package = Package {
245 dependencies: dependent_packages,
246 ..Default::default()
247 };
248
249 Ok(package)
250 }
251}
252
253#[async_trait::async_trait]
254impl PackageManifest for JavaMavenPom {
255 fn get_name(&self) -> String {
256 "maven".into()
257 }
258
259 async fn recognize(&self, path: &Path) -> Result<Package, SourcePkgError> {
260 self.parse(path).await
261 }
262
263 fn file_name_patterns(&self) -> &'static [&'static str] {
264 &["pom.xml", "pom.xml", "*.pom"]
265 }
266}