use std::path::Path;
use super::coordinates::build_maven_purl;
use super::manifest::interpret_manifest_mf;
use super::properties::interpret_pom_properties;
use crate::models::{DatasourceId, PackageData, PackageType};
use crate::parser_warn as warn;
use crate::parsers::archive::{
ValidatedZipEntry, find_entries_by_suffix, find_entry_by_name, open_bounded_zip,
read_entry_to_string,
};
use crate::parsers::utils::truncate_field;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum JvmArchiveKind {
Jar,
War,
Aar,
}
impl JvmArchiveKind {
fn datasource_id(self) -> DatasourceId {
match self {
Self::Jar => DatasourceId::JavaJar,
Self::War => DatasourceId::JavaWarArchive,
Self::Aar => DatasourceId::AndroidAarLibrary,
}
}
fn package_type(self) -> PackageType {
match self {
Self::Jar => PackageType::Jar,
Self::War => PackageType::War,
Self::Aar => PackageType::AndroidLib,
}
}
}
fn bare_recognizer_row(kind: JvmArchiveKind) -> PackageData {
PackageData {
package_type: Some(kind.package_type()),
datasource_id: Some(kind.datasource_id()),
..Default::default()
}
}
pub fn extract_jvm_archive(path: &Path, kind: JvmArchiveKind) -> Vec<PackageData> {
let Some((mut archive, entries)) = open_bounded_zip(path) else {
return vec![bare_recognizer_row(kind)];
};
let manifest = read_named_entry(&mut archive, &entries, "META-INF/MANIFEST.MF", path);
let pom_properties = read_pom_properties(&mut archive, &entries, path);
if manifest.is_none() && pom_properties.is_none() {
return vec![bare_recognizer_row(kind)];
}
let mut package_data = match &manifest {
Some(content) => interpret_manifest_mf(content, path),
None => bare_recognizer_row(kind),
};
apply_pom_properties(&mut package_data, pom_properties.as_deref());
package_data.datasource_id = Some(kind.datasource_id());
if package_data.primary_language.is_none() {
package_data.primary_language = Some("Java".to_string());
}
let unidentified = package_data.name.is_none() && package_data.version.is_none();
if unidentified || package_data.package_type.is_none() {
package_data.package_type = Some(kind.package_type());
}
vec![package_data]
}
fn read_named_entry(
archive: &mut zip::ZipArchive<std::fs::File>,
entries: &[ValidatedZipEntry],
name: &str,
path: &Path,
) -> Option<String> {
let entry = find_entry_by_name(entries, name)?;
match read_entry_to_string(archive, entry, path) {
Ok(content) => Some(content),
Err(e) => {
warn!("Failed to read {} from {:?}: {}", name, path, e);
None
}
}
}
fn read_pom_properties(
archive: &mut zip::ZipArchive<std::fs::File>,
entries: &[ValidatedZipEntry],
path: &Path,
) -> Option<String> {
let mut candidates: Vec<&ValidatedZipEntry> =
find_entries_by_suffix(entries, "/pom.properties")
.into_iter()
.filter(|entry| entry.name().starts_with("META-INF/maven/"))
.collect();
candidates.sort_by(|a, b| {
let a_depth = a.name().matches('/').count();
let b_depth = b.name().matches('/').count();
a_depth.cmp(&b_depth).then_with(|| a.name().cmp(b.name()))
});
let entry = candidates.first()?;
match read_entry_to_string(archive, entry, path) {
Ok(content) => Some(content),
Err(e) => {
warn!("Failed to read {} from {:?}: {}", entry.name(), path, e);
None
}
}
}
fn apply_pom_properties(package_data: &mut PackageData, pom_properties: Option<&str>) {
let Some(content) = pom_properties else {
return;
};
let coords = interpret_pom_properties(content);
if let Some(group_id) = coords.group_id.as_ref() {
package_data.namespace = Some(truncate_field(group_id.clone()));
}
if let Some(artifact_id) = coords.artifact_id.as_ref() {
package_data.name = Some(truncate_field(artifact_id.clone()));
}
if let Some(version) = coords.version.as_ref() {
package_data.version = Some(truncate_field(version.clone()));
}
if let (Some(group_id), Some(artifact_id), Some(version)) =
(coords.group_id, coords.artifact_id, coords.version)
{
package_data.package_type = Some(PackageType::Maven);
package_data.purl = Some(truncate_field(build_maven_purl(
&group_id,
&artifact_id,
Some(&version),
None,
None,
)));
}
}