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.



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

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

Instance Attribute Details

#entriesObject (readonly)

Returns the value of attribute entries.



10
11
12
# File 'lib/omnizip/formats/zip/reader.rb', line 10

def entries
  @entries
end

#file_pathObject (readonly)

Returns the value of attribute file_path.



10
11
12
# File 'lib/omnizip/formats/zip/reader.rb', line 10

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



158
159
160
# File 'lib/omnizip/formats/zip/reader.rb', line 158

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



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

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



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
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
# File 'lib/omnizip/formats/zip/reader.rb', line 47

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



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

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



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

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



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

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



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

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