Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cargo_hack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ jobs:
with:
path: target
key: ${{ runner.os }}-rust-cargo-hack-${{ hashFiles('Cargo.toml') }}
- run: cargo hack test --feature-powerset --all-targets --exclude-features _bzip2_any,_arbitrary,_deflate-any,_all-features
- run: cargo hack test --feature-powerset --all-targets --exclude-features _bzip2_any,_arbitrary,_deflate-any
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ jiff-02 = ["dep:jiff"]
nt-time = ["dep:nt-time"]
lzma = ["dep:lzma-rust2"]
ppmd = ["dep:ppmd-rust"]
# This feature allows writing custom extra-data field IDs in file headers.
unreserved = []
xz = ["dep:lzma-rust2"]
_bzip2_any = []
Expand Down
116 changes: 115 additions & 1 deletion src/extra_fields/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! types for extra fields
//! Types for extra fields

/// marker trait to denote the place where this extra field has been stored
pub trait ExtraFieldVersion {}
Expand All @@ -20,6 +20,7 @@ mod extended_timestamp;
mod ntfs;
mod zipinfo_utf8;

// re-export
pub use extended_timestamp::*;
pub use ntfs::Ntfs;
pub use zipinfo_utf8::UnicodeExtraField;
Expand All @@ -33,3 +34,116 @@ pub enum ExtraField {
/// extended timestamp, as described in <https://libzip.org/specifications/extrafld.txt>
ExtendedTimestamp(ExtendedTimestamp),
}

/// Extra field used in this crate
#[repr(u16)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum UsedExtraField {
/// ZIP64 extended information extra field
Zip64ExtendedInfo = 0x0001,
/// NTFS
Ntfs = 0x000a,
/// extended timestamp
/// from https://libzip.org/specifications/extrafld.txt
ExtendedTimestamp = 0x5455,
/// Info-ZIP Unicode Comment Extra Field
UnicodeComment = 0x6375,
/// Info-ZIP Unicode Path Extra Field
UnicodePath = 0x7075,
/// AE-x encryption structure
AeXEncryption = 0x9901,
/// Data Stream Alignment (Apache Commons-Compress)
DataStreamAlignement = 0xa11e,
}

macro_rules! extra_field_match {
($x:expr, $( $variant:path ),+ $(,)?) => {
match $x {
$(
v if v == $variant as u16 => Ok($variant),
)+
_ => Err(()),
}
};
}

impl TryFrom<u16> for UsedExtraField {
type Error = ();

fn try_from(value: u16) -> Result<Self, Self::Error> {
extra_field_match!(
value,
UsedExtraField::Zip64ExtendedInfo,
UsedExtraField::Ntfs,
UsedExtraField::ExtendedTimestamp,
UsedExtraField::UnicodeComment,
UsedExtraField::UnicodePath,
UsedExtraField::DataStreamAlignement,
UsedExtraField::AeXEncryption,
)
}
}
// AE-x encryption structure

