rubyzip 1.3.0 → 3.0.0.alpha

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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +368 -0
  3. data/README.md +123 -46
  4. data/Rakefile +13 -6
  5. data/lib/zip/central_directory.rb +166 -116
  6. data/lib/zip/compressor.rb +3 -1
  7. data/lib/zip/constants.rb +77 -21
  8. data/lib/zip/crypto/decrypted_io.rb +42 -0
  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 +14 -12
  12. data/lib/zip/decompressor.rb +21 -2
  13. data/lib/zip/deflater.rb +10 -8
  14. data/lib/zip/dirtyable.rb +32 -0
  15. data/lib/zip/dos_time.rb +53 -12
  16. data/lib/zip/entry.rb +306 -184
  17. data/lib/zip/entry_set.rb +11 -7
  18. data/lib/zip/errors.rb +115 -15
  19. data/lib/zip/extra_field/generic.rb +11 -17
  20. data/lib/zip/extra_field/ntfs.rb +8 -2
  21. data/lib/zip/extra_field/old_unix.rb +6 -2
  22. data/lib/zip/extra_field/universal_time.rb +45 -13
  23. data/lib/zip/extra_field/unix.rb +7 -3
  24. data/lib/zip/extra_field/unknown.rb +33 -0
  25. data/lib/zip/extra_field/zip64.rb +16 -7
  26. data/lib/zip/extra_field.rb +22 -26
  27. data/lib/zip/file.rb +196 -240
  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 +31 -584
  35. data/lib/zip/inflater.rb +27 -37
  36. data/lib/zip/input_stream.rb +67 -42
  37. data/lib/zip/ioextras/abstract_input_stream.rb +32 -16
  38. data/lib/zip/ioextras/abstract_output_stream.rb +5 -3
  39. data/lib/zip/ioextras.rb +7 -7
  40. data/lib/zip/null_compressor.rb +3 -1
  41. data/lib/zip/null_decompressor.rb +4 -10
  42. data/lib/zip/null_input_stream.rb +3 -1
  43. data/lib/zip/output_stream.rb +58 -43
  44. data/lib/zip/pass_thru_compressor.rb +5 -3
  45. data/lib/zip/pass_thru_decompressor.rb +16 -23
  46. data/lib/zip/streamable_directory.rb +6 -4
  47. data/lib/zip/streamable_stream.rb +9 -10
  48. data/lib/zip/version.rb +3 -1
  49. data/lib/zip.rb +19 -4
  50. data/rubyzip.gemspec +38 -0
  51. data/samples/example.rb +9 -4
  52. data/samples/example_filesystem.rb +3 -2
  53. data/samples/example_recursive.rb +3 -1
  54. data/samples/gtk_ruby_zip.rb +22 -20
  55. data/samples/qtzip.rb +12 -11
  56. data/samples/write_simple.rb +3 -4
  57. data/samples/zipfind.rb +24 -22
  58. metadata +86 -179
  59. data/TODO +0 -15
  60. data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
  61. data/test/basic_zip_file_test.rb +0 -60
  62. data/test/case_sensitivity_test.rb +0 -69
  63. data/test/central_directory_entry_test.rb +0 -69
  64. data/test/central_directory_test.rb +0 -100
  65. data/test/crypto/null_encryption_test.rb +0 -57
  66. data/test/crypto/traditional_encryption_test.rb +0 -80
  67. data/test/data/WarnInvalidDate.zip +0 -0
  68. data/test/data/file1.txt +0 -46
  69. data/test/data/file1.txt.deflatedData +0 -0
  70. data/test/data/file2.txt +0 -1504
  71. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  72. data/test/data/globTest/foo.txt +0 -0
  73. data/test/data/globTest/food.txt +0 -0
  74. data/test/data/globTest.zip +0 -0
  75. data/test/data/gpbit3stored.zip +0 -0
  76. data/test/data/mimetype +0 -1
  77. data/test/data/notzippedruby.rb +0 -7
  78. data/test/data/ntfs.zip +0 -0
  79. data/test/data/oddExtraField.zip +0 -0
  80. data/test/data/path_traversal/Makefile +0 -10
  81. data/test/data/path_traversal/jwilk/README.md +0 -5
  82. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  83. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  84. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  85. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  86. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  87. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  88. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  89. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  90. data/test/data/path_traversal/relative1.zip +0 -0
  91. data/test/data/path_traversal/tilde.zip +0 -0
  92. data/test/data/path_traversal/tuzovakaoff/README.md +0 -3
  93. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  94. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  95. data/test/data/rubycode.zip +0 -0
  96. data/test/data/rubycode2.zip +0 -0
  97. data/test/data/test.xls +0 -0
  98. data/test/data/testDirectory.bin +0 -0
  99. data/test/data/zip64-sample.zip +0 -0
  100. data/test/data/zipWithDirs.zip +0 -0
  101. data/test/data/zipWithEncryption.zip +0 -0
  102. data/test/deflater_test.rb +0 -65
  103. data/test/encryption_test.rb +0 -42
  104. data/test/entry_set_test.rb +0 -163
  105. data/test/entry_test.rb +0 -154
  106. data/test/errors_test.rb +0 -35
  107. data/test/extra_field_test.rb +0 -76
  108. data/test/file_extract_directory_test.rb +0 -54
  109. data/test/file_extract_test.rb +0 -145
  110. data/test/file_permissions_test.rb +0 -65
  111. data/test/file_split_test.rb +0 -57
  112. data/test/file_test.rb +0 -666
  113. data/test/filesystem/dir_iterator_test.rb +0 -58
  114. data/test/filesystem/directory_test.rb +0 -139
  115. data/test/filesystem/file_mutating_test.rb +0 -87
  116. data/test/filesystem/file_nonmutating_test.rb +0 -508
  117. data/test/filesystem/file_stat_test.rb +0 -64
  118. data/test/gentestfiles.rb +0 -126
  119. data/test/inflater_test.rb +0 -14
  120. data/test/input_stream_test.rb +0 -182
  121. data/test/ioextras/abstract_input_stream_test.rb +0 -102
  122. data/test/ioextras/abstract_output_stream_test.rb +0 -106
  123. data/test/ioextras/fake_io_test.rb +0 -18
  124. data/test/local_entry_test.rb +0 -154
  125. data/test/output_stream_test.rb +0 -128
  126. data/test/pass_thru_compressor_test.rb +0 -30
  127. data/test/pass_thru_decompressor_test.rb +0 -14
  128. data/test/path_traversal_test.rb +0 -141
  129. data/test/samples/example_recursive_test.rb +0 -37
  130. data/test/settings_test.rb +0 -95
  131. data/test/test_helper.rb +0 -234
  132. data/test/unicode_file_names_and_comments_test.rb +0 -62
  133. data/test/zip64_full_test.rb +0 -51
  134. data/test/zip64_support_test.rb +0 -14
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zip
4
+ module FileSplit # :nodoc:
5
+ MAX_SEGMENT_SIZE = 3_221_225_472
6
+ MIN_SEGMENT_SIZE = 65_536
7
+ DATA_BUFFER_SIZE = 8192
8
+
9
+ def get_segment_size_for_split(segment_size)
10
+ if MIN_SEGMENT_SIZE > segment_size
11
+ MIN_SEGMENT_SIZE
12
+ elsif MAX_SEGMENT_SIZE < segment_size
13
+ MAX_SEGMENT_SIZE
14
+ else
15
+ segment_size
16
+ end
17
+ end
18
+
19
+ def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
20
+ unless partial_zip_file_name.nil?
21
+ partial_zip_file_name = zip_file_name.sub(
22
+ /#{::File.basename(zip_file_name)}\z/,
23
+ partial_zip_file_name + ::File.extname(zip_file_name)
24
+ )
25
+ end
26
+ partial_zip_file_name ||= zip_file_name
27
+ partial_zip_file_name
28
+ end
29
+
30
+ def get_segment_count_for_split(zip_file_size, segment_size)
31
+ (zip_file_size / segment_size).to_i +
32
+ ((zip_file_size % segment_size).zero? ? 0 : 1)
33
+ end
34
+
35
+ def put_split_signature(szip_file, segment_size)
36
+ signature_packed = [SPLIT_FILE_SIGNATURE].pack('V')
37
+ szip_file << signature_packed
38
+ segment_size - signature_packed.size
39
+ end
40
+
41
+ #
42
+ # TODO: Make the code more understandable
43
+ #
44
+ def save_splited_part(
45
+ zip_file, partial_zip_file_name, zip_file_size,
46
+ szip_file_index, segment_size, segment_count
47
+ )
48
+ ssegment_size = zip_file_size - zip_file.pos
49
+ ssegment_size = segment_size if ssegment_size > segment_size
50
+ szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}"
51
+ ::File.open(szip_file_name, 'wb') do |szip_file|
52
+ if szip_file_index == 1
53
+ ssegment_size = put_split_signature(szip_file, segment_size)
54
+ end
55
+ chunk_bytes = 0
56
+ until ssegment_size == chunk_bytes || zip_file.eof?
57
+ segment_bytes_left = ssegment_size - chunk_bytes
58
+ buffer_size = [segment_bytes_left, DATA_BUFFER_SIZE].min
59
+ chunk = zip_file.read(buffer_size)
60
+ chunk_bytes += buffer_size
61
+ szip_file << chunk
62
+ # Info for track splitting
63
+ yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given?
64
+ end
65
+ end
66
+ end
67
+
68
+ # Splits an archive into parts with segment size
69
+ def split(
70
+ zip_file_name, segment_size: MAX_SEGMENT_SIZE,
71
+ delete_original: true, partial_zip_file_name: nil
72
+ )
73
+ raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name)
74
+ raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
75
+
76
+ zip_file_size = ::File.size(zip_file_name)
77
+ segment_size = get_segment_size_for_split(segment_size)
78
+ return if zip_file_size <= segment_size
79
+
80
+ segment_count = get_segment_count_for_split(zip_file_size, segment_size)
81
+ ::Zip::File.open(zip_file_name) {} # Check for correct zip structure.
82
+ partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
83
+ szip_file_index = 0
84
+ ::File.open(zip_file_name, 'rb') do |zip_file|
85
+ until zip_file.eof?
86
+ szip_file_index += 1
87
+ save_splited_part(
88
+ zip_file, partial_zip_file_name, zip_file_size,
89
+ szip_file_index, segment_size, segment_count
90
+ )
91
+ end
92
+ end
93
+ ::File.delete(zip_file_name) if delete_original
94
+ szip_file_index
95
+ end
96
+ end
97
+ end
@@ -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