Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/znippy

plugins/native/znippy-plugin-maven/src/lib.rs

pub mod pom;
pub mod row;
#[cfg(feature = "resolve")]
pub mod resolver;

pub use row::{MAVEN_EXTENSIONS, MAVEN_SCHEMA, classifier_from_path, maven_fields};

#[cfg(feature = "host-decompressors")]
mod native;
#[cfg(feature = "host-decompressors")]
pub use native::NativeMavenPlugin;

use pom::{MavenCoord, parse_pom_project};

/// ZIP64 sentinel in a central-directory size field: the real size lives in the
/// entry's Zip64 extended-information extra field. The metadata entries this
/// scan targets (`META-INF/maven/**/pom.xml`) are never ≥ 4 GiB, so a sentinel
/// here means "not a pom we can serve" rather than a size we should guess.
const ZIP64_SIZE_SENTINEL: u32 = 0xFFFF_FFFF;

/// Minimal single-threaded JAR parser — no ljar dependency required.
/// Extracts Maven GAV from a JAR/WAR/EAR's embedded pom.xml, or parses a .pom directly.
pub fn extract_maven_metadata(path: &str, data: &[u8]) -> Option<MavenCoord> {
    if path.ends_with(".pom") {
        return parse_pom_project(data);
    }
    let pom_bytes = find_pom_in_jar(data)?;
    parse_pom_project(&pom_bytes)
}

fn find_pom_in_jar(data: &[u8]) -> Option<Vec<u8>> {
    let eocd = find_eocd(data)?;
    let entry_count = u16::from_le_bytes(data[eocd + 8..eocd + 10].try_into().ok()?) as usize;
    let cd_offset = u32::from_le_bytes(data[eocd + 16..eocd + 20].try_into().ok()?) as usize;

    let mut pos = cd_offset;
    for _ in 0..entry_count {
        if pos + 46 > data.len() {
            break;
        }
        if &data[pos..pos + 4] != b"PK\x01\x02" {
            break;
        }
        let method = u16::from_le_bytes(data[pos + 10..pos + 12].try_into().ok()?);
        let comp_size = u32::from_le_bytes(data[pos + 20..pos + 24].try_into().ok()?) as usize;
        let uncomp_size_raw = u32::from_le_bytes(data[pos + 24..pos + 28].try_into().ok()?);
        let name_len = u16::from_le_bytes(data[pos + 28..pos + 30].try_into().ok()?) as usize;
        let extra_len = u16::from_le_bytes(data[pos + 30..pos + 32].try_into().ok()?) as usize;
        let comment_len = u16::from_le_bytes(data[pos + 32..pos + 34].try_into().ok()?) as usize;
        let local_off = u32::from_le_bytes(data[pos + 42..pos + 46].try_into().ok()?) as usize;

        let name = data
            .get(pos + 46..pos + 46 + name_len)
            .and_then(|b| std::str::from_utf8(b).ok())
            .unwrap_or("");

        if name.starts_with("META-INF/maven/") && name.ends_with("/pom.xml") {
            if local_off + 30 > data.len() {
                return None;
            }
            if &data[local_off..local_off + 4] != b"PK\x03\x04" {
                return None;
            }
            let lname_len =
                u16::from_le_bytes(data[local_off + 26..local_off + 28].try_into().ok()?)
                    as usize;
            let lextra_len =
                u16::from_le_bytes(data[local_off + 28..local_off + 30].try_into().ok()?)
                    as usize;
            let data_start = local_off + 30 + lname_len + lextra_len;
            let raw = data.get(data_start..data_start + comp_size)?;
            return match method {
                0 => Some(raw.to_vec()),
                // DEFLATE — decoded by the house inflate (`linflate`), the same
                // engine `ljar` fans out over in the `host-decompressors` native
                // path. One decompressor for both paths (no third-party twin).
                // `linflate` needs an exact output size, which the central
                // directory carries; ZIP64 hides it in an extra field, so bail
                // rather than guess (see ZIP64_SIZE_SENTINEL).
                8 => {
                    if uncomp_size_raw == ZIP64_SIZE_SENTINEL {
                        return None;
                    }
                    linflate::inflate_to_vec(raw, uncomp_size_raw as usize).ok()
                }
                _ => None,
            };
        }

        pos += 46 + name_len + extra_len + comment_len;
    }
    None
}

fn find_eocd(data: &[u8]) -> Option<usize> {
    const MIN_EOCD: usize = 22;
    if data.len() < MIN_EOCD {
        return None;
    }
    let earliest = data.len().saturating_sub(MIN_EOCD + 65535);
    for i in (earliest..=data.len() - MIN_EOCD).rev() {
        if data[i..].starts_with(b"PK\x05\x06") {
            return Some(i);
        }
    }
    None
}