Class: Omnizip::Formats::Zip::Reader

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/omnizip/formats/zip/reader.rb

Overview

ZIP archive reader

Constant Summary

Constants included from Constants

Constants::ATTR_ARCHIVE, Constants::ATTR_DIRECTORY, Constants::CENTRAL_DIRECTORY_SIGNATURE, Constants::COMPRESSION_BZIP2, Constants::COMPRESSION_DEFLATE, Constants::COMPRESSION_DEFLATE64, Constants::COMPRESSION_IMPLODED, Constants::COMPRESSION_LZMA, Constants::COMPRESSION_PPMD, Constants::COMPRESSION_REDUCED_1, Constants::COMPRESSION_REDUCED_2, Constants::COMPRESSION_REDUCED_3, Constants::COMPRESSION_REDUCED_4, Constants::COMPRESSION_SHRUNK, Constants::COMPRESSION_STORE, Constants::COMPRESSION_ZSTANDARD, Constants::DATA_DESCRIPTOR_SIGNATURE, Constants::END_OF_CENTRAL_DIRECTORY_SIGNATURE, Constants::FLAG_DATA_DESCRIPTOR, Constants::FLAG_ENCRYPTED, Constants::FLAG_STRONG_ENCRYPTION, Constants::FLAG_UTF8, Constants::LOCAL_FILE_HEADER_SIGNATURE, Constants::MAX_COMMENT_LENGTH, Constants::UNIX_DIR_PERMISSIONS, Constants::UNIX_EXTRA_FIELD_TAG, Constants::UNIX_FILE_PERMISSIONS, Constants::UNIX_SYMLINK_PERMISSIONS, Constants::VERSION_BZIP2, Constants::VERSION_DEFAULT, Constants::VERSION_DEFLATE, Constants::VERSION_LZMA, Constants::VERSION_MADE_BY_UNIX, Constants::VERSION_MADE_BY_WINDOWS, Constants::VERSION_ZIP64, Constants::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE, Constants::ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE, Constants::ZIP64_EXTRA_FIELD_TAG, Constants::ZIP64_LIMIT

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file_path) ⇒ Reader

Returns a new instance of Reader.



14
15
16
17
18
# File 'lib/omnizip/formats/zip/reader.rb', line 14

def initialize(file_path)
  @file_path = file_path
  @entries = []
  @central_directory = []
end

Instance Attribute Details

#entriesObject (readonly)

Returns the value of attribute entries.



12
13
14
# File 'lib/omnizip/formats/zip/reader.rb', line 12

def entries
  @entries
end

#file_pathObject (readonly)

Returns the value of attribute file_path.



12
13
14
# File 'lib/omnizip/formats/zip/reader.rb', line 12

def file_path
  @file_path
end

Instance Method Details

#decompress(compressed_data, method, uncompressed_size) ⇒ String

Decompress compressed_data according to method. Public API for callers (e.g., ParallelExtractor) that hold raw compressed payload outside an entry stream.

Parameters:

  • compressed_data (String)

    Compressed bytes

  • method (Integer)

    ZIP compression method code

  • uncompressed_size (Integer)

    Expected uncompressed size

Returns:

  • (String)

    Decompressed bytes



160
161
162
# File 'lib/omnizip/formats/zip/reader.rb', line 160

def decompress(compressed_data, method, uncompressed_size)
  decompress_data(compressed_data, method, uncompressed_size)
end

#extract_all(output_dir, preserve_links: true, dereference_links: false) ⇒ Object

Extract all files to a directory



40
41
42
43
44
45
46
# File 'lib/omnizip/formats/zip/reader.rb', line 40

def extract_all(output_dir, preserve_links: true,
dereference_links: false)
  entries.each do |entry|
    extract_entry(entry, output_dir, preserve_links: preserve_links,
                                     dereference_links: dereference_links)
  end
end

#extract_entry(entry, output_dir, preserve_links: true, dereference_links: false) ⇒ Object

