image_processing 1.14.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 613676364c0ba04b655a087a89296cec4604c8f362809ada23d984538f9e962b
4
- data.tar.gz: 77a768424b857418d131257ce2c539715aa69999da7e78319255d9eee3f68157
3
+ metadata.gz: a52c42f1463cc15caf0dfe57bc8a5efeb464006a2a29bbf1b8c842ada339e4aa
4
+ data.tar.gz: 2344e1015b02f4b24363371d8877bdcc4fb28b9fb820e15d2e2446f7874443c6
5
5
  SHA512:
6
- metadata.gz: ff2e1a9bf128d872ad2c5663525c4e970e8b18457f2b4f0a50588a9f33c736b0ef46a3fb4bd10638be5a276e9aa8bacbae09c9685b954416902de3687a3b2181
7
- data.tar.gz: c7efff88134c14c44001ea99ed7632ecc86b93bd3afe5e933cfdc8cb2d57a82fc69333192777ac17cdf9e5bf5ef15e012c17e6c7dffbd7124ed58d765e4bff4b
6
+ metadata.gz: 90c29ed45d475cc54f814a368e894504f5bc012835ef989d3678568dfb3cd29aca144e88cb17d102ffc82dcbd94c4d46de7d9a5fbc0474c0f6b15c481a1f4bb3
7
+ data.tar.gz: 8a7f991cf00725e1d5618add34f9b57814e2e273d8b9575f49e61701e80259ac8c2cd61028cdaefc6b4a6694a8a922d2246f5f6b8f7ee7184ff31bd0f8644813
data/CHANGELOG.md CHANGED
@@ -1,3 +1,35 @@
1
+ ## 2.1.0 (2026-09-01)
2
+
3
+ * [minimagick] Add `inherit_fds:` so a loader can name an already-open input (thanks to @flavorjones)
4
+
5
+ ## 2.0.3 (2026-08-06)
6
+
7
+ * Prevent remote code execution when operation names come from user input, closing bypasses through the `#operation` meta-builder, `#method_missing`, and nested `#send` calls (reported by @szymonsec)
8
+
9
+ * [minimagick] Prevent remote code execution through unsafe public methods (e.g. `#instance_eval`, `#send`) passed as loader/saver option names
10
+
11
+ ## 2.0.2 (2026-06-03)
12
+
13
+ * Raise `LoadError` instead of `ImageProcessing::Error` when soft dependencies are missing (@bdewater-thatch)
14
+
15
+ ## 2.0.1 (2026-05-22)
16
+
17
+ * [minimagick] Prevent remote shell execution when passing loader/saver options from user input
18
+
19
+ ## 2.0.0 (2026-05-20)
20
+
21
+ * `mini_magick`/`ruby-vips` are now soft dependencies and need to be manually added to the Gemfile (@janko)
22
+
23
+ * Avoid remote shell execution vulnerability in `#apply` when arguments are coming from user input (@janko)
24
+
25
+ * [vips] Unfuzzed loaders are now blocked by default (@janko)
26
+
27
+ * [vips] Sharpening after resize has been disabled by default (@janko)
28
+
29
+ * [minimagick] Remove deprecated `:compose` and `:geometry` keyword arguments for `#composite` (@janko)
30
+
31
+ * Ruby 3.0+ is now required (@janko)
32
+
1
33
  ## 1.14.0 (2025-02-10)
2
34
 
3
35
  * Add support for MiniMagick 5.x (@lukeasrodgers)
data/README.md CHANGED
@@ -3,11 +3,10 @@
3
3
  Provides higher-level image processing helpers that are commonly needed
4
4
  when handling image uploads.
5
5
 
6
- This gem can process images with either [ImageMagick]/[GraphicsMagick] or
7
- [libvips] libraries. ImageMagick is a good default choice, especially if you
8
- are migrating from another gem or library that uses ImageMagick. Libvips is a
9
- newer library that can process images [very rapidly][libvips performance]
10
- (often multiple times faster than ImageMagick).
6
+ This gem can process images with [ImageMagick] or [libvips]. ImageMagick is a
7
+ good default choice, especially if you are migrating from another gem or library
8
+ that uses ImageMagick. Libvips is a newer library that can process images [very
9
+ rapidly][libvips performance] (often multiple times faster than ImageMagick).
11
10
 
12
11
 
13
12
  ## Goal
@@ -16,7 +15,7 @@ The goal of this project is to have a single gem that contains all the
16
15
  helper methods needed to resize and process images.
17
16
 
18
17
  Currently, existing attachment gems (like Paperclip, CarrierWave, Refile,
19
- Dragonfly, ActiveStorage, and others) implement their own custom image
18
+ Dragonfly, Active Storage, and others) implement their own custom image
20
19
  helper methods. But why? That's not very DRY, is it?
21
20
 
22
21
  Let's be honest. Image processing is a dark, mysterious art. So we want to
@@ -32,19 +31,23 @@ how to resize and process images.
32
31
  In a Mac terminal:
33
32
 
34
33
  ```sh
35
- $ brew install imagemagick vips
34
+ $ brew install imagemagick # if using ImageMagick
35
+ $ brew install vips # if using libvips
36
36
  ```
37
37
 
38
38
  In a debian/ubuntu terminal:
39
39
 
40
40
  ```sh
41
- $ sudo apt install imagemagick libvips
41
+ $ sudo apt install imagemagick # if using ImageMagick
42
+ $ sudo apt install libvips # if using libvips
42
43
  ```
43
44
 
44
- 2. Add the gem to your Gemfile:
45
+ 2. Add the gem(s) to your Gemfile:
45
46
 
46
47
  ```rb
47
- gem "image_processing", "~> 1.0"
48
+ gem "image_processing", "~> 2.0"
49
+ gem "mini_magick", "~> 5.0" # if using ImageMagick
50
+ gem "ruby-vips", "~> 2.0" # if using libvips
48
51
  ```
49
52
 
50
53
 
@@ -66,7 +69,7 @@ processed = ImageProcessing::MiniMagick
66
69
  processed #=> #<Tempfile:/var/folders/.../image_processing20180316-18446-1j247h6.png>
67
70
  ```
68
71
 
69
- This allows easy branching when generating multiple derivates:
72
+ This allows easy branching when generating multiple derivatives:
70
73
 
71
74
  ```rb
72
75
  require "image_processing/vips"
@@ -187,22 +190,9 @@ pipeline
187
190
 
188
191
  ## Contributing
189
192
 
190
- Our test suite requires both `imagemagick` and `libvips` libraries to be installed.
193
+ Our test suite requires both `imagemagick` and `libvips` libraries to be installed. Afterwards you can run tests with:
191
194
 
192
- In a Mac terminal:
193
-
194
- ```
195
- $ brew install imagemagick vips
196
- ```
197
-
198
- In a debian/ubuntu terminal:
199
- ```shell
200
- sudo apt install imagemagick libvips
201
- ```
202
-
203
- Afterwards you can run tests with
204
-
205
- ```
195
+ ```sh
206
196
  $ bundle exec rake test
207
197
  ```
208
198
 
@@ -4,7 +4,7 @@ Gem::Specification.new do |spec|
4
4
  spec.name = "image_processing"
5
5
  spec.version = ImageProcessing::VERSION
6
6
 
7
- spec.required_ruby_version = ">= 2.3"
7
+ spec.required_ruby_version = ">= 3.0"
8
8
 
9
9
  spec.summary = "High-level wrapper for processing images for the web with ImageMagick or libvips."
10
10
  spec.description = "High-level wrapper for processing images for the web with ImageMagick or libvips."
@@ -19,12 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.metadata = { "changelog_uri" => spec.homepage + "/blob/master/CHANGELOG.md",
20
20
  "rubygems_mfa_required" => "true" }
21
21
 
22
- spec.add_dependency "mini_magick", ">= 4.9.5", "< 6"
23
- spec.add_dependency "ruby-vips", ">= 2.0.17", "< 3"
24
-
25
22
  spec.add_development_dependency "rake"
26
23
  spec.add_development_dependency "minitest", "~> 5.8"
27
24
  spec.add_development_dependency "minitest-hooks", ">= 1.4.2"
28
25
  spec.add_development_dependency "minispec-metadata"
29
- spec.add_development_dependency "dhash-vips"
30
26
  end
@@ -33,6 +33,10 @@ module ImageProcessing
33
33
  # .apply([[:resize_to_limit, [400, 400]], [:strip, true])
34
34
  def apply(operations)
35
35
  operations.inject(self) do |builder, (name, argument)|
36
+ if invalid_operation?(name)
37
+ fail ArgumentError, "#{name.inspect} is not a valid ImageProcessing operation"
38
+ end
39
+
36
40
  if argument == true || argument == nil
37
41
  builder.public_send(name)
38
42
  elsif argument.is_a?(Array)
@@ -81,15 +85,25 @@ module ImageProcessing
81
85
 
82
86
  private
83
87
 
88
+ # This prevents calling unsafe Ruby core methods such as `Kernel#system`,
89
+ # which would allow for remote shell execution. The processor enforces the
90
+ # same guard when operations are dispatched, which also covers paths that
91
+ # bypass this early check (e.g. the #operation meta-builder).
92
+ #
93
+ # Bang names are rejected here because they are Chainable's execution
94
+ # shortcut (see #method_missing), not operations to chain.
95
+ def invalid_operation?(name)
96
+ name.end_with?("!") || ImageProcessing.unsafe_method?(self, name)
97
+ end
98
+
84
99
  # Assume that any unknown method names an operation supported by the
85
100
  # processor. Add a bang ("!") if you want processing to be performed.
86
- def method_missing(name, *args, &block)
87
- return super if name.to_s.end_with?("?")
88
- return send(name.to_s.chomp("!"), *args, &block).call if name.to_s.end_with?("!")
101
+ def method_missing(name, ...)
102
+ return super if name.end_with?("?")
103
+ return public_send(name.to_s.chomp("!"), ...).call if name.end_with?("!")
89
104
 
90
- operation(name, *args, &block)
105
+ operation(name, ...)
91
106
  end
92
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
93
107
 
94
108
  # Empty options which the builder starts with.
95
109
  DEFAULT_OPTIONS = {
@@ -1,15 +1,28 @@
1
- require "mini_magick"
2
1
  require "image_processing"
2
+ begin
3
+ require "mini_magick"
4
+ rescue LoadError
5
+ raise LoadError, "ImageProcessing::MiniMagick requires the mini_magick gem. Please add `gem \"mini_magick\", \"~> 5.0\"` to your Gemfile."
6
+ end
3
7
 
4
8
  module ImageProcessing
5
9
  module MiniMagick
6
10
  extend Chainable
7
11
 
8
- def self.convert_shim(&block)
12
+ # mini_magick gained `inherit_fds:` on MiniMagick::Shell#execute in 5.4.0.
13
+ INHERIT_FDS_MINIMUM_VERSION = Gem::Version.new("5.4.0")
14
+
15
+ def self.convert_shim(inherit_fds: nil, &block)
16
+ if inherit_fds && ::MiniMagick.version < INHERIT_FDS_MINIMUM_VERSION
17
+ raise LoadError, "The `inherit_fds` loader option requires mini_magick #{INHERIT_FDS_MINIMUM_VERSION} or newer, but mini_magick #{::MiniMagick.version} is loaded. Please upgrade the gem."
18
+ end
19
+
20
+ options = inherit_fds ? { inherit_fds: inherit_fds } : {}
21
+
9
22
  if ::MiniMagick.respond_to?(:convert)
10
- ::MiniMagick.convert(&block)
23
+ ::MiniMagick.convert(**options, &block)
11
24
  else
12
- ::MiniMagick::Tool::Convert.new(&block)
25
+ ::MiniMagick::Tool::Convert.new(**options, &block)
13
26
  end
14
27
  end
15
28
 
@@ -33,12 +46,16 @@ module ImageProcessing
33
46
  # Initializes the image on disk into a MiniMagick::Tool object. Accepts
34
47
  # additional options related to loading the image (e.g. geometry).
35
48
  # Additionally auto-orients the image to be upright.
36
- def self.load_image(path_or_magick, loader: nil, page: nil, geometry: nil, auto_orient: true, **options)
49
+ # `inherit_fds` names IO objects the tool inherits, so the source may be a
50
+ # `/dev/fd/N` path. The source stays a path, so `loader`, `page` and
51
+ # `geometry` still apply to it, which they would not if the caller passed
52
+ # a pre-built MiniMagick::Tool carrying the descriptor.
53
+ def self.load_image(path_or_magick, loader: nil, page: nil, geometry: nil, auto_orient: true, inherit_fds: nil, **options)
37
54
  if path_or_magick.is_a?(::MiniMagick::Tool)
38
55
  magick = path_or_magick
39
56
  else
40
57
  source_path = path_or_magick
41
- magick = ::ImageProcessing::MiniMagick.convert_shim
58
+ magick = ::ImageProcessing::MiniMagick.convert_shim(inherit_fds: inherit_fds)
42
59
 
43
60
  Utils.apply_options(magick, **options)
44
61
 
@@ -120,18 +137,9 @@ module ImageProcessing
120
137
  # Overlays the specified image over the current one. Supports specifying
121
138
  # an additional mask, composite mode, direction or offset of the overlay
122
139
  # image.
123
- def composite(overlay = :none, mask: nil, mode: nil, gravity: nil, offset: nil, args: nil, **options, &block)
140
+ def composite(overlay = :none, mask: nil, mode: nil, gravity: nil, offset: nil, args: nil, &block)
124
141
  return magick.composite if overlay == :none
125
142
 
126
- if options.key?(:compose)
127
- warn "[IMAGE_PROCESSING] The :compose parameter in #composite has been renamed to :mode, the :compose alias will be removed in ImageProcessing 2."
128
- mode = options[:compose]
129
- end
130
-
131
- if options.key?(:geometry)
132
- warn "[IMAGE_PROCESSING] The :geometry parameter in #composite has been deprecated and will be removed in ImageProcessing 2. Use :offset instead, e.g. `geometry: \"+10+15\"` should be replaced with `offset: [10, 15]`."
133
- geometry = options[:geometry]
134
- end
135
143
  geometry = "%+d%+d" % offset if offset
136
144
 
137
145
  overlay_path = convert_to_path(overlay, "overlay")
@@ -230,10 +238,17 @@ module ImageProcessing
230
238
  # Applies options from the provided hash.
231
239
  def apply_options(magick, define: {}, **options)
232
240
  options.each do |option, value|
241
+ # Loader/saver option names may come from user input, so guard
242
+ # against dispatching to unsafe Ruby core methods (e.g. #send,
243
+ # #instance_eval) that public_send does not block on its own.
244
+ if ImageProcessing.unsafe_method?(magick, option)
245
+ raise Error, "#{option.inspect} is not a valid option"
246
+ end
247
+
233
248
  case value
234
- when true, nil then magick.send(option)
235
- when false then magick.send(option).+
236
- else magick.send(option, *value)
249
+ when true, nil then magick.public_send(option)
250
+ when false then magick.public_send(option).+
251
+ else magick.public_send(option, *value)
237
252
  end
238
253
  end
239
254
 
@@ -53,6 +53,15 @@ module ImageProcessing
53
53
  # operation directly on the accumulator object. This provides a common
54
54
  # umbrella above defined macros and direct operations.
55
55
  def apply_operation(name, *args, &block)
56
+ # Chainable performs the same check when operations are built, but that
57
+ # guard only sees the outer builder method name, so it can be routed
58
+ # around via the #operation meta-builder, #method_missing, or a nested
59
+ # #send. This is the single point every operation is finally dispatched
60
+ # through, so enforcing the guard here closes all of those paths.
61
+ if ImageProcessing.unsafe_method?(self, name)
62
+ fail Error, "#{name.inspect} is not a valid operation"
63
+ end
64
+
56
65
  receiver = respond_to?(name) ? self : @accumulator
57
66
 
58
67
  if args.last.is_a?(Hash)
@@ -1,3 +1,3 @@
1
1
  module ImageProcessing
2
- VERSION = "1.14.0"
2
+ VERSION = "2.1.0"
3
3
  end
@@ -1,7 +1,11 @@
1
- require "vips"
2
1
  require "image_processing"
2
+ begin
3
+ require "vips"
4
+ rescue LoadError
5
+ raise LoadError, "ImageProcessing::Vips requires the ruby-vips gem. Please add `gem \"ruby-vips\", \"~> 2.0\"` to your Gemfile."
6
+ end
3
7
 
4
- fail "image_processing/vips requires libvips 8.6+" unless Vips.at_least_libvips?(8, 6)
8
+ Vips.block_untrusted(true) if Vips.respond_to?(:block_untrusted) && !ENV["VIPS_BLOCK_UNTRUSTED"]
5
9
 
6
10
  module ImageProcessing
7
11
  module Vips
@@ -155,7 +159,7 @@ module ImageProcessing
155
159
 
156
160
  # Resizes the image according to the specified parameters, and sharpens
157
161
  # the resulting thumbnail.
158
- def thumbnail(width, height, sharpen: SHARPEN_MASK, **options)
162
+ def thumbnail(width, height, sharpen: nil, **options)
159
163
  if self.image.is_a?(String) # path
160
164
  # resize on load
161
165
  image = ::Vips::Image.thumbnail(self.image, width, height: height, **options)
@@ -167,7 +171,11 @@ module ImageProcessing
167
171
  image = self.image.thumbnail_image(width, height: height, **options)
168
172
  end
169
173
 
170
- image = image.conv(sharpen, precision: :integer) if sharpen
174
+ if sharpen
175
+ sharpen_mask = sharpen.is_a?(TrueClass) ? SHARPEN_MASK : sharpen
176
+ image = image.conv(sharpen_mask, precision: :integer)
177
+ end
178
+
171
179
  image
172
180
  end
173
181
 
@@ -7,6 +7,25 @@ require "image_processing/version"
7
7
  module ImageProcessing
8
8
  Error = Class.new(StandardError)
9
9
 
10
- autoload :MiniMagick, 'image_processing/mini_magick'
11
- autoload :Vips, 'image_processing/vips'
10
+ # Method owners considered unsafe to dispatch to when a method name comes
11
+ # from user input, because they expose Ruby's metaprogramming and shell
12
+ # primitives (e.g. Kernel#send, Kernel#eval, BasicObject#instance_eval,
13
+ # Kernel#system). Any name whose implementation is inherited from one of
14
+ # these should never be treated as an image operation or a loader/saver
15
+ # option.
16
+ UNSAFE_METHOD_OWNERS = [BasicObject, Kernel, Object, Module].freeze
17
+
18
+ # Whether calling +name+ on +receiver+ would dispatch to an unsafe Ruby core
19
+ # method rather than a genuine operation. Used as a single, shared guard at
20
+ # every place where a user-provided name is dispatched via +public_send+.
21
+ def self.unsafe_method?(receiver, name)
22
+ UNSAFE_METHOD_OWNERS.include?(receiver.method(name.to_s).owner)
23
+ rescue NameError
24
+ # An unknown name is not a Ruby core method, so it is safe to forward to
25
+ # the receiver (which will reject it if it is not a real operation).
26
+ false
27
+ end
28
+
29
+ autoload :MiniMagick, "image_processing/mini_magick"
30
+ autoload :Vips, "image_processing/vips"
12
31
  end
metadata CHANGED
@@ -1,54 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: image_processing
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.14.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-10 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: mini_magick
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - ">="
17
- - !ruby/object:Gem::Version
18
- version: 4.9.5
19
- - - "<"
20
- - !ruby/object:Gem::Version
21
- version: '6'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- version: 4.9.5
29
- - - "<"
30
- - !ruby/object:Gem::Version
31
- version: '6'
32
- - !ruby/object:Gem::Dependency
33
- name: ruby-vips
34
- requirement: !ruby/object:Gem::Requirement
35
- requirements:
36
- - - ">="
37
- - !ruby/object:Gem::Version
38
- version: 2.0.17
39
- - - "<"
40
- - !ruby/object:Gem::Version
41
- version: '3'
42
- type: :runtime
43
- prerelease: false
44
- version_requirements: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - ">="
47
- - !ruby/object:Gem::Version
48
- version: 2.0.17
49
- - - "<"
50
- - !ruby/object:Gem::Version
51
- version: '3'
52
12
  - !ruby/object:Gem::Dependency
53
13
  name: rake
54
14
  requirement: !ruby/object:Gem::Requirement
@@ -105,20 +65,6 @@ dependencies:
105
65
  - - ">="
106
66
  - !ruby/object:Gem::Version
107
67
  version: '0'
108
- - !ruby/object:Gem::Dependency
109
- name: dhash-vips
110
- requirement: !ruby/object:Gem::Requirement
111
- requirements:
112
- - - ">="
113
- - !ruby/object:Gem::Version
114
- version: '0'
115
- type: :development
116
- prerelease: false
117
- version_requirements: !ruby/object:Gem::Requirement
118
- requirements:
119
- - - ">="
120
- - !ruby/object:Gem::Version
121
- version: '0'
122
68
  description: High-level wrapper for processing images for the web with ImageMagick
123
69
  or libvips.
124
70
  email:
@@ -152,14 +98,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
152
98
  requirements:
153
99
  - - ">="
154
100
  - !ruby/object:Gem::Version
155
- version: '2.3'
101
+ version: '3.0'
156
102
  required_rubygems_version: !ruby/object:Gem::Requirement
157
103
  requirements:
158
104
  - - ">="
159
105
  - !ruby/object:Gem::Version
160
106
  version: '0'
161
107
  requirements: []
162
- rubygems_version: 3.6.2
108
+ rubygems_version: 4.0.13
163
109
  specification_version: 4
164
110
  summary: High-level wrapper for processing images for the web with ImageMagick or
165
111
  libvips.