rubyzip 1.2.0 → 2.3.0

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