safe_image 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +21 -12
- data/README.md +140 -287
- data/SECURITY.md +22 -11
- data/docs/architecture.md +63 -0
- data/ext/safe_image_vips_helper/extconf.rb +43 -0
- data/ext/safe_image_vips_helper/safe_image_vips_helper.c +1007 -0
- data/lib/safe_image/api/metadata.rb +85 -0
- data/lib/safe_image/api/transform.rb +152 -0
- data/lib/safe_image/backend_label.rb +24 -0
- data/lib/safe_image/formats.rb +96 -0
- data/lib/safe_image/ico.rb +42 -40
- data/lib/safe_image/image_magick_backend.rb +219 -162
- data/lib/safe_image/jpegli_backend.rb +64 -44
- data/lib/safe_image/metadata_operations.rb +155 -0
- data/lib/safe_image/native.rb +96 -290
- data/lib/safe_image/native_helper.rb +281 -0
- data/lib/safe_image/operation_backends/base.rb +83 -0
- data/lib/safe_image/operation_backends/image_magick.rb +123 -0
- data/lib/safe_image/operation_backends/vips.rb +251 -0
- data/lib/safe_image/operation_backends.rb +27 -0
- data/lib/safe_image/operation_set.rb +22 -0
- data/lib/safe_image/optimizer.rb +225 -98
- data/lib/safe_image/path_safety.rb +11 -0
- data/lib/safe_image/processor.rb +122 -85
- data/lib/safe_image/quality_defaults.rb +23 -0
- data/lib/safe_image/remote.rb +248 -144
- data/lib/safe_image/result.rb +71 -23
- data/lib/safe_image/runner.rb +60 -23
- data/lib/safe_image/sandbox.rb +44 -218
- data/lib/safe_image/staged_output.rb +44 -0
- data/lib/safe_image/svg_metadata.rb +74 -69
- data/lib/safe_image/transform_operations.rb +139 -0
- data/lib/safe_image/version.rb +1 -1
- data/lib/safe_image/vips_backend.rb +13 -12
- data/lib/safe_image.rb +62 -306
- metadata +43 -37
- data/lib/safe_image/discourse_compat.rb +0 -441
- data/lib/safe_image/svg_css.rb +0 -314
- data/lib/safe_image/svg_sanitizer.rb +0 -583
- data/lib/safe_image/vips_glue.rb +0 -361
- data/lib/safe_image/zygote.rb +0 -619
data/lib/safe_image.rb
CHANGED
|
@@ -3,22 +3,40 @@
|
|
|
3
3
|
require_relative "safe_image/version"
|
|
4
4
|
|
|
5
5
|
module SafeImage
|
|
6
|
-
class Error < StandardError
|
|
6
|
+
class Error < StandardError
|
|
7
|
+
attr_reader :original_error_class, :stderr_tail
|
|
8
|
+
|
|
9
|
+
def initialize(message = nil, original_error_class: nil, stderr_tail: nil)
|
|
10
|
+
super(message)
|
|
11
|
+
@original_error_class = original_error_class
|
|
12
|
+
@stderr_tail = stderr_tail
|
|
13
|
+
end
|
|
14
|
+
end
|
|
7
15
|
|
|
8
16
|
# Raised when any operation is attempted before SafeImage.configure!.
|
|
9
|
-
class NotConfiguredError < Error
|
|
17
|
+
class NotConfiguredError < Error
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class UnsupportedFormatError < Error
|
|
21
|
+
end
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
# Raised when the native libvips helper cannot initialize or execute.
|
|
24
|
+
# configure!(backend: :vips) surfaces this at boot; operations never fall back
|
|
25
|
+
# to ImageMagick.
|
|
26
|
+
class VipsUnavailableError < UnsupportedFormatError
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class UnsafePathError < Error
|
|
30
|
+
end
|
|
12
31
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class
|
|
17
|
-
|
|
18
|
-
class LimitError < Error; end
|
|
32
|
+
class InvalidImageError < Error
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class LimitError < Error
|
|
36
|
+
end
|
|
19
37
|
|
|
20
38
|
# Default decompression-bomb ceiling when configure! is not given an explicit
|
|
21
|
-
# max_pixels. Mirrored in the native
|
|
39
|
+
# max_pixels. Mirrored in the native helper (SAFE_IMAGE_DEFAULT_MAX_PIXELS)
|
|
22
40
|
# and aligned with the 128MP area limit on the ImageMagick path. Per-call
|
|
23
41
|
# max_pixels: overrides the configured value.
|
|
24
42
|
DEFAULT_MAX_PIXELS = 128 * 1024 * 1024
|
|
@@ -33,40 +51,49 @@ end
|
|
|
33
51
|
|
|
34
52
|
require_relative "safe_image/native"
|
|
35
53
|
require_relative "safe_image/result"
|
|
54
|
+
require_relative "safe_image/quality_defaults"
|
|
36
55
|
require_relative "safe_image/runner"
|
|
37
|
-
require_relative "safe_image/sandbox"
|
|
38
|
-
require_relative "safe_image/zygote"
|
|
39
56
|
require_relative "safe_image/path_safety"
|
|
57
|
+
require_relative "safe_image/formats"
|
|
58
|
+
require_relative "safe_image/backend_label"
|
|
59
|
+
require_relative "safe_image/operation_set"
|
|
60
|
+
require_relative "safe_image/staged_output"
|
|
61
|
+
require_relative "safe_image/sandbox"
|
|
62
|
+
require_relative "safe_image/native_helper"
|
|
40
63
|
require_relative "safe_image/optimizer"
|
|
41
64
|
require_relative "safe_image/svg_metadata"
|
|
42
|
-
require_relative "safe_image/svg_css"
|
|
43
|
-
require_relative "safe_image/svg_sanitizer"
|
|
44
65
|
require_relative "safe_image/remote"
|
|
45
66
|
require_relative "safe_image/ico"
|
|
46
67
|
require_relative "safe_image/image_magick_backend"
|
|
47
68
|
require_relative "safe_image/jpegli_backend"
|
|
48
69
|
require_relative "safe_image/vips_backend"
|
|
49
70
|
require_relative "safe_image/processor"
|
|
50
|
-
require_relative "safe_image/
|
|
71
|
+
require_relative "safe_image/operation_backends"
|
|
72
|
+
require_relative "safe_image/metadata_operations"
|
|
73
|
+
require_relative "safe_image/transform_operations"
|
|
74
|
+
require_relative "safe_image/api/metadata"
|
|
75
|
+
require_relative "safe_image/api/transform"
|
|
51
76
|
|
|
52
77
|
module SafeImage
|
|
78
|
+
private_constant :API
|
|
79
|
+
|
|
53
80
|
module_function
|
|
54
81
|
|
|
55
82
|
@config = nil
|
|
56
83
|
|
|
57
84
|
# Decides, in one place, everything that varies by host: which backend
|
|
58
|
-
# decodes untrusted bytes, whether
|
|
59
|
-
#
|
|
60
|
-
#
|
|
85
|
+
# decodes untrusted bytes, whether child helpers/tools run under Landlock,
|
|
86
|
+
# and the default decompression-bomb ceiling. Must be called before any
|
|
87
|
+
# operation; calling it again replaces the configuration.
|
|
61
88
|
#
|
|
62
89
|
# Validation is eager so a misconfigured host fails at boot rather than on
|
|
63
90
|
# the first request.
|
|
64
91
|
def configure!(backend:, landlock:, max_pixels: DEFAULT_MAX_PIXELS)
|
|
65
92
|
backend = backend.to_sym
|
|
66
|
-
|
|
93
|
+
if BACKENDS.none? { |candidate| candidate == backend }
|
|
67
94
|
raise ArgumentError, "unknown backend: #{backend.inspect} (expected :vips or :imagemagick)"
|
|
68
95
|
end
|
|
69
|
-
unless [true, false].
|
|
96
|
+
unless [true, false].any? { |candidate| candidate == landlock }
|
|
70
97
|
raise ArgumentError, "landlock must be true or false, got: #{landlock.inspect}"
|
|
71
98
|
end
|
|
72
99
|
max_pixels = Integer(max_pixels)
|
|
@@ -75,9 +102,9 @@ module SafeImage
|
|
|
75
102
|
case backend
|
|
76
103
|
when :vips
|
|
77
104
|
begin
|
|
78
|
-
|
|
79
|
-
rescue
|
|
80
|
-
raise Error, "backend: :vips requested but libvips is unavailable: #{e.message}"
|
|
105
|
+
NativeHelper.verify!
|
|
106
|
+
rescue Error => e
|
|
107
|
+
raise Error, "backend: :vips requested but the native libvips helper is unavailable: #{e.message}"
|
|
81
108
|
end
|
|
82
109
|
when :imagemagick
|
|
83
110
|
unless Runner.available?("magick") || Runner.available?("convert")
|
|
@@ -87,305 +114,34 @@ module SafeImage
|
|
|
87
114
|
if landlock && !Sandbox.available?
|
|
88
115
|
raise Error, "landlock: true requested but the Landlock sandbox is unavailable on this host"
|
|
89
116
|
end
|
|
90
|
-
|
|
91
|
-
# The zygote bakes the backend and max_pixels in at boot; a reconfigure
|
|
92
|
-
# must not serve from a stale one.
|
|
93
|
-
Zygote.shutdown!
|
|
117
|
+
NativeHelper.ensure_available! if landlock && backend == :vips
|
|
94
118
|
|
|
95
119
|
@config = Config.new(backend: backend, landlock: landlock, max_pixels: max_pixels)
|
|
96
120
|
end
|
|
97
121
|
|
|
98
122
|
def config
|
|
99
|
-
@config ||
|
|
123
|
+
@config ||
|
|
124
|
+
raise(
|
|
125
|
+
NotConfiguredError,
|
|
126
|
+
"call SafeImage.configure!(backend: :vips | :imagemagick, landlock: true | false) before using SafeImage"
|
|
127
|
+
)
|
|
100
128
|
end
|
|
101
129
|
|
|
102
130
|
def configured? = !@config.nil?
|
|
103
131
|
|
|
104
132
|
def sandbox_available? = Sandbox.available?
|
|
105
133
|
|
|
106
|
-
# Internal: whether
|
|
107
|
-
# before configure!
|
|
108
|
-
# commands) and inside worker children (so sandboxed operations never nest).
|
|
134
|
+
# Internal: whether child commands/helpers must run under Landlock. False
|
|
135
|
+
# before configure! so configure!'s own availability probes can run.
|
|
109
136
|
def sandbox?
|
|
110
|
-
!!@config&.landlock
|
|
137
|
+
!!@config&.landlock
|
|
111
138
|
end
|
|
112
139
|
|
|
113
140
|
# Internal: per-call max_pixels overrides the configured default.
|
|
114
|
-
def resolved_max_pixels(max_pixels)
|
|
141
|
+
def resolved_max_pixels(max_pixels, config: self.config)
|
|
115
142
|
max_pixels.nil? ? config.max_pixels : max_pixels
|
|
116
143
|
end
|
|
117
144
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return yield unless sandbox?
|
|
121
|
-
|
|
122
|
-
Sandbox.public_call!(operation, args: args, kwargs: kwargs)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def probe(path, max_pixels: nil)
|
|
126
|
-
maybe_sandbox(:probe, args: [path], kwargs: { max_pixels: max_pixels }) do
|
|
127
|
-
path = PathSafety.local_path(path)
|
|
128
|
-
max_pixels = resolved_max_pixels(max_pixels)
|
|
129
|
-
|
|
130
|
-
case File.extname(path).downcase
|
|
131
|
-
when ".svg"
|
|
132
|
-
info = SvgMetadata.probe(path, max_pixels: max_pixels)
|
|
133
|
-
Result.new(
|
|
134
|
-
input: File.expand_path(path),
|
|
135
|
-
output: nil,
|
|
136
|
-
input_format: "svg",
|
|
137
|
-
output_format: nil,
|
|
138
|
-
width: info.fetch(:width),
|
|
139
|
-
height: info.fetch(:height),
|
|
140
|
-
filesize: File.size(path),
|
|
141
|
-
backend: "svg-metadata",
|
|
142
|
-
duration_ms: info.fetch(:duration_ms),
|
|
143
|
-
optimizer: nil
|
|
144
|
-
)
|
|
145
|
-
when ".ico"
|
|
146
|
-
# Pure-Ruby directory parse; reports the largest entry's dimensions.
|
|
147
|
-
info = Ico.probe(path, max_pixels: max_pixels)
|
|
148
|
-
Result.new(
|
|
149
|
-
input: File.expand_path(path),
|
|
150
|
-
output: nil,
|
|
151
|
-
input_format: "ico",
|
|
152
|
-
output_format: nil,
|
|
153
|
-
width: info.fetch(:width),
|
|
154
|
-
height: info.fetch(:height),
|
|
155
|
-
filesize: File.size(path),
|
|
156
|
-
backend: "ico-metadata",
|
|
157
|
-
duration_ms: info.fetch(:duration_ms),
|
|
158
|
-
optimizer: nil
|
|
159
|
-
)
|
|
160
|
-
else
|
|
161
|
-
case config.backend
|
|
162
|
-
when :vips
|
|
163
|
-
Processor.new(max_pixels: max_pixels).probe(path)
|
|
164
|
-
when :imagemagick
|
|
165
|
-
info = ImageMagickBackend.probe(path, max_pixels: max_pixels)
|
|
166
|
-
Result.new(
|
|
167
|
-
input: File.expand_path(path),
|
|
168
|
-
output: nil,
|
|
169
|
-
input_format: info.fetch(:input_format),
|
|
170
|
-
output_format: nil,
|
|
171
|
-
width: info.fetch(:width),
|
|
172
|
-
height: info.fetch(:height),
|
|
173
|
-
filesize: File.size(path),
|
|
174
|
-
backend: "imagemagick",
|
|
175
|
-
duration_ms: info.fetch(:duration_ms),
|
|
176
|
-
optimizer: nil
|
|
177
|
-
)
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def type(path, max_pixels: nil)
|
|
184
|
-
maybe_sandbox(:type, args: [path], kwargs: { max_pixels: max_pixels }) do
|
|
185
|
-
fastimage_type(probe(path, max_pixels: max_pixels).input_format)
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def size(path, max_pixels: nil)
|
|
190
|
-
maybe_sandbox(:size, args: [path], kwargs: { max_pixels: max_pixels }) do
|
|
191
|
-
result = probe(path, max_pixels: max_pixels)
|
|
192
|
-
[result.width, result.height]
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
def dimensions(path, max_pixels: nil)
|
|
197
|
-
size(path, max_pixels: max_pixels)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def info(path, max_pixels: nil, animated: false, orientation: false)
|
|
201
|
-
maybe_sandbox(:info, args: [path], kwargs: { max_pixels: max_pixels, animated: animated, orientation: orientation }) do
|
|
202
|
-
result = probe(path, max_pixels: max_pixels)
|
|
203
|
-
type = fastimage_type(result.input_format)
|
|
204
|
-
Info.new(
|
|
205
|
-
path: result.input,
|
|
206
|
-
type: type,
|
|
207
|
-
width: result.width,
|
|
208
|
-
height: result.height,
|
|
209
|
-
size: [result.width, result.height],
|
|
210
|
-
animated: animated ? animated?(path, max_pixels: max_pixels) : nil,
|
|
211
|
-
orientation: orientation ? orientation(path, max_pixels: max_pixels) : nil
|
|
212
|
-
)
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
def orientation(path, max_pixels: nil)
|
|
217
|
-
maybe_sandbox(:orientation, args: [path], kwargs: { max_pixels: max_pixels }) do
|
|
218
|
-
case File.extname(PathSafety.local_path(path)).downcase
|
|
219
|
-
when ".svg", ".ico"
|
|
220
|
-
# No EXIF orientation in either format; upright by definition.
|
|
221
|
-
1
|
|
222
|
-
else
|
|
223
|
-
max_pixels = resolved_max_pixels(max_pixels)
|
|
224
|
-
case config.backend
|
|
225
|
-
when :vips
|
|
226
|
-
# Header-only native read.
|
|
227
|
-
VipsBackend.orientation(path, max_pixels: max_pixels)
|
|
228
|
-
when :imagemagick
|
|
229
|
-
# Probe first: rejects undecodable files and enforces the pixel cap.
|
|
230
|
-
ImageMagickBackend.probe(path, max_pixels: max_pixels)
|
|
231
|
-
ImageMagickBackend.orientation(path)
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def dominant_color(path, max_pixels: nil)
|
|
238
|
-
maybe_sandbox(:dominant_color, args: [path], kwargs: { max_pixels: max_pixels }) do
|
|
239
|
-
max_pixels = resolved_max_pixels(max_pixels)
|
|
240
|
-
case config.backend
|
|
241
|
-
when :vips
|
|
242
|
-
if File.extname(PathSafety.local_path(path)).downcase == ".ico"
|
|
243
|
-
# Pure-Ruby ICO decode; vips only averages the decoded pixels.
|
|
244
|
-
Ico.dominant_color(path, max_pixels: max_pixels)
|
|
245
|
-
else
|
|
246
|
-
VipsBackend.dominant_color(path, max_pixels: max_pixels)
|
|
247
|
-
end
|
|
248
|
-
when :imagemagick
|
|
249
|
-
imagemagick_dominant_color(path, max_pixels: max_pixels)
|
|
250
|
-
end
|
|
251
|
-
end
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
def imagemagick_dominant_color(path, max_pixels:)
|
|
255
|
-
# Probe first: rejects undecodable files and enforces the pixel cap
|
|
256
|
-
# before ImageMagick fully decodes the image to average it.
|
|
257
|
-
probe(path, max_pixels: max_pixels)
|
|
258
|
-
ImageMagickBackend.dominant_color(path)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
def fastimage_type(format)
|
|
262
|
-
format.to_s == "jpg" ? :jpeg : format.to_s.to_sym
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
def remote_info(url, **kwargs)
|
|
266
|
-
config
|
|
267
|
-
Remote.info(url, **kwargs)
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
def remote_size(url, **kwargs)
|
|
271
|
-
config
|
|
272
|
-
Remote.size(url, **kwargs)
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
def remote_dimensions(url, **kwargs)
|
|
276
|
-
remote_size(url, **kwargs)
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
def remote_type(url, **kwargs)
|
|
280
|
-
config
|
|
281
|
-
Remote.type(url, **kwargs)
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
def remote_animated?(url, **kwargs)
|
|
285
|
-
config
|
|
286
|
-
Remote.animated?(url, **kwargs)
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
def remote_dominant_color(url, **kwargs)
|
|
290
|
-
config
|
|
291
|
-
Remote.dominant_color(url, **kwargs)
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
def fetch_remote(url, **kwargs, &block)
|
|
295
|
-
config
|
|
296
|
-
Remote.fetch(url, **kwargs, &block)
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
def thumbnail(input:, output:, width:, height:, format: nil, quality: 85, max_pixels: nil, optimize: false, optimize_mode: :lossless, chroma_subsampling: :auto)
|
|
300
|
-
maybe_sandbox(
|
|
301
|
-
:thumbnail,
|
|
302
|
-
kwargs: {
|
|
303
|
-
input: input,
|
|
304
|
-
output: output,
|
|
305
|
-
width: width,
|
|
306
|
-
height: height,
|
|
307
|
-
format: format,
|
|
308
|
-
quality: quality,
|
|
309
|
-
max_pixels: max_pixels,
|
|
310
|
-
optimize: optimize,
|
|
311
|
-
optimize_mode: optimize_mode,
|
|
312
|
-
chroma_subsampling: chroma_subsampling
|
|
313
|
-
}
|
|
314
|
-
) do
|
|
315
|
-
Processor.new(max_pixels: resolved_max_pixels(max_pixels), chroma_subsampling: chroma_subsampling).thumbnail(
|
|
316
|
-
input: input,
|
|
317
|
-
output: output,
|
|
318
|
-
width: width,
|
|
319
|
-
height: height,
|
|
320
|
-
format: format,
|
|
321
|
-
quality: quality,
|
|
322
|
-
optimize: optimize,
|
|
323
|
-
optimize_mode: optimize_mode
|
|
324
|
-
)
|
|
325
|
-
end
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
def optimize(path, mode: :lossless, strip_metadata: true, quality: nil, strict: true)
|
|
329
|
-
maybe_sandbox(:optimize, args: [path], kwargs: { mode: mode, strip_metadata: strip_metadata, quality: quality, strict: strict }) do
|
|
330
|
-
Optimizer.optimize(path, mode: mode, strip_metadata: strip_metadata, quality: quality, strict: strict)
|
|
331
|
-
end
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
def resize(*args, **kwargs)
|
|
335
|
-
maybe_sandbox(:resize, args: args, kwargs: kwargs) { DiscourseCompat.resize(*args, **kwargs) }
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
def crop(*args, **kwargs)
|
|
339
|
-
maybe_sandbox(:crop, args: args, kwargs: kwargs) { DiscourseCompat.crop(*args, **kwargs) }
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
def downsize(*args, **kwargs)
|
|
343
|
-
maybe_sandbox(:downsize, args: args, kwargs: kwargs) { DiscourseCompat.downsize(*args, **kwargs) }
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
def convert(*args, **kwargs)
|
|
347
|
-
maybe_sandbox(:convert, args: args, kwargs: kwargs) { DiscourseCompat.convert(*args, **kwargs) }
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
def convert_to_jpeg(*args, **kwargs)
|
|
351
|
-
maybe_sandbox(:convert_to_jpeg, args: args, kwargs: kwargs) { DiscourseCompat.convert_to_jpeg(*args, **kwargs) }
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
def fix_orientation(*args, **kwargs)
|
|
355
|
-
maybe_sandbox(:fix_orientation, args: args, kwargs: kwargs) { DiscourseCompat.fix_orientation(*args, **kwargs) }
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
def convert_favicon_to_png(*args, **kwargs)
|
|
359
|
-
maybe_sandbox(:convert_favicon_to_png, args: args, kwargs: kwargs) { DiscourseCompat.convert_favicon_to_png(*args, **kwargs) }
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
def frame_count(*args, **kwargs)
|
|
363
|
-
maybe_sandbox(:frame_count, args: args, kwargs: kwargs) { DiscourseCompat.frame_count(*args, **kwargs) }
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
def animated?(*args, **kwargs)
|
|
367
|
-
config
|
|
368
|
-
path = args.first
|
|
369
|
-
return false if path && File.extname(PathSafety.local_path(path)).downcase == ".svg"
|
|
370
|
-
|
|
371
|
-
maybe_sandbox(:animated?, args: args, kwargs: kwargs) { DiscourseCompat.animated?(*args, **kwargs) }
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
def letter_avatar(*args, **kwargs)
|
|
375
|
-
maybe_sandbox(:letter_avatar, args: args, kwargs: kwargs) { DiscourseCompat.letter_avatar(*args, **kwargs) }
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
def optimize_image!(*args, **kwargs)
|
|
379
|
-
maybe_sandbox(:optimize_image!, args: args, kwargs: kwargs) { DiscourseCompat.optimize_image!(*args, **kwargs) }
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
def sanitize_svg!(*args, **kwargs)
|
|
383
|
-
# Validate the required id_namespace in the parent (after the configured
|
|
384
|
-
# check) so omitting/malformed values raise ArgumentError consistently —
|
|
385
|
-
# otherwise, under the sandbox, the worker raises and it surfaces as a
|
|
386
|
-
# sandbox CommandError instead of the documented ArgumentError.
|
|
387
|
-
config
|
|
388
|
-
SvgSanitizer.resolve_namespace(kwargs.fetch(:id_namespace, SvgSanitizer::NAMESPACE_REQUIRED))
|
|
389
|
-
maybe_sandbox(:sanitize_svg!, args: args, kwargs: kwargs) { SvgSanitizer.sanitize!(*args, **kwargs) }
|
|
390
|
-
end
|
|
145
|
+
extend API::Metadata
|
|
146
|
+
extend API::Transform
|
|
391
147
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: safe_image
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Saffron
|
|
@@ -10,20 +10,6 @@ bindir: bin
|
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: fiddle
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1.0'
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '1.0'
|
|
27
13
|
- !ruby/object:Gem::Dependency
|
|
28
14
|
name: nokogiri
|
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -39,112 +25,132 @@ dependencies:
|
|
|
39
25
|
- !ruby/object:Gem::Version
|
|
40
26
|
version: '1.16'
|
|
41
27
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
28
|
+
name: minitest
|
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
|
44
30
|
requirements:
|
|
45
31
|
- - "~>"
|
|
46
32
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
33
|
+
version: '5.25'
|
|
48
34
|
type: :development
|
|
49
35
|
prerelease: false
|
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
37
|
requirements:
|
|
52
38
|
- - "~>"
|
|
53
39
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
40
|
+
version: '5.25'
|
|
55
41
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
42
|
+
name: rake
|
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
|
58
44
|
requirements:
|
|
59
45
|
- - "~>"
|
|
60
46
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
47
|
+
version: '13.0'
|
|
62
48
|
type: :development
|
|
63
49
|
prerelease: false
|
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
51
|
requirements:
|
|
66
52
|
- - "~>"
|
|
67
53
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
54
|
+
version: '13.0'
|
|
69
55
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
56
|
+
name: rubocop-discourse
|
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
|
72
58
|
requirements:
|
|
73
59
|
- - "~>"
|
|
74
60
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
61
|
+
version: '3.18'
|
|
76
62
|
type: :development
|
|
77
63
|
prerelease: false
|
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
65
|
requirements:
|
|
80
66
|
- - "~>"
|
|
81
67
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
68
|
+
version: '3.18'
|
|
83
69
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
70
|
+
name: syntax_tree
|
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
|
86
72
|
requirements:
|
|
87
73
|
- - "~>"
|
|
88
74
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '3
|
|
75
|
+
version: '6.3'
|
|
90
76
|
type: :development
|
|
91
77
|
prerelease: false
|
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
79
|
requirements:
|
|
94
80
|
- - "~>"
|
|
95
81
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '3
|
|
82
|
+
version: '6.3'
|
|
97
83
|
- !ruby/object:Gem::Dependency
|
|
98
84
|
name: landlock
|
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
|
100
86
|
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0.3'
|
|
101
90
|
- - ">="
|
|
102
91
|
- !ruby/object:Gem::Version
|
|
103
|
-
version:
|
|
92
|
+
version: 0.3.0
|
|
104
93
|
type: :development
|
|
105
94
|
prerelease: false
|
|
106
95
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
96
|
requirements:
|
|
97
|
+
- - "~>"
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '0.3'
|
|
108
100
|
- - ">="
|
|
109
101
|
- !ruby/object:Gem::Version
|
|
110
|
-
version:
|
|
102
|
+
version: 0.3.0
|
|
111
103
|
description: 'Safe Image is a small Ruby image-processing boundary for untrusted uploads:
|
|
112
|
-
|
|
113
|
-
optimisation, SVG
|
|
104
|
+
helper-contained libvips thumbnails/probing, hardened ImageMagick compatibility
|
|
105
|
+
operations, optimisation, SVG metadata probing, and optional atomic Landlock sandbox
|
|
106
|
+
execution.'
|
|
114
107
|
email:
|
|
115
108
|
- sam@discourse.org
|
|
116
109
|
executables: []
|
|
117
|
-
extensions:
|
|
110
|
+
extensions:
|
|
111
|
+
- ext/safe_image_vips_helper/extconf.rb
|
|
118
112
|
extra_rdoc_files: []
|
|
119
113
|
files:
|
|
120
114
|
- CHANGELOG.md
|
|
121
115
|
- LICENSE
|
|
122
116
|
- README.md
|
|
123
117
|
- SECURITY.md
|
|
118
|
+
- docs/architecture.md
|
|
119
|
+
- ext/safe_image_vips_helper/extconf.rb
|
|
120
|
+
- ext/safe_image_vips_helper/safe_image_vips_helper.c
|
|
124
121
|
- lib/safe_image.rb
|
|
125
122
|
- lib/safe_image/RT_sRGB.icm
|
|
126
|
-
- lib/safe_image/
|
|
123
|
+
- lib/safe_image/api/metadata.rb
|
|
124
|
+
- lib/safe_image/api/transform.rb
|
|
125
|
+
- lib/safe_image/backend_label.rb
|
|
127
126
|
- lib/safe_image/fonts/DEJAVU-LICENSE
|
|
128
127
|
- lib/safe_image/fonts/DejaVuSans.ttf
|
|
128
|
+
- lib/safe_image/formats.rb
|
|
129
129
|
- lib/safe_image/ico.rb
|
|
130
130
|
- lib/safe_image/image_magick_backend.rb
|
|
131
131
|
- lib/safe_image/imagemagick_policy/policy.xml
|
|
132
132
|
- lib/safe_image/jpegli_backend.rb
|
|
133
|
+
- lib/safe_image/metadata_operations.rb
|
|
133
134
|
- lib/safe_image/native.rb
|
|
135
|
+
- lib/safe_image/native_helper.rb
|
|
136
|
+
- lib/safe_image/operation_backends.rb
|
|
137
|
+
- lib/safe_image/operation_backends/base.rb
|
|
138
|
+
- lib/safe_image/operation_backends/image_magick.rb
|
|
139
|
+
- lib/safe_image/operation_backends/vips.rb
|
|
140
|
+
- lib/safe_image/operation_set.rb
|
|
134
141
|
- lib/safe_image/optimizer.rb
|
|
135
142
|
- lib/safe_image/path_safety.rb
|
|
136
143
|
- lib/safe_image/processor.rb
|
|
144
|
+
- lib/safe_image/quality_defaults.rb
|
|
137
145
|
- lib/safe_image/remote.rb
|
|
138
146
|
- lib/safe_image/result.rb
|
|
139
147
|
- lib/safe_image/runner.rb
|
|
140
148
|
- lib/safe_image/sandbox.rb
|
|
141
|
-
- lib/safe_image/
|
|
149
|
+
- lib/safe_image/staged_output.rb
|
|
142
150
|
- lib/safe_image/svg_metadata.rb
|
|
143
|
-
- lib/safe_image/
|
|
151
|
+
- lib/safe_image/transform_operations.rb
|
|
144
152
|
- lib/safe_image/version.rb
|
|
145
153
|
- lib/safe_image/vips_backend.rb
|
|
146
|
-
- lib/safe_image/vips_glue.rb
|
|
147
|
-
- lib/safe_image/zygote.rb
|
|
148
154
|
homepage: https://github.com/sam-saffron-jarvis/safe-image
|
|
149
155
|
licenses:
|
|
150
156
|
- MIT
|