diff --git a/ext/seven_zip_ruby/extconf.rb b/ext/seven_zip_ruby/extconf.rb index b584429..44285b7 100644 --- a/ext/seven_zip_ruby/extconf.rb +++ b/ext/seven_zip_ruby/extconf.rb @@ -140,11 +140,11 @@ def main if (RUBY_PLATFORM.include?("mswin")) # mswin32 - $LIBS = "oleaut32.lib" + $LIBS = "oleaut32.lib shlwapi.lib" $CPPFLAGS = "/I.. /EHsc /DNDEBUG /DUSE_WIN32_FILE_API #{base_flag} #{$CPPFLAGS} " elsif (RUBY_PLATFORM.include?("mingw")) # MinGW - $LIBS = "-loleaut32 -static-libgcc -static-libstdc++" + $LIBS = "-loleaut32 -lshlwapi -static-libgcc -static-libstdc++" cpp0x_flag = [ "", "-std=gnu++11", "-std=c++11", "-std=gnu++0x", "-std=c++0x" ].find do |opt| try_compile(sample_cpp_source, "#{opt} -x c++ ") diff --git a/ext/seven_zip_ruby/seven_zip_archive.cpp b/ext/seven_zip_ruby/seven_zip_archive.cpp index 05319e1..ff09d44 100644 --- a/ext/seven_zip_ruby/seven_zip_archive.cpp +++ b/ext/seven_zip_ruby/seven_zip_archive.cpp @@ -317,7 +317,11 @@ VALUE ArchiveReader::open(VALUE in_stream, VALUE param) checkState(STATE_INITIAL, "Open error"); if (ret != S_OK){ - throw RubyCppUtil::RubyException("Invalid file format. open"); + if (m_password_specified){ + throw RubyCppUtil::RubyException("Invalid file format. open. or password is incorrect."); + }else{ + throw RubyCppUtil::RubyException("Invalid file format. open."); + } } m_state = STATE_OPENED; @@ -1761,15 +1765,44 @@ STDMETHODIMP OutStream::SetSize(UInt64 size) } +#ifdef _WIN32 +#include "Shlwapi.h" +static HINSTANCE gDllInstance = NULL; + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) +{ + // Perform actions based on the reason for calling. + switch( fdwReason ) + { + case DLL_PROCESS_ATTACH: + gDllInstance = hinstDLL; + break; + case DLL_PROCESS_DETACH: + gDllInstance = NULL; + break; + } + return TRUE; +} +#endif + extern "C" void Init_seven_zip_archive(void) { using namespace SevenZip; using namespace RubyCppUtil; #ifdef _WIN32 - gSevenZipHandle = LoadLibrary("./7z.dll"); + WCHAR modulePath[MAX_PATH]; + GetModuleFileNameW(gDllInstance, modulePath, _countof(modulePath)); + + SetDllDirectory(""); + + PathRemoveFileSpecW(modulePath); + PathAppendW(modulePath, L"7z.dll"); + gSevenZipHandle = LoadLibraryW(modulePath); if (!gSevenZipHandle){ - gSevenZipHandle = LoadLibrary("./7z64.dll"); + PathRemoveFileSpecW(modulePath); + PathAppendW(modulePath, L"7z64.dll"); + gSevenZipHandle = LoadLibraryW(modulePath); } #else gSevenZipHandle = dlopen("./7z.so", RTLD_NOW); diff --git a/ext/seven_zip_ruby/utils.cpp b/ext/seven_zip_ruby/utils.cpp index 8e1639b..9d9bfd4 100644 --- a/ext/seven_zip_ruby/utils.cpp +++ b/ext/seven_zip_ruby/utils.cpp @@ -448,13 +448,13 @@ VALUE ConvertBstrToString(const BSTR &bstr) const int char_count = SysStringLen(bstr); #ifdef _WIN32 const int len = WideCharToMultiByte(CP_UTF8, 0, bstr, char_count, NULL, 0, NULL, NULL); - VALUE str = rb_str_new(NULL, len); + VALUE str = rb_tainted_str_new(NULL, len); WideCharToMultiByte(CP_UTF8, 0, bstr, char_count, RSTRING_PTR(str), len, NULL, NULL); #else size_t len; Utf16_To_Utf8(NULL, &len, bstr, char_count); - VALUE str = rb_str_new(NULL, len); + VALUE str = rb_tainted_str_new(NULL, len); Utf16_To_Utf8(RSTRING_PTR(str), &len, bstr, char_count); #endif return str; diff --git a/lib/seven_zip_ruby/seven_zip_reader.rb b/lib/seven_zip_ruby/seven_zip_reader.rb index 378ad4f..6e6be51 100644 --- a/lib/seven_zip_ruby/seven_zip_reader.rb +++ b/lib/seven_zip_ruby/seven_zip_reader.rb @@ -462,13 +462,28 @@ def idx_prj.[](index) def file_proc(base_dir) # :nodoc: base_dir = base_dir.to_s + base_dir = File.expand_path(base_dir) return Proc.new do |type, arg| case(type) when :stream ret = nil arg_path = Pathname(arg.path) + rp = arg_path.cleanpath + if "..#{File::SEPARATOR}" == rp.to_s[0..2] + raise InvalidArchive.new("#{arg.path} is Dangerous Path.") + end if (arg.anti?) - arg_path.rmtree if (arg_path.exist?) + pwd = Dir.pwd + Dir.chdir(base_dir) + rp = File.join(".", arg_path.to_s) + begin + if (File.exist?(rp)) + require 'fileutils' + FileUtils.remove_entry_secure(rp) + end + ensure + Dir.chdir(pwd) rescue nil + end elsif (arg.file?) path = arg_path.expand_path(base_dir) path.parent.mkpath diff --git a/test/res/The Flying Spaghetti Monster.txt b/test/res/The Flying Spaghetti Monster.txt new file mode 100755 index 0000000..4be351f --- /dev/null +++ b/test/res/The Flying Spaghetti Monster.txt @@ -0,0 +1,3 @@ +宇宙は空飛ぶスパゲッティ・モンスターによって創造された。これは空飛ぶスパゲッティ・モンスターが大酒を飲んだ後の事であった。 + +https://ja.wikipedia.org/wiki/%E7%A9%BA%E9%A3%9B%E3%81%B6%E3%82%B9%E3%83%91%E3%82%B2%E3%83%83%E3%83%86%E3%82%A3%E3%83%BB%E3%83%A2%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%BC%E6%95%99 diff --git a/test/res/The Three Little Pigs.txt b/test/res/The Three Little Pigs.txt new file mode 100755 index 0000000..3daeff2 --- /dev/null +++ b/test/res/The Three Little Pigs.txt @@ -0,0 +1,5 @@ +Little pig, little pig, let me come in. +No, no, by the hair on my chinny chin chin. +Then I'll huff, and I'll puff, and I'll blow your house in. + +https://en.wikipedia.org/wiki/The_Three_Little_Pigs diff --git a/test/res/test_reader_data.7z b/test/res/test_reader_data.7z new file mode 100755 index 0000000..2904d8c Binary files /dev/null and b/test/res/test_reader_data.7z differ diff --git a/test/res/test_reader_filename_cp932.7z b/test/res/test_reader_filename_cp932.7z new file mode 100755 index 0000000..6bd07ff Binary files /dev/null and b/test/res/test_reader_filename_cp932.7z differ diff --git a/test/res/test_reader_files.7z b/test/res/test_reader_files.7z new file mode 100644 index 0000000..b210625 Binary files /dev/null and b/test/res/test_reader_files.7z differ diff --git "a/test/res/\347\237\263\350\202\245\344\270\211\345\271\264.txt" "b/test/res/\347\237\263\350\202\245\344\270\211\345\271\264.txt" new file mode 100755 index 0000000..54763ff --- /dev/null +++ "b/test/res/\347\237\263\350\202\245\344\270\211\345\271\264.txt" @@ -0,0 +1,3 @@ +狐たちの間では、みんなが痛い目に遭っている彦一に一杯食わせようと企む奴は少なくない。ある狐は、夜のうちに彦一の家の畑に大量の石を投げ入れ、近くに隠れて様子をうかがっていた。翌朝になって畑にやって来た彦一は驚いたが、これが狐の仕業だと気付くと、わざと嬉しそうな顔をして、「こりゃ助かった。石の肥料は三年はもつ。これが馬の糞だったら大変な所じゃった」と、狐に聞こえるような大声で嘘をついた。これを真に受けた狐はその夜に石を全部取り除き、代わりに馬の糞をどっさり投げ入れた(馬の糞は良い肥料になる)。翌朝、彦一は「困った困った」と言いながらご機嫌な顔で畑を耕し、狐はこれで彦一を困らせてやったと満足して帰って行くのだった。 + +https://ja.wikipedia.org/wiki/%E5%BD%A6%E4%B8%80 \ No newline at end of file diff --git a/test/test_seven_zip_reader.rb b/test/test_seven_zip_reader.rb new file mode 100755 index 0000000..72da1b0 --- /dev/null +++ b/test/test_seven_zip_reader.rb @@ -0,0 +1,187 @@ +# vim: tabstop=4 fileformat=unix fileencoding=utf-8 filetype=ruby + +require 'rubygems' +# gem install test-unit +require 'test/unit' + +STDERR.sync = true + +$basedir = File.dirname( $0 ) +$tmpdir = File.join( $basedir, "tmp" ) +$resourcedir = File.join( $basedir, "res" ) + +#gem 'seven_zip_ruby_am', '< 1.2.6' +require 'seven_zip_ruby' + +class TestSevenZipReader < Test::Unit::TestCase + + def self.startup + self.remove_tmpdir + end + + def self.shutdown + self.remove_tmpdir + end + + def tmpdir + unless File.exist?( $tmpdir ) + Dir.mkdir( $tmpdir ) rescue nil + end + $tmpdir + end + + def self.remove_tmpdir + if File.exist?( $tmpdir ) + Dir.rmdir( $tmpdir ) rescue nil + end + end + + def remove_tmp_entries( entries ) + entries.each do |f| + pth = File.join( $tmpdir, f ) + File.delete( pth ) if File.exist?( pth ) + end + end + + def test_reader_extract_data + formats = [ + ["7ZIP", "7z", "It's a 7z!\r\n"], + ] + formats.each do |e| + type, ext, text, = *e + pth = File.join( $resourcedir, "test_reader_data.#{ext}" ) + File.open( pth, "rb" ) do |file| + SevenZipRuby::Reader.open( file, :type => type ) do |szr| + first_file = szr.entries.select( &:file? ).first + data = szr.extract_data( first_file ) + data.force_encoding( Encoding::UTF_8 ) + assert_equal( text, data ) + end + end + end + end + + def test_reader_entries + pth = File.join( $resourcedir, "test_reader_files.7z" ) + File.open( pth, "rb" ) do |file| + SevenZipRuby::Reader.open( file ) do |szr| + ent = szr.entries + assert_equal( "The Flying Spaghetti Monster.txt", ent[0].path ) + assert_equal( "The Three Little Pigs.txt", ent[1].path ) + end + end + end + + def test_reader_extract + tmp = tmpdir() + + entries = [ + "The Flying Spaghetti Monster.txt", + "The Three Little Pigs.txt" + ] + remove_tmp_entries( entries ) + + pth = File.join( $resourcedir, "test_reader_files.7z" ) + File.open( pth, "rb" ) do |file| + SevenZipRuby::Reader.open( file ) do |szr| + szr.extract_all( tmp ) + end + end + + entries.each do |f| + pth = File.join( tmp, f ) + assert( File.exist?( pth ) ) + end + + remove_tmp_entries( entries ) + end + + def test_reader_filepath_encoding_cp932 + tmp = tmpdir() + + pth = File.join( $resourcedir, "test_reader_filename_cp932.7z" ) + File.open( pth, "rb" ) do |file| + SevenZipRuby::Reader.open( file ) do |szr| + ent = szr.entries + assert_equal( "石肥三年.txt", ent[0].path ) + szr.extract_all( tmp ) + end + end + assert( File.exist?( File.join( tmp, "石肥三年.txt" ) ) ) + + remove_tmp_entries( ["石肥三年.txt"] ) + end + + def test_reader_extract_zs + data = < err + assert_match( /Dangerous Path/i, err.message ) + safety = true + end + end + rescue SevenZipRuby::InvalidOperation => err + # ignore + end + unless safety + notify( "The expected exception is not thrown." ) + end + assert_path_not_exist( File.join( tmp, file_name ) ) + + file.rewind + # szr.extract_all + safety = false + begin + SevenZipRuby::Reader.open( file ) do |szr| + ent = szr.entries + begin + szr.extract_all( tmp_tmp ) +# notify( "Vulnerable ?" ) + rescue SevenZipRuby::InvalidArchive => err + assert_match( /Dangerous Path/i, err.message ) + safety = true + end + end + rescue SevenZipRuby::InvalidOperation => err + # ignore + end + unless safety + notify( "The expected exception is not thrown." ) + end + assert_path_not_exist( File.join( tmp, file_name ) ) + + file.close + + Dir.rmdir( tmp_tmp ) if Dir.exist?( tmp_tmp ) + end + +end + + + diff --git a/test/test_seven_zip_writer.rb b/test/test_seven_zip_writer.rb new file mode 100755 index 0000000..dc6bc8d --- /dev/null +++ b/test/test_seven_zip_writer.rb @@ -0,0 +1,184 @@ +# vim: tabstop=4 fileformat=unix fileencoding=utf-8 filetype=ruby + +require 'rubygems' +# gem install test-unit +require 'test/unit' + +STDERR.sync = true + +$basedir = File.dirname( $0 ) +$tmpdir = File.join( $basedir, "tmp" ) +$resourcedir = File.join( $basedir, "res" ) + +#gem 'seven_zip_ruby_am', '< 1.2.6' +require 'seven_zip_ruby' +require 'stringio' + +class TestSevenZipWriter < Test::Unit::TestCase + + def test_writer_files + entries = [ + "The Flying Spaghetti Monster.txt", + "The Three Little Pigs.txt", + "石肥三年.txt" + ] + + file = StringIO.new( "test_writer_files.7z", "w+b" ) + SevenZipRuby::Writer.open( file ) do |szw| + Dir.chdir( $resourcedir ) do |dummy| + entries.each do |e| + szw.add_file( e ) + end + end + end + + file.rewind + SevenZipRuby::Reader.open( file ) do |szr| + ent = szr.entries + assert_equal( entries.size, ent.size ) + assert_equal( entries[0], ent[0].path ) + assert_equal( entries[1], ent[1].path ) + assert_equal( entries[2], ent[2].path ) + end + + file.close + end + + def test_writer_encrypt + require 'digest/md5' + password = Random.rand + notify( "Random.rand: #{password}" ) + password = Digest::MD5.digest( password.to_s ) + password = [password].pack( "m0" ) # base64 + + fl = "The Three Little Pigs.txt" + + file = StringIO.new( "test_writer_crypt.7z", "w+b" ) + SevenZipRuby::Writer.open( file, :password => password ) do |szw| + Dir.chdir( $resourcedir ) do |dummy| + szw.add_file( fl ) + end + end + + file.rewind + SevenZipRuby::Reader.open( file ) do |szr| + first_file = szr.entries.select( &:file? ).first + assert_equal( fl, first_file.path ) + assert_raise( StandardError ) do + data = szr.extract_data( first_file ) + flunk( "The archive could be opened without a password." ) + end + end + + file.rewind + begin + SevenZipRuby::Reader.open( file, :password => "INCORRECT PASSWORD" ) do |szr| + first_file = szr.entries.select( &:file? ).first + assert_equal( fl, first_file.path ) + # 7z 19.00 throws SevenZipRuby::InvalidArchive. + assert_raise( SevenZipRuby::InvalidArchive ) do + data = szr.extract_data( first_file ) + # p7zip 16.02 returns nil. + raise SevenZipRuby::InvalidArchive.new if data.nil? + flunk( "The archive could be opened with an incorrect password." ) + end + end + rescue SevenZipRuby::InvalidOperation => err + # ignore + end + + file.rewind + SevenZipRuby::Reader.open( file, :password => password ) do |szr| + ent = szr.entries + assert_equal( fl, ent[0].path ) + end + + file.close + end + + def test_writer_encrypt_header + require 'digest/md5' + password = Random.rand + notify( "Random.rand: #{password}" ) + password = Digest::MD5.digest( password.to_s ) + password = [password].pack( "m0" ) # base64 + + + fl = "The Three Little Pigs.txt" + + file = StringIO.new( "test_writer_crypt.7z", "w+b" ) + SevenZipRuby::Writer.open( file, :password => password ) do |szw| + szw.header_encryption = true + Dir.chdir( $resourcedir ) do |dummy| + szw.add_file( fl ) + end + end + + file.rewind + # StandardError: Invalid file format. open + assert_raise( StandardError ) do + SevenZipRuby::Reader.open( file ) do |szr| + flunk( "The archive could be opened without a password." ) + end + end + + file.rewind + # StandardError: Invalid file format. open + assert_raise( StandardError ) do + SevenZipRuby::Reader.open( file, :password => "INCORRECT PASSWORD" ) do |szr| + flunk( "The archive could be opened with an incorrect password." ) + end + end + + file.rewind + SevenZipRuby::Reader.open( file, :password => password ) do |szr| + ent = szr.entries + assert_equal( fl, ent[0].path ) + end + + file.close + end + + def test_writer_levels + fl = '石肥三年.txt' + pth = File.join( $resourcedir, fl ) + data = File.read( pth, :encoding => Encoding::UTF_8 ) + + ["LZMA", "LZMA2", "PPMd", "BZIP2", "DEFLATE"].each do |method| + [0, 1, 3, 5, 7, 9].each do |level| + msg = "method: #{method}, level: #{level}." + __test_writer_levels_compress( fl, data, method, level, msg ) + end + end + end + + def __test_writer_levels_compress( i_name, i_data, i_method, i_level, i_message ) + file = StringIO.new( "test_writer_levels.7z", "w+b" ) + SevenZipRuby::Writer.open( file ) do |szw| + szw.method = i_method +# szw.multi_thread = true + szw.level = i_level + + d = i_data.dup + szw.add_data( d, i_name ) + szw.compress + d.replace( "\0" * d.size ) + end + + file.rewind + SevenZipRuby::Reader.open( file ) do |szr| + first_file = szr.entries.select( &:file? ).first + assert_equal( i_name, first_file.path, i_message ) + + d = szr.extract_data( first_file ) + d.force_encoding( i_data.encoding ) + assert_equal( i_data, d, i_message ) + end + + file.close + end + +end + + +