rubyzip 0.9.1 → 2.3.2

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