rubyzip 1.2.0 → 2.3.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 (102) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +89 -43
  3. data/Rakefile +3 -0
  4. data/lib/zip.rb +14 -3
  5. data/lib/zip/central_directory.rb +12 -8
  6. data/lib/zip/compressor.rb +1 -2
  7. data/lib/zip/constants.rb +55 -3
  8. data/lib/zip/crypto/decrypted_io.rb +40 -0
  9. data/lib/zip/crypto/null_encryption.rb +2 -4
  10. data/lib/zip/crypto/traditional_encryption.rb +9 -9
  11. data/lib/zip/decompressor.rb +20 -2
  12. data/lib/zip/dos_time.rb +13 -8
  13. data/lib/zip/entry.rb +115 -80
  14. data/lib/zip/entry_set.rb +6 -4
  15. data/lib/zip/errors.rb +2 -0
  16. data/lib/zip/extra_field.rb +12 -10
  17. data/lib/zip/extra_field/generic.rb +10 -9
  18. data/lib/zip/extra_field/ntfs.rb +4 -0
  19. data/lib/zip/extra_field/old_unix.rb +3 -1
  20. data/lib/zip/extra_field/universal_time.rb +42 -12
  21. data/lib/zip/extra_field/unix.rb +3 -1
  22. data/lib/zip/extra_field/zip64.rb +4 -2
  23. data/lib/zip/extra_field/zip64_placeholder.rb +1 -2
  24. data/lib/zip/file.rb +136 -100
  25. data/lib/zip/filesystem.rb +199 -179
  26. data/lib/zip/inflater.rb +24 -36
  27. data/lib/zip/input_stream.rb +37 -27
  28. data/lib/zip/ioextras.rb +1 -1
  29. data/lib/zip/ioextras/abstract_input_stream.rb +20 -9
  30. data/lib/zip/ioextras/abstract_output_stream.rb +4 -4
  31. data/lib/zip/null_decompressor.rb +1 -9
  32. data/lib/zip/output_stream.rb +19 -10
  33. data/lib/zip/pass_thru_compressor.rb +2 -2
  34. data/lib/zip/pass_thru_decompressor.rb +14 -23
  35. data/lib/zip/streamable_directory.rb +3 -3
  36. data/lib/zip/streamable_stream.rb +6 -10
  37. data/lib/zip/version.rb +1 -1
  38. data/samples/example.rb +2 -2
  39. data/samples/example_filesystem.rb +1 -1
  40. data/samples/example_recursive.rb +15 -18
  41. data/samples/gtk_ruby_zip.rb +20 -20
  42. data/samples/qtzip.rb +7 -7
  43. data/samples/write_simple.rb +2 -4
  44. data/samples/zipfind.rb +23 -22
  45. metadata +41 -130
  46. data/test/basic_zip_file_test.rb +0 -60
  47. data/test/case_sensitivity_test.rb +0 -69
  48. data/test/central_directory_entry_test.rb +0 -69
  49. data/test/central_directory_test.rb +0 -100
  50. data/test/crypto/null_encryption_test.rb +0 -53
  51. data/test/crypto/traditional_encryption_test.rb +0 -80
  52. data/test/data/WarnInvalidDate.zip +0 -0
  53. data/test/data/file1.txt +0 -46
  54. data/test/data/file1.txt.deflatedData +0 -0
  55. data/test/data/file2.txt +0 -1504
  56. data/test/data/globTest.zip +0 -0
  57. data/test/data/globTest/foo.txt +0 -0
  58. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  59. data/test/data/globTest/food.txt +0 -0
  60. data/test/data/mimetype +0 -1
  61. data/test/data/notzippedruby.rb +0 -7
  62. data/test/data/ntfs.zip +0 -0
  63. data/test/data/oddExtraField.zip +0 -0
  64. data/test/data/rubycode.zip +0 -0
  65. data/test/data/rubycode2.zip +0 -0
  66. data/test/data/test.xls +0 -0
  67. data/test/data/testDirectory.bin +0 -0
  68. data/test/data/zip64-sample.zip +0 -0
  69. data/test/data/zipWithDirs.zip +0 -0
  70. data/test/data/zipWithEncryption.zip +0 -0
  71. data/test/deflater_test.rb +0 -65
  72. data/test/encryption_test.rb +0 -42
  73. data/test/entry_set_test.rb +0 -152
  74. data/test/entry_test.rb +0 -163
  75. data/test/errors_test.rb +0 -34
  76. data/test/extra_field_test.rb +0 -76
  77. data/test/file_extract_directory_test.rb +0 -54
  78. data/test/file_extract_test.rb +0 -83
  79. data/test/file_permissions_test.rb +0 -69
  80. data/test/file_split_test.rb +0 -57
  81. data/test/file_test.rb +0 -563
  82. data/test/filesystem/dir_iterator_test.rb +0 -58
  83. data/test/filesystem/directory_test.rb +0 -121
  84. data/test/filesystem/file_mutating_test.rb +0 -88
  85. data/test/filesystem/file_nonmutating_test.rb +0 -508
  86. data/test/filesystem/file_stat_test.rb +0 -64
  87. data/test/gentestfiles.rb +0 -122
  88. data/test/inflater_test.rb +0 -14
  89. data/test/input_stream_test.rb +0 -182
  90. data/test/ioextras/abstract_input_stream_test.rb +0 -102
  91. data/test/ioextras/abstract_output_stream_test.rb +0 -106
  92. data/test/ioextras/fake_io_test.rb +0 -18
  93. data/test/local_entry_test.rb +0 -154
  94. data/test/output_stream_test.rb +0 -128
  95. data/test/pass_thru_compressor_test.rb +0 -30
  96. data/test/pass_thru_decompressor_test.rb +0 -14
  97. data/test/samples/example_recursive_test.rb +0 -37
  98. data/test/settings_test.rb +0 -95
  99. data/test/test_helper.rb +0 -221
  100. data/test/unicode_file_names_and_comments_test.rb +0 -50
  101. data/test/zip64_full_test.rb +0 -51
  102. data/test/zip64_support_test.rb +0 -14