Extract a specific entry



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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/omnizip/formats/zip/reader.rb', line 49

def extract_entry(entry, output_dir, preserve_links: true,
dereference_links: false)
  output_path = File.join(output_dir, entry.filename)

  if entry.directory?
    FileUtils.mkdir_p(output_path)
  elsif preserve_links && !dereference_links && entry.symlink?
    extract_symlink(entry, output_dir)
  else
    FileUtils.mkdir_p(File.dirname(output_path))

    File.open(file_path, "rb") do |io|
      # Seek to local file header
      io.seek(entry.local_header_offset, ::IO::SEEK_SET)

      # Read fixed part of local file header (30 bytes)
      fixed_header = io.read(30)

      # Extract variable lengths from fixed header
      _signature, _version, _flags, _method, _time, _date, _crc32,
      _comp_size, _uncomp_size, filename_length, extra_length = fixed_header.unpack("VvvvvvVVVvv")

      # Read variable parts
      variable_data = io.read(filename_length + extra_length)

      # Parse complete local file header
      LocalFileHeader.from_binary(fixed_header + variable_data)

      # Now we're positioned right after the local file header, read compressed data
      compressed_data = io.read(entry.compressed_size)

      # Decompress data
      decompressed_data = decompress_data(
        compressed_data,
        entry.compression_method,
        entry.uncompressed_size,
      )

      # Verify CRC
      calculated_crc = Omnizip::Checksums::Crc32.new.tap do |c|
        c.update(decompressed_data)
      end.finalize
      if calculated_crc != entry.crc32
        raise Omnizip::ChecksumError,
              "CRC mismatch for #{entry.filename}"
      end

      # Write decompressed data
      File.binwrite(output_path, decompressed_data)

      # Set file permissions if Unix
      if entry.unix_permissions.positive?
        File.chmod(entry.unix_permissions & 0o777, output_path)
      end
    end
  end
end

Extract a symbolic link



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/omnizip/formats/zip/reader.rb', line 108

def extract_symlink(entry, output_dir)
  output_path = File.join(output_dir, entry.filename)

  unless LinkHandler.symlink_supported?
    warn "Warning: Symbolic links not supported on #{RUBY_PLATFORM}, extracting as regular file"
    extract_entry(entry, output_dir, preserve_links: false)
    return
  end

  target = entry.link_target
  unless target
    warn "Warning: No link target found for #{entry.filename}, skipping"
    return
  end

  FileUtils.mkdir_p(File.dirname(output_path))

  # Remove existing file/link if present
  FileUtils.rm_f(output_path) if File.exist?(output_path) || File.symlink?(output_path)

  LinkHandler.create_symlink(target, output_path)
end

#list_entries(show_links: false) ⇒ Object

List all entries in the archive



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/omnizip/formats/zip/reader.rb', line 132

def list_entries(show_links: false)
  entries.map do |entry|
    info = {
      filename: entry.filename,
      compressed_size: entry.compressed_size,
      uncompressed_size: entry.uncompressed_size,
      compression_method: compression_method_name(entry.compression_method),
      crc32: entry.crc32,
      directory: entry.directory?,
    }

    if show_links && entry.symlink?
      info[:symlink] = true
      info[:link_target] = entry.link_target
    end

    info
  end
end

#readObject

Read and parse the ZIP archive



21
22
23
24
25
26
# File 'lib/omnizip/formats/zip/reader.rb', line 21

def read
  File.open(file_path, "rb") do |io|
    read_from_io(io)
  end
  self
end

#read_from_io(io) ⇒ Object

Read from an IO object



29
30
31
32
33
34
35
36
37
# File 'lib/omnizip/formats/zip/reader.rb', line 29

def read_from_io(io)
  # Find and read End of Central Directory
  eocd = EndOfCentralDirectory.find_in_file(io)

  # Read Central Directory
  read_central_directory(io, eocd)

  self
end