superp-rubyzip 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,135 @@
1
+ module Zip
2
+ class CentralDirectory
3
+ include Enumerable
4
+
5
+ END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50
6
+ MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE = 65536 + 18
7
+ STATIC_EOCD_SIZE = 22
8
+
9
+ attr_reader :comment
10
+
11
+ # Returns an Enumerable containing the entries.
12
+ def entries
13
+ @entry_set.entries
14
+ end
15
+
16
+ def initialize(entries = EntrySet.new, comment = "") #:nodoc:
17
+ super()
18
+ @entry_set = entries.kind_of?(EntrySet) ? entries : EntrySet.new(entries)
19
+ @comment = comment
20
+ end
21
+
22
+ def write_to_stream(io) #:nodoc:
23
+ offset = io.tell
24
+ @entry_set.each { |entry| entry.write_c_dir_entry(io) }
25
+ write_e_o_c_d(io, offset)
26
+ end
27
+
28
+ def write_e_o_c_d(io, offset) #:nodoc:
29
+ tmp = [
30
+ END_OF_CENTRAL_DIRECTORY_SIGNATURE,
31
+ 0, # @numberOfThisDisk
32
+ 0, # @numberOfDiskWithStartOfCDir
33
+ @entry_set ? @entry_set.size : 0,
34
+ @entry_set ? @entry_set.size : 0,
35
+ cdir_size,
36
+ offset,
37
+ @comment ? @comment.length : 0
38
+ ]
39
+ io << tmp.pack('VvvvvVVv')
40
+ io << @comment
41
+ end
42
+
43
+ private :write_e_o_c_d
44
+
45
+ def cdir_size #:nodoc:
46
+ # does not include eocd
47
+ @entry_set.inject(0) do |value, entry|
48
+ entry.cdir_header_size + value
49
+ end
50
+ end
51
+
52
+ private :cdir_size
53
+
54
+ def read_e_o_c_d(io) #:nodoc:
55
+ buf = get_e_o_c_d(io)
56
+ @numberOfThisDisk = Entry.read_zip_short(buf)
57
+ @numberOfDiskWithStartOfCDir = Entry.read_zip_short(buf)
58
+ @totalNumberOfEntriesInCDirOnThisDisk = Entry.read_zip_short(buf)
59
+ @size = Entry.read_zip_short(buf)
60
+ @sizeInBytes = Entry.read_zip_long(buf)
61
+ @cdirOffset = Entry.read_zip_long(buf)
62
+ commentLength = Entry.read_zip_short(buf)
63
+ if commentLength <= 0
64
+ @comment = buf.slice!(0, buf.size)
65
+ else
66
+ @comment = buf.read(commentLength)
67
+ end
68
+ raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0
69
+ end
70
+
71
+ def read_central_directory_entries(io) #:nodoc:
72
+ begin
73
+ io.seek(@cdirOffset, IO::SEEK_SET)
74
+ rescue Errno::EINVAL
75
+ raise ZipError, "Zip consistency problem while reading central directory entry"
76
+ end
77
+ @entry_set = EntrySet.new
78
+ @size.times do
79
+ tmp = Entry.read_c_dir_entry(io)
80
+ @entry_set << tmp
81
+ end
82
+ end
83
+
84
+ def read_from_stream(io) #:nodoc:
85
+ read_e_o_c_d(io)
86
+ read_central_directory_entries(io)
87
+ end
88
+
89
+ def get_e_o_c_d(io) #:nodoc:
90
+ begin
91
+ io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END)
92
+ rescue Errno::EINVAL
93
+ io.seek(0, IO::SEEK_SET)
94
+ end
95
+ buf = io.read
96
+ sigIndex = buf.rindex([END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack('V'))
97
+ raise ZipError, "Zip end of central directory signature not found" unless sigIndex
98
+ buf = buf.slice!((sigIndex + 4)..(buf.bytesize))
99
+
100
+ def buf.read(count)
101
+ slice!(0, count)
102
+ end
103
+
104
+ buf
105
+ end
106
+
107
+ # For iterating over the entries.
108
+ def each(&proc)
109
+ @entry_set.each(&proc)
110
+ end
111
+
112
+ # Returns the number of entries in the central directory (and
113
+ # consequently in the zip archive).
114
+ def size
115
+ @entry_set.size
116
+ end
117
+
118
+ def CentralDirectory.read_from_stream(io) #:nodoc:
119
+ cdir = new
120
+ cdir.read_from_stream(io)
121
+ return cdir
122
+ rescue ZipError
123
+ return nil
124
+ end
125
+
126
+ def ==(other) #:nodoc:
127
+ return false unless other.kind_of?(CentralDirectory)
128
+ @entry_set.entries.sort == other.entries.sort && comment == other.comment
129
+ end
130
+ end
131
+ end
132
+
133
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
134
+ # rubyzip is free software; you can redistribute it and/or
135
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,10 @@
1
+ module Zip
2
+ class Compressor #:nodoc:all
3
+ def finish
4
+ end
5
+ end
6
+ end
7
+
8
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
9
+ # rubyzip is free software; you can redistribute it and/or
10
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,61 @@
1
+ module Zip
2
+ RUNNING_ON_WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/i
3
+
4
+ CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50
5
+ CDIR_ENTRY_STATIC_HEADER_LENGTH = 46
6
+
7
+ LOCAL_ENTRY_SIGNATURE = 0x04034b50
8
+ LOCAL_ENTRY_STATIC_HEADER_LENGTH = 30
9
+ LOCAL_ENTRY_TRAILING_DESCRIPTOR_LENGTH = 4+4+4
10
+ VERSION_NEEDED_TO_EXTRACT = 20
11
+
12
+ FILE_TYPE_FILE = 010
13
+ FILE_TYPE_DIR = 004
14
+ FILE_TYPE_SYMLINK = 012
15
+
16
+ FSTYPE_FAT = 0
17
+ FSTYPE_AMIGA = 1
18
+ FSTYPE_VMS = 2
19
+ FSTYPE_UNIX = 3
20
+ FSTYPE_VM_CMS = 4
21
+ FSTYPE_ATARI = 5
22
+ FSTYPE_HPFS = 6
23
+ FSTYPE_MAC = 7
24
+ FSTYPE_Z_SYSTEM = 8
25
+ FSTYPE_CPM = 9
26
+ FSTYPE_TOPS20 = 10
27
+ FSTYPE_NTFS = 11
28
+ FSTYPE_QDOS = 12
29
+ FSTYPE_ACORN = 13
30
+ FSTYPE_VFAT = 14
31
+ FSTYPE_MVS = 15
32
+ FSTYPE_BEOS = 16
33
+ FSTYPE_TANDEM = 17
34
+ FSTYPE_THEOS = 18
35
+ FSTYPE_MAC_OSX = 19
36
+ FSTYPE_ATHEOS = 30
37
+
38
+ FSTYPES = {
39
+ FSTYPE_FAT => 'FAT'.freeze,
40
+ FSTYPE_AMIGA => 'Amiga'.freeze,
41
+ FSTYPE_VMS => 'VMS (Vax or Alpha AXP)'.freeze,
42
+ FSTYPE_UNIX => 'Unix'.freeze,
43
+ FSTYPE_VM_CMS => 'VM/CMS'.freeze,
44
+ FSTYPE_ATARI => 'Atari ST'.freeze,
45
+ FSTYPE_HPFS => 'OS/2 or NT HPFS'.freeze,
46
+ FSTYPE_MAC => 'Macintosh'.freeze,
47
+ FSTYPE_Z_SYSTEM => 'Z-System'.freeze,
48
+ FSTYPE_CPM => 'CP/M'.freeze,
49
+ FSTYPE_TOPS20 => 'TOPS-20'.freeze,
50
+ FSTYPE_NTFS => 'NTFS'.freeze,
51
+ FSTYPE_QDOS => 'SMS/QDOS'.freeze,
52
+ FSTYPE_ACORN => 'Acorn RISC OS'.freeze,
53
+ FSTYPE_VFAT => 'Win32 VFAT'.freeze,
54
+ FSTYPE_MVS => 'MVS'.freeze,
55
+ FSTYPE_BEOS => 'BeOS'.freeze,
56
+ FSTYPE_TANDEM => 'Tandem NSK'.freeze,
57
+ FSTYPE_THEOS => 'Theos'.freeze,
58
+ FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)'.freeze,
59
+ FSTYPE_ATHEOS => 'AtheOS'.freeze,
60
+ }.freeze
61
+ end
@@ -0,0 +1,13 @@
1
+ module Zip
2
+ class Decompressor #:nodoc:all
3
+ CHUNK_SIZE=32768
4
+ def initialize(input_stream)
5
+ super()
6
+ @input_stream=input_stream
7
+ end
8
+ end
9
+ end
10
+
11
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
12
+ # rubyzip is free software; you can redistribute it and/or
13
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,29 @@
1
+ module Zip
2
+ class Deflater < Compressor #:nodoc:all
3
+
4
+ def initialize(output_stream, level = ::Zlib::DEFAULT_COMPRESSION)
5
+ super()
6
+ @output_stream = output_stream
7
+ @zlib_deflater = ::Zlib::Deflate.new(level, -::Zlib::MAX_WBITS)
8
+ @size = 0
9
+ @crc = ::Zlib.crc32
10
+ end
11
+
12
+ def << (data)
13
+ val = data.to_s
14
+ @crc = Zlib::crc32(val, @crc)
15
+ @size += val.bytesize
16
+ @output_stream << @zlib_deflater.deflate(data)
17
+ end
18
+
19
+ def finish
20
+ @output_stream << @zlib_deflater.finish until @zlib_deflater.finished?
21
+ end
22
+
23
+ attr_reader :size, :crc
24
+ end
25
+ end
26
+
27
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
28
+ # rubyzip is free software; you can redistribute it and/or
29
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,49 @@
1
+ module Zip
2
+ class DOSTime < Time #:nodoc:all
3
+
4
+ #MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
5
+
6
+ # Register CX, the Time:
7
+ # Bits 0-4 2 second increments (0-29)
8
+ # Bits 5-10 minutes (0-59)
9
+ # bits 11-15 hours (0-24)
10
+
11
+ # Register DX, the Date:
12
+ # Bits 0-4 day (1-31)
13
+ # bits 5-8 month (1-12)
14
+ # bits 9-15 year (four digit year minus 1980)
15
+
16
+ def to_binary_dos_time
17
+ (sec/2) +
18
+ (min << 5) +
19
+ (hour << 11)
20
+ end
21
+
22
+ def to_binary_dos_date
23
+ (day) +
24
+ (month << 5) +
25
+ ((year - 1980) << 9)
26
+ end
27
+
28
+ # Dos time is only stored with two seconds accuracy
29
+ def dos_equals(other)
30
+ to_i/2 == other.to_i/2
31
+ end
32
+
33
+ def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
34
+ second = 2 * (0b11111 & binaryDosTime)
35
+ minute = (0b11111100000 & binaryDosTime) >> 5
36
+ hour = (0b1111100000000000 & binaryDosTime) >> 11
37
+ day = (0b11111 & binaryDosDate)
38
+ month = (0b111100000 & binaryDosDate) >> 5
39
+ year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980
40
+ begin
41
+ self.local(year, month, day, hour, minute, second)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
48
+ # rubyzip is free software; you can redistribute it and/or
49
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,609 @@
1
+ module Zip
2
+ class Entry
3
+ STORED = 0
4
+ DEFLATED = 8
5
+ # Language encoding flag (EFS) bit
6
+ EFS = 0b100000000000
7
+
8
+ attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
9
+ :name, :size, :local_header_offset, :zipfile, :fstype, :external_file_attributes,
10
+ :gp_flags, :header_signature, :follow_symlinks,
11
+ :restore_times, :restore_permissions, :restore_ownership,
12
+ :unix_uid, :unix_gid, :unix_perms,
13
+ :dirty
14
+ attr_reader :ftype, :filepath # :nodoc:
15
+
16
+ def set_default_vars_values
17
+ @local_header_offset = 0
18
+ @local_header_size = 0
19
+ @internal_file_attributes = 1
20
+ @external_file_attributes = 0
21
+ @header_signature = ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
22
+
23
+ @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT
24
+ @version = 52 # this library's version
25
+
26
+ @ftype = nil # unspecified or unknown
27
+ @filepath = nil
28
+ @gp_flags = 0
29
+ if ::Zip.unicode_names
30
+ @gp_flags |= EFS
31
+ @version = 63
32
+ end
33
+ @follow_symlinks = false
34
+
35
+ @restore_times = true
36
+ @restore_permissions = false
37
+ @restore_ownership = false
38
+ # BUG: need an extra field to support uid/gid's
39
+ @unix_uid = nil
40
+ @unix_gid = nil
41
+ @unix_perms = nil
42
+ #@posix_acl = nil
43
+ #@ntfs_acl = nil
44
+ @dirty = false
45
+ end
46
+
47
+ def check_name(name)
48
+ if name.start_with?('/')
49
+ raise ::Zip::ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
50
+ end
51
+ end
52
+
53
+ def initialize(*args)
54
+ name = args[1] || ''
55
+ check_name(name)
56
+
57
+ set_default_vars_values
58
+ @fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX
59
+
60
+ @zipfile = args[0] || ''
61
+ @name = name
62
+ @comment = args[2] || ''
63
+ @extra = args[3] || ''
64
+ @compressed_size = args[4] || 0
65
+ @crc = args[5] || 0
66
+ @compression_method = args[6] || ::Zip::Entry::DEFLATED
67
+ @size = args[7] || 0
68
+ @time = args[8] || ::Zip::DOSTime.now
69
+
70
+ @ftype = name_is_directory? ? :directory : :file
71
+ @extra = ::Zip::ExtraField.new(@extra.to_s) unless ::Zip::ExtraField === @extra
72
+ end
73
+
74
+ def time
75
+ if @extra['UniversalTime']
76
+ @extra['UniversalTime'].mtime
77
+ else
78
+ # Standard time field in central directory has local time
79
+ # under archive creator. Then, we can't get timezone.
80
+ @time
81
+ end
82
+ end
83
+
84
+ alias :mtime :time
85
+
86
+ def time=(value)
87
+ unless @extra.member?('UniversalTime')
88
+ @extra.create('UniversalTime')
89
+ end
90
+ @extra['UniversalTime'].mtime = value
91
+ @time = value
92
+ end
93
+
94
+ def file_type_is?(type)
95
+ raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
96
+ @ftype == type
97
+ end
98
+
99
+ # Dynamic checkers
100
+ %w(directory file symlink).each do |k|
101
+ define_method "#{k}?" do
102
+ file_type_is?(k.to_sym)
103
+ end
104
+ end
105
+
106
+ def name_is_directory? #:nodoc:all
107
+ @name.end_with?('/')
108
+ end
109
+
110
+ def local_entry_offset #:nodoc:all
111
+ local_header_offset + @local_header_size
112
+ end
113
+
114
+ def name_size
115
+ @name ? @name.bytesize : 0
116
+ end
117
+
118
+ def extra_size
119
+ @extra ? @extra.local_size : 0
120
+ end
121
+
122
+ def comment_size
123
+ @comment ? @comment.bytesize : 0
124
+ end
125
+
126
+ def calculate_local_header_size #:nodoc:all
127
+ LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_size
128
+ end
129
+
130
+ def cdir_header_size #:nodoc:all
131
+ CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size +
132
+ (@extra ? @extra.c_dir_size : 0) + comment_size
133
+ end
134
+
135
+ def next_header_offset #:nodoc:all
136
+ local_entry_offset + self.compressed_size
137
+ end
138
+
139
+ # Extracts entry to file dest_path (defaults to @name).
140
+ def extract(dest_path = @name, &block)
141
+ block ||= proc { ::Zip.on_exists_proc }
142
+
143
+ if directory? || file? || symlink?
144
+ self.__send__("create_#{@ftype}", dest_path, &block)
145
+ else
146
+ raise RuntimeError, "unknown file type #{self.inspect}"
147
+ end
148
+
149
+ self
150
+ end
151
+
152
+ def to_s
153
+ @name
154
+ end
155
+
156
+ protected
157
+
158
+ class << self
159
+ def read_zip_short(io) # :nodoc:
160
+ io.read(2).unpack('v')[0]
161
+ end
162
+
163
+ def read_zip_long(io) # :nodoc:
164
+ io.read(4).unpack('V')[0]
165
+ end
166
+
167
+ def read_c_dir_entry(io) #:nodoc:all
168
+ entry = new(io.path)
169
+ entry.read_c_dir_entry(io)
170
+ entry
171
+ rescue ZipError
172
+ nil
173
+ end
174
+
175
+ def read_local_entry(io)
176
+ entry = new(io.path)
177
+ entry.read_local_entry(io)
178
+ entry
179
+ rescue ZipError
180
+ nil
181
+ end
182
+
183
+ end
184
+
185
+ public
186
+
187
+ def unpack_local_entry(buf)
188
+ @header_signature,
189
+ @version,
190
+ @fstype,
191
+ @gp_flags,
192
+ @compression_method,
193
+ @last_mod_time,
194
+ @last_mod_date,
195
+ @crc,
196
+ @compressed_size,
197
+ @size,
198
+ @name_length,
199
+ @extra_length = buf.unpack('VCCvvvvVVVvv')
200
+ end
201
+
202
+ def read_local_entry(io) #:nodoc:all
203
+ @local_header_offset = io.tell
204
+
205
+ static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH)
206
+
207
+ unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
208
+ raise ZipError, "Premature end of file. Not enough data for zip entry local header"
209
+ end
210
+
211
+ unpack_local_entry(static_sized_fields_buf)
212
+
213
+ unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE
214
+ raise ::Zip::ZipError, "Zip local header magic not found at location '#{local_header_offset}'"
215
+ end
216
+ set_time(@last_mod_date, @last_mod_time)
217
+
218
+ @name = io.read(@name_length)
219
+ extra = io.read(@extra_length)
220
+
221
+ @name.gsub!('\\', '/')
222
+
223
+ if extra && extra.bytesize != @extra_length
224
+ raise ::Zip::ZipError, "Truncated local zip entry header"
225
+ else
226
+ if ::Zip::ExtraField === @extra
227
+ @extra.merge(extra)
228
+ else
229
+ @extra = ::Zip::ExtraField.new(extra)
230
+ end
231
+ end
232
+ @local_header_size = calculate_local_header_size
233
+ end
234
+
235
+ def pack_local_entry
236
+ [::Zip::LOCAL_ENTRY_SIGNATURE,
237
+ @version_needed_to_extract, # version needed to extract
238
+ @gp_flags, # @gp_flags ,
239
+ @compression_method,
240
+ @time.to_binary_dos_time, # @last_mod_time ,
241
+ @time.to_binary_dos_date, # @last_mod_date ,
242
+ @crc,
243
+ @compressed_size,
244
+ @size,
245
+ name_size,
246
+ @extra ? @extra.local_length : 0].pack('VvvvvvVVVvv')
247
+ end
248
+
249
+ def write_local_entry(io) #:nodoc:all
250
+ @local_header_offset = io.tell
251
+
252
+ io << pack_local_entry
253
+
254
+ io << @name
255
+ io << (@extra ? @extra.to_local_bin : '')
256
+ end
257
+
258
+ def unpack_c_dir_entry(buf)
259
+ @header_signature,
260
+ @version, # version of encoding software
261
+ @fstype, # filesystem type
262
+ @version_needed_to_extract,
263
+ @gp_flags,
264
+ @compression_method,
265
+ @last_mod_time,
266
+ @last_mod_date,
267
+ @crc,
268
+ @compressed_size,
269
+ @size,
270
+ @name_length,
271
+ @extra_length,
272
+ @comment_length,
273
+ _, # diskNumberStart
274
+ @internal_file_attributes,
275
+ @external_file_attributes,
276
+ @local_header_offset,
277
+ @name,
278
+ @extra,
279
+ @comment = buf.unpack('VCCvvvvvVVVvvvvvVV')
280
+ end
281
+
282
+ def set_ftype_from_c_dir_entry
283
+ @ftype = case @fstype
284
+ when ::Zip::FSTYPE_UNIX
285
+ @unix_perms = (@external_file_attributes >> 16) & 07777
286
+ case (@external_file_attributes >> 28)
287
+ when ::Zip::FILE_TYPE_DIR
288
+ :directory
289
+ when ::Zip::FILE_TYPE_FILE
290
+ :file
291
+ when ::Zip::FILE_TYPE_SYMLINK
292
+ :symlink
293
+ else
294
+ #best case guess for whether it is a file or not
295
+ #Otherwise this would be set to unknown and that entry would never be able to extracted
296
+ if name_is_directory?
297
+ :directory
298
+ else
299
+ :file
300
+ end
301
+ end
302
+ else
303
+ if name_is_directory?
304
+ :directory
305
+ else
306
+ :file
307
+ end
308
+ end
309
+ end
310
+
311
+ def check_c_dir_entry_static_header_length(buf)
312
+ unless buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
313
+ raise ZipError, 'Premature end of file. Not enough data for zip cdir entry header'
314
+ end
315
+ end
316
+
317
+ def check_c_dir_entry_signature
318
+ unless header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
319
+ raise ZipError, "Zip local header magic not found at location '#{local_header_offset}'"
320
+ end
321
+ end
322
+
323
+ def check_c_dir_entry_comment_size
324
+ unless @comment && @comment.bytesize == @comment_length
325
+ raise ::Zip::ZipError, "Truncated cdir zip entry header"
326
+ end
327
+ end
328
+
329
+ def read_c_dir_extra_field(io)
330
+ if @extra.is_a?(::Zip::ExtraField)
331
+ @extra.merge(io.read(@extra_length))
332
+ else
333
+ @extra = ::Zip::ExtraField.new(io.read(@extra_length))
334
+ end
335
+ end
336
+
337
+ def read_c_dir_entry(io) #:nodoc:all
338
+ static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
339
+ check_c_dir_entry_static_header_length(static_sized_fields_buf)
340
+ unpack_c_dir_entry(static_sized_fields_buf)
341
+ check_c_dir_entry_signature
342
+ set_time(@last_mod_date, @last_mod_time)
343
+ @name = io.read(@name_length).gsub('\\', '/')
344
+ read_c_dir_extra_field(io)
345
+ @comment = io.read(@comment_length)
346
+ check_c_dir_entry_comment_size
347
+ set_ftype_from_c_dir_entry
348
+ @local_header_size = calculate_local_header_size
349
+ end
350
+
351
+ def file_stat(path) # :nodoc:
352
+ if @follow_symlinks
353
+ ::File::stat(path)
354
+ else
355
+ ::File::lstat(path)
356
+ end
357
+ end
358
+
359
+ def get_extra_attributes_from_path(path) # :nodoc:
360
+ unless Zip::RUNNING_ON_WINDOWS
361
+ stat = file_stat(path)
362
+ @unix_uid = stat.uid
363
+ @unix_gid = stat.gid
364
+ @unix_perms = stat.mode & 07777
365
+ end
366
+ end
367
+
368
+ def set_unix_permissions_on_path(dest_path)
369
+ # BUG: does not update timestamps into account
370
+ # ignore setuid/setgid bits by default. honor if @restore_ownership
371
+ unix_perms_mask = 01777
372
+ unix_perms_mask = 07777 if @restore_ownership
373
+ ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
374
+ ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
375
+ # File::utimes()
376
+ end
377
+
378
+ def set_extra_attributes_on_path(dest_path) # :nodoc:
379
+ return unless (file? || directory?)
380
+
381
+ case @fstype
382
+ when ::Zip::FSTYPE_UNIX
383
+ set_unix_permissions_on_path(dest_path)
384
+ end
385
+ end
386
+
387
+ def pack_c_dir_entry
388
+ [
389
+ @header_signature,
390
+ @version, # version of encoding software
391
+ @fstype, # filesystem type
392
+ @version_needed_to_extract, # @versionNeededToExtract ,
393
+ @gp_flags, # @gp_flags ,
394
+ @compression_method,
395
+ @time.to_binary_dos_time, # @last_mod_time ,
396
+ @time.to_binary_dos_date, # @last_mod_date ,
397
+ @crc,
398
+ @compressed_size,
399
+ @size,
400
+ name_size,
401
+ @extra ? @extra.c_dir_length : 0,
402
+ comment_size,
403
+ 0, # disk number start
404
+ @internal_file_attributes, # file type (binary=0, text=1)
405
+ @external_file_attributes, # native filesystem attributes
406
+ @local_header_offset,
407
+ @name,
408
+ @extra,
409
+ @comment
410
+ ].pack('VCCvvvvvVVVvvvvvVV')
411
+ end
412
+
413
+ def write_c_dir_entry(io) #:nodoc:all
414
+ case @fstype
415
+ when ::Zip::FSTYPE_UNIX
416
+ ft = case @ftype
417
+ when :file
418
+ @unix_perms ||= 0644
419
+ ::Zip::FILE_TYPE_FILE
420
+ when :directory
421
+ @unix_perms ||= 0755
422
+ ::Zip::FILE_TYPE_DIR
423
+ when :symlink
424
+ @unix_perms ||= 0755
425
+ ::Zip::FILE_TYPE_SYMLINK
426
+ end
427
+
428
+ unless ft.nil?
429
+ @external_file_attributes = (ft << 12 | (@unix_perms & 07777)) << 16
430
+ end
431
+ end
432
+
433
+ io << pack_c_dir_entry
434
+
435
+ io << @name
436
+ io << (@extra ? @extra.to_c_dir_bin : '')
437
+ io << @comment
438
+ end
439
+
440
+ def ==(other)
441
+ return false unless other.class == self.class
442
+ # Compares contents of local entry and exposed fields
443
+ keys_equal = %w(compression_method crc compressed_size size name extra filepath).all? do |k|
444
+ other.__send__(k.to_sym) == self.__send__(k.to_sym)
445
+ end
446
+ keys_equal && self.time.dos_equals(other.time)
447
+ end
448
+
449
+ def <=> (other)
450
+ self.to_s <=> other.to_s
451
+ end
452
+
453
+ # Returns an IO like object for the given ZipEntry.
454
+ # Warning: may behave weird with symlinks.
455
+ def get_input_stream(&block)
456
+ if @ftype == :directory
457
+ yield(::Zip::NullInputStream.instance) if block_given?
458
+ ::Zip::NullInputStream.instance
459
+ elsif @filepath
460
+ case @ftype
461
+ when :file
462
+ ::File.open(@filepath, 'rb', &block)
463
+ when :symlink
464
+ linkpath = ::File.readlink(@filepath)
465
+ stringio = ::StringIO.new(linkpath)
466
+ yield(stringio) if block_given?
467
+ stringio
468
+ else
469
+ raise "unknown @file_type #{@ftype}"
470
+ end
471
+ else
472
+ zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
473
+ zis.get_next_entry
474
+ if block_given?
475
+ begin
476
+ yield(zis)
477
+ ensure
478
+ zis.close
479
+ end
480
+ else
481
+ zis
482
+ end
483
+ end
484
+ end
485
+
486
+ def gather_fileinfo_from_srcpath(src_path) # :nodoc:
487
+ stat = file_stat(src_path)
488
+ @ftype = case stat.ftype
489
+ when 'file'
490
+ if name_is_directory?
491
+ raise ArgumentError,
492
+ "entry name '#{newEntry}' indicates directory entry, but "+
493
+ "'#{src_path}' is not a directory"
494
+ end
495
+ :file
496
+ when 'directory'
497
+ @name += "/" unless name_is_directory?
498
+ :directory
499
+ when 'link'
500
+ if name_is_directory?
501
+ raise ArgumentError,
502
+ "entry name '#{newEntry}' indicates directory entry, but "+
503
+ "'#{src_path}' is not a directory"
504
+ end
505
+ :symlink
506
+ else
507
+ raise RuntimeError, "unknown file type: #{src_path.inspect} #{stat.inspect}"
508
+ end
509
+
510
+ @filepath = src_path
511
+ get_extra_attributes_from_path(@filepath)
512
+ end
513
+
514
+ def write_to_zip_output_stream(zip_output_stream) #:nodoc:all
515
+ if @ftype == :directory
516
+ zip_output_stream.put_next_entry(self)
517
+ elsif @filepath
518
+ zip_output_stream.put_next_entry(self, nil, nil, nil)
519
+ get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) }
520
+ else
521
+ zip_output_stream.copy_raw_entry(self)
522
+ end
523
+ end
524
+
525
+ def parent_as_string
526
+ entry_name = name.chomp('/')
527
+ slash_index = entry_name.rindex('/')
528
+ slash_index ? entry_name.slice(0, slash_index+1) : nil
529
+ end
530
+
531
+ def get_raw_input_stream(&block)
532
+ ::File.open(@zipfile, "rb", &block)
533
+ end
534
+
535
+ private
536
+
537
+ def set_time(binary_dos_date, binary_dos_time)
538
+ @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
539
+ rescue ArgumentError
540
+ puts "Invalid date/time in zip entry"
541
+ end
542
+
543
+ def create_file(dest_path, continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
544
+ if ::File.exists?(dest_path) && !yield(self, dest_path)
545
+ raise ::Zip::ZipDestinationFileExistsError,
546
+ "Destination '#{dest_path}' already exists"
547
+ end
548
+ ::File.open(dest_path, "wb") do |os|
549
+ get_input_stream do |is|
550
+ set_extra_attributes_on_path(dest_path)
551
+
552
+ buf = ''
553
+ while buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)
554
+ os << buf
555
+ end
556
+ end
557
+ end
558
+ end
559
+
560
+ def create_directory(dest_path)
561
+ return if ::File.directory?(dest_path)
562
+ if ::File.exists?(dest_path)
563
+ if block_given? && yield(self, dest_path)
564
+ ::FileUtils::rm_f dest_path
565
+ else
566
+ raise ::Zip::ZipDestinationFileExistsError,
567
+ "Cannot create directory '#{dest_path}'. "+
568
+ "A file already exists with that name"
569
+ end
570
+ end
571
+ ::FileUtils.mkdir_p(dest_path)
572
+ set_extra_attributes_on_path(dest_path)
573
+ end
574
+
575
+ # BUG: create_symlink() does not use &block
576
+ def create_symlink(dest_path)
577
+ stat = nil
578
+ begin
579
+ stat = ::File.lstat(dest_path)
580
+ rescue Errno::ENOENT
581
+ end
582
+
583
+ io = get_input_stream
584
+ linkto = io.read
585
+
586
+ if stat
587
+ if stat.symlink?
588
+ if ::File.readlink(dest_path) == linkto
589
+ return
590
+ else
591
+ raise ZipDestinationFileExistsError,
592
+ "Cannot create symlink '#{dest_path}'. "+
593
+ "A symlink already exists with that name"
594
+ end
595
+ else
596
+ raise ZipDestinationFileExistsError,
597
+ "Cannot create symlink '#{dest_path}'. "+
598
+ "A file already exists with that name"
599
+ end
600
+ end
601
+
602
+ ::File.symlink(linkto, dest_path)
603
+ end
604
+ end
605
+ end
606
+
607
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
608
+ # rubyzip is free software; you can redistribute it and/or
609
+ # modify it under the terms of the ruby license.