// ZIP extra fields support
// Extra fields provide extensible metadata for ZIP entries
// Extra field structure
///|
pub struct ExtraField {
header_id : Int // 2 bytes - identifies the field type
data_size : Int // 2 bytes - size of data
data : String // Variable length data
}
// Common extra field header IDs
///|
pub let zip64_extra_field_id : Int = 0x0001 // ZIP64 extended information
///|
pub let ntfs_extra_field_id : Int = 0x000a // NTFS timestamps
///|
pub let unix_extra_field_id : Int = 0x000d // Unix timestamps
///|
pub let info_zip_unix_id : Int = 0x5455 // Info-ZIP Unix timestamp
///|
pub let unicode_path_id : Int = 0x7075 // Unicode path extra field
///|
pub let unicode_comment_id : Int = 0x6375 // Unicode comment extra field
// Parse extra fields from binary data
///|
pub fn parse_extra_fields(data : String) -> Array[ExtraField] {
let fields : Array[ExtraField] = []
let mut offset = 0
while offset + 4 <= data.length() {
// Read header ID (2 bytes, little-endian)
let header_id = read_u16_le_extra(data, offset)
offset = offset + 2
// Read data size (2 bytes, little-endian)
let data_size = read_u16_le_extra(data, offset)
offset = offset + 2
// Read data
if offset + data_size <= data.length() {
let field_data = data.substring(start=offset, end=offset + data_size)
fields.push({ header_id, data_size, data: field_data })
offset = offset + data_size
} else {
// Truncated field, stop parsing
break
}
}
fields
}
// Serialize extra fields to binary data
///|
pub fn serialize_extra_fields(fields : Array[ExtraField]) -> String {
let mut result = ""
for i = 0; i < fields.length(); i = i + 1 {
let field = fields[i]
// Write header ID (2 bytes, little-endian)
result = result + write_u16_le_extra(field.header_id)
// Write data size (2 bytes, little-endian)
result = result + write_u16_le_extra(field.data.length())
// Write data
result = result + field.data
}
result
}
// Create Unix timestamp extra field (Info-ZIP format)
///|
pub fn create_unix_timestamp_field(
modification_time : Int,
access_time : Int?,
creation_time : Int?,
) -> ExtraField {
let mut data = ""
// Flags byte (which timestamps are present)
let mut flags = 1 // Modification time is always present
match access_time {
Some(_) => flags = flags | 2
None => ()
}
match creation_time {
Some(_) => flags = flags | 4
None => ()
}
data = data + flags.unsafe_to_char().to_string()
// Modification time (4 bytes, little-endian)
data = data + write_u32_le_extra(modification_time)
// Access time (if present)
match access_time {
Some(time) => data = data + write_u32_le_extra(time)
None => ()
}
// Creation time (if present)
match creation_time {
Some(time) => data = data + write_u32_le_extra(time)
None => ()
}
{ header_id: info_zip_unix_id, data_size: data.length(), data }
}
// Parse Unix timestamp extra field
///|
pub fn parse_unix_timestamp_field(field : ExtraField) -> (Int, Int?, Int?)? {
if field.header_id != info_zip_unix_id || field.data.length() < 5 {
return None
}
let flags = field.data[0]
let mut offset = 1
// Modification time (always present)
if offset + 4 > field.data.length() {
return None
}
let modification_time = read_u32_le_extra(field.data, offset)
offset = offset + 4
// Access time (if flag bit 1 is set)
let access_time = if (flags & 2) != 0 {
if offset + 4 > field.data.length() {
None
} else {
let time = read_u32_le_extra(field.data, offset)
offset = offset + 4
Some(time)
}
} else {
None
}
// Creation time (if flag bit 2 is set)
let creation_time = if (flags & 4) != 0 {
if offset + 4 > field.data.length() {
None
} else {
let time = read_u32_le_extra(field.data, offset)
Some(time)
}
} else {
None
}
Some((modification_time, access_time, creation_time))
}
// Create Unicode path extra field
///|
pub fn create_unicode_path_field(
unicode_path : String,
crc32 : Int,
) -> ExtraField {
let mut data = ""
// Version (1 byte)
data = data + "\u{01}"
// CRC-32 of original path (4 bytes, little-endian)
data = data + write_u32_le_extra(crc32)
// Unicode path (UTF-8)
data = data + unicode_path
{ header_id: unicode_path_id, data_size: data.length(), data }
}
// Parse Unicode path extra field
///|
pub fn parse_unicode_path_field(field : ExtraField) -> (String, Int)? {
if field.header_id != unicode_path_id || field.data.length() < 6 {
return None
}
let version = field.data[0]
if version != 1 {
return None // Unsupported version
}
let crc32 = read_u32_le_extra(field.data, 1)
let unicode_path = field.data.substring(start=5, end=field.data.length())
Some((unicode_path, crc32))
}
// Find extra field by header ID
///|
pub fn find_extra_field(
fields : Array[ExtraField],
header_id : Int,
) -> ExtraField? {
for i = 0; i < fields.length(); i = i + 1 {
let field = fields[i]
if field.header_id == header_id {
return Some(field)
}
}
None
}
// Remove extra field by header ID
///|
pub fn remove_extra_field(
fields : Array[ExtraField],
header_id : Int,
) -> Array[ExtraField] {
let result : Array[ExtraField] = []
for i = 0; i < fields.length(); i = i + 1 {
let field = fields[i]
if field.header_id != header_id {
result.push(field)
}
}
result
}
// Add or replace extra field
///|
pub fn set_extra_field(
fields : Array[ExtraField],
new_field : ExtraField,
) -> Array[ExtraField] {
let result = remove_extra_field(fields, new_field.header_id)
result.push(new_field)
result
}
// Calculate total size of extra fields
///|
pub fn extra_fields_size(fields : Array[ExtraField]) -> Int {
let mut total = 0
for i = 0; i < fields.length(); i = i + 1 {
let field = fields[i]
total = total + 4 + field.data.length() // 4 bytes header + data
}
total
}
// Validate extra field
///|
pub fn validate_extra_field(field : ExtraField) -> Bool {
// Check that data size matches actual data length
field.data_size == field.data.length() &&
// Check reasonable size limits
field.data.length() <= 65535 &&
// Header ID should be non-zero
field.header_id > 0
}
// Helper functions for binary I/O (extra fields version to avoid conflicts)
///|
pub fn read_u16_le_extra(data : String, offset : Int) -> Int {
if offset + 1 >= data.length() {
return 0
}
let b0 = data[offset]
let b1 = data[offset + 1]
b0 + (b1 << 8)
}
///|
pub fn write_u16_le_extra(value : Int) -> String {
let b0 = value & 0xff
let b1 = (value >> 8) & 0xff
b0.unsafe_to_char().to_string() + b1.unsafe_to_char().to_string()
}
///|
pub fn read_u32_le_extra(data : String, offset : Int) -> Int {
if offset + 3 >= data.length() {
return 0
}
let b0 = data[offset]
let b1 = data[offset + 1]
let b2 = data[offset + 2]
let b3 = data[offset + 3]
b0 + (b1 << 8) + (b2 << 16) + (b3 << 24)
}
///|
pub fn write_u32_le_extra(value : Int) -> String {
let b0 = value & 0xff
let b1 = (value >> 8) & 0xff
let b2 = (value >> 16) & 0xff
let b3 = (value >> 24) & 0xff
b0.unsafe_to_char().to_string() +
b1.unsafe_to_char().to_string() +
b2.unsafe_to_char().to_string() +
b3.unsafe_to_char().to_string()
}