Rubyでパスワード付きのzipファイルを解凍する案件があり色々しらべてみたが、解凍の情報って案外すくないので、最近の方法をまとめてみた。
まず、調べて出てくるのは以下の方法
①rubyzip
②zipruby
③archive-zip
④外部コマンド unar
【結論】
④unar外部コマンドを利用して一時ディレクトリに出力してから処理する。(エラ-処理は無視してます)
require 'tmpdir'
module Unzip
extend ActiveSupport::Concern
class_methods do
def tmpzipfiles(path, password)
Dir.mktmpdir do |dir|
system("unar -f -o '#{dir}' -p '#{password}' '#{path}'")
Dir.glob(File.join(dir, "**/*")){ |f| yield(f) }
end
end
end
end
Railsでの使い方はモデル内で
include Unzip
def hoge
tmpzipfiles do |f|
# f(一時ファイル名)に対する処理
end
end
以下は色々試した経緯です。
どちらもgemで入れられるライブラリーだが、②ziprubyはどうやらオワコンらしい。
ってことで一番メジャーそうな①rubyzipを使ってみる。
Railsなので、
gem 'rubyzip'
一時ディレクトリに解凍してブロック処理を行い、終わったら削除としたいので、
Dir::mktmpdirを利用
require 'zip'
require 'tmpdir'
module Unzip
extend ActiveSupport::Concern
class_methods do
def tmpzipfiles(path, password)
entrys = []
decrypter = password.present? ? Zip::TraditionalDecrypter.new(password) : nil
Zip::InputStream.open(path, 0, decrypter) do |input|
# get_next_entryするとinputのoffset(ポインタ)が動く
while (entry = input.get_next_entry)
Dir.mktmpdir do |dir|
save_path = File.join(dir, entry.name)
File.open(save_path, "wb") {|wf| wf.puts(input.read) }
yield(save_path)
end
end
end
end
end
end
としたんだが、どうもSJISの1文字だけのファイル("0\n\r")が正しく解凍できない。
それに、このコードがどうも嫌だ。input.get_next_entryって!そんで取得したentryではなく、inputからreadするところが、とっても嫌なので却下!
次に③のarchive-zip使ってみる。これは殆ど情報がないけど、シンプルが売りで使いやすそうですね。
gem 'archive-zip'
require 'archive/zip'
require 'tmpdir'
module Unzip
extend ActiveSupport::Concern
class_methods do
def tmpzipfiles(path, password)
Dir.mktmpdir do |dir|
Archive::Zip.extract(path, dir, password: password)
Dir.glob(File.join(dir, "**/*")){ |f| yield(f) }
end
end
end
end
こっちは3バイトファイルも問題なし、しかもコードもシンプルになりました。
しかし、ZIPファイル内にSJISの日本語ファイルとかフォルダがあると文字化けしてしまいます。