class Zip::Entry

Constants

DEFLATED
EFS

Language encoding flag (EFS) bit

STORED

Attributes

comment[RW]
compressed_size[RW]
compression_method[RW]
crc[RW]
dirty[RW]
external_file_attributes[RW]
extra[RW]
fstype[RW]
gp_flags[RW]
header_signature[RW]
internal_file_attributes[RW]
local_header_offset[RW]
name[RW]
restore_ownership[RW]
restore_permissions[RW]
restore_times[RW]
size[RW]
unix_gid[RW]
unix_perms[RW]
unix_uid[RW]
zipfile[RW]

Public Class Methods

new(*args) click to toggle source
# File lib/zip/entry.rb, line 55
def initialize(*args)
  name = args[1] || ''
  check_name(name)

  set_default_vars_values
  @fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX

  @zipfile            = args[0] || ''
  @name               = name
  @comment            = args[2] || ''
  @extra              = args[3] || ''
  @compressed_size    = args[4] || 0
  @crc                = args[5] || 0
  @compression_method = args[6] || ::Zip::Entry::DEFLATED
  @size               = args[7] || 0
  @time               = args[8] || ::Zip::DOSTime.now

  @ftype = name_is_directory? ? :directory : :file
  @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.kind_of?(::Zip::ExtraField)
end
read_local_entry(io) click to toggle source
# File lib/zip/entry.rb, line 221
def read_local_entry(io)
  entry = new(io)
  entry.read_local_entry(io)
  entry
rescue Error
  nil
end

Public Instance Methods

<=>(other) click to toggle source
# File lib/zip/entry.rb, line 512
def <=>(other)
  to_s <=> other.to_s
end
==(other) click to toggle source
# File lib/zip/entry.rb, line 502
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|
    other.__send__(k.to_sym) == __send__(k.to_sym)
  end
  keys_equal && time.dos_equals(other.time)
end
check_c_dir_entry_comment_size() click to toggle source
# File lib/zip/entry.rb, line 376
def check_c_dir_entry_comment_size
  return if @comment && @comment.bytesize == @comment_length

  raise ::Zip::Error, 'Truncated cdir zip entry header'
end
check_c_dir_entry_signature() click to toggle source
# File lib/zip/entry.rb, line 370
def check_c_dir_entry_signature
  return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE

  raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
end
check_c_dir_entry_static_header_length(buf) click to toggle source
# File lib/zip/entry.rb, line 364
def check_c_dir_entry_static_header_length(buf)
  return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH

  raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
end
check_name(name) click to toggle source
# File lib/zip/entry.rb, line 49
def check_name(name)
  return unless name.start_with?('/')

  raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
end
clean_up() click to toggle source
# File lib/zip/entry.rb, line 603
def clean_up
  # By default, do nothing
end
comment_size() click to toggle source
# File lib/zip/entry.rb, line 147
def comment_size
  @comment ? @comment.bytesize : 0
end
encrypted?() click to toggle source
# File lib/zip/entry.rb, line 76
def encrypted?
  gp_flags & 1 == 1
end
extra_size() click to toggle source
# File lib/zip/entry.rb, line 143
def extra_size
  @extra ? @extra.local_size : 0
end
extract(dest_path = nil, &block) click to toggle source

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.

# File lib/zip/entry.rb, line 176
def extract(dest_path = nil, &block)
  if dest_path.nil? && !name_safe?
    warn "WARNING: skipped '#{@name}' as unsafe."
    return self
  end

  dest_path ||= @name
  block ||= proc { ::Zip.on_exists_proc }

  raise "unknown file type #{inspect}" unless directory? || file? || symlink?

  __send__("create_#{@ftype}", dest_path, &block)
  self
end
file_type_is?(type) click to toggle source
# File lib/zip/entry.rb, line 106
def file_type_is?(type)
  raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype

  @ftype == type
end
get_input_stream() { |NullInputStream| ... } click to toggle source

Returns an IO like object for the given ZipEntry. Warning: may behave weird with symlinks.

# File lib/zip/entry.rb, line 518
def get_input_stream(&block)
  if @ftype == :directory
    yield ::Zip::NullInputStream if block_given?
    ::Zip::NullInputStream
  elsif @filepath
    case @ftype
    when :file
      ::File.open(@filepath, 'rb', &block)
    when :symlink
      linkpath = ::File.readlink(@filepath)
      stringio = ::StringIO.new(linkpath)
      yield(stringio) if block_given?
      stringio
    else
      raise "unknown @file_type #{@ftype}"
    end
  else
    zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
    zis.instance_variable_set(:@complete_entry, self)
    zis.get_next_entry
    if block_given?
      begin
        yield(zis)
      ensure
        zis.close
      end
    else
      zis
    end
  end