@@ -5,7 +5,7 @@ module Zip
5
5
 
6
6
  def initialize(an_enumerable = [])
7
7
  super()
8
- @entry_set = {}
8
+ @entry_set = {}
9
9
  an_enumerable.each { |o| push(o) }
10
10
  end
11
11
 
@@ -33,9 +33,9 @@ module Zip
33
33
  entry if @entry_set.delete(to_key(entry))
34
34
  end
35
35
 
36
- def each(&block)
36
+ def each
37
37
  @entry_set = sorted_entries.dup.each do |_, value|
38
- block.call(value)
38
+ yield(value)
39
39
  end
40
40
  end
41
41
 
@@ -50,6 +50,7 @@ module Zip
50
50
 
51
51
  def ==(other)
52
52
  return false unless other.kind_of?(EntrySet)
53
+
53
54
  @entry_set.values == other.entry_set.values
54
55
  end
55
56
 
@@ -57,9 +58,10 @@ module Zip
57
58
  @entry_set[to_key(entry.parent_as_string)]
58
59
  end
59
60
 
60
- def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH)
61
+ def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB)
61
62
  entries.map do |entry|
62
63
  next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
64
+
63
65
  yield(entry) if block_given?
64
66
  entry
65
67
  end.compact
@@ -4,8 +4,10 @@ module Zip
4
4
  class DestinationFileExistsError < Error; end
5
5
  class CompressionMethodError < Error; end
6
6
  class EntryNameError < Error; end
7
+ class EntrySizeError < Error; end
7
8
  class InternalError < Error; end
8
9
  class GPFBit3Error < Error; end
10
+ class DecompressionError < Error; end
9
11
 
10
12
  # Backwards compatibility with v1 (delete in v2)
11
13
  ZipError = Error
@@ -6,27 +6,27 @@ module Zip
6
6
  merge(binstr) if binstr
7
7
  end
8
8
 
9
- def extra_field_type_exist(binstr, id, len, i)
9
+ def extra_field_type_exist(binstr, id, len, index)
10
10
  field_name = ID_MAP[id].name
11
- if self.member?(field_name)
12
- self[field_name].merge(binstr[i, len + 4])
11
+ if member?(field_name)
12
+ self[field_name].merge(binstr[index, len + 4])
13
13
  else
14
- field_obj = ID_MAP[id].new(binstr[i, len + 4])
14
+ field_obj = ID_MAP[id].new(binstr[index, len + 4])
15
15
  self[field_name] = field_obj
16
16
  end
17
17
  end
18
18
 
19
- def extra_field_type_unknown(binstr, len, i)
19
+ def extra_field_type_unknown(binstr, len, index)
20
20
  create_unknown_item unless self['Unknown']
21
- if !len || len + 4 > binstr[i..-1].bytesize
22
- self['Unknown'] << binstr[i..-1]
21
+ if !len || len + 4 > binstr[index..-1].bytesize
22
+ self['Unknown'] << binstr[index..-1]
23
23
  return
24
24
  end
