use std::io::{SeekFrom, Write}; use crate::compression::CompressionMethod; use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField}; use crate::{cp437, general_purpose_flags, Encoding, Signature}; use androscalpel_serializer::{ReadSeek, Result, Serializable}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct LocalFileHeader { // signature: Signature(0x04034b50) pub version_needed_to_extract: u16, pub general_purpose_flags: u16, pub compression_method: CompressionMethod, pub last_mod_file_time: u16, pub last_mod_file_data: u16, pub crc_32: u32, pub compressed_size: u32, pub uncompressed_size: u32, // file_name_length: u16, // extra_field_length: u16, pub file_name: Vec, pub extra_field: Vec, /// Remaining bytes in the extra_fields that could not be parsed as ExtraField pub malformed_extra_field: Vec, pub decryption_header: Option, } impl Serializable for LocalFileHeader { fn serialize(&self, output: &mut dyn Write) -> Result<()> { Self::SIGNATURE.serialize(output)?; self.version_needed_to_extract.serialize(output)?; self.general_purpose_flags.serialize(output)?; self.compression_method.serialize(output)?; self.last_mod_file_time.serialize(output)?; self.last_mod_file_data.serialize(output)?; self.crc_32.serialize(output)?; self.compressed_size.serialize(output)?; self.uncompressed_size.serialize(output)?; (self.file_name.len() as u16).serialize(output)?; (self.extra_field.len() as u16).serialize(output)?; for c in &self.file_name { c.serialize(output)?; } for c in &self.extra_field { c.serialize(output)?; } for c in &self.malformed_extra_field { c.serialize(output)?; } if let Some(header) = &self.decryption_header { header.serialize(output)?; } Ok(()) } fn deserialize(input: &mut dyn ReadSeek) -> Result { let signature = Signature::deserialize(input)?; assert_eq!(signature, Self::SIGNATURE); // TODO let version_needed_to_extract = u16::deserialize(input)?; let general_purpose_flags = u16::deserialize(input)?; let compression_method = CompressionMethod::deserialize(input)?; let last_mod_file_time = u16::deserialize(input)?; let last_mod_file_data = u16::deserialize(input)?; let crc_32 = u32::deserialize(input)?; let compressed_size = u32::deserialize(input)?; let uncompressed_size = u32::deserialize(input)?; let file_name_length = u16::deserialize(input)?; let extra_field_length = u16::deserialize(input)?; let mut file_name = vec![]; for _ in 0..file_name_length { file_name.push(u8::deserialize(input)?); } let mut header = Self { version_needed_to_extract, general_purpose_flags, compression_method, last_mod_file_time, last_mod_file_data, crc_32, compressed_size, uncompressed_size, file_name, extra_field: vec![], malformed_extra_field: vec![], decryption_header: None, }; //let end_of_extra_field = input.stream_position().unwrap() + extra_field_length as u64; let extra_field_off = input.stream_position().unwrap(); let mut extra_size_read = 0; while extra_size_read < extra_field_length as usize { let field_off = input.stream_position().unwrap(); let field = ExtraField::deserialize(input); if let Err(err) = field { println!( "Failed to parsed extra field in {}: {err:?}", header.get_name() ); input.seek(SeekFrom::Start(field_off)).unwrap(); break; } else { let field = field.unwrap(); extra_size_read += field.size(); header.extra_field.push(field); } } let mut failed_last_extra_field = false; if extra_size_read > extra_field_length as usize { //println!("Failed to parse last extra field in {}", header.get_name()); failed_last_extra_field = true; let size = header.extra_field.pop().unwrap().size(); input.seek(SeekFrom::Current(-(size as i64))).unwrap(); } let mut extra_size_read = input.stream_position().unwrap() - extra_field_off; while extra_size_read < extra_field_length as u64 { header.malformed_extra_field.push(u8::deserialize(input)?); extra_size_read += 1; } // If it is not padding from zipalign if failed_last_extra_field && header.malformed_extra_field != vec![0] && header.malformed_extra_field != vec![0, 0] && header.malformed_extra_field != vec![0, 0, 0] { println!("Failed to parse last extra field in {}", header.get_name()); } //input.seek(SeekFrom::Start(end_of_extra_field)).unwrap(); for field in &mut header.extra_field { if let ExtraField::Generic(GenericExtraField { id: Zip64ExtraField::ID, data, }) = field { let original_size = uncompressed_size == u32::MAX; let compressed_size = compressed_size == u32::MAX; let offset_header = false; let disk_number = false; let zip64_filed = Zip64ExtraField::from_generic( &GenericExtraField { id: Zip64ExtraField::ID, data: data.clone(), }, original_size, compressed_size, offset_header, disk_number, ) .unwrap(); *field = ExtraField::Zip64(zip64_filed); } } if (header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED != 0) && (header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION != 0) { header.decryption_header = Some(DecryptionHeader::deserialize(input)?); } Ok(header) } fn size(&self) -> usize { Self::MIN_SIZE + self.file_name.len() + self.extra_field.iter().map(|f| f.size()).sum::() + self.malformed_extra_field.len() + self .decryption_header .as_ref() .map(|h| h.size()) .unwrap_or(0) } } impl LocalFileHeader { const SIGNATURE: Signature = Signature(0x04034b50); const MIN_SIZE: usize = 4 + 5 * 2 + 3 * 4 + 2 * 2; const MASK_UTF8_FILENAME: u16 = 1 << 11; pub fn get_name_encoding(&self) -> Encoding { if self.general_purpose_flags & Self::MASK_UTF8_FILENAME != 0 { Encoding::UTF8 } else { Encoding::CP437 } } pub fn get_name(&self) -> String { match self.get_name_encoding() { Encoding::UTF8 => std::str::from_utf8(&self.file_name).unwrap().into(), Encoding::CP437 => cp437::cp437_to_string(&self.file_name), } } pub fn get_uncompressed_size(&self) -> u64 { if self.uncompressed_size != u32::MAX { self.uncompressed_size as u64 } else if let Some(ExtraField::Zip64(Zip64ExtraField { original_size: Some(original_size), .. })) = self .extra_field .iter() .find(|f| matches!(f, ExtraField::Zip64(_))) { *original_size } else { self.uncompressed_size as u64 } } pub fn get_compressed_size(&self) -> u64 { if self.compressed_size != u32::MAX { self.compressed_size as u64 } else if let Some(ExtraField::Zip64(Zip64ExtraField { compressed_size: Some(compressed_size), .. })) = self .extra_field .iter() .find(|f| matches!(f, ExtraField::Zip64(_))) { *compressed_size } else { self.compressed_size as u64 } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct DecryptionHeader { // pub ivsize: u16, pub iv_data: Vec, // pub remaining_size: u32, // TODO: parse, not needed for now pub other_data: Vec, } impl Serializable for DecryptionHeader { fn serialize(&self, output: &mut dyn Write) -> Result<()> { (self.iv_data.len() as u16).serialize(output)?; for c in &self.iv_data { c.serialize(output)?; } (self.other_data.len() as u32).serialize(output)?; for c in &self.other_data { c.serialize(output)?; } Ok(()) } fn deserialize(input: &mut dyn ReadSeek) -> Result { let iv_size = u16::deserialize(input)?; let mut iv_data = vec![]; for _ in 0..iv_size { iv_data.push(u8::deserialize(input)?); } let remaining_size = u32::deserialize(input)?; let mut other_data = vec![]; for _ in 0..remaining_size { other_data.push(u8::deserialize(input)?); } Ok(Self { iv_data, other_data, }) } fn size(&self) -> usize { 2 + self.iv_data.len() + 4 + self.other_data.len() } }