/// Known Extra fields (PKWARE and Third party) mappings
pub const EXTRA_FIELD_MAPPING: [u16; 58] = [
UsedExtraField::Zip64ExtendedInfo as u16,
0x0007, // AV Info
0x0008, // Reserved for extended language encoding data (PFS)
0x0009, // OS/2
UsedExtraField::Ntfs as u16,
0x000c, // OpenVMS
0x000d, // UNIX
0x000e, // Reserved for file stream and fork descriptors
0x000f, // Patch Descriptor
0x0014, // PKCS#7 Store for X.509 Certificates
0x0015, // X.509 Certificate ID and Signature for individual file
0x0016, // X.509 Certificate ID for Central Directory
0x0017, // Strong Encryption Header
0x0018, // Record Management Controls
0x0019, // PKCS#7 Encryption Recipient Certificate List
0x0020, // Reserved for Timestamp record
0x0021, // Policy Decryption Key Record
0x0022, // Smartcrypt Key Provider Record
0x0023, // Smartcrypt Policy Key Data Record
0x0065, // IBM S/390 (Z390), AS/400 (I400) attributes - uncompressed
0x0066, // Reserved for IBM S/390 (Z390), AS/400 (I400) attributes - compressed
0x4690, // POSZIP 4690 (reserved)
// Third party mappings commonly used
0x07c8, // Macintosh
0x1986, // Pixar USD header ID
0x2605, // ZipIt Macintosh
0x2705, // ZipIt Macintosh 1.3.5+
0x2805, // ZipIt Macintosh 1.3.5+
0x334d, // Info-ZIP Macintosh
0x4154, // Tandem
0x4341, // Acorn/SparkFS
0x4453, // Windows NT security descriptor (binary ACL)
0x4704, // VM/CMS
0x470f, // MVS
0x4854, // THEOS (old?)
0x4b46, // FWKCS MD5
0x4c41, // OS/2 access control list (text ACL)
0x4d49, // Info-ZIP OpenVMS
0x4d63, // Macintosh Smartzip (??)
0x4f4c, // Xceed original location extra field
0x5356, // AOS/VS (ACL)
0x554e, // Xceed unicode extra field
0x5855, // Info-ZIP UNIX (original, also OS/2, NT, etc)
UsedExtraField::UnicodeComment as u16,
0x6542, // BeOS/BeBox
0x6854, // THEOS
UsedExtraField::UnicodePath as u16,
0x7441, // AtheOS/Syllable
0x756e, // ASi UNIX
0x7855, // Info-ZIP UNIX (new)
0x7875, // Info-ZIP UNIX (newer UID/GID)
UsedExtraField::DataStreamAlignement as u16,
0xa220, // Microsoft Open Packaging Growth Hint
0xcafe, // Java JAR file Extra Field Header ID
0xd935, // Android ZIP Alignment Extra Field
0xe57a, // Korean ZIP code page info
0xfd4a, // SMS/QDOS
UsedExtraField::AeXEncryption as u16,
0x9902, // unknown
];
19 changes: 8 additions & 11 deletions src/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::cfg_if;
use crate::compression::{CompressionMethod, Decompressor};
use crate::cp437::FromCp437;
use crate::crc32::Crc32Reader;
use crate::extra_fields::{ExtendedTimestamp, ExtraField, Ntfs};
use crate::extra_fields::{ExtendedTimestamp, ExtraField, Ntfs, UsedExtraField};
use crate::result::{invalid, ZipError, ZipResult};
use crate::spec::{
self, CentralDirectoryEndInfo, DataAndPosition, FixedSizeBlock, Pod, ZIP64_BYTES_THR,
Expand Down Expand Up @@ -1573,9 +1573,9 @@ pub(crate) fn parse_single_extra_field<R: Read>(
}
Err(e) => return Err(e.into()),
};
match kind {
match UsedExtraField::try_from(kind) {
// Zip64 extended information extra field
0x0001 => {
Ok(UsedExtraField::Zip64ExtendedInfo) => {
if disallow_zip64 {
return Err(invalid!("Can't write a custom field using the ZIP64 ID"));
}
Expand Down Expand Up @@ -1622,12 +1622,12 @@ pub(crate) fn parse_single_extra_field<R: Read>(
}
return Ok(true);
}
0x000a => {
Ok(UsedExtraField::Ntfs) => {
// NTFS extra field
file.extra_fields
.push(ExtraField::Ntfs(Ntfs::try_from_reader(reader, len)?));
}
0x9901 => {
Ok(UsedExtraField::AeXEncryption) => {
// AES
if len != 7 {
return Err(ZipError::UnsupportedArchive(
Expand Down Expand Up @@ -1663,15 +1663,12 @@ pub(crate) fn parse_single_extra_field<R: Read>(
file.compression_method = compression_method;
file.aes_extra_data_start = bytes_already_read;
}
0x5455 => {
// extended timestamp
// https://libzip.org/specifications/extrafld.txt

Ok(UsedExtraField::ExtendedTimestamp) => {
file.extra_fields.push(ExtraField::ExtendedTimestamp(
ExtendedTimestamp::try_from_reader(reader, len)?,
));
}
0x6375 => {
Ok(UsedExtraField::UnicodeComment) => {
// Info-ZIP Unicode Comment Extra Field
// APPNOTE 4.6.8 and https://libzip.org/specifications/extrafld.txt
file.file_comment = String::from_utf8(
Expand All @@ -1681,7 +1678,7 @@ pub(crate) fn parse_single_extra_field<R: Read>(
)?
.into();
}
0x7075 => {
Ok(UsedExtraField::UnicodePath) => {
// Info-ZIP Unicode Path Extra Field
// APPNOTE 4.6.9 and https://libzip.org/specifications/extrafld.txt
file.file_name_raw = UnicodeExtraField::try_from_reader(reader, len)?
Expand Down
3 changes: 2 additions & 1 deletion src/spec.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![macro_use]

use crate::extra_fields::UsedExtraField;
use crate::read::magic_finder::{Backwards, Forward, MagicFinder, OptimisticMagicFinder};
use crate::read::ArchiveOffset;
use crate::result::{invalid, ZipError, ZipResult};
Expand Down Expand Up @@ -86,7 +87,7 @@ impl ExtraFieldMagic {
Self(u16::to_le(self.0))
}

pub const ZIP64_EXTRA_FIELD_TAG: Self = Self::literal(0x0001);
pub const ZIP64_EXTRA_FIELD_TAG: Self = Self::literal(UsedExtraField::Zip64ExtendedInfo as u16);
}

/// The file size at which a ZIP64 record becomes necessary.
Expand Down
41 changes: 22 additions & 19 deletions src/write.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Writing a ZIP archive

use crate::compression::CompressionMethod;
use crate::extra_fields::UsedExtraField;
use crate::read::{parse_single_extra_field, Config, ZipArchive, ZipFile};
use crate::result::{invalid, ZipError, ZipResult};
use crate::spec::{self, FixedSizeBlock, Zip32CDEBlock};
Expand Down Expand Up @@ -307,14 +308,20 @@ impl ExtendedFileOptions {
}
#[cfg(not(feature = "unreserved"))]
{
use crate::unstable::LittleEndianReadExt;
use crate::{
extra_fields::{UsedExtraField, EXTRA_FIELD_MAPPING},
unstable::LittleEndianReadExt,
};
let header_id = data.read_u16_le()?;
if EXTRA_FIELD_MAPPING.contains(&header_id) {
return Err(ZipError::Io(io::Error::other(
format!(
"Extra data header ID {header_id:#06} requires crate feature \"unreserved\"",
),
)));
// Some extra fields are authorized
if let Err(()) = UsedExtraField::try_from(header_id) {
if EXTRA_FIELD_MAPPING.contains(&header_id) {
return Err(ZipError::Io(io::Error::other(format!(
"Extra data header ID {:#06} (0x{:x}) \
requires crate feature \"unreserved\"",
header_id, header_id,
))));
}
}
data.seek(SeekFrom::Current(-2))?;
}
Expand Down Expand Up @@ -1013,7 +1020,11 @@ impl<W: Write + Seek> ZipWriter<W> {
body[4] = mode as u8; // strength
[body[5], body[6]] = underlying.serialize_to_u16().to_le_bytes(); // real compression method
aes_extra_data_start = extra_data.len() as u64;
ExtendedFileOptions::add_extra_data_unchecked(&mut extra_data, 0x9901, &body)?;
ExtendedFileOptions::add_extra_data_unchecked(
&mut extra_data,
UsedExtraField::AeXEncryption as u16,
&body,
)?;
}
let header_end =
header_start + size_of::<ZipLocalEntryBlock>() as u64 + name.to_string().len() as u64;
Expand All @@ -1038,7 +1049,7 @@ impl<W: Write + Seek> ZipWriter<W> {
[pad_body[0], pad_body[1]] = options.alignment.to_le_bytes();
ExtendedFileOptions::add_extra_data_unchecked(
&mut extra_data,
0xa11e,
UsedExtraField::DataStreamAlignement as u16,
&pad_body,
)?;
debug_assert_eq!((extra_data.len() as u64 + header_end) % align, 0);
Expand Down Expand Up @@ -2157,7 +2168,7 @@ fn update_aes_extra_data<W: Write + Seek>(

/* TODO: implement this using the Block trait! */
// Extra field header ID.
buf.write_u16_le(0x9901)?;
buf.write_u16_le(UsedExtraField::AeXEncryption as u16)?;
// Data size.
buf.write_u16_le(7)?;
// Integer version number.
Expand Down Expand Up @@ -2260,7 +2271,7 @@ fn strip_alignment_extra_field(extra_field: &[u8]) -> Vec<u8> {
break;
}

if tag != 0xa11e {
if tag != UsedExtraField::DataStreamAlignement as u16 {
new_extra.extend_from_slice(&extra_field[cursor..cursor + 4 + len]);
}
cursor += 4 + len;
Expand Down Expand Up @@ -2350,14 +2361,6 @@ impl<W: Write> Seek for StreamWriter<W> {
}
}

#[cfg(not(feature = "unreserved"))]
const EXTRA_FIELD_MAPPING: [u16; 43] = [
0x0007, 0x0008, 0x0009, 0x000a, 0x000c, 0x000d, 0x000e, 0x000f, 0x0014, 0x0015, 0x0016, 0x0017,
0x0018, 0x0019, 0x0020, 0x0021, 0x0022, 0x0023, 0x0065, 0x0066, 0x4690, 0x07c8, 0x2605, 0x2705,
0x2805, 0x334d, 0x4341, 0x4453, 0x4704, 0x470f, 0x4b46, 0x4c41, 0x4d49, 0x4f4c, 0x5356, 0x554e,
0x5855, 0x6542, 0x756e, 0x7855, 0xa220, 0xfd4a, 0x9902,
];

#[cfg(test)]
#[allow(unknown_lints)] // needless_update is new in clippy pre 1.29.0
#[allow(clippy::needless_update)] // So we can use the same FileOptions decls with and without zopfli_buffer_size
Expand Down
12 changes: 12 additions & 0 deletions tests/end_to_end.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,15 @@ const ENTRY_NAME: &str = "test/lorem_ipsum.txt";
const COPY_ENTRY_NAME: &str = "test/lorem_ipsum_renamed.txt";

const INTERNAL_COPY_ENTRY_NAME: &str = "test/lorem_ipsum_copied.txt";

#[test]
fn test_extra_field_access() {
// just a test to access the variable in the crate
use zip::extra_fields::EXTRA_FIELD_MAPPING;

assert_eq!(EXTRA_FIELD_MAPPING[0], 1);
assert_eq!(EXTRA_FIELD_MAPPING[0], 0x0001); // ZIP64 extended information extra field

assert_eq!(EXTRA_FIELD_MAPPING[12], 23);
assert_eq!(EXTRA_FIELD_MAPPING[12], 0x0017); // Strong Encryption Header
}