Files
danbooru/test/unit/danbooru_archive_test.rb
evazion 2deae38a4e uploads: allow uploading .zip, .rar., and .7z files from disk.
Allow uploading .zip, .rar, and .7z files from disk. The archive will be extracted and the images
inside will be uploaded.

This only works for archive files uploaded from disk, not from a source URL.

Post source URLs will look something like this: "file://foo.zip/1.jpg", "file://foo.zip/2.jpg", etc.
Sometimes artists uses Shift JIS or other encodings instead of UTF-8 for filenames. In these cases
we just assume the filename is UTF-8 and replace invalid characters with '?', so filenames might be
wrong in some cases.

There are various protections to prevent uploading malicious archive files:

* Archives with more than 100 files aren't allowed.
* Archives that decompress to more than 100MB aren't allowed.
* Archives with filenames containing '..' components aren't allowed (e.g. '../../../../../etc/passwd').
* Archives with filenames containing absolute paths aren't allowed (e.g. '/etc/passwd').
* Archives containing symlinks aren't allowed (e.g. 'foo -> /etc/passwd').
* Archive types other than .zip, .rar, and .7z aren't allowed (e.g. .tar.gz, .cpio).
* File permissions, owners, and other metadata are ignored.

Partial fix for #5340: Add support for extracting archive attachments from certain sources
2022-11-16 16:47:37 -06:00

181 lines
6.7 KiB
Ruby

