zippo 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +22 -0
  7. data/README.md +83 -0
  8. data/Rakefile +11 -0
  9. data/lib/zippo/binary_structure/base.rb +119 -0
  10. data/lib/zippo/binary_structure/binary_packer.rb +17 -0
  11. data/lib/zippo/binary_structure/binary_unpacker.rb +32 -0
  12. data/lib/zippo/binary_structure/meta.rb +146 -0
  13. data/lib/zippo/binary_structure/structure.rb +24 -0
  14. data/lib/zippo/binary_structure/structure_member.rb +31 -0
  15. data/lib/zippo/binary_structure.rb +6 -0
  16. data/lib/zippo/cd_file_header.rb +36 -0
  17. data/lib/zippo/central_directory_entries_unpacker.rb +23 -0
  18. data/lib/zippo/central_directory_reader.rb +44 -0
  19. data/lib/zippo/end_cd_record.rb +21 -0
  20. data/lib/zippo/filter/base.rb +29 -0
  21. data/lib/zippo/filter/compressor/deflate.rb +23 -0
  22. data/lib/zippo/filter/compressor/store.rb +12 -0
  23. data/lib/zippo/filter/compressor.rb +42 -0
  24. data/lib/zippo/filter/compressors.rb +3 -0
  25. data/lib/zippo/filter/null_filters.rb +15 -0
  26. data/lib/zippo/filter/uncompressor/deflate.rb +25 -0
  27. data/lib/zippo/filter/uncompressor/store.rb +12 -0
  28. data/lib/zippo/filter/uncompressor.rb +59 -0
  29. data/lib/zippo/filter/uncompressors.rb +3 -0
  30. data/lib/zippo/io_zip_member.rb +24 -0
  31. data/lib/zippo/local_file_header.rb +28 -0
  32. data/lib/zippo/version.rb +3 -0
  33. data/lib/zippo/zip_directory.rb +80 -0
  34. data/lib/zippo/zip_file.rb +121 -0
  35. data/lib/zippo/zip_file_writer.rb +57 -0
  36. data/lib/zippo/zip_member.rb +85 -0
  37. data/lib/zippo.rb +18 -0
  38. data/spec/binary_structure_spec.rb +132 -0
  39. data/spec/central_directory_entries_unpacker_spec.rb +29 -0
  40. data/spec/central_directory_parser_spec.rb +50 -0
  41. data/spec/central_directory_unpacker_spec.rb +31 -0
  42. data/spec/compressor_spec.rb +14 -0
  43. data/spec/data/comment.zip +0 -0
  44. data/spec/data/deflate.zip +0 -0
  45. data/spec/data/multi.zip +0 -0
  46. data/spec/data/not_a.zip +1 -0
  47. data/spec/data/test.zip +0 -0
  48. data/spec/deflate_compressor_spec.rb +21 -0
  49. data/spec/deflate_uncompressor_spec.rb +23 -0
  50. data/spec/integration/compressors_spec.rb +21 -0
  51. data/spec/integration/zippo_spec.rb +55 -0
  52. data/spec/io_zip_member_spec.rb +32 -0
  53. data/spec/local_file_header_spec.rb +18 -0
  54. data/spec/spec_helper.rb +12 -0
  55. data/spec/store_compressor_spec.rb +19 -0
  56. data/spec/store_uncompressor_spec.rb +19 -0
  57. data/spec/uncompressor_spec.rb +14 -0
  58. data/spec/zip_directory_spec.rb +63 -0
  59. data/spec/zip_file_spec.rb +50 -0
  60. data/spec/zip_file_writer_spec.rb +42 -0
  61. data/spec/zip_member_spec.rb +42 -0
  62. data/yard_extensions.rb +10 -0
  63. data/zippo.gemspec +23 -0
  64. metadata +163 -0
@@ -0,0 +1,29 @@
1
+ module Zippo
2
+ # Filters used to compress and decompress archive data.
3
+ module Filter
4
+ # Generic filter mixin.
5
+ module Base
6
+ # Use same block size as rubyzip for better comparison
7
+ BLOCK_SIZE = 131072
8
+ module ClassMethods
9
+ def filters
10
+ @filters_hash ||= Hash[@filters.map{|u| [u::METHOD, u]}]
11
+ end
12
+ def for(method)
13
+ filters[method] or raise "unknown compression method #{method}"
14
+ end
15
+
16
+ def inherited(klass)
17
+ @filters_hash = nil
18
+ @filters << klass
19
+ end
20
+ end
21
+ def self.included(base)
22
+ base.class_eval do
23
+ @filters = []
24
+ extend ClassMethods
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ require 'zippo/filter/compressor'
2
+
3
+ module Zippo::Filter
4
+ # Compresses the input data using zlib.
5
+ class DeflateCompressor < Compressor
6
+ METHOD = 8
7
+ DEFAULT_COMPRESSION = Zlib::DEFAULT_COMPRESSION
8
+
9
+ def initialize(io, compression_mode = DEFAULT_COMPRESSION)
10
+ super(io)
11
+ @zlib = Zlib::Deflate.new(compression_mode, -Zlib::MAX_WBITS)
12
+ end
13
+
14
+ private
15
+ def filter(buf)
16
+ @zlib.deflate(buf)
17
+ end
18
+
19
+ def tail_filter
20
+ @zlib.finish unless @zlib.finished?
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ require 'zippo/filter/compressor'
2
+ require 'zippo/filter/null_filters'
3
+
4
+ module Zippo::Filter
5
+ # StoreCompressor stores the original member data directly in the zip
6
+ # file. No compression is performed.
7
+ class StoreCompressor < Compressor
8
+ METHOD = 0
9
+
10
+ include NullFilters
11
+ end
12
+ end
@@ -0,0 +1,42 @@
1
+ require 'zippo/filter/base'
2
+ require 'zlib'
3
+
4
+ module Zippo::Filter
5
+ # A compressor is responsible for writing (and likely
6
+ # compressing) data into a zip file.
7
+ #
8
+ # @example Obtaining a compressor
9
+ # compression_method = 8
10
+ # compressor = Compressor.for(compression_method).new(io)
11
+ # compressor.compress_to out
12
+ #
13
+ # Subclasse should include a METHOD constant to indicate
14
+ # which zip compression method they handle. The actual
15
+ # compression should be implemented in the filter and
16
+ # tail_filter methods.
17
+ class Compressor
18
+ include Zippo::Filter::Base
19
+
20
+ def initialize(io)
21
+ @io = io
22
+ end
23
+
24
+ def read count = nil, buf = nil
25
+ @io.read count, buf
26
+ end
27
+
28
+ def compress_to io
29
+ data_size = 0
30
+ compressed_size = 0
31
+ crc32 = 0
32
+ buf = ""
33
+ while (read BLOCK_SIZE, buf)
34
+ data_size += buf.bytesize
35
+ compressed_size += io.write filter(buf)
36
+ crc32 = Zlib.crc32(buf, crc32)
37
+ end
38
+ compressed_size += io.write tail_filter
39
+ return compressed_size, data_size, crc32
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ Dir[File.join(File.dirname(__FILE__), 'compressor/*.rb')].each do |f|
2
+ require "zippo/filter/compressor/#{File.basename(f, '.rb')}"
3
+ end
@@ -0,0 +1,15 @@
1
+ module Zippo
2
+ module Filter
3
+ # Mixin for a no-op filter.
4
+ module NullFilters
5
+ protected
6
+ def filter(buf)
7
+ buf
8
+ end
9
+
10
+ def tail_filter
11
+ ""
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ require 'zippo/filter/uncompressor'
2
+
3
+ require 'zlib'
4
+ require 'stringio'
5
+
6
+ module Zippo::Filter
7
+ # Uncompresses the data using Zlib.
8
+ class DeflateUncompressor < Uncompressor
9
+ METHOD = 8
10
+
11
+ def initialize(io, compressed_size, zlib = Zlib::Inflate.new(-Zlib::MAX_WBITS)) # no header
12
+ super(io, compressed_size)
13
+ @zlib = zlib
14
+ end
15
+
16
+ private
17
+ def filter(buf)
18
+ @zlib.inflate(buf)
19
+ end
20
+
21
+ def tail_filter
22
+ @zlib.finish
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ require 'zippo/filter/uncompressor'
2
+ require 'zippo/filter/null_filters'
3
+
4
+ module Zippo::Filter
5
+ # StoreUncompressor returns the member data as stored in the zip file.
6
+ # No uncompression is performed.
7
+ class StoreUncompressor < Uncompressor
8
+ METHOD = 0
9
+
10
+ include NullFilters
11
+ end
12
+ end
@@ -0,0 +1,59 @@
1
+ require 'zippo/filter/base'
2
+
3
+ module Zippo::Filter
4
+ # An Uncompressor is responsible for reading (and likely
5
+ # uncompressing) member data from a Zip file.
6
+ #
7
+ # @example Obtaining an uncompressor
8
+ # compression_method = 8
9
+ # uncompressor = Uncompressor.for(compression_method).new(io)
10
+ # uncompressor.uncompress_to out
11
+ #
12
+ # Subclasse should include a METHOD constant to indicate
13
+ # which zip compression method they handle. The actual
14
+ # uncompression should be implemented in the filter and
15
+ # tail_filter methods.
16
+ class Uncompressor
17
+ include Zippo::Filter::Base
18
+
19
+ # @param [IO] io the IO the member data is located in
20
+ # @param [Integer] compressed_size the compressed size of the data
21
+ def initialize(io, compressed_size)
22
+ # XXX should probably capture the offset here
23
+ # current usage assumes the IO has already been positioned at
24
+ # the appropriate location
25
+ @io = io
26
+ @compressed_size = compressed_size
27
+ @remaining = @compressed_size
28
+ end
29
+
30
+ def read n, buf = nil
31
+ if @remaining >= n
32
+ @remaining -= n
33
+ elsif (n = @remaining) > 0
34
+ @remaining = 0
35
+ else
36
+ return nil
37
+ end
38
+
39
+ @io.read n, buf
40
+ buf.replace filter(buf)
41
+ end
42
+
43
+ # Uncompresses the data to the specified IO
44
+ #
45
+ # @param [IO] io the object to uncompress to, must respond to #<<
46
+ def uncompress_to io
47
+ buf = ""
48
+ while (read BLOCK_SIZE, buf)
49
+ io << buf
50
+ end
51
+ io << tail_filter
52
+ end
53
+
54
+ # @return [String] the uncompressed data
55
+ def uncompress
56
+ uncompress_to ""
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ Dir[File.join(File.dirname(__FILE__), 'uncompressor/*.rb')].each do |f|
2
+ require "zippo/filter/uncompressor/#{File.basename(f, '.rb')}"
3
+ end
@@ -0,0 +1,24 @@
1
+ require 'zippo/filter/compressor'
2
+ require 'zippo/filter/compressor/deflate' # XXX only used for the default method
3
+
4
+ module Zippo
5
+ # A zip member sourced from an IO object
6
+ class IOZipMember
7
+ attr_reader :name
8
+
9
+ def initialize(name, source)
10
+ @name = name
11
+ @source = source
12
+ end
13
+
14
+ def read
15
+ @source.read
16
+ ensure
17
+ @source.rewind
18
+ end
19
+
20
+ def write_to out, preferred_compression_method = Filter::DeflateCompressor::METHOD, recompress = nil
21
+ Filter::Compressor.for(preferred_compression_method).new(@source).compress_to(out)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ require 'zippo/binary_structure'
2
+
3
+ module Zippo
4
+ # Represents a local file header as documented in APPNOTE.TXT
5
+ # @see http://www.pkware.com/documents/casestudies/APPNOTE.TXT
6
+ class LocalFileHeader
7
+ SIGNATURE = 0x04034b50
8
+ binary_structure do
9
+ # @!macro [attach] bs.field
10
+ # @!attribute [rw] $1
11
+ field :signature, 'L', :signature => SIGNATURE
12
+ field :version_extractable_by, 'S', :default => 20
13
+ field :bit_flags, 'S', :default => 0
14
+ field :compression_method, 'S'
15
+ field :last_modified_time, 'S', :default => 0 # XXX
16
+ field :last_modified_date, 'S', :default => 0 # XXX
17
+ field :crc32, 'L'
18
+ field :compressed_size, 'L'
19
+ field :uncompressed_size, 'L'
20
+ # set when name is set
21
+ field :file_name_length, 'S'
22
+ # set when extra_field is set
23
+ field :extra_field_length, 'S', :default => 0
24
+ field :name, 'a*', :size => :file_name_length
25
+ field :extra_field, 'a*', :default => '', :size => :extra_field_length
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Zippo
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,80 @@
1
+ require 'zippo/zip_member'
2
+ require 'zippo/io_zip_member'
3
+ require 'zippo/central_directory_reader'
4
+ require 'forwardable'
5
+
6
+ module Zippo
7
+ # The ZipDirectory is responsible for managing the set of ZipMembers
8
+ # belonging to a ZipFile.
9
+ class ZipDirectory
10
+ extend Forwardable
11
+ include Enumerable
12
+ # should delegate to entries_hash instead of entries whenever we can
13
+ def_delegators :entries_hash, :empty?
14
+ def_delegators :entries, :each, :map
15
+
16
+ def initialize io = nil
17
+ @io = io
18
+ end
19
+
20
+ # @param [String] name the name of the ZipMember
21
+ # @return [ZipMember] the ZipMember with the specified name
22
+ def [](name)
23
+ entries_hash[name]
24
+ end
25
+
26
+ # Inserts data into the ZipFile
27
+ #
28
+ # @param [String] name the name of the member to insert
29
+ # @param [String] data the data to insert
30
+ def []=(name, data)
31
+ insert(name, StringIO.new(data))
32
+ end
33
+
34
+ # Inserts data into the ZipFile
35
+ #
36
+ # - when the source is a ZipMember, the already-compressed data may
37
+ # be re-used when writing out
38
+ # - when the source is a String, it is interpreted as a filename,
39
+ # and will be used as the source of the data
40
+ # - otherwise, source is assumed to an already opened IO object
41
+ #
42
+ # @param [String] name the name of the member to insert
43
+ # @param source where to read the data from
44
+ #
45
+ # Source can be any of
46
+ # - an IO object
47
+ # - a string (path to file)
48
+ # - another ZipMember (allowing direct stream copy)
49
+ def insert(name, source)
50
+ set name,
51
+ case source
52
+ when ZipMember then source.with_name name
53
+ when String then IOZipMember.new name, File.open(source, 'r:BINARY')
54
+ else IOZipMember.new name, source
55
+ end
56
+ end
57
+
58
+ # @return [Hash] the hash of ZipMembers, the hash key is the
59
+ # member's name
60
+ def entries_hash
61
+ @entries_hash ||= if @io
62
+ CentralDirectoryReader.new(@io).cd_file_headers.each_with_object({}) do |header, hash|
63
+ hash[header.name] = ZipMember.new @io, header
64
+ end
65
+ else
66
+ {}
67
+ end
68
+ end
69
+
70
+ # @return [Array] the members of the ZipFile
71
+ def entries
72
+ entries_hash.values
73
+ end
74
+
75
+ private
76
+ def set(name, member)
77
+ entries_hash[name] = member
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,121 @@
1
+ require 'zippo/zip_directory'
2
+ require 'zippo/zip_file_writer'
3
+
4
+ require 'forwardable'
5
+
6
+ module Zippo
7
+ # ZipFile represents a Zip archive.
8
+ #
9
+ # It can be called in block form like this:
10
+ #
11
+ # Zippo.open("file.zip") do |zip|
12
+ # str = zip["file.txt"]
13
+ # other = zip["other/file.txt"]
14
+ # puts str
15
+ # end
16
+ #
17
+ # Or without a block:
18
+ #
19
+ # zip = Zippo.open("file.zip")
20
+ # puts zip["file.txt"]
21
+ # zip.close
22
+ #
23
+ # == Inserting archive members
24
+ #
25
+ # New archive members can be inserted using the insert method:
26
+ #
27
+ # zip = Zippo.open("out.zip", "w")
28
+ # zip.insert "out.txt", "something.txt"
29
+ #
30
+ # Additionally Zipfile#[]= has been overridden to support inserting
31
+ # strings directly:
32
+ #
33
+ # zip["other.txt"] = "now is the time"
34
+ #
35
+ # If you already have an IO object, you can just insert it:
36
+ #
37
+ # io = File.open("foo.bin")
38
+ # zip.insert "rename.bin", io
39
+ #
40
+ # Finally, you can insert a Zippo::ZipMember from another Zip file,
41
+ # which can allow the compressed data to be copied directly (avoiding
42
+ # having to uncompress and then recompress it):
43
+ #
44
+ # other = Zippo.open("other.zip")
45
+ # zip.insert "final.bin", other["final.bin"]
46
+ #
47
+ # No data will actually be written until the ZipFile is closed:
48
+ #
49
+ # zip.close
50
+ class ZipFile
51
+ # Opens a zip archive file
52
+ #
53
+ # @param [String] filename path to the archive
54
+ # @param [String] mode the mode to open in, 'r' or 'w' or 'rw'
55
+ #
56
+ # @return [Zippo::ZipFile] the opened zip file if no block is given
57
+ def self.open(filename, mode = 'r')
58
+ if block_given?
59
+ zippo = new(filename, mode)
60
+ a = yield zippo
61
+ zippo.close
62
+ return a
63
+ else
64
+ new filename, mode
65
+ end
66
+ end
67
+
68
+ def initialize(filename, mode)
69
+ @filename = filename
70
+ @mode = mode
71
+ end
72
+
73
+ extend Forwardable
74
+ def_delegators :directory, :map, :[], :[]=, :each, :insert
75
+
76
+ # Closes the ZipFile, writing it to disk if it was opened in write
77
+ # mode
78
+ def close
79
+ # XXX should optimize to not write anything to unchanged files
80
+ # In update mode, we first write the zip to a temporary zip file,
81
+ # then move it on top of the original file
82
+ out_zip = update? ? tmp_zipfile : @filename
83
+ ZipFileWriter.new(directory, out_zip).write if write?
84
+ @io.close if @io
85
+ File.rename out_zip, @filename if update?
86
+ end
87
+
88
+ # @return [ZipDirectory] the ZipDirectory
89
+ def directory
90
+ @directory ||= if read?
91
+ ZipDirectory.new io
92
+ else
93
+ ZipDirectory.new
94
+ end
95
+ end
96
+
97
+ private
98
+ def read?
99
+ File.exist? @filename and @mode.include? 'r'
100
+ end
101
+
102
+ def write?
103
+ @mode.include? 'w'
104
+ end
105
+
106
+ def update?
107
+ read? and write?
108
+ end
109
+
110
+ def io
111
+ @io ||= File.open(@filename, 'r:ASCII-8BIT')
112
+ end
113
+
114
+ def tmp_zipfile
115
+ # Not using Tempfile for performance
116
+ # Should probably throw a timestamp in there, in case multiple
117
+ # temps are being written at once from the one process
118
+ File.join File.dirname(@filename), ".zippo-tmp-#{Process.pid}-#{File.basename(@filename)}"
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,57 @@
1
+ module Zippo
2
+ # ZipFileWriter writes the contents of a ZipDirectory to a Zip file.
3
+ class ZipFileWriter
4
+ # @param [ZipDirectory] directory the ZipDirectory to write
5
+ # @param [String] filename the filename to write to
6
+ def initialize(directory, filename)
7
+ @directory = directory
8
+ @filename = filename
9
+ end
10
+
11
+ # Writes the directory to the file.
12
+ def write
13
+ File.open(@filename,'wb:ASCII-8BIT') do |io|
14
+ packer = LocalFileHeader::Packer.new io
15
+ headers = []
16
+ for member in @directory
17
+ header = CdFileHeader.default
18
+ header.compression_method = 8 # XXX configurable
19
+ # XXX hack fix for maintaining method in zipped data copies
20
+ if member.is_a? ZipMember
21
+ header.compression_method = member.compression_method
22
+ end
23
+ header.name = member.name
24
+ header.extra_field = "" # XXX extra field unimplemented
25
+ header_size = header.file_name_length + header.extra_field_length + 30
26
+
27
+ # record header position so we can write it later
28
+ header.local_file_header_offset = io.pos
29
+
30
+ # move to after the header
31
+ io.seek header_size, IO::SEEK_CUR
32
+
33
+ # write the compressed data
34
+ header.compressed_size, header.uncompressed_size, header.crc32 =
35
+ member.write_to io, header.compression_method
36
+
37
+ # write the completed header, returning to the current position
38
+ io.seek header.local_file_header_offset
39
+ #packer.pack LocalFileHeader.from header.convert_to LocalHileHeader
40
+ packer.pack header.convert_to LocalFileHeader
41
+ io.seek header.compressed_size, IO::SEEK_CUR
42
+ headers << header
43
+ end
44
+
45
+ eocdr = EndCdRecord.default
46
+ eocdr.cd_offset = io.pos
47
+ packer = CdFileHeader::Packer.new io
48
+ for header in headers
49
+ packer.pack header
50
+ end
51
+ eocdr.cd_size = io.pos - eocdr.cd_offset
52
+ eocdr.records = eocdr.total_records = headers.size
53
+ EndCdRecord::Packer.new(io).pack eocdr
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,85 @@
1
+ require 'zippo/local_file_header'
2
+ require 'zippo/filter/uncompressors'
3
+ require 'zippo/filter/compressors'
4
+
5
+ require 'forwardable'
6
+
7
+ module Zippo
8
+ # A member of a Zip archive file.
9
+ class ZipMember
10
+ def initialize io, header
11
+ @io = io
12
+ @header = header
13
+ end
14
+
15
+ # @return [String] the name of the member
16
+ def name
17
+ @name ||= @header.name
18
+ end
19
+
20
+ # @return [Boolean] True if the member is a directory, False
21
+ # otherwise
22
+ def directory?
23
+ name.end_with? '/'
24
+ end
25
+
26
+ extend Forwardable
27
+ def_delegators :@header, :crc32, :compressed_size, :uncompressed_size, :compression_method
28
+
29
+ # Reads (and possibly uncompresses) the member's data
30
+ #
31
+ # @return [String] the uncompressed member data
32
+ def read
33
+ seek_to_compressed_data
34
+ uncompressor.uncompress
35
+ end
36
+
37
+ # Duplicates this zip member and overrides the name.
38
+ #
39
+ # @param [String] name the name to use
40
+ # @return [ZipMember] the new ZipMember
41
+ def with_name name
42
+ dup.tap do |obj|
43
+ obj.instance_variable_set :@name, name
44
+ end
45
+ end
46
+
47
+ # Writes the member data to the specified IO using the specified
48
+ # compression method.
49
+ #
50
+ # @param [IO] out the IO to write to
51
+ # @param [Integer] preferred_method the compression method to use
52
+ # @param [Boolean] recompress whether or not to recompress the data
53
+ #
54
+ # @return [Integer, Integer, Integer] the amount written, the
55
+ # original size of the data, the crc32 of the data
56
+ def write_to out, preferred_method = Filter::DeflateCompressor::METHOD, recompress = false
57
+ seek_to_compressed_data
58
+ if recompress
59
+ Filter::Compressor.for(preferred_method).new(uncompressor).compress_to(out)
60
+ else
61
+ IO.copy_stream @io, out, @header.compressed_size
62
+ return @header.compressed_size, @header.uncompressed_size, @header.crc32
63
+ end
64
+ end
65
+
66
+ private
67
+ def local_file_header
68
+ @io.seek @header.local_file_header_offset
69
+ LocalFileHeader::Unpacker.new(@io).unpack
70
+ end
71
+
72
+ def seek_to_compressed_data
73
+ @io.seek @header.local_file_header_offset + local_file_header.size
74
+ end
75
+
76
+ def uncompressor
77
+ Filter::Uncompressor.for(@header.compression_method).new(@io, @header.compressed_size)
78
+ end
79
+
80
+ def compressed_member_data
81
+ seek_to_compressed_data
82
+ @io.read @header.compressed_size
83
+ end
84
+ end
85
+ end
data/lib/zippo.rb ADDED
@@ -0,0 +1,18 @@
1
+ # Ensure binary structure optimisations are active before any structures are defined
2
+ require 'zippo/binary_structure/meta'
3
+
4
+ require "zippo/version"
5
+ require 'zippo/zip_file'
6
+ require 'zippo/filter/uncompressors'
7
+
8
+ # Zippo is a Zip library.
9
+ module Zippo
10
+ class << self
11
+ # Calls Zippo::ZipFile.open
12
+ # @see ZipFile.open
13
+ def open filename, mode = 'r', &block
14
+ Zippo::ZipFile.open filename, mode, &block
15
+ end
16
+ public :open
17
+ end
18
+ end