defmodule Packmatic.Field.Local.FileHeader do @moduledoc """ Represents the Local File Header, which is emitted before the content of each file is incorporated into the Zip stream. ## Structure ### Local File Header Size | Content -------- | - 4 bytes | Signature 2 bytes | Version needed to extract 2 bytes | General Purpose Flag 2 bytes | Compression Method (0 = No Compression; 8 = Deflated) 2 bytes | Modification Time (DOS Format) 2 bytes | Modification Date (DOS Format) 4 bytes | Checksum (CRC-32; 0 since Data Descriptor is used) 4 bytes | Compressed Size (Bytes; 0 since Data Descriptor is used) 4 bytes | Original Size (Bytes; 0 since Data Descriptor is used) 2 bytes | File Path Length (Bytes) 2 bytes | Extra Fields Length (Bytes) Variable | File Path Variable | Extra Fields #### Notes 1. The General Purpose Flag has the following bits set. - Bit 3: Indicating a Streaming Archive; Data Descriptor is used, and the Local File Header has no Size or CRC information. - Bit 11: Language encoding flag, indicating that the Filename and Comment are both already in UTF-8. As per APPNOTE, the presence of this flag obviates the need to emit a separate Info-ZIP Unicode Path Extra Field. 2. The Checksum, Compressed Size and Original Size fields are set to 0, since when the Local File Header is written, no further data has been read and so this information is not available. When the file has been read fully, a Data Descriptor will be written, which contains relevant information. 3. The following Extra Field is emitted: - Extended Timestamp, see `Packmatic.Field.Shared.ExtendedTimestamp` """ @type t :: %__MODULE__{path: Path.t(), timestamp: DateTime.t()} defstruct path: nil, timestamp: nil end defimpl Packmatic.Field, for: Packmatic.Field.Local.FileHeader do import Packmatic.Field.Helpers alias Packmatic.Field def encode(%{timestamp: %DateTime{time_zone: "Etc/UTC"}} = target) do entry_timestamp = encode_timestamp(target) entry_extra_timestamp = encode_extended_timestamp(target) entry_extras = [entry_extra_timestamp] [ <<0x50, 0x4B, 0x03, 0x04>>, encode_16(45), encode_16(2056), encode_16(8), entry_timestamp, encode_32(0), encode_32(0), encode_32(0), encode_16(:erlang.iolist_size(target.path)), encode_16(:erlang.iolist_size(entry_extras)), target.path, entry_extras ] end defp encode_timestamp(target) do Field.encode(%Field.Shared.Timestamp{ timestamp: target.timestamp }) end defp encode_extended_timestamp(target) do Field.encode(%Field.Shared.ExtendedTimestamp{ timestamp: target.timestamp }) end end