require 'test_helper'
class DanbooruArchiveTest < ActiveSupport::TestCase
context "Danbooru::Archive" do
context ".open! method" do
should "work without a block" do
archive = Danbooru::Archive.open!("test/files/ugoira.zip")
assert_equal(5, archive.entries.count)
end
should "work with a block" do
Danbooru::Archive.open!("test/files/ugoira.zip") do |archive|
assert_equal(5, archive.entries.count)
end
end
should "raise an error if the block raises an error" do
assert_raises(Danbooru::Archive::Error) { Danbooru::Archive.open!("test/files/ugoira.zip") { raise "failed" } }
end
should "raise an error if the file doesn't exist" do
assert_raises(Danbooru::Archive::Error) { Danbooru::Archive.open!("test/files/does_not_exist.zip") }
end
end
context ".open method" do
should "work without a block" do
archive = Danbooru::Archive.open("test/files/ugoira.zip")
assert_equal(5, archive.entries.count)
end
should "work with a block" do
Danbooru::Archive.open("test/files/ugoira.zip") do |archive|
assert_equal(5, archive.entries.count)
end
end
should "return nil if the block raises an error" do
assert_nil(Danbooru::Archive.open("test/files/ugoira.zip") { raise "failed" })
end
should "return nil if the file doesn't exist" do
assert_nil(Danbooru::Archive.open("test/files/does_not_exist.zip"))
end
end
context ".extract! method" do
should "extract to temp directory if not given a block or directory" do
dir, filenames = Danbooru::Archive.extract!("test/files/ugoira.zip")
assert_equal(true, File.directory?(dir))
assert_equal(5, filenames.size)
filenames.each { |filename| assert_equal(true, File.exist?(filename)) }
ensure
FileUtils.rm_rf(dir)
end
should "extract to a temp directory and delete it afterwards if given a block" do
Danbooru::Archive.extract!("test/files/ugoira.zip") do |dir, filenames|
@tmpdir = dir
assert_equal(true, File.directory?(dir))
assert_equal(5, filenames.size)
filenames.each { |filename| assert_equal(true, File.exist?(filename)) }
end
assert_equal(true, @tmpdir.present?)
assert_equal(false, File.exist?(@tmpdir))
end
should "extract to given directory if given a directory" do
Dir.mktmpdir do |tmpdir|
dir, filenames = Danbooru::Archive.extract!("test/files/ugoira.zip", tmpdir)
assert_equal(dir, tmpdir)
assert_equal(5, filenames.size)
filenames.each { |filename| assert_equal(true, File.exist?(filename)) }
end
end
should "work with a .zip file" do
Danbooru::Archive.extract!("test/files/archive/ugoira.zip") do |dir, filenames|
assert_equal(5, filenames.size)
filenames.each { |filename| assert_equal(true, File.exist?(filename)) }
end
end
should "work with a .rar file" do
Danbooru::Archive.extract!("test/files/archive/ugoira.rar") do |dir, filenames|
assert_equal(5, filenames.size)
filenames.each { |filename| assert_equal(true, File.exist?(filename)) }
end
end
should "work with a .7z file" do
Danbooru::Archive.extract!("test/files/archive/ugoira.7z") do |dir, filenames|
assert_equal(5, filenames.size)
filenames.each { |filename| assert_equal(true, File.exist?(filename)) }
end
end
end
context "#uncompressed_size method" do
should "work" do
archive = Danbooru::Archive.open!("test/files/ugoira.zip")
assert_equal(6161, archive.uncompressed_size)
end
end
context "#exists? method" do
should "work" do
archive = Danbooru::Archive.open!("test/files/ugoira.zip")
assert_equal(true, archive.exists? { |entry, count| count > 4 })
end
end
context "#format method" do
should "detect the file type" do
assert_equal("ZIP 2.0 (uncompressed)", Danbooru::Archive.open("test/files/archive/ugoira.zip").format)
assert_equal("RAR5", Danbooru::Archive.open("test/files/archive/ugoira.rar").format)
assert_equal("7-Zip", Danbooru::Archive.open("test/files/archive/ugoira.7z").format)
assert_equal("GNU tar format", Danbooru::Archive.open("test/files/archive/ugoira.tar").format)
assert_equal("GNU tar format", Danbooru::Archive.open("test/files/archive/ugoira.tar.gz").format)
end
end
context "#file_ext method" do
should "detect the file extension" do
assert_equal(:zip, Danbooru::Archive.open("test/files/archive/ugoira.zip").file_ext)
assert_equal(:rar, Danbooru::Archive.open("test/files/archive/ugoira.rar").file_ext)
assert_equal(:"7z", Danbooru::Archive.open("test/files/archive/ugoira.7z").file_ext)
assert_equal(:bin, Danbooru::Archive.open("test/files/archive/ugoira.tar").file_ext)
assert_equal(:bin, Danbooru::Archive.open("test/files/archive/ugoira.tar.gz").file_ext)
end
end
context "#ls method" do
should "work" do
archive = Danbooru::Archive.open!("test/files/ugoira.zip")
output = StringIO.new
archive.ls(output)
assert_match(/^-rw-rw-r-- *0 0 *1639 2014-10-05 23:31:06 000000\.jpg$/, output.tap(&:rewind).read)
end
end
should "detect directory traversal attacks" do
archive = Danbooru::Archive.open!("test/files/archive/zip-slip.zip")
assert_equal(true, archive.entries.any? { |e| e.directory_traversal? })
assert_raises(Danbooru::Archive::Error) { archive.extract! }
end
should "detect symlinks" do
archive = Danbooru::Archive.open!("test/files/archive/symlink.zip")
assert_equal(true, archive.entries.any? { |e| !e.file? && !e.directory? })
assert_raises(Danbooru::Archive::Error) { archive.extract! }
end
should "detect absolute paths" do
archive = Danbooru::Archive.open!("test/files/archive/absolute-path.7z")
assert_equal(true, archive.entries.any? { |e| e.pathname.starts_with?("/") })
assert_raises(Danbooru::Archive::Error) { archive.extract! }
end
should "detect archives with large numbers of files" do
archive = Danbooru::Archive.open!("test/files/archive/bomb-10k-files.7z")
assert_equal(true, archive.exists? { |_, count| count > 100 })
assert_equal(10_000, archive.file_count)
end
should "detect decompression bombs" do
archive = Danbooru::Archive.open!("test/files/archive/bomb-1-1G.rar")
assert_equal(1, archive.file_count)
assert_equal(1_048_576_000, archive.uncompressed_size)
end
end
end