rubyzip 0.9.9 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +284 -41
  3. data/Rakefile +11 -6
  4. data/TODO +0 -1
  5. data/lib/zip/central_directory.rb +208 -0
  6. data/lib/zip/compressor.rb +1 -2
  7. data/lib/zip/constants.rb +59 -7
  8. data/lib/zip/crypto/encryption.rb +11 -0
  9. data/lib/zip/crypto/null_encryption.rb +43 -0
  10. data/lib/zip/crypto/traditional_encryption.rb +99 -0
  11. data/lib/zip/decompressor.rb +4 -4
  12. data/lib/zip/deflater.rb +17 -13
  13. data/lib/zip/dos_time.rb +13 -14
  14. data/lib/zip/entry.rb +700 -0
  15. data/lib/zip/entry_set.rb +86 -0
  16. data/lib/zip/errors.rb +18 -0
  17. data/lib/zip/extra_field/generic.rb +43 -0
  18. data/lib/zip/extra_field/ntfs.rb +90 -0
  19. data/lib/zip/extra_field/old_unix.rb +44 -0
  20. data/lib/zip/extra_field/universal_time.rb +47 -0
  21. data/lib/zip/extra_field/unix.rb +37 -0
  22. data/lib/zip/extra_field/zip64.rb +68 -0
  23. data/lib/zip/extra_field/zip64_placeholder.rb +15 -0
  24. data/lib/zip/extra_field.rb +101 -0
  25. data/lib/zip/file.rb +443 -0
  26. data/lib/zip/{zipfilesystem.rb → filesystem.rb} +162 -157
  27. data/lib/zip/inflater.rb +29 -28
  28. data/lib/zip/input_stream.rb +173 -0
  29. data/lib/zip/ioextras/abstract_input_stream.rb +111 -0
  30. data/lib/zip/ioextras/abstract_output_stream.rb +43 -0
  31. data/lib/zip/ioextras.rb +21 -149
  32. data/lib/zip/null_compressor.rb +2 -2
  33. data/lib/zip/null_decompressor.rb +8 -6
  34. data/lib/zip/null_input_stream.rb +3 -2
  35. data/lib/zip/output_stream.rb +189 -0
  36. data/lib/zip/pass_thru_compressor.rb +6 -6
  37. data/lib/zip/pass_thru_decompressor.rb +19 -19
  38. data/lib/zip/{zip_streamable_directory.rb → streamable_directory.rb} +3 -3
  39. data/lib/zip/streamable_stream.rb +56 -0
  40. data/lib/zip/version.rb +3 -0
  41. data/lib/zip.rb +71 -0
  42. data/samples/example.rb +44 -32
  43. data/samples/example_filesystem.rb +16 -18
  44. data/samples/example_recursive.rb +33 -28
  45. data/samples/{gtkRubyzip.rb → gtk_ruby_zip.rb} +26 -28
  46. data/samples/qtzip.rb +22 -31
  47. data/samples/write_simple.rb +12 -13
  48. data/samples/zipfind.rb +31 -39
  49. data/test/basic_zip_file_test.rb +60 -0
  50. data/test/case_sensitivity_test.rb +69 -0
  51. data/test/central_directory_entry_test.rb +69 -0
  52. data/test/central_directory_test.rb +100 -0
  53. data/test/crypto/null_encryption_test.rb +57 -0
  54. data/test/crypto/traditional_encryption_test.rb +80 -0
  55. data/test/data/WarnInvalidDate.zip +0 -0
  56. data/test/data/file1.txt +46 -0
  57. data/test/data/file1.txt.deflatedData +0 -0
  58. data/test/data/file2.txt +1504 -0
  59. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  60. data/test/data/globTest/foo.txt +0 -0
  61. data/test/data/globTest/food.txt +0 -0
  62. data/test/data/globTest.zip +0 -0
  63. data/test/data/gpbit3stored.zip +0 -0
  64. data/test/data/mimetype +1 -0
  65. data/test/data/notzippedruby.rb +7 -0
  66. data/test/data/ntfs.zip +0 -0
  67. data/test/data/oddExtraField.zip +0 -0
  68. data/test/data/path_traversal/Makefile +10 -0
  69. data/test/data/path_traversal/jwilk/README.md +5 -0
  70. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  71. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  72. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  73. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  74. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  75. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  76. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  77. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  78. data/test/data/path_traversal/relative1.zip +0 -0
  79. data/test/data/path_traversal/tilde.zip +0 -0
  80. data/test/data/path_traversal/tuzovakaoff/README.md +3 -0
  81. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  82. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  83. data/test/data/rubycode.zip +0 -0
  84. data/test/data/rubycode2.zip +0 -0
  85. data/test/data/test.xls +0 -0
  86. data/test/data/testDirectory.bin +0 -0
  87. data/test/data/zip64-sample.zip +0 -0
  88. data/test/data/zipWithDirs.zip +0 -0
  89. data/test/data/zipWithEncryption.zip +0 -0
  90. data/test/deflater_test.rb +65 -0
  91. data/test/encryption_test.rb +42 -0
  92. data/test/entry_set_test.rb +163 -0
  93. data/test/entry_test.rb +154 -0
  94. data/test/errors_test.rb +35 -0
  95. data/test/extra_field_test.rb +76 -0
  96. data/test/file_extract_directory_test.rb +54 -0
  97. data/test/file_extract_test.rb +145 -0
  98. data/test/file_permissions_test.rb +65 -0
  99. data/test/file_split_test.rb +57 -0
  100. data/test/file_test.rb +666 -0
  101. data/test/filesystem/dir_iterator_test.rb +58 -0
  102. data/test/filesystem/directory_test.rb +139 -0
  103. data/test/filesystem/file_mutating_test.rb +87 -0
  104. data/test/filesystem/file_nonmutating_test.rb +508 -0
  105. data/test/filesystem/file_stat_test.rb +64 -0
  106. data/test/gentestfiles.rb +126 -0
  107. data/test/inflater_test.rb +14 -0
  108. data/test/input_stream_test.rb +182 -0
  109. data/test/ioextras/abstract_input_stream_test.rb +102 -0
  110. data/test/ioextras/abstract_output_stream_test.rb +106 -0
  111. data/test/ioextras/fake_io_test.rb +18 -0
  112. data/test/local_entry_test.rb +154 -0
  113. data/test/output_stream_test.rb +128 -0
  114. data/test/pass_thru_compressor_test.rb +30 -0
  115. data/test/pass_thru_decompressor_test.rb +14 -0
  116. data/test/path_traversal_test.rb +141 -0
  117. data/test/samples/example_recursive_test.rb +37 -0
  118. data/test/settings_test.rb +95 -0
  119. data/test/test_helper.rb +234 -0
  120. data/test/unicode_file_names_and_comments_test.rb +62 -0
  121. data/test/zip64_full_test.rb +51 -0
  122. data/test/zip64_support_test.rb +14 -0
  123. metadata +274 -41
  124. data/NEWS +0 -172
  125. data/lib/zip/settings.rb +0 -10
  126. data/lib/zip/tempfile_bugfixed.rb +0 -195
  127. data/lib/zip/zip.rb +0 -56
  128. data/lib/zip/zip_central_directory.rb +0 -135
  129. data/lib/zip/zip_entry.rb +0 -638
  130. data/lib/zip/zip_entry_set.rb +0 -77
  131. data/lib/zip/zip_extra_field.rb +0 -213
  132. data/lib/zip/zip_file.rb +0 -340
  133. data/lib/zip/zip_input_stream.rb +0 -144
  134. data/lib/zip/zip_output_stream.rb +0 -173
  135. data/lib/zip/zip_streamable_stream.rb +0 -47
