rubyzip 2.3.1 → 3.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +368 -0
  3. data/README.md +122 -39
  4. data/Rakefile +11 -7
  5. data/lib/zip/central_directory.rb +164 -118
  6. data/lib/zip/compressor.rb +3 -1
  7. data/lib/zip/constants.rb +25 -21
  8. data/lib/zip/crypto/decrypted_io.rb +3 -1
  9. data/lib/zip/crypto/encryption.rb +4 -2
  10. data/lib/zip/crypto/null_encryption.rb +5 -3
  11. data/lib/zip/crypto/traditional_encryption.rb +5 -3
  12. data/lib/zip/decompressor.rb +4 -3
  13. data/lib/zip/deflater.rb +10 -8
  14. data/lib/zip/dirtyable.rb +32 -0
  15. data/lib/zip/dos_time.rb +41 -5
  16. data/lib/zip/entry.rb +273 -170
  17. data/lib/zip/entry_set.rb +9 -7
  18. data/lib/zip/errors.rb +115 -16
  19. data/lib/zip/extra_field/generic.rb +3 -10
  20. data/lib/zip/extra_field/ntfs.rb +4 -2
  21. data/lib/zip/extra_field/old_unix.rb +3 -1
  22. data/lib/zip/extra_field/universal_time.rb +3 -1
  23. data/lib/zip/extra_field/unix.rb +5 -3
  24. data/lib/zip/extra_field/unknown.rb +33 -0
  25. data/lib/zip/extra_field/zip64.rb +12 -5
  26. data/lib/zip/extra_field.rb +15 -21
  27. data/lib/zip/file.rb +152 -221
  28. data/lib/zip/file_split.rb +97 -0
  29. data/lib/zip/filesystem/dir.rb +86 -0
  30. data/lib/zip/filesystem/directory_iterator.rb +48 -0
  31. data/lib/zip/filesystem/file.rb +262 -0
  32. data/lib/zip/filesystem/file_stat.rb +110 -0
  33. data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
  34. data/lib/zip/filesystem.rb +26 -595
  35. data/lib/zip/inflater.rb +6 -4
  36. data/lib/zip/input_stream.rb +43 -25
  37. data/lib/zip/ioextras/abstract_input_stream.rb +13 -8
  38. data/lib/zip/ioextras/abstract_output_stream.rb +5 -3
  39. data/lib/zip/ioextras.rb +6 -6
  40. data/lib/zip/null_compressor.rb +3 -1
  41. data/lib/zip/null_decompressor.rb +3 -1
  42. data/lib/zip/null_input_stream.rb +3 -1
  43. data/lib/zip/output_stream.rb +45 -39
  44. data/lib/zip/pass_thru_compressor.rb +3 -1
  45. data/lib/zip/pass_thru_decompressor.rb +4 -2
  46. data/lib/zip/streamable_directory.rb +3 -1
  47. data/lib/zip/streamable_stream.rb +3 -0
  48. data/lib/zip/version.rb +3 -1
  49. data/lib/zip.rb +15 -22
  50. data/rubyzip.gemspec +38 -0
  51. data/samples/example.rb +8 -3
  52. data/samples/example_filesystem.rb +2 -1
  53. data/samples/example_recursive.rb +3 -1
  54. data/samples/gtk_ruby_zip.rb +4 -2
  55. data/samples/qtzip.rb +6 -5
  56. data/samples/write_simple.rb +1 -0
  57. data/samples/zipfind.rb +1 -0
  58. metadata +83 -35
  59. data/TODO +0 -15
  60. data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zip
4
+ module FileSystem
5
+ class Dir # :nodoc:all
6
+ def initialize(mapped_zip)
7
+ @mapped_zip = mapped_zip
8
+ end
9
+
10
+ attr_writer :file
11
+
12
+ def new(directory_name)
13
+ DirectoryIterator.new(entries(directory_name))
14
+ end
15
+
16
+ def open(directory_name)
17
+ dir_iter = new(directory_name)
18
+ if block_given?
19
+ begin
20
+ yield(dir_iter)
21
+ return nil
22
+ ensure
23
+ dir_iter.close
24
+ end
25
+ end
26
+ dir_iter
27
+ end
28
+
29
+ def pwd
30
+ @mapped_zip.pwd
31
+ end
32
+ alias getwd pwd
33
+
34
+ def chdir(directory_name)
35
+ unless @file.stat(directory_name).directory?
36
+ raise Errno::EINVAL, "Invalid argument - #{directory_name}"
37
+ end
38
+
39
+ @mapped_zip.pwd = @file.expand_path(directory_name)
40
+ end
41
+
42
+ def entries(directory_name)
43
+ entries = []
44
+ foreach(directory_name) { |e| entries << e }
45
+ entries
46
+ end
47
+
48
+ def glob(*args, &block)
49
+ @mapped_zip.glob(*args, &block)
50
+ end
51
+
52
+ def foreach(directory_name)
53
+ unless @file.stat(directory_name).directory?
54
+ raise Errno::ENOTDIR, directory_name
55
+ end
56
+
57
+ path = @file.expand_path(directory_name)
58
+ path << '/' unless path.end_with?('/')
59
+ path = Regexp.escape(path)
60
+ subdir_entry_regex = Regexp.new("^#{path}([^/]+)$")
61
+ @mapped_zip.each do |filename|
62
+ match = subdir_entry_regex.match(filename)
63
+ yield(match[1]) unless match.nil?
64
+ end
65
+ end
66
+
67
+ def delete(entry_name)
68
+ unless @file.stat(entry_name).directory?
69
+ raise Errno::EINVAL, "Invalid argument - #{entry_name}"
70
+ end
71
+
72
+ @mapped_zip.remove(entry_name)
73
+ end
74
+ alias rmdir delete
75
+ alias unlink delete
76
+
77
+ def mkdir(entry_name, permissions = 0o755)
78
+ @mapped_zip.mkdir(entry_name, permissions)
79
+ end
80
+
81
+ def chroot(*_args)
82
+ raise NotImplementedError, 'The chroot() function is not implemented'
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zip
4
+ module FileSystem
5
+ class DirectoryIterator # :nodoc:all
6
+ include Enumerable
7
+
8
+ def initialize(filenames)
9
+ @filenames = filenames
10
+ @index = 0
11
+ end
12
+
13
+ def close
14
+ @filenames = nil
15
+ end
16
+
17
+ def each(&a_proc)
18
+ raise IOError, 'closed directory' if @filenames.nil?
19
+
20
+ @filenames.each(&a_proc)
21
+ end
22
+
23
+ def read
24
+ raise IOError, 'closed directory' if @filenames.nil?
25
+
26
+ @filenames[(@index += 1) - 1]
27
+ end
28
+
29
+ def rewind
30
+ raise IOError, 'closed directory' if @filenames.nil?
31
+
32
+ @index = 0
33
+ end
34
+
35
+ def seek(position)
36
+ raise IOError, 'closed directory' if @filenames.nil?
37
+
38
+ @index = position
39
+ end
40
+
41
+ def tell
42
+ raise IOError, 'closed directory' if @filenames.nil?
43
+
44
+ @index
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,262 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'file_stat'
4
+
5
+ module Zip
6
+ module FileSystem
7
+ # Instances of this class are normally accessed via the accessor
8
+ # Zip::File::file. An instance of File behaves like ruby's
9
+ # builtin File (class) object, except it works on Zip::File entries.
10
+ #
11
+ # The individual methods are not documented due to their
12
+ # similarity with the methods in File
13
+ class File # :nodoc:all
14
+ attr_writer :dir
15
+
16
+ def initialize(mapped_zip)
17
+ @mapped_zip = mapped_zip
18
+ end
19
+
20
+ def find_entry(filename)
21
+ unless exists?(filename)
22
+ raise Errno::ENOENT, "No such file or directory - #{filename}"
23
+ end
24
+
25
+ @mapped_zip.find_entry(filename)
26
+ end
27
+
28
+ def unix_mode_cmp(filename, mode)
29
+ e = find_entry(filename)
30
+ e.fstype == FSTYPE_UNIX && ((e.external_file_attributes >> 16) & mode) != 0
31
+ rescue Errno::ENOENT
32
+ false
33
+ end
34
+ private :unix_mode_cmp
35
+
36
+ def exists?(filename)
37
+ expand_path(filename) == '/' || !@mapped_zip.find_entry(filename).nil?
38
+ end
39
+ alias exist? exists?
40
+
41
+ # Permissions not implemented, so if the file exists it is accessible
42
+ alias owned? exists?
43
+ alias grpowned? exists?
44
+
45
+ def readable?(filename)
46
+ unix_mode_cmp(filename, 0o444)
47
+ end
48
+ alias readable_real? readable?
49
+
50
+ def writable?(filename)
51
+ unix_mode_cmp(filename, 0o222)
52
+ end
53
+ alias writable_real? writable?
54
+
55
+ def executable?(filename)
56
+ unix_mode_cmp(filename, 0o111)
57
+ end
58
+ alias executable_real? executable?
59
+
60
+ def setuid?(filename)
61
+ unix_mode_cmp(filename, 0o4000)
62
+ end
63
+
64
+ def setgid?(filename)
65
+ unix_mode_cmp(filename, 0o2000)
66
+ end
67
+
68
+ def sticky?(filename)
69
+ unix_mode_cmp(filename, 0o1000)
70
+ end
71
+
72
+ def umask(*args)
73
+ ::File.umask(*args)
74
+ end
75
+
76
+ def truncate(_filename, _len)
77
+ raise StandardError, 'truncate not supported'
78
+ end
79
+
80
+ def directory?(filename)
81
+ entry = @mapped_zip.find_entry(filename)
82
+ expand_path(filename) == '/' || (!entry.nil? && entry.directory?)
83
+ end
84
+
85
+ def open(filename, mode = 'r', permissions = 0o644, &block)
86
+ mode = mode.tr('b', '') # ignore b option
87
+ case mode
88
+ when 'r'
89
+ @mapped_zip.get_input_stream(filename, &block)
90
+ when 'w'
91
+ @mapped_zip.get_output_stream(filename, permissions, &block)
92
+ else
93
+ raise StandardError, "openmode '#{mode} not supported" unless mode == 'r'
94
+ end
95
+ end
96
+
97
+ def new(filename, mode = 'r')
98
+ self.open(filename, mode)
99
+ end
100
+
101
+ def size(filename)
102
+ @mapped_zip.get_entry(filename).size
103
+ end
104
+
105
+ # Returns nil for not found and nil for directories
106
+ def size?(filename)
107
+ entry = @mapped_zip.find_entry(filename)
108
+ entry.nil? || entry.directory? ? nil : entry.size
109
+ end
110
+
111
+ def chown(owner, group, *filenames)
112
+ filenames.each do |filename|
113
+ e = find_entry(filename)
114
+ e.extra.create('IUnix') unless e.extra.member?('IUnix')
115
+ e.extra['IUnix'].uid = owner
116
+ e.extra['IUnix'].gid = group
117
+ end
118
+ filenames.size
119
+ end
120
+
121
+ def chmod(mode, *filenames)
122
+ filenames.each do |filename|
123
+ e = find_entry(filename)
124
+ e.fstype = FSTYPE_UNIX # Force conversion filesystem type to unix.
125
+ e.unix_perms = mode
126
+ e.external_file_attributes = mode << 16
127
+ end
128
+ filenames.size
129
+ end
130
+
131
+ def zero?(filename)
132
+ sz = size(filename)
133
+ sz.nil? || sz == 0
134
+ rescue Errno::ENOENT
135
+ false
136
+ end
137
+
138
+ def file?(filename)
139
+ entry = @mapped_zip.find_entry(filename)
140
+ !entry.nil? && entry.file?
141
+ end
142
+
143
+ def dirname(filename)
144
+ ::File.dirname(filename)
145
+ end
146
+
147
+ def basename(filename)
148
+ ::File.basename(filename)
149
+ end
150
+
151
+ def split(filename)
152
+ ::File.split(filename)
153
+ end
154
+
155
+ def join(*fragments)
156
+ ::File.join(*fragments)
157
+ end
158
+
159
+ def utime(modified_time, *filenames)
160
+ filenames.each do |filename|
161
+ find_entry(filename).time = modified_time
162
+ end
163
+ end
164
+
165
+ def mtime(filename)
166
+ @mapped_zip.get_entry(filename).mtime
167
+ end
168
+
169
+ def atime(filename)
170
+ @mapped_zip.get_entry(filename).atime
171
+ end
172
+
173
+ def ctime(filename)
174
+ @mapped_zip.get_entry(filename).ctime
175
+ end
176
+
177
+ def pipe?(_filename)
178
+ false
179
+ end
180
+
181
+ def blockdev?(_filename)
182
+ false
183
+ end
184
+
185
+ def chardev?(_filename)
186
+ false
187
+ end
188
+
189
+ def symlink?(filename)
190
+ @mapped_zip.get_entry(filename).symlink?
191
+ end
192
+
193
+ def socket?(_filename)
194
+ false
195
+ end
196
+
197
+ def ftype(filename)
198
+ @mapped_zip.get_entry(filename).directory? ? 'directory' : 'file'
199
+ end
200
+
201
+ def readlink(_filename)
202
+ raise NotImplementedError, 'The readlink() function is not implemented'
203
+ end
204
+
205
+ def symlink(_filename, _symlink_name)
206
+ raise NotImplementedError, 'The symlink() function is not implemented'
207
+ end
208
+
209
+ def link(_filename, _symlink_name)
210
+ raise NotImplementedError, 'The link() function is not implemented'
211
+ end
212
+
213
+ def pipe
214
+ raise NotImplementedError, 'The pipe() function is not implemented'
215
+ end
216
+
217
+ def stat(filename)
218
+ raise Errno::ENOENT, filename unless exists?(filename)
219
+
220
+ Stat.new(self, filename)
221
+ end
222
+
223
+ alias lstat stat
224
+
225
+ def readlines(filename)
226
+ self.open(filename, &:readlines)
227
+ end
228
+
229
+ def read(filename)
230
+ @mapped_zip.read(filename)
231
+ end
232
+
233
+ def popen(*args, &a_proc)
234
+ ::File.popen(*args, &a_proc)
235
+ end
236
+
237
+ def foreach(filename, sep = $INPUT_RECORD_SEPARATOR, &a_proc)
238
+ self.open(filename) { |is| is.each_line(sep, &a_proc) }
239
+ end
240
+
241
+ def delete(*args)
242
+ args.each do |filename|
243
+ if directory?(filename)
244
+ raise Errno::EISDIR, "Is a directory - \"#{filename}\""
245
+ end
246
+
247
+ @mapped_zip.remove(filename)
248
+ end
249
+ end
250
+
251
+ def rename(file_to_rename, new_name)
252
+ @mapped_zip.rename(file_to_rename, new_name) { true }
253
+ end
254
+
255
+ alias unlink delete
256
+
257
+ def expand_path(path)
258
+ @mapped_zip.expand_path(path)
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zip
4
+ module FileSystem
5
+ class File # :nodoc:all
6
+ class Stat # :nodoc:all
7
+ class << self
8
+ def delegate_to_fs_file(*methods)
9
+ methods.each do |method|
10
+ class_exec do
11
+ define_method(method) do
12
+ @zip_fs_file.__send__(method, @entry_name)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ def initialize(zip_fs_file, entry_name)
20
+ @zip_fs_file = zip_fs_file
21
+ @entry_name = entry_name
22
+ end
23
+
24
+ def kind_of?(type)
25
+ super || type == ::File::Stat
26
+ end
27
+
28
+ delegate_to_fs_file :file?, :directory?, :pipe?, :chardev?, :symlink?,
29
+ :socket?, :blockdev?, :readable?, :readable_real?, :writable?, :ctime,
30
+ :writable_real?, :executable?, :executable_real?, :sticky?, :owned?,
31
+ :grpowned?, :setuid?, :setgid?, :zero?, :size, :size?, :mtime, :atime
32
+
33
+ def blocks
34
+ nil
35
+ end
36
+
37
+ def gid
38
+ e = find_entry
39
+ if e.extra.member? 'IUnix'
40
+ e.extra['IUnix'].gid || 0
41
+ else
42
+ 0
43
+ end
44
+ end
45
+
46
+ def uid
47
+ e = find_entry
48
+ if e.extra.member? 'IUnix'
49
+ e.extra['IUnix'].uid || 0
50
+ else
51
+ 0
52
+ end
53
+ end
54
+
55
+ def ino
56
+ 0
57
+ end
58
+
59
+ def dev
60
+ 0
61
+ end
62
+
63
+ def rdev
64
+ 0
65
+ end
66
+
67
+ def rdev_major
68
+ 0
69
+ end
70
+
71
+ def rdev_minor
72
+ 0
73
+ end
74
+
75
+ def ftype
76
+ if file?
77
+ 'file'
78
+ elsif directory?
79
+ 'directory'
80
+ else
81
+ raise StandardError, 'Unknown file type'
82
+ end
83
+ end
84
+
85
+ def nlink
86
+ 1
87
+ end
88
+
89
+ def blksize
90
+ nil
91
+ end
92
+
93
+ def mode
94
+ e = find_entry
95
+ if e.fstype == FSTYPE_UNIX
96
+ e.external_file_attributes >> 16
97
+ else
98
+ 0o100_666 # Equivalent to -rw-rw-rw-.
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def find_entry
105
+ @zip_fs_file.find_entry(@entry_name)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zip
4
+ module FileSystem
5
+ # All access to Zip::File from FileSystem::File and FileSystem::Dir
6
+ # goes through a ZipFileNameMapper, which has one responsibility: ensure
7
+ class ZipFileNameMapper # :nodoc:all
8
+ include Enumerable
9
+
10
+ def initialize(zip_file)
11
+ @zip_file = zip_file
12
+ @pwd = '/'
13
+ end
14
+
15
+ attr_accessor :pwd
16
+
17
+ def find_entry(filename)
18
+ @zip_file.find_entry(expand_to_entry(filename))
19
+ end
20
+
21
+ def get_entry(filename)
22
+ @zip_file.get_entry(expand_to_entry(filename))
23
+ end
24
+
25
+ def get_input_stream(filename, &a_proc)
26
+ @zip_file.get_input_stream(expand_to_entry(filename), &a_proc)
27
+ end
28
+
29
+ def get_output_stream(filename, permissions = nil, &a_proc)
30
+ @zip_file.get_output_stream(
31
+ expand_to_entry(filename), permissions: permissions, &a_proc
32
+ )
33
+ end
34
+
35
+ def glob(pattern, *flags, &block)
36
+ @zip_file.glob(expand_to_entry(pattern), *flags, &block)
37
+ end
38
+
39
+ def read(filename)
40
+ @zip_file.read(expand_to_entry(filename))
41
+ end
42
+
43
+ def remove(filename)
44
+ @zip_file.remove(expand_to_entry(filename))
45
+ end
46
+
47
+ def rename(filename, new_name, &continue_on_exists_proc)
48
+ @zip_file.rename(
49
+ expand_to_entry(filename),
50
+ expand_to_entry(new_name),
51
+ &continue_on_exists_proc
52
+ )
53
+ end
54
+
55
+ def mkdir(filename, permissions = 0o755)
56
+ @zip_file.mkdir(expand_to_entry(filename), permissions)
57
+ end
58
+
59
+ # Turns entries into strings and adds leading /
60
+ # and removes trailing slash on directories
61
+ def each
62
+ @zip_file.each do |e|
63
+ yield("/#{e.to_s.chomp('/')}")
64
+ end
65
+ end
66
+
67
+ def expand_path(path)
68
+ expanded = path.start_with?('/') ? path.dup : ::File.join(@pwd, path)
69
+ expanded.gsub!(/\/\.(\/|$)/, '')
70
+ expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, '')
71
+ expanded.empty? ? '/' : expanded
72
+ end
73
+
74
+ private
75
+
76
+ def expand_to_entry(path)
77
+ expand_path(path)[1..-1]
78
+ end
79
+ end
80
+ end
81
+ end