summaryrefslogtreecommitdiff
path: root/lib/safe_zip/entry.rb
blob: 664e2f52f914abc3e320edb6c5e8c6e2cc8d9417 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# 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
      return false unless matching_target_directory

      # 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 => 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 read_symlink
      zip_archive.read(zip_entry)
    end

    def expand_symlink(source_path)
      ::File.realpath(source_path, path_dir)
    rescue
      raise SafeZip::Extract::SymlinkSourceDoesNotExistError, "Symlink source #{source_path} does not exist"
    end
  end
end