data/lib/zip/entry.rb ADDED
@@ -0,0 +1,700 @@
1
+ require 'pathname'
2
+ module Zip
3
+ class Entry
4
+ STORED = 0
5
+ DEFLATED = 8
6
+ # Language encoding flag (EFS) bit
7
+ EFS = 0b100000000000
8
+
9
+ attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
10
+ :name, :size, :local_header_offset, :zipfile, :fstype, :external_file_attributes,
11
+ :internal_file_attributes,
12
+ :gp_flags, :header_signature, :follow_symlinks,
13
+ :restore_times, :restore_permissions, :restore_ownership,
14
+ :unix_uid, :unix_gid, :unix_perms,
15
+ :dirty
16
+ attr_reader :ftype, :filepath # :nodoc:
17
+
18
+ def set_default_vars_values
19
+ @local_header_offset = 0
20
+ @local_header_size = nil # not known until local entry is created or read
21
+ @internal_file_attributes = 1
22
+ @external_file_attributes = 0
23
+ @header_signature = ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
24
+
25
+ @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT
26
+ @version = VERSION_MADE_BY
27
+
28
+ @ftype = nil # unspecified or unknown
29
+ @filepath = nil
30
+ @gp_flags = 0
31
+ if ::Zip.unicode_names
32
+ @gp_flags |= EFS
33
+ @version = 63
34
+ end
35
+ @follow_symlinks = false
36
+
37
+ @restore_times = true
38
+ @restore_permissions = false
39
+ @restore_ownership = false
40
+ # BUG: need an extra field to support uid/gid's
41
+ @unix_uid = nil
42
+ @unix_gid = nil
43
+ @unix_perms = nil
44
+ # @posix_acl = nil
45
+ # @ntfs_acl = nil
46
+ @dirty = false
47
+ end
48
+
49
+ def check_name(name)
50
+ return unless name.start_with?('/')
51
+ raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
52
+ end
53
+
54
+ def initialize(*args)
55
+ name = args[1] || ''
56
+ check_name(name)
57
+
58
+ set_default_vars_values
59
+ @fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX
60
+
61
+ @zipfile = args[0] || ''
62
+ @name = name
63
+ @comment = args[2] || ''
64
+ @extra = args[3] || ''
65
+ @compressed_size = args[4] || 0
66
+ @crc = args[5] || 0
67
+ @compression_method = args[6] || ::Zip::Entry::DEFLATED
68
+ @size = args[7] || 0
69
+ @time = args[8] || ::Zip::DOSTime.now
70
+
71
+ @ftype = name_is_directory? ? :directory : :file
72
+ @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField)
73
+ end
74
+
75
+ def time
76
+ if @extra['UniversalTime']
77
+ @extra['UniversalTime'].mtime
78
+ elsif @extra['NTFS']
79
+ @extra['NTFS'].mtime
80
+ else
81
+ # Standard time field in central directory has local time
82
+ # under archive creator. Then, we can't get timezone.
83
+ @time
84
+ end
85
+ end
86
+
87
+ alias mtime time
88
+
89
+ def time=(value)
90
+ unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
91
+ @extra.create('UniversalTime')
92
+ end
93
+ (@extra['UniversalTime'] || @extra['NTFS']).mtime = value
94
+ @time = value
95
+ end
96
+
97
+ def file_type_is?(type)
98
+ raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype
99
+ @ftype == type
100
+ end
101
+
102
+ # Dynamic checkers
103
+ %w[directory file symlink].each do |k|
104
+ define_method "#{k}?" do
105
+ file_type_is?(k.to_sym)
106
+ end
107
+ end
108
+
109
+ def name_is_directory? #:nodoc:all
110
+ @name.end_with?('/')
111
+ end
112
+
113
+ # Is the name a relative path, free of `..` patterns that could lead to
114
+ # path traversal attacks? This does NOT handle symlinks; if the path
115
+ # contains symlinks, this check is NOT enough to guarantee safety.
116
+ def name_safe?
117
+ cleanpath = Pathname.new(@name).cleanpath
118
+ return false unless cleanpath.relative?
119
+ root = ::File::SEPARATOR
120
+ naive_expanded_path = ::File.join(root, cleanpath.to_s)
121
+ ::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
122
+ end
123
+
124
+ def local_entry_offset #:nodoc:all
125
+ local_header_offset + @local_header_size
126
+ end
127
+
128
+ def name_size
129
+ @name ? @name.bytesize : 0
130
+ end
131
+
132
+ def extra_size
133
+ @extra ? @extra.local_size : 0
134
+ end
135
+
136
+ def comment_size
137
+ @comment ? @comment.bytesize : 0
138
+ end
139
+
140
+ def calculate_local_header_size #:nodoc:all
141
+ LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_size
142
+ end
143
+
144
+ # check before rewriting an entry (after file sizes are known)
145
+ # that we didn't change the header size (and thus clobber file data or something)
146
+ def verify_local_header_size!
147
+ return if @local_header_size.nil?
148
+ new_size = calculate_local_header_size
149
+ raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size
150
+ end
151
+
152
+ def cdir_header_size #:nodoc:all
153
+ CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size +
154
+ (@extra ? @extra.c_dir_size : 0) + comment_size
155
+ end
156
+
157
+ def next_header_offset #:nodoc:all
158
+ local_entry_offset + compressed_size + data_descriptor_size
159
+ end
160
+
161
+ # Extracts entry to file dest_path (defaults to @name).
162
+ # NB: The caller is responsible for making sure dest_path is safe, if it
163
+ # is passed.
164
+ def extract(dest_path = nil, &block)
165
+ if dest_path.nil? && !name_safe?
166
+ puts "WARNING: skipped #{@name} as unsafe"
167
+ return self
168
+ end
169
+
170
+ dest_path ||= @name
171
+ block ||= proc { ::Zip.on_exists_proc }
172
+
173
+ if directory? || file? || symlink?
174
+ __send__("create_#{@ftype}", dest_path, &block)
175
+ else
176
+ raise "unknown file type #{inspect}"
177
+ end
178
+
179
+ self
180
+ end
181
+
182
+ def to_s
183
+ @name
184
+ end
185
+
186
+ class << self
187
+ def read_zip_short(io) # :nodoc:
188
+ io.read(2).unpack('v')[0]
189
+ end
190
+
191
+ def read_zip_long(io) # :nodoc:
192
+ io.read(4).unpack('V')[0]
193
+ end
194
+
195
+ def read_zip_64_long(io) # :nodoc:
196
+ io.read(8).unpack('Q<')[0]
197
+ end
198
+
199
+ def read_c_dir_entry(io) #:nodoc:all
200
+ path = if io.respond_to?(:path)
201
+ io.path
202
+ else
203
+ io
204
+ end
205
+ entry = new(path)
206
+ entry.read_c_dir_entry(io)
207
+ entry
208
+ rescue Error
209
+ nil
210
+ end
211
+
212
+ def read_local_entry(io)
213
+ entry = new(io)
214
+ entry.read_local_entry(io)
215
+ entry
216
+ rescue Error
217
+ nil
218
+ end
219
+ end
220
+
221
+ public
222
+
223
+ def unpack_local_entry(buf)
224
+ @header_signature,
225
+ @version,
226
+ @fstype,
227
+ @gp_flags,
228
+ @compression_method,
229
+ @last_mod_time,
230
+ @last_mod_date,
231
+ @crc,
232
+ @compressed_size,
233
+ @size,
234
+ @name_length,
235
+ @extra_length = buf.unpack('VCCvvvvVVVvv')
236
+ end
237
+
238
+ def read_local_entry(io) #:nodoc:all
239
+ @local_header_offset = io.tell
240
+
241
+ static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
242
+
243
+ unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
244
+ raise Error, 'Premature end of file. Not enough data for zip entry local header'
245
+ end
246
+
247
+ unpack_local_entry(static_sized_fields_buf)
248
+
249
+ unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE
250
+ raise ::Zip::Error, "Zip local header magic not found at location '#{local_header_offset}'"
251
+ end
252
+ set_time(@last_mod_date, @last_mod_time)
253
+
254
+ @name = io.read(@name_length)
255
+ extra = io.read(@extra_length)
256
+
257
+ @name.tr!('\\', '/')
258
+ if ::Zip.force_entry_names_encoding
259
+ @name.force_encoding(::Zip.force_entry_names_encoding)
260
+ end
261
+
262
+ if extra && extra.bytesize != @extra_length
263
+ raise ::Zip::Error, 'Truncated local zip entry header'
264
+ else
265
+ if @extra.is_a?(::Zip::ExtraField)
266
+ @extra.merge(extra) if extra
267
+ else
268
+ @extra = ::Zip::ExtraField.new(extra)
269
+ end
270
+ end
271
+ parse_zip64_extra(true)
272
+ @local_header_size = calculate_local_header_size
273
+ end
274
+
275
+ def pack_local_entry
276
+ zip64 = @extra['Zip64']
277
+ [::Zip::LOCAL_ENTRY_SIGNATURE,
278
+ @version_needed_to_extract, # version needed to extract
279
+ @gp_flags, # @gp_flags
280
+ @compression_method,
281
+ @time.to_binary_dos_time, # @last_mod_time
282
+ @time.to_binary_dos_date, # @last_mod_date
283
+ @crc,
284
+ zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
285
+ zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
286
+ name_size,
287
+ @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
288
+ end
289
+
290
+ def write_local_entry(io, rewrite = false) #:nodoc:all
291
+ prep_zip64_extra(true)
292
+ verify_local_header_size! if rewrite
293
+ @local_header_offset = io.tell
294
+
295
+ io << pack_local_entry
296
+
297
+ io << @name
298
+ io << @extra.to_local_bin if @extra
299
+ @local_header_size = io.tell - @local_header_offset
300
+ end
301
+
302
+ def unpack_c_dir_entry(buf)
303
+ @header_signature,
304
+ @version, # version of encoding software
305
+ @fstype, # filesystem type
306
+ @version_needed_to_extract,
307
+ @gp_flags,
308
+ @compression_method,
309
+ @last_mod_time,
310
+ @last_mod_date,
311
+ @crc,
312
+ @compressed_size,
313
+ @size,
314
+ @name_length,
315
+ @extra_length,
316
+ @comment_length,
317
+ _, # diskNumberStart
318
+ @internal_file_attributes,
319
+ @external_file_attributes,
320
+ @local_header_offset,
321
+ @name,
322
+ @extra,
323
+ @comment = buf.unpack('VCCvvvvvVVVvvvvvVV')
324
+ end
325
+
326
+ def set_ftype_from_c_dir_entry
327
+ @ftype = case @fstype
328
+ when ::Zip::FSTYPE_UNIX
329
+ @unix_perms = (@external_file_attributes >> 16) & 0o7777
330
+ case (@external_file_attributes >> 28)
331
+ when ::Zip::FILE_TYPE_DIR
332
+ :directory
333
+ when ::Zip::FILE_TYPE_FILE
334
+ :file
335
+ when ::Zip::FILE_TYPE_SYMLINK
336
+ :symlink
337
+ else
338
+ # best case guess for whether it is a file or not
339
+ # Otherwise this would be set to unknown and that entry would never be able to extracted
340
+ if name_is_directory?
341
+ :directory
342
+ else
343
+ :file
344
+ end
345
+ end
346
+ else
347
+ if name_is_directory?
348
+ :directory
349
+ else
350
+ :file
351
+ end
352
+ end
353
+ end
354
+
355
+ def check_c_dir_entry_static_header_length(buf)
356
+ return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
357
+ raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
358
+ end
359
+
360
+ def check_c_dir_entry_signature
361
+ return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
362
+ raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
363
+ end
364
+
365
+ def check_c_dir_entry_comment_size
366
+ return if @comment && @comment.bytesize == @comment_length
367
+ raise ::Zip::Error, 'Truncated cdir zip entry header'
368
+ end
369
+
370
+ def read_c_dir_extra_field(io)
371
+ if @extra.is_a?(::Zip::ExtraField)
372
+ @extra.merge(io.read(@extra_length))
373
+ else
374
+ @extra = ::Zip::ExtraField.new(io.read(@extra_length))
375
+ end
376
+ end
377
+
378
+ def read_c_dir_entry(io) #:nodoc:all
379
+ static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
380
+ check_c_dir_entry_static_header_length(static_sized_fields_buf)
381
+ unpack_c_dir_entry(static_sized_fields_buf)
382
+ check_c_dir_entry_signature
383
+ set_time(@last_mod_date, @last_mod_time)
384
+ @name = io.read(@name_length)
385
+ if ::Zip.force_entry_names_encoding
386
+ @name.force_encoding(::Zip.force_entry_names_encoding)
387
+ end
388
+ read_c_dir_extra_field(io)
389
+ @comment = io.read(@comment_length)
390
+ check_c_dir_entry_comment_size
391
+ set_ftype_from_c_dir_entry
392
+ parse_zip64_extra(false)
393
+ end
394
+
395
+ def file_stat(path) # :nodoc:
396
+ if @follow_symlinks
397
+ ::File.stat(path)
398
+ else
399
+ ::File.lstat(path)
400
+ end
401
+ end
402
+
403
+ def get_extra_attributes_from_path(path) # :nodoc:
404
+ return if Zip::RUNNING_ON_WINDOWS
405
+ stat = file_stat(path)
406
+ @unix_uid = stat.uid
407
+ @unix_gid = stat.gid
408
+ @unix_perms = stat.mode & 0o7777
409
+ end
410
+
411
+ def set_unix_permissions_on_path(dest_path)
412
+ # BUG: does not update timestamps into account
413
+ # ignore setuid/setgid bits by default. honor if @restore_ownership
414
+ unix_perms_mask = 0o1777
415
+ unix_perms_mask = 0o7777 if @restore_ownership
416
+ ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
417
+ ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
418
+ # File::utimes()
419
+ end
420
+
421
+ def set_extra_attributes_on_path(dest_path) # :nodoc:
422
+ return unless file? || directory?
423
+
424
+ case @fstype
425
+ when ::Zip::FSTYPE_UNIX
426
+ set_unix_permissions_on_path(dest_path)
427
+ end
428
+ end
429
+
430
+ def pack_c_dir_entry
431
+ zip64 = @extra['Zip64']
432
+ [
433
+ @header_signature,
434
+ @version, # version of encoding software
435
+ @fstype, # filesystem type
436
+ @version_needed_to_extract, # @versionNeededToExtract
437
+ @gp_flags, # @gp_flags
438
+ @compression_method,
439
+ @time.to_binary_dos_time, # @last_mod_time
440
+ @time.to_binary_dos_date, # @last_mod_date
441
+ @crc,
442
+ zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
443
+ zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
444
+ name_size,
445
+ @extra ? @extra.c_dir_size : 0,
446
+ comment_size,
447
+ zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
448
+ @internal_file_attributes, # file type (binary=0, text=1)
449
+ @external_file_attributes, # native filesystem attributes
450
+ zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
451
+ @name,
452
+ @extra,
453
+ @comment
454
+ ].pack('VCCvvvvvVVVvvvvvVV')
455
+ end
456
+
457
+ def write_c_dir_entry(io) #:nodoc:all
458
+ prep_zip64_extra(false)
459
+ case @fstype
460
+ when ::Zip::FSTYPE_UNIX
461
+ ft = case @ftype
462
+ when :file
463
+ @unix_perms ||= 0o644
464
+ ::Zip::FILE_TYPE_FILE
465
+ when :directory
466
+ @unix_perms ||= 0o755
467
+ ::Zip::FILE_TYPE_DIR
468
+ when :symlink
469
+ @unix_perms ||= 0o755
470
+ ::Zip::FILE_TYPE_SYMLINK
471
+ end
472
+
473
+ unless ft.nil?
474
+ @external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
475
+ end
476
+ end
477
+
478
+ io << pack_c_dir_entry
479
+
480
+ io << @name
481
+ io << (@extra ? @extra.to_c_dir_bin : '')
482
+ io << @comment
483
+ end
484
+
485
+ def ==(other)
486
+ return false unless other.class == self.class
487
+ # Compares contents of local entry and exposed fields
488
+ keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k|
489
+ other.__send__(k.to_sym) == __send__(k.to_sym)
490
+ end
491
+ keys_equal && time.dos_equals(other.time)
492
+ end
493
+
494
+ def <=>(other)
495
+ to_s <=> other.to_s
496
+ end
497
+
498
+ # Returns an IO like object for the given ZipEntry.
499
+ # Warning: may behave weird with symlinks.
500
+ def get_input_stream(&block)
501
+ if @ftype == :directory
502
+ yield ::Zip::NullInputStream if block_given?
503
+ ::Zip::NullInputStream
504
+ elsif @filepath
505
+ case @ftype
506
+ when :file
507
+ ::File.open(@filepath, 'rb', &block)
508
+ when :symlink
509
+ linkpath = ::File.readlink(@filepath)
510
+ stringio = ::StringIO.new(linkpath)
511
+ yield(stringio) if block_given?
512
+ stringio
513
+ else
514
+ raise "unknown @file_type #{@ftype}"
515
+ end
516
+ else
517
+ zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
518
+ zis.instance_variable_set(:@complete_entry, self)
519
+ zis.get_next_entry
520
+ if block_given?
521
+ begin
522
+ yield(zis)
523
+ ensure
524
+ zis.close
525
+ end
526
+ else
527
+ zis
528
+ end
529
+ end
530
+ end
531
+
532
+ def gather_fileinfo_from_srcpath(src_path) # :nodoc:
533
+ stat = file_stat(src_path)
534
+ @ftype = case stat.ftype
535
+ when 'file'
536
+ if name_is_directory?
537
+ raise ArgumentError,
538
+ "entry name '#{newEntry}' indicates directory entry, but " \
539
+ "'#{src_path}' is not a directory"
540
+ end
541
+ :file
542
+ when 'directory'
543
+ @name += '/' unless name_is_directory?
544
+ :directory
545
+ when 'link'
546
+ if name_is_directory?
547
+ raise ArgumentError,
548
+ "entry name '#{newEntry}' indicates directory entry, but " \
549
+ "'#{src_path}' is not a directory"
550
+ end
551
+ :symlink
552
+ else
553
+ raise "unknown file type: #{src_path.inspect} #{stat.inspect}"
554
+ end
555
+
556
+ @filepath = src_path
557
+ get_extra_attributes_from_path(@filepath)
558
+ end
559
+
560
+ def write_to_zip_output_stream(zip_output_stream) #:nodoc:all
561
+ if @ftype == :directory
562
+ zip_output_stream.put_next_entry(self, nil, nil, ::Zip::Entry::STORED)
563
+ elsif @filepath
564
+ zip_output_stream.put_next_entry(self, nil, nil, compression_method || ::Zip::Entry::DEFLATED)
565
+ get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) }
566
+ else
567
+ zip_output_stream.copy_raw_entry(self)
568
+ end
569
+ end
570
+
571
+ def parent_as_string
572
+ entry_name = name.chomp('/')
573
+ slash_index = entry_name.rindex('/')
574
+ slash_index ? entry_name.slice(0, slash_index + 1) : nil
575
+ end
576
+
577
+ def get_raw_input_stream(&block)
578
+ if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
579
+ yield @zipfile
580
+ else
581
+ ::File.open(@zipfile, 'rb', &block)
582
+ end
583
+ end
584
+
585
+ def clean_up
586
+ # By default, do nothing
587
+ end
588
+
589
+ private
590
+
591
+ def set_time(binary_dos_date, binary_dos_time)
592
+ @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
593
+ rescue ArgumentError
594
+ warn 'Invalid date/time in zip entry' if ::Zip.warn_invalid_date
595
+ end
596
+
597
+ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
598
+ if ::File.exist?(dest_path) && !yield(self, dest_path)
599
+ raise ::Zip::DestinationFileExistsError,
600
+ "Destination '#{dest_path}' already exists"
601
+ end
602
+ ::File.open(dest_path, 'wb') do |os|
603
+ get_input_stream do |is|
604
+ set_extra_attributes_on_path(dest_path)
605
+
606
+ bytes_written = 0
607
+ warned = false
608
+ buf = ''.dup
609
+ while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
610
+ os << buf
611
+ bytes_written += buf.bytesize
612
+ if bytes_written > size && !warned
613
+ message = "Entry #{name} should be #{size}B but is larger when inflated"
614
+ if ::Zip.validate_entry_sizes
615
+ raise ::Zip::EntrySizeError, message
616
+ else
617
+ puts "WARNING: #{message}"
618
+ warned = true
619
+ end
620
+ end
621
+ end
622
+ end
623
+ end
624
+ end
625
+
626
+ def create_directory(dest_path)
627
+ return if ::File.directory?(dest_path)
628
+ if ::File.exist?(dest_path)
629
+ if block_given? && yield(self, dest_path)
630
+ ::FileUtils.rm_f dest_path
631
+ else
632
+ raise ::Zip::DestinationFileExistsError,
633
+ "Cannot create directory '#{dest_path}'. " \
634
+ 'A file already exists with that name'
635
+ end
636
+ end
637
+ ::FileUtils.mkdir_p(dest_path)
638
+ set_extra_attributes_on_path(dest_path)
639
+ end
640
+
641
+ # BUG: create_symlink() does not use &block
642
+ def create_symlink(dest_path)
643
+ # TODO: Symlinks pose security challenges. Symlink support temporarily
644
+ # removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
645
+ puts "WARNING: skipped symlink #{dest_path}"
646
+ end
647
+
648
+ # apply missing data from the zip64 extra information field, if present
649
+ # (required when file sizes exceed 2**32, but can be used for all files)
650
+ def parse_zip64_extra(for_local_header) #:nodoc:all
651
+ return if @extra['Zip64'].nil?
652
+ if for_local_header
653
+ @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
654
+ else
655
+ @size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(@size, @compressed_size, @local_header_offset)
656
+ end
657
+ end
658
+
659
+ def data_descriptor_size
660
+ (@gp_flags & 0x0008) > 0 ? 16 : 0
661
+ end
662
+
663
+ # create a zip64 extra information field if we need one
664
+ def prep_zip64_extra(for_local_header) #:nodoc:all
665
+ return unless ::Zip.write_zip64_support
666
+ need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
667
+ need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
668
+ if need_zip64
669
+ @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
670
+ @extra.delete('Zip64Placeholder')
671
+ zip64 = @extra.create('Zip64')
672
+ if for_local_header
673
+ # local header always includes size and compressed size
674
+ zip64.original_size = @size
675
+ zip64.compressed_size = @compressed_size
676
+ else
677
+ # central directory entry entries include whichever fields are necessary
678
+ zip64.original_size = @size if @size >= 0xFFFFFFFF
679
+ zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
680
+ zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
681
+ end
682
+ else
683
+ @extra.delete('Zip64')
684
+
685
+ # if this is a local header entry, create a placeholder
686
+ # so we have room to write a zip64 extra field afterward
687
+ # (we won't know if it's needed until the file data is written)
688
+ if for_local_header
689
+ @extra.create('Zip64Placeholder')
690
+ else
691
+ @extra.delete('Zip64Placeholder')
692
+ end
693
+ end
694
+ end
695
+ end
696
+ end
697
+
698
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
699
+ # rubyzip is free software; you can redistribute it and/or
700
+ # modify it under the terms of the ruby license.