25
- self['Unknown'] << binstr[i, len + 4]
25
+ self['Unknown'] << binstr[index, len + 4]
26
26
  end
27
27
 
28
28
  def create_unknown_item
29
- s = ''
29
+ s = +''
30
30
  class << s
31
31
  alias_method :to_c_dir_bin, :to_s
32
32
  alias_method :to_local_bin, :to_s
@@ -36,10 +36,11 @@ module Zip
36
36
 
37
37
  def merge(binstr)
38
38
  return if binstr.empty?
39
+
39
40
  i = 0
40
41
  while i < binstr.bytesize
41
42
  id = binstr[i, 2]
42
- len = binstr[i + 2, 2].to_s.unpack('v').first
43
+ len = binstr[i + 2, 2].to_s.unpack1('v')
43
44
  if id && ID_MAP.member?(id)
44
45
  extra_field_type_exist(binstr, id, len, i)
45
46
  elsif id
@@ -54,6 +55,7 @@ module Zip
54
55
  unless (field_class = ID_MAP.values.find { |k| k.name == name })
55
56
  raise Error, "Unknown extra field '#{name}'"
56
57
  end
58
+
57
59
  self[name] = field_class.new
58
60
  end
59
61
 
@@ -1,9 +1,9 @@
1
1
  module Zip
2
2
  class ExtraField::Generic
3
3
  def self.register_map
4
- if self.const_defined?(:HEADER_ID)
5
- ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
6
- end
4
+ return unless const_defined?(:HEADER_ID)
5
+
6
+ ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
7
7
  end
8
8
 
9
9
  def self.name
@@ -12,18 +12,19 @@ module Zip
12
12
 
13
13
  # return field [size, content] or false
14
14
  def initial_parse(binstr)
15
- if !binstr
16
- # If nil, start with empty.
17
- return false
18
- elsif binstr[0, 2] != self.class.const_get(:HEADER_ID)
19
- $stderr.puts 'Warning: weired extra feild header ID. skip parsing'
15
+ return false unless binstr
16
+
17
+ if binstr[0, 2] != self.class.const_get(:HEADER_ID)
18
+ warn 'WARNING: weird extra field header ID. Skip parsing it.'
20
19
  return false
21
20
  end
22
- [binstr[2, 2].unpack('v')[0], binstr[4..-1]]
21
+
22
+ [binstr[2, 2].unpack1('v'), binstr[4..-1]]
23
23
  end
24
24
 
25
25
  def ==(other)
26
26
  return false if self.class != other.class
27
+
27
28
  each do |k, v|
28
29
  return false if v != other[k]
29
30
  end
@@ -19,6 +19,7 @@ module Zip
19
19
 
20
20
  def merge(binstr)
21
21
  return if binstr.empty?
22
+
22
23
  size, content = initial_parse(binstr)
23
24
  (size && content) || return
24
25
 
@@ -27,6 +28,7 @@ module Zip
27
28
 
28
29
  tag1 = tags[1]
29
30
  return unless tag1
31
+
30
32
  ntfs_mtime, ntfs_atime, ntfs_ctime = tag1.unpack('Q<Q<Q<')
31
33
  ntfs_mtime && @mtime ||= from_ntfs_time(ntfs_mtime)
32
34
  ntfs_atime && @atime ||= from_ntfs_time(ntfs_atime)
@@ -65,12 +67,14 @@ module Zip
65
67
 
66
68
  def parse_tags(content)
67
69
  return {} if content.nil?
70
+
68
71
  tags = {}
69
72
  i = 0
70
73
  while i < content.bytesize
71
74
  tag, size = content[i, 4].unpack('vv')
72
75
  i += 4
73
76
  break unless tag && size
77
+
74
78
  value = content[i, size]
75
79
  i += size
76
80
  tags[tag] = value
@@ -16,14 +16,16 @@ module Zip
16
16
 
17
17
  def merge(binstr)
18
18
  return if binstr.empty?
19
+
19
20
  size, content = initial_parse(binstr)
20
21
  # size: 0 for central directory. 4 for local header
21
22
  return if !size || size == 0
23
+
22
24
  atime, mtime, uid, gid = content.unpack('VVvv')
23
25
  @uid ||= uid
24
26
  @gid ||= gid
25
27
  @atime ||= atime
26
- @mtime ||= mtime
28
+ @mtime ||= mtime # rubocop:disable Naming/MemoizedInstanceVariableName
27
29
  end
28
30
 
29
31
  def ==(other)
@@ -4,24 +4,54 @@ module Zip
4
4
  HEADER_ID = 'UT'
