use super::class_reader::ClassFile;
use crate::sector::SectorSource;
use crate::udf::UdfFs;
use std::io::{Cursor, Read};
use zip::ZipArchive;
const MAX_CLASS_BYTES: u64 = 64 * 1024 * 1024;
pub type Jar = ZipArchive<Cursor<Vec<u8>>>;
pub fn has_any_top_level_jar(udf: &UdfFs) -> bool {
let Some(jar_dir) = udf.find_dir("/BDMV/JAR") else {
return false;
};
jar_dir
.entries
.iter()
.any(|e| !e.is_dir && e.name.to_lowercase().ends_with(".jar"))
}
pub fn for_each_jar<R, F>(reader: &mut dyn SectorSource, udf: &UdfFs, mut f: F) -> Option<R>
where
F: FnMut(&str, &mut Jar) -> Option<R>,
{
let jar_dir = udf.find_dir("/BDMV/JAR")?;
for entry in &jar_dir.entries {
if entry.is_dir {
continue;
}
if !entry.name.to_lowercase().ends_with(".jar") {
continue;
}
let path = format!("/BDMV/JAR/{}", entry.name);
let Ok(bytes) = udf.read_file(reader, &path) else {
continue;
};
let Ok(mut archive) = ZipArchive::new(Cursor::new(bytes)) else {
continue;
};
if let Some(r) = f(&entry.name, &mut archive) {
return Some(r);
}
}
None
}
pub fn has_path_prefix(archive: &Jar, prefix: &str) -> bool {
archive.file_names().any(|n| n.starts_with(prefix))
}
pub fn for_each_class<F>(archive: &mut Jar, mut f: F)
where
F: FnMut(&str, &ClassFile),
{
try_each_class(archive, |name, class| {
f(name, class);
None::<()>
});
}
pub fn try_each_class<R, F>(archive: &mut Jar, mut f: F) -> Option<R>
where
F: FnMut(&str, &ClassFile) -> Option<R>,
{
for i in 0..archive.len() {
let Ok(entry) = archive.by_index(i) else {
continue;
};
if !entry.name().ends_with(".class") {
continue;
}
let name = entry.name().to_string();
let mut bytes = Vec::new();
if entry.take(MAX_CLASS_BYTES).read_to_end(&mut bytes).is_err() {
continue;
}
let Ok(class) = ClassFile::parse(&bytes) else {
continue;
};
if let Some(r) = f(&name, &class) {
return Some(r);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
const MINIMAL_CLASS: &[u8] = &[
0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
fn build_stored_zip(name: &str, payload: &[u8], declared_size: u32) -> Vec<u8> {
let name_bytes = name.as_bytes();
let crc: u32 = {
let mut crc = 0xFFFF_FFFFu32;
for &b in payload {
crc ^= b as u32;
for _ in 0..8 {
let mask = (crc & 1).wrapping_neg();
crc = (crc >> 1) ^ (0xEDB8_8320 & mask);
}
}
!crc
};
let mut out = Vec::new();
let lfh_offset = out.len() as u32;
out.extend_from_slice(&0x0403_4b50u32.to_le_bytes()); out.extend_from_slice(&20u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&crc.to_le_bytes()); out.extend_from_slice(&(payload.len() as u32).to_le_bytes()); out.extend_from_slice(&declared_size.to_le_bytes()); out.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(name_bytes);
out.extend_from_slice(payload);
let cd_offset = out.len() as u32;
out.extend_from_slice(&0x0201_4b50u32.to_le_bytes()); out.extend_from_slice(&20u16.to_le_bytes()); out.extend_from_slice(&20u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&(payload.len() as u32).to_le_bytes()); out.extend_from_slice(&declared_size.to_le_bytes()); out.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&lfh_offset.to_le_bytes()); out.extend_from_slice(name_bytes);
let cd_size = out.len() as u32 - cd_offset;
out.extend_from_slice(&0x0605_4b50u32.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&1u16.to_le_bytes()); out.extend_from_slice(&1u16.to_le_bytes()); out.extend_from_slice(&cd_size.to_le_bytes());
out.extend_from_slice(&cd_offset.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes()); out
}
fn open(bytes: Vec<u8>) -> Jar {
ZipArchive::new(Cursor::new(bytes)).expect("valid zip")
}
#[test]
fn try_each_class_reads_minimal_class() {
let mut jar = open(build_stored_zip(
"Foo.class",
MINIMAL_CLASS,
MINIMAL_CLASS.len() as u32,
));
let mut seen = Vec::new();
let r: Option<()> = try_each_class(&mut jar, |name, _class| {
seen.push(name.to_string());
None
});
assert!(r.is_none());
assert_eq!(seen, vec!["Foo.class".to_string()]);
}
#[test]
fn for_each_class_visits_every_class() {
let mut jar = open(build_stored_zip(
"Bar.class",
MINIMAL_CLASS,
MINIMAL_CLASS.len() as u32,
));
let mut count = 0usize;
for_each_class(&mut jar, |_, _| count += 1);
assert_eq!(count, 1);
}
#[test]
fn forged_huge_uncompressed_size_does_not_preallocate() {
let mut jar = open(build_stored_zip("Evil.class", MINIMAL_CLASS, 0xFFFF_FFFF));
let mut parsed = false;
for_each_class(&mut jar, |name, _class| {
assert_eq!(name, "Evil.class");
parsed = true;
});
assert!(parsed);
}
#[test]
fn read_is_bounded_by_cap() {
let mut payload = MINIMAL_CLASS.to_vec();
payload.extend(std::iter::repeat(0u8).take(4096));
let mut jar = open(build_stored_zip(
"Padded.class",
&payload,
0xFFFF_FFFF,
));
let mut visited = 0usize;
for_each_class(&mut jar, |_, _| visited += 1);
assert_eq!(visited, 1);
}
}