Class: Omnizip::Formats::Xar::Header
- Inherits:
-
Object
- Object
- Omnizip::Formats::Xar::Header
- Includes:
- Constants
- Defined in:
- lib/omnizip/formats/xar/header.rb
Overview
XAR archive header parser and builder
The XAR header is 28 bytes (or 64 bytes for extended format):
- Bytes 0-3: Magic number (0x78617221 = "xar!")
- Bytes 4-5: Header size (little-endian)
- Bytes 6-7: Format version (little-endian)
- Bytes 8-15: TOC compressed size (big-endian uint64)
- Bytes 16-23: TOC uncompressed size (big-endian uint64)
- Bytes 24-27: Checksum algorithm (big-endian uint32)
- Bytes 28-63: (Optional) Checksum name for CKSUM_OTHER
Constant Summary
Constants included from Constants
Constants::CHECKSUM_ALGORITHMS, Constants::CHECKSUM_NAMES, Constants::CHECKSUM_SIZES, Constants::CKSUM_MD5, Constants::CKSUM_NONE, Constants::CKSUM_OTHER, Constants::CKSUM_SHA1, Constants::COMPRESSION_BZIP2, Constants::COMPRESSION_GZIP, Constants::COMPRESSION_LZMA, Constants::COMPRESSION_MIME_TYPES, Constants::COMPRESSION_NONE, Constants::COMPRESSION_XZ, Constants::DEFAULT_COMPRESSION, Constants::DEFAULT_COMPRESSION_LEVEL, Constants::DEFAULT_FILE_CHECKSUM, Constants::DEFAULT_TOC_CHECKSUM, Constants::HEADER_CKSUM_ALG_OFFSET, Constants::HEADER_MAGIC_OFFSET, Constants::HEADER_SIZE, Constants::HEADER_SIZE_EX, Constants::HEADER_SIZE_OFFSET, Constants::HEADER_TOC_COMPRESSED_OFFSET, Constants::HEADER_TOC_UNCOMPRESSED_OFFSET, Constants::HEADER_VERSION_OFFSET, Constants::MAGIC, Constants::MAGIC_BYTES, Constants::MAX_PATH_LENGTH, Constants::MIME_TYPE_TO_COMPRESSION, Constants::TOC_XML_DECLARATION, Constants::TYPE_BLOCK, Constants::TYPE_CHAR, Constants::TYPE_DIRECTORY, Constants::TYPE_FIFO, Constants::TYPE_FILE, Constants::TYPE_HARDLINK, Constants::TYPE_SOCKET, Constants::TYPE_SYMLINK, Constants::XAR_VERSION
Instance Attribute Summary collapse
-
#checksum_algorithm ⇒ Object
readonly
Returns the value of attribute checksum_algorithm.
-
#checksum_name ⇒ Object
readonly
Returns the value of attribute checksum_name.
-
#header_size ⇒ Object
readonly
Returns the value of attribute header_size.
-
#magic ⇒ Object
readonly
Returns the value of attribute magic.
-
#toc_compressed_size ⇒ Object
readonly
Returns the value of attribute toc_compressed_size.
-
#toc_uncompressed_size ⇒ Object
readonly
Returns the value of attribute toc_uncompressed_size.
-
#version ⇒ Object
readonly
Returns the value of attribute version.
Class Method Summary collapse
-
.parse(data) ⇒ Header
Parse header from binary data.
-
.read(file) ⇒ Header
Read header from file.
Instance Method Summary collapse
-
#checksum? ⇒ Boolean
Check if checksum is used.
-
#checksum_algorithm_name ⇒ String
Get checksum algorithm name.
-
#checksum_size ⇒ Integer
Get checksum size in bytes.
-
#initialize(magic: MAGIC, header_size: HEADER_SIZE, version: XAR_VERSION, toc_compressed_size: 0, toc_uncompressed_size: 0, checksum_algorithm: CKSUM_SHA1, checksum_name: nil) ⇒ Header
constructor
Initialize header.
-
#to_bytes ⇒ String
Serialize header to binary.
-
#update_toc_sizes(compressed, uncompressed) ⇒ Header
Update TOC sizes.
-
#valid? ⇒ Boolean
Check if header is valid.
-
#validate! ⇒ Boolean
Validate header.
Constructor Details
#initialize(magic: MAGIC, header_size: HEADER_SIZE, version: XAR_VERSION, toc_compressed_size: 0, toc_uncompressed_size: 0, checksum_algorithm: CKSUM_SHA1, checksum_name: nil) ⇒ Header
Initialize header
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/omnizip/formats/xar/header.rb', line 107 def initialize(magic: MAGIC, header_size: HEADER_SIZE, version: XAR_VERSION, toc_compressed_size: 0, toc_uncompressed_size: 0, checksum_algorithm: CKSUM_SHA1, checksum_name: nil) @magic = magic @header_size = checksum_algorithm == CKSUM_OTHER && checksum_name ? HEADER_SIZE_EX : header_size @version = version @toc_compressed_size = toc_compressed_size @toc_uncompressed_size = toc_uncompressed_size @checksum_algorithm = checksum_algorithm @checksum_name = checksum_name end |
Instance Attribute Details
#checksum_algorithm ⇒ Object (readonly)
Returns the value of attribute checksum_algorithm.
19 20 21 |
# File 'lib/omnizip/formats/xar/header.rb', line 19 def checksum_algorithm @checksum_algorithm end |
#checksum_name ⇒ Object (readonly)
Returns the value of attribute checksum_name.
19 20 21 |
# File 'lib/omnizip/formats/xar/header.rb', line 19 def checksum_name @checksum_name end |
#header_size ⇒ Object (readonly)
Returns the value of attribute header_size.
19 20 21 |
# File 'lib/omnizip/formats/xar/header.rb', line 19 def header_size @header_size end |
#magic ⇒ Object (readonly)
Returns the value of attribute magic.
19 20 21 |
# File 'lib/omnizip/formats/xar/header.rb', line 19 def magic @magic end |
#toc_compressed_size ⇒ Object (readonly)
Returns the value of attribute toc_compressed_size.
19 20 21 |
# File 'lib/omnizip/formats/xar/header.rb', line 19 def toc_compressed_size @toc_compressed_size end |
#toc_uncompressed_size ⇒ Object (readonly)
Returns the value of attribute toc_uncompressed_size.
19 20 21 |
# File 'lib/omnizip/formats/xar/header.rb', line 19 def toc_uncompressed_size @toc_uncompressed_size end |
#version ⇒ Object (readonly)
Returns the value of attribute version.
19 20 21 |
# File 'lib/omnizip/formats/xar/header.rb', line 19 def version @version end |
Class Method Details
.parse(data) ⇒ Header
Parse header from binary data
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/omnizip/formats/xar/header.rb', line 28 def self.parse(data) if data.bytesize < HEADER_SIZE raise ArgumentError, "Header data too short (#{data.bytesize} bytes)" end magic = data[0, 4].unpack1("N") # XAR spec: header_size and version are little-endian, rest are big-endian # However, some tools (like macOS xar) store these in big-endian format. # We detect this by checking if the parsed values make sense. header_size_le = data[4, 2].unpack1("v") # little-endian header_size_be = data[4, 2].unpack1("n") # big-endian version_le = data[6, 2].unpack1("v") # little-endian version_be = data[6, 2].unpack1("n") # big-endian # Detect endianness: standard header is 28 bytes, version is 1 # If little-endian gives valid values, use it; otherwise use big-endian if header_size_le == HEADER_SIZE && version_le == XAR_VERSION header_size = header_size_le version = version_le elsif header_size_be == HEADER_SIZE && version_be == XAR_VERSION header_size = header_size_be version = version_be else # Default to little-endian (spec-compliant) header_size = header_size_le version = version_le # Normalize version 256 to 1 (big-endian encoding of version 1) version = 1 if version == 256 end toc_compressed_size = data[8, 8].unpack1("Q>") # big-endian uint64 toc_uncompressed_size = data[16, 8].unpack1("Q>") # big-endian uint64 checksum_algorithm = data[24, 4].unpack1("N") # Parse checksum name for custom algorithms checksum_name = nil if checksum_algorithm == CKSUM_OTHER && data.bytesize >= HEADER_SIZE_EX checksum_name = data[28, 36].strip end new( magic: magic, header_size: header_size, version: version, toc_compressed_size: toc_compressed_size, toc_uncompressed_size: toc_uncompressed_size, checksum_algorithm: checksum_algorithm, checksum_name: checksum_name, ) end |
.read(file) ⇒ Header
Read header from file
83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/omnizip/formats/xar/header.rb', line 83 def self.read(file) data = file.read(HEADER_SIZE_EX) # Read max possible size raise ArgumentError, "Failed to read header" unless data # Parse to get actual header size header = parse(data) # Seek back if we read too much if data.bytesize > header.header_size file.seek(header.header_size, ::IO::SEEK_SET) end header end |
Instance Method Details
#checksum? ⇒ Boolean
Check if checksum is used
185 186 187 |
# File 'lib/omnizip/formats/xar/header.rb', line 185 def checksum? @checksum_algorithm != CKSUM_NONE end |
#checksum_algorithm_name ⇒ String
Get checksum algorithm name
167 168 169 170 171 172 173 |
# File 'lib/omnizip/formats/xar/header.rb', line 167 def checksum_algorithm_name if @checksum_algorithm == CKSUM_OTHER @checksum_name || "unknown" else CHECKSUM_NAMES[@checksum_algorithm] || "unknown" end end |
#checksum_size ⇒ Integer
Get checksum size in bytes
178 179 180 |
# File 'lib/omnizip/formats/xar/header.rb', line 178 def checksum_size CHECKSUM_SIZES[checksum_algorithm_name] || 0 end |
#to_bytes ⇒ String
Serialize header to binary
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/omnizip/formats/xar/header.rb', line 192 def to_bytes data = +"" data << [@magic].pack("N") # 4 bytes, big-endian data << [@header_size].pack("v") # 2 bytes, little-endian data << [@version].pack("v") # 2 bytes, little-endian data << [@toc_compressed_size].pack("Q>") # 8 bytes, big-endian data << [@toc_uncompressed_size].pack("Q>") # 8 bytes, big-endian data << [@checksum_algorithm].pack("N") # 4 bytes, big-endian # Add checksum name for custom algorithms if @checksum_algorithm == CKSUM_OTHER && @checksum_name name_bytes = @checksum_name.to_s.encode("ASCII").ljust(36, "\x00") data << name_bytes[0, 36] end data end |
#update_toc_sizes(compressed, uncompressed) ⇒ Header
Update TOC sizes
215 216 217 218 219 |
# File 'lib/omnizip/formats/xar/header.rb', line 215 def update_toc_sizes(compressed, uncompressed) @toc_compressed_size = compressed @toc_uncompressed_size = uncompressed self end |
#valid? ⇒ Boolean
Check if header is valid
158 159 160 161 162 |
# File 'lib/omnizip/formats/xar/header.rb', line 158 def valid? validate! rescue ArgumentError false end |
#validate! ⇒ Boolean
Validate header
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/omnizip/formats/xar/header.rb', line 127 def validate! unless @magic == MAGIC raise ArgumentError, format("Invalid magic: 0x%08x (expected 0x%08x)", @magic, MAGIC) end unless @header_size >= HEADER_SIZE raise ArgumentError, "Header size too small: #{@header_size}" end unless @version == XAR_VERSION raise ArgumentError, "Unsupported version: #{@version}" end unless [CKSUM_NONE, CKSUM_SHA1, CKSUM_MD5, CKSUM_OTHER].include?(@checksum_algorithm) raise ArgumentError, "Unknown checksum algorithm: #{@checksum_algorithm}" end if @checksum_algorithm == CKSUM_OTHER && @checksum_name.to_s.strip.empty? raise ArgumentError, "Custom checksum requires checksum_name" end true end |