dragonfly 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/History.md +10 -0
  4. data/lib/dragonfly/content.rb +17 -18
  5. data/lib/dragonfly/image_magick/commands.rb +35 -0
  6. data/lib/dragonfly/image_magick/generators/plain.rb +13 -7
  7. data/lib/dragonfly/image_magick/generators/plasma.rb +10 -6
  8. data/lib/dragonfly/image_magick/generators/text.rb +67 -58
  9. data/lib/dragonfly/image_magick/plugin.rb +26 -25
  10. data/lib/dragonfly/image_magick/processors/encode.rb +16 -5
  11. data/lib/dragonfly/image_magick/processors/thumb.rb +37 -31
  12. data/lib/dragonfly/param_validators.rb +37 -0
  13. data/lib/dragonfly/response.rb +2 -2
  14. data/lib/dragonfly/version.rb +1 -1
  15. data/spec/dragonfly/image_magick/commands_spec.rb +98 -0
  16. data/spec/dragonfly/image_magick/generators/plain_spec.rb +39 -13
  17. data/spec/dragonfly/image_magick/generators/plasma_spec.rb +28 -9
  18. data/spec/dragonfly/image_magick/generators/text_spec.rb +51 -20
  19. data/spec/dragonfly/image_magick/plugin_spec.rb +45 -28
  20. data/spec/dragonfly/image_magick/processors/encode_spec.rb +30 -0
  21. data/spec/dragonfly/image_magick/processors/thumb_spec.rb +46 -45
  22. data/spec/dragonfly/param_validators_spec.rb +89 -0
  23. data/spec/functional/shell_commands_spec.rb +6 -9
  24. data/spec/spec_helper.rb +12 -14
  25. metadata +11 -10
  26. data/lib/dragonfly/image_magick/generators/convert.rb +0 -19
  27. data/lib/dragonfly/image_magick/processors/convert.rb +0 -33
  28. data/spec/dragonfly/image_magick/generators/convert_spec.rb +0 -19
  29. 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: 16341445bb67f27ae4dc7492244b038df290440a5f4b64d71cca3c637fd8b4d0
4
+ data.tar.gz: '0285f83e005198572fb9a52dea16eef2acca80ded36e9fa905b88d66701221fe'
5
5
  SHA512:
6
- metadata.gz: 28b284e2ca3d15914787357c2035f900a2c0c61c5fdc0b36302b2bcd24b01683b073774b70a9583ef446ba764dbb7019e3e92eab8cd83b042d428236498ccaf4
7
- data.tar.gz: 2fc17f0f4587fe85ccf6af91798a0a89aa58086a9518445d2684abcffd16ab9d53c10fdea2e7f0a5cb2f07a24774b3865b80e7ddd293f5a53bfea4f04b0b24bd
6
+ metadata.gz: ea5f8440e718e3521eaca6824b3dd600817994ed144b5c69a9e609fa24ff8e9583a415a9a2ee9a309e7f622d7d33e0712525ce18ed020942a1585b80ef889cef
7
+ data.tar.gz: 1c81c3c4ed0bfe6421d3ca6ca69432929a4e8233b468d582586fca5d308758bbc9c6042d830e42bd4421c198452df019e802fcf151c277e3ac7248942cf30af0
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,13 @@
1
+ # 1.4.0 (2021-05-19)
2
+
3
+ ## Changes
4
+
5
+ - Removed `convert` processor and generator (which were quite insecure), in favour of utility commands in `Dragonfly::ImageMagick::Commands`
6
+
7
+ ## Fixes
8
+
9
+ - Better security for all job steps with parameter validations
10
+
1
11
  # 1.3.0 (2021-01-09)
2
12
 
3
13
  ## Changes
@@ -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
-