end
get_raw_input_stream() { |zipfile| ... } click to toggle source
# File lib/zip/entry.rb, line 595
def get_raw_input_stream(&block)
  if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
    yield @zipfile
  else
    ::File.open(@zipfile, 'rb', &block)
  end
end
incomplete?() click to toggle source
# File lib/zip/entry.rb, line 80
def incomplete?
  gp_flags & 8 == 8
end
mtime()
Alias for: time
name_safe?() click to toggle source

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.

# File lib/zip/entry.rb, line 126
def name_safe?
  cleanpath = Pathname.new(@name).cleanpath
  return false unless cleanpath.relative?

  root = ::File::SEPARATOR
  naive_expanded_path = ::File.join(root, cleanpath.to_s)
  ::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
end
name_size() click to toggle source
# File lib/zip/entry.rb, line 139
def name_size
  @name ? @name.bytesize : 0
end
pack_c_dir_entry() click to toggle source
# File lib/zip/entry.rb, line 447
def pack_c_dir_entry
  zip64 = @extra['Zip64']
  [
    @header_signature,
    @version, # version of encoding software
    @fstype, # filesystem type
    @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
    @crc,
    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
    @internal_file_attributes, # file type (binary=0, text=1)
    @external_file_attributes, # native filesystem attributes
    zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
    @name,
    @extra,
    @comment
  ].pack('VCCvvvvvVVVvvvvvVV')