5
5
  register_map
6
6
 
7
+ ATIME_MASK = 0b010
8
+ CTIME_MASK = 0b100
9
+ MTIME_MASK = 0b001
10
+
7
11
  def initialize(binstr = nil)
8
12
  @ctime = nil
9
13
  @mtime = nil
10
14
  @atime = nil
11
- @flag = nil
12
- binstr && merge(binstr)
15
+ @flag = 0
16
+
17
+ merge(binstr) unless binstr.nil?
18
+ end
19
+
20
+ attr_reader :atime, :ctime, :mtime, :flag
21
+
22
+ def atime=(time)
23
+ @flag = time.nil? ? @flag & ~ATIME_MASK : @flag | ATIME_MASK
24
+ @atime = time
25
+ end
26
+
27
+ def ctime=(time)
28
+ @flag = time.nil? ? @flag & ~CTIME_MASK : @flag | CTIME_MASK
29
+ @ctime = time
13
30
  end
14
31
 
15
- attr_accessor :atime, :ctime, :mtime, :flag
32
+ def mtime=(time)
33
+ @flag = time.nil? ? @flag & ~MTIME_MASK : @flag | MTIME_MASK
34
+ @mtime = time
35
+ end
16
36
 
17
37
  def merge(binstr)
18
38
  return if binstr.empty?
39
+
19
40
  size, content = initial_parse(binstr)
20
- size || return
21
- @flag, mtime, atime, ctime = content.unpack('CVVV')
22
- mtime && @mtime ||= ::Zip::DOSTime.at(mtime)
23
- atime && @atime ||= ::Zip::DOSTime.at(atime)
24
- ctime && @ctime ||= ::Zip::DOSTime.at(ctime)
41
+ return if !size || size <= 0
42
+
43
+ @flag, *times = content.unpack('Cl<l<l<')
44
+
45
+ # Parse the timestamps, in order, based on which flags are set.
46
+ return if times[0].nil?
47
+
48
+ @mtime ||= ::Zip::DOSTime.at(times.shift) unless @flag & MTIME_MASK == 0
49
+ return if times[0].nil?
50
+
51
+ @atime ||= ::Zip::DOSTime.at(times.shift) unless @flag & ATIME_MASK == 0
52
+ return if times[0].nil?
53
+
54
+ @ctime ||= ::Zip::DOSTime.at(times.shift) unless @flag & CTIME_MASK == 0
25
55
  end
26
56
 
27
57
  def ==(other)
@@ -32,15 +62,15 @@ module Zip
32
62
 
33
63
  def pack_for_local
34
64
  s = [@flag].pack('C')
35
- @flag & 1 != 0 && s << [@mtime.to_i].pack('V')
36
- @flag & 2 != 0 && s << [@atime.to_i].pack('V')
37
- @flag & 4 != 0 && s << [@ctime.to_i].pack('V')
65
+ s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
66
+ s << [@atime.to_i].pack('l<') unless @flag & ATIME_MASK == 0
67
+ s << [@ctime.to_i].pack('l<') unless @flag & CTIME_MASK == 0
38
68
  s
39
69
  end
40
70
 
41
71
  def pack_for_c_dir
42
72
  s = [@flag].pack('C')
43
- @flag & 1 == 1 && s << [@mtime.to_i].pack('V')
73
+ s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
44
74
  s
45
75
  end
46
76
  end
@@ -14,12 +14,14 @@ module Zip
14
14
 
15
15
  def merge(binstr)
16
16
  return if binstr.empty?
17
+
17
18
  size, content = initial_parse(binstr)
18
19
  # size: 0 for central directory. 4 for local header
19
20
  return if !size || size == 0
21
+
20
22
  uid, gid = content.unpack('vv')
21
23
  @uid ||= uid
22
- @gid ||= gid
24
+ @gid ||= gid # rubocop:disable Naming/MemoizedInstanceVariableName
23
25
  end
24
26
 
25
27
  def ==(other)
@@ -9,7 +9,7 @@ module Zip
9
9
  # unparsed binary; we don't actually know what this contains
10
10
  # without looking for FFs in the associated file header
11
11
  # call parse after initializing with a binary string
12
- @content = nil
12
+ @content = nil
13
13
  @original_size = nil
14
14
  @compressed_size = nil
15
15
  @relative_header_offset = nil
@@ -26,6 +26,7 @@ module Zip
26
26
 
27
27
  def merge(binstr)
28
28
  return if binstr.empty?
29
+
29
30
  _, @content = initial_parse(binstr)
30
31
  end
31
32
 
