dragonfly 1.3.0 → 1.4.1

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.
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
-