rubyzip 0.9.1 → 2.3.2

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