@@ -45,13 +46,14 @@ module Zip
45
46
  end
46
47
 
47
48
  def extract(size, format)
48
- @content.slice!(0, size).unpack(format)[0]
49
+ @content.slice!(0, size).unpack1(format)
49
50
  end
50
51
  private :extract
51
52
 
52
53
  def pack_for_local
53
54
  # local header entries must contain original size and compressed size; other fields do not apply
54
55
  return '' unless @original_size && @compressed_size
56
+
55
57
  [@original_size, @compressed_size].pack('Q<Q<')
56
58
  end
57
59
 
@@ -6,8 +6,7 @@ module Zip
6
6
  HEADER_ID = ['9999'].pack('H*') # this ID is used by other libraries such as .NET's Ionic.zip
7
7
  register_map
8
8
 
9
- def initialize(_binstr = nil)
10
- end
9
+ def initialize(_binstr = nil); end
11
10
 
12
11
  def pack_for_local
13
12
  "\x00" * 16
@@ -43,61 +43,84 @@ module Zip
43
43
  # interface for accessing the filesystem, ie. the File and Dir classes.
44
44
 
45
45
  class File < CentralDirectory
46
- CREATE = 1
46
+ CREATE = true
47
47
  SPLIT_SIGNATURE = 0x08074b50
48
48
  ZIP64_EOCD_SIGNATURE = 0x06064b50
49
49
  MAX_SEGMENT_SIZE = 3_221_225_472
50
50
  MIN_SEGMENT_SIZE = 65_536
51
51
  DATA_BUFFER_SIZE = 8192
52
- IO_METHODS = [:tell, :seek, :read, :close]
52
+ IO_METHODS = [:tell, :seek, :read, :eof, :close]
53
+
54
+ DEFAULT_OPTIONS = {
55
+ restore_ownership: false,
56
+ restore_permissions: false,
57
+ restore_times: false
58
+ }.freeze
53
59
 
54
60
  attr_reader :name
55
61
 
56
- # default -> false
62
+ # default -> false.
57
63
  attr_accessor :restore_ownership
58
- # default -> false
64
+
65
+ # default -> false, but will be set to true in a future version.
59
66
  attr_accessor :restore_permissions
60
- # default -> true
67
+
68
+ # default -> false, but will be set to true in a future version.
61
69
  attr_accessor :restore_times
70
+
62
71
  # Returns the zip files comment, if it has one
63
72
  attr_accessor :comment
64
73
 
65
74
  # Opens a zip archive. Pass true as the second parameter to create
66
75
  # a new archive if it doesn't exist already.
67
- def initialize(file_name, create = nil, buffer = false, options = {})
76
+ def initialize(path_or_io, create = false, buffer = false, options = {})
68
77
  super()
69
- @name = file_name
78
+ options = DEFAULT_OPTIONS.merge(options)
79
+ @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
70
80
  @comment = ''
71
- @create = create
72
- case
73
- when !buffer && ::File.size?(file_name)
74
- @create = nil
75
- @file_permissions = ::File.stat(file_name).mode
76
- ::File.open(name, 'rb') do |f|
77
- read_from_stream(f)
81
+ @create = create ? true : false # allow any truthy value to mean true
82
+
83
+ if ::File.size?(@name.to_s)
84
+ # There is a file, which exists, that is associated with this zip.
85
+ @create = false
86
+ @file_permissions = ::File.stat(@name).mode
87
+
88
+ if buffer
89
+ read_from_stream(path_or_io)
90
+ else
91
+ ::File.open(@name, 'rb') do |f|
92
+ read_from_stream(f)
93
+ end
78
94
  end
79
- when create
80
- @file_permissions = create_file_permissions
95
+ elsif buffer && path_or_io.size > 0
96
+ # This zip is probably a non-empty StringIO.
97
+ read_from_stream(path_or_io)
98
+ elsif @create
99
+ # This zip is completely new/empty and is to be created.
81
100
  @entry_set = EntrySet.new
82
- when ::File.zero?(file_name)
83
- raise Error, "File #{file_name} has zero size. Did you mean to pass the create flag?"
101
+ elsif ::File.zero?(@name)
102
+ # A file exists, but it is empty.
103
+ raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
84
104
  else
85
- raise Error, "File #{file_name} not found"
105
+ # Everything is wrong.
106
+ raise Error, "File #{@name} not found"
86
107
  end
108
+
87
109
  @stored_entries = @entry_set.dup
88
110
  @stored_comment = @comment
