104 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			104 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
# frozen_string_literal: true
 | 
						|
 | 
						|
module SafeZip
 | 
						|
  class Entry
 | 
						|
    attr_reader :zip_archive, :zip_entry
 | 
						|
    attr_reader :path, :params
 | 
						|
 | 
						|
    def initialize(zip_archive, zip_entry, params)
 | 
						|
      @zip_archive = zip_archive
 | 
						|
      @zip_entry = zip_entry
 | 
						|
      @params = params
 | 
						|
      @path = ::File.expand_path(zip_entry.name, params.extract_path)
 | 
						|
    end
 | 
						|
 | 
						|
    def path_dir
 | 
						|
      ::File.dirname(path)
 | 
						|
    end
 | 
						|
 | 
						|
    def real_path_dir
 | 
						|
      ::File.realpath(path_dir)
 | 
						|
    end
 | 
						|
 | 
						|
    def exist?
 | 
						|
      ::File.exist?(path)
 | 
						|
    end
 | 
						|
 | 
						|
    def extract
 | 
						|
      # do not extract if file is not part of target directory or target file
 | 
						|
      return false unless matching_target_directory || matching_target_file
 | 
						|
 | 
						|
      # do not overwrite existing file
 | 
						|
      raise SafeZip::Extract::AlreadyExistsError, "File already exists #{zip_entry.name}" if exist?
 | 
						|
 | 
						|
      create_path_dir
 | 
						|
 | 
						|
      if zip_entry.file?
 | 
						|
        extract_file
 | 
						|
      elsif zip_entry.directory?
 | 
						|
        extract_dir
 | 
						|
      elsif zip_entry.symlink?
 | 
						|
        extract_symlink
 | 
						|
      else
 | 
						|
        raise SafeZip::Extract::UnsupportedEntryError, "File #{zip_entry.name} cannot be extracted"
 | 
						|
      end
 | 
						|
    rescue SafeZip::Extract::Error
 | 
						|
      raise
 | 
						|
    rescue Zip::EntrySizeError => e
 | 
						|
      raise SafeZip::Extract::EntrySizeError, e.message
 | 
						|
    rescue StandardError => e
 | 
						|
      raise SafeZip::Extract::ExtractError, e.message
 | 
						|
    end
 | 
						|
 | 
						|
    private
 | 
						|
 | 
						|
    def extract_file
 | 
						|
      zip_archive.extract(zip_entry, path)
 | 
						|
    end
 | 
						|
 | 
						|
    def extract_dir
 | 
						|
      FileUtils.mkdir(path)
 | 
						|
    end
 | 
						|
 | 
						|
    def extract_symlink
 | 
						|
      source_path = read_symlink
 | 
						|
      real_source_path = expand_symlink(source_path)
 | 
						|
 | 
						|
      # ensure that source path of symlink is within target directories
 | 
						|
      unless real_source_path.start_with?(matching_target_directory)
 | 
						|
        raise SafeZip::Extract::PermissionDeniedError, "Symlink cannot be created targeting: #{source_path}"
 | 
						|
      end
 | 
						|
 | 
						|
      ::File.symlink(source_path, path)
 | 
						|
    end
 | 
						|
 | 
						|
    def create_path_dir
 | 
						|
      # Create all directories, but ignore permissions
 | 
						|
      FileUtils.mkdir_p(path_dir)
 | 
						|
 | 
						|
      # disallow to make path dirs to point to another directories
 | 
						|
      unless path_dir == real_path_dir
 | 
						|
        raise SafeZip::Extract::PermissionDeniedError, "Directory of #{zip_entry.name} points to another directory"
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    def matching_target_directory
 | 
						|
      params.matching_target_directory(path)
 | 
						|
    end
 | 
						|
 | 
						|
    def matching_target_file
 | 
						|
      params.matching_target_file(path)
 | 
						|
    end
 | 
						|
 | 
						|
    def read_symlink
 | 
						|
      zip_archive.read(zip_entry)
 | 
						|
    end
 | 
						|
 | 
						|
    def expand_symlink(source_path)
 | 
						|
      ::File.realpath(source_path, path_dir)
 | 
						|
    rescue StandardError
 | 
						|
      raise SafeZip::Extract::SymlinkSourceDoesNotExistError, "Symlink source #{source_path} does not exist"
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 |