You've already forked image_processing
mirror of
https://github.com/usetrmnl/image_processing.git
synced 2026-04-29 13:33:11 -07:00
038e4574e8
If the operations are coming from user input, this could allow the user
to execute arbitrary shell commands via `Kernel#system` and
`Kernel#spawn`:
ImageProcessing::Vips.apply({ system: "echo something" })
We prevent this by using `#public_send` instead of `#send`, which goes
to method missing instead of calling private methods, which include
`Kernel#system` and `Kernel#spawn`.
277 lines
9.1 KiB
Ruby
277 lines
9.1 KiB
Ruby
require "test_helper"
|
|
require "image_processing/vips"
|
|
require "pathname"
|
|
|
|
describe "ImageProcessing::Pipeline" do
|
|
before do
|
|
@portrait = fixture_image("portrait.jpg")
|
|
end
|
|
|
|
it "accepts source" do
|
|
pipeline = ImageProcessing::Vips.source(@portrait)
|
|
assert_equal @portrait, pipeline.options[:source]
|
|
end
|
|
|
|
it "accepts File, Tempfile, String, and Pathname objects as source" do
|
|
ImageProcessing::Vips.source(@portrait).call
|
|
ImageProcessing::Vips.source(copy_to_tempfile(@portrait, "jpg")).call
|
|
ImageProcessing::Vips.source(@portrait.path).call
|
|
ImageProcessing::Vips.source(Pathname(@portrait.path)).call
|
|
end
|
|
|
|
it "accepts format" do
|
|
pipeline = ImageProcessing::Vips.source(@portrait)
|
|
|
|
result = pipeline.convert!("png")
|
|
assert_equal ".png", File.extname(result.path)
|
|
|
|
result = pipeline.convert!(nil)
|
|
assert_equal ".jpg", File.extname(result.path)
|
|
assert_similar @portrait, result
|
|
end
|
|
|
|
it "retains original format if format was not specified" do
|
|
jpg = ImageProcessing::Vips.convert("jpg").call(@portrait)
|
|
png = ImageProcessing::Vips.convert("png").call(@portrait)
|
|
result_jpg = ImageProcessing::Vips.invert.call(jpg)
|
|
result_png = ImageProcessing::Vips.invert.call(png)
|
|
assert_equal ".jpg", File.extname(result_jpg.path)
|
|
assert_equal ".png", File.extname(result_png.path)
|
|
end
|
|
|
|
it "saves as JPEG when format is unknown" do
|
|
png = ImageProcessing::Vips.convert("png").call(@portrait)
|
|
result = ImageProcessing::Vips.invert.call(copy_to_tempfile(png))
|
|
assert_equal ".jpg", File.extname(result.path)
|
|
assert_type "JPEG", result
|
|
end
|
|
|
|
it "saves as JPEG when format is unknown and the path ends with a '.'" do
|
|
png = ImageProcessing::Vips.convert("png").call(@portrait)
|
|
result = ImageProcessing::Vips.invert.call(copy_to_tempfile(png, "."))
|
|
assert_equal ".jpg", File.extname(result.path)
|
|
assert_type "JPEG", result
|
|
end
|
|
|
|
it "accepts destination path" do
|
|
destination = Tempfile.new(["destination", ".jpg"])
|
|
ImageProcessing::Vips.source(@portrait).call(destination: destination.path)
|
|
refute_equal 0, destination.path
|
|
end
|
|
|
|
it "makes sure the destination file is deleted on processing errors" do
|
|
destination_path = Dir::Tmpname.create(["destination", ".jpg"]) {}
|
|
pipeline = ImageProcessing::MiniMagick.source(fixture_image("invalid.jpg"))
|
|
assert_raises(MiniMagick::Error) { pipeline.call(destination: destination_path) }
|
|
refute File.exist?(destination_path)
|
|
end
|
|
|
|
it "doesn't delete the destination file if it has already existed" do
|
|
destination = Tempfile.new(["destination", ".jpg"])
|
|
pipeline = ImageProcessing::MiniMagick.source(fixture_image("invalid.jpg"))
|
|
assert_raises(MiniMagick::Error) { pipeline.call(destination: destination.path) }
|
|
assert File.exist?(destination.path)
|
|
end
|
|
|
|
it "doesn't fail when destination file hasn't been created" do
|
|
destination_path = Dir::Tmpname.create("destination") {}
|
|
pipeline = ImageProcessing::Vips.source(@portrait)
|
|
assert_raises(Vips::Error) { pipeline.call(destination: destination_path) }
|
|
end
|
|
|
|
it "accepts loader options" do
|
|
pipeline = ImageProcessing::Vips.loader(shrink: 2)
|
|
assert_equal Hash[shrink: 2], pipeline.options[:loader]
|
|
|
|
pipeline = pipeline.loader(autorotate: true)
|
|
assert_equal Hash[shrink: 2, autorotate: true], pipeline.options[:loader]
|
|
end
|
|
|
|
it "accepts saver options" do
|
|
pipeline = ImageProcessing::Vips.saver(strip: true)
|
|
assert_equal Hash[strip: true], pipeline.options[:saver]
|
|
|
|
pipeline = pipeline.saver(Q: 100)
|
|
assert_equal Hash[strip: true, Q: 100], pipeline.options[:saver]
|
|
end
|
|
|
|
it "accepts operations" do
|
|
pipeline = ImageProcessing::Vips.shrink(2, 2)
|
|
assert_equal [[:shrink, [2, 2]]], pipeline.options[:operations]
|
|
|
|
pipeline = pipeline.invert
|
|
assert_equal [[:shrink, [2, 2]], [:invert, []]], pipeline.options[:operations]
|
|
end
|
|
|
|
it "saves blocks passed to operations" do
|
|
pipeline = ImageProcessing::MiniMagick.stack { |stack| stack.foo("bar") }
|
|
assert_equal :stack, pipeline.options[:operations].first[0]
|
|
assert_equal [], pipeline.options[:operations].first[1]
|
|
assert_instance_of Proc, pipeline.options[:operations].first[2]
|
|
end
|
|
|
|
it "accepts a list of commands" do
|
|
pipeline = ImageProcessing::Vips.source(fixture_image("rotated.jpg"))
|
|
|
|
# hash
|
|
actual1 = pipeline
|
|
.apply(
|
|
loader: { autorotate: true },
|
|
resize_to_fit: [400, 400, sharpen: false],
|
|
invert: true,
|
|
rot90: nil,
|
|
rot: :d90,
|
|
convert: "png",
|
|
)
|
|
.call
|
|
|
|
# array
|
|
actual2 = pipeline
|
|
.apply([
|
|
[:loader, { autorotate: true }],
|
|
[:resize_to_fit, [400, 400, sharpen: false]],
|
|
[:invert, true],
|
|
[:rot90, nil],
|
|
[:rot, :d90],
|
|
[:convert, "png"],
|
|
])
|
|
.call
|
|
|
|
expected = pipeline
|
|
.loader(autorotate: true)
|
|
.resize_to_fit(400, 400, sharpen: false)
|
|
.invert
|
|
.rot90
|
|
.rot(:d90)
|
|
.convert("png")
|
|
.call
|
|
|
|
assert_similar expected, actual1
|
|
assert_similar expected, actual2
|
|
end
|
|
|
|
it "applies a custom block" do
|
|
actual = ImageProcessing::Vips.custom(&:invert).call(@portrait)
|
|
expected = ImageProcessing::Vips.invert.call(@portrait)
|
|
assert_similar expected, actual
|
|
|
|
actual = ImageProcessing::Vips.custom { nil }.call(@portrait)
|
|
expected = ImageProcessing::Vips.call(@portrait)
|
|
assert_similar expected, actual
|
|
|
|
identity = ImageProcessing::Vips.custom.call(@portrait)
|
|
assert_similar @portrait, identity
|
|
end
|
|
|
|
it "merges different options" do
|
|
pipeline = ImageProcessing::Vips
|
|
.resize_to_fill(400, 400)
|
|
.convert("png")
|
|
|
|
assert_equal [[:resize_to_fill, [400, 400]]], pipeline.options[:operations]
|
|
assert_equal "png", pipeline.options[:format]
|
|
end
|
|
|
|
it "doesn't mutate the receiver when branching" do
|
|
pipeline_jpg = ImageProcessing::Vips.convert("jpg")
|
|
pipeline_png = pipeline_jpg.convert("png")
|
|
|
|
assert_equal "jpg", pipeline_jpg.options[:format]
|
|
assert_equal "png", pipeline_png.options[:format]
|
|
end
|
|
|
|
it "executes processing on #call with source" do
|
|
result = ImageProcessing::Vips.convert("png").call(@portrait)
|
|
assert_instance_of Tempfile, result
|
|
assert_type "PNG", result
|
|
end
|
|
|
|
it "executes processing on #call without source" do
|
|
result = ImageProcessing::Vips.source(@portrait).convert("png").call
|
|
assert_instance_of Tempfile, result
|
|
assert_type "PNG", result
|
|
end
|
|
|
|
it "executes processing on bang operation method" do
|
|
result = ImageProcessing::Vips.source(@portrait).convert!("png")
|
|
assert_instance_of Tempfile, result
|
|
assert_type "PNG", result
|
|
|
|
result = ImageProcessing::Vips.source(@portrait).shrink!(2, 2)
|
|
assert_instance_of Tempfile, result
|
|
assert_dimensions [300, 400], result
|
|
|
|
result = ImageProcessing::Vips.source(@portrait).resize_to_fill!(400, 400)
|
|
assert_instance_of Tempfile, result
|
|
assert_dimensions [400, 400], result
|
|
end
|
|
|
|
it "can be called directly" do
|
|
result = ImageProcessing::Vips.call(@portrait)
|
|
assert_similar result, @portrait
|
|
end
|
|
|
|
it "applies a sequence of operations" do
|
|
actual = ImageProcessing::Vips
|
|
.invert
|
|
.shrink(2, 2)
|
|
.call(@portrait)
|
|
|
|
expected = ImageProcessing::Vips.invert.call(@portrait)
|
|
expected = ImageProcessing::Vips.shrink(2, 2).call(expected)
|
|
|
|
assert_similar expected, actual
|
|
end
|
|
|
|
it "returns a rewinded and refreshed tempfile in binary mode" do
|
|
tempfile = ImageProcessing::Vips.convert("png").call(@portrait)
|
|
assert_instance_of Tempfile, tempfile
|
|
assert tempfile.binmode?
|
|
assert_equal 0, tempfile.pos
|
|
assert_equal File.binread(tempfile.path), tempfile.read
|
|
end
|
|
|
|
it "returns an intermediary object on #call(save: false)" do
|
|
vips_image = ImageProcessing::Vips.resize_to_limit(400, 400).call(@portrait, save: false)
|
|
assert_instance_of Vips::Image, vips_image
|
|
assert_equal [300, 400], vips_image.size
|
|
|
|
magick = ImageProcessing::MiniMagick.source(@portrait).resize_to_limit(400, 400).call(save: false)
|
|
assert_instance_of MiniMagick::Tool::Convert, magick
|
|
assert_includes magick.args, "400x400>"
|
|
end
|
|
|
|
it "raises exception when source was not provided" do
|
|
assert_raises(ImageProcessing::Error) do
|
|
ImageProcessing::Vips.call
|
|
end
|
|
end
|
|
|
|
it "raises exception when invalid source was provided" do
|
|
assert_raises(ImageProcessing::Error) do
|
|
ImageProcessing::Vips.source(:invalid).call
|
|
end
|
|
end
|
|
|
|
it "raises a NoMethodError when predicate method is not defined" do
|
|
assert_raises(NoMethodError) do
|
|
ImageProcessing::Vips.valid?(@portrait)
|
|
end
|
|
end
|
|
|
|
it "doesn't allow making system calls" do
|
|
ImageProcessing::Vips.source(@portrait).apply(system: "touch foo.txt")
|
|
refute File.exist?("foo.txt")
|
|
|
|
assert_raises Vips::Error do
|
|
ImageProcessing::Vips.source(@portrait).spawn("touch foo.txt").call
|
|
end
|
|
refute File.exist?("foo.txt")
|
|
|
|
assert_raises MiniMagick::Error do
|
|
ImageProcessing::MiniMagick.source(@portrait).spawn("touch foo.txt").call
|
|
end
|
|
refute File.exist?("foo.txt")
|
|
end
|
|
end
|