89
- @restore_ownership = options[:restore_ownership] || false
90
- @restore_permissions = options[:restore_permissions] || true
91
- @restore_times = options[:restore_times] || true
111
+ @restore_ownership = options[:restore_ownership]
112
+ @restore_permissions = options[:restore_permissions]
113
+ @restore_times = options[:restore_times]
92
114
  end
93
115
 
94
116
  class << self
95
- # Same as #new. If a block is passed the ZipFile object is passed
96
- # to the block and is automatically closed afterwards just as with
97
- # ruby's builtin File.open method.
98
- def open(file_name, create = nil)
99
- zf = ::Zip::File.new(file_name, create)
117
+ # Similar to ::new. If a block is passed the Zip::File object is passed
118
+ # to the block and is automatically closed afterwards, just as with
119
+ # ruby's builtin File::open method.
120
+ def open(file_name, create = false, options = {})
121
+ zf = ::Zip::File.new(file_name, create, false, options)
100
122
  return zf unless block_given?
123
+
101
124
  begin
102
125
  yield zf
103
126
  ensure
@@ -118,19 +141,20 @@ module Zip
118
141
  # (This can be used to extract data from a
119
142
  # downloaded zip archive without first saving it to disk.)
120
143
  def open_buffer(io, options = {})
121
- unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.is_a?(String)
144
+ unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
122
145
  raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
123
146
  end
124
- if io.is_a?(::String)
125
- require 'stringio'
126
- io = ::StringIO.new(io)
127
- elsif io.respond_to?(:binmode)
128
- # https://github.com/rubyzip/rubyzip/issues/119
129
- io.binmode
130
- end
147
+
148
+ io = ::StringIO.new(io) if io.kind_of?(::String)
149
+
150
+ # https://github.com/rubyzip/rubyzip/issues/119
151
+ io.binmode if io.respond_to?(:binmode)
152
+
131
153
  zf = ::Zip::File.new(io, true, true, options)
132
- zf.read_from_stream(io)
154
+ return zf unless block_given?
155
+
133
156
  yield zf
157
+
134
158
  begin
135
159
  zf.write_buffer(io)
136
160
  rescue IOError => e
@@ -144,17 +168,16 @@ module Zip
144
168
  # whereas ZipInputStream jumps through the entire archive accessing the
145
169
  # local entry headers (which contain the same information as the
146
170
  # central directory).
147
- def foreach(aZipFileName, &block)
148
- open(aZipFileName) do |zipFile|
149
- zipFile.each(&block)
171
+ def foreach(zip_file_name, &block)
172
+ ::Zip::File.open(zip_file_name) do |zip_file|
173
+ zip_file.each(&block)
150
174
  end
151
175
  end
152
176
 
153
177
  def get_segment_size_for_split(segment_size)
154
- case
155
- when MIN_SEGMENT_SIZE > segment_size
178
+ if MIN_SEGMENT_SIZE > segment_size
156
179
  MIN_SEGMENT_SIZE
157
- when MAX_SEGMENT_SIZE < segment_size
180
+ elsif MAX_SEGMENT_SIZE < segment_size
158
181
  MAX_SEGMENT_SIZE
159
182
  else
160
183
  segment_size
@@ -162,8 +185,10 @@ module Zip
162
185
  end
163
186
 
164
187
  def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
165
- partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/,
166
- partial_zip_file_name + ::File.extname(zip_file_name)) unless partial_zip_file_name.nil?
188
+ unless partial_zip_file_name.nil?
189
+ partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/,
190
+ partial_zip_file_name + ::File.extname(zip_file_name))
191
+ end
167
192
  partial_zip_file_name ||= zip_file_name
168
193
  partial_zip_file_name
169
194
  end
@@ -206,12 +231,14 @@ module Zip
206
231
  def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true, partial_zip_file_name = nil)
207
232
  raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name)
208
233
  raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
234
+
209
235
  zip_file_size = ::File.size(zip_file_name)
210
236
  segment_size = get_segment_size_for_split(segment_size)
211
237
  return if zip_file_size <= segment_size
238
+
212
239
  segment_count = get_segment_count_for_split(zip_file_size, segment_size)
213
240
  # Checking for correct zip structure
214
- open(zip_file_name) {}
241
+ ::Zip::File.open(zip_file_name) {}
215
242
  partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
216
243
  szip_file_index = 0
217
244
  ::File.open(zip_file_name, 'rb') do |zip_file|
@@ -228,8 +255,8 @@ module Zip
228
255
  # Returns an input stream to the specified entry. If a block is passed
229
256
  # the stream object is passed to the block and the stream is automatically
230
257
  # closed afterwards just as with ruby's builtin File.open method.
231
- def get_input_stream(entry, &aProc)
232
- get_entry(entry).get_input_stream(&aProc)
258
+ def get_input_stream(entry, &a_proc)
259
+ get_entry(entry).get_input_stream(&a_proc)
233
260
  end
234
261
 
235
262
  # Returns an output stream to the specified entry. If entry is not an instance
@@ -237,7 +264,11 @@ module Zip
237
264
  # specified. If a block is passed the stream object is passed to the block and
238
265
  # the stream is automatically closed afterwards just as with ruby's builtin
239
266
  # File.open method.
240
- def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil, &aProc)
267
+ def get_output_stream(entry, permission_int = nil, comment = nil,
268
+ extra = nil, compressed_size = nil, crc = nil,
269
+ compression_method = nil, size = nil, time = nil,
270
+ &a_proc)
271
+
241
272
  new_entry =
242
273
  if entry.kind_of?(Entry)
243
274
  entry
@@ -251,7 +282,7 @@ module Zip
251
282
  new_entry.unix_perms = permission_int
252
283
  zip_streamable_entry = StreamableStream.new(new_entry)
253
284
  @entry_set << zip_streamable_entry
254
- zip_streamable_entry.get_output_stream(&aProc)
285
+ zip_streamable_entry.get_output_stream(&a_proc)
255
286
  end
256
287
 
257
288
  # Returns the name of the zip archive
@@ -261,7 +292,7 @@ module Zip
261
292
 
262
293
  # Returns a string containing the contents of the specified entry
263
294
  def read(entry)
264
- get_input_stream(entry) { |is| is.read }
295
+ get_input_stream(entry, &:read)
265
296
  end
266
297
 
267
298
  # Convenience method for adding the contents of a file to the archive
@@ -274,6 +305,13 @@ module Zip
274
305
  @entry_set << new_entry
275
306
  end
276
307
 
308
+ # Convenience method for adding the contents of a file to the archive
309
+ # in Stored format (uncompressed)
310
+ def add_stored(entry, src_path, &continue_on_exists_proc)
311
+ entry = ::Zip::Entry.new(@name, entry.to_s, nil, nil, nil, nil, ::Zip::Entry::STORED)
312
+ add(entry, src_path, &continue_on_exists_proc)
313
+ end
314
+
277
315
  # Removes the specified entry.
278
316
  def remove(entry)
279
317
  @entry_set.delete(get_entry(entry))
@@ -281,19 +319,19 @@ module Zip
281
319
 
282
320
  # Renames the specified entry.
283
321
  def rename(entry, new_name, &continue_on_exists_proc)
284
- foundEntry = get_entry(entry)
322
+ found_entry = get_entry(entry)
285
323
  check_entry_exists(new_name, continue_on_exists_proc, 'rename')
286
- @entry_set.delete(foundEntry)
287
- foundEntry.name = new_name
288
- @entry_set << foundEntry
324
+ @entry_set.delete(found_entry)
325
+ found_entry.name = new_name
326
+ @entry_set << found_entry
289
327
  end
290
328
 
291
- # Replaces the specified entry with the contents of srcPath (from
329
+ # Replaces the specified entry with the contents of src_path (from
292
330
  # the file system).
293
- def replace(entry, srcPath)
294
- check_file(srcPath)
331
+ def replace(entry, src_path)
332
+ check_file(src_path)
295
333
  remove(entry)
296
- add(entry, srcPath)
334
+ add(entry, src_path)
297
335
  end
298
336
 
299
337
  # Extracts entry to file dest_path.
@@ -306,7 +344,8 @@ module Zip
306
344
  # Commits changes that has been made since the previous commit to
307
345
  # the zip archive.
308
346
  def commit
309
- return unless commit_required?
347
+ return if name.kind_of?(StringIO) || !commit_required?
348
+
310
349
  on_success_replace do |tmp_file|
311
350
  ::Zip::OutputStream.open(tmp_file) do |zos|
312
351
  @entry_set.each do |e|
@@ -340,13 +379,19 @@ module Zip
340
379
  @entry_set.each do |e|
341
380
  return true if e.dirty
342
381
  end
343
- @comment != @stored_comment || @entry_set != @stored_entries || @create == ::Zip::File::CREATE
382
+ @comment != @stored_comment || @entry_set != @stored_entries || @create
344
383
  end
345
384
 
346
385
  # Searches for entry with the specified name. Returns nil if
347
386
  # no entry is found. See also get_entry
348
387
  def find_entry(entry_name)
349
- @entry_set.find_entry(entry_name)
388
+ selected_entry = @entry_set.find_entry(entry_name)
389
+ return if selected_entry.nil?
390
+
391
+ selected_entry.restore_ownership = @restore_ownership
392
+ selected_entry.restore_permissions = @restore_permissions
393
+ selected_entry.restore_times = @restore_times
394
+ selected_entry
350
395
  end
351
396
 
352
397
  # Searches for entries given a glob
@@ -358,43 +403,43 @@ module Zip
358
403
  # if no entry is found.
359
404
  def get_entry(entry)
360
405
  selected_entry = find_entry(entry)
361
- raise Errno::ENOENT, entry unless selected_entry
362
- selected_entry.restore_ownership = @restore_ownership
363
- selected_entry.restore_permissions = @restore_permissions
364
- selected_entry.restore_times = @restore_times
406
+ raise Errno::ENOENT, entry if selected_entry.nil?
407
+
365
408
  selected_entry
366
409
  end
367
410
 
368
411
  # Creates a directory
369
- def mkdir(entryName, permissionInt = 0755)
370
- raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName)
371
- entryName = entryName.dup.to_s
372
- entryName << '/' unless entryName.end_with?('/')
373
- @entry_set << ::Zip::StreamableDirectory.new(@name, entryName, nil, permissionInt)
412
+ def mkdir(entry_name, permission = 0o755)
413
+ raise Errno::EEXIST, "File exists - #{entry_name}" if find_entry(entry_name)
414
+
415
+ entry_name = entry_name.dup.to_s
416
+ entry_name << '/' unless entry_name.end_with?('/')
417
+ @entry_set << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
374
418
  end
375
419
 
376
420
  private
377
421
 
378
- def directory?(newEntry, srcPath)
379
- srcPathIsDirectory = ::File.directory?(srcPath)
380
- if newEntry.directory? && !srcPathIsDirectory
422
+ def directory?(new_entry, src_path)
423
+ path_is_directory = ::File.directory?(src_path)
424
+ if new_entry.directory? && !path_is_directory
381
425
  raise ArgumentError,
382
- "entry name '#{newEntry}' indicates directory entry, but " \
383
- "'#{srcPath}' is not a directory"
384
- elsif !newEntry.directory? && srcPathIsDirectory
385
- newEntry.name += '/'
426
+ "entry name '#{new_entry}' indicates directory entry, but " \
427
+ "'#{src_path}' is not a directory"
428
+ elsif !new_entry.directory? && path_is_directory
429
+ new_entry.name += '/'
386
430
  end
387
- newEntry.directory? && srcPathIsDirectory
431
+ new_entry.directory? && path_is_directory
388
432
  end
389
433
 
390
- def check_entry_exists(entryName, continue_on_exists_proc, procedureName)
434
+ def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
391
435
  continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
392
- return unless @entry_set.include?(entryName)
436
+ return unless @entry_set.include?(entry_name)
437
+
393
438
  if continue_on_exists_proc.call
394
- remove get_entry(entryName)
439
+ remove get_entry(entry_name)
395
440
  else
396
441
  raise ::Zip::EntryExistsError,
397
- procedureName + " failed. Entry #{entryName} already exists"
442
+ proc_name + " failed. Entry #{entry_name} already exists"
398
443
  end
399
444
  end
400
445
 
@@ -403,27 +448,18 @@ module Zip
403
448
  end
404
449
 
405
450
  def on_success_replace
406
- tmp_filename = create_tmpname
407
- if yield tmp_filename
408
- ::File.rename(tmp_filename, name)
409
- ::File.chmod(@file_permissions, name) if defined?(@file_permissions)
410
- end
411
- ensure
412
- ::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
413
- end
414
-
415
- def create_tmpname
416
451
  dirname, basename = ::File.split(name)
417
- ::Dir::Tmpname.create(basename, dirname) do |tmpname|
418
- opts = {perm: 0600, mode: ::File::CREAT | ::File::WRONLY | ::File::EXCL}
419
- f = File.open(tmpname, opts)
420
- f.close
452
+ ::Dir::Tmpname.create(basename, dirname) do |tmp_filename|
453
+ begin
454
+ if yield tmp_filename
455
+ ::File.rename(tmp_filename, name)
456
+ ::File.chmod(@file_permissions, name) unless @create
457
+ end
458
+ ensure
459
+ ::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
460
+ end
421
461
  end
422
462
  end
423
-
424
- def create_file_permissions
425
- ::Zip::RUNNING_ON_WINDOWS ? 0644 : 0666 - ::File.umask
426
- end
427
463
  end
428
464
  end
429
465