1+ require 'zip/version'
2+
13module ROCrate
24 ##
35 # A class to handle reading of RO-Crates from Zip files or directories.
46 class Reader
7+ LEGACY_EXTRACT = Zip ::VERSION . start_with? ( '2.' ) . freeze
8+
59 ##
610 # Reads an RO-Crate from a directory or zip file.
711 #
@@ -43,14 +47,20 @@ def self.unzip_to(source, target)
4347 # @param source [#read] An IO-like object containing a Zip file.
4448 # @param target [String, ::File, Pathname] The target directory where the file should be unzipped.
4549 def self . unzip_io_to ( source , target )
46- Dir . chdir ( target ) do
47- Zip ::InputStream . open ( source ) do |input |
48- while ( entry = input . get_next_entry )
49- unless ::File . exist? ( entry . name ) || entry . name_is_directory?
50- FileUtils ::mkdir_p ( ::File . dirname ( entry . name ) )
51- ::File . binwrite ( entry . name , input . read )
52- end
53- end
50+ target = Pathname ( target )
51+ Zip ::InputStream . open ( source ) do |input |
52+ while ( entry = input . get_next_entry )
53+ next if entry . name_is_directory?
54+
55+ dest = target . join ( entry . name )
56+
57+ # Guard against zip slip attacks, even though Rubyzip should block them.
58+ raise "Unsafe path in zip entry: #{ entry . name } " unless dest . to_s . start_with? ( ::File . realpath ( target ) + ::File ::SEPARATOR )
59+
60+ next if dest . exist?
61+
62+ FileUtils . mkdir_p ( dest . dirname )
63+ ::File . binwrite ( dest , input . read )
5464 end
5565 end
5666 end
@@ -61,14 +71,18 @@ def self.unzip_io_to(source, target)
6171 # @param source [String, ::File, Pathname] The location of the zip file.
6272 # @param target [String, ::File, Pathname] The target directory where the file should be unzipped.
6373 def self . unzip_file_to ( source , target )
64- Dir . chdir ( target ) do
65- Zip ::File . open ( source ) do |zipfile |
66- zipfile . each do |entry |
67- unless ::File . exist? ( entry . name )
68- FileUtils ::mkdir_p ( ::File . dirname ( entry . name ) )
69- zipfile . extract ( entry , entry . name )
70- end
71- end
74+ target = Pathname ( target )
75+ Zip ::File . open ( source ) do |zipfile |
76+ zipfile . each do |entry |
77+ dest = target . join ( entry . name )
78+
79+ # Guard against zip slip attacks, even though Rubyzip should block them.
80+ raise "Unsafe path in zip entry: #{ entry . name } " unless dest . to_s . start_with? ( ::File . realpath ( target ) + ::File ::SEPARATOR )
81+
82+ next if dest . exist?
83+
84+ FileUtils . mkdir_p ( dest . dirname )
85+ LEGACY_EXTRACT ? entry . extract ( dest ) : entry . extract ( entry . name , destination_directory : target )
7286 end
7387 end
7488 end
0 commit comments