end
pack_local_entry() click to toggle source
# File lib/zip/entry.rb, line 284
def pack_local_entry
  zip64 = @extra['Zip64']
  [::Zip::LOCAL_ENTRY_SIGNATURE,
   @version_needed_to_extract, # version needed to extract
   @gp_flags, # @gp_flags
   @compression_method,
   @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,
   name_size,
   @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
end
parent_as_string() click to toggle source
# File lib/zip/entry.rb, line 589
def parent_as_string
  entry_name  = name.chomp('/')
  slash_index = entry_name.rindex('/')
  slash_index ? entry_name.slice(0, slash_index + 1) : nil
end
read_c_dir_extra_field(io) click to toggle source
# File lib/zip/entry.rb, line 382
def read_c_dir_extra_field(io)
  if @extra.kind_of?(::Zip::ExtraField)
    @extra.merge(io.read(@extra_length))
  else
    @extra = ::Zip::ExtraField.new(io.read(@extra_length))
  end
end
set_default_vars_values() click to toggle source
# File lib/zip/entry.rb, line 18
def set_default_vars_values
  @local_header_offset      = 0
  @local_header_size        = nil # not known until local entry is created or read
  @internal_file_attributes = 1
  @external_file_attributes = 0
  @header_signature         = ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE

  @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT
  @version                   = VERSION_MADE_BY

  @ftype           = nil          # unspecified or unknown
  @filepath        = nil
  @gp_flags        = 0
  if ::Zip.unicode_names
    @gp_flags |= EFS
    @version = 63
  end
  @follow_symlinks = false

  @restore_times       = false
  @restore_permissions = false
  @restore_ownership   = false
  # BUG: need an extra field to support uid/gid's
  @unix_uid            = nil
  @unix_gid            = nil
  @unix_perms          = nil
  # @posix_acl = nil
  # @ntfs_acl = nil
  @dirty               = false
end
set_ftype_from_c_dir_entry() click to toggle source
# File lib/zip/entry.rb, line 335
def set_ftype_from_c_dir_entry
  @ftype = case @fstype
           when ::Zip::FSTYPE_UNIX
             @unix_perms = (@external_file_attributes >> 16) & 0o7777
             case (@external_file_attributes >> 28)
             when ::Zip::FILE_TYPE_DIR
               :directory
             when ::Zip::FILE_TYPE_FILE
               :file
             when ::Zip::FILE_TYPE_SYMLINK
               :symlink
             else
               # best case guess for whether it is a file or not
               # Otherwise this would be set to unknown and that entry would never be able to extracted
               if name_is_directory?
                 :directory
               else
                 :file
               end
             end
           else
             if name_is_directory?
               :directory
             else
               :file
             end
           end
end
set_unix_attributes_on_path(dest_path) click to toggle source
# File lib/zip/entry.rb, line 425
def set_unix_attributes_on_path(dest_path)
  # ignore setuid/setgid bits by default.  honor 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

  # Restore the timestamp on a file. This will either have come from the
  # original source file that was copied into the archive, or from the
  # creation date of the archive if there was no original source file.
  ::FileUtils.touch(dest_path, mtime: time) if @restore_times
end
time() click to toggle source
# File lib/zip/entry.rb, line 84
def time
  if @extra['UniversalTime']
    @extra['UniversalTime'].mtime
  elsif @extra['NTFS']
    @extra['NTFS'].mtime
  else
    # Standard time field in central directory has local time
    # under archive creator. Then, we can't get timezone.
    @time
  end
end
Also aliased as: mtime
time=(value) click to toggle source
# File lib/zip/entry.rb, line 98
def time=(value)
  unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
    @extra.create('UniversalTime')
  end
  (@extra['UniversalTime'] || @extra['NTFS']).mtime = value
  @time = value
end
to_s() click to toggle source
# File lib/zip/entry.rb, line 191
def to_s
  @name
end
unpack_c_dir_entry(buf) click to toggle source
# File lib/zip/entry.rb, line 311
def unpack_c_dir_entry(buf)
  @header_signature,
    @version, # version of encoding software
    @fstype, # filesystem type
    @version_needed_to_extract,
    @gp_flags,
    @compression_method,
    @last_mod_time,
    @last_mod_date,
    @crc,
    @compressed_size,
    @size,
    @name_length,
    @extra_length,
    @comment_length,
    _, # diskNumberStart
    @internal_file_attributes,
    @external_file_attributes,
    @local_header_offset,
    @name,
    @extra,
    @comment = buf.unpack('VCCvvvvvVVVvvvvvVV')
end
unpack_local_entry(buf) click to toggle source
# File lib/zip/entry.rb, line 230
def unpack_local_entry(buf)
  @header_signature,
    @version,
    @fstype,
    @gp_flags,
    @compression_method,
    @last_mod_time,
    @last_mod_date,
    @crc,
    @compressed_size,
    @size,
    @name_length,
    @extra_length = buf.unpack('VCCvvvvVVVvv')
end
verify_local_header_size!() click to toggle source

check before rewriting an entry (after file sizes are known) that we didn’t change the header size (and thus clobber file data or something)

# File lib/zip/entry.rb, line 157
def verify_local_header_size!
  return if @local_header_size.nil?

  new_size = calculate_local_header_size
  raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size
end

Private Instance Methods

create_directory(dest_path) { |self, dest_path| ... } click to toggle source
# File lib/zip/entry.rb, line 642
def create_directory(dest_path)
  return if ::File.directory?(dest_path)

  if ::File.exist?(dest_path)
    if block_given? && yield(self, dest_path)
      ::FileUtils.rm_f dest_path
    else
      raise ::Zip::DestinationFileExistsError,
            "Cannot create directory '#{dest_path}'. " \
                'A file already exists with that name'
    end
  end
  ::FileUtils.mkdir_p(dest_path)
  set_extra_attributes_on_path(dest_path)
end
create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc }) { |self, dest_path| ... } click to toggle source
# File lib/zip/entry.rb, line 615
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
  if ::File.exist?(dest_path) && !yield(self, dest_path)
    raise ::Zip::DestinationFileExistsError,
          "Destination '#{dest_path}' already exists"
  end
  ::File.open(dest_path, 'wb') do |os|
    get_input_stream do |is|
      bytes_written = 0
      warned = false
      buf = +''
      while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
        os << buf
        bytes_written += buf.bytesize
        next unless bytes_written > size && !warned

        message = "entry '#{name}' should be #{size}B, but is larger when inflated."
        raise ::Zip::EntrySizeError, message if ::Zip.validate_entry_sizes

        warn "WARNING: #{message}"
        warned = true
      end
    end
  end

  set_extra_attributes_on_path(dest_path)
end
data_descriptor_size() click to toggle source
# File lib/zip/entry.rb, line 677
def data_descriptor_size
  (@gp_flags & 0x0008) > 0 ? 16 : 0
end
set_time(binary_dos_date, binary_dos_time) click to toggle source
# File lib/zip/entry.rb, line 609
def set_time(binary_dos_date, binary_dos_time)
  @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
rescue ArgumentError
  warn 'WARNING: invalid date/time in zip entry.' if ::Zip.warn_invalid_date
end