Module: SafeImage::DiscourseCompat
- Defined in:
- lib/safe_image/discourse_compat.rb
Overview
Compatibility-shaped API for the operations Discourse currently performs in OptimizedImage, UploadCreator, ShrinkUploadedImage and FileHelper. The backend is decided once by SafeImage.configure!; these methods only dispatch to it.
Constant Summary collapse
- NATIVE_CONVERT_DEFAULT_QUALITY =
JPEG default when the caller passes no quality: matches what ImageMagick uses for sources without quality tables, rather than libvips’ Q75.
92- JPEGTRAN_OPERATIONS =
EXIF orientation values mapped onto jpegtran’s lossless transforms.
{ 2 => ["-flip", "horizontal"], 3 => ["-rotate", "180"], 4 => ["-flip", "vertical"], 5 => ["-transpose"], 6 => ["-rotate", "90"], 7 => ["-transverse"], 8 => ["-rotate", "270"] }.freeze
Class Method Summary collapse
- .animated?(path, max_pixels: nil) ⇒ Boolean
- .compat_backend_name(backend, info) ⇒ Object
- .compat_probe(path, max_pixels: nil) ⇒ Object
- .convert(from, to, format:, quality: nil, optimize: true, max_pixels: nil, chroma_subsampling: :auto) ⇒ Object
- .convert_favicon_to_png(from, to, optimize: true, max_pixels: nil) ⇒ Object
- .convert_to_jpeg(from, to, quality: nil, optimize: true, max_pixels: nil, chroma_subsampling: :auto) ⇒ Object
- .crop(from, to, width, height, quality: nil, optimize: true, max_pixels: nil, chroma_subsampling: :auto) ⇒ Object
- .downsize(from, to, dimensions, optimize: true, max_pixels: nil, quality: 85, chroma_subsampling: :auto) ⇒ Object
- .fix_orientation(from, to = from, max_pixels: nil, quality: nil) ⇒ Object
- .frame_count(path, max_pixels: nil) ⇒ Object
- .imagemagick_convert(from, output, format:, quality:, optimize:, max_pixels:) ⇒ Object
- .imagemagick_crop(from, to, width, height, quality:, optimize:, max_pixels:) ⇒ Object
- .imagemagick_downsize(from, to, dimensions, optimize:, max_pixels:) ⇒ Object
- .imagemagick_fix_orientation(from, output, max_pixels:) ⇒ Object
- .imagemagick_resize(from, to, width, height, quality:, optimize:, max_pixels:) ⇒ Object
- .jpegtran_fix_orientation(input, output, orient) ⇒ Object
- .letter_avatar(output:, size:, background_rgb:, letter:, pointsize: 280, font: "DejaVu-Sans") ⇒ Object
- .native_convert(from, output, format:, quality:, optimize:, max_pixels:, chroma_subsampling:) ⇒ Object
- .native_fix_orientation(from, output, max_pixels:, quality:) ⇒ Object
- .optimize_image!(path, allow_lossy_png: false, strip_metadata: true, quality: nil, strict: true) ⇒ Object
-
.optimize_output(output, quality) ⇒ Object
Post-processing applies only to the formats the optimizer tools understand; other outputs (gif, jxl, …) skip the pass.
- .resize(from, to, width, height, quality: nil, optimize: true, max_pixels: nil, chroma_subsampling: :auto) ⇒ Object
- .result_from_info(input, output, info, backend) ⇒ Object
- .use_jpegli_for_convert?(input, normalized_format) ⇒ Boolean
-
.use_jpegli_for_generated_jpeg?(format) ⇒ Boolean
cjpegli is an output-quality tool, not a configuration choice: installed means used for JPEG output on the native path.
- .vips_crop(from, to, width, height, quality:, optimize:, max_pixels:, chroma_subsampling:) ⇒ Object
- .vips_downsize(from, to, dimensions, quality:, optimize:, max_pixels:, chroma_subsampling:) ⇒ Object
- .vips_resize(from, to, width, height, quality:, optimize:, max_pixels:, chroma_subsampling:) ⇒ Object
- .with_temp_png(output) ⇒ Object
-
.write_through_tempfile(output) ⇒ Object
Writes via a sibling tempfile and renames into place, so in-place calls (to == from) never feed an output path that libvips is still reading from as input.
Class Method Details
.animated?(path, max_pixels: nil) ⇒ Boolean
387 388 389 |
# File 'lib/safe_image/discourse_compat.rb', line 387 def animated?(path, max_pixels: nil) frame_count(path, max_pixels: max_pixels).to_i > 1 end |
.compat_backend_name(backend, info) ⇒ Object
258 259 260 261 |
# File 'lib/safe_image/discourse_compat.rb', line 258 def compat_backend_name(backend, info) base = backend.to_sym == :vips ? "libvips-direct" : "imagemagick" info[:encoder] == "cjpegli" ? "#{base}+cjpegli" : base end |
.compat_probe(path, max_pixels: nil) ⇒ Object
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 |
# File 'lib/safe_image/discourse_compat.rb', line 416 def compat_probe(path, max_pixels: nil) path = Pathname.new(path)..to_s if SafeImage.config.backend == :vips SafeImage.probe(path, max_pixels: max_pixels) else info = ImageMagickBackend.probe(path, max_pixels: max_pixels) Result.new( input: path, output: nil, input_format: info.fetch(:input_format), output_format: nil, width: info.fetch(:width), height: info.fetch(:height), filesize: File.size(path), backend: "imagemagick", duration_ms: info.fetch(:duration_ms), optimizer: nil ) end end |
.convert(from, to, format:, quality: nil, optimize: true, max_pixels: nil, chroma_subsampling: :auto) ⇒ Object
192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/safe_image/discourse_compat.rb', line 192 def convert(from, to, format:, quality: nil, optimize: true, max_pixels: nil, chroma_subsampling: :auto) max_pixels = SafeImage.resolved_max_pixels(max_pixels) output = PathSafety.ensure_safe_output_path!(to).to_s case SafeImage.config.backend when :vips native_convert(from, output, format: format, quality: quality, optimize: optimize, max_pixels: max_pixels, chroma_subsampling: chroma_subsampling) when :imagemagick imagemagick_convert(from, output, format: format, quality: quality, optimize: optimize, max_pixels: max_pixels) end end |
.convert_favicon_to_png(from, to, optimize: true, max_pixels: nil) ⇒ Object
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'lib/safe_image/discourse_compat.rb', line 356 def convert_favicon_to_png(from, to, optimize: true, max_pixels: nil) max_pixels = SafeImage.resolved_max_pixels(max_pixels) output = PathSafety.ensure_safe_output_path!(to).to_s case SafeImage.config.backend when :vips # Pure-Ruby ICO parse; libvips only encodes the extracted pixels. info = Ico.convert_to_png(from, output, max_pixels: max_pixels) backend_name = "ico-ruby+libvips" when :imagemagick info = ImageMagickBackend.convert_ico_to_png(input: Pathname.new(from)..to_s, output: output) backend_name = "imagemagick" end Optimizer.optimize(output, mode: :lossless, strip_metadata: true) if optimize result_from_info(from, output, info, backend_name) end |
.convert_to_jpeg(from, to, quality: nil, optimize: true, max_pixels: nil, chroma_subsampling: :auto) ⇒ Object
263 264 265 |
# File 'lib/safe_image/discourse_compat.rb', line 263 def convert_to_jpeg(from, to, quality: nil, optimize: true, max_pixels: nil, chroma_subsampling: :auto) convert(from, to, format: "jpg", quality: quality, optimize: optimize, max_pixels: max_pixels, chroma_subsampling: chroma_subsampling) end |
.crop(from, to, width, height, quality: nil, optimize: true, max_pixels: nil, chroma_subsampling: :auto) ⇒ Object
53 54 55 56 57 58 59 60 61 |
# File 'lib/safe_image/discourse_compat.rb', line 53 def crop(from, to, width, height, quality: nil, optimize: true, max_pixels: nil, chroma_subsampling: :auto) max_pixels = SafeImage.resolved_max_pixels(max_pixels) case SafeImage.config.backend when :vips vips_crop(from, to, width, height, quality: quality, optimize: optimize, max_pixels: max_pixels, chroma_subsampling: chroma_subsampling) when :imagemagick imagemagick_crop(from, to, width, height, quality: quality, optimize: optimize, max_pixels: max_pixels) end end |
.downsize(from, to, dimensions, optimize: true, max_pixels: nil, quality: 85, chroma_subsampling: :auto) ⇒ Object
119 120 121 122 123 124 125 126 127 |
# File 'lib/safe_image/discourse_compat.rb', line 119 def downsize(from, to, dimensions, optimize: true, max_pixels: nil, quality: 85, chroma_subsampling: :auto) max_pixels = SafeImage.resolved_max_pixels(max_pixels) case SafeImage.config.backend when :vips vips_downsize(from, to, dimensions, quality: quality, optimize: optimize, max_pixels: max_pixels, chroma_subsampling: chroma_subsampling) when :imagemagick imagemagick_downsize(from, to, dimensions, optimize: optimize, max_pixels: max_pixels) end end |
.fix_orientation(from, to = from, max_pixels: nil, quality: nil) ⇒ Object
278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/safe_image/discourse_compat.rb', line 278 def fix_orientation(from, to = from, max_pixels: nil, quality: nil) max_pixels = SafeImage.resolved_max_pixels(max_pixels) output = PathSafety.ensure_safe_output_path!(to).to_s case SafeImage.config.backend when :vips native_fix_orientation(from, output, max_pixels: max_pixels, quality: quality) when :imagemagick imagemagick_fix_orientation(from, output, max_pixels: max_pixels) end end |
.frame_count(path, max_pixels: nil) ⇒ Object
373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/safe_image/discourse_compat.rb', line 373 def frame_count(path, max_pixels: nil) max_pixels = SafeImage.resolved_max_pixels(max_pixels) # ico directories are counted by the pure-Ruby parser on either backend; # everything else is a header-only count. return Ico.frame_count(path, max_pixels: max_pixels) if File.extname(PathSafety.local_path(path)).downcase == ".ico" case SafeImage.config.backend when :vips VipsBackend.frame_count(path, max_pixels: max_pixels) when :imagemagick ImageMagickBackend.frame_count(path, max_pixels: max_pixels) end end |
.imagemagick_convert(from, output, format:, quality:, optimize:, max_pixels:) ⇒ Object
204 205 206 207 208 209 210 |
# File 'lib/safe_image/discourse_compat.rb', line 204 def imagemagick_convert(from, output, format:, quality:, optimize:, max_pixels:) probe = compat_probe(from, max_pixels: max_pixels) normalized_format = format.to_s.downcase == "jpeg" ? "jpg" : format.to_s.downcase info = ImageMagickBackend.convert(input: probe.input, output: output, format: format, quality: quality) optimize_output(output, normalized_format == "jpg" ? quality : nil) if optimize result_from_info(probe.input, output, info, "imagemagick") end |
.imagemagick_crop(from, to, width, height, quality:, optimize:, max_pixels:) ⇒ Object
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/safe_image/discourse_compat.rb', line 103 def imagemagick_crop(from, to, width, height, quality:, optimize:, max_pixels:) probe = compat_probe(from, max_pixels: max_pixels) output = PathSafety.ensure_safe_output_path!(to).to_s info = ImageMagickBackend.resize_like( input: probe.input, output: output, width: width, height: height, format: File.extname(output).delete_prefix(".").downcase, quality: quality, crop: :north ) optimize_output(output, quality) if optimize result_from_info(probe.input, output, info, "imagemagick") end |
.imagemagick_downsize(from, to, dimensions, optimize:, max_pixels:) ⇒ Object
166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/safe_image/discourse_compat.rb', line 166 def imagemagick_downsize(from, to, dimensions, optimize:, max_pixels:) probe = compat_probe(from, max_pixels: max_pixels) output = PathSafety.ensure_safe_output_path!(to).to_s info = ImageMagickBackend.downsize( input: probe.input, output: output, dimensions: dimensions, format: File.extname(output).delete_prefix(".").downcase ) optimize_output(output, nil) if optimize result_from_info(probe.input, output, info, "imagemagick") end |
.imagemagick_fix_orientation(from, output, max_pixels:) ⇒ Object
290 291 292 293 294 |
# File 'lib/safe_image/discourse_compat.rb', line 290 def imagemagick_fix_orientation(from, output, max_pixels:) probe = compat_probe(from, max_pixels: max_pixels) info = ImageMagickBackend.fix_orientation(input: probe.input, output: output) result_from_info(probe.input, output, info, "imagemagick") end |
.imagemagick_resize(from, to, width, height, quality:, optimize:, max_pixels:) ⇒ Object
38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/safe_image/discourse_compat.rb', line 38 def imagemagick_resize(from, to, width, height, quality:, optimize:, max_pixels:) probe = compat_probe(from, max_pixels: max_pixels) output = PathSafety.ensure_safe_output_path!(to).to_s info = ImageMagickBackend.thumbnail( input: probe.input, output: output, width: width, height: height, format: File.extname(output).delete_prefix(".").downcase, quality: quality ) optimize_output(output, quality) if optimize result_from_info(probe.input, output, info, "imagemagick") end |
.jpegtran_fix_orientation(input, output, orient) ⇒ Object
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/safe_image/discourse_compat.rb', line 323 def jpegtran_fix_orientation(input, output, orient) started = Process.clock_gettime(Process::CLOCK_MONOTONIC) info = write_through_tempfile(output) do |tmp_path| Runner.run!(["jpegtran", "-copy", "none", "-perfect", *JPEGTRAN_OPERATIONS.fetch(orient), "-outfile", tmp_path, input]) Native.probe(tmp_path) end result_from_info( input, output, { input_format: "jpg", output_format: "jpg", width: info.fetch(:width), height: info.fetch(:height), duration_ms: (Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000 }, "jpegtran" ) end |
.letter_avatar(output:, size:, background_rgb:, letter:, pointsize: 280, font: "DejaVu-Sans") ⇒ Object
391 392 393 394 395 396 397 398 399 400 401 402 403 404 |
# File 'lib/safe_image/discourse_compat.rb', line 391 def letter_avatar(output:, size:, background_rgb:, letter:, pointsize: 280, font: "DejaVu-Sans") output = PathSafety.ensure_safe_output_path!(output).to_s request = { output: output, size: size, background_rgb: background_rgb, letter: letter, pointsize: pointsize, font: font } info, backend_name = case SafeImage.config.backend when :vips [VipsBackend.letter_avatar(**request), "libvips-direct"] when :imagemagick [ImageMagickBackend.letter_avatar(**request), "imagemagick"] end result_from_info("generated", output, info, backend_name) end |
.native_convert(from, output, format:, quality:, optimize:, max_pixels:, chroma_subsampling:) ⇒ Object
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/safe_image/discourse_compat.rb', line 212 def native_convert(from, output, format:, quality:, optimize:, max_pixels:, chroma_subsampling:) input = PathSafety.ensure_regular_file!(from).to_s normalized_format = format.to_s.downcase == "jpeg" ? "jpg" : format.to_s.downcase if use_jpegli_for_convert?(input, normalized_format) info = JpegliBackend.convert( input: input, output: output, quality: quality || JpegliBackend::DEFAULT_QUALITY, chroma_subsampling: chroma_subsampling ) return result_from_info(input, output, info, "cjpegli") end info = write_through_tempfile(output) do |tmp_path| Native.convert(input, tmp_path, normalized_format, quality || NATIVE_CONVERT_DEFAULT_QUALITY, max_pixels) end optimize_output(output, normalized_format == "jpg" ? quality : nil) if optimize result_from_info(input, output, info, "libvips-direct") end |
.native_fix_orientation(from, output, max_pixels:, quality:) ⇒ Object
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/safe_image/discourse_compat.rb', line 296 def native_fix_orientation(from, output, max_pixels:, quality:) input = PathSafety.ensure_regular_file!(from).to_s format = File.extname(input).delete_prefix(".").downcase format = "jpg" if format == "jpeg" # Validates the format against the native loader allowlist and enforces # the pixel cap before any pixel decode. orient = VipsBackend.orientation(input, max_pixels: max_pixels) # Lossless tier: jpegtran transforms JPEG DCT coefficients directly, so # there is no generation loss. -perfect refuses when the dimensions are # not MCU-aligned; fall through to the re-encode tier. if format == "jpg" && orient > 1 && Runner.available?("jpegtran") begin return jpegtran_fix_orientation(input, output, orient) rescue CommandError nil end end quality = quality.nil? ? 95 : Integer(quality) raise ArgumentError, "quality must be 1..100" unless (1..100).cover?(quality) info = write_through_tempfile(output) do |tmp_path| Native.resize(input, tmp_path, 1.0, format, quality, max_pixels) end result_from_info(input, output, info, "libvips-direct") end |
.optimize_image!(path, allow_lossy_png: false, strip_metadata: true, quality: nil, strict: true) ⇒ Object
406 407 408 409 410 411 412 413 414 |
# File 'lib/safe_image/discourse_compat.rb', line 406 def optimize_image!(path, allow_lossy_png: false, strip_metadata: true, quality: nil, strict: true) Optimizer.optimize( path, mode: allow_lossy_png ? :lossy : :lossless, strip_metadata: , quality: quality, strict: strict ) end |
.optimize_output(output, quality) ⇒ Object
Post-processing applies only to the formats the optimizer tools understand; other outputs (gif, jxl, …) skip the pass.
181 182 183 184 185 186 |
# File 'lib/safe_image/discourse_compat.rb', line 181 def optimize_output(output, quality) format = File.extname(output).delete_prefix(".").downcase format = "jpg" if format == "jpeg" return unless Processor::OPTIMIZABLE_OUTPUTS.include?(format) Optimizer.optimize(output, mode: :lossless, strip_metadata: true, quality: quality) end |
.resize(from, to, width, height, quality: nil, optimize: true, max_pixels: nil, chroma_subsampling: :auto) ⇒ Object
15 16 17 18 19 20 21 22 23 |
# File 'lib/safe_image/discourse_compat.rb', line 15 def resize(from, to, width, height, quality: nil, optimize: true, max_pixels: nil, chroma_subsampling: :auto) max_pixels = SafeImage.resolved_max_pixels(max_pixels) case SafeImage.config.backend when :vips vips_resize(from, to, width, height, quality: quality, optimize: optimize, max_pixels: max_pixels, chroma_subsampling: chroma_subsampling) when :imagemagick imagemagick_resize(from, to, width, height, quality: quality, optimize: optimize, max_pixels: max_pixels) end end |
.result_from_info(input, output, info, backend) ⇒ Object
437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
# File 'lib/safe_image/discourse_compat.rb', line 437 def result_from_info(input, output, info, backend) Result.new( input: input.to_s, output: output.to_s, input_format: info.fetch(:input_format), output_format: info.fetch(:output_format), width: info.fetch(:width), height: info.fetch(:height), filesize: File.size(output), backend: backend, duration_ms: info.fetch(:duration_ms), optimizer: nil ) end |
.use_jpegli_for_convert?(input, normalized_format) ⇒ Boolean
233 234 235 |
# File 'lib/safe_image/discourse_compat.rb', line 233 def use_jpegli_for_convert?(input, normalized_format) normalized_format == "jpg" && JpegliBackend.available? && JpegliBackend.suitable_direct_input?(input) end |
.use_jpegli_for_generated_jpeg?(format) ⇒ Boolean
cjpegli is an output-quality tool, not a configuration choice: installed means used for JPEG output on the native path. It encodes only pixels this gem already decoded, so it is not part of the untrusted-input surface the backend choice controls.
241 242 243 244 |
# File 'lib/safe_image/discourse_compat.rb', line 241 def use_jpegli_for_generated_jpeg?(format) normalized_format = format.to_s.downcase == "jpeg" ? "jpg" : format.to_s.downcase normalized_format == "jpg" && JpegliBackend.available? end |
.vips_crop(from, to, width, height, quality:, optimize:, max_pixels:, chroma_subsampling:) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/safe_image/discourse_compat.rb', line 63 def vips_crop(from, to, width, height, quality:, optimize:, max_pixels:, chroma_subsampling:) probe = compat_probe(from, max_pixels: max_pixels) output = PathSafety.ensure_safe_output_path!(to).to_s format = File.extname(output).delete_prefix(".").downcase info = if use_jpegli_for_generated_jpeg?(format) with_temp_png(output) do |tmp_path| VipsBackend.crop_north( input: probe.input, output: tmp_path, width: width, height: height, format: "png", quality: 100, max_pixels: max_pixels ) JpegliBackend.encode( input: tmp_path, output: output, quality: quality || JpegliBackend::DEFAULT_QUALITY, chroma_subsampling: JpegliBackend.validate_chroma_subsampling!(chroma_subsampling, input_format: probe.input_format), input_format: probe.input_format ) end else VipsBackend.crop_north( input: probe.input, output: output, width: width, height: height, format: format, quality: quality || 85, max_pixels: max_pixels ) end optimize_output(output, quality) if optimize result_from_info(probe.input, output, info, compat_backend_name(:vips, info)) end |
.vips_downsize(from, to, dimensions, quality:, optimize:, max_pixels:, chroma_subsampling:) ⇒ Object
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/safe_image/discourse_compat.rb', line 129 def vips_downsize(from, to, dimensions, quality:, optimize:, max_pixels:, chroma_subsampling:) probe = compat_probe(from, max_pixels: max_pixels) output = PathSafety.ensure_safe_output_path!(to).to_s format = File.extname(output).delete_prefix(".").downcase info = if use_jpegli_for_generated_jpeg?(format) with_temp_png(output) do |tmp_path| VipsBackend.downsize( input: probe.input, output: tmp_path, dimensions: dimensions, format: "png", quality: 100, max_pixels: max_pixels ) JpegliBackend.encode( input: tmp_path, output: output, quality: quality, chroma_subsampling: JpegliBackend.validate_chroma_subsampling!(chroma_subsampling, input_format: probe.input_format), input_format: probe.input_format ) end else VipsBackend.downsize( input: probe.input, output: output, dimensions: dimensions, format: format, quality: quality, max_pixels: max_pixels ) end optimize_output(output, nil) if optimize result_from_info(probe.input, output, info, compat_backend_name(:vips, info)) end |
.vips_resize(from, to, width, height, quality:, optimize:, max_pixels:, chroma_subsampling:) ⇒ Object
25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/safe_image/discourse_compat.rb', line 25 def vips_resize(from, to, width, height, quality:, optimize:, max_pixels:, chroma_subsampling:) SafeImage.thumbnail( input: from, output: to, width: width, height: height, quality: quality || 85, optimize: optimize, max_pixels: max_pixels, chroma_subsampling: chroma_subsampling ) end |
.with_temp_png(output) ⇒ Object
246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/safe_image/discourse_compat.rb', line 246 def with_temp_png(output) output_path = Pathname.new(output) output_path.dirname.mkpath Tempfile.create([output_path.basename(".*").to_s, ".safe-image.png"], output_path.dirname.to_s) do |tmp| tmp_path = Pathname.new(tmp.path) tmp.close yield tmp_path ensure FileUtils.rm_f(tmp_path) if defined?(tmp_path) && tmp_path end end |
.write_through_tempfile(output) ⇒ Object
Writes via a sibling tempfile and renames into place, so in-place calls (to == from) never feed an output path that libvips is still reading from as input.
346 347 348 349 350 351 352 353 354 |
# File 'lib/safe_image/discourse_compat.rb', line 346 def write_through_tempfile(output) tmp_path = File.join(File.dirname(output), ".safe-image-#{Process.pid}-#{output.object_id}#{File.extname(output)}") PathSafety.ensure_safe_output_path!(tmp_path) result = yield tmp_path FileUtils.mv(tmp_path, output) result ensure FileUtils.rm_f(tmp_path) end |