rubyzip 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubyzip might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- NjQ3YjE3YmQ0MDUyYzViOThmNDBjNGVhYTRmNDdiNzAzZjYzNjZkOA==
5
- data.tar.gz: !binary |-
6
- OGI5ZjFlYTk3NmQwMGY2NmM2NmVkYWI3ZjczMTVjNDAxYzg0YzY3Zg==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- ODI3NzAwZDg5NzYyZDdjZTNlNDUyOWJjYmY0YTU0MGEzMjUwOTE5MjdkOTM3
10
- ZjkzMTFjNzQzZmRlZDA4MTllZmU2N2NjMDQyMDM5NTRjMjg5ODVmMzUyODMw
11
- ZGExYjk3MDkwNzRmYTJkYjhhYjNmMmY4YTIyYTQ2ODA4MDdiNzk=
12
- data.tar.gz: !binary |-
13
- MWM5ODRlNzdmNDNiOGE4ZjkzMWYzZTIxZTE4OTU1ZTVhZmQ4YzhhZGQ0ZWNi
14
- N2Q4N2NhZmE1ZmJiZjFjMzcxMWI3YzgzNmRiNDVlZGYwZmYyZTJiNTljY2Yx
15
- MTY4NGM1YjNhOGJiZmE2MGUyZTBjYTc2YzdjMzc0ZTE3NTIyZDc=
2
+ SHA1:
3
+ metadata.gz: 68e2727c6e1f49edc93c3081e74a8b8dd6cf8ea8
4
+ data.tar.gz: c6e0a74722936557ccec3a74f3d2efcf2e4a21b1
5
+ SHA512:
6
+ metadata.gz: 77c0f46c33bfe45177adcc0bddf68fc3ae55624ed5fc95163eaf0546c028fd204fdf8167a2ce041389c4c9fe4961868dfd0ed79dfe3ea3439634542db9aecd1d
7
+ data.tar.gz: 7a2a4164ce2591204f30a36b337a4e1ce5907b3649487c9a366aff6d366b68cb61b15136cb2c0015845239a40d99158aabba2d635e7b46e5e508813dc1512aae
data/README.md CHANGED
@@ -9,6 +9,17 @@ rubyzip is a ruby library for reading and writing zip files.
9
9
 
10
10
  Rubyzip interface changed!!! No need to do `require "zip/zip"` and `Zip` prefix in class names removed.
11
11
 
12
+ If you have issues with any third-party gems what required rubyzip you can use next temporary fix:
13
+
14
+ ```ruby
15
+ # Place this line before your library or on the head of your Gemfile
16
+ gem 'rubyzip', '< 1.0.0'
17
+ ```
18
+
19
+ ## Requirements
20
+
21
+ * Ruby 1.9.2 or greater
22
+
12
23
  ## Installation
13
24
  rubyzip is available on RubyGems, so:
14
25
 
@@ -61,6 +72,24 @@ Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile|
61
72
  end
