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