rubyzip 0.5.7

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

Potentially problematic release.


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

@@ -0,0 +1,609 @@
1
+ require 'zip/zip'
2
+
3
+ module Zip
4
+
5
+ # The ZipFileSystem API provides an API for accessing entries in
6
+ # a zip archive that is similar to ruby's builtin File and Dir
7
+ # classes.
8
+ #
9
+ # Requiring 'zip/zipfilesystem' includes this module in ZipFile
10
+ # making the methods in this module available on ZipFile objects.
11
+ #
12
+ # Using this API the following example creates a new zip file
13
+ # <code>my.zip</code> containing a normal entry with the name
14
+ # <code>first.txt</code>, a directory entry named <code>mydir</code>
15
+ # and finally another normal entry named <code>second.txt</code>
16
+ #
17
+ # require 'zip/zipfilesystem'
18
+ #
19
+ # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
20
+ # |zipfile|
21
+ # zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" }
22
+ # zipfile.dir.mkdir("mydir")
23
+ # zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" }
24
+ # }
25
+ #
26
+ # Reading is as easy as writing, as the following example shows. The
27
+ # example writes the contents of <code>first.txt</code> from zip archive
28
+ # <code>my.zip</code> to standard out.
29
+ #
30
+ # require 'zip/zipfilesystem'
31
+ #
32
+ # Zip::ZipFile.open("my.zip") {
33
+ # |zipfile|
34
+ # puts zipfile.file.read("first.txt")
35
+ # }
36
+
37
+ module ZipFileSystem
38
+
39
+ def initialize # :nodoc:
40
+ mappedZip = ZipFileNameMapper.new(self)
41
+ @zipFsDir = ZipFsDir.new(mappedZip)
42
+ @zipFsFile = ZipFsFile.new(mappedZip)
43
+ @zipFsDir.file = @zipFsFile
44
+ @zipFsFile.dir = @zipFsDir
45
+ end
46
+
47
+ # Returns a ZipFsDir which is much like ruby's builtin Dir (class)
48
+ # object, except it works on the ZipFile on which this method is
49
+ # invoked
50
+ def dir
51
+ @zipFsDir
52
+ end
53
+
54
+ # Returns a ZipFsFile which is much like ruby's builtin File (class)
55
+ # object, except it works on the ZipFile on which this method is
56
+ # invoked
57
+ def file
58
+ @zipFsFile
59
+ end
60
+
61
+ # Instances of this class are normally accessed via the accessor
62
+ # ZipFile::file. An instance of ZipFsFile behaves like ruby's
63
+ # builtin File (class) object, except it works on ZipFile entries.
64
+ #
65
+ # The individual methods are not documented due to their
66
+ # similarity with the methods in File
67
+ class ZipFsFile
68
+
69
+ attr_writer :dir
70
+ # protected :dir
71
+
72
+ class ZipFsStat
73
+ def initialize(zipFsFile, entryName)
74
+ @zipFsFile = zipFsFile
75
+ @entryName = entryName
76
+ end
77
+
78
+ def forward_invoke(msg)
79
+ @zipFsFile.send(msg, @entryName)
80
+ end
81
+
82
+ def kind_of?(t)
83
+ super || t == ::File::Stat
84
+ end
85
+
86
+ forward_message :forward_invoke, :file?, :directory?, :pipe?, :chardev?
87
+ forward_message :forward_invoke, :symlink?, :socket?, :blockdev?
88
+ forward_message :forward_invoke, :readable?, :readable_real?
89
+ forward_message :forward_invoke, :writable?, :writable_real?
90
+ forward_message :forward_invoke, :executable?, :executable_real?
91
+ forward_message :forward_invoke, :sticky?, :owned?, :grpowned?
92
+ forward_message :forward_invoke, :setuid?, :setgid?
93
+ forward_message :forward_invoke, :zero?
94
+ forward_message :forward_invoke, :size, :size?
95
+ forward_message :forward_invoke, :mtime, :atime, :ctime
96
+
97
+ def blocks; nil; end
98
+
99
+ def get_entry
100
+ @zipFsFile.__send__(:get_entry, @entryName)
101
+ end
102
+ private :get_entry
103
+
104
+ def gid
105
+ e = get_entry
106
+ if e.extra.member? "IUnix"
107
+ e.extra["IUnix"].gid || 0
108
+ else
109
+ 0
110
+ end
111
+ end
112
+
113
+ def uid
114
+ e = get_entry
115
+ if e.extra.member? "IUnix"
116
+ e.extra["IUnix"].uid || 0
117
+ else
118
+ 0
119
+ end
120
+ end
121
+
122
+ def ino; 0; end
123
+
124
+ def dev; 0; end
125
+
126
+ def rdev; 0; end
127
+
128
+ def rdev_major; 0; end
129
+
130
+ def rdev_minor; 0; end
131
+
132
+ def ftype
133
+ if file?
134
+ return "file"
135
+ elsif directory?
136
+ return "directory"
137
+ else
138
+ raise StandardError, "Unknown file type"
139
+ end
140
+ end
141
+
142
+ def nlink; 1; end
143
+
144
+ def blksize; nil; end
145
+
146
+ def mode
147
+ e = get_entry
148
+ if e.fstype == 3
149
+ e.externalFileAttributes >> 16
150
+ else
151
+ 33206 # 33206 is equivalent to -rw-rw-rw-
152
+ end
153
+ end
154
+ end
155
+
156
+ def initialize(mappedZip)
157
+ @mappedZip = mappedZip
158
+ end
159
+
160
+ def get_entry(fileName)
161
+ if ! exists?(fileName)
162
+ raise Errno::ENOENT, "No such file or directory - #{fileName}"
163
+ end
164
+ @mappedZip.find_entry(fileName)
165
+ end
166
+ private :get_entry
167
+
168
+ def unix_mode_cmp(fileName, mode)
169
+ begin
170
+ e = get_entry(fileName)
171
+ e.fstype == 3 && ((e.externalFileAttributes >> 16) & mode ) != 0
172
+ rescue Errno::ENOENT
173
+ false
174
+ end
175
+ end
176
+ private :unix_mode_cmp
177
+
178
+ def exists?(fileName)
179
+ expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil
180
+ end
181
+ alias :exist? :exists?
182
+
183
+ # Permissions not implemented, so if the file exists it is accessible
184
+ alias owned? exists?
185
+ alias grpowned? exists?
186
+
187
+ def readable?(fileName)
188
+ unix_mode_cmp(fileName, 0444)
189
+ end
190
+ alias readable_real? readable?
191
+
192
+ def writable?(fileName)
193
+ unix_mode_cmp(fileName, 0222)
194
+ end
195
+ alias writable_real? writable?
196
+
197
+ def executable?(fileName)
198
+ unix_mode_cmp(fileName, 0111)
199
+ end
200
+ alias executable_real? executable?
201
+
202
+ def setuid?(fileName)
203
+ unix_mode_cmp(fileName, 04000)
204
+ end
205
+
206
+ def setgid?(fileName)
207
+ unix_mode_cmp(fileName, 02000)
208
+ end
209
+
210
+ def sticky?(fileName)
211
+ unix_mode_cmp(fileName, 01000)
212
+ end
213
+
214
+ def umask(*args)
215
+ ::File.umask(*args)
216
+ end
217
+
218
+ def truncate(fileName, len)
219
+ raise StandardError, "truncate not supported"
220
+ end
221
+
222
+ def directory?(fileName)
223
+ entry = @mappedZip.find_entry(fileName)
224
+ expand_path(fileName) == "/" || (entry != nil && entry.directory?)
225
+ end
226
+
227
+ def open(fileName, openMode = "r", &block)
228
+ case openMode
229
+ when "r"
230
+ @mappedZip.get_input_stream(fileName, &block)
231
+ when "w"
232
+ @mappedZip.get_output_stream(fileName, &block)
233
+ else
234
+ raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r"
235
+ end
236
+ end
237
+
238
+ def new(fileName, openMode = "r")
239
+ open(fileName, openMode)
240
+ end
241
+
242
+ def size(fileName)
243
+ @mappedZip.get_entry(fileName).size
244
+ end
245
+
246
+ # Returns nil for not found and nil for directories
247
+ def size?(fileName)
248
+ entry = @mappedZip.find_entry(fileName)
249
+ return (entry == nil || entry.directory?) ? nil : entry.size
250
+ end
251
+
252
+ def chown(ownerInt, groupInt, *filenames)
253
+ filenames.each { |fileName|
254
+ e = get_entry(fileName)
255
+ unless e.extra.member?("IUnix")
256
+ e.extra.create("IUnix")
257
+ end
258
+ e.extra["IUnix"].uid = ownerInt
259
+ e.extra["IUnix"].gid = groupInt
260
+ }
261
+ filenames.size
262
+ end
263
+
264
+ def chmod (modeInt, *filenames)
265
+ filenames.each { |fileName|
266
+ e = get_entry(fileName)
267
+ e.fstype = 3 # force convertion filesystem type to unix
268
+ e.externalFileAttributes = modeInt << 16
269
+ }
270
+ filenames.size
271
+ end
272
+
273
+ def zero?(fileName)
274
+ sz = size(fileName)
275
+ sz == nil || sz == 0
276
+ rescue Errno::ENOENT
277
+ false
278
+ end
279
+
280
+ def file?(fileName)
281
+ entry = @mappedZip.find_entry(fileName)
282
+ entry != nil && entry.file?
283
+ end
284
+
285
+ def dirname(fileName)
286
+ ::File.dirname(fileName)
287
+ end
288
+
289
+ def basename(fileName)
290
+ ::File.basename(fileName)
291
+ end
292
+
293
+ def split(fileName)
294
+ ::File.split(fileName)
295
+ end
296
+
297
+ def join(*fragments)
298
+ ::File.join(*fragments)
299
+ end
300
+
301
+ def utime(modifiedTime, *fileNames)
302
+ fileNames.each { |fileName|
303
+ get_entry(fileName).time = modifiedTime
304
+ }
305
+ end
306
+
307
+ def mtime(fileName)
308
+ @mappedZip.get_entry(fileName).mtime
309
+ end
310
+
311
+ def atime(fileName)
312
+ e = get_entry(fileName)
313
+ if e.extra.member? "UniversalTime"
314
+ e.extra["UniversalTime"].atime
315
+ else
316
+ nil
317
+ end
318
+ end
319
+
320
+ def ctime(fileName)
321
+ e = get_entry(fileName)
322
+ if e.extra.member? "UniversalTime"
323
+ e.extra["UniversalTime"].ctime
324
+ else
325
+ nil
326
+ end
327
+ end
328
+
329
+ def pipe?(filename)
330
+ false
331
+ end
332
+
333
+ def blockdev?(filename)
334
+ false
335
+ end
336
+
337
+ def chardev?(filename)
338
+ false
339
+ end
340
+
341
+ def symlink?(fileName)
342
+ false
343
+ end
344
+
345
+ def socket?(fileName)
346
+ false
347
+ end
348
+
349
+ def ftype(fileName)
350
+ @mappedZip.get_entry(fileName).directory? ? "directory" : "file"
351
+ end
352
+
353
+ def readlink(fileName)
354
+ raise NotImplementedError, "The readlink() function is not implemented"
355
+ end
356
+
357
+ def symlink(fileName, symlinkName)
358
+ raise NotImplementedError, "The symlink() function is not implemented"
359
+ end
360
+
361
+ def link(fileName, symlinkName)
362
+ raise NotImplementedError, "The link() function is not implemented"
363
+ end
364
+
365
+ def pipe
366
+ raise NotImplementedError, "The pipe() function is not implemented"
367
+ end
368
+
369
+ def stat(fileName)
370
+ if ! exists?(fileName)
371
+ raise Errno::ENOENT, fileName
372
+ end
373
+ ZipFsStat.new(self, fileName)
374
+ end
375
+
376
+ alias lstat stat
377
+
378
+ def readlines(fileName)
379
+ open(fileName) { |is| is.readlines }
380
+ end
381
+
382
+ def read(fileName)
383
+ @mappedZip.read(fileName)
384
+ end
385
+
386
+ def popen(*args, &aProc)
387
+ File.popen(*args, &aProc)
388
+ end
389
+
390
+ def foreach(fileName, aSep = $/, &aProc)
391
+ open(fileName) { |is| is.each_line(aSep, &aProc) }
392
+ end
393
+
394
+ def delete(*args)
395
+ args.each {
396
+ |fileName|
397
+ if directory?(fileName)
398
+ raise Errno::EISDIR, "Is a directory - \"#{fileName}\""
399
+ end
400
+ @mappedZip.remove(fileName)
401
+ }
402
+ end
403
+
404
+ def rename(fileToRename, newName)
405
+ @mappedZip.rename(fileToRename, newName) { true }
406
+ end
407
+
408
+ alias :unlink :delete
409
+
410
+ def expand_path(aPath)
411
+ @mappedZip.expand_path(aPath)
412
+ end
413
+ end
414
+
415
+ # Instances of this class are normally accessed via the accessor
416
+ # ZipFile::dir. An instance of ZipFsDir behaves like ruby's
417
+ # builtin Dir (class) object, except it works on ZipFile entries.
418
+ #
419
+ # The individual methods are not documented due to their
420
+ # similarity with the methods in Dir
421
+ class ZipFsDir
422
+
423
+ def initialize(mappedZip)
424
+ @mappedZip = mappedZip
425
+ end
426
+
427
+ attr_writer :file
428
+
429
+ def new(aDirectoryName)
430
+ ZipFsDirIterator.new(entries(aDirectoryName))
431
+ end
432
+
433
+ def open(aDirectoryName)
434
+ dirIt = new(aDirectoryName)
435
+ if block_given?
436
+ begin
437
+ yield(dirIt)
438
+ return nil
439
+ ensure
440
+ dirIt.close
441
+ end
442
+ end
443
+ dirIt
444
+ end
445
+
446
+ def pwd; @mappedZip.pwd; end
447
+ alias getwd pwd
448
+
449
+ def chdir(aDirectoryName)
450
+ unless @file.stat(aDirectoryName).directory?
451
+ raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}"
452
+ end
453
+ @mappedZip.pwd = @file.expand_path(aDirectoryName)
454
+ end
455
+
456
+ def entries(aDirectoryName)
457
+ entries = []
458
+ foreach(aDirectoryName) { |e| entries << e }
459
+ entries
460
+ end
461
+
462
+ def foreach(aDirectoryName)
463
+ unless @file.stat(aDirectoryName).directory?
464
+ raise Errno::ENOTDIR, aDirectoryName
465
+ end
466
+ path = @file.expand_path(aDirectoryName).ensure_end("/")
467
+
468
+ subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$")
469
+ @mappedZip.each {
470
+ |fileName|
471
+ match = subDirEntriesRegex.match(fileName)
472
+ yield(match[1]) unless match == nil
473
+ }
474
+ end
475
+
476
+ def delete(entryName)
477
+ unless @file.stat(entryName).directory?
478
+ raise Errno::EINVAL, "Invalid argument - #{entryName}"
479
+ end
480
+ @mappedZip.remove(entryName)
481
+ end
482
+ alias rmdir delete
483
+ alias unlink delete
484
+
485
+ def mkdir(entryName, permissionInt = 0)
486
+ @mappedZip.mkdir(entryName, permissionInt)
487
+ end
488
+
489
+ def chroot(*args)
490
+ raise NotImplementedError, "The chroot() function is not implemented"
491
+ end
492
+
493
+ end
494
+
495
+ class ZipFsDirIterator # :nodoc:all
496
+ include Enumerable
497
+
498
+ def initialize(arrayOfFileNames)
499
+ @fileNames = arrayOfFileNames
500
+ @index = 0
501
+ end
502
+
503
+ def close
504
+ @fileNames = nil
505
+ end
506
+
507
+ def each(&aProc)
508
+ raise IOError, "closed directory" if @fileNames == nil
509
+ @fileNames.each(&aProc)
510
+ end
511
+
512
+ def read
513
+ raise IOError, "closed directory" if @fileNames == nil
514
+ @fileNames[(@index+=1)-1]
515
+ end
516
+
517
+ def rewind
518
+ raise IOError, "closed directory" if @fileNames == nil
519
+ @index = 0
520
+ end
521
+
522
+ def seek(anIntegerPosition)
523
+ raise IOError, "closed directory" if @fileNames == nil
524
+ @index = anIntegerPosition
525
+ end
526
+
527
+ def tell
528
+ raise IOError, "closed directory" if @fileNames == nil
529
+ @index
530
+ end
531
+ end
532
+
533
+ # All access to ZipFile from ZipFsFile and ZipFsDir goes through a
534
+ # ZipFileNameMapper, which has one responsibility: ensure
535
+ class ZipFileNameMapper # :nodoc:all
536
+ include Enumerable
537
+
538
+ def initialize(zipFile)
539
+ @zipFile = zipFile
540
+ @pwd = "/"
541
+ end
542
+
543
+ attr_accessor :pwd
544
+
545
+ def find_entry(fileName)
546
+ @zipFile.find_entry(expand_to_entry(fileName))
547
+ end
548
+
549
+ def get_entry(fileName)
550
+ @zipFile.get_entry(expand_to_entry(fileName))
551
+ end
552
+
553
+ def get_input_stream(fileName, &aProc)
554
+ @zipFile.get_input_stream(expand_to_entry(fileName), &aProc)
555
+ end
556
+
557
+ def get_output_stream(fileName, &aProc)
558
+ @zipFile.get_output_stream(expand_to_entry(fileName), &aProc)
559
+ end
560
+
561
+ def read(fileName)
562
+ @zipFile.read(expand_to_entry(fileName))
563
+ end
564
+
565
+ def remove(fileName)
566
+ @zipFile.remove(expand_to_entry(fileName))
567
+ end
568
+
569
+ def rename(fileName, newName, &continueOnExistsProc)
570
+ @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName),
571
+ &continueOnExistsProc)
572
+ end
573
+
574
+ def mkdir(fileName, permissionInt = 0)
575
+ @zipFile.mkdir(expand_to_entry(fileName), permissionInt)
576
+ end
577
+
578
+ # Turns entries into strings and adds leading /
579
+ # and removes trailing slash on directories
580
+ def each
581
+ @zipFile.each {
582
+ |e|
583
+ yield("/"+e.to_s.chomp("/"))
584
+ }
585
+ end
586
+
587
+ def expand_path(aPath)
588
+ expanded = aPath.starts_with("/") ? aPath : @pwd.ensure_end("/") + aPath
589
+ expanded.gsub!(/\/\.(\/|$)/, "")
590
+ expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "")
591
+ expanded.empty? ? "/" : expanded
592
+ end
593
+
594
+ private
595
+
596
+ def expand_to_entry(aPath)
597
+ expand_path(aPath).lchop
598
+ end
599
+ end
600
+ end
601
+
602
+ class ZipFile
603
+ include ZipFileSystem
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.