zippo 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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