How to generate password protected (as optional) zip file in Ruby?

230 Views Asked by At

Basic archive creation works like this (using rubyzip)

Zip::File.open(zipfile_name, create: true) do |zipfile|
  input_filenames.each do |filename|
    zipfile.add(filename, File.join(folder, filename))
  end
end

Okay I got that to work a while back... But now I need to create a password-protected zip. For this the documentation has:

enc = Zip::TraditionalEncrypter.new('password')
buffer = Zip::OutputStream.write_buffer(encrypter: enc) do |output|
  output.put_next_entry("my_file.txt")
  output.write my_data
end

Which I don't quite understand how to combine with the first approach (iterating over a list of files).

Also password is optional in my case - it's a user choice whether the archive will be password protected or not. I would like to avoid having to use a completely different code in each case.

1

There are 1 best solutions below

0
mechnicov On

As you can see by default encrypter is nil

But you can pass it dynamically if needed

I suggest such method:

require "zip"

def zipfile(filenames:, zip_filename:, password: nil)
  # If password was passed, intitialize encrypter
  encrypter = Zip::TraditionalEncrypter.new(password) if password

  # Create buffer, it is the object like StringIO
  # Pass encrypter there, it will be nil if no password
  buffer = Zip::OutputStream.write_buffer(encrypter: encrypter) do |out|
    Array(filenames).each do |filename|
      # Create file inside zip archive
      out.put_next_entry(File.basename(filename))
      # And fill with file content
      out.write File.read(filename)
    end
  end

  # Write result to the zip file
  File.open(zip_filename, "w") { |zipfile| zipfile.write(buffer.string) }
end

Of course you can modify it or create separate entity for you needs

Now you can call this method passing string or array of filenames, passing/not passing password

zipfile(filenames: ["1.txt", "2.txt"], zip_filename: "new.zip", password: "123456")
zipfile(filenames: "/Users/abc/111.txt", zip_filename: "/Users/abc/111.zip")

As I see this is pretty new feature of rubyzip 3.0 and you need to specify this version (for example in the Gemfile)

# Gemfile
source "https://rubygems.org"

gem "rubyzip", "3.0.0.alpha"

And run your script in the context of that version, let's say

bundle exec ruby zipper.rb