62
73
  ```
63
74
 
75
+ ### Save zip archive entries in sorted by name state
76
+
77
+ To saving zip archives in sorted order like below you need to set `::Zip.sort_entries` to `true`
78
+
79
+ ```
80
+ Vegetable/
81
+ Vegetable/bean
82
+ Vegetable/carrot
83
+ Vegetable/celery
84
+ fruit/
85
+ fruit/apple
86
+ fruit/kiwi
87
+ fruit/mango
88
+ fruit/orange
89
+ ```
90
+
91
+ After this entries in zip archive will be saved in ordered state.
92
+
64
93
  ## Known issues
65
94
 
66
95
  ### Modify docx file with rubyzip
data/Rakefile CHANGED
@@ -8,6 +8,12 @@ Rake::TestTask.new(:test) do |test|
8
8
  test.libs << File.join(File.dirname(__FILE__), 'test')
9
9
  test.pattern = File.join(File.dirname(__FILE__), 'test/alltests.rb')
10
10
  test.verbose = true
11
+ end
11
12
 
13
+ Rake::TestTask.new(:zip64_full_test) do |test|
14
+ test.libs << File.join(File.dirname(__FILE__), 'lib')
15
+ test.libs << File.join(File.dirname(__FILE__), 'test')
16
+ test.pattern = File.join(File.dirname(__FILE__), 'test/zip64_full_test.rb')
17
+ test.verbose = true
12
18
  end
13
19
 
data/lib/zip.rb CHANGED
@@ -27,16 +27,21 @@ require 'zip/streamable_stream'
27
27
  require 'zip/streamable_directory'
28
28
  require 'zip/constants'
29
29
  require 'zip/errors'
30
+ if defined? JRUBY_VERSION
31
+ require 'jruby'
32
+ JRuby.objectspace = true
33
+ end
30
34
 
31
35
  module Zip
32
36
  extend self
33
- attr_accessor :unicode_names, :on_exists_proc, :continue_on_exists_proc
37
+ attr_accessor :unicode_names, :on_exists_proc, :continue_on_exists_proc, :sort_entries
34
38
 
35
39
  def reset!
36
40
  @_ran_once = false
37
41
  @unicode_names = false
38
42
  @on_exists_proc = false
39
43
  @continue_on_exists_proc = false
44
+ @sort_entries = false
40
45
  end
41
46
 
42
47
  def setup
@@ -49,4 +54,4 @@ end
49
54
 
50
55
  # Copyright (C) 2002, 2003 Thomas Sondergaard
51
56
  # rubyzip is free software; you can redistribute it and/or
52
- # modify it under the terms of the ruby license.
57
+ # modify it under the terms of the ruby license.
@@ -22,20 +22,27 @@ module Zip
22
22
  end
23
23
 
24
24
  def write_to_stream(io) #:nodoc:
25
- offset = io.tell
25
+ cdir_offset = io.tell
26
26
  @entry_set.each { |entry| entry.write_c_dir_entry(io) }
27
- write_e_o_c_d(io, offset)
27
+ eocd_offset = io.tell
28
+ cdir_size = eocd_offset - cdir_offset
29
+ has_zip64_entry = @entry_set.any? { |entry| entry.extra['Zip64'] }
30
+ if has_zip64_entry || cdir_offset > 0xFFFFFFFF || cdir_size > 0xFFFFFFFF || @entry_set.size > 0xFFFF
31
+ write_64_e_o_c_d(io, cdir_offset, cdir_size)
32
+ write_64_eocd_locator(io, eocd_offset)
33
+ end
34
+ write_e_o_c_d(io, cdir_offset, cdir_size)
28
35
  end
29
36
 
30
- def write_e_o_c_d(io, offset) #:nodoc:
37
+ def write_e_o_c_d(io, offset, cdir_size) #:nodoc:
31
38
  tmp = [
32
39
  END_OF_CDS,
33
40
  0, # @numberOfThisDisk
34
41
  0, # @numberOfDiskWithStartOfCDir
35
- @entry_set ? @entry_set.size : 0,
36
- @entry_set ? @entry_set.size : 0,
37
- cdir_size,
38
- offset,
42
+ @entry_set ? [@entry_set.size, 0xFFFF].min : 0,
43
+ @entry_set ? [@entry_set.size, 0xFFFF].min : 0,
44
+ [cdir_size, 0xFFFFFFFF].min,
45
+ [offset, 0xFFFFFFFF].min,
39
46
  @comment ? @comment.length : 0
40
47
  ]
41
48
  io << tmp.pack('VvvvvVVv')
@@ -44,14 +51,35 @@ module Zip
44
51
 
45
52
  private :write_e_o_c_d
46
53
 
47
- def cdir_size #:nodoc:
48
- # does not include eocd
49
- @entry_set.inject(0) do |value, entry|
50
- entry.cdir_header_size + value
51
- end
54
+ def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc:
55
+ tmp = [
56
+ ZIP64_END_OF_CDS,
57
+ 44, # size of zip64 end of central directory record (excludes signature and field itself)
58
+ VERSION_MADE_BY,
59
+ VERSION_NEEDED_TO_EXTRACT_ZIP64,
60
+ 0, # @numberOfThisDisk
61
+ 0, # @numberOfDiskWithStartOfCDir
62
+ @entry_set ? @entry_set.size : 0, # number of entries on this disk
63
+ @entry_set ? @entry_set.size : 0, # number of entries total
64
+ cdir_size, # size of central directory
65
+ offset, # offset of start of central directory in its disk
66
+ ]
67
+ io << tmp.pack('VQ<vvVVQ<Q<Q<Q<')
68
+ end
69
+
70
+ private :write_64_e_o_c_d
71
+
72
+ def write_64_eocd_locator(io, zip64_eocd_offset)
73
+ tmp = [
74
+ ZIP64_EOCD_LOCATOR,
75
+ 0, # number of disk containing the start of zip64 eocd record
76
+ zip64_eocd_offset, # offset of the start of zip64 eocd record in its disk
77
+ 1 # total number of disks
78
+ ]
79
+ io << tmp.pack('VVQ<V')
52
80
  end
53
81
 
54
- private :cdir_size
82
+ private :write_64_eocd_locator
55
83
 
56
84
  def read_64_e_o_c_d(buf) #:nodoc:
57
85
  buf = get_64_e_o_c_d(buf)
@@ -7,7 +7,9 @@ module Zip
7
7
  LOCAL_ENTRY_SIGNATURE = 0x04034b50
8
8
  LOCAL_ENTRY_STATIC_HEADER_LENGTH = 30
9
9
  LOCAL_ENTRY_TRAILING_DESCRIPTOR_LENGTH = 4+4+4
10
+ VERSION_MADE_BY = 52 # this library's version
10
11
  VERSION_NEEDED_TO_EXTRACT = 20
12
+ VERSION_NEEDED_TO_EXTRACT_ZIP64 = 45
11
13
 
12
14
  FILE_TYPE_FILE = 010
13
15
  FILE_TYPE_DIR = 004
@@ -21,7 +21,7 @@ module Zip
21
21
  @header_signature = ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
22
22
 
23
23
  @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT
24
- @version = 52 # this library's version
24
+ @version = VERSION_MADE_BY
25
25
 
26
26
  @ftype = nil # unspecified or unknown
27
27
  @filepath = nil
@@ -124,10 +124,17 @@ module Zip
124
124
  end
125
125
 
126
126
  def calculate_local_header_size #:nodoc:all
127
- fix_zip64_sizes!
128
127
  LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_size
129
128
  end
130
129
 
130
+ # check before rewriting an entry (after file sizes are known)
131
+ # that we didn't change the header size (and thus clobber file data or something)
132
+ def verify_local_header_size!
133
+ return if @local_header_size == 0
134
+ new_size = calculate_local_header_size
135
+ raise ZipError, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size
136
+ end
137
+
131
138
  def cdir_header_size #:nodoc:all
132
139
  CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size +
133
140
  (@extra ? @extra.c_dir_size : 0) + comment_size
@@ -166,7 +173,7 @@ module Zip
166
173
  end
167
174
 
168
175
  def read_zip_64_long(io) # :nodoc:
169
- io.read(8).unpack('V')[0]
176
+ io.read(8).unpack('Q<')[0]
170
177
  end
171
178
 
172
179
  def read_c_dir_entry(io) #:nodoc:all
@@ -178,7 +185,7 @@ module Zip
178
185
  end
179
186
 
180
187
  def read_local_entry(io)
181
- entry = new(io.path)
188
+ entry = self.new
182
189
  entry.read_local_entry(io)
183
190
  entry
184
191
  rescue ZipError
@@ -234,10 +241,12 @@ module Zip
234
241
  @extra = ::Zip::ExtraField.new(extra)
235
242
  end
236
243
  end
244
+ parse_zip64_extra(true)
237
245
  @local_header_size = calculate_local_header_size
238
246
  end
239
247
 
240
248
  def pack_local_entry
249
+ zip64 = @extra['Zip64']
241
250
  [::Zip::LOCAL_ENTRY_SIGNATURE,
242
251
  @version_needed_to_extract, # version needed to extract
243
252
  @gp_flags, # @gp_flags ,
@@ -245,19 +254,22 @@ module Zip
245
254
  @time.to_binary_dos_time, # @last_mod_time ,
246
255
  @time.to_binary_dos_date, # @last_mod_date ,
247
256
  @crc,
248
- @compressed_size,
249
- @size,
257
+ (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
258
+ (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
250
259
  name_size,
251
260
  @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
252
261
  end
253
262
 
254
- def write_local_entry(io) #:nodoc:all
263
+ def write_local_entry(io, rewrite = false) #:nodoc:all
264
+ prep_zip64_extra(true)
265
+ verify_local_header_size! if rewrite
255
266
  @local_header_offset = io.tell
256
267
 
257
268
  io << pack_local_entry
258
269
 
259
270
  io << @name
260
271
  io << (@extra ? @extra.to_local_bin : '')
272
+ @local_header_size = io.tell - @local_header_offset
261
273
  end
262
274
 
263
275
  def unpack_c_dir_entry(buf)
@@ -350,6 +362,7 @@ module Zip
350
362
  @comment = io.read(@comment_length)
351
363
  check_c_dir_entry_comment_size
352
364
  set_ftype_from_c_dir_entry
365
+ parse_zip64_extra(false)
353
366
  @local_header_size = calculate_local_header_size
354
367
  end
355
368
 
@@ -390,6 +403,7 @@ module Zip
390
403
  end
391
404
 
392
405
  def pack_c_dir_entry
406
+ zip64 = @extra['Zip64']
393
407
  [
394
408
  @header_signature,
395
409
  @version, # version of encoding software
@@ -400,22 +414,20 @@ module Zip
400
414
  @time.to_binary_dos_time, # @last_mod_time ,
401
415
  @time.to_binary_dos_date, # @last_mod_date ,
402
416
  @crc,
403
- @compressed_size,
404
- @size,
417
+ (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
418
+ (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
405
419
  name_size,
406
420
  @extra ? @extra.c_dir_size : 0,
407
421
  comment_size,
408
- 0, # disk number start
422
+ (zip64 && zip64.disk_start_number) ? 0xFFFF : 0, # disk number start
409
423
  @internal_file_attributes, # file type (binary=0, text=1)
410
424
  @external_file_attributes, # native filesystem attributes
411
- @local_header_offset,
412
- @name,
413
- @extra,
414
- @comment
425
+ (zip64 && zip64.relative_header_offset) ? 0xFFFFFFFF : @local_header_offset
415
426
  ].pack('VCCvvvvvVVVvvvvvVV')
416
427
  end
417
428
 
418
429
  def write_c_dir_entry(io) #:nodoc:all
430
+ prep_zip64_extra(false)
419
431
  case @fstype
420
432
  when ::Zip::FSTYPE_UNIX
421
433
  ft = case @ftype
@@ -459,8 +471,8 @@ module Zip
459
471
  # Warning: may behave weird with symlinks.
460
472
  def get_input_stream(&block)
461
473
  if @ftype == :directory
462
- yield(::Zip::NullInputStream.instance) if block_given?
463
- ::Zip::NullInputStream.instance
474
+ yield ::Zip::NullInputStream if block_given?
475
+ ::Zip::NullInputStream
464
476
  elsif @filepath
465
477
  case @ftype
466
478
  when :file
@@ -607,10 +619,50 @@ module Zip
607
619
  ::File.symlink(linkto, dest_path)
608
620
  end
609
621
 
610
- def fix_zip64_sizes! #:nodoc:all
611
- if zip64 = @extra["Zip64"]
612
- @size = zip64.original_size
613
- @compressed_size = zip64.compressed_size
622
+ # apply missing data from the zip64 extra information field, if present
623
+ # (required when file sizes exceed 2**32, but can be used for all files)
624
+ def parse_zip64_extra(for_local_header) #:nodoc:all
625
+ if zip64 = @extra['Zip64']
626
+ if for_local_header
627
+ @size, @compressed_size = zip64.parse(@size, @compressed_size)
628
+ else
629
+ @size, @compressed_size, @local_header_offset = zip64.parse(@size, @compressed_size, @local_header_offset)
630
+ end
631
+ end
632
+ end
633
+
634
+ # create a zip64 extra information field if we need one
635
+ def prep_zip64_extra(for_local_header) #:nodoc:all
636
+ need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
637
+ unless for_local_header
638
+ need_zip64 ||= @local_header_offset >= 0xFFFFFFFF
639
+ end
640
+
641
+ if need_zip64
642
+ @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
643
+ @extra.delete('Zip64Placeholder')
644
+ zip64 = @extra.create('Zip64')
645
+ if for_local_header
646
+ # local header always includes size and compressed size
647
+ zip64.original_size = @size
648
+ zip64.compressed_size = @compressed_size
649
+ else
650
+ # central directory entry entries include whichever fields are necessary
651
+ zip64.original_size = @size if @size >= 0xFFFFFFFF
652
+ zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
653
+ zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
654
+ end
655
+ else
656
+ @extra.delete('Zip64')
657
+
658
+ # if this is a local header entry, create a placeholder
659
+ # so we have room to write a zip64 extra field afterward
660
+ # (we won't know if it's needed until the file data is written)
661
+ if for_local_header
662
+ @extra.create('Zip64Placeholder')
663
+ else
664
+ @extra.delete('Zip64Placeholder')
665
+ end
614
666
  end
615
667
  end
616
668
 
@@ -6,7 +6,6 @@ module Zip
6
6
  def initialize(an_enumerable = [])
7
7
  super()
8
8
  @entry_set = {}
9
- @entry_order = []
10
9
  an_enumerable.each { |o| push(o) }
11
10
  end
12
11
 
@@ -19,8 +18,6 @@ module Zip
19
18
  end
20
19
 
21
20
  def <<(entry)
22
- @entry_order.delete(to_key(entry))
23
- @entry_order << to_key(entry)
24
21
  @entry_set[to_key(entry)] = entry
25
22
  end
26
23
 
@@ -33,7 +30,7 @@ module Zip
33
30
  alias :length :size
34
31
 
35
32
  def delete(entry)
36
- if @entry_order.delete(to_key(entry)) && @entry_set.delete(to_key(entry))
33
+ if @entry_set.delete(to_key(entry))
37
34
  entry
38
35
  else
39
36
  nil
@@ -41,23 +38,27 @@ module Zip
41
38
  end
42
39
 
43
40
  def each(&block)
44
- @entry_order.each do |key|
45
- block.call @entry_set[key]
41
+ @entry_set = @entry_set.dup.each do |_, value|
42
+ block.call(value)
46
43
  end
47
44
  end
48
45
 
49
46
  def entries
50
- @entry_order.map { |key| @entry_set[key] }
47
+ if ::Zip.sort_entries == true
48
+ @entry_set.values.sort_by{|x| x.name}
49
+ else
50
+ @entry_set.values
51
+ end
51
52
  end
52
53
 
53
54
  # deep clone
54
55
  def dup
55
- EntrySet.new(@entry_order.map { |key| @entry_set[key].dup })
56
+ EntrySet.new(@entry_set.map { |key, value| value.dup })
56
57
  end
57
58
 
58
59
  def ==(other)
59
60
  return false unless other.kind_of?(EntrySet)
60
- @entry_set == other.entry_set && @entry_order == other.entry_order
61
+ @entry_set.values == other.entry_set.values
61
62
  end
62
63
 
63
64
  def parent(entry)
@@ -57,14 +57,20 @@ module Zip
57
57
  self[name] = field_class.new
58
58
  end
59
59
 
60
+ # place Unknown last, so "extra" data that is missing the proper signature/size
61
+ # does not prevent known fields from being read back in
62
+ def ordered_values
63
+ self.keys.sort_by { |k| k == 'Unknown' ? 1 : 0 }.map { |k| self[k] }
64
+ end
65
+
60
66
  def to_local_bin
61
- self.map { |_, v| v.to_local_bin }.join
67
+ ordered_values.map { |v| v.to_local_bin.force_encoding('BINARY') }.join
62
68
  end
63
69
 
64
70
  alias :to_s :to_local_bin
65
71
 
66
72
  def to_c_dir_bin
67
- self.map { |_, v| v.to_c_dir_bin }.join
73
+ ordered_values.map { |v| v.to_c_dir_bin.force_encoding('BINARY') }.join
68
74
  end
69
75
 
70
76
  def c_dir_size
@@ -83,6 +89,8 @@ end
83
89
  require 'zip/extra_field/generic'
84
90
  require 'zip/extra_field/universal_time'
85
91
  require 'zip/extra_field/unix'
92
+ require 'zip/extra_field/zip64'
93
+ require 'zip/extra_field/zip64_placeholder'
86
94
 
87
95
  # Copyright (C) 2002, 2003 Thomas Sondergaard
88
96
  # rubyzip is free software; you can redistribute it and/or