Skip to content

Commit

Permalink
Extra fields are present in the local file header too, neat!
Browse files Browse the repository at this point in the history
  • Loading branch information
fasterthanlime committed Feb 5, 2024
1 parent cd3a9a9 commit ac6319c
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 27 deletions.
12 changes: 9 additions & 3 deletions rc-zip-sync/src/entry_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use rc_zip::{
parse::StoredEntry,
};
use std::io;
use tracing::trace;

pub(crate) struct EntryReader<R>
where
Expand Down Expand Up @@ -35,26 +36,31 @@ where
};

if fsm.wants_read() {
tracing::trace!("fsm wants read");
trace!("fsm wants read");
let n = self.rd.read(fsm.space())?;
tracing::trace!("giving fsm {} bytes", n);
trace!("giving fsm {} bytes", n);
fsm.fill(n);
} else {
tracing::trace!("fsm does not want read");
trace!("fsm does not want read");
}

match fsm.process(buf)? {
FsmResult::Continue((fsm, outcome)) => {
self.fsm = Some(fsm);

if outcome.bytes_written > 0 {
Ok(outcome.bytes_written)
} else if outcome.bytes_read == 0 {
// that's EOF, baby!
Ok(0)
} else {
// loop, it happens
self.read(buf)
}
}
FsmResult::Done(_) => {
// neat!
trace!("fsm done");
Ok(0)
}
}
Expand Down
104 changes: 94 additions & 10 deletions rc-zip-sync/src/read_zip.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use rc_zip::chrono::{DateTime, TimeZone, Utc};
use rc_zip::{
error::Error,
error::{Error, FormatError},
fsm::{ArchiveFsm, FsmResult},
parse::{Archive, LocalFileHeaderRecord, StoredEntry},
parse::{
Archive, ExtraField, ExtraFieldSettings, LocalFileHeaderRecord, NtfsAttr, StoredEntry,
},
};
use tracing::trace;
use winnow::{
error::ErrMode,
stream::{AsBytes, Offset},
Expand Down Expand Up @@ -43,14 +47,14 @@ where
type File = F;

fn read_zip_with_size(&self, size: u64) -> Result<SyncArchive<'_, F>, Error> {
tracing::trace!(%size, "read_zip_with_size");
trace!(%size, "read_zip_with_size");
let mut fsm = ArchiveFsm::new(size);
loop {
if let Some(offset) = fsm.wants_read() {
tracing::trace!(%offset, "read_zip_with_size: wants_read, space len = {}", fsm.space().len());
trace!(%offset, "read_zip_with_size: wants_read, space len = {}", fsm.space().len());
match self.cursor_at(offset).read(fsm.space()) {
Ok(read_bytes) => {
tracing::trace!(%read_bytes, "read_zip_with_size: read");
trace!(%read_bytes, "read_zip_with_size: read");
if read_bytes == 0 {
return Err(Error::IO(std::io::ErrorKind::UnexpectedEof.into()));
}
Expand All @@ -62,7 +66,7 @@ where

fsm = match fsm.process()? {
FsmResult::Done(archive) => {
tracing::trace!("read_zip_with_size: done");
trace!("read_zip_with_size: done");
return Ok(SyncArchive {
file: self,
archive,
Expand Down Expand Up @@ -251,17 +255,97 @@ where

let header = loop {
let n = self.read(buf.space())?;
tracing::trace!("read {} bytes into buf for first zip entry", n);
trace!("read {} bytes into buf for first zip entry", n);
buf.fill(n);

let mut input = Partial::new(buf.data());
match LocalFileHeaderRecord::parser.parse_next(&mut input) {
Ok(header) => {
let consumed = input.as_bytes().offset_from(&buf.data());
tracing::trace!(?header, %consumed, "Got local file header record!");
trace!(?header, %consumed, "Got local file header record!");
// write extra bytes to `/tmp/extra.bin` for debugging
std::fs::write("/tmp/extra.bin", input.as_bytes()).unwrap();
tracing::trace!("wrote extra bytes to /tmp/extra.bin");
std::fs::write("/tmp/extra.bin", &header.extra.0).unwrap();
trace!("wrote extra bytes to /tmp/extra.bin");

let mut modified: Option<DateTime<Utc>> = None;
let mut created: Option<DateTime<Utc>> = None;
let mut accessed: Option<DateTime<Utc>> = None;

let mut compressed_size = header.compressed_size as u64;
let mut uncompressed_size = header.uncompressed_size as u64;

let mut uid: Option<u32> = None;
let mut gid: Option<u32> = None;

let mut extra_fields: Vec<ExtraField> = Vec::new();

let settings = ExtraFieldSettings {
needs_compressed_size: header.compressed_size == !0u32,
needs_uncompressed_size: header.uncompressed_size == !0u32,
needs_header_offset: false,
};

let mut slice = Partial::new(&header.extra.0[..]);
while !slice.is_empty() {
match ExtraField::mk_parser(settings).parse_next(&mut slice) {
Ok(ef) => {
match &ef {
ExtraField::Zip64(z64) => {
if let Some(n) = z64.uncompressed_size {
uncompressed_size = n;
}
if let Some(n) = z64.compressed_size {
compressed_size = n;
}
}
ExtraField::Timestamp(ts) => {
modified = Utc.timestamp_opt(ts.mtime as i64, 0).single();
}
ExtraField::Ntfs(nf) => {
for attr in &nf.attrs {
// note: other attributes are unsupported
if let NtfsAttr::Attr1(attr) = attr {
modified = attr.mtime.to_datetime();
created = attr.ctime.to_datetime();
accessed = attr.atime.to_datetime();
}
}
}
ExtraField::Unix(uf) => {
modified = Utc.timestamp_opt(uf.mtime as i64, 0).single();
if uid.is_none() {
uid = Some(uf.uid as u32);
}
if gid.is_none() {
gid = Some(uf.gid as u32);
}
}
ExtraField::NewUnix(uf) => {
uid = Some(uf.uid as u32);
gid = Some(uf.uid as u32);
}
_ => {}
};
extra_fields.push(ef);
}
Err(e) => {
trace!("extra field error: {:#?}", e);
return Err(FormatError::InvalidExtraField.into());
}
}
}

trace!(
?modified,
?created,
?accessed,
?compressed_size,
?uncompressed_size,
?uid,
?gid,
"parsed extra fields"
);

break header;
}
// TODO: keep reading if we don't have enough data
Expand Down
4 changes: 3 additions & 1 deletion rc-zip-sync/src/streaming_entry_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ where

if outcome.bytes_written > 0 {
Ok(outcome.bytes_written)
} else if outcome.bytes_read == 0 {
// that's EOF, baby!
Ok(0)
} else {
// loop, it happens
self.read(buf)
Expand All @@ -96,7 +99,6 @@ where
}

// FIXME: read the next local file header here

self.state = State::Finished { remain: fsm_remain };

// neat!
Expand Down
3 changes: 3 additions & 0 deletions rc-zip/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ pub mod parse;

#[cfg(any(test, feature = "corpus"))]
pub mod corpus;

// dependencies re-exports
pub use chrono;
33 changes: 20 additions & 13 deletions rc-zip/src/parse/extra_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,24 @@ impl<'a> ExtraFieldRecord<'a> {
}
}

// Useful because zip64 extended information extra field has fixed order *but*
// optional fields. From the appnote:
//
// If one of the size or offset fields in the Local or Central directory record
// is too small to hold the required data, a Zip64 extended information record
// is created. The order of the fields in the zip64 extended information record
// is fixed, but the fields MUST only appear if the corresponding Local or
// Central directory record field is set to 0xFFFF or 0xFFFFFFFF.
/// Useful because zip64 extended information extra field has fixed order *but*
/// optional fields. From the appnote:
///
/// If one of the size or offset fields in the Local or Central directory record
/// is too small to hold the required data, a Zip64 extended information record
/// is created. The order of the fields in the zip64 extended information record
/// is fixed, but the fields MUST only appear if the corresponding Local or
/// Central directory record field is set to 0xFFFF or 0xFFFFFFFF.
#[derive(Debug, Clone, Copy)]
pub(crate) struct ExtraFieldSettings {
pub(crate) needs_uncompressed_size: bool,
pub(crate) needs_compressed_size: bool,
pub(crate) needs_header_offset: bool,
pub struct ExtraFieldSettings {
/// Whether the "zip64 extra field" uncompressed size field is needed/present
pub needs_uncompressed_size: bool,

/// Whether the "zip64 extra field" compressed size field is needed/present
pub needs_compressed_size: bool,

/// Whether the "zip64 extra field" header offset field is needed/present
pub needs_header_offset: bool,
}

/// Information stored in the central directory header `extra` field
Expand Down Expand Up @@ -66,7 +71,9 @@ pub enum ExtraField {
}

impl ExtraField {
pub(crate) fn mk_parser(
/// Make a parser for extra fields, given the settings for the zip64 extra
/// field (which depend on whether the u32 values are 0xFFFF_FFFF or not)
pub fn mk_parser(
settings: ExtraFieldSettings,
) -> impl FnMut(&mut Partial<&'_ [u8]>) -> PResult<Self> {
move |i| {
Expand Down

0 comments on commit ac6319c

Please sign in to comment.