dragonfly 1.3.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/History.md +16 -0
  4. data/README.md +5 -1
  5. data/dev/test.ru +1 -1
  6. data/dragonfly.gemspec +2 -1
  7. data/lib/dragonfly/content.rb +17 -18
  8. data/lib/dragonfly/image_magick/commands.rb +35 -0
  9. data/lib/dragonfly/image_magick/generators/plain.rb +13 -7
  10. data/lib/dragonfly/image_magick/generators/plasma.rb +10 -6
  11. data/lib/dragonfly/image_magick/generators/text.rb +67 -58
  12. data/lib/dragonfly/image_magick/plugin.rb +26 -25
  13. data/lib/dragonfly/image_magick/processors/encode.rb +16 -5
  14. data/lib/dragonfly/image_magick/processors/thumb.rb +37 -31
  15. data/lib/dragonfly/middleware.rb +2 -1
  16. data/lib/dragonfly/param_validators.rb +37 -0
  17. data/lib/dragonfly/response.rb +11 -11
  18. data/lib/dragonfly/routed_endpoint.rb +1 -1
  19. data/lib/dragonfly/server.rb +7 -7
  20. data/lib/dragonfly/version.rb +1 -1
  21. data/spec/dragonfly/app_spec.rb +1 -1
  22. data/spec/dragonfly/configurable_spec.rb +4 -4
  23. data/spec/dragonfly/content_spec.rb +1 -1
  24. data/spec/dragonfly/file_data_store_spec.rb +2 -2
  25. data/spec/dragonfly/image_magick/commands_spec.rb +98 -0
  26. data/spec/dragonfly/image_magick/generators/plain_spec.rb +39 -13
  27. data/spec/dragonfly/image_magick/generators/plasma_spec.rb +28 -9
  28. data/spec/dragonfly/image_magick/generators/text_spec.rb +51 -20
  29. data/spec/dragonfly/image_magick/plugin_spec.rb +45 -28
  30. data/spec/dragonfly/image_magick/processors/encode_spec.rb +30 -0
  31. data/spec/dragonfly/image_magick/processors/thumb_spec.rb +46 -45
  32. data/spec/dragonfly/job/fetch_url_spec.rb +1 -1
  33. data/spec/dragonfly/job_endpoint_spec.rb +26 -26
  34. data/spec/dragonfly/middleware_spec.rb +15 -6
  35. data/spec/dragonfly/model/active_record_spec.rb +2 -2
  36. data/spec/dragonfly/model/model_spec.rb +1 -1
  37. data/spec/dragonfly/param_validators_spec.rb +89 -0
  38. data/spec/dragonfly/server_spec.rb +4 -4
  39. data/spec/dragonfly/temp_object_spec.rb +5 -5
  40. data/spec/functional/shell_commands_spec.rb +6 -9
  41. data/spec/functional/to_response_spec.rb +2 -2
  42. data/spec/functional/urls_spec.rb +1 -1
  43. data/spec/spec_helper.rb +18 -14
  44. data/spec/support/image_matchers.rb +1 -1
  45. metadata +27 -12
  46. data/lib/dragonfly/image_magick/generators/convert.rb +0 -19
  47. data/lib/dragonfly/image_magick/processors/convert.rb +0 -33
  48. data/spec/dragonfly/image_magick/generators/convert_spec.rb +0 -19
  49. data/spec/dragonfly/image_magick/processors/convert_spec.rb +0 -88
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5385c46218c68349adc9f33a89600ed52901b483fc4b8b7ad3bab0ea1e3aa579
4
- data.tar.gz: bd4341674548d3526d786be990912b527745000b6d887148e91bfe88e7a9618c
3
+ metadata.gz: 53b6c0b281a6030e69614429aa17a2f7e86e0037bffa2b7346e37c950444c563
4
+ data.tar.gz: 101cc564359bef667df565eec4ee6a8e4d00aad2f2f792c8cf0313e939ebfa67
5
5
  SHA512:
6
- metadata.gz: 28b284e2ca3d15914787357c2035f900a2c0c61c5fdc0b36302b2bcd24b01683b073774b70a9583ef446ba764dbb7019e3e92eab8cd83b042d428236498ccaf4
7
- data.tar.gz: 2fc17f0f4587fe85ccf6af91798a0a89aa58086a9518445d2684abcffd16ab9d53c10fdea2e7f0a5cb2f07a24774b3865b80e7ddd293f5a53bfea4f04b0b24bd
6
+ metadata.gz: 1afbcdd97912f2ec6bf7ba49261f885e0ddf0c4df16b1fd8b76824d6a87e1dca3be024fe666f7eb961269017f5af841aaaebe4bb7c21396f0f15db76fc7791ed
7
+ data.tar.gz: f26309d103bc2101287cf5e9c5cf9915defe5072275ea36b9b3cc1fa579a34c84723b48244c3ede6d49dbad71915b2267840077401a67bb8a1b585e1cc8edb66
data/.travis.yml CHANGED
@@ -6,6 +6,7 @@ rvm:
6
6
  - "2.5"
7
7
  - "2.6"
8
8
  - "2.7"
9
+ - "3.0"
9
10
  - "jruby"
10
11
 
11
12
  env:
data/History.md CHANGED
@@ -1,3 +1,19 @@
1
+ # 1.4.1 (2025-01-03)
2
+
3
+ ## Fixes
4
+
5
+ - Added ostruct dependency as Ruby 3.5 will remove it from stdlib
6
+
7
+ # 1.4.0 (2021-05-19)
8
+
9
+ ## Changes
10
+
11
+ - Removed `convert` processor and generator (which were quite insecure), in favour of utility commands in `Dragonfly::ImageMagick::Commands`
12
+
13
+ ## Fixes
14
+
15
+ - Better security for all job steps with parameter validations - addresses CVE-2021-33564
16
+
1
17
  # 1.3.0 (2021-01-09)
2
18
 
3
19
  ## Changes
data/README.md CHANGED
@@ -47,7 +47,7 @@ Installation
47
47
 
48
48
  or in your Gemfile
49
49
  ```ruby
50
- gem 'dragonfly', '~> 1.2.2'
50
+ gem 'dragonfly', '~> 1.4.0'
51
51
  ```
52
52
 
53
53
  Require with
@@ -72,6 +72,10 @@ See [the Add-ons wiki](http://github.com/markevans/dragonfly/wiki/Dragonfly-add-
72
72
 
73
73
  Please feel free to contribute!!
74
74
 
75
+ Security notice!
76
+ =================
77
+ If you have set `verify_urls` to `false` (which is **not** recommended) then you should upgrade to version `1.4.x` for a security fix ([CVE-2021-33564](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-33564)).
78
+
75
79
  Issues
76
80
  ======
77
81
  Please use the <a href="http://github.com/markevans/dragonfly/issues">github issue tracker</a> if you have any issues.
data/dev/test.ru CHANGED
@@ -24,7 +24,7 @@ class App
24
24
  end
25
25
  [
26
26
  200,
27
- {'Content-Type' => 'text/html'},
27
+ {'content-type' => 'text/html'},
28
28
  [%(
29
29
  <style>
30
30
  form, input {
data/dragonfly.gemspec CHANGED
@@ -25,9 +25,10 @@ Gem::Specification.new do |spec|
25
25
  spec.add_runtime_dependency("rack", ">= 1.3")
26
26
  spec.add_runtime_dependency("multi_json", "~> 1.0")
27
27
  spec.add_runtime_dependency("addressable", "~> 2.3")
28
+ spec.add_runtime_dependency("ostruct", "~> 0.6.1")
28
29
 
29
30
  # Development dependencies
30
- spec.add_development_dependency("rspec", "~> 2.5")
31
+ spec.add_development_dependency("rspec", "~> 3.0")
31
32
  spec.add_development_dependency("webmock")
32
33
  spec.add_development_dependency("activemodel")
33
34
  if RUBY_PLATFORM == "java"
@@ -1,8 +1,8 @@
1
- require 'base64'
2
- require 'forwardable'
3
- require 'dragonfly/has_filename'
4
- require 'dragonfly/temp_object'
5
- require 'dragonfly/utils'
1
+ require "base64"
2
+ require "forwardable"
3
+ require "dragonfly/has_filename"
4
+ require "dragonfly/temp_object"
5
+ require "dragonfly/utils"
6
6
 
7
7
  module Dragonfly
8
8
 
@@ -16,11 +16,10 @@ module Dragonfly
16
16
  # It is acted upon in generator, processor, analyser and datastore methods and provides a standard interface for updating content,
17
17
  # no matter how that content first got there (whether in the form of a String/Pathname/File/etc.)
18
18
  class Content
19
-
20
19
  include HasFilename
21
20
  extend Forwardable
22
21
 
23
- def initialize(app, obj="", meta=nil)
22
+ def initialize(app, obj = "", meta = nil)
24
23
  @app = app
25
24
  @meta = {}
26
25
  @previous_temp_objects = []
@@ -79,7 +78,7 @@ module Dragonfly
79
78
  # @example "image/jpeg"
80
79
  # @return [String]
81
80
  def mime_type
82
- meta['mime_type'] || app.mime_type_for(ext)
81
+ meta["mime_type"] || app.mime_type_for(ext)
83
82
  end
84
83
 
85
84
  # Set the content using a pre-registered generator
@@ -93,7 +92,7 @@ module Dragonfly
93
92
 
94
93
  # Update the content using a pre-registered processor
95
94
  # @example
96
- # content.process!(:convert, "-resize 300x300")
95
+ # content.process!(:thumb, "300x300")
97
96
  # @return [Content] self
98
97
  def process!(name, *args)
99
98
  app.get_processor(name).call(self, *args)
@@ -111,10 +110,10 @@ module Dragonfly
111
110
  # @param obj [String, Pathname, Tempfile, File, Content, TempObject] can be any of these types
112
111
  # @param meta [Hash] - should be json-like, i.e. contain no types other than String, Number, Boolean
113
112
  # @return [Content] self
114
- def update(obj, meta=nil)
113
+ def update(obj, meta = nil)
115
114
  meta ||= {}
116
- self.temp_object = TempObject.new(obj, meta['name'])
117
- self.meta['name'] ||= temp_object.name if temp_object.name
115
+ self.temp_object = TempObject.new(obj, meta["name"])
116
+ self.meta["name"] ||= temp_object.name if temp_object.name
118
117
  clear_analyser_cache
119
118
  add_meta(obj.meta) if obj.respond_to?(:meta)
120
119
  add_meta(meta)
@@ -135,7 +134,7 @@ module Dragonfly
135
134
  # "file --mime-type #{path}"
136
135
  # end
137
136
  # # ===> "beach.jpg: image/jpeg"
138
- def shell_eval(opts={})
137
+ def shell_eval(opts = {})
139
138
  should_escape = opts[:escape] != false
140
139
  command = yield(should_escape ? shell.escape(path) : path)
141
140
  run command, :escape => should_escape
@@ -148,7 +147,7 @@ module Dragonfly
148
147
  # "/usr/local/bin/generate_text gumfry -o #{path}"
149
148
  # end
150
149
  # @return [Content] self
151
- def shell_generate(opts={})
150
+ def shell_generate(opts = {})
152
151
  ext = opts[:ext] || self.ext
153
152
  should_escape = opts[:escape] != false
154
153
  tempfile = Utils.new_tempfile(ext)
@@ -165,7 +164,7 @@ module Dragonfly
165
164
  # "convert -resize 20x10 #{old_path} #{new_path}"
166
165
  # end
167
166
  # @return [Content] self
168
- def shell_update(opts={})
167
+ def shell_update(opts = {})
169
168
  ext = opts[:ext] || self.ext
170
169
  should_escape = opts[:escape] != false
171
170
  tempfile = Utils.new_tempfile(ext)
@@ -176,7 +175,7 @@ module Dragonfly
176
175
  update(tempfile)
177
176
  end
178
177
 
179
- def store(opts={})
178
+ def store(opts = {})
180
179
  datastore.write(self, opts)
181
180
  end
182
181
 
@@ -188,7 +187,7 @@ module Dragonfly
188
187
  end
189
188
 
190
189
  def close
191
- previous_temp_objects.each{|temp_object| temp_object.close }
190
+ previous_temp_objects.each { |temp_object| temp_object.close }
192
191
  temp_object.close
193
192
  end
194
193
 
@@ -199,6 +198,7 @@ module Dragonfly
199
198
  private
200
199
 
201
200
  attr_reader :previous_temp_objects
201
+
202
202
  def temp_object=(temp_object)
203
203
  previous_temp_objects.push(@temp_object) if @temp_object
204
204
  @temp_object = temp_object
@@ -215,6 +215,5 @@ module Dragonfly
215
215
  def run(command, opts)
216
216
  shell.run(command, opts)
217
217
  end
218
-
219
218
  end
220
219
  end
@@ -0,0 +1,35 @@
1
+ module Dragonfly
2
+ module ImageMagick
3
+ module Commands
4
+ module_function
5
+
6
+ def convert(content, args = "", opts = {})
7
+ convert_command = content.env[:convert_command] || "convert"
8
+ format = opts["format"]
9
+
10
+ input_args = opts["input_args"] if opts["input_args"]
11
+ delegate_string = "#{opts["delegate"]}:" if opts["delegate"]
12
+ frame_string = "[#{opts["frame"]}]" if opts["frame"]
13
+
14
+ content.shell_update :ext => format do |old_path, new_path|
15
+ "#{convert_command} #{input_args} #{delegate_string}#{old_path}#{frame_string} #{args} #{new_path}"
16
+ end
17
+
18
+ if format
19
+ content.meta["format"] = format.to_s
20
+ content.ext = format
21
+ content.meta["mime_type"] = nil # don't need it as we have ext now
22
+ end
23
+ end
24
+
25
+ def generate(content, args, format)
26
+ format = format.to_s
27
+ convert_command = content.env[:convert_command] || "convert"
28
+ content.shell_generate :ext => format do |path|
29
+ "#{convert_command} #{args} #{path}"
30
+ end
31
+ content.add_meta("format" => format)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,25 +1,31 @@
1
+ require "dragonfly/image_magick/commands"
2
+ require "dragonfly/param_validators"
3
+
1
4
  module Dragonfly
2
5
  module ImageMagick
3
6
  module Generators
4
7
  class Plain
8
+ include ParamValidators
5
9
 
6
- def call(content, width, height, opts={})
10
+ def call(content, width, height, opts = {})
11
+ validate_all!([width, height], &is_number)
12
+ validate_all_keys!(opts, %w(colour color format), &is_word)
7
13
  format = extract_format(opts)
8
- colour = opts['colour'] || opts['color'] || 'white'
9
- content.generate!(:convert, "-size #{width}x#{height} xc:#{colour}", format)
10
- content.add_meta('format' => format, 'name' => "plain.#{format}")
14
+
15
+ colour = opts["colour"] || opts["color"] || "white"
16
+ Commands.generate(content, "-size #{width}x#{height} xc:#{colour}", format)
17
+ content.add_meta("format" => format, "name" => "plain.#{format}")
11
18
  end
12
19
 
13
- def update_url(url_attributes, width, height, opts={})
20
+ def update_url(url_attributes, width, height, opts = {})
14
21
  url_attributes.name = "plain.#{extract_format(opts)}"
15
22
  end
16
23
 
17
24
  private
18
25
 
19
26
  def extract_format(opts)
20
- opts['format'] || 'png'
27
+ opts["format"] || "png"
21
28
  end
22
-
23
29
  end
24
30
  end
25
31
  end
@@ -1,24 +1,28 @@
1
+ require "dragonfly/image_magick/commands"
2
+
1
3
  module Dragonfly
2
4
  module ImageMagick
3
5
  module Generators
4
6
  class Plasma
7
+ include ParamValidators
5
8
 
6
- def call(content, width, height, opts={})
9
+ def call(content, width, height, opts = {})
10
+ validate_all!([width, height], &is_number)
11
+ validate!(opts["format"], &is_word)
7
12
  format = extract_format(opts)
8
- content.generate!(:convert, "-size #{width}x#{height} plasma:fractal", format)
9
- content.add_meta('format' => format, 'name' => "plasma.#{format}")
13
+ Commands.generate(content, "-size #{width}x#{height} plasma:fractal", format)
14
+ content.add_meta("format" => format, "name" => "plasma.#{format}")
10
15
  end
11
16
 
12
- def update_url(url_attributes, width, height, opts={})
17
+ def update_url(url_attributes, width, height, opts = {})
13
18
  url_attributes.name = "plasma.#{extract_format(opts)}"
14
19
  end
15
20
 
16
21
  private
17
22
 
18
23
  def extract_format(opts)
19
- opts['format'] || 'png'
24
+ opts["format"] || "png"
20
25
  end
21
-
22
26
  end
23
27
  end
24
28
  end
@@ -1,100 +1,111 @@
1
- require 'dragonfly/hash_with_css_style_keys'
1
+ require "dragonfly/hash_with_css_style_keys"
2
+ require "dragonfly/image_magick/commands"
3
+ require "dragonfly/param_validators"
2
4
 
3
5
  module Dragonfly
4
6
  module ImageMagick
5
7
  module Generators
6
8
  class Text
9
+ include ParamValidators
7
10
 
8
11
  FONT_STYLES = {
9
- 'normal' => 'normal',
10
- 'italic' => 'italic',
11
- 'oblique' => 'oblique'
12
+ "normal" => "normal",
13
+ "italic" => "italic",
14
+ "oblique" => "oblique",
12
15
  }
13
16
 
14
17
  FONT_STRETCHES = {
15
- 'normal' => 'normal',
16
- 'semi-condensed' => 'semi-condensed',
17
- 'condensed' => 'condensed',
18
- 'extra-condensed' => 'extra-condensed',
19
- 'ultra-condensed' => 'ultra-condensed',
20
- 'semi-expanded' => 'semi-expanded',
21
- 'expanded' => 'expanded',
22
- 'extra-expanded' => 'extra-expanded',
23
- 'ultra-expanded' => 'ultra-expanded'
18
+ "normal" => "normal",
19
+ "semi-condensed" => "semi-condensed",
20
+ "condensed" => "condensed",
21
+ "extra-condensed" => "extra-condensed",
22
+ "ultra-condensed" => "ultra-condensed",
23
+ "semi-expanded" => "semi-expanded",
24
+ "expanded" => "expanded",
25
+ "extra-expanded" => "extra-expanded",
26
+ "ultra-expanded" => "ultra-expanded",
24
27
  }
25
28
 
26
29
  FONT_WEIGHTS = {
27
- 'normal' => 'normal',
28
- 'bold' => 'bold',
29
- 'bolder' => 'bolder',
30
- 'lighter' => 'lighter',
31
- '100' => 100,
32
- '200' => 200,
33
- '300' => 300,
34
- '400' => 400,
35
- '500' => 500,
36
- '600' => 600,
37
- '700' => 700,
38
- '800' => 800,
39
- '900' => 900
30
+ "normal" => "normal",
31
+ "bold" => "bold",
32
+ "bolder" => "bolder",
33
+ "lighter" => "lighter",
34
+ "100" => 100,
35
+ "200" => 200,
36
+ "300" => 300,
37
+ "400" => 400,
38
+ "500" => 500,
39
+ "600" => 600,
40
+ "700" => 700,
41
+ "800" => 800,
42
+ "900" => 900,
40
43
  }
41
44
 
42
- def update_url(url_attributes, string, opts={})
45
+ IS_COLOUR = ->(param) {
46
+ /\A(#\w+|rgba?\([\d\.,]+\)|\w+)\z/ === param
47
+ }
48
+
49
+ def update_url(url_attributes, string, opts = {})
43
50
  url_attributes.name = "text.#{extract_format(opts)}"
44
51
  end
45
52
 
46
- def call(content, string, opts={})
53
+ def call(content, string, opts = {})
54
+ validate_all_keys!(opts, %w(font font_family), &is_words)
55
+ validate_all_keys!(opts, %w(color background_color stroke_color), &IS_COLOUR)
56
+ validate!(opts["format"], &is_word)
57
+
47
58
  opts = HashWithCssStyleKeys[opts]
48
59
  args = []
49
60
  format = extract_format(opts)
50
- background = opts['background_color'] || 'none'
51
- font_size = (opts['font_size'] || 12).to_i
61
+ background = opts["background_color"] || "none"
62
+ font_size = (opts["font_size"] || 12).to_i
63
+ font_family = opts["font_family"] || opts["font"]
52
64
  escaped_string = "\"#{string.gsub(/"/, '\"')}\""
53
65
 
54
66
  # Settings
55
67
  args.push("-gravity NorthWest")
56
68
  args.push("-antialias")
57
69
  args.push("-pointsize #{font_size}")
58
- args.push("-font \"#{opts['font']}\"") if opts['font']
59
- args.push("-family '#{opts['font_family']}'") if opts['font_family']
60
- args.push("-fill #{opts['color']}") if opts['color']
61
- args.push("-stroke #{opts['stroke_color']}") if opts['stroke_color']
62
- args.push("-style #{FONT_STYLES[opts['font_style']]}") if opts['font_style']
63
- args.push("-stretch #{FONT_STRETCHES[opts['font_stretch']]}") if opts['font_stretch']
64
- args.push("-weight #{FONT_WEIGHTS[opts['font_weight']]}") if opts['font_weight']
70
+ args.push("-family '#{font_family}'") if font_family
71
+ args.push("-fill #{opts["color"]}") if opts["color"]
72
+ args.push("-stroke #{opts["stroke_color"]}") if opts["stroke_color"]
73
+ args.push("-style #{FONT_STYLES[opts["font_style"]]}") if opts["font_style"]
74
+ args.push("-stretch #{FONT_STRETCHES[opts["font_stretch"]]}") if opts["font_stretch"]
75
+ args.push("-weight #{FONT_WEIGHTS[opts["font_weight"]]}") if opts["font_weight"]
65
76
  args.push("-background #{background}")
66
77
  args.push("label:#{escaped_string}")
67
78
 
68
79
  # Padding
69
- pt, pr, pb, pl = parse_padding_string(opts['padding']) if opts['padding']
70
- padding_top = (opts['padding_top'] || pt || 0)
71
- padding_right = (opts['padding_right'] || pr || 0)
72
- padding_bottom = (opts['padding_bottom'] || pb || 0)
73
- padding_left = (opts['padding_left'] || pl || 0)
80
+ pt, pr, pb, pl = parse_padding_string(opts["padding"]) if opts["padding"]
81
+ padding_top = (opts["padding_top"] || pt).to_i
82
+ padding_right = (opts["padding_right"] || pr).to_i
83
+ padding_bottom = (opts["padding_bottom"] || pb).to_i
84
+ padding_left = (opts["padding_left"] || pl).to_i
74
85
 
75
- content.generate!(:convert, args.join(' '), format)
86
+ Commands.generate(content, args.join(" "), format)
76
87
 
77
88
  if (padding_top || padding_right || padding_bottom || padding_left)
78
89
  dimensions = content.analyse(:image_properties)
79
- text_width = dimensions['width']
80
- text_height = dimensions['height']
81
- width = padding_left + text_width + padding_right
82
- height = padding_top + text_height + padding_bottom
90
+ text_width = dimensions["width"]
91
+ text_height = dimensions["height"]
92
+ width = padding_left + text_width + padding_right
93
+ height = padding_top + text_height + padding_bottom
83
94
 
84
95
  args = args.slice(0, args.length - 2)
85
96
  args.push("-size #{width}x#{height}")
86
97
  args.push("xc:#{background}")
87
98
  args.push("-annotate 0x0+#{padding_left}+#{padding_top} #{escaped_string}")
88
- content.generate!(:convert, args.join(' '), format)
99
+ Commands.generate(content, args.join(" "), format)
89
100
  end
90
101
 
91
- content.add_meta('format' => format, 'name' => "text.#{format}")
102
+ content.add_meta("format" => format, "name" => "text.#{format}")
92
103
  end
93
104
 
94
105
  private
95
106
 
96
107
  def extract_format(opts)
97
- opts['format'] || 'png'
108
+ opts["format"] || "png"
98
109
  end
99
110
 
100
111
  # Use css-style padding declaration, i.e.
@@ -103,25 +114,23 @@ module Dragonfly
103
114
  # 10 5 10 (top, left/right, bottom)
104
115
  # 10 5 10 5 (top, right, bottom, left)
105
116
  def parse_padding_string(str)
106
- padding_parts = str.gsub('px','').split(/\s+/).map{|px| px.to_i}
117
+ padding_parts = str.gsub("px", "").split(/\s+/).map { |px| px.to_i }
107
118
  case padding_parts.size
108
119
  when 1
109
120
  p = padding_parts.first
110
- [p,p,p,p]
121
+ [p, p, p, p]
111
122
  when 2
112
- p,q = padding_parts
113
- [p,q,p,q]
123
+ p, q = padding_parts
124
+ [p, q, p, q]
114
125
  when 3
115
- p,q,r = padding_parts
116
- [p,q,r,q]
126
+ p, q, r = padding_parts
127
+ [p, q, r, q]
117
128
  when 4
118
129
  padding_parts
119
130
  else raise ArgumentError, "Couldn't parse padding string '#{str}' - should be a css-style string"
120
131
  end
121
132
  end
122
133
  end
123
-
124
134
  end
125
135
  end
126
136
  end
127
-
@@ -1,11 +1,11 @@
1
- require 'dragonfly/image_magick/analysers/image_properties'
2
- require 'dragonfly/image_magick/generators/convert'
3
- require 'dragonfly/image_magick/generators/plain'
4
- require 'dragonfly/image_magick/generators/plasma'
5
- require 'dragonfly/image_magick/generators/text'
6
- require 'dragonfly/image_magick/processors/convert'
7
- require 'dragonfly/image_magick/processors/encode'
8
- require 'dragonfly/image_magick/processors/thumb'
1
+ require "dragonfly/image_magick/analysers/image_properties"
2
+ require "dragonfly/image_magick/generators/plain"
3
+ require "dragonfly/image_magick/generators/plasma"
4
+ require "dragonfly/image_magick/generators/text"
5
+ require "dragonfly/image_magick/processors/encode"
6
+ require "dragonfly/image_magick/processors/thumb"
7
+ require "dragonfly/image_magick/commands"
8
+ require "dragonfly/param_validators"
9
9
 
10
10
  module Dragonfly
11
11
  module ImageMagick
@@ -13,37 +13,36 @@ module Dragonfly
13
13
  # The ImageMagick Plugin registers an app with generators, analysers and processors.
14
14
  # Look at the source code for #call to see exactly how it configures the app.
15
15
  class Plugin
16
-
17
- def call(app, opts={})
16
+ def call(app, opts = {})
18
17
  # ENV
19
- app.env[:convert_command] = opts[:convert_command] || 'convert'
20
- app.env[:identify_command] = opts[:identify_command] || 'identify'
18
+ app.env[:convert_command] = opts[:convert_command] || "convert"
19
+ app.env[:identify_command] = opts[:identify_command] || "identify"
21
20
 
22
21
  # Analysers
23
22
  app.add_analyser :image_properties, ImageMagick::Analysers::ImageProperties.new
24
23
  app.add_analyser :width do |content|
25
- content.analyse(:image_properties)['width']
24
+ content.analyse(:image_properties)["width"]
26
25
  end
27
26
  app.add_analyser :height do |content|
28
- content.analyse(:image_properties)['height']
27
+ content.analyse(:image_properties)["height"]
29
28
  end
30
29
  app.add_analyser :format do |content|
31
- content.analyse(:image_properties)['format']
30
+ content.analyse(:image_properties)["format"]
32
31
  end
33
32
  app.add_analyser :aspect_ratio do |content|
34
33
  attrs = content.analyse(:image_properties)
35
- attrs['width'].to_f / attrs['height']
34
+ attrs["width"].to_f / attrs["height"]
36
35
  end
37
36
  app.add_analyser :portrait do |content|
38
37
  attrs = content.analyse(:image_properties)
39
- attrs['width'] <= attrs['height']
38
+ attrs["width"] <= attrs["height"]
40
39
  end
41
40
  app.add_analyser :landscape do |content|
42
41
  !content.analyse(:portrait)
43
42
  end
44
43
  app.add_analyser :image do |content|
45
44
  begin
46
- content.analyse(:image_properties)['format'] != 'pdf'
45
+ content.analyse(:image_properties)["format"] != "pdf"
47
46
  rescue Shell::CommandFailed
48
47
  false
49
48
  end
@@ -55,29 +54,31 @@ module Dragonfly
55
54
  app.define(:image?) { image }
56
55
 
57
56
  # Generators
58
- app.add_generator :convert, ImageMagick::Generators::Convert.new
59
57
  app.add_generator :plain, ImageMagick::Generators::Plain.new
60
58
  app.add_generator :plasma, ImageMagick::Generators::Plasma.new
61
59
  app.add_generator :text, ImageMagick::Generators::Text.new
60
+ app.add_generator :convert do
61
+ raise "The convert generator is deprecated for better security - use Dragonfly::ImageMagick::Commands.generate(content, args, format) instead."
62
+ end
62
63
 
63
64
  # Processors
64
- app.add_processor :convert, Processors::Convert.new
65
65
  app.add_processor :encode, Processors::Encode.new
66
66
  app.add_processor :thumb, Processors::Thumb.new
67
67
  app.add_processor :rotate do |content, amount|
68
- content.process!(:convert, "-rotate #{amount}")
68
+ ParamValidators.validate!(amount, &ParamValidators.is_number)
69
+ Commands.convert(content, "-rotate #{amount}")
70
+ end
71
+ app.add_processor :convert do
72
+ raise "The convert processor is deprecated for better security - use Dragonfly::ImageMagick::Commands.convert(content, args, opts) instead."
69
73
  end
70
74
 
71
75
  # Extra methods
72
- app.define :identify do |cli_args=nil|
76
+ app.define :identify do |cli_args = nil|
73
77
  shell_eval do |path|
74
78
  "#{app.env[:identify_command]} #{cli_args} #{path}"
75
79
  end
76
80
  end
77
-
78
81
  end
79
-
80
82
  end
81
83
  end
82
84
  end
83
-
@@ -1,18 +1,29 @@
1
+ require "dragonfly/image_magick/commands"
2
+
1
3
  module Dragonfly
2
4
  module ImageMagick
3
5
  module Processors
4
6
  class Encode
7
+ include ParamValidators
8
+
9
+ WHITELISTED_ARGS = %w(quality)
10
+
11
+ IS_IN_WHITELISTED_ARGS = ->(args_string) {
12
+ args_string.scan(/-\w+/).all? { |arg|
13
+ WHITELISTED_ARGS.include?(arg.sub("-", ""))
14
+ }
15
+ }
5
16
 
6
- def update_url(attrs, format, args="")
17
+ def update_url(attrs, format, args = "")
7
18
  attrs.ext = format.to_s
8
19
  end
9
20
 
10
- def call(content, format, args="")
11
- content.process!(:convert, args, 'format' => format)
21
+ def call(content, format, args = "")
22
+ validate!(format, &is_word)
23
+ validate!(args, &IS_IN_WHITELISTED_ARGS)
24
+ Commands.convert(content, args, "format" => format)
12
25
  end
13
-
14
26
  end
15
27
  end
16
28
  end
17
29
  end
18
-