From 722ee6efa94722347e3bd67018bff6b4d4a52045 Mon Sep 17 00:00:00 2001 From: Gaurish Sharma Date: Mon, 3 Apr 2017 14:15:31 -0500 Subject: [PATCH 01/72] File.join() is our friend for joining paths --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8979d06f..354b82da 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile| # Two arguments: # - The name of the file as it will appear in the archive # - The original file, including the path to find it - zipfile.add(filename, folder + '/' + filename) + zipfile.add(filename, File.join(folder, filename)) end zipfile.get_output_stream("myFile") { |os| os.write "myFile contains just this" } end From 258ef0291421ebccd205824e9ae9f624b269984b Mon Sep 17 00:00:00 2001 From: Takumasa Ochi Date: Fri, 14 Apr 2017 20:56:49 +0900 Subject: [PATCH 02/72] Save temporary files to a temporary directory --- lib/zip/streamable_stream.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/streamable_stream.rb b/lib/zip/streamable_stream.rb index fc1c9a2d..2a4bf507 100644 --- a/lib/zip/streamable_stream.rb +++ b/lib/zip/streamable_stream.rb @@ -5,7 +5,7 @@ def initialize(entry) dirname = if zipfile.is_a?(::String) ::File.dirname(zipfile) else - '.' + nil end @temp_file = Tempfile.new(::File.basename(name), dirname) @temp_file.binmode From d80e7203d8ef21b97ec16623738a1bd252d94e3c Mon Sep 17 00:00:00 2001 From: Takumasa Ochi Date: Wed, 28 Jun 2017 11:36:14 +0900 Subject: [PATCH 03/72] Add rubocop dependency and correct settings --- .rubocop.yml | 4 +++- rubyzip.gemspec | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 75dce5fd..2733bcda 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,2 +1,4 @@ inherit_from: - - .rubocop_rubyzip.yml \ No newline at end of file + - .rubocop_rubyzip.yml +AllCops: + TargetRubyVersion: 1.9 diff --git a/rubyzip.gemspec b/rubyzip.gemspec index ccd9ba56..4857e0fb 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -20,4 +20,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'pry', '~> 0.10' s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'coveralls', '~> 0.7' + s.add_development_dependency 'rubocop', '~> 0.49.1' end From 9bfc52b2b7d01f9c1ff49b74d233d6ce6c13d3ed Mon Sep 17 00:00:00 2001 From: Takumasa Ochi Date: Thu, 29 Jun 2017 11:50:04 +0900 Subject: [PATCH 04/72] Disable Style/MutableConstant because existent code relies on it --- .rubocop.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 2733bcda..a408fa0d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,3 +2,5 @@ inherit_from: - .rubocop_rubyzip.yml AllCops: TargetRubyVersion: 1.9 +Style/MutableConstant: + Enabled: false # Because some existent code relies on mutable constant From cf91112b57404af217437b20d9414db3b7941538 Mon Sep 17 00:00:00 2001 From: Takumasa Ochi Date: Thu, 29 Jun 2017 11:57:12 +0900 Subject: [PATCH 05/72] Apply automatic correction by rubocop --- lib/zip/central_directory.rb | 6 +- lib/zip/compressor.rb | 3 +- lib/zip/constants.rb | 6 +- lib/zip/crypto/null_encryption.rb | 6 +- lib/zip/decompressor.rb | 2 +- lib/zip/dos_time.rb | 2 +- lib/zip/entry.rb | 34 +++++------ lib/zip/entry_set.rb | 6 +- lib/zip/extra_field.rb | 2 +- lib/zip/extra_field/generic.rb | 2 +- lib/zip/extra_field/zip64_placeholder.rb | 3 +- lib/zip/file.rb | 22 +++---- lib/zip/filesystem.rb | 26 ++++---- lib/zip/inflater.rb | 2 +- lib/zip/input_stream.rb | 11 ++-- lib/zip/ioextras/abstract_input_stream.rb | 2 +- lib/zip/ioextras/abstract_output_stream.rb | 2 +- lib/zip/output_stream.rb | 10 ++-- lib/zip/pass_thru_decompressor.rb | 2 +- rubyzip.gemspec | 3 +- samples/example_recursive.rb | 4 +- samples/gtk_ruby_zip.rb | 2 +- samples/qtzip.rb | 2 +- samples/zipfind.rb | 4 +- test/central_directory_entry_test.rb | 2 +- test/errors_test.rb | 1 + test/file_permissions_test.rb | 26 ++++---- test/file_test.rb | 13 ++-- test/filesystem/dir_iterator_test.rb | 2 +- test/filesystem/directory_test.rb | 12 ++-- test/filesystem/file_mutating_test.rb | 7 +-- test/filesystem/file_nonmutating_test.rb | 62 ++++++++++---------- test/filesystem/file_stat_test.rb | 8 +-- test/gentestfiles.rb | 12 ++-- test/input_stream_test.rb | 12 ++-- test/ioextras/abstract_output_stream_test.rb | 4 +- test/test_helper.rb | 4 +- test/zip64_full_test.rb | 2 +- 38 files changed, 161 insertions(+), 170 deletions(-) diff --git a/lib/zip/central_directory.rb b/lib/zip/central_directory.rb index cb7e2da7..0b6874ef 100644 --- a/lib/zip/central_directory.rb +++ b/lib/zip/central_directory.rb @@ -96,7 +96,7 @@ def read_64_e_o_c_d(buf) #:nodoc: @size_in_bytes = Entry.read_zip_64_long(buf) @cdir_offset = Entry.read_zip_64_long(buf) @zip_64_extensible = buf.slice!(0, buf.bytesize) - raise Error, 'Zip consistency problem while reading eocd structure' unless buf.size == 0 + raise Error, 'Zip consistency problem while reading eocd structure' unless buf.empty? end def read_e_o_c_d(buf) #:nodoc: @@ -113,7 +113,7 @@ def read_e_o_c_d(buf) #:nodoc: else buf.read(comment_length) end - raise Error, 'Zip consistency problem while reading eocd structure' unless buf.size == 0 + raise Error, 'Zip consistency problem while reading eocd structure' unless buf.empty? end def read_central_directory_entries(io) #:nodoc: @@ -130,7 +130,7 @@ def read_central_directory_entries(io) #:nodoc: def read_from_stream(io) #:nodoc: buf = start_buf(io) - if self.zip64_file?(buf) + if zip64_file?(buf) read_64_e_o_c_d(buf) else read_e_o_c_d(buf) diff --git a/lib/zip/compressor.rb b/lib/zip/compressor.rb index ce2b847f..079c1cb0 100644 --- a/lib/zip/compressor.rb +++ b/lib/zip/compressor.rb @@ -1,7 +1,6 @@ module Zip class Compressor #:nodoc:all - def finish - end + def finish; end end end diff --git a/lib/zip/constants.rb b/lib/zip/constants.rb index 94f8f501..5eb5c1da 100644 --- a/lib/zip/constants.rb +++ b/lib/zip/constants.rb @@ -11,9 +11,9 @@ module Zip VERSION_NEEDED_TO_EXTRACT = 20 VERSION_NEEDED_TO_EXTRACT_ZIP64 = 45 - FILE_TYPE_FILE = 010 - FILE_TYPE_DIR = 004 - FILE_TYPE_SYMLINK = 012 + FILE_TYPE_FILE = 0o10 + FILE_TYPE_DIR = 0o04 + FILE_TYPE_SYMLINK = 0o12 FSTYPE_FAT = 0 FSTYPE_AMIGA = 1 diff --git a/lib/zip/crypto/null_encryption.rb b/lib/zip/crypto/null_encryption.rb index 62d47f0e..a93f707c 100644 --- a/lib/zip/crypto/null_encryption.rb +++ b/lib/zip/crypto/null_encryption.rb @@ -24,8 +24,7 @@ def data_descriptor(_crc32, _compressed_size, _uncomprssed_size) '' end - def reset! - end + def reset!; end end class NullDecrypter < Decrypter @@ -35,8 +34,7 @@ def decrypt(data) data end - def reset!(_header) - end + def reset!(_header); end end end diff --git a/lib/zip/decompressor.rb b/lib/zip/decompressor.rb index cd0fb054..047ed5e7 100644 --- a/lib/zip/decompressor.rb +++ b/lib/zip/decompressor.rb @@ -1,5 +1,5 @@ module Zip - class Decompressor #:nodoc:all + class Decompressor #:nodoc:all CHUNK_SIZE = 32_768 def initialize(input_stream) super() diff --git a/lib/zip/dos_time.rb b/lib/zip/dos_time.rb index fd9353c6..bf0cb7e0 100644 --- a/lib/zip/dos_time.rb +++ b/lib/zip/dos_time.rb @@ -19,7 +19,7 @@ def to_binary_dos_time end def to_binary_dos_date - (day) + + day + (month << 5) + ((year - 1980) << 9) end diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 0aba0eb8..6e1829ae 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -99,7 +99,7 @@ def file_type_is?(type) end # Dynamic checkers - %w(directory file symlink).each do |k| + %w[directory file symlink].each do |k| define_method "#{k}?" do file_type_is?(k.to_sym) end @@ -239,7 +239,7 @@ def read_local_entry(io) #:nodoc:all @name = io.read(@name_length) extra = io.read(@extra_length) - @name.gsub!('\\', '/') + @name.tr!('\\', '/') if extra && extra.bytesize != @extra_length raise ::Zip::Error, 'Truncated local zip entry header' @@ -263,8 +263,8 @@ def pack_local_entry @time.to_binary_dos_time, # @last_mod_time , @time.to_binary_dos_date, # @last_mod_date , @crc, - (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size, - (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size, + zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size, + zip64 && zip64.original_size ? 0xFFFFFFFF : @size, name_size, @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv') end @@ -308,7 +308,7 @@ def unpack_c_dir_entry(buf) def set_ftype_from_c_dir_entry @ftype = case @fstype when ::Zip::FSTYPE_UNIX - @unix_perms = (@external_file_attributes >> 16) & 07777 + @unix_perms = (@external_file_attributes >> 16) & 0o7777 case (@external_file_attributes >> 28) when ::Zip::FILE_TYPE_DIR :directory @@ -384,14 +384,14 @@ def get_extra_attributes_from_path(path) # :nodoc: stat = file_stat(path) @unix_uid = stat.uid @unix_gid = stat.gid - @unix_perms = stat.mode & 07777 + @unix_perms = stat.mode & 0o7777 end def set_unix_permissions_on_path(dest_path) # BUG: does not update timestamps into account # ignore setuid/setgid bits by default. honor if @restore_ownership - unix_perms_mask = 01777 - unix_perms_mask = 07777 if @restore_ownership + unix_perms_mask = 0o1777 + unix_perms_mask = 0o7777 if @restore_ownership ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0 # File::utimes() @@ -418,15 +418,15 @@ def pack_c_dir_entry @time.to_binary_dos_time, # @last_mod_time , @time.to_binary_dos_date, # @last_mod_date , @crc, - (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size, - (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size, + zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size, + zip64 && zip64.original_size ? 0xFFFFFFFF : @size, name_size, @extra ? @extra.c_dir_size : 0, comment_size, - (zip64 && zip64.disk_start_number) ? 0xFFFF : 0, # disk number start + zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start @internal_file_attributes, # file type (binary=0, text=1) @external_file_attributes, # native filesystem attributes - (zip64 && zip64.relative_header_offset) ? 0xFFFFFFFF : @local_header_offset, + zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset, @name, @extra, @comment @@ -439,18 +439,18 @@ def write_c_dir_entry(io) #:nodoc:all when ::Zip::FSTYPE_UNIX ft = case @ftype when :file - @unix_perms ||= 0644 + @unix_perms ||= 0o644 ::Zip::FILE_TYPE_FILE when :directory - @unix_perms ||= 0755 + @unix_perms ||= 0o755 ::Zip::FILE_TYPE_DIR when :symlink - @unix_perms ||= 0755 + @unix_perms ||= 0o755 ::Zip::FILE_TYPE_SYMLINK end unless ft.nil? - @external_file_attributes = (ft << 12 | (@unix_perms & 07777)) << 16 + @external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16 end end @@ -464,7 +464,7 @@ def write_c_dir_entry(io) #:nodoc:all def ==(other) return false unless other.class == self.class # Compares contents of local entry and exposed fields - keys_equal = %w(compression_method crc compressed_size size name extra filepath).all? do |k| + keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k| other.__send__(k.to_sym) == __send__(k.to_sym) end keys_equal && time.dos_equals(other.time) diff --git a/lib/zip/entry_set.rb b/lib/zip/entry_set.rb index 21bfd381..3272b2a4 100644 --- a/lib/zip/entry_set.rb +++ b/lib/zip/entry_set.rb @@ -5,7 +5,7 @@ class EntrySet #:nodoc:all def initialize(an_enumerable = []) super() - @entry_set = {} + @entry_set = {} an_enumerable.each { |o| push(o) } end @@ -33,9 +33,9 @@ def delete(entry) entry if @entry_set.delete(to_key(entry)) end - def each(&block) + def each @entry_set = sorted_entries.dup.each do |_, value| - block.call(value) + yield(value) end end diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb index 225abeb5..cbc2fa8d 100644 --- a/lib/zip/extra_field.rb +++ b/lib/zip/extra_field.rb @@ -8,7 +8,7 @@ def initialize(binstr = nil) def extra_field_type_exist(binstr, id, len, i) field_name = ID_MAP[id].name - if self.member?(field_name) + if member?(field_name) self[field_name].merge(binstr[i, len + 4]) else field_obj = ID_MAP[id].new(binstr[i, len + 4]) diff --git a/lib/zip/extra_field/generic.rb b/lib/zip/extra_field/generic.rb index 7b60bebb..5931b5c2 100644 --- a/lib/zip/extra_field/generic.rb +++ b/lib/zip/extra_field/generic.rb @@ -1,7 +1,7 @@ module Zip class ExtraField::Generic def self.register_map - if self.const_defined?(:HEADER_ID) + if const_defined?(:HEADER_ID) ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self end end diff --git a/lib/zip/extra_field/zip64_placeholder.rb b/lib/zip/extra_field/zip64_placeholder.rb index f2573eb4..dfaa56e8 100644 --- a/lib/zip/extra_field/zip64_placeholder.rb +++ b/lib/zip/extra_field/zip64_placeholder.rb @@ -6,8 +6,7 @@ class ExtraField::Zip64Placeholder < ExtraField::Generic HEADER_ID = ['9999'].pack('H*') # this ID is used by other libraries such as .NET's Ionic.zip register_map - def initialize(_binstr = nil) - end + def initialize(_binstr = nil); end def pack_for_local "\x00" * 16 diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 8c5173ba..4bc3edc4 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -69,16 +69,15 @@ def initialize(file_name, create = false, buffer = false, options = {}) @name = file_name @comment = '' @create = create ? true : false # allow any truthy value to mean true - case - when !buffer && ::File.size?(file_name) + if !buffer && ::File.size?(file_name) @create = false @file_permissions = ::File.stat(file_name).mode ::File.open(name, 'rb') do |f| read_from_stream(f) end - when @create + elsif @create @entry_set = EntrySet.new - when ::File.zero?(file_name) + elsif ::File.zero?(file_name) raise Error, "File #{file_name} has zero size. Did you mean to pass the create flag?" else raise Error, "File #{file_name} not found" @@ -151,10 +150,9 @@ def foreach(aZipFileName, &block) end def get_segment_size_for_split(segment_size) - case - when MIN_SEGMENT_SIZE > segment_size + if MIN_SEGMENT_SIZE > segment_size MIN_SEGMENT_SIZE - when MAX_SEGMENT_SIZE < segment_size + elsif MAX_SEGMENT_SIZE < segment_size MAX_SEGMENT_SIZE else segment_size @@ -162,8 +160,10 @@ def get_segment_size_for_split(segment_size) end def get_partial_zip_file_name(zip_file_name, partial_zip_file_name) - partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/, - partial_zip_file_name + ::File.extname(zip_file_name)) unless partial_zip_file_name.nil? + unless partial_zip_file_name.nil? + partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/, + partial_zip_file_name + ::File.extname(zip_file_name)) + end partial_zip_file_name ||= zip_file_name partial_zip_file_name end @@ -237,7 +237,7 @@ def get_input_stream(entry, &aProc) # specified. If a block is passed the stream object is passed to the block and # the stream is automatically closed afterwards just as with ruby's builtin # File.open method. - def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil, &aProc) + def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil, &aProc) new_entry = if entry.kind_of?(Entry) entry @@ -366,7 +366,7 @@ def get_entry(entry) end # Creates a directory - def mkdir(entryName, permissionInt = 0755) + def mkdir(entryName, permissionInt = 0o755) raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName) entryName = entryName.dup.to_s entryName << '/' unless entryName.end_with?('/') diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index c77cdf4d..fb64325b 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -142,9 +142,9 @@ def rdev_minor def ftype if file? - return 'file' + 'file' elsif directory? - return 'directory' + 'directory' else raise StandardError, 'Unknown file type' end @@ -198,30 +198,30 @@ def exists?(fileName) alias grpowned? exists? def readable?(fileName) - unix_mode_cmp(fileName, 0444) + unix_mode_cmp(fileName, 0o444) end alias readable_real? readable? def writable?(fileName) - unix_mode_cmp(fileName, 0222) + unix_mode_cmp(fileName, 0o222) end alias writable_real? writable? def executable?(fileName) - unix_mode_cmp(fileName, 0111) + unix_mode_cmp(fileName, 0o111) end alias executable_real? executable? def setuid?(fileName) - unix_mode_cmp(fileName, 04000) + unix_mode_cmp(fileName, 0o4000) end def setgid?(fileName) - unix_mode_cmp(fileName, 02000) + unix_mode_cmp(fileName, 0o2000) end def sticky?(fileName) - unix_mode_cmp(fileName, 01000) + unix_mode_cmp(fileName, 0o1000) end def umask(*args) @@ -237,8 +237,8 @@ def directory?(fileName) expand_path(fileName) == '/' || (!entry.nil? && entry.directory?) end - def open(fileName, openMode = 'r', permissionInt = 0644, &block) - openMode.gsub!('b', '') # ignore b option + def open(fileName, openMode = 'r', permissionInt = 0o644, &block) + openMode.delete!('b') # ignore b option case openMode when 'r' @mappedZip.get_input_stream(fileName, &block) @@ -260,7 +260,7 @@ def size(fileName) # Returns nil for not found and nil for directories def size?(fileName) entry = @mappedZip.find_entry(fileName) - (entry.nil? || entry.directory?) ? nil : entry.size + entry.nil? || entry.directory? ? nil : entry.size end def chown(ownerInt, groupInt, *filenames) @@ -498,7 +498,7 @@ def delete(entryName) alias rmdir delete alias unlink delete - def mkdir(entryName, permissionInt = 0755) + def mkdir(entryName, permissionInt = 0o755) @mappedZip.mkdir(entryName, permissionInt) end @@ -586,7 +586,7 @@ def rename(fileName, newName, &continueOnExistsProc) &continueOnExistsProc) end - def mkdir(fileName, permissionInt = 0755) + def mkdir(fileName, permissionInt = 0o755) @zipFile.mkdir(expand_to_entry(fileName), permissionInt) end diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index 0e2b97e1..ef952f07 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -5,7 +5,7 @@ def initialize(input_stream, decrypter = NullDecrypter.new) @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS) @output_buffer = '' @has_returned_empty_string = false - @decrypter = decrypter + @decrypter = decrypter end def sysread(number_of_bytes = nil, buf = '') diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index f8e78868..4c861c56 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -129,23 +129,22 @@ def open_entry end if @current_entry && @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 \ && @current_entry.compressed_size == 0 \ - && @current_entry.size == 0 && !@internal + && @current_entry.empty? && !@internal raise GPFBit3Error, 'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \ 'Please use ::Zip::File instead of ::Zip::InputStream' end - @decompressor = get_decompressor + @decompressor = get_decompressor flush @current_entry end def get_decompressor - case - when @current_entry.nil? + if @current_entry.nil? ::Zip::NullDecompressor - when @current_entry.compression_method == ::Zip::Entry::STORED + elsif @current_entry.compression_method == ::Zip::Entry::STORED ::Zip::PassThruDecompressor.new(@archive_io, @current_entry.size) - when @current_entry.compression_method == ::Zip::Entry::DEFLATED + elsif @current_entry.compression_method == ::Zip::Entry::DEFLATED header = @archive_io.read(@decrypter.header_bytesize) @decrypter.reset!(header) ::Zip::Inflater.new(@archive_io, @decrypter) diff --git a/lib/zip/ioextras/abstract_input_stream.rb b/lib/zip/ioextras/abstract_input_stream.rb index 5db051e1..7b7fd61d 100644 --- a/lib/zip/ioextras/abstract_input_stream.rb +++ b/lib/zip/ioextras/abstract_input_stream.rb @@ -33,7 +33,7 @@ def read(number_of_bytes = nil, buf = '') sysread(number_of_bytes, buf) end - if tbuf.nil? || tbuf.length == 0 + if tbuf.nil? || tbuf.empty? return nil if number_of_bytes return '' end diff --git a/lib/zip/ioextras/abstract_output_stream.rb b/lib/zip/ioextras/abstract_output_stream.rb index c1246f97..69d0cc7c 100644 --- a/lib/zip/ioextras/abstract_output_stream.rb +++ b/lib/zip/ioextras/abstract_output_stream.rb @@ -15,7 +15,7 @@ def print(*params) end def printf(a_format_string, *params) - self << sprintf(a_format_string, *params) + self << format(a_format_string, *params) end def putc(an_object) diff --git a/lib/zip/output_stream.rb b/lib/zip/output_stream.rb index 693678be..d9bbc4df 100644 --- a/lib/zip/output_stream.rb +++ b/lib/zip/output_stream.rb @@ -87,11 +87,11 @@ def close_buffer # +entry+ can be a ZipEntry object or a string. def put_next_entry(entry_name, comment = nil, extra = nil, compression_method = Entry::DEFLATED, level = Zip.default_compression) raise Error, 'zip stream is closed' if @closed - if entry_name.kind_of?(Entry) - new_entry = entry_name - else - new_entry = Entry.new(@file_name, entry_name.to_s) - end + new_entry = if entry_name.kind_of?(Entry) + entry_name + else + Entry.new(@file_name, entry_name.to_s) + end new_entry.comment = comment unless comment.nil? unless extra.nil? new_entry.extra = extra.is_a?(ExtraField) ? extra : ExtraField.new(extra.to_s) diff --git a/lib/zip/pass_thru_decompressor.rb b/lib/zip/pass_thru_decompressor.rb index ca30f5d4..485462c5 100644 --- a/lib/zip/pass_thru_decompressor.rb +++ b/lib/zip/pass_thru_decompressor.rb @@ -1,5 +1,5 @@ module Zip - class PassThruDecompressor < Decompressor #:nodoc:all + class PassThruDecompressor < Decompressor #:nodoc:all def initialize(input_stream, chars_to_read) super(input_stream) @chars_to_read = chars_to_read diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 4857e0fb..4ca36c2d 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -1,4 +1,5 @@ #-*- encoding: utf-8 -*- + lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'zip/version' @@ -11,7 +12,7 @@ Gem::Specification.new do |s| s.homepage = 'http://github.com/rubyzip/rubyzip' s.platform = Gem::Platform::RUBY s.summary = 'rubyzip is a ruby module for reading and writing zip files' - s.files = Dir.glob('{samples,lib}/**/*.rb') + %w(README.md TODO Rakefile) + s.files = Dir.glob('{samples,lib}/**/*.rb') + %w[README.md TODO Rakefile] s.test_files = Dir.glob('test/**/*') s.require_paths = ['lib'] s.license = 'BSD 2-Clause' diff --git a/samples/example_recursive.rb b/samples/example_recursive.rb index c0680b72..ea4c082b 100644 --- a/samples/example_recursive.rb +++ b/samples/example_recursive.rb @@ -19,7 +19,7 @@ def initialize(input_dir, output_file) # Zip the input directory. def write - entries = Dir.entries(@input_dir) - %w(. ..) + entries = Dir.entries(@input_dir) - %w[. ..] ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |io| write_entries entries, '', io @@ -45,7 +45,7 @@ def write_entries(entries, path, io) def recursively_deflate_directory(disk_file_path, io, zip_file_path) io.mkdir zip_file_path - subdir = Dir.entries(disk_file_path) - %w(. ..) + subdir = Dir.entries(disk_file_path) - %w[. ..] write_entries subdir, zip_file_path, io end diff --git a/samples/gtk_ruby_zip.rb b/samples/gtk_ruby_zip.rb index 2b5a2883..62f005a5 100755 --- a/samples/gtk_ruby_zip.rb +++ b/samples/gtk_ruby_zip.rb @@ -31,7 +31,7 @@ def initialize sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) box.pack_start(sw, true, true, 0) - @clist = Gtk::CList.new(%w(Name Size Compression)) + @clist = Gtk::CList.new(%w[Name Size Compression]) @clist.set_selection_mode(Gtk::SELECTION_BROWSE) @clist.set_column_width(0, 120) @clist.set_column_width(1, 120) diff --git a/samples/qtzip.rb b/samples/qtzip.rb index c47fc97c..1d450a78 100755 --- a/samples/qtzip.rb +++ b/samples/qtzip.rb @@ -65,7 +65,7 @@ def extract_files end puts "selected_items.size = #{selected_items.size}" puts "unselected_items.size = #{unselected_items.size}" - items = selected_items.size > 0 ? selected_items : unselected_items + items = !selected_items.empty? ? selected_items : unselected_items puts "items.size = #{items.size}" d = Qt::FileDialog.get_existing_directory(nil, self) diff --git a/samples/zipfind.rb b/samples/zipfind.rb index fa2aa4e6..400e0a69 100755 --- a/samples/zipfind.rb +++ b/samples/zipfind.rb @@ -31,7 +31,7 @@ def self.find_file(path, fileNamePattern, zipFilePattern = /\.zip$/i) end end -if __FILE__ == $0 +if $0 == __FILE__ module ZipFindConsoleRunner PATH_ARG_INDEX = 0 FILENAME_PATTERN_ARG_INDEX = 1 @@ -47,7 +47,7 @@ def self.run(args) end def self.check_args(args) - if (args.size != 3) + if args.size != 3 usage exit end diff --git a/test/central_directory_entry_test.rb b/test/central_directory_entry_test.rb index 35a16cde..fa0d8065 100644 --- a/test/central_directory_entry_test.rb +++ b/test/central_directory_entry_test.rb @@ -2,7 +2,7 @@ class ZipCentralDirectoryEntryTest < MiniTest::Test def test_read_from_stream - File.open('test/data/testDirectory.bin', 'rb') do |file| + File.open('test/data/testDirectory.bin', 'rb') do |file| entry = ::Zip::Entry.read_c_dir_entry(file) assert_equal('longAscii.txt', entry.name) diff --git a/test/errors_test.rb b/test/errors_test.rb index a65a3afa..2c6adb2f 100644 --- a/test/errors_test.rb +++ b/test/errors_test.rb @@ -1,4 +1,5 @@ # encoding: utf-8 + require 'test_helper' class ErrorsTest < MiniTest::Test diff --git a/test/file_permissions_test.rb b/test/file_permissions_test.rb index fd666b88..4e4573a4 100644 --- a/test/file_permissions_test.rb +++ b/test/file_permissions_test.rb @@ -1,9 +1,8 @@ require 'test_helper' class FilePermissionsTest < MiniTest::Test - - ZIPNAME = File.join(File.dirname(__FILE__), "umask.zip") - FILENAME = File.join(File.dirname(__FILE__), "umask.txt") + ZIPNAME = File.join(File.dirname(__FILE__), 'umask.zip') + FILENAME = File.join(File.dirname(__FILE__), 'umask.txt') def teardown ::File.unlink(ZIPNAME) @@ -16,7 +15,7 @@ def test_current_umask end def test_umask_000 - set_umask(0000) do + set_umask(0o000) do create_files end @@ -24,7 +23,7 @@ def test_umask_000 end def test_umask_066 - set_umask(0066) do + set_umask(0o066) do create_files end @@ -32,7 +31,7 @@ def test_umask_066 end def test_umask_027 - set_umask(0027) do + set_umask(0o027) do create_files end @@ -48,7 +47,7 @@ def assert_matching_permissions(expected_file, actual_file) def create_files ::Zip::File.open(ZIPNAME, ::Zip::File::CREATE) do |zip| - zip.comment = "test" + zip.comment = 'test' end ::File.open(FILENAME, 'w') do |file| @@ -57,13 +56,10 @@ def create_files end # If anything goes wrong, make sure the umask is restored. - def set_umask(umask, &block) - begin - saved_umask = ::File.umask(umask) - yield - ensure - ::File.umask(saved_umask) - end + def set_umask(umask) + saved_umask = ::File.umask(umask) + yield + ensure + ::File.umask(saved_umask) end - end diff --git a/test/file_test.rb b/test/file_test.rb index a6d187ea..73f60af5 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -142,15 +142,15 @@ def test_add def test_recover_permissions_after_add_files_to_archive srcZip = TEST_ZIP.zip_name - ::File.chmod(0664, srcZip) + ::File.chmod(0o664, srcZip) srcFile = 'test/data/file2.txt' entryName = 'newEntryName.rb' - assert_equal(::File.stat(srcZip).mode, 0100664) + assert_equal(::File.stat(srcZip).mode, 0o100664) assert(::File.exist?(srcZip)) zf = ::Zip::File.new(srcZip, ::Zip::File::CREATE) zf.add(entryName, srcFile) zf.close - assert_equal(::File.stat(srcZip).mode, 0100664) + assert_equal(::File.stat(srcZip).mode, 0o100664) end def test_add_existing_entry_name @@ -234,7 +234,7 @@ def test_rename_with_each zf.mkdir('test') arr << 'test/' arr_renamed << 'Ztest/' - %w(a b c d).each do |f| + %w[a b c d].each do |f| zf.get_output_stream("test/#{f}") { |file| file.puts 'aaaa' } arr << "test/#{f}" arr_renamed << "Ztest/#{f}" @@ -329,7 +329,7 @@ def test_replace def test_replace_non_entry entryToReplace = 'nonExistingEntryname' - ::Zip::File.open(TEST_ZIP.zip_name) do |zf| + ::Zip::File.open(TEST_ZIP.zip_name) do |zf| assert_raises(Errno::ENOENT) { zf.replace(entryToReplace, 'test/data/file2.txt') } end end @@ -434,7 +434,6 @@ def test_compound1 filename_to_remove = originalEntries.map(&:to_s).find { |name| name.match('longBinary') } zf.remove(filename_to_remove) assert_not_contains(zf, filename_to_remove) - ensure zf.close end @@ -558,7 +557,7 @@ def test_odd_extra_field entry_count = 0 File.open 'test/data/oddExtraField.zip', 'rb' do |zip_io| Zip::File.open_buffer zip_io.read do |zip| - zip.each do |zip_entry| + zip.each do |_zip_entry| entry_count += 1 end end diff --git a/test/filesystem/dir_iterator_test.rb b/test/filesystem/dir_iterator_test.rb index c2cf00ac..8d12ce27 100644 --- a/test/filesystem/dir_iterator_test.rb +++ b/test/filesystem/dir_iterator_test.rb @@ -2,7 +2,7 @@ require 'zip/filesystem' class ZipFsDirIteratorTest < MiniTest::Test - FILENAME_ARRAY = %w(f1 f2 f3 f4 f5 f6) + FILENAME_ARRAY = %w[f1 f2 f3 f4 f5 f6] def setup @dirIt = ::Zip::FileSystem::ZipFsDirIterator.new(FILENAME_ARRAY) diff --git a/test/filesystem/directory_test.rb b/test/filesystem/directory_test.rb index d6c029f3..ec0a7371 100644 --- a/test/filesystem/directory_test.rb +++ b/test/filesystem/directory_test.rb @@ -51,10 +51,10 @@ def test_pwd_chdir_entries zf.dir.chdir 'file1' end - assert_equal(%w(dir1 dir2 file1).sort, zf.dir.entries('.').sort) + assert_equal(%w[dir1 dir2 file1].sort, zf.dir.entries('.').sort) zf.dir.chdir 'dir1' assert_equal('/dir1', zf.dir.pwd) - assert_equal(%w(dir11 file11 file12), zf.dir.entries('.').sort) + assert_equal(%w[dir11 file11 file12], zf.dir.entries('.').sort) zf.dir.chdir '../dir2/dir21' assert_equal('/dir2/dir21', zf.dir.pwd) @@ -77,11 +77,11 @@ def test_foreach entries = [] zf.dir.foreach('.') { |e| entries << e } - assert_equal(%w(dir1 dir2 file1).sort, entries.sort) + assert_equal(%w[dir1 dir2 file1].sort, entries.sort) entries = [] zf.dir.foreach('dir1') { |e| entries << e } - assert_equal(%w(dir11 file11 file12), entries.sort) + assert_equal(%w[dir11 file11 file12], entries.sort) end end @@ -110,11 +110,11 @@ def test_open_new end d = zf.dir.new('.') - assert_equal(%w(file1 dir1 dir2).sort, d.entries.sort) + assert_equal(%w[file1 dir1 dir2].sort, d.entries.sort) d.close zf.dir.open('dir1') do |dir| - assert_equal(%w(dir11 file11 file12).sort, dir.entries.sort) + assert_equal(%w[dir11 file11 file12].sort, dir.entries.sort) end end end diff --git a/test/filesystem/file_mutating_test.rb b/test/filesystem/file_mutating_test.rb index e398060a..ccba6e3d 100644 --- a/test/filesystem/file_mutating_test.rb +++ b/test/filesystem/file_mutating_test.rb @@ -7,8 +7,7 @@ def setup FileUtils.cp('test/data/zipWithDirs.zip', TEST_ZIP) end - def teardown - end + def teardown; end def test_delete do_test_delete_or_unlink(:delete) @@ -51,11 +50,11 @@ def test_rename def test_chmod ::Zip::File.open(TEST_ZIP) do |zf| - zf.file.chmod(0765, 'file1') + zf.file.chmod(0o765, 'file1') end ::Zip::File.open(TEST_ZIP) do |zf| - assert_equal(0100765, zf.file.stat('file1').mode) + assert_equal(0o100765, zf.file.stat('file1').mode) end end diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index e8242258..62486666 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -14,11 +14,11 @@ def teardown def test_umask assert_equal(::File.umask, @zip_file.file.umask) - @zip_file.file.umask(0006) + @zip_file.file.umask(0o006) end def test_exists? - assert(! @zip_file.file.exists?('notAFile')) + assert(!@zip_file.file.exists?('notAFile')) assert(@zip_file.file.exists?('file1')) assert(@zip_file.file.exists?('dir1')) assert(@zip_file.file.exists?('dir1/')) @@ -114,13 +114,13 @@ def test_size? def test_file? assert(@zip_file.file.file?('file1')) assert(@zip_file.file.file?('dir2/file21')) - assert(! @zip_file.file.file?('dir1')) - assert(! @zip_file.file.file?('dir1/dir11')) + assert(!@zip_file.file.file?('dir1')) + assert(!@zip_file.file.file?('dir1/dir11')) assert(@zip_file.file.stat('file1').file?) assert(@zip_file.file.stat('dir2/file21').file?) - assert(! @zip_file.file.stat('dir1').file?) - assert(! @zip_file.file.stat('dir1/dir11').file?) + assert(!@zip_file.file.stat('dir1').file?) + assert(!@zip_file.file.stat('dir1/dir11').file?) end include ExtraAssertions @@ -160,15 +160,15 @@ def test_utime end def assert_always_false(operation) - assert(! @zip_file.file.send(operation, 'noSuchFile')) - assert(! @zip_file.file.send(operation, 'file1')) - assert(! @zip_file.file.send(operation, 'dir1')) - assert(! @zip_file.file.stat('file1').send(operation)) - assert(! @zip_file.file.stat('dir1').send(operation)) + assert(!@zip_file.file.send(operation, 'noSuchFile')) + assert(!@zip_file.file.send(operation, 'file1')) + assert(!@zip_file.file.send(operation, 'dir1')) + assert(!@zip_file.file.stat('file1').send(operation)) + assert(!@zip_file.file.stat('dir1').send(operation)) end def assert_true_if_entry_exists(operation) - assert(! @zip_file.file.send(operation, 'noSuchFile')) + assert(!@zip_file.file.send(operation, 'noSuchFile')) assert(@zip_file.file.send(operation, 'file1')) assert(@zip_file.file.send(operation, 'dir1')) assert(@zip_file.file.stat('file1').send(operation)) @@ -221,15 +221,15 @@ def test_link end def test_directory? - assert(! @zip_file.file.directory?('notAFile')) - assert(! @zip_file.file.directory?('file1')) - assert(! @zip_file.file.directory?('dir1/file11')) + assert(!@zip_file.file.directory?('notAFile')) + assert(!@zip_file.file.directory?('file1')) + assert(!@zip_file.file.directory?('dir1/file11')) assert(@zip_file.file.directory?('dir1')) assert(@zip_file.file.directory?('dir1/')) assert(@zip_file.file.directory?('dir2/dir21')) - assert(! @zip_file.file.stat('file1').directory?) - assert(! @zip_file.file.stat('dir1/file11').directory?) + assert(!@zip_file.file.stat('file1').directory?) + assert(!@zip_file.file.stat('dir1/file11').directory?) assert(@zip_file.file.stat('dir1').directory?) assert(@zip_file.file.stat('dir1/').directory?) assert(@zip_file.file.stat('dir2/dir21').directory?) @@ -243,8 +243,8 @@ def test_chown end def test_zero? - assert(! @zip_file.file.zero?('notAFile')) - assert(! @zip_file.file.zero?('file1')) + assert(!@zip_file.file.zero?('notAFile')) + assert(!@zip_file.file.zero?('file1')) assert(@zip_file.file.zero?('dir1')) blockCalled = false ::Zip::File.open('test/data/generated/5entry.zip') do |zf| @@ -253,7 +253,7 @@ def test_zero? end assert(blockCalled) - assert(! @zip_file.file.stat('file1').zero?) + assert(!@zip_file.file.stat('file1').zero?) assert(@zip_file.file.stat('dir1').zero?) blockCalled = false ::Zip::File.open('test/data/generated/5entry.zip') do |zf| @@ -309,7 +309,7 @@ def test_ntfs_time end def test_readable? - assert(! @zip_file.file.readable?('noSuchFile')) + assert(!@zip_file.file.readable?('noSuchFile')) assert(@zip_file.file.readable?('file1')) assert(@zip_file.file.readable?('dir1')) assert(@zip_file.file.stat('file1').readable?) @@ -317,7 +317,7 @@ def test_readable? end def test_readable_real? - assert(! @zip_file.file.readable_real?('noSuchFile')) + assert(!@zip_file.file.readable_real?('noSuchFile')) assert(@zip_file.file.readable_real?('file1')) assert(@zip_file.file.readable_real?('dir1')) assert(@zip_file.file.stat('file1').readable_real?) @@ -325,7 +325,7 @@ def test_readable_real? end def test_writable? - assert(! @zip_file.file.writable?('noSuchFile')) + assert(!@zip_file.file.writable?('noSuchFile')) assert(@zip_file.file.writable?('file1')) assert(@zip_file.file.writable?('dir1')) assert(@zip_file.file.stat('file1').writable?) @@ -333,7 +333,7 @@ def test_writable? end def test_writable_real? - assert(! @zip_file.file.writable_real?('noSuchFile')) + assert(!@zip_file.file.writable_real?('noSuchFile')) assert(@zip_file.file.writable_real?('file1')) assert(@zip_file.file.writable_real?('dir1')) assert(@zip_file.file.stat('file1').writable_real?) @@ -341,18 +341,18 @@ def test_writable_real? end def test_executable? - assert(! @zip_file.file.executable?('noSuchFile')) - assert(! @zip_file.file.executable?('file1')) + assert(!@zip_file.file.executable?('noSuchFile')) + assert(!@zip_file.file.executable?('file1')) assert(@zip_file.file.executable?('dir1')) - assert(! @zip_file.file.stat('file1').executable?) + assert(!@zip_file.file.stat('file1').executable?) assert(@zip_file.file.stat('dir1').executable?) end def test_executable_real? - assert(! @zip_file.file.executable_real?('noSuchFile')) - assert(! @zip_file.file.executable_real?('file1')) + assert(!@zip_file.file.executable_real?('noSuchFile')) + assert(!@zip_file.file.executable_real?('file1')) assert(@zip_file.file.executable_real?('dir1')) - assert(! @zip_file.file.stat('file1').executable_real?) + assert(!@zip_file.file.stat('file1').executable_real?) assert(@zip_file.file.stat('dir1').executable_real?) end @@ -455,7 +455,7 @@ def test_glob zf.glob('**/foo.txt') do |match| results << "<#{match.class.name}: #{match}>" end - assert((!results.empty?), 'block not run, or run out of context') + assert(!results.empty?, 'block not run, or run out of context') assert_equal 2, results.size assert_operator results, :include?, '' assert_operator results, :include?, '' diff --git a/test/filesystem/file_stat_test.rb b/test/filesystem/file_stat_test.rb index 51e60d9c..05d7fff8 100644 --- a/test/filesystem/file_stat_test.rb +++ b/test/filesystem/file_stat_test.rb @@ -32,10 +32,10 @@ def test_ftype end def test_mode - assert_equal(0600, @zip_file.file.stat('file1').mode & 0777) - assert_equal(0600, @zip_file.file.stat('file1').mode & 0777) - assert_equal(0755, @zip_file.file.stat('dir1').mode & 0777) - assert_equal(0755, @zip_file.file.stat('dir1').mode & 0777) + assert_equal(0o600, @zip_file.file.stat('file1').mode & 0o777) + assert_equal(0o600, @zip_file.file.stat('file1').mode & 0o777) + assert_equal(0o755, @zip_file.file.stat('dir1').mode & 0o777) + assert_equal(0o755, @zip_file.file.stat('dir1').mode & 0o777) end def test_dev diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index 88ffd385..a8a2126b 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -76,7 +76,7 @@ def self.create_test_zips File.open('test/data/generated/empty.txt', 'w') {} File.open('test/data/generated/empty_chmod640.txt', 'w') {} - ::File.chmod(0640, 'test/data/generated/empty_chmod640.txt') + ::File.chmod(0o640, 'test/data/generated/empty_chmod640.txt') File.open('test/data/generated/short.txt', 'w') { |file| file << 'ABCDEF' } ziptestTxt = '' @@ -112,15 +112,15 @@ def self.create_test_zips # http://stahlworks.com/dev/index.php?tool=zipunzip # that works with the above code raise $!.to_s + - "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" \ - "to create test data. If you don't have it you can download\n" \ - 'the necessary test files at http://sf.net/projects/rubyzip.' + "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" \ + "to create test data. If you don't have it you can download\n" \ + 'the necessary test files at http://sf.net/projects/rubyzip.' end TEST_ZIP1 = TestZipFile.new('test/data/generated/empty.zip', []) - TEST_ZIP2 = TestZipFile.new('test/data/generated/5entry.zip', %w(test/data/generated/longAscii.txt test/data/generated/empty.txt test/data/generated/empty_chmod640.txt test/data/generated/short.txt test/data/generated/longBinary.bin), + TEST_ZIP2 = TestZipFile.new('test/data/generated/5entry.zip', %w[test/data/generated/longAscii.txt test/data/generated/empty.txt test/data/generated/empty_chmod640.txt test/data/generated/short.txt test/data/generated/longBinary.bin], 'my zip comment') - TEST_ZIP3 = TestZipFile.new('test/data/generated/test1.zip', %w(test/data/file1.txt)) + TEST_ZIP3 = TestZipFile.new('test/data/generated/test1.zip', %w[test/data/file1.txt]) TEST_ZIP4 = TestZipFile.new('test/data/generated/zipWithDir.zip', ['test/data/file1.txt', TestFiles::EMPTY_TEST_DIR]) end diff --git a/test/input_stream_test.rb b/test/input_stream_test.rb index 3a31684d..773ee6b5 100644 --- a/test/input_stream_test.rb +++ b/test/input_stream_test.rb @@ -70,7 +70,7 @@ def test_incomplete_reads entry = zis.get_next_entry # longAscii.txt assert_equal(false, zis.eof?) assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], entry.name) - assert zis.gets.length > 0 + assert !zis.gets.empty? assert_equal(false, zis.eof?) entry = zis.get_next_entry # empty.txt assert_equal(TestZipFile::TEST_ZIP2.entry_names[1], entry.name) @@ -84,10 +84,10 @@ def test_incomplete_reads assert_equal(true, zis.eof?) entry = zis.get_next_entry # short.txt assert_equal(TestZipFile::TEST_ZIP2.entry_names[3], entry.name) - assert zis.gets.length > 0 + assert !zis.gets.empty? entry = zis.get_next_entry # longBinary.bin assert_equal(TestZipFile::TEST_ZIP2.entry_names[4], entry.name) - assert zis.gets.length > 0 + assert !zis.gets.empty? end end @@ -97,7 +97,7 @@ def test_incomplete_reads_from_string_io entry = zis.get_next_entry # longAscii.txt assert_equal(false, zis.eof?) assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], entry.name) - assert zis.gets.length > 0 + assert !zis.gets.empty? assert_equal(false, zis.eof?) entry = zis.get_next_entry # empty.txt assert_equal(TestZipFile::TEST_ZIP2.entry_names[1], entry.name) @@ -111,10 +111,10 @@ def test_incomplete_reads_from_string_io assert_equal(true, zis.eof?) entry = zis.get_next_entry # short.txt assert_equal(TestZipFile::TEST_ZIP2.entry_names[3], entry.name) - assert zis.gets.length > 0 + assert !zis.gets.empty? entry = zis.get_next_entry # longBinary.bin assert_equal(TestZipFile::TEST_ZIP2.entry_names[4], entry.name) - assert zis.gets.length > 0 + assert !zis.gets.empty? end end diff --git a/test/ioextras/abstract_output_stream_test.rb b/test/ioextras/abstract_output_stream_test.rb index 3c2cefa0..3077db43 100644 --- a/test/ioextras/abstract_output_stream_test.rb +++ b/test/ioextras/abstract_output_stream_test.rb @@ -92,11 +92,11 @@ def test_puts assert_equal("hello\nworld\n", @output_stream.buffer) @output_stream.buffer = '' - @output_stream.puts(["hello\n", "world\n"]) + @output_stream.puts(%W[hello\n world\n]) assert_equal("hello\nworld\n", @output_stream.buffer) @output_stream.buffer = '' - @output_stream.puts(["hello\n", "world\n"], 'bingo') + @output_stream.puts(%W[hello\n world\n], 'bingo') assert_equal("hello\nworld\nbingo\n", @output_stream.buffer) @output_stream.buffer = '' diff --git a/test/test_helper.rb b/test/test_helper.rb index c7cbfb95..ddeba58b 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -103,7 +103,7 @@ def assert_entry_contents_for_stream(filename, zis, entryName) File.open(filename, 'rb') do |file| expected = file.read actual = zis.read - if (expected != actual) + if expected != actual if (expected && actual) && (expected.length > 400 || actual.length > 400) zipEntryFilename = entryName + '.zipEntry' File.open(zipEntryFilename, 'wb') { |entryfile| entryfile << actual } @@ -118,7 +118,7 @@ def assert_entry_contents_for_stream(filename, zis, entryName) def self.assert_contents(filename, aString) fileContents = '' File.open(filename, 'rb') { |f| fileContents = f.read } - if (fileContents != aString) + if fileContents != aString if fileContents.length > 400 || aString.length > 400 stringFile = filename + '.other' File.open(stringFile, 'wb') { |f| f << aString } diff --git a/test/zip64_full_test.rb b/test/zip64_full_test.rb index d7fccbb4..464388dc 100644 --- a/test/zip64_full_test.rb +++ b/test/zip64_full_test.rb @@ -36,7 +36,7 @@ def test_large_zip_file end ::Zip::File.open(test_filename) do |zf| - assert_equal %w(first_file.txt huge_file last_file.txt), zf.entries.map(&:name) + assert_equal %w[first_file.txt huge_file last_file.txt], zf.entries.map(&:name) assert_equal first_text, zf.read('first_file.txt') assert_equal last_text, zf.read('last_file.txt') end From 3c0de6cd0866d3e53859f52894ab18bfa2611fa6 Mon Sep 17 00:00:00 2001 From: Takumasa Ochi Date: Mon, 17 Apr 2017 14:53:10 +0900 Subject: [PATCH 06/72] Make naming on README more consistent --- README.md | 26 +++++++++++++------------- samples/example_recursive.rb | 24 ++++++++++++------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 354b82da..6236f152 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile| # - The original file, including the path to find it zipfile.add(filename, File.join(folder, filename)) end - zipfile.get_output_stream("myFile") { |os| os.write "myFile contains just this" } + zipfile.get_output_stream("myFile") { |f| f.write "myFile contains just this" } end ``` @@ -85,36 +85,36 @@ class ZipFileGenerator def write entries = Dir.entries(@input_dir) - %w(. ..) - ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |io| - write_entries entries, '', io + ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile| + write_entries entries, '', zipfile end end private # A helper method to make the recursion work. - def write_entries(entries, path, io) + def write_entries(entries, path, zipfile) entries.each do |e| - zip_file_path = path == '' ? e : File.join(path, e) - disk_file_path = File.join(@input_dir, zip_file_path) + zipfile_path = path == '' ? e : File.join(path, e) + disk_file_path = File.join(@input_dir, zipfile_path) puts "Deflating #{disk_file_path}" if File.directory? disk_file_path - recursively_deflate_directory(disk_file_path, io, zip_file_path) + recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) else - put_into_archive(disk_file_path, io, zip_file_path) + put_into_archive(disk_file_path, zipfile, zipfile_path) end end end - def recursively_deflate_directory(disk_file_path, io, zip_file_path) - io.mkdir zip_file_path + def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) + zipfile.mkdir zipfile_path subdir = Dir.entries(disk_file_path) - %w(. ..) - write_entries subdir, zip_file_path, io + write_entries subdir, zipfile_path, zipfile end - def put_into_archive(disk_file_path, io, zip_file_path) - io.get_output_stream(zip_file_path) do |f| + def put_into_archive(disk_file_path, zipfile, zipfile_path) + zipfile.get_output_stream(zipfile_path) do |f| f.write(File.open(disk_file_path, 'rb').read) end end diff --git a/samples/example_recursive.rb b/samples/example_recursive.rb index ea4c082b..207934b2 100644 --- a/samples/example_recursive.rb +++ b/samples/example_recursive.rb @@ -21,35 +21,35 @@ def initialize(input_dir, output_file) def write entries = Dir.entries(@input_dir) - %w[. ..] - ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |io| - write_entries entries, '', io + ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile| + write_entries entries, '', zipfile end end private # A helper method to make the recursion work. - def write_entries(entries, path, io) + def write_entries(entries, path, zipfile) entries.each do |e| - zip_file_path = path == '' ? e : File.join(path, e) - disk_file_path = File.join(@input_dir, zip_file_path) + zipfile_path = path == '' ? e : File.join(path, e) + disk_file_path = File.join(@input_dir, zipfile_path) puts "Deflating #{disk_file_path}" if File.directory? disk_file_path - recursively_deflate_directory(disk_file_path, io, zip_file_path) + recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) else - put_into_archive(disk_file_path, io, zip_file_path) + put_into_archive(disk_file_path, zipfile, zipfile_path) end end end - def recursively_deflate_directory(disk_file_path, io, zip_file_path) - io.mkdir zip_file_path + def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) + zipfile.mkdir zipfile_path subdir = Dir.entries(disk_file_path) - %w[. ..] - write_entries subdir, zip_file_path, io + write_entries subdir, zipfile_path, zipfile end - def put_into_archive(disk_file_path, io, zip_file_path) - io.add(zip_file_path, disk_file_path) + def put_into_archive(disk_file_path, zipfile, zipfile_path) + zipfile.add(zipfile_path, disk_file_path) end end From a9f020c0a067eee25fa2cb1f0e0dd8a00e4a307f Mon Sep 17 00:00:00 2001 From: Alexey Sorokin Date: Wed, 18 Oct 2017 18:20:56 +0300 Subject: [PATCH 07/72] add option to force entry names encoding if you need to work with existing zip files which contain names with non-ascii characters then you can specify this option. Without this option find_entry will not work properly --- README.md | 8 ++++++++ lib/zip.rb | 10 +++++++++- lib/zip/entry.rb | 6 ++++++ test/unicode_file_names_and_comments_test.rb | 12 ++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8979d06f..64932c02 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,14 @@ Zip.default_compression = Zlib::DEFAULT_COMPRESSION ``` It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION` +Sometimes file names inside zip contain non-ASCII characters. If you can assume which encoding was used for such names and want to be able to find such entries using `find_entry` then you can force assumed encoding like so: + +```ruby +Zip.force_entry_names_encoding = 'UTF-8' +``` + +Allowed encoding names are the same as accepted by `String#force_encoding` + You can set multiple settings at the same time by using a block: ```ruby diff --git a/lib/zip.rb b/lib/zip.rb index bb44361a..9145207b 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -34,7 +34,15 @@ module Zip extend self - attr_accessor :unicode_names, :on_exists_proc, :continue_on_exists_proc, :sort_entries, :default_compression, :write_zip64_support, :warn_invalid_date, :case_insensitive_match + attr_accessor :unicode_names, + :on_exists_proc, + :continue_on_exists_proc, + :sort_entries, + :default_compression, + :write_zip64_support, + :warn_invalid_date, + :case_insensitive_match, + :force_entry_names_encoding def reset! @_ran_once = false diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 0aba0eb8..d5883642 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -240,6 +240,9 @@ def read_local_entry(io) #:nodoc:all extra = io.read(@extra_length) @name.gsub!('\\', '/') + if ::Zip.force_entry_names_encoding + @name.force_encoding(::Zip.force_entry_names_encoding) + end if extra && extra.bytesize != @extra_length raise ::Zip::Error, 'Truncated local zip entry header' @@ -364,6 +367,9 @@ def read_c_dir_entry(io) #:nodoc:all check_c_dir_entry_signature set_time(@last_mod_date, @last_mod_time) @name = io.read(@name_length) + if ::Zip.force_entry_names_encoding + @name.force_encoding(::Zip.force_entry_names_encoding) + end read_c_dir_extra_field(io) @comment = io.read(@comment_length) check_c_dir_entry_comment_size diff --git a/test/unicode_file_names_and_comments_test.rb b/test/unicode_file_names_and_comments_test.rb index b9b1967a..aac3e256 100644 --- a/test/unicode_file_names_and_comments_test.rb +++ b/test/unicode_file_names_and_comments_test.rb @@ -33,6 +33,18 @@ def test_unicode_file_name assert(filepath == entry_name) end end + + ::Zip.force_entry_names_encoding = 'UTF-8' + ::Zip::File.open(FILENAME) do |zip| + file_entrys.each do |filename| + refute_nil(zip.find_entry(filename)) + end + directory_entrys.each do |filepath| + refute_nil(zip.find_entry(filepath)) + end + end + ::Zip.force_entry_names_encoding = nil + ::File.unlink(FILENAME) end From a27204fef97277a2c4da6059264cc9ae1c5b52bc Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Tue, 28 Nov 2017 14:59:23 +0100 Subject: [PATCH 08/72] README: Use a blockquote to make text readable --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b0e4de3d..d5dbe76b 100644 --- a/README.md +++ b/README.md @@ -175,9 +175,8 @@ end But there is one exception when it is not working - General Purpose Flag Bit 3. -``` -If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data -``` +> If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data + If `::Zip::InputStream` finds such entry in the zip archive it will raise an exception. From 43f01f4631503912b08b6578a64bc406030d9bce Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Tue, 28 Nov 2017 15:12:39 +0100 Subject: [PATCH 09/72] Travis: use pre-installed Travis rubies - these are newer - rvm does gem update --system built-in --- .travis.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 12aac095..f7ef98fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,18 +4,18 @@ cache: bundler rvm: - 2.0.0 - 2.1.10 - - 2.2.6 - - 2.3.3 - - 2.4.0 + - 2.2.7 + - 2.3.4 + - 2.4.1 - ruby-head - rbx-2 matrix: include: - - rvm: jruby-9.1.4.0 + - rvm: jruby-9.1.14.0 jdk: oraclejdk7 - - rvm: jruby-9.1.4.0 + - rvm: jruby-9.1.14.0 jdk: oraclejdk8 - - rvm: jruby-9.1.4.0 + - rvm: jruby-9.1.14.0 jdk: openjdk7 - rvm: jruby-head jdk: oraclejdk8 @@ -24,7 +24,6 @@ matrix: - rvm: rbx-2 - rvm: jruby-head before_install: - - gem update --system - gem install bundler - gem --version before_script: From e19a5c15b653e3f9a859ee82d1265547bc0558be Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Tue, 28 Nov 2017 15:19:33 +0100 Subject: [PATCH 10/72] Travis: use JRUBY_OPTS="--debug" --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index f7ef98fc..f1d8fd28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,3 +29,6 @@ before_install: before_script: - echo `whereis zip` - echo `whereis unzip` +env: + global: + - JRUBY_OPTS="--debug" From e65bc45123773604267723068f97e334ec80fd0a Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Tue, 28 Nov 2017 15:20:51 +0100 Subject: [PATCH 11/72] Travis: drop oraclejdk-7 --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f1d8fd28..75f6ca8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,6 @@ rvm: - rbx-2 matrix: include: - - rvm: jruby-9.1.14.0 - jdk: oraclejdk7 - rvm: jruby-9.1.14.0 jdk: oraclejdk8 - rvm: jruby-9.1.14.0 From cbdea2a3311bdd8c776afa183c1b2d82ad77ddbb Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Tue, 28 Nov 2017 15:23:30 +0100 Subject: [PATCH 12/72] Travis: update RubyGems --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 75f6ca8d..cd0e445e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ matrix: - rvm: rbx-2 - rvm: jruby-head before_install: + - gem update --system - gem install bundler - gem --version before_script: From 849e7744430e9c36494196568d1d61017fac19bd Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Tue, 28 Nov 2017 15:53:14 +0100 Subject: [PATCH 13/72] Travis: Try using rbx-3 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cd0e445e..c40e9be0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ matrix: jdk: oraclejdk8 allow_failures: - rvm: ruby-head - - rvm: rbx-2 + - rvm: rbx-3 - rvm: jruby-head before_install: - gem update --system From 75cd0dca687ff8a96f2e252aa2956aad308b3992 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Tue, 28 Nov 2017 16:02:38 +0100 Subject: [PATCH 14/72] Travis: typo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c40e9be0..0d19859d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ rvm: - 2.3.4 - 2.4.1 - ruby-head - - rbx-2 + - rbx-3 matrix: include: - rvm: jruby-9.1.14.0 From 5ac27073b84b45d9012e259265dda605d0ae180a Mon Sep 17 00:00:00 2001 From: swamp09 Date: Tue, 5 Dec 2017 23:45:47 +0900 Subject: [PATCH 15/72] CI against Ruby 2.2.8, 2.3.5, and 2.4.2 --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 12aac095..671fab91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,9 @@ cache: bundler rvm: - 2.0.0 - 2.1.10 - - 2.2.6 - - 2.3.3 - - 2.4.0 + - 2.2.8 + - 2.3.5 + - 2.4.2 - ruby-head - rbx-2 matrix: From 50b01d0144cbbea46cdba8e28df9eea2885eb05e Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sat, 9 Dec 2017 15:41:33 +0100 Subject: [PATCH 16/72] Travis: Workaround a rbx-3 autoload issue - see https://github.com/bundler/bundler/issues/6163 --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 59c43b1d..a66ad6d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ rvm: - 2.3.5 - 2.4.2 - ruby-head - - rbx-3 matrix: include: - rvm: jruby-9.1.14.0 @@ -17,6 +16,9 @@ matrix: jdk: openjdk7 - rvm: jruby-head jdk: oraclejdk8 + - rvm: rbx-3 + env: + - RUBYOPT="-rbundler/deprecate bundle install" allow_failures: - rvm: ruby-head - rvm: rbx-3 From 6c2bddbb26f9cb38b05167f74f37b205bc3ed304 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sat, 9 Dec 2017 15:42:14 +0100 Subject: [PATCH 17/72] Travis: Typo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a66ad6d6..f811a2bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ matrix: jdk: oraclejdk8 - rvm: rbx-3 env: - - RUBYOPT="-rbundler/deprecate bundle install" + - RUBYOPT="-rbundler/deprecate" allow_failures: - rvm: ruby-head - rvm: rbx-3 From 888ca88beebf5e8262ad92f2508d6bf43edc8d34 Mon Sep 17 00:00:00 2001 From: Ryunosuke Sato Date: Thu, 18 Jan 2018 12:13:18 +0900 Subject: [PATCH 18/72] Bump Ruby versions on Travis CI --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f811a2bd..b9532a3a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,10 @@ cache: bundler rvm: - 2.0.0 - 2.1.10 - - 2.2.8 - - 2.3.5 - - 2.4.2 + - 2.2.9 + - 2.3.6 + - 2.4.3 + - 2.5.0 - ruby-head matrix: include: From 792266dbf3f85773d82976a441a59fcbf895ff5e Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 21 Feb 2018 11:48:56 -0800 Subject: [PATCH 19/72] Added fix for calling 'close' on a StringIO-backed zip file, and specs --- lib/zip/file.rb | 2 +- test/file_test.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 4bc3edc4..6952ba99 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -306,7 +306,7 @@ def extract(entry, dest_path, &block) # Commits changes that has been made since the previous commit to # the zip archive. def commit - return unless commit_required? + return if name.is_a?(StringIO) || !commit_required? on_success_replace do |tmp_file| ::Zip::OutputStream.open(tmp_file) do |zos| @entry_set.each do |e| diff --git a/test/file_test.rb b/test/file_test.rb index 73f60af5..32e21e33 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -104,6 +104,19 @@ def test_open_buffer_with_stringio end end + def test_close_buffer_with_stringio + string_io = StringIO.new File.read('test/data/rubycode.zip') + zf = ::Zip::File.open_buffer string_io + assert(zf.close || true) # Poor man's refute_raises + end + + def test_close_buffer_with_io + f = File.open('test/data/rubycode.zip') + zf = ::Zip::File.open_buffer f + assert zf.close + f.close + end + def test_open_buffer_without_block string_io = StringIO.new File.read('test/data/rubycode.zip') zf = ::Zip::File.open_buffer string_io From ffd0b671b845216fe40f2cd41ee9622801d68321 Mon Sep 17 00:00:00 2001 From: Phil Date: Fri, 23 Feb 2018 12:48:38 -0800 Subject: [PATCH 20/72] Fix regression caused by Rubocop cleanup InputStream: ``` if @current_entry && @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 \ && @current_entry.compressed_size == 0 \ && @current_entry.empty? && !@internal ``` `Zip::Entry#empty?` doesn't exist, but an automatic Rubocop check thought it should be converted from `size == 0`. Unfortunately, this is tricky to write a test for as it only fails under some very specific conditions. --- lib/zip/input_stream.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 4c861c56..5a82f145 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -129,7 +129,7 @@ def open_entry end if @current_entry && @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 \ && @current_entry.compressed_size == 0 \ - && @current_entry.empty? && !@internal + && @current_entry.size == 0 && !@internal raise GPFBit3Error, 'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \ 'Please use ::Zip::File instead of ::Zip::InputStream' From c787d94852b7d3e90212e8f7b08ad6ab6279c74d Mon Sep 17 00:00:00 2001 From: Andrew Meyer Date: Tue, 3 Apr 2018 16:07:18 -0400 Subject: [PATCH 21/72] Handle stored files with general purpose bit 3 set Signed-off-by: Sam Coward --- lib/zip/entry.rb | 2 +- lib/zip/input_stream.rb | 8 ++++++-- test/data/gpbit3stored.zip | Bin 0 -> 132 bytes test/file_test.rb | 6 ++++++ 4 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 test/data/gpbit3stored.zip diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 791ab32a..4d8e1751 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -500,7 +500,7 @@ def get_input_stream(&block) end else zis = ::Zip::InputStream.new(@zipfile, local_header_offset) - zis.instance_variable_set(:@internal, true) + zis.instance_variable_set(:@complete_entry, self) zis.get_next_entry if block_given? begin diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index 5a82f145..95fc3c16 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -129,7 +129,7 @@ def open_entry end if @current_entry && @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 \ && @current_entry.compressed_size == 0 \ - && @current_entry.size == 0 && !@internal + && @current_entry.size == 0 && !@complete_entry raise GPFBit3Error, 'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \ 'Please use ::Zip::File instead of ::Zip::InputStream' @@ -143,7 +143,11 @@ def get_decompressor if @current_entry.nil? ::Zip::NullDecompressor elsif @current_entry.compression_method == ::Zip::Entry::STORED - ::Zip::PassThruDecompressor.new(@archive_io, @current_entry.size) + if @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry + ::Zip::PassThruDecompressor.new(@archive_io, @complete_entry.size) + else + ::Zip::PassThruDecompressor.new(@archive_io, @current_entry.size) + end elsif @current_entry.compression_method == ::Zip::Entry::DEFLATED header = @archive_io.read(@decrypter.header_bytesize) @decrypter.reset!(header) diff --git a/test/data/gpbit3stored.zip b/test/data/gpbit3stored.zip new file mode 100644 index 0000000000000000000000000000000000000000..3c73eeb3bc4564638443ea75dcc6fcf46cc856bd GIT binary patch literal 132 zcmWIWW@Zs#;9y{22(4-M0a9?l4rHa}=j)YJlmIEN0B?4V6{$vbEI?rp4)A7V5@AMY fMV13;g@G-NAQsf10B=?{5SI}MO@Xu}h{FH?7~2w0 literal 0 HcmV?d00001 diff --git a/test/file_test.rb b/test/file_test.rb index 32e21e33..489c0798 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -55,6 +55,12 @@ def test_create_from_scratch_with_old_create_parameter assert_equal(2, zfRead.entries.length) end + def test_get_input_stream_stored_with_gpflag_bit3 + ::Zip::File.open('test/data/gpbit3stored.zip') do |zf| + assert_equal("foo\n", zf.read("foo.txt")) + end + end + def test_get_output_stream entryCount = nil ::Zip::File.open(TEST_ZIP.zip_name) do |zf| From 7cd263e6a0d53f793ba41b249f4a78f84c4dc016 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 3 Apr 2018 23:45:04 +0100 Subject: [PATCH 22/72] Clean up use of file_name in Zip::File::initialize. --- lib/zip/file.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 6952ba99..6ff3463d 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -69,18 +69,18 @@ def initialize(file_name, create = false, buffer = false, options = {}) @name = file_name @comment = '' @create = create ? true : false # allow any truthy value to mean true - if !buffer && ::File.size?(file_name) + if !buffer && ::File.size?(@name) @create = false - @file_permissions = ::File.stat(file_name).mode - ::File.open(name, 'rb') do |f| + @file_permissions = ::File.stat(@name).mode + ::File.open(@name, 'rb') do |f| read_from_stream(f) end elsif @create @entry_set = EntrySet.new - elsif ::File.zero?(file_name) - raise Error, "File #{file_name} has zero size. Did you mean to pass the create flag?" + elsif ::File.zero?(@name) + raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?" else - raise Error, "File #{file_name} not found" + raise Error, "File #{@name} not found" end @stored_entries = @entry_set.dup @stored_comment = @comment From cfa9441914d56bb866dd70c29fd5ec33bd2a8fc6 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Tue, 3 Apr 2018 23:48:54 +0100 Subject: [PATCH 23/72] Handle passing an IO to Zip::File.new better. This now actually extracts the path from the IO if one is passed in. --- lib/zip/file.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 6ff3463d..8cb5f03a 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -64,9 +64,9 @@ class File < CentralDirectory # Opens a zip archive. Pass true as the second parameter to create # a new archive if it doesn't exist already. - def initialize(file_name, create = false, buffer = false, options = {}) + def initialize(path_or_io, create = false, buffer = false, options = {}) super() - @name = file_name + @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io @comment = '' @create = create ? true : false # allow any truthy value to mean true if !buffer && ::File.size?(@name) From 03633933ebb714cdff1e3f5604434f35bad0e0cf Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 4 Apr 2018 14:31:34 +0100 Subject: [PATCH 24/72] No need to require stringio in Zip::File.open_buffer. It's already required in zip.rb. --- lib/zip/file.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index 8cb5f03a..dc86bb72 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -120,7 +120,6 @@ def open_buffer(io, options = {}) raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}" end if io.is_a?(::String) - require 'stringio' io = ::StringIO.new(io) elsif io.respond_to?(:binmode) # https://github.com/rubyzip/rubyzip/issues/119 From 15ccc25da1755200f6d2cb29fde49c303a236073 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 4 Apr 2018 15:12:22 +0100 Subject: [PATCH 25/72] Fix File.open_buffer when no changes are made. Things are now more carefully set up, and if a buffer is passed in which represents a file that already exists then this is taken into account. All initialization is now done in File.new, rather than being split between there and File.open_buffer. This has also needed a bit of a re-write of Zip::File.initialize. I've tried to bring some logic to it as a result, and have added comments to explain what is now happening. --- lib/zip/file.rb | 24 ++++++++++++++++++++---- test/file_test.rb | 5 +++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index dc86bb72..c0a44207 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -69,19 +69,33 @@ def initialize(path_or_io, create = false, buffer = false, options = {}) @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io @comment = '' @create = create ? true : false # allow any truthy value to mean true - if !buffer && ::File.size?(@name) + + if ::File.size?(@name.to_s) + # There is a file, which exists, that is associated with this zip. @create = false @file_permissions = ::File.stat(@name).mode - ::File.open(@name, 'rb') do |f| - read_from_stream(f) + + if buffer + read_from_stream(path_or_io) + else + ::File.open(@name, 'rb') do |f| + read_from_stream(f) + end end + elsif buffer && path_or_io.size > 0 + # This zip is probably a non-empty StringIO. + read_from_stream(path_or_io) elsif @create + # This zip is completely new/empty and is to be created. @entry_set = EntrySet.new elsif ::File.zero?(@name) + # A file exists, but it is empty. raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?" else + # Everything is wrong. raise Error, "File #{@name} not found" end + @stored_entries = @entry_set.dup @stored_comment = @comment @restore_ownership = options[:restore_ownership] || false @@ -119,16 +133,18 @@ def open_buffer(io, options = {}) unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.is_a?(String) raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}" end + if io.is_a?(::String) io = ::StringIO.new(io) elsif io.respond_to?(:binmode) # https://github.com/rubyzip/rubyzip/issues/119 io.binmode end + zf = ::Zip::File.new(io, true, true, options) - zf.read_from_stream(io) return zf unless block_given? yield zf + begin zf.write_buffer(io) rescue IOError => e diff --git a/test/file_test.rb b/test/file_test.rb index 32e21e33..53124bea 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -107,13 +107,14 @@ def test_open_buffer_with_stringio def test_close_buffer_with_stringio string_io = StringIO.new File.read('test/data/rubycode.zip') zf = ::Zip::File.open_buffer string_io - assert(zf.close || true) # Poor man's refute_raises + assert_nil zf.close end def test_close_buffer_with_io f = File.open('test/data/rubycode.zip') zf = ::Zip::File.open_buffer f - assert zf.close + refute zf.commit_required? + assert_nil zf.close f.close end From 84c208982f717f10f7133cdfc1f016607398858d Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 4 Apr 2018 15:40:38 +0100 Subject: [PATCH 26/72] Switch newly created StringIOs to binmode. StringIO objects created within File.open_buffer were not being switched into binmode, but those passed in were. Fix this inconsistency and add a test. --- lib/zip/file.rb | 10 ++++------ test/file_test.rb | 7 +++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index c0a44207..b5b85eea 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -134,12 +134,10 @@ def open_buffer(io, options = {}) raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}" end - if io.is_a?(::String) - io = ::StringIO.new(io) - elsif io.respond_to?(:binmode) - # https://github.com/rubyzip/rubyzip/issues/119 - io.binmode - end + io = ::StringIO.new(io) if io.is_a?(::String) + + # https://github.com/rubyzip/rubyzip/issues/119 + io.binmode if io.respond_to?(:binmode) zf = ::Zip::File.new(io, true, true, options) return zf unless block_given? diff --git a/test/file_test.rb b/test/file_test.rb index 53124bea..8bbf7cf8 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -97,6 +97,13 @@ def test_get_output_stream end end + def test_open_buffer_with_string + string = File.read('test/data/rubycode.zip') + ::Zip::File.open_buffer string do |zf| + assert zf.entries.map { |e| e.name }.include?('zippedruby1.rb') + end + end + def test_open_buffer_with_stringio string_io = StringIO.new File.read('test/data/rubycode.zip') ::Zip::File.open_buffer string_io do |zf| From cd7bb142a4d882366787aa7dd13276312a55b6c6 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Wed, 4 Apr 2018 19:45:54 +0100 Subject: [PATCH 27/72] Turn off all terminal output in all tests. Makes things a lot easier when trying to track down bugs. --- samples/example_recursive.rb | 1 - test/file_test.rb | 4 ++-- test/gentestfiles.rb | 14 +++++++------- test/zip64_full_test.rb | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/samples/example_recursive.rb b/samples/example_recursive.rb index 207934b2..56a5cc7c 100644 --- a/samples/example_recursive.rb +++ b/samples/example_recursive.rb @@ -33,7 +33,6 @@ def write_entries(entries, path, zipfile) entries.each do |e| zipfile_path = path == '' ? e : File.join(path, e) disk_file_path = File.join(@input_dir, zipfile_path) - puts "Deflating #{disk_file_path}" if File.directory? disk_file_path recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) diff --git a/test/file_test.rb b/test/file_test.rb index 32e21e33..c4dc6d10 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -360,7 +360,7 @@ def test_commit zfRead.close zf.close - res = system("unzip -t #{TEST_ZIP.zip_name}") + res = system("unzip -tqq #{TEST_ZIP.zip_name}") assert_equal(res, true) end @@ -376,7 +376,7 @@ def test_double_commit(filename = 'test/data/generated/double_commit_test.zip') zf2 = ::Zip::File.open(filename) assert(zf2.entries.detect { |e| e.name == 'test1.txt' } != nil) assert(zf2.entries.detect { |e| e.name == 'test2.txt' } != nil) - res = system("unzip -t #{filename}") + res = system("unzip -tqq #{filename}") assert_equal(res, true) end diff --git a/test/gentestfiles.rb b/test/gentestfiles.rb index a8a2126b..3e76e7d0 100755 --- a/test/gentestfiles.rb +++ b/test/gentestfiles.rb @@ -71,8 +71,8 @@ def initialize(zip_name, entry_names, comment = '') end def self.create_test_zips - raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" unless system("/usr/bin/zip #{TEST_ZIP1.zip_name} test/data/file2.txt") - raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" unless system("/usr/bin/zip #{TEST_ZIP1.zip_name} -d test/data/file2.txt") + raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" unless system("/usr/bin/zip -q #{TEST_ZIP1.zip_name} test/data/file2.txt") + raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" unless system("/usr/bin/zip -q #{TEST_ZIP1.zip_name} -d test/data/file2.txt") File.open('test/data/generated/empty.txt', 'w') {} File.open('test/data/generated/empty_chmod640.txt', 'w') {} @@ -93,19 +93,19 @@ def self.create_test_zips file << testBinaryPattern << rand << "\0" while file.tell < 6E5 end - raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" unless system("/usr/bin/zip #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.join(' ')}") + raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" unless system("/usr/bin/zip -q #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.join(' ')}") if RUBY_PLATFORM =~ /mswin|mingw|cygwin/ - raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" unless system("echo #{TEST_ZIP2.comment}| /usr/bin/zip -z #{TEST_ZIP2.zip_name}\"") + raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" unless system("echo #{TEST_ZIP2.comment}| /usr/bin/zip -zq #{TEST_ZIP2.zip_name}\"") else # without bash system interprets everything after echo as parameters to # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" unless system("bash -c \"echo #{TEST_ZIP2.comment} | /usr/bin/zip -z #{TEST_ZIP2.zip_name}\"") + raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" unless system("bash -c \"echo #{TEST_ZIP2.comment} | /usr/bin/zip -zq #{TEST_ZIP2.zip_name}\"") end - raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" unless system("/usr/bin/zip #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}") + raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" unless system("/usr/bin/zip -q #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}") - raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" unless system("/usr/bin/zip #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}") + raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" unless system("/usr/bin/zip -q #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}") rescue # If there are any Windows developers wanting to use a command line zip.exe # to help create the following files, there's a free one available from diff --git a/test/zip64_full_test.rb b/test/zip64_full_test.rb index 464388dc..ed11ed65 100644 --- a/test/zip64_full_test.rb +++ b/test/zip64_full_test.rb @@ -44,7 +44,7 @@ def test_large_zip_file # note: if this fails, be sure you have UnZip version 6.0 or newer # as this is the first version to support zip64 extensions # but some OSes (*cough* OSX) still bundle a 5.xx release - assert system("unzip -t #{test_filename}"), 'third-party zip validation failed' + assert system("unzip -tqq #{test_filename}"), 'third-party zip validation failed' end end From 1673da744d5618eceabbc98b9990ad85b457c766 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 30 Apr 2018 19:43:23 +0100 Subject: [PATCH 28/72] Pass glob through from ZipFileNameMapper. Just pass the basic glob straight through to the underlying Zip::File implementation. --- lib/zip/filesystem.rb | 4 ++++ test/filesystem/directory_test.rb | 23 ++++++++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index fb64325b..692017ef 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -573,6 +573,10 @@ def get_output_stream(fileName, permissionInt = nil, &aProc) @zipFile.get_output_stream(expand_to_entry(fileName), permissionInt, &aProc) end + def glob(*args, &block) + @zipFile.glob(*args, &block) + end + def read(fileName) @zipFile.read(expand_to_entry(fileName)) end diff --git a/test/filesystem/directory_test.rb b/test/filesystem/directory_test.rb index ec0a7371..f20a6dc1 100644 --- a/test/filesystem/directory_test.rb +++ b/test/filesystem/directory_test.rb @@ -3,6 +3,7 @@ class ZipFsDirectoryTest < MiniTest::Test TEST_ZIP = 'test/data/generated/zipWithDirs_copy.zip' + GLOB_TEST_ZIP = 'test/data/globTest.zip' def setup FileUtils.cp('test/data/zipWithDirs.zip', TEST_ZIP) @@ -93,11 +94,23 @@ def test_chroot end end - # Globbing not supported yet - # def test_glob - # # test alias []-operator too - # fail "implement test" - # end + def test_glob + globbed_files = [ + 'globTest/foo/bar/baz/foo.txt', + 'globTest/foo.txt', + 'globTest/food.txt' + ] + + ::Zip::File.open(GLOB_TEST_ZIP) do |zf| + zf.dir.glob('**/*.txt') do |f| + assert globbed_files.include?(f.name) + end + + zf.dir.glob('globTest/foo/**/*.txt') do |f| + assert_equal globbed_files[0], f.name + end + end + end def test_open_new ::Zip::File.open(TEST_ZIP) do |zf| From aa6284db7ac4d3d2f708fb262304420e81c8abd3 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Mon, 30 Apr 2018 20:04:49 +0100 Subject: [PATCH 29/72] When globbing in ZipFSDir, take CWD into account. --- lib/zip/filesystem.rb | 4 ++-- test/filesystem/directory_test.rb | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/zip/filesystem.rb b/lib/zip/filesystem.rb index 692017ef..81ad1a18 100644 --- a/lib/zip/filesystem.rb +++ b/lib/zip/filesystem.rb @@ -573,8 +573,8 @@ def get_output_stream(fileName, permissionInt = nil, &aProc) @zipFile.get_output_stream(expand_to_entry(fileName), permissionInt, &aProc) end - def glob(*args, &block) - @zipFile.glob(*args, &block) + def glob(pattern, *flags, &block) + @zipFile.glob(expand_to_entry(pattern), *flags, &block) end def read(fileName) diff --git a/test/filesystem/directory_test.rb b/test/filesystem/directory_test.rb index f20a6dc1..f36ede53 100644 --- a/test/filesystem/directory_test.rb +++ b/test/filesystem/directory_test.rb @@ -109,6 +109,11 @@ def test_glob zf.dir.glob('globTest/foo/**/*.txt') do |f| assert_equal globbed_files[0], f.name end + + zf.dir.chdir('globTest/foo') + zf.dir.glob('**/*.txt') do |f| + assert_equal globbed_files[0], f.name + end end end From e89f6aca440b36f90a961a8c5274c12fcacd9a19 Mon Sep 17 00:00:00 2001 From: Oleksandr Simonov Date: Wed, 22 Aug 2018 12:53:52 +0300 Subject: [PATCH 30/72] Fix jruby version --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b9532a3a..ad59d61e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,9 @@ rvm: - ruby-head matrix: include: - - rvm: jruby-9.1.14.0 + - rvm: jruby jdk: oraclejdk8 - - rvm: jruby-9.1.14.0 + - rvm: jruby jdk: openjdk7 - rvm: jruby-head jdk: oraclejdk8 From 6e0d23178a39f1b9ee0debc4fffb6d90994c6955 Mon Sep 17 00:00:00 2001 From: Bart de Water Date: Sun, 1 Jul 2018 14:57:50 -0400 Subject: [PATCH 31/72] Fix CVE-2018-1000544 absolute path traversal Small refactor along the way to centralize destination handling when no explicit path is given and a potential malicious one from the zipfile is used --- lib/zip/entry.rb | 12 ++++++++---- test/data/absolutepath.zip | Bin 0 -> 289 bytes test/entry_test.rb | 26 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 test/data/absolutepath.zip diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 4d8e1751..37222a52 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -147,14 +147,18 @@ def next_header_offset #:nodoc:all end # Extracts entry to file dest_path (defaults to @name). - def extract(dest_path = @name, &block) - block ||= proc { ::Zip.on_exists_proc } - - if @name.squeeze('/') =~ /\.{2}(?:\/|\z)/ + def extract(dest_path = nil, &block) + if dest_path.nil? && Pathname.new(@name).absolute? + puts "WARNING: skipped absolute path in #{@name}" + return self + elsif @name.squeeze('/') =~ /\.{2}(?:\/|\z)/ puts "WARNING: skipped \"../\" path component(s) in #{@name}" return self end + dest_path ||= @name + block ||= proc { ::Zip.on_exists_proc } + if directory? || file? || symlink? __send__("create_#{@ftype}", dest_path, &block) else diff --git a/test/data/absolutepath.zip b/test/data/absolutepath.zip new file mode 100644 index 0000000000000000000000000000000000000000..59fceed7d1e59f2323afa67c0a0b175c5dfd2489 GIT binary patch literal 289 zcmWIWW@h1H0D*No&-#EFP=b{~fI+_`w?IEMf`>t}O)>g;m16W)MiGVppcWAZ4j|l$ zriB-#B`q^2Rj;I?1f;d28K@1aRoio|#zQ8c3G5u|x89hu1Fd2O;s9?(CQ)Ww&ftN$ z%HgdehzWHR2i#E*?Fc6!8_mmr(7vS67^WTJKn}PA1H4(;K;|<6VH=Qc1#uVv>aI9M literal 0 HcmV?d00001 diff --git a/test/entry_test.rb b/test/entry_test.rb index b49783d3..a75052e3 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -151,4 +151,30 @@ def test_store_file_without_compression assert_match(/mimetypeapplication\/epub\+zip/, first_100_bytes) end + + def test_entry_name_with_absolute_path_does_not_extract + path = '/tmp/file.txt' + File.delete(path) if File.exist?(path) + + Zip::File.open('test/data/absolutepath.zip') do |zip_file| + zip_file.each do |entry| + entry.extract + end + end + + refute File.exist?(path) + end + + def test_entry_name_with_absolute_path_extract_when_given_different_path + path = '/tmp/CVE-2018-1000544' + FileUtils.rm_rf(path) if Dir.exist?(path) + + Zip::File.open('test/data/absolutepath.zip') do |zip_file| + zip_file.each do |entry| + entry.extract("#{path}/#{entry.name}") + end + end + + assert File.exist?("#{path}/tmp/file.txt") + end end From 8e78311d670ba70476fb46062c988849a82d1e02 Mon Sep 17 00:00:00 2001 From: Bart de Water Date: Sun, 1 Jul 2018 16:45:06 -0400 Subject: [PATCH 32/72] Fix CVE-2018-1000544 symlink path traversal Not sure if the exception is the right way to go --- lib/zip/entry.rb | 3 +++ test/data/symlink.zip | Bin 0 -> 330 bytes test/entry_test.rb | 10 ++++++++++ 3 files changed, 13 insertions(+) create mode 100644 test/data/symlink.zip diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 37222a52..28d60091 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -154,6 +154,9 @@ def extract(dest_path = nil, &block) elsif @name.squeeze('/') =~ /\.{2}(?:\/|\z)/ puts "WARNING: skipped \"../\" path component(s) in #{@name}" return self + elsif symlink? && get_input_stream.read =~ %r{../..} + puts "WARNING: skipped \"#{get_input_stream.read}\" symlink path in #{@name}" + return self end dest_path ||= @name diff --git a/test/data/symlink.zip b/test/data/symlink.zip new file mode 100644 index 0000000000000000000000000000000000000000..e74ee19ab39d8e1e97d84522ff3209bda4ca1ad4 GIT binary patch literal 330 zcmWIWW@h1H0D$_BEQ2?*x`>FFR20|3>Y BIivsp literal 0 HcmV?d00001 diff --git a/test/entry_test.rb b/test/entry_test.rb index a75052e3..eaa9c0d9 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -177,4 +177,14 @@ def test_entry_name_with_absolute_path_extract_when_given_different_path assert File.exist?("#{path}/tmp/file.txt") end + + def test_entry_name_with_relative_symlink + assert_raises Errno::ENOENT do + Zip::File.open('test/data/symlink.zip') do |zip_file| + zip_file.each do |entry| + entry.extract + end + end + end + end end From eda8862c59aeb9f55b6e14b614ebe77ce0217332 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Sun, 26 Aug 2018 11:36:08 +0900 Subject: [PATCH 33/72] Move jruby to allow failures matrix till crc uint 32 issues are resolved --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ad59d61e..b197c86b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ matrix: - rvm: ruby-head - rvm: rbx-3 - rvm: jruby-head + - rvm: jruby before_install: - gem update --system - gem install bundler From cf7158344c65a67dc5f18bf589a6b742e3452f45 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Sun, 26 Aug 2018 11:36:08 +0900 Subject: [PATCH 34/72] Move jruby to allow failures matrix till crc uint 32 issues are resolved --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ad59d61e..b197c86b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ matrix: - rvm: ruby-head - rvm: rbx-3 - rvm: jruby-head + - rvm: jruby before_install: - gem update --system - gem install bundler From 0586329d3be19728c20941faa401cb838f461dc3 Mon Sep 17 00:00:00 2001 From: Bart de Water Date: Sun, 26 Aug 2018 00:52:10 -0400 Subject: [PATCH 35/72] Trigger CI again From 9c468f30f38d09451e5a65edfff277cfe381fd49 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 26 Aug 2018 10:00:35 +0100 Subject: [PATCH 36/72] Add jwilk's path traversal tests --- .../jwilk-path-traversal-samples/README.md | 5 + .../absolute1.zip | Bin 0 -> 118 bytes .../absolute2.zip | Bin 0 -> 120 bytes .../dirsymlink.zip | Bin 0 -> 202 bytes .../dirsymlink2a.zip | Bin 0 -> 287 bytes .../dirsymlink2b.zip | Bin 0 -> 291 bytes .../relative0.zip | Bin 0 -> 114 bytes .../relative2.zip | Bin 0 -> 128 bytes .../jwilk-path-traversal-samples/symlink.zip | Bin 0 -> 198 bytes test/path_traversal_test.rb | 88 ++++++++++++++++++ 10 files changed, 93 insertions(+) create mode 100644 test/data/jwilk-path-traversal-samples/README.md create mode 100644 test/data/jwilk-path-traversal-samples/absolute1.zip create mode 100644 test/data/jwilk-path-traversal-samples/absolute2.zip create mode 100644 test/data/jwilk-path-traversal-samples/dirsymlink.zip create mode 100644 test/data/jwilk-path-traversal-samples/dirsymlink2a.zip create mode 100644 test/data/jwilk-path-traversal-samples/dirsymlink2b.zip create mode 100644 test/data/jwilk-path-traversal-samples/relative0.zip create mode 100644 test/data/jwilk-path-traversal-samples/relative2.zip create mode 100644 test/data/jwilk-path-traversal-samples/symlink.zip create mode 100644 test/path_traversal_test.rb diff --git a/test/data/jwilk-path-traversal-samples/README.md b/test/data/jwilk-path-traversal-samples/README.md new file mode 100644 index 00000000..2ecceb23 --- /dev/null +++ b/test/data/jwilk-path-traversal-samples/README.md @@ -0,0 +1,5 @@ +# Path Traversal Samples + +Copied from https://github.com/jwilk/path-traversal-samples on 2018-08-26. + +License: MIT diff --git a/test/data/jwilk-path-traversal-samples/absolute1.zip b/test/data/jwilk-path-traversal-samples/absolute1.zip new file mode 100644 index 0000000000000000000000000000000000000000..27c615d98bae9a331eedd1425f5830c40812fd2c GIT binary patch literal 118 zcmWIWW@h1H0D<3gj{B^9WW16E$Od5!Al5I*Ezr-+&j%u|0B=SnIcD5yfyx;efp|$H Why~Lb;LXYg;xhuF8IaZjaTox`+Z8VW literal 0 HcmV?d00001 diff --git a/test/data/jwilk-path-traversal-samples/absolute2.zip b/test/data/jwilk-path-traversal-samples/absolute2.zip new file mode 100644 index 0000000000000000000000000000000000000000..c82c14eaa68624f85dd0f8ed63da5cef56c0bb24 GIT binary patch literal 120 zcmWIWW@h1H0D<3gj{B^9WW16E$Od6fAlBC}$t}>&&CdrSt^jXFCOKx@ih=4G7=d_6 YBZvjp8sN>!1`=QdLUSOk4dO5W0Q9UCkpKVy literal 0 HcmV?d00001 diff --git a/test/data/jwilk-path-traversal-samples/dirsymlink.zip b/test/data/jwilk-path-traversal-samples/dirsymlink.zip new file mode 100644 index 0000000000000000000000000000000000000000..978b5d8a061316af9e1d7de091032281dee5e515 GIT binary patch literal 202 zcmWIWW@h1H0D<3gj{E2x+0DfQWP>m>5SQc@=mT*8ilUW|j90=Gu|pN*=H~+uSAaJo zlN>W{^MI3K_6&MfHx}}NP-Cn(}1)eh{FH?U=Sr) literal 0 HcmV?d00001 diff --git a/test/data/jwilk-path-traversal-samples/dirsymlink2a.zip b/test/data/jwilk-path-traversal-samples/dirsymlink2a.zip new file mode 100644 index 0000000000000000000000000000000000000000..443deede33be9e5eaf90b496296becca2e181d52 GIT binary patch literal 287 zcmWIWW@h1H0D<3gj{7)0y28f@WP>m>5GR)w=>?#uICw8jn++%f!XOm|iA6v~`g&-} zRz5Ob$pVxHVRncz{oMR~AmR$}W@M6M#%(81H5mL~2%><7qiaBS0z^3j!~ca!Kqk;Z k=o-)+1JVHU;gUvAhz5|;0=!v4egb-mnPEDRE&_2F0PO@eP5=M^ literal 0 HcmV?d00001 diff --git a/test/data/jwilk-path-traversal-samples/dirsymlink2b.zip b/test/data/jwilk-path-traversal-samples/dirsymlink2b.zip new file mode 100644 index 0000000000000000000000000000000000000000..5a5a12b4b6bb08eb331850e1a2547fb78434d1a2 GIT binary patch literal 291 zcmWIWW@h1H0D<3gj{7)0y28f@WP>m>5GR)w=>?#ukP+jU#sriBVRncL{er|IJv3!2 z9~rM?0ZM}~NEuLxer|p~5OD=~Gcw6BJ85n_hNh62_ V(HG#&3Ni#J&d6W_q%}Ys1^^b>65jv- literal 0 HcmV?d00001 diff --git a/test/data/jwilk-path-traversal-samples/relative2.zip b/test/data/jwilk-path-traversal-samples/relative2.zip new file mode 100644 index 0000000000000000000000000000000000000000..8957028d9597f9658eae2df18f0e138ead49714f GIT binary patch literal 128 zcmWIWW@h1H0D<3gj{B^9WW16E$Od6vATG%*(AU!gq1^m@AmR$}W@M6M#;pOUhk+4@ Zmo$P{DB1(OS=m5>j6i4&r1e1@1^~L>7ZCse literal 0 HcmV?d00001 diff --git a/test/data/jwilk-path-traversal-samples/symlink.zip b/test/data/jwilk-path-traversal-samples/symlink.zip new file mode 100644 index 0000000000000000000000000000000000000000..edaa7526aeb60c51a2f8140ee9b34767964a7cc4 GIT binary patch literal 198 zcmWIWW@h1H0D<3gj{8J;-<{3@WP>m>5a;IS>zCvf=mY5h6oo4v8LwmkDga@qLZBM1 x0B=SnIcD7E0ab&+|Ain5Y$~b-baO$<7=d_6qb5uPD;r3V2?&#bv>%AW001u9B$EID literal 0 HcmV?d00001 diff --git a/test/path_traversal_test.rb b/test/path_traversal_test.rb new file mode 100644 index 00000000..ab8269b7 --- /dev/null +++ b/test/path_traversal_test.rb @@ -0,0 +1,88 @@ +class PathTraversalTest < MiniTest::Test + TEST_FILE_ROOT = File.absolute_path('test/data/jwilk-path-traversal-samples') + + def setup + FileUtils.rm_f '/tmp/moo' # with apologies to anyone using this file + end + + def extract_path_traversal_zip(name) + Zip::File.open(File.join(TEST_FILE_ROOT, name)) do |zip_file| + zip_file.each do |entry| + entry.extract + end + end + end + + def in_tmpdir + Dir.mktmpdir do |tmp| + test_path = File.join(tmp, 'test') + Dir.mkdir test_path + Dir.chdir(test_path) do + yield + end + end + end + + def test_leading_slash + in_tmpdir do + extract_path_traversal_zip 'absolute1.zip' + assert !File.exist?('/tmp/moo') + end + end + + def test_multiple_leading_slashes + in_tmpdir do + extract_path_traversal_zip 'absolute2.zip' + assert !File.exist?('/tmp/moo') + end + end + + def test_leading_dot_dot + in_tmpdir do + extract_path_traversal_zip 'relative0.zip' + assert !File.exist?('../moo') + end + end + + def test_non_leading_dot_dot + in_tmpdir do + extract_path_traversal_zip 'relative2.zip' + assert !File.exist?('../moo') + end + end + + def test_file_symlink + in_tmpdir do + extract_path_traversal_zip 'symlink.zip' + assert File.exist?('moo') + assert !File.exist?('/tmp/moo') + end + end + + def test_directory_symlink + in_tmpdir do + extract_path_traversal_zip 'dirsymlink.zip' + assert !File.exist?('/tmp/moo') + end + end + + def test_two_directory_symlinks_a + in_tmpdir do + # Can't create par/moo because the symlink par is skipped. + assert_raises Errno::ENOENT do + extract_path_traversal_zip 'dirsymlink2a.zip' + end + assert File.exist?('cur') + assert_equal '.', File.readlink('cur') + end + end + + def test_two_directory_symlinks_b + in_tmpdir do + extract_path_traversal_zip 'dirsymlink2b.zip' + assert File.exist?('cur') + assert_equal '.', File.readlink('cur') + assert !File.exist?('../moo') + end + end +end From ffebfa34189a46a766bf6630796c93d81b5ef7ed Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 26 Aug 2018 12:13:12 +0100 Subject: [PATCH 37/72] Consolidate path traversal tests --- .../jwilk}/README.md | 0 .../jwilk}/absolute1.zip | Bin .../jwilk}/absolute2.zip | Bin .../jwilk}/dirsymlink.zip | Bin .../jwilk}/dirsymlink2a.zip | Bin .../jwilk}/dirsymlink2b.zip | Bin .../jwilk}/relative0.zip | Bin .../jwilk}/relative2.zip | Bin .../jwilk}/symlink.zip | Bin .../data/path_traversal/tuzovakaoff/README.md | 3 + .../tuzovakaoff}/absolutepath.zip | Bin .../tuzovakaoff}/symlink.zip | Bin test/entry_test.rb | 36 -------- test/path_traversal_test.rb | 77 +++++++++++++----- 14 files changed, 58 insertions(+), 58 deletions(-) rename test/data/{jwilk-path-traversal-samples => path_traversal/jwilk}/README.md (100%) rename test/data/{jwilk-path-traversal-samples => path_traversal/jwilk}/absolute1.zip (100%) rename test/data/{jwilk-path-traversal-samples => path_traversal/jwilk}/absolute2.zip (100%) rename test/data/{jwilk-path-traversal-samples => path_traversal/jwilk}/dirsymlink.zip (100%) rename test/data/{jwilk-path-traversal-samples => path_traversal/jwilk}/dirsymlink2a.zip (100%) rename test/data/{jwilk-path-traversal-samples => path_traversal/jwilk}/dirsymlink2b.zip (100%) rename test/data/{jwilk-path-traversal-samples => path_traversal/jwilk}/relative0.zip (100%) rename test/data/{jwilk-path-traversal-samples => path_traversal/jwilk}/relative2.zip (100%) rename test/data/{jwilk-path-traversal-samples => path_traversal/jwilk}/symlink.zip (100%) create mode 100644 test/data/path_traversal/tuzovakaoff/README.md rename test/data/{ => path_traversal/tuzovakaoff}/absolutepath.zip (100%) rename test/data/{ => path_traversal/tuzovakaoff}/symlink.zip (100%) diff --git a/test/data/jwilk-path-traversal-samples/README.md b/test/data/path_traversal/jwilk/README.md similarity index 100% rename from test/data/jwilk-path-traversal-samples/README.md rename to test/data/path_traversal/jwilk/README.md diff --git a/test/data/jwilk-path-traversal-samples/absolute1.zip b/test/data/path_traversal/jwilk/absolute1.zip similarity index 100% rename from test/data/jwilk-path-traversal-samples/absolute1.zip rename to test/data/path_traversal/jwilk/absolute1.zip diff --git a/test/data/jwilk-path-traversal-samples/absolute2.zip b/test/data/path_traversal/jwilk/absolute2.zip similarity index 100% rename from test/data/jwilk-path-traversal-samples/absolute2.zip rename to test/data/path_traversal/jwilk/absolute2.zip diff --git a/test/data/jwilk-path-traversal-samples/dirsymlink.zip b/test/data/path_traversal/jwilk/dirsymlink.zip similarity index 100% rename from test/data/jwilk-path-traversal-samples/dirsymlink.zip rename to test/data/path_traversal/jwilk/dirsymlink.zip diff --git a/test/data/jwilk-path-traversal-samples/dirsymlink2a.zip b/test/data/path_traversal/jwilk/dirsymlink2a.zip similarity index 100% rename from test/data/jwilk-path-traversal-samples/dirsymlink2a.zip rename to test/data/path_traversal/jwilk/dirsymlink2a.zip diff --git a/test/data/jwilk-path-traversal-samples/dirsymlink2b.zip b/test/data/path_traversal/jwilk/dirsymlink2b.zip similarity index 100% rename from test/data/jwilk-path-traversal-samples/dirsymlink2b.zip rename to test/data/path_traversal/jwilk/dirsymlink2b.zip diff --git a/test/data/jwilk-path-traversal-samples/relative0.zip b/test/data/path_traversal/jwilk/relative0.zip similarity index 100% rename from test/data/jwilk-path-traversal-samples/relative0.zip rename to test/data/path_traversal/jwilk/relative0.zip diff --git a/test/data/jwilk-path-traversal-samples/relative2.zip b/test/data/path_traversal/jwilk/relative2.zip similarity index 100% rename from test/data/jwilk-path-traversal-samples/relative2.zip rename to test/data/path_traversal/jwilk/relative2.zip diff --git a/test/data/jwilk-path-traversal-samples/symlink.zip b/test/data/path_traversal/jwilk/symlink.zip similarity index 100% rename from test/data/jwilk-path-traversal-samples/symlink.zip rename to test/data/path_traversal/jwilk/symlink.zip diff --git a/test/data/path_traversal/tuzovakaoff/README.md b/test/data/path_traversal/tuzovakaoff/README.md new file mode 100644 index 00000000..4bfd8cb6 --- /dev/null +++ b/test/data/path_traversal/tuzovakaoff/README.md @@ -0,0 +1,3 @@ +# Path Traversal Samples + +Copied from https://github.com/jwilk/path-traversal-samples on 2018-08-25. diff --git a/test/data/absolutepath.zip b/test/data/path_traversal/tuzovakaoff/absolutepath.zip similarity index 100% rename from test/data/absolutepath.zip rename to test/data/path_traversal/tuzovakaoff/absolutepath.zip diff --git a/test/data/symlink.zip b/test/data/path_traversal/tuzovakaoff/symlink.zip similarity index 100% rename from test/data/symlink.zip rename to test/data/path_traversal/tuzovakaoff/symlink.zip diff --git a/test/entry_test.rb b/test/entry_test.rb index eaa9c0d9..b49783d3 100644 --- a/test/entry_test.rb +++ b/test/entry_test.rb @@ -151,40 +151,4 @@ def test_store_file_without_compression assert_match(/mimetypeapplication\/epub\+zip/, first_100_bytes) end - - def test_entry_name_with_absolute_path_does_not_extract - path = '/tmp/file.txt' - File.delete(path) if File.exist?(path) - - Zip::File.open('test/data/absolutepath.zip') do |zip_file| - zip_file.each do |entry| - entry.extract - end - end - - refute File.exist?(path) - end - - def test_entry_name_with_absolute_path_extract_when_given_different_path - path = '/tmp/CVE-2018-1000544' - FileUtils.rm_rf(path) if Dir.exist?(path) - - Zip::File.open('test/data/absolutepath.zip') do |zip_file| - zip_file.each do |entry| - entry.extract("#{path}/#{entry.name}") - end - end - - assert File.exist?("#{path}/tmp/file.txt") - end - - def test_entry_name_with_relative_symlink - assert_raises Errno::ENOENT do - Zip::File.open('test/data/symlink.zip') do |zip_file| - zip_file.each do |entry| - entry.extract - end - end - end - end end diff --git a/test/path_traversal_test.rb b/test/path_traversal_test.rb index ab8269b7..406fc0ef 100644 --- a/test/path_traversal_test.rb +++ b/test/path_traversal_test.rb @@ -1,8 +1,11 @@ class PathTraversalTest < MiniTest::Test - TEST_FILE_ROOT = File.absolute_path('test/data/jwilk-path-traversal-samples') + TEST_FILE_ROOT = File.absolute_path('test/data/path_traversal') def setup - FileUtils.rm_f '/tmp/moo' # with apologies to anyone using this file + # With apologies to anyone using these files... but they are the files in + # the sample zips, so we don't have much choice here. + FileUtils.rm_f '/tmp/moo' + FileUtils.rm_f '/tmp/file.txt' end def extract_path_traversal_zip(name) @@ -17,72 +20,102 @@ def in_tmpdir Dir.mktmpdir do |tmp| test_path = File.join(tmp, 'test') Dir.mkdir test_path - Dir.chdir(test_path) do - yield + Dir.chdir test_path do + yield test_path end end end def test_leading_slash in_tmpdir do - extract_path_traversal_zip 'absolute1.zip' - assert !File.exist?('/tmp/moo') + extract_path_traversal_zip 'jwilk/absolute1.zip' + refute File.exist?('/tmp/moo') end end def test_multiple_leading_slashes in_tmpdir do - extract_path_traversal_zip 'absolute2.zip' - assert !File.exist?('/tmp/moo') + extract_path_traversal_zip 'jwilk/absolute2.zip' + refute File.exist?('/tmp/moo') end end def test_leading_dot_dot in_tmpdir do - extract_path_traversal_zip 'relative0.zip' - assert !File.exist?('../moo') + extract_path_traversal_zip 'jwilk/relative0.zip' + refute File.exist?('../moo') end end def test_non_leading_dot_dot in_tmpdir do - extract_path_traversal_zip 'relative2.zip' - assert !File.exist?('../moo') + extract_path_traversal_zip 'jwilk/relative2.zip' + refute File.exist?('../moo') end end def test_file_symlink in_tmpdir do - extract_path_traversal_zip 'symlink.zip' + extract_path_traversal_zip 'jwilk/symlink.zip' assert File.exist?('moo') - assert !File.exist?('/tmp/moo') + refute File.exist?('/tmp/moo') end end def test_directory_symlink in_tmpdir do - extract_path_traversal_zip 'dirsymlink.zip' - assert !File.exist?('/tmp/moo') + extract_path_traversal_zip 'jwilk/dirsymlink.zip' + refute File.exist?('/tmp/moo') end end def test_two_directory_symlinks_a in_tmpdir do - # Can't create par/moo because the symlink par is skipped. + # Can't create par/moo because the symlinks are skipped. assert_raises Errno::ENOENT do - extract_path_traversal_zip 'dirsymlink2a.zip' + extract_path_traversal_zip 'jwilk/dirsymlink2a.zip' end - assert File.exist?('cur') - assert_equal '.', File.readlink('cur') + refute File.exist?('cur') + refute File.exist?('par') + refute File.exist?('par/moo') end end def test_two_directory_symlinks_b in_tmpdir do - extract_path_traversal_zip 'dirsymlink2b.zip' + extract_path_traversal_zip 'jwilk/dirsymlink2b.zip' assert File.exist?('cur') assert_equal '.', File.readlink('cur') - assert !File.exist?('../moo') + refute File.exist?('../moo') + end + end + + def test_entry_name_with_absolute_path_does_not_extract + in_tmpdir do + extract_path_traversal_zip 'tuzovakaoff/absolutepath.zip' + refute File.exist?('/tmp/file.txt') + end + end + + def test_entry_name_with_absolute_path_extract_when_given_different_path + in_tmpdir do |test_path| + zip_path = File.join(TEST_FILE_ROOT, 'tuzovakaoff/absolutepath.zip') + Zip::File.open(zip_path) do |zip_file| + zip_file.each do |entry| + entry.extract(File.join(test_path, entry.name)) + end + end + refute File.exist?('/tmp/file.txt') + end + end + + def test_entry_name_with_relative_symlink + in_tmpdir do + # Doesn't create the symlink path, so can't create path/file.txt. + assert_raises Errno::ENOENT do + extract_path_traversal_zip 'tuzovakaoff/symlink.zip' + end + refute File.exist?('/tmp/file.txt') end end end From 3dd165b494f29d410184b2a135ed99527d4b4aa8 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 26 Aug 2018 12:32:18 +0100 Subject: [PATCH 38/72] Disable symlinks and check for path traversal --- lib/zip/entry.rb | 51 ++++++------------ test/data/path_traversal/Makefile | 10 ++++ test/data/path_traversal/relative1.zip | Bin 0 -> 212 bytes .../data/path_traversal/tuzovakaoff/README.md | 2 +- test/data/rubycode.zip | Bin 617 -> 617 bytes test/path_traversal_test.rb | 23 ++++++-- 6 files changed, 46 insertions(+), 40 deletions(-) create mode 100644 test/data/path_traversal/Makefile create mode 100644 test/data/path_traversal/relative1.zip diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 28d60091..34317d43 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -109,6 +109,16 @@ def name_is_directory? #:nodoc:all @name.end_with?('/') end + # Is the name a relative path, free of `..` patterns that could lead to + # path traversal attacks? This does NOT handle symlinks; if the path + # contains symlinks, this check is NOT enough to guarantee safety. + def name_safe? + cleanpath = Pathname.new(@name).cleanpath + return false unless cleanpath.relative? + naive_expanded_path = ::File.join(Dir.pwd, cleanpath.to_s) + cleanpath.expand_path.to_s == naive_expanded_path + end + def local_entry_offset #:nodoc:all local_header_offset + @local_header_size end @@ -147,15 +157,11 @@ def next_header_offset #:nodoc:all end # Extracts entry to file dest_path (defaults to @name). + # NB: The caller is responsible for making sure dest_path is safe, if it + # is passed. def extract(dest_path = nil, &block) - if dest_path.nil? && Pathname.new(@name).absolute? - puts "WARNING: skipped absolute path in #{@name}" - return self - elsif @name.squeeze('/') =~ /\.{2}(?:\/|\z)/ - puts "WARNING: skipped \"../\" path component(s) in #{@name}" - return self - elsif symlink? && get_input_stream.read =~ %r{../..} - puts "WARNING: skipped \"#{get_input_stream.read}\" symlink path in #{@name}" + if dest_path.nil? && !name_safe? + puts "WARNING: skipped #{@name} as unsafe" return self end @@ -620,32 +626,9 @@ def create_directory(dest_path) # BUG: create_symlink() does not use &block def create_symlink(dest_path) - stat = nil - begin - stat = ::File.lstat(dest_path) - rescue Errno::ENOENT - end - - io = get_input_stream - linkto = io.read - - if stat - if stat.symlink? - if ::File.readlink(dest_path) == linkto - return - else - raise ::Zip::DestinationFileExistsError, - "Cannot create symlink '#{dest_path}'. " \ - 'A symlink already exists with that name' - end - else - raise ::Zip::DestinationFileExistsError, - "Cannot create symlink '#{dest_path}'. " \ - 'A file already exists with that name' - end - end - - ::File.symlink(linkto, dest_path) + # TODO: Symlinks pose security challenges. Symlink support temporarily + # removed in view of https://github.com/rubyzip/rubyzip/issues/369 . + puts "WARNING: skipped symlink #{dest_path}" end # apply missing data from the zip64 extra information field, if present diff --git a/test/data/path_traversal/Makefile b/test/data/path_traversal/Makefile new file mode 100644 index 00000000..9ff4d816 --- /dev/null +++ b/test/data/path_traversal/Makefile @@ -0,0 +1,10 @@ +# Based on 'relative2' in https://github.com/jwilk/path-traversal-samples, +# but create the local `tmp` folder before adding the symlink. Otherwise +# we may bail out before we get to trying to create the file. +all: relative1.zip +relative1.zip: + rm -f $(@) + mkdir -p -m 755 tmp/tmp + umask 022 && echo moo > moo + cd tmp && zip -X ../$(@) tmp tmp/../../moo + rm -rf tmp moo diff --git a/test/data/path_traversal/relative1.zip b/test/data/path_traversal/relative1.zip new file mode 100644 index 0000000000000000000000000000000000000000..bfcb9deff18c2655f2bcf109d09967a37a9ccb11 GIT binary patch literal 212 zcmWIWW@h1H0D-whQodjYlwbkUCAkIq0Vpa~J~Cbjk^x~}s0uwjeIUxs&j%u|0B=Sn zIcD5O0`&la0K;2H5Dl>aRR_8iAa#sDyrfYHssnCefHx}}NSX-nD;YsN0Ky3(ga7~l delta 151 zcmaFK@{(nOu1E+c1M{&>2W-x6KVTDD!NQQLEvPxsu?j4H5JOyj; Date: Sun, 26 Aug 2018 19:55:26 +0100 Subject: [PATCH 39/72] Expand from root rather than current working directory --- lib/zip/entry.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 34317d43..fddab51e 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -115,8 +115,9 @@ def name_is_directory? #:nodoc:all def name_safe? cleanpath = Pathname.new(@name).cleanpath return false unless cleanpath.relative? - naive_expanded_path = ::File.join(Dir.pwd, cleanpath.to_s) - cleanpath.expand_path.to_s == naive_expanded_path + root = ::File::SEPARATOR + naive_expanded_path = ::File.join(root, cleanpath.to_s) + cleanpath.expand_path(root).to_s == naive_expanded_path end def local_entry_offset #:nodoc:all From ffb374c6b1757f6b5eb93e68b8b37ebc7df3f310 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Mon, 27 Aug 2018 08:37:53 +0100 Subject: [PATCH 40/72] Bump version to 2.0.0 --- lib/zip/version.rb | 2 +- test/data/rubycode.zip | Bin 617 -> 617 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/version.rb b/lib/zip/version.rb index f1dc85f4..eb9cfa9b 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '1.2.1' + VERSION = '2.0.0' end diff --git a/test/data/rubycode.zip b/test/data/rubycode.zip index 06134bbcdb260c14da8d915eebdf2ca7176ac723..7f923a08cf83596d9cdc5f6d61ae6809983f7816 100644 GIT binary patch delta 139 zcmaFK@{(nOu0RMU1M{&>2W%LCAhd#oVWMpvSnME*nD)eP!C>J{+Yw49M>59pgs?*N shgL8!Og_YD3l;!r1`BXA*(iZjfV6=G0=!w-KsGT0;VmFt$q3>B0BR>9cmMzZ delta 139 zcmaFK@{(nOu0RC~0|$E_QY?&D8iezAF!Dm$r#I10n*C~ m(L4DNqb*zjq;WDglZ_Hg1xQPPH!B;+I%Xie1*9t(K|BC1`674# From cf35774ed686057d8cc17aa4b015a2a850cc2bce Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Mon, 27 Aug 2018 09:02:11 +0100 Subject: [PATCH 41/72] Bump version to 1.3.0 --- lib/zip/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/version.rb b/lib/zip/version.rb index eb9cfa9b..37fba090 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '2.0.0' + VERSION = '1.3.0' end From fd81bd523cd53096c1a1dce1e950ef0b7658a02c Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Mon, 27 Aug 2018 09:07:21 +0100 Subject: [PATCH 42/72] Bump version to 1.2.2 --- lib/zip/version.rb | 2 +- test/data/rubycode.zip | Bin 617 -> 617 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zip/version.rb b/lib/zip/version.rb index 37fba090..14a9f99e 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '1.3.0' + VERSION = '1.2.2' end diff --git a/test/data/rubycode.zip b/test/data/rubycode.zip index 7f923a08cf83596d9cdc5f6d61ae6809983f7816..06134bbcdb260c14da8d915eebdf2ca7176ac723 100644 GIT binary patch delta 139 zcmaFK@{(nOu0RC~0|$E_QY?&D8iezAF!Dm$r#I10n*C~ m(L4DNqb*zjq;WDglZ_Hg1xQPPH!B;+I%Xie1*9t(K|BC1`674# delta 139 zcmaFK@{(nOu0RMU1M{&>2W%LCAhd#oVWMpvSnME*nD)eP!C>J{+Yw49M>59pgs?*N shgL8!Og_YD3l;!r1`BXA*(iZjfV6=G0=!w-KsGT0;VmFt$q3>B0BR>9cmMzZ From afb1b79efd34f8d144104bbe4665037eac7c974a Mon Sep 17 00:00:00 2001 From: Mihyaeru Date: Tue, 4 Dec 2018 00:14:32 +0900 Subject: [PATCH 43/72] remove some strange commas --- lib/zip/entry.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index fddab51e..6e91c213 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -275,10 +275,10 @@ def pack_local_entry zip64 = @extra['Zip64'] [::Zip::LOCAL_ENTRY_SIGNATURE, @version_needed_to_extract, # version needed to extract - @gp_flags, # @gp_flags , + @gp_flags, # @gp_flags @compression_method, - @time.to_binary_dos_time, # @last_mod_time , - @time.to_binary_dos_date, # @last_mod_date , + @time.to_binary_dos_time, # @last_mod_time + @time.to_binary_dos_date, # @last_mod_date @crc, zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size, zip64 && zip64.original_size ? 0xFFFFFFFF : @size, @@ -432,11 +432,11 @@ def pack_c_dir_entry @header_signature, @version, # version of encoding software @fstype, # filesystem type - @version_needed_to_extract, # @versionNeededToExtract , - @gp_flags, # @gp_flags , + @version_needed_to_extract, # @versionNeededToExtract + @gp_flags, # @gp_flags @compression_method, - @time.to_binary_dos_time, # @last_mod_time , - @time.to_binary_dos_date, # @last_mod_date , + @time.to_binary_dos_time, # @last_mod_time + @time.to_binary_dos_date, # @last_mod_date @crc, zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size, zip64 && zip64.original_size ? 0xFFFFFFFF : @size, From 9eac0d66e8cf069e8528daa89b7c998a4898d260 Mon Sep 17 00:00:00 2001 From: Adam Spiers Date: Wed, 23 Jan 2019 11:02:17 +0000 Subject: [PATCH 44/72] Add Changelog for 1.2.2 (#378) 1.2.2 was already released in #376, so unfortunately this is too late for inclusion in that, but at least future releases will have it. This is just a list of the titles of all non-merge commits since 1.2.1, so it won't be as concise or readable a summary as for previous releases, but it's better than nothing, and anyone is welcome to volunteer to condense it further. Closes #378. --- Changelog.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Changelog.md b/Changelog.md index 7318fd10..7ed352dc 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,40 @@ +1.2.2 +===== + +* Expand from root rather than current working directory +* Disable symlinks and check for path traversal +* Consolidate path traversal tests +* Add jwilk's path traversal tests +* Trigger CI again +* Move jruby to allow failures matrix till crc uint 32 issues are resolved +* Fix CVE-2018-1000544 symlink path traversal +* Fix CVE-2018-1000544 absolute path traversal +* Fix jruby version +* When globbing in ZipFSDir, take CWD into account. +* Pass glob through from ZipFileNameMapper. +* Turn off all terminal output in all tests. +* Handle stored files with general purpose bit 3 set +* Fix regression caused by Rubocop cleanup +* Added fix for calling 'close' on a StringIO-backed zip file, and specs +* Bump Ruby versions on Travis CI +* Travis: Typo +* Travis: Workaround a rbx-3 autoload issue +* CI against Ruby 2.2.8, 2.3.5, and 2.4.2 +* Travis: typo +* Travis: Try using rbx-3 +* Travis: update RubyGems +* Travis: drop oraclejdk-7 +* Travis: use JRUBY_OPTS="--debug" +* Travis: use pre-installed Travis rubies +* README: Use a blockquote to make text readable +* add option to force entry names encoding +* Make naming on README more consistent +* Apply automatic correction by rubocop +* Disable Style/MutableConstant because existent code relies on it +* Add rubocop dependency and correct settings +* Save temporary files to a temporary directory +* File.join() is our friend for joining paths + 1.2.1 ===== From a420323c84e32df1ac2b95cd878826c9f41c06b9 Mon Sep 17 00:00:00 2001 From: David Ryskalczyk Date: Sun, 10 Feb 2019 11:51:29 -0500 Subject: [PATCH 45/72] require pathname where it is used --- lib/zip/entry.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index fddab51e..f50ea31a 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -1,3 +1,4 @@ +require 'pathname' module Zip class Entry STORED = 0 From 74f0d4eabbadb005979aa7595507e41cb67a1950 Mon Sep 17 00:00:00 2001 From: taichi Date: Thu, 28 Feb 2019 01:23:29 +0900 Subject: [PATCH 46/72] fixed errors caused by frozen-string-literal --- lib/zip/entry.rb | 2 +- lib/zip/extra_field.rb | 2 +- lib/zip/inflater.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index fddab51e..357f74d1 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -602,7 +602,7 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi get_input_stream do |is| set_extra_attributes_on_path(dest_path) - buf = '' + buf = ''.dup while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)) os << buf end diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb index cbc2fa8d..72c36764 100644 --- a/lib/zip/extra_field.rb +++ b/lib/zip/extra_field.rb @@ -26,7 +26,7 @@ def extra_field_type_unknown(binstr, len, i) end def create_unknown_item - s = '' + s = ''.dup class << s alias_method :to_c_dir_bin, :to_s alias_method :to_local_bin, :to_s diff --git a/lib/zip/inflater.rb b/lib/zip/inflater.rb index ef952f07..f1b26d45 100644 --- a/lib/zip/inflater.rb +++ b/lib/zip/inflater.rb @@ -3,7 +3,7 @@ class Inflater < Decompressor #:nodoc:all def initialize(input_stream, decrypter = NullDecrypter.new) super(input_stream) @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS) - @output_buffer = '' + @output_buffer = ''.dup @has_returned_empty_string = false @decrypter = decrypter end From 0e6e626d45bcf85e520de83f5c1cf69cfec93b03 Mon Sep 17 00:00:00 2001 From: taichi Date: Thu, 28 Feb 2019 17:40:12 +0900 Subject: [PATCH 47/72] fixed CI error --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b197c86b..5c70ff27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,8 +26,10 @@ matrix: - rvm: jruby-head - rvm: jruby before_install: - - gem update --system - - gem install bundler + - "if $(ruby -e 'exit(RUBY_VERSION >= \"2.3.0\")'); then gem update --system; fi" + - "if $(ruby -e 'exit(RUBY_VERSION < \"2.3.0\")'); then gem update --system 2.7.8; fi" + - "if $(ruby -e 'exit(RUBY_VERSION >= \"2.3.0\")'); then gem install bundler; fi" + - "if $(ruby -e 'exit(RUBY_VERSION < \"2.3.0\")'); then gem install bundler --version 1.17.3; fi" - gem --version before_script: - echo `whereis zip` From fa4f7fb1c2e23ab9dc13e680821355e438804a1d Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 22 Mar 2019 11:05:52 +0200 Subject: [PATCH 48/72] Stop allowing jruby failures --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5c70ff27..1dfd67b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,11 +24,10 @@ matrix: - rvm: ruby-head - rvm: rbx-3 - rvm: jruby-head - - rvm: jruby before_install: - "if $(ruby -e 'exit(RUBY_VERSION >= \"2.3.0\")'); then gem update --system; fi" - "if $(ruby -e 'exit(RUBY_VERSION < \"2.3.0\")'); then gem update --system 2.7.8; fi" - - "if $(ruby -e 'exit(RUBY_VERSION >= \"2.3.0\")'); then gem install bundler; fi" + - "if $(ruby -e 'exit(RUBY_VERSION >= \"2.3.0\")'); then gem install bundler; fi" - "if $(ruby -e 'exit(RUBY_VERSION < \"2.3.0\")'); then gem install bundler --version 1.17.3; fi" - gem --version before_script: From d2f0f021e67a58b5002c8eb81d207c72bd7d1209 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Thu, 21 Mar 2019 23:39:17 +0200 Subject: [PATCH 49/72] Enable parallel build support for coveralls --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1dfd67b8..5e13b199 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,3 +36,6 @@ before_script: env: global: - JRUBY_OPTS="--debug" + - COVERALLS_PARALLEL=true +notifications: + webhooks: https://coveralls.io/webhook From 0f36838981669a6242fc579a3579294b274ff6ed Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 22 Mar 2019 11:31:21 +0200 Subject: [PATCH 50/72] Update ruby dependencies --- .travis.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5e13b199..aad67df0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,11 @@ cache: bundler rvm: - 2.0.0 - 2.1.10 - - 2.2.9 - - 2.3.6 - - 2.4.3 - - 2.5.0 + - 2.2.10 + - 2.3.8 + - 2.4.5 + - 2.5.3 + - 2.6.0 - ruby-head matrix: include: @@ -25,10 +26,6 @@ matrix: - rvm: rbx-3 - rvm: jruby-head before_install: - - "if $(ruby -e 'exit(RUBY_VERSION >= \"2.3.0\")'); then gem update --system; fi" - - "if $(ruby -e 'exit(RUBY_VERSION < \"2.3.0\")'); then gem update --system 2.7.8; fi" - - "if $(ruby -e 'exit(RUBY_VERSION >= \"2.3.0\")'); then gem install bundler; fi" - - "if $(ruby -e 'exit(RUBY_VERSION < \"2.3.0\")'); then gem install bundler --version 1.17.3; fi" - gem --version before_script: - echo `whereis zip` From ad15c3c49464097390248220fd93ce4caa8f43e3 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 3 Mar 2019 14:46:49 +0000 Subject: [PATCH 51/72] Allow tilde in zip entry names Use absolute_path rather than expand_path to allow tilde to pass through unchanged. Otherwise, we try to expand it to a home directory. --- lib/zip/entry.rb | 2 +- test/data/path_traversal/tilde.zip | Bin 0 -> 577 bytes test/path_traversal_test.rb | 7 +++++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 test/data/path_traversal/tilde.zip diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index a98c0772..80160b57 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -118,7 +118,7 @@ def name_safe? return false unless cleanpath.relative? root = ::File::SEPARATOR naive_expanded_path = ::File.join(root, cleanpath.to_s) - cleanpath.expand_path(root).to_s == naive_expanded_path + ::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path end def local_entry_offset #:nodoc:all diff --git a/test/data/path_traversal/tilde.zip b/test/data/path_traversal/tilde.zip new file mode 100644 index 0000000000000000000000000000000000000000..0442ab93701831639904d7f1c1538cc476c18dbe GIT binary patch literal 577 zcmWIWW@Zs#-~d9~%0xc~B*4xfz))9`nUj)Q7aGCCu;fm4jGK35%vVMc2JMq)JUyQ> zF$8$Cb9`Q=RLlX?#sIxMlp;NXjpu_ucNbnaD+a{xRpTTKp12kNE>!H z2qA0Ji^t)d=^n|2@557KOANa#M0vebmNHD7lNHcy^QegYI-avp=#@VT9fzO0K zNr#-~Wi5h=db8%tUp+k{V8Y~8;SsYUrpySRG;{i?fED=(35glGvvTH5nUs^0^>$9+ zl=-u>auc7VJ$&`-*|O{xZ=S8le)KGD#p{%em#eb#o;=G)c#x9!Amha$knhoUs4^ literal 0 HcmV?d00001 diff --git a/test/path_traversal_test.rb b/test/path_traversal_test.rb index 9a361a59..e5bdd722 100644 --- a/test/path_traversal_test.rb +++ b/test/path_traversal_test.rb @@ -131,4 +131,11 @@ def test_entry_name_with_relative_symlink refute File.exist?('/tmp/file.txt') end end + + def test_entry_name_with_tilde + in_tmpdir do + extract_path_traversal_zip 'tilde.zip' + assert File.exist?('~tilde~') + end + end end From fb1c230cac322d776bb010748e5e1ac87f15100a Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 22 Mar 2019 17:51:57 +0200 Subject: [PATCH 52/72] Bump version to 1.2.3 --- Changelog.md | 72 ++++++++++++++++++++++++---------------------- lib/zip/version.rb | 2 +- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/Changelog.md b/Changelog.md index 7ed352dc..5cf9622a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,39 +1,41 @@ +X.X.X (Next) +===== + + + +1.2.3 +===== + +* Allow tilde in zip entry names [#391](https://github.com/rubyzip/rubyzip/pull/391) (fixes regression in 1.2.2 from [#376](https://github.com/rubyzip/rubyzip/pull/376)) +* Support frozen string literals in more files [#390](https://github.com/rubyzip/rubyzip/pull/390) +* Require `pathname` explicitly [#388](https://github.com/rubyzip/rubyzip/pull/388) (fixes regression in 1.2.2 from [#376](https://github.com/rubyzip/rubyzip/pull/376)) + +Tooling / Documentation: + +* CI updates [#392](https://github.com/rubyzip/rubyzip/pull/392) + * Bump supported ruby versions and add 2.6.0 + * JRuby failures are no longer ignored (reverts [#375](https://github.com/rubyzip/rubyzip/pull/375) / part of [#371](https://github.com/rubyzip/rubyzip/pull/371)) +* Add changelog entry that was missing for last release [#387](https://github.com/rubyzip/rubyzip/pull/387) +* Comment cleanup [#385](https://github.com/rubyzip/rubyzip/pull/385) + 1.2.2 ===== -* Expand from root rather than current working directory -* Disable symlinks and check for path traversal -* Consolidate path traversal tests -* Add jwilk's path traversal tests -* Trigger CI again -* Move jruby to allow failures matrix till crc uint 32 issues are resolved -* Fix CVE-2018-1000544 symlink path traversal -* Fix CVE-2018-1000544 absolute path traversal -* Fix jruby version -* When globbing in ZipFSDir, take CWD into account. -* Pass glob through from ZipFileNameMapper. -* Turn off all terminal output in all tests. -* Handle stored files with general purpose bit 3 set -* Fix regression caused by Rubocop cleanup -* Added fix for calling 'close' on a StringIO-backed zip file, and specs -* Bump Ruby versions on Travis CI -* Travis: Typo -* Travis: Workaround a rbx-3 autoload issue -* CI against Ruby 2.2.8, 2.3.5, and 2.4.2 -* Travis: typo -* Travis: Try using rbx-3 -* Travis: update RubyGems -* Travis: drop oraclejdk-7 -* Travis: use JRUBY_OPTS="--debug" -* Travis: use pre-installed Travis rubies -* README: Use a blockquote to make text readable -* add option to force entry names encoding -* Make naming on README more consistent -* Apply automatic correction by rubocop -* Disable Style/MutableConstant because existent code relies on it -* Add rubocop dependency and correct settings -* Save temporary files to a temporary directory -* File.join() is our friend for joining paths +NB: This release drops support for extracting symlinks, because there was no clear way to support this securely. See https://github.com/rubyzip/rubyzip/pull/376#issue-210954555 for details. + +* Fix CVE-2018-1000544 [#376](https://github.com/rubyzip/rubyzip/pull/376) / [#371](https://github.com/rubyzip/rubyzip/pull/371) +* Fix NoMethodError: undefined method `glob' [#363](https://github.com/rubyzip/rubyzip/pull/363) +* Fix handling of stored files (i.e. files not using compression) with general purpose bit 3 set [#358](https://github.com/rubyzip/rubyzip/pull/358) +* Fix `close` on StringIO-backed zip file [#353](https://github.com/rubyzip/rubyzip/pull/353) +* Add `Zip.force_entry_names_encoding` option [#340](https://github.com/rubyzip/rubyzip/pull/340) +* Update rubocop, apply auto-fixes, and fix regressions caused by said auto-fixes [#332](https://github.com/rubyzip/rubyzip/pull/332), [#355](https://github.com/rubyzip/rubyzip/pull/355) +* Save temporary files to temporary directory (rather than current directory) [#325](https://github.com/rubyzip/rubyzip/pull/325) + +Tooling / Documentation: + +* Turn off all terminal output in all tests [#361](https://github.com/rubyzip/rubyzip/pull/361) +* Several CI updates [#346](https://github.com/rubyzip/rubyzip/pull/346), [#347](https://github.com/rubyzip/rubyzip/pull/347), [#350](https://github.com/rubyzip/rubyzip/pull/350), [#352](https://github.com/rubyzip/rubyzip/pull/352) +* Several README improvements [#345](https://github.com/rubyzip/rubyzip/pull/345), [#326](https://github.com/rubyzip/rubyzip/pull/326), [#321](https://github.com/rubyzip/rubyzip/pull/321) 1.2.1 ===== @@ -100,7 +102,7 @@ * Fix compatibility of ::OutputStream::write_buffer (@orien) * Clean up tempfiles from output stream (@iangreenleaf) -1.1.2 +1.1.2 ===== * Fix compatibility of ::Zip::File.write_buffer @@ -113,7 +115,7 @@ * Fix Zip64 writting support (@mrjamesriley) * Fix StringIO support (@simonoff) * Posibility to change default compression level -* Make Zip64 write support optional via configuration +* Make Zip64 write support optional via configuration 1.1.0 ===== diff --git a/lib/zip/version.rb b/lib/zip/version.rb index 14a9f99e..4d6ab8b3 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '1.2.2' + VERSION = '1.2.3' end From a8609e1e2ba306dbfc5c17e2837315577f376d15 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Fri, 22 Mar 2019 17:54:30 +0100 Subject: [PATCH 53/72] CI: update to latest MRI, drop a setting - drop unused Travis configuration: sudo: false - see https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration for historical detail about when it was removed. --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index aad67df0..00f3b2d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: ruby -sudo: false cache: bundler rvm: - 2.0.0 @@ -7,8 +6,8 @@ rvm: - 2.2.10 - 2.3.8 - 2.4.5 - - 2.5.3 - - 2.6.0 + - 2.5.5 + - 2.6.2 - ruby-head matrix: include: From ada408d60a7d3aa708c8560bbab5f6d32694a45a Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 22 Mar 2019 21:18:40 +0200 Subject: [PATCH 54/72] Add #394 to changelog --- Changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 5cf9622a..20e61c35 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,8 +12,8 @@ X.X.X (Next) Tooling / Documentation: -* CI updates [#392](https://github.com/rubyzip/rubyzip/pull/392) - * Bump supported ruby versions and add 2.6.0 +* CI updates [#392](https://github.com/rubyzip/rubyzip/pull/392), [#394]((https://github.com/rubyzip/rubyzip/pull/394) + * Bump supported ruby versions and add 2.6 * JRuby failures are no longer ignored (reverts [#375](https://github.com/rubyzip/rubyzip/pull/375) / part of [#371](https://github.com/rubyzip/rubyzip/pull/371)) * Add changelog entry that was missing for last release [#387](https://github.com/rubyzip/rubyzip/pull/387) * Comment cleanup [#385](https://github.com/rubyzip/rubyzip/pull/385) From 9d891f7353e66052283562d3e252fe380bb4b199 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Thu, 23 May 2019 18:35:24 +0100 Subject: [PATCH 55/72] Fix link typo in changelog --- Changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 20e61c35..591b7ca0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,7 +12,7 @@ X.X.X (Next) Tooling / Documentation: -* CI updates [#392](https://github.com/rubyzip/rubyzip/pull/392), [#394]((https://github.com/rubyzip/rubyzip/pull/394) +* CI updates [#392](https://github.com/rubyzip/rubyzip/pull/392), [#394](https://github.com/rubyzip/rubyzip/pull/394) * Bump supported ruby versions and add 2.6 * JRuby failures are no longer ignored (reverts [#375](https://github.com/rubyzip/rubyzip/pull/375) / part of [#371](https://github.com/rubyzip/rubyzip/pull/371)) * Add changelog entry that was missing for last release [#387](https://github.com/rubyzip/rubyzip/pull/387) From 1e21121f6cdb105ee8d6ab7551950b72120a261f Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 24 May 2019 17:07:35 +0100 Subject: [PATCH 56/72] Update example_recursive in README The sample has been updated several times since the last update to the README. Also ran through prettier for formatting consistency. --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d5dbe76b..8255cd90 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # rubyzip + [![Gem Version](https://badge.fury.io/rb/rubyzip.svg)](http://badge.fury.io/rb/rubyzip) [![Build Status](https://secure.travis-ci.org/rubyzip/rubyzip.svg)](http://travis-ci.org/rubyzip/rubyzip) [![Code Climate](https://codeclimate.com/github/rubyzip/rubyzip.svg)](https://codeclimate.com/github/rubyzip/rubyzip) @@ -19,9 +20,10 @@ gem 'zip-zip' # will load compatibility for old rubyzip API. ## Requirements -* Ruby 1.9.2 or greater +- Ruby 1.9.2 or greater ## Installation + Rubyzip is available on RubyGems: ``` @@ -59,7 +61,8 @@ end ``` ### Zipping a directory recursively -Copy from [here](https://github.com/rubyzip/rubyzip/blob/05916bf89181e1955118fd3ea059f18acac28cc8/samples/example_recursive.rb ) + +Copy from [here](https://github.com/rubyzip/rubyzip/blob/9d891f7353e66052283562d3e252fe380bb4b199/samples/example_recursive.rb) ```ruby require 'zip' @@ -83,7 +86,7 @@ class ZipFileGenerator # Zip the input directory. def write - entries = Dir.entries(@input_dir) - %w(. ..) + entries = Dir.entries(@input_dir) - %w[. ..] ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile| write_entries entries, '', zipfile @@ -97,7 +100,6 @@ class ZipFileGenerator entries.each do |e| zipfile_path = path == '' ? e : File.join(path, e) disk_file_path = File.join(@input_dir, zipfile_path) - puts "Deflating #{disk_file_path}" if File.directory? disk_file_path recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) @@ -109,14 +111,12 @@ class ZipFileGenerator def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) zipfile.mkdir zipfile_path - subdir = Dir.entries(disk_file_path) - %w(. ..) + subdir = Dir.entries(disk_file_path) - %w[. ..] write_entries subdir, zipfile_path, zipfile end def put_into_archive(disk_file_path, zipfile, zipfile_path) - zipfile.get_output_stream(zipfile_path) do |f| - f.write(File.open(disk_file_path, 'rb').read) - end + zipfile.add(zipfile_path, disk_file_path) end end ``` @@ -177,7 +177,6 @@ But there is one exception when it is not working - General Purpose Flag Bit 3. > If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data - If `::Zip::InputStream` finds such entry in the zip archive it will raise an exception. ### Password Protection (Experimental) @@ -220,7 +219,7 @@ File.open(new_path, "wb") {|f| f.write(buffer.string) } ## Configuration -By default, rubyzip will not overwrite files if they already exist inside of the extracted path. To change this behavior, you may specify a configuration option like so: +By default, rubyzip will not overwrite files if they already exist inside of the extracted path. To change this behavior, you may specify a configuration option like so: ```ruby Zip.on_exists_proc = true @@ -251,6 +250,7 @@ You can set the default compression level like so: ```ruby Zip.default_compression = Zlib::DEFAULT_COMPRESSION ``` + It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION` Sometimes file names inside zip contain non-ASCII characters. If you can assume which encoding was used for such names and want to be able to find such entries using `find_entry` then you can force assumed encoding like so: From 952950e474a07ef8fe2f5cf894bad189c6247ac1 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 24 May 2019 17:25:17 +0100 Subject: [PATCH 57/72] Update changelog for #397 Also run changelog through prettier for consistency with README.md. --- Changelog.md | 399 ++++++++++++++++++++++----------------------------- 1 file changed, 170 insertions(+), 229 deletions(-) diff --git a/Changelog.md b/Changelog.md index 591b7ca0..57fccdf4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,320 +1,261 @@ -X.X.X (Next) -===== +# X.X.X (Next) +- +Tooling / Documentation -1.2.3 -===== +- Update `example_recursive.rb` in README [#397](https://github.com/rubyzip/rubyzip/pull/397) -* Allow tilde in zip entry names [#391](https://github.com/rubyzip/rubyzip/pull/391) (fixes regression in 1.2.2 from [#376](https://github.com/rubyzip/rubyzip/pull/376)) -* Support frozen string literals in more files [#390](https://github.com/rubyzip/rubyzip/pull/390) -* Require `pathname` explicitly [#388](https://github.com/rubyzip/rubyzip/pull/388) (fixes regression in 1.2.2 from [#376](https://github.com/rubyzip/rubyzip/pull/376)) +# 1.2.3 + +- Allow tilde in zip entry names [#391](https://github.com/rubyzip/rubyzip/pull/391) (fixes regression in 1.2.2 from [#376](https://github.com/rubyzip/rubyzip/pull/376)) +- Support frozen string literals in more files [#390](https://github.com/rubyzip/rubyzip/pull/390) +- Require `pathname` explicitly [#388](https://github.com/rubyzip/rubyzip/pull/388) (fixes regression in 1.2.2 from [#376](https://github.com/rubyzip/rubyzip/pull/376)) Tooling / Documentation: -* CI updates [#392](https://github.com/rubyzip/rubyzip/pull/392), [#394](https://github.com/rubyzip/rubyzip/pull/394) - * Bump supported ruby versions and add 2.6 - * JRuby failures are no longer ignored (reverts [#375](https://github.com/rubyzip/rubyzip/pull/375) / part of [#371](https://github.com/rubyzip/rubyzip/pull/371)) -* Add changelog entry that was missing for last release [#387](https://github.com/rubyzip/rubyzip/pull/387) -* Comment cleanup [#385](https://github.com/rubyzip/rubyzip/pull/385) +- CI updates [#392](https://github.com/rubyzip/rubyzip/pull/392), [#394](https://github.com/rubyzip/rubyzip/pull/394) + - Bump supported ruby versions and add 2.6 + - JRuby failures are no longer ignored (reverts [#375](https://github.com/rubyzip/rubyzip/pull/375) / part of [#371](https://github.com/rubyzip/rubyzip/pull/371)) +- Add changelog entry that was missing for last release [#387](https://github.com/rubyzip/rubyzip/pull/387) +- Comment cleanup [#385](https://github.com/rubyzip/rubyzip/pull/385) -1.2.2 -===== +# 1.2.2 NB: This release drops support for extracting symlinks, because there was no clear way to support this securely. See https://github.com/rubyzip/rubyzip/pull/376#issue-210954555 for details. -* Fix CVE-2018-1000544 [#376](https://github.com/rubyzip/rubyzip/pull/376) / [#371](https://github.com/rubyzip/rubyzip/pull/371) -* Fix NoMethodError: undefined method `glob' [#363](https://github.com/rubyzip/rubyzip/pull/363) -* Fix handling of stored files (i.e. files not using compression) with general purpose bit 3 set [#358](https://github.com/rubyzip/rubyzip/pull/358) -* Fix `close` on StringIO-backed zip file [#353](https://github.com/rubyzip/rubyzip/pull/353) -* Add `Zip.force_entry_names_encoding` option [#340](https://github.com/rubyzip/rubyzip/pull/340) -* Update rubocop, apply auto-fixes, and fix regressions caused by said auto-fixes [#332](https://github.com/rubyzip/rubyzip/pull/332), [#355](https://github.com/rubyzip/rubyzip/pull/355) -* Save temporary files to temporary directory (rather than current directory) [#325](https://github.com/rubyzip/rubyzip/pull/325) +- Fix CVE-2018-1000544 [#376](https://github.com/rubyzip/rubyzip/pull/376) / [#371](https://github.com/rubyzip/rubyzip/pull/371) +- Fix NoMethodError: undefined method `glob' [#363](https://github.com/rubyzip/rubyzip/pull/363) +- Fix handling of stored files (i.e. files not using compression) with general purpose bit 3 set [#358](https://github.com/rubyzip/rubyzip/pull/358) +- Fix `close` on StringIO-backed zip file [#353](https://github.com/rubyzip/rubyzip/pull/353) +- Add `Zip.force_entry_names_encoding` option [#340](https://github.com/rubyzip/rubyzip/pull/340) +- Update rubocop, apply auto-fixes, and fix regressions caused by said auto-fixes [#332](https://github.com/rubyzip/rubyzip/pull/332), [#355](https://github.com/rubyzip/rubyzip/pull/355) +- Save temporary files to temporary directory (rather than current directory) [#325](https://github.com/rubyzip/rubyzip/pull/325) Tooling / Documentation: -* Turn off all terminal output in all tests [#361](https://github.com/rubyzip/rubyzip/pull/361) -* Several CI updates [#346](https://github.com/rubyzip/rubyzip/pull/346), [#347](https://github.com/rubyzip/rubyzip/pull/347), [#350](https://github.com/rubyzip/rubyzip/pull/350), [#352](https://github.com/rubyzip/rubyzip/pull/352) -* Several README improvements [#345](https://github.com/rubyzip/rubyzip/pull/345), [#326](https://github.com/rubyzip/rubyzip/pull/326), [#321](https://github.com/rubyzip/rubyzip/pull/321) - -1.2.1 -===== - -* Add accessor to @internal_file_attributes #304 -* Extended globbing #303 -* README updates #283, #289 -* Cleanup after tests #298, #306 -* Fix permissions on new zip files #294, #300 -* Fix examples #297 -* Support cp932 encoding #308 -* Fix Directory traversal vulnerability #315 -* Allow open_buffer to work without a given block #314 +- Turn off all terminal output in all tests [#361](https://github.com/rubyzip/rubyzip/pull/361) +- Several CI updates [#346](https://github.com/rubyzip/rubyzip/pull/346), [#347](https://github.com/rubyzip/rubyzip/pull/347), [#350](https://github.com/rubyzip/rubyzip/pull/350), [#352](https://github.com/rubyzip/rubyzip/pull/352) +- Several README improvements [#345](https://github.com/rubyzip/rubyzip/pull/345), [#326](https://github.com/rubyzip/rubyzip/pull/326), [#321](https://github.com/rubyzip/rubyzip/pull/321) -1.2.0 -===== +# 1.2.1 -* Don't enable JRuby objectspace #252 -* Fixes an exception thrown when decoding some weird .zip files #248 -* Use duck typing with IO methods #244 -* Added error for empty (zero bit) zip file #242 -* Accept StringIO in Zip.open_buffer #238 -* Do something more expected with new file permissions #237 -* Case insensitivity option for #find_entry #222 -* Fixes in documentation and examples +- Add accessor to @internal_file_attributes #304 +- Extended globbing #303 +- README updates #283, #289 +- Cleanup after tests #298, #306 +- Fix permissions on new zip files #294, #300 +- Fix examples #297 +- Support cp932 encoding #308 +- Fix Directory traversal vulnerability #315 +- Allow open_buffer to work without a given block #314 -1.1.7 -===== +# 1.2.0 -* Fix UTF-8 support for comments -* `Zip.sort_entries` working for zip output -* Prevent tempfile path from being unlinked by garbage collection -* NTFS Extra Field (0x000a) support -* Use String#tr instead of String#gsub -* Ability to not show warning about incorrect date -* Be smarter about handling buffer file modes. -* Support for Traditional Encryption (ZipCrypto) +- Don't enable JRuby objectspace #252 +- Fixes an exception thrown when decoding some weird .zip files #248 +- Use duck typing with IO methods #244 +- Added error for empty (zero bit) zip file #242 +- Accept StringIO in Zip.open_buffer #238 +- Do something more expected with new file permissions #237 +- Case insensitivity option for #find_entry #222 +- Fixes in documentation and examples -1.1.6 -===== +# 1.1.7 -* Revert "Return created zip file from Zip::File.open when supplied a block" +- Fix UTF-8 support for comments +- `Zip.sort_entries` working for zip output +- Prevent tempfile path from being unlinked by garbage collection +- NTFS Extra Field (0x000a) support +- Use String#tr instead of String#gsub +- Ability to not show warning about incorrect date +- Be smarter about handling buffer file modes. +- Support for Traditional Encryption (ZipCrypto) -1.1.5 -===== +# 1.1.6 -* Treat empty file as non-exists (@layerssss) -* Revert regression commit -* Return created zip file from Zip::File.open when supplied a block (@tpickett66) -* Zip::Entry::DEFLATED is forced on every file (@mehmetc) -* Add InputStream#ungetc (@zacstewart) -* Alias for legacy error names (@orien) +- Revert "Return created zip file from Zip::File.open when supplied a block" -1.1.4 -===== +# 1.1.5 -* Don't send empty string to stream (@mrloop) -* Zip::Entry::DEFLATED was forced on every file (@mehmetc) -* Alias for legacy error names (@orien) +- Treat empty file as non-exists (@layerssss) +- Revert regression commit +- Return created zip file from Zip::File.open when supplied a block (@tpickett66) +- Zip::Entry::DEFLATED is forced on every file (@mehmetc) +- Add InputStream#ungetc (@zacstewart) +- Alias for legacy error names (@orien) -1.1.3 -===== +# 1.1.4 -* Fix compatibility of ::OutputStream::write_buffer (@orien) -* Clean up tempfiles from output stream (@iangreenleaf) +- Don't send empty string to stream (@mrloop) +- Zip::Entry::DEFLATED was forced on every file (@mehmetc) +- Alias for legacy error names (@orien) -1.1.2 -===== +# 1.1.3 -* Fix compatibility of ::Zip::File.write_buffer +- Fix compatibility of ::OutputStream::write_buffer (@orien) +- Clean up tempfiles from output stream (@iangreenleaf) -1.1.1 -===== +# 1.1.2 -* Speedup deflater (@loadhigh) -* Less Arrays and Strings allocations (@srawlins) -* Fix Zip64 writting support (@mrjamesriley) -* Fix StringIO support (@simonoff) -* Posibility to change default compression level -* Make Zip64 write support optional via configuration +- Fix compatibility of ::Zip::File.write_buffer -1.1.0 -===== +# 1.1.1 -* StringIO Support -* Zip64 Support -* Better jRuby Support -* Order of files in the archive can be sorted -* Other small fixes +- Speedup deflater (@loadhigh) +- Less Arrays and Strings allocations (@srawlins) +- Fix Zip64 writing support (@mrjamesriley) +- Fix StringIO support (@simonoff) +- Possibility to change default compression level +- Make Zip64 write support optional via configuration -1.0.0 -===== +# 1.1.0 -* Removed support for Ruby 1.8 -* Changed the API for gem. Now it can be used without require param in Gemfile. -* Added read-only support for Zip64 files. -* Added support for setting Unicode file names. +- StringIO Support +- Zip64 Support +- Better jRuby Support +- Order of files in the archive can be sorted +- Other small fixes -0.9.9 -===== +# 1.0.0 -* Added support for backslashes in zip files (generated by the default Windows zip packer for example) and comment sections with the comment length set to zero even though there is actually a comment. +- Removed support for Ruby 1.8 +- Changed the API for gem. Now it can be used without require param in Gemfile. +- Added read-only support for Zip64 files. +- Added support for setting Unicode file names. -0.9.8 -===== +# 0.9.9 -* Fixed: "Unitialized constant NullInputStream" error +- Added support for backslashes in zip files (generated by the default Windows zip packer for example) and comment sections with the comment length set to zero even though there is actually a comment. -0.9.5 -===== +# 0.9.8 -* Removed support for loading ruby in zip files (ziprequire.rb). +- Fixed: "Unitialized constant NullInputStream" error -0.9.4 -===== +# 0.9.5 -* Changed ZipOutputStream.put_next_entry signature (API CHANGE!). Now allows comment, extra field and compression method to be specified. +- Removed support for loading ruby in zip files (ziprequire.rb). -0.9.3 -===== +# 0.9.4 -* Fixed: Added ZipEntry::name_encoding which retrieves the character -encoding of the name and comment of the entry. -* Added convenience methods ZipEntry::name_in(enc) and ZipEntry::comment_in(enc) for -getting zip entry names and comments in a specified character -encoding. +- Changed ZipOutputStream.put_next_entry signature (API CHANGE!). Now allows comment, extra field and compression method to be specified. -0.9.2 -===== +# 0.9.3 -* Fixed: Renaming an entry failed if the entry's new name was a different length than its old name. (Diego Barros) +- Fixed: Added ZipEntry::name_encoding which retrieves the character encoding of the name and comment of the entry. +- Added convenience methods ZipEntry::name_in(enc) and ZipEntry::comment_in(enc) for getting zip entry names and comments in a specified character encoding. -0.9.1 -===== +# 0.9.2 -* Added symlink support and support for unix file permissions. Reduced memory usage during decompression. -* New methods ZipFile::[follow_symlinks, restore_times, restore_permissions, restore_ownership]. -* New methods ZipEntry::unix_perms, ZipInputStream::eof?. -* Added documentation and test for new ZipFile::extract. -* Added some of the API suggestions from sf.net #1281314. -* Applied patch for sf.net bug #1446926. -* Applied patch for sf.net bug #1459902. -* Rework ZipEntry and delegate classes. +- Fixed: Renaming an entry failed if the entry's new name was a different length than its old name. (Diego Barros) -0.5.12 -====== +# 0.9.1 -* Fixed problem with writing binary content to a ZipFile in MS Windows. +- Added symlink support and support for unix file permissions. Reduced memory usage during decompression. +- New methods ZipFile::[follow_symlinks, restore_times, restore_permissions, restore_ownership]. +- New methods ZipEntry::unix_perms, ZipInputStream::eof?. +- Added documentation and test for new ZipFile::extract. +- Added some of the API suggestions from sf.net #1281314. +- Applied patch for sf.net bug #1446926. +- Applied patch for sf.net bug #1459902. +- Rework ZipEntry and delegate classes. -0.5.11 -====== +# 0.5.12 -* Fixed name clash file method copy_stream from fileutils.rb. Fixed problem with references to constant CHUNK_SIZE. -* ZipInputStream/AbstractInputStream read is now buffered like ruby IO's read method, which means that read and gets etc can be mixed. The - unbuffered read method has been renamed to sysread. +- Fixed problem with writing binary content to a ZipFile in MS Windows. -0.5.10 -====== +# 0.5.11 -* Fixed method name resolution problem with FileUtils::copy_stream and IOExtras::copy_stream. +- Fixed name clash file method copy_stream from fileutils.rb. Fixed problem with references to constant CHUNK_SIZE. +- ZipInputStream/AbstractInputStream read is now buffered like ruby IO's read method, which means that read and gets etc can be mixed. The unbuffered read method has been renamed to sysread. -0.5.9 -===== +# 0.5.10 -* Fixed serious memory consumption issue +- Fixed method name resolution problem with FileUtils::copy_stream and IOExtras::copy_stream. -0.5.8 -===== +# 0.5.9 -* Fixed install script. +- Fixed serious memory consumption issue -0.5.7 -===== -* install.rb no longer assumes it is being run from the toplevel source -dir. Directory structure changed to reflect common ruby library -project structure. Migrated from RubyUnit to Test::Unit format. Now -uses Rake to build source packages and gems and run unit tests. +# 0.5.8 -0.5.6 -===== -* Fix for FreeBSD 4.9 which returns Errno::EFBIG instead of -Errno::EINVAL for some invalid seeks. Fixed 'version needed to -extract'-field incorrect in local headers. +- Fixed install script. -0.5.5 -===== +# 0.5.7 -* Fix for a problem with writing zip files that concerns only ruby 1.8.1. +- install.rb no longer assumes it is being run from the toplevel source dir. Directory structure changed to reflect common ruby library project structure. Migrated from RubyUnit to Test::Unit format. Now uses Rake to build source packages and gems and run unit tests. -0.5.4 -===== +# 0.5.6 -* Significantly reduced memory footprint when modifying zip files. +- Fix for FreeBSD 4.9 which returns Errno::EFBIG instead of Errno::EINVAL for some invalid seeks. Fixed 'version needed to extract'-field incorrect in local headers. -0.5.3 -===== -* Added optimization to avoid decompressing and recompressing individual -entries when modifying a zip archive. +# 0.5.5 -0.5.2 -===== -* Fixed ZipFile corruption bug in ZipFile class. Added basic unix -extra-field support. +- Fix for a problem with writing zip files that concerns only ruby 1.8.1. -0.5.1 -===== +# 0.5.4 -* Fixed ZipFile.get_output_stream bug. +- Significantly reduced memory footprint when modifying zip files. -0.5.0 -===== +# 0.5.3 -* Ruby 1.8.0 and ruby-zlib 0.6.0 compatibility -* Changed method names from camelCase to rubys underscore style. -* Installs to zip/ subdir instead of directly to site_ruby -* Added ZipFile.directory and ZipFile.file - each method return an -object that can be used like Dir and File only for the contents of the -zip file. -* Added sample application zipfind which works like Find.find, only -Zip::ZipFind.find traverses into zip archives too. -* FIX: AbstractInputStream.each_line with non-default separator +- Added optimization to avoid decompressing and recompressing individual entries when modifying a zip archive. +# 0.5.2 -0.5.0a -====== -Source reorganized. Added ziprequire, which can be used to load ruby -modules from a zip file, in a fashion similar to jar files in -Java. Added gtk_ruby_zip, another sample application. Implemented -ZipInputStream.lineno and ZipInputStream.rewind +- Fixed ZipFile corruption bug in ZipFile class. Added basic unix extra-field support. -Bug fixes: +# 0.5.1 + +- Fixed ZipFile.get_output_stream bug. -* Read and write date and time information correctly for zip entries. -* Fixed read() using separate buffer, causing mix of gets/readline/read to -cause problems. +# 0.5.0 -0.4.2 -===== +- Ruby 1.8.0 and ruby-zlib 0.6.0 compatibility +- Changed method names from camelCase to rubys underscore style. +- Installs to zip/ subdir instead of directly to site_ruby +- Added ZipFile.directory and ZipFile.file - each method return an + object that can be used like Dir and File only for the contents of the + zip file. +- Added sample application zipfind which works like Find.find, only + Zip::ZipFind.find traverses into zip archives too. +- FIX: AbstractInputStream.each_line with non-default separator -* Performance optimizations. Test suite runs in half the time. +# 0.5.0a + +Source reorganized. Added ziprequire, which can be used to load ruby modules from a zip file, in a fashion similar to jar files in Java. Added gtk_ruby_zip, another sample application. Implemented ZipInputStream.lineno and ZipInputStream.rewind + +Bug fixes: -0.4.1 -===== +- Read and write date and time information correctly for zip entries. +- Fixed read() using separate buffer, causing mix of gets/readline/read to cause problems. -* Windows compatibility fixes. +# 0.4.2 -0.4.0 -===== +- Performance optimizations. Test suite runs in half the time. -* Zip::ZipFile is now mutable and provides a more convenient way of -modifying zip archives than Zip::ZipOutputStream. Operations for -adding, extracting, renaming, replacing and removing entries to zip -archives are now available. +# 0.4.1 -* Runs without warnings with -w switch. +- Windows compatibility fixes. -* Install script install.rb added. +# 0.4.0 -0.3.1 -===== +- Zip::ZipFile is now mutable and provides a more convenient way of modifying zip archives than Zip::ZipOutputStream. Operations for adding, extracting, renaming, replacing and removing entries to zip archives are now available. +- Runs without warnings with -w switch. +- Install script install.rb added. -* Rudimentary support for writing zip archives. +# 0.3.1 -0.2.2 -===== +- Rudimentary support for writing zip archives. -* Fixed and extended unit test suite. Updated to work with ruby/zlib -0.5. It doesn't work with earlier versions of ruby/zlib. +# 0.2.2 -0.2.0 -===== +- Fixed and extended unit test suite. Updated to work with ruby/zlib 0.5. It doesn't work with earlier versions of ruby/zlib. -* Class ZipFile added. Where ZipInputStream is used to read the -individual entries in a zip file, ZipFile reads the central directory -in the zip archive, so you can get to any entry in the zip archive -without having to skipping through all the preceeding entries. +# 0.2.0 +- Class ZipFile added. Where ZipInputStream is used to read the individual entries in a zip file, ZipFile reads the central directory in the zip archive, so you can get to any entry in the zip archive without having to skipping through all the preceeding entries. -0.1.0 -===== +# 0.1.0 -* First working version of ZipInputStream. +- First working version of ZipInputStream. From 5152f6f7a0f5515d0fe1717d0c3dcb40c26ab2c9 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 7 Jul 2019 17:59:35 +0100 Subject: [PATCH 58/72] Put CI back to trusty Xenial is now the default. Trusty is now out of support but still not end of life. Also omit the ruby patch versions so we don't have to keep updating them. --- .travis.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 00f3b2d6..6b7d6e05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,14 @@ language: ruby +dist: trusty cache: bundler rvm: - - 2.0.0 - - 2.1.10 - - 2.2.10 - - 2.3.8 - - 2.4.5 - - 2.5.5 - - 2.6.2 + - 2.0 + - 2.1 + - 2.2 + - 2.3 + - 2.4 + - 2.5 + - 2.6 - ruby-head matrix: include: From b2573f6069ef1eecb440d23c93015dfa011d283a Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 7 Jul 2019 18:18:49 +0100 Subject: [PATCH 59/72] Use rbx-4 in CI --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6b7d6e05..d98d0e27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,12 +18,10 @@ matrix: jdk: openjdk7 - rvm: jruby-head jdk: oraclejdk8 - - rvm: rbx-3 - env: - - RUBYOPT="-rbundler/deprecate" + - rvm: rbx-4 allow_failures: - rvm: ruby-head - - rvm: rbx-3 + - rvm: rbx-4 - rvm: jruby-head before_install: - gem --version From fc23db2efc8ba7b39e5ef94ddbd0bf23a4d5ba5e Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 20 Jul 2019 15:06:17 +0100 Subject: [PATCH 60/72] Update changelog for #399 --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 57fccdf4..1240b3de 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Tooling / Documentation - Update `example_recursive.rb` in README [#397](https://github.com/rubyzip/rubyzip/pull/397) +- Fix CI on `trusty` for now, and automatically pick the latest ruby patch version [#399](https://github.com/rubyzip/rubyzip/pull/399) # 1.2.3 From 8dfc95dc79c93c0a4c10cf9407784bc736600564 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sat, 20 Jul 2019 15:15:30 +0100 Subject: [PATCH 61/72] Hold jruby at 9.1 on JDK 7 --- .travis.yml | 2 +- Changelog.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d98d0e27..358e2a8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ matrix: include: - rvm: jruby jdk: oraclejdk8 - - rvm: jruby + - rvm: jruby-9.1 jdk: openjdk7 - rvm: jruby-head jdk: oraclejdk8 diff --git a/Changelog.md b/Changelog.md index 1240b3de..8bb4f6dd 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,7 +5,7 @@ Tooling / Documentation - Update `example_recursive.rb` in README [#397](https://github.com/rubyzip/rubyzip/pull/397) -- Fix CI on `trusty` for now, and automatically pick the latest ruby patch version [#399](https://github.com/rubyzip/rubyzip/pull/399) +- Hold CI at `trusty` for now, automatically pick the latest ruby patch version, use rbx-4 and hold jruby at 9.1 [#399](https://github.com/rubyzip/rubyzip/pull/399) # 1.2.3 From eeef5073d58253e2044dbf81d1b205efd590b59a Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Thu, 5 Sep 2019 19:00:34 +0100 Subject: [PATCH 62/72] Add test case based on #146 --- test/file_test.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/file_test.rb b/test/file_test.rb index 3c52c778..f2d248e3 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -131,6 +131,15 @@ def test_close_buffer_with_io f.close end + def test_open_buffer_with_io_and_block + File.open('test/data/rubycode.zip') do |io| + io.set_encoding(Encoding::BINARY) # not strictly required but can be set + Zip::File.open_buffer(io) do |zip_io| + # left empty on purpose + end + end + end + def test_open_buffer_without_block string_io = StringIO.new File.read('test/data/rubycode.zip') zf = ::Zip::File.open_buffer string_io From 9a41ce65c432bf90e30824d7a6b60f9a75ccfe0d Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 6 Sep 2019 17:58:38 +0100 Subject: [PATCH 63/72] Add more explicit test for #280 --- test/file_test.rb | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/test/file_test.rb b/test/file_test.rb index f2d248e3..abe4e4a6 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -123,12 +123,40 @@ def test_close_buffer_with_stringio assert_nil zf.close end - def test_close_buffer_with_io - f = File.open('test/data/rubycode.zip') - zf = ::Zip::File.open_buffer f - refute zf.commit_required? - assert_nil zf.close - f.close + def test_open_buffer_no_op_does_not_change_file + Dir.mktmpdir do |tmp| + test_zip = File.join(tmp, 'test.zip') + FileUtils.cp 'test/data/rubycode.zip', test_zip + + # Note: this may change the file if it is opened with r+b instead of rb. + # The 'extra fields' in this particular zip file get reordered. + File.open(test_zip, 'rb') do |file| + Zip::File.open_buffer(file) do |zf| + nil # do nothing + end + end + + assert_equal \ + File.binread('test/data/rubycode.zip'), + File.binread(test_zip) + end + end + + def test_open_buffer_close_does_not_change_file + Dir.mktmpdir do |tmp| + test_zip = File.join(tmp, 'test.zip') + FileUtils.cp 'test/data/rubycode.zip', test_zip + + File.open(test_zip, 'rb') do |file| + zf = Zip::File.open_buffer(file) + refute zf.commit_required? + assert_nil zf.close + end + + assert_equal \ + File.binread('test/data/rubycode.zip'), + File.binread(test_zip) + end end def test_open_buffer_with_io_and_block From 0d85cb6a49cce7ef51186e64c8f3f147d0fd2b72 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Fri, 6 Sep 2019 18:01:30 +0100 Subject: [PATCH 64/72] Bump to 1.2.4 --- Changelog.md | 4 ++++ lib/zip/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 8bb4f6dd..50fc6e5b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,10 @@ - +# 1.2.4 (2019-09-06) + +- Do not rewrite zip files opened with `open_buffer` that have not changed [#360](https://github.com/rubyzip/rubyzip/pull/360) + Tooling / Documentation - Update `example_recursive.rb` in README [#397](https://github.com/rubyzip/rubyzip/pull/397) diff --git a/lib/zip/version.rb b/lib/zip/version.rb index 4d6ab8b3..afbccee8 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '1.2.3' + VERSION = '1.2.4' end From 72e7ca0d04de580e31717555db20d340c69e68de Mon Sep 17 00:00:00 2001 From: Orien Madgwick <_@orien.io> Date: Thu, 12 Sep 2019 12:56:00 +1000 Subject: [PATCH 65/72] Add project metadata to the gemspec As per https://guides.rubygems.org/specification-reference/#metadata, add metadata to the gemspec file. This'll allow people to more easily access the source code, raise issues and read the changelog. These `bug_tracker_uri`, `changelog_uri`, `documentation_uri`, `wiki_uri` and `source_code_uri` links will appear on the rubygems page at https://rubygems.org/gems/rubyzip and be available via the rubygems API after the next release. --- rubyzip.gemspec | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rubyzip.gemspec b/rubyzip.gemspec index 4ca36c2d..6b873752 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -16,6 +16,13 @@ Gem::Specification.new do |s| s.test_files = Dir.glob('test/**/*') s.require_paths = ['lib'] s.license = 'BSD 2-Clause' + s.metadata = { + 'bug_tracker_uri' => 'https://github.com/rubyzip/rubyzip/issues', + 'changelog_uri' => "https://github.com/rubyzip/rubyzip/blob/v#{s.version}/Changelog.md", + 'documentation_uri' => "https://www.rubydoc.info/gems/rubyzip/#{s.version}", + 'source_code_uri' => "https://github.com/rubyzip/rubyzip/tree/v#{s.version}", + 'wiki_uri' => 'https://github.com/rubyzip/rubyzip/wiki' + } s.required_ruby_version = '>= 1.9.2' s.add_development_dependency 'rake', '~> 10.3' s.add_development_dependency 'pry', '~> 0.10' From ecb277621852589ecc1557f228665a5338ac0809 Mon Sep 17 00:00:00 2001 From: Robert Haines Date: Sun, 20 May 2018 15:34:55 +0100 Subject: [PATCH 66/72] Zip::File.add_stored() to add uncompressed files. Adding uncompressed files to a zip archive can be overly complex, so this convenience method makes it easier. --- lib/zip/file.rb | 7 +++++++ test/file_test.rb | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/zip/file.rb b/lib/zip/file.rb index b5b85eea..9c7f3cbd 100644 --- a/lib/zip/file.rb +++ b/lib/zip/file.rb @@ -287,6 +287,13 @@ def add(entry, src_path, &continue_on_exists_proc) @entry_set << new_entry end + # Convenience method for adding the contents of a file to the archive + # in Stored format (uncompressed) + def add_stored(entry, src_path, &continue_on_exists_proc) + entry = ::Zip::Entry.new(@name, entry.to_s, nil, nil, nil, nil, ::Zip::Entry::STORED) + add(entry, src_path, &continue_on_exists_proc) + end + # Removes the specified entry. def remove(entry) @entry_set.delete(get_entry(entry)) diff --git a/test/file_test.rb b/test/file_test.rb index abe4e4a6..3b53c2b9 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -204,6 +204,25 @@ def test_add zfRead.get_input_stream(entryName) { |zis| zis.read }) end + def test_add_stored + srcFile = 'test/data/file2.txt' + entryName = 'newEntryName.rb' + assert(::File.exist?(srcFile)) + zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE) + zf.add_stored(entryName, srcFile) + zf.close + + zfRead = ::Zip::File.new(EMPTY_FILENAME) + entry = zfRead.entries.first + assert_equal('', zfRead.comment) + assert_equal(1, zfRead.entries.length) + assert_equal(entryName, entry.name) + assert_equal(entry.size, entry.compressed_size) + assert_equal(::Zip::Entry::STORED, entry.compression_method) + AssertEntry.assert_contents(srcFile, + zfRead.get_input_stream(entryName) { |zis| zis.read }) + end + def test_recover_permissions_after_add_files_to_archive srcZip = TEST_ZIP.zip_name ::File.chmod(0o664, srcZip) From 93505ca16f0444bdb04f88f4b8f820ae5d628353 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 15 Sep 2019 14:58:13 +0100 Subject: [PATCH 67/72] Check expected entry size in add_stored test --- test/file_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/file_test.rb b/test/file_test.rb index 3b53c2b9..94ff769c 100644 --- a/test/file_test.rb +++ b/test/file_test.rb @@ -217,6 +217,7 @@ def test_add_stored assert_equal('', zfRead.comment) assert_equal(1, zfRead.entries.length) assert_equal(entryName, entry.name) + assert_equal(File.size(srcFile), entry.size) assert_equal(entry.size, entry.compressed_size) assert_equal(::Zip::Entry::STORED, entry.compression_method) AssertEntry.assert_contents(srcFile, From 94b7fa276992933592d69eb6bb17fc09105f8395 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 15 Sep 2019 15:03:19 +0100 Subject: [PATCH 68/72] [ci skip] Update changelog --- Changelog.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 50fc6e5b..e8a7e16b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,10 @@ # X.X.X (Next) -- +- Add `add_stored` method to simplify adding entries without compression [#366](https://github.com/rubyzip/rubyzip/pull/366) + +Tooling / Documentation + +- Add more gem metadata links [#402](https://github.com/rubyzip/rubyzip/pull/402) # 1.2.4 (2019-09-06) From 4167f0ce67e42b082605bca75c7bdfd01eb23804 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Thu, 12 Sep 2019 22:01:38 +0100 Subject: [PATCH 69/72] Validate entry sizes when extracting --- README.md | 67 +++++++++++++++++++++++++++++++-------- lib/zip.rb | 4 ++- lib/zip/entry.rb | 7 ++++ lib/zip/errors.rb | 1 + test/file_extract_test.rb | 62 ++++++++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8255cd90..2ff41ed9 100644 --- a/README.md +++ b/README.md @@ -152,12 +152,15 @@ When modifying a zip archive the file permissions of the archive are preserved. ### Reading a Zip file ```ruby +MAX_SIZE = 1024**2 # 1MiB (but of course you can increase this) Zip::File.open('foo.zip') do |zip_file| # Handle entries one by one zip_file.each do |entry| - # Extract to file/directory/symlink puts "Extracting #{entry.name}" - entry.extract(dest_file) + raise 'File too large when extracted' if entry.size > MAX_SIZE + + # Extract to file or directory based on name in the archive + entry.extract # Read into memory content = entry.get_input_stream.read @@ -165,6 +168,7 @@ Zip::File.open('foo.zip') do |zip_file| # Find specific entry entry = zip_file.glob('*.csv').first + raise 'File too large when extracted' if entry.size > MAX_SIZE puts entry.get_input_stream.read end ``` @@ -219,6 +223,8 @@ File.open(new_path, "wb") {|f| f.write(buffer.string) } ## Configuration +### Existing Files + By default, rubyzip will not overwrite files if they already exist inside of the extracted path. To change this behavior, you may specify a configuration option like so: ```ruby @@ -233,18 +239,57 @@ Additionally, if you want to configure rubyzip to overwrite existing files while Zip.continue_on_exists_proc = true ``` +### Non-ASCII Names + If you want to store non-english names and want to open them on Windows(pre 7) you need to set this option: ```ruby Zip.unicode_names = true ``` +Sometimes file names inside zip contain non-ASCII characters. If you can assume which encoding was used for such names and want to be able to find such entries using `find_entry` then you can force assumed encoding like so: + +```ruby +Zip.force_entry_names_encoding = 'UTF-8' +``` + +Allowed encoding names are the same as accepted by `String#force_encoding` + +### Date Validation + Some zip files might have an invalid date format, which will raise a warning. You can hide this warning with the following setting: ```ruby Zip.warn_invalid_date = false ``` +### Size Validation + +By default, `rubyzip`'s `extract` method checks that an entry's reported uncompressed size is not (significantly) smaller than its actual size. This is to help you protect your application against [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). Before `extract`ing an entry, you should check that its size is in the range you expect. For example, if your application supports processing up to 100 files at once, each up to 10MiB, your zip extraction code might look like: + +```ruby +MAX_FILE_SIZE = 10 * 1024**2 # 10MiB +MAX_FILES = 100 +Zip::File.open('foo.zip') do |zip_file| + num_files = 0 + zip_file.each do |entry| + num_files += 1 if entry.file? + raise 'Too many extracted files' if num_files > MAX_FILES + raise 'File too large when extracted' if entry.size > MAX_FILE_SIZE + entry.extract + end +end +``` + +If you need to extract zip files that report incorrect uncompressed sizes and you really trust them not too be too large, you can disable this setting with +```ruby +Zip.validate_entry_sizes = false +``` + +Note that if you use the lower level `Zip::InputStream` interface, `rubyzip` does *not* check the entry `size`s. In this case, the caller is responsible for making sure it does not read more data than expected from the input stream. + +### Default Compression + You can set the default compression level like so: ```ruby @@ -253,13 +298,17 @@ Zip.default_compression = Zlib::DEFAULT_COMPRESSION It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION` -Sometimes file names inside zip contain non-ASCII characters. If you can assume which encoding was used for such names and want to be able to find such entries using `find_entry` then you can force assumed encoding like so: +### Zip64 Support + +By default, Zip64 support is disabled for writing. To enable it do this: ```ruby -Zip.force_entry_names_encoding = 'UTF-8' +Zip.write_zip64_support = true ``` -Allowed encoding names are the same as accepted by `String#force_encoding` +_NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive. + +### Block Form You can set multiple settings at the same time by using a block: @@ -272,14 +321,6 @@ You can set multiple settings at the same time by using a block: end ``` -By default, Zip64 support is disabled for writing. To enable it do this: - -```ruby -Zip.write_zip64_support = true -``` - -_NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive. - ## Developing To run the test you need to do this: diff --git a/lib/zip.rb b/lib/zip.rb index 9145207b..c3a6ed5e 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -42,7 +42,8 @@ module Zip :write_zip64_support, :warn_invalid_date, :case_insensitive_match, - :force_entry_names_encoding + :force_entry_names_encoding, + :validate_entry_sizes def reset! @_ran_once = false @@ -54,6 +55,7 @@ def reset! @write_zip64_support = false @warn_invalid_date = true @case_insensitive_match = false + @validate_entry_sizes = true end def setup diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 80160b57..bd3e4f34 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -603,9 +603,16 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi get_input_stream do |is| set_extra_attributes_on_path(dest_path) + bytes_written = 0 buf = ''.dup while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)) os << buf + + next unless ::Zip.validate_entry_sizes + bytes_written += buf.bytesize + if bytes_written > size + raise ::Zip::EntrySizeError, "Entry #{name} should be #{size}B but is larger when inflated" + end end end end diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index b2bcccd2..364c6eee 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -4,6 +4,7 @@ class EntryExistsError < Error; end class DestinationFileExistsError < Error; end class CompressionMethodError < Error; end class EntryNameError < Error; end + class EntrySizeError < Error; end class InternalError < Error; end class GPFBit3Error < Error; end diff --git a/test/file_extract_test.rb b/test/file_extract_test.rb index 57833fcb..6103aeae 100644 --- a/test/file_extract_test.rb +++ b/test/file_extract_test.rb @@ -10,6 +10,10 @@ def setup ::File.delete(EXTRACTED_FILENAME) if ::File.exist?(EXTRACTED_FILENAME) end + def teardown + ::Zip.reset! + end + def test_extract ::Zip::File.open(TEST_ZIP.zip_name) do |zf| zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) @@ -80,4 +84,62 @@ def test_extract_non_entry_2 end assert(!File.exist?(outFile)) end + + def test_extract_incorrect_size + # The uncompressed size fields in the zip file cannot be trusted. This makes + # it harder for callers to validate the sizes of the files they are + # extracting, which can lead to denial of service. See also + # https://en.wikipedia.org/wiki/Zip_bomb + Dir.mktmpdir do |tmp| + real_zip = File.join(tmp, 'real.zip') + fake_zip = File.join(tmp, 'fake.zip') + file_name = 'a' + true_size = 500_000 + fake_size = 1 + + ::Zip::File.open(real_zip, ::Zip::File::CREATE) do |zf| + zf.get_output_stream(file_name) do |os| + os.write 'a' * true_size + end + end + + compressed_size = nil + ::Zip::File.open(real_zip) do |zf| + a_entry = zf.find_entry(file_name) + compressed_size = a_entry.compressed_size + assert_equal true_size, a_entry.size + end + + true_size_bytes = [compressed_size, true_size, file_name.size].pack('LLS') + fake_size_bytes = [compressed_size, fake_size, file_name.size].pack('LLS') + + data = File.binread(real_zip) + assert data.include?(true_size_bytes) + data.gsub! true_size_bytes, fake_size_bytes + + File.open(fake_zip, 'wb') do |file| + file.write data + end + + Dir.chdir tmp do + ::Zip::File.open(fake_zip) do |zf| + a_entry = zf.find_entry(file_name) + assert_equal fake_size, a_entry.size + + ::Zip.validate_entry_sizes = false + a_entry.extract + assert_equal true_size, File.size(file_name) + FileUtils.rm file_name + + ::Zip.validate_entry_sizes = true + error = assert_raises ::Zip::EntrySizeError do + a_entry.extract + end + assert_equal \ + 'Entry a should be 1B but is larger when inflated', + error.message + end + end + end + end end From 7849f7362ab0cd23d5730ef8b6f2c39252da2285 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Sun, 15 Sep 2019 15:23:35 +0100 Subject: [PATCH 70/72] Default validate_entry_sizes to false for 1.3 release --- Changelog.md | 11 +++++++++++ README.md | 8 +++++++- lib/zip.rb | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index e8a7e16b..45f14333 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,16 @@ # X.X.X (Next) +- + +# 1.3.0 (Next) + +Security + +- Add `validate_entry_sizes` option so that callers can trust an entry's reported size when using `extract` [#403](https://github.com/rubyzip/rubyzip/pull/403) + - This option defaults to `false` for backward compatibility in this release, but you are strongly encouraged to set it to `true`. It will default to `true` in rubyzip 2.0. + +New Feature + - Add `add_stored` method to simplify adding entries without compression [#366](https://github.com/rubyzip/rubyzip/pull/366) Tooling / Documentation diff --git a/README.md b/README.md index 2ff41ed9..51b275b9 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,13 @@ Zip.warn_invalid_date = false ### Size Validation -By default, `rubyzip`'s `extract` method checks that an entry's reported uncompressed size is not (significantly) smaller than its actual size. This is to help you protect your application against [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). Before `extract`ing an entry, you should check that its size is in the range you expect. For example, if your application supports processing up to 100 files at once, each up to 10MiB, your zip extraction code might look like: +**This setting defaults to `false` in rubyzip 1.3 for backward compatibility, but it will default to `true` in rubyzip 2.0.** + +If you set +``` +Zip.validate_entry_sizes = true +``` +then `rubyzip`'s `extract` method checks that an entry's reported uncompressed size is not (significantly) smaller than its actual size. This is to help you protect your application against [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). Before `extract`ing an entry, you should check that its size is in the range you expect. For example, if your application supports processing up to 100 files at once, each up to 10MiB, your zip extraction code might look like: ```ruby MAX_FILE_SIZE = 10 * 1024**2 # 10MiB diff --git a/lib/zip.rb b/lib/zip.rb index c3a6ed5e..eeac96a0 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -55,7 +55,7 @@ def reset! @write_zip64_support = false @warn_invalid_date = true @case_insensitive_match = false - @validate_entry_sizes = true + @validate_entry_sizes = false end def setup From 97cb6aefe6d12bd2429d7a2e119ccb26f259d71d Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Wed, 18 Sep 2019 18:34:23 +0100 Subject: [PATCH 71/72] Warn when an entry size is invalid --- lib/zip/entry.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index bd3e4f34..677e49ef 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -604,14 +604,19 @@ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exi set_extra_attributes_on_path(dest_path) bytes_written = 0 + warned = false buf = ''.dup while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)) os << buf - - next unless ::Zip.validate_entry_sizes bytes_written += buf.bytesize - if bytes_written > size - raise ::Zip::EntrySizeError, "Entry #{name} should be #{size}B but is larger when inflated" + if bytes_written > size && !warned + message = "Entry #{name} should be #{size}B but is larger when inflated" + if ::Zip.validate_entry_sizes + raise ::Zip::EntrySizeError, message + else + puts "WARNING: #{message}" + warned = true + end end end end From 7c65e1e3595031392f1050b81fb2b95b0f2ee764 Mon Sep 17 00:00:00 2001 From: John Lees-Miller Date: Wed, 25 Sep 2019 19:58:16 +0100 Subject: [PATCH 72/72] Bump version to 1.3.0 --- Changelog.md | 2 +- lib/zip/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 45f14333..36ae1009 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,7 +2,7 @@ - -# 1.3.0 (Next) +# 1.3.0 (2019-09-25) Security diff --git a/lib/zip/version.rb b/lib/zip/version.rb index afbccee8..37fba090 100644 --- a/lib/zip/version.rb +++ b/lib/zip/version.rb @@ -1,3 +1,3 @@ module Zip - VERSION = '1.2.4' + VERSION = '1.3.0' end