paperclip 3.3.1 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of paperclip might be problematic. Click here for more details.
- data/.gitignore +2 -1
- data/.travis.yml +2 -0
- data/NEWS +1 -0
- data/README.md +1 -1
- data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +1 -1
- data/lib/paperclip.rb +4 -1
- data/lib/paperclip/attachment.rb +27 -6
- data/lib/paperclip/callbacks.rb +2 -2
- data/lib/paperclip/content_type_detector.rb +1 -12
- data/lib/paperclip/file_command_content_type_detector.rb +32 -0
- data/lib/paperclip/geometry.rb +33 -30
- data/lib/paperclip/geometry_detector_factory.rb +41 -0
- data/lib/paperclip/geometry_parser_factory.rb +31 -0
- data/lib/paperclip/helpers.rb +7 -7
- data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +17 -1
- data/lib/paperclip/io_adapters/uri_adapter.rb +1 -0
- data/lib/paperclip/storage/filesystem.rb +11 -2
- data/lib/paperclip/storage/fog.rb +28 -13
- data/lib/paperclip/storage/s3.rb +39 -18
- data/lib/paperclip/thumbnail.rb +3 -0
- data/lib/paperclip/validators/attachment_content_type_validator.rb +29 -8
- data/lib/paperclip/version.rb +1 -1
- data/paperclip.gemspec +1 -1
- data/test/attachment_processing_test.rb +29 -0
- data/test/attachment_test.rb +97 -11
- data/test/file_command_content_type_detector_test.rb +25 -0
- data/test/generator_test.rb +3 -3
- data/test/geometry_detector_test.rb +24 -0
- data/test/geometry_parser_test.rb +73 -0
- data/test/geometry_test.rb +39 -7
- data/test/helper.rb +5 -1
- data/test/integration_test.rb +46 -1
- data/test/io_adapters/uploaded_file_adapter_test.rb +28 -4
- data/test/io_adapters/uri_adapter_test.rb +4 -0
- data/test/paperclip_test.rb +17 -7
- data/test/storage/fog_test.rb +66 -7
- data/test/storage/s3_test.rb +1 -1
- data/test/style_test.rb +18 -14
- data/test/thumbnail_test.rb +10 -56
- data/test/validators/attachment_content_type_validator_test.rb +155 -55
- metadata +137 -127
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -2,6 +2,7 @@ rvm:
|
|
2
2
|
- 1.9.2
|
3
3
|
- 1.9.3
|
4
4
|
- jruby-19mode
|
5
|
+
- rbx-19mode
|
5
6
|
|
6
7
|
before_script: "sudo ntpdate -ub ntp.ubuntu.com pool.ntp.org; true"
|
7
8
|
script: "bundle exec rake clean test cucumber"
|
@@ -14,3 +15,4 @@ gemfile:
|
|
14
15
|
matrix:
|
15
16
|
allow_failures:
|
16
17
|
- rvm: jruby-19mode
|
18
|
+
- rvm: rbx-19mode
|
data/NEWS
CHANGED
@@ -123,6 +123,7 @@ New in 3.0.1:
|
|
123
123
|
|
124
124
|
* Feature: Introduce Paperlip IO adapter.
|
125
125
|
* Bug fix: Regression in AttachmentContentTypeValidator has been fixed.
|
126
|
+
* API CHANGE: #to_file has been removed. Use the #copy_to_local_file method instead.
|
126
127
|
|
127
128
|
New in 3.0.0:
|
128
129
|
|
data/README.md
CHANGED
@@ -615,7 +615,7 @@ Please see `CONTRIBUTING.md` for more details on contributing and running test.
|
|
615
615
|
Credits
|
616
616
|
-------
|
617
617
|
|
618
|
-
![thoughtbot](http://thoughtbot.com/
|
618
|
+
![thoughtbot](http://thoughtbot.com/assets/tm/logo.png)
|
619
619
|
|
620
620
|
Paperclip is maintained and funded by [thoughtbot, inc](http://thoughtbot.com/community)
|
621
621
|
|
data/lib/paperclip.rb
CHANGED
@@ -29,6 +29,8 @@ require 'erb'
|
|
29
29
|
require 'digest'
|
30
30
|
require 'tempfile'
|
31
31
|
require 'paperclip/version'
|
32
|
+
require 'paperclip/geometry_parser_factory'
|
33
|
+
require 'paperclip/geometry_detector_factory'
|
32
34
|
require 'paperclip/geometry'
|
33
35
|
require 'paperclip/processor'
|
34
36
|
require 'paperclip/tempfile'
|
@@ -40,6 +42,7 @@ require 'paperclip/attachment'
|
|
40
42
|
require 'paperclip/attachment_options'
|
41
43
|
require 'paperclip/storage'
|
42
44
|
require 'paperclip/callbacks'
|
45
|
+
require 'paperclip/file_command_content_type_detector'
|
43
46
|
require 'paperclip/content_type_detector'
|
44
47
|
require 'paperclip/glue'
|
45
48
|
require 'paperclip/errors'
|
@@ -179,7 +182,7 @@ module Paperclip
|
|
179
182
|
|
180
183
|
attachment_definitions[name] = Paperclip::AttachmentOptions.new(options)
|
181
184
|
Paperclip.classes_with_attachments << self.name
|
182
|
-
Paperclip.
|
185
|
+
Paperclip.check_for_path_clash(name,attachment_definitions[name][:path],self.name)
|
183
186
|
|
184
187
|
after_save :save_attached_files
|
185
188
|
before_destroy :prepare_for_destroy
|
data/lib/paperclip/attachment.rb
CHANGED
@@ -12,6 +12,7 @@ module Paperclip
|
|
12
12
|
:convert_options => {},
|
13
13
|
:default_style => :original,
|
14
14
|
:default_url => "/:attachment/:style/missing.png",
|
15
|
+
:escape_url => true,
|
15
16
|
:restricted_characters => /[&$+,\/:;=?@<>\[\]\{\}\|\\\^~%# ]/,
|
16
17
|
:hash_data => ":class/:attachment/:id/:style/:updated_at",
|
17
18
|
:hash_digest => "SHA1",
|
@@ -60,6 +61,7 @@ module Paperclip
|
|
60
61
|
# +preserve_files+ - whether to keep files on the filesystem when deleting or clearing the attachment. Defaults to false
|
61
62
|
# +interpolator+ - the object used to interpolate filenames and URLs. Defaults to Paperclip::Interpolations
|
62
63
|
# +url_generator+ - the object used to generate URLs, using the interpolator. Defaults to Paperclip::UrlGenerator
|
64
|
+
# +escape_url+ - Perform URI escaping to URLs. Defaults to true
|
63
65
|
def initialize(name, instance, options = {})
|
64
66
|
@name = name
|
65
67
|
@instance = instance
|
@@ -90,8 +92,7 @@ module Paperclip
|
|
90
92
|
ensure_required_accessors!
|
91
93
|
file = Paperclip.io_adapters.for(uploaded_file)
|
92
94
|
|
93
|
-
|
94
|
-
self.clear(*@options[:only_process])
|
95
|
+
self.clear(*only_process)
|
95
96
|
return nil if file.nil?
|
96
97
|
|
97
98
|
@queued_for_write[:original] = file
|
@@ -104,7 +105,7 @@ module Paperclip
|
|
104
105
|
|
105
106
|
@dirty = true
|
106
107
|
|
107
|
-
post_process(
|
108
|
+
post_process(*only_process) if post_processing && valid_assignment?
|
108
109
|
|
109
110
|
# Reset the file size if the original file was reprocessed.
|
110
111
|
instance_write(:file_size, @queued_for_write[:original].size)
|
@@ -134,7 +135,7 @@ module Paperclip
|
|
134
135
|
# +#new(Paperclip::Attachment, options_hash)+
|
135
136
|
# +#for(style_name, options_hash)+
|
136
137
|
def url(style_name = default_style, options = {})
|
137
|
-
default_options = {:timestamp => @options[:use_timestamp], :escape =>
|
138
|
+
default_options = {:timestamp => @options[:use_timestamp], :escape => @options[:escape_url]}
|
138
139
|
|
139
140
|
if options == true || options == false # Backwards compatibility.
|
140
141
|
@url_generator.for(style_name, default_options.merge(:timestamp => options))
|
@@ -157,6 +158,10 @@ module Paperclip
|
|
157
158
|
url(style_name)
|
158
159
|
end
|
159
160
|
|
161
|
+
def as_json(options = nil)
|
162
|
+
to_s((options && options[:style]) || default_style)
|
163
|
+
end
|
164
|
+
|
160
165
|
def default_style
|
161
166
|
@options[:default_style]
|
162
167
|
end
|
@@ -174,6 +179,12 @@ module Paperclip
|
|
174
179
|
@normalized_styles
|
175
180
|
end
|
176
181
|
|
182
|
+
def only_process
|
183
|
+
only_process = @options[:only_process].dup
|
184
|
+
only_process = only_process.call(self) if only_process.respond_to?(:call)
|
185
|
+
only_process.map(&:to_sym)
|
186
|
+
end
|
187
|
+
|
177
188
|
def processors
|
178
189
|
processing_option = @options[:processors]
|
179
190
|
|
@@ -311,6 +322,10 @@ module Paperclip
|
|
311
322
|
|
312
323
|
alias :present? :file?
|
313
324
|
|
325
|
+
def blank?
|
326
|
+
not present?
|
327
|
+
end
|
328
|
+
|
314
329
|
# Determines whether the instance responds to this attribute. Used to prevent
|
315
330
|
# calculations on fields we won't even store.
|
316
331
|
def instance_respond_to?(attr)
|
@@ -354,8 +369,14 @@ module Paperclip
|
|
354
369
|
Paperclip.log(message)
|
355
370
|
end
|
356
371
|
|
357
|
-
def valid_assignment?
|
358
|
-
|
372
|
+
def valid_assignment? #:nodoc:
|
373
|
+
if instance.valid?
|
374
|
+
true
|
375
|
+
else
|
376
|
+
instance.errors.none? do |attr, message|
|
377
|
+
attr.to_s.start_with?(@name.to_s)
|
378
|
+
end
|
379
|
+
end
|
359
380
|
end
|
360
381
|
|
361
382
|
def initialize_storage #:nodoc:
|
data/lib/paperclip/callbacks.rb
CHANGED
@@ -49,18 +49,7 @@ module Paperclip
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def type_from_file_command
|
52
|
-
|
53
|
-
# On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
|
54
|
-
Paperclip.run("file", "-b --mime :file", :file => @filename)
|
55
|
-
rescue Cocaine::CommandLineError => e
|
56
|
-
Paperclip.log("Error while determining content type: #{e}")
|
57
|
-
SENSIBLE_DEFAULT
|
58
|
-
end
|
59
|
-
|
60
|
-
if type.match(/\(.*?\)/)
|
61
|
-
type = SENSIBLE_DEFAULT
|
62
|
-
end
|
63
|
-
type.split(/[:;\s]+/)[0]
|
52
|
+
FileCommandContentTypeDetector.new(@filename).detect
|
64
53
|
end
|
65
54
|
|
66
55
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Paperclip
|
2
|
+
class FileCommandContentTypeDetector
|
3
|
+
SENSIBLE_DEFAULT = "application/octet-stream"
|
4
|
+
|
5
|
+
def initialize(filename)
|
6
|
+
@filename = filename
|
7
|
+
end
|
8
|
+
|
9
|
+
def detect
|
10
|
+
type_from_file_command
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def type_from_file_command
|
16
|
+
type = begin
|
17
|
+
# On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
|
18
|
+
Paperclip.run("file", "-b --mime :file", :file => @filename)
|
19
|
+
rescue Cocaine::CommandLineError => e
|
20
|
+
Paperclip.log("Error while determining content type: #{e}")
|
21
|
+
SENSIBLE_DEFAULT
|
22
|
+
end
|
23
|
+
|
24
|
+
if type.nil? || type.match(/\(.*?\)/)
|
25
|
+
type = SENSIBLE_DEFAULT
|
26
|
+
end
|
27
|
+
type.split(/[:;\s]+/)[0]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
data/lib/paperclip/geometry.rb
CHANGED
@@ -4,37 +4,40 @@ module Paperclip
|
|
4
4
|
class Geometry
|
5
5
|
attr_accessor :height, :width, :modifier
|
6
6
|
|
7
|
+
EXIF_ROTATED_ORIENTATION_VALUES = [5, 6, 7, 8]
|
8
|
+
|
7
9
|
# Gives a Geometry representing the given height and width
|
8
|
-
def initialize
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
#
|
35
|
-
def
|
36
|
-
if
|
37
|
-
|
10
|
+
def initialize(width = nil, height = nil, modifier = nil)
|
11
|
+
if width.is_a?(Hash)
|
12
|
+
options = width
|
13
|
+
@height = options[:height].to_f
|
14
|
+
@width = options[:width].to_f
|
15
|
+
@modifier = options[:modifier]
|
16
|
+
@orientation = options[:orientation].to_i
|
17
|
+
else
|
18
|
+
@height = height.to_f
|
19
|
+
@width = width.to_f
|
20
|
+
@modifier = modifier
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Extracts the Geometry from a file (or path to a file)
|
25
|
+
def self.from_file(file)
|
26
|
+
GeometryDetector.new(file).make
|
27
|
+
end
|
28
|
+
|
29
|
+
# Extracts the Geometry from a "WxH,O" string
|
30
|
+
# Where W is the width, H is the height,
|
31
|
+
# and O is the EXIF orientation
|
32
|
+
def self.parse(string)
|
33
|
+
GeometryParser.new(string).make
|
34
|
+
end
|
35
|
+
|
36
|
+
# Swaps the height and width if necessary
|
37
|
+
def auto_orient
|
38
|
+
if EXIF_ROTATED_ORIENTATION_VALUES.include?(@orientation)
|
39
|
+
@height, @width = @width, @height
|
40
|
+
@orientation -= 4
|
38
41
|
end
|
39
42
|
end
|
40
43
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Paperclip
|
2
|
+
class GeometryDetector
|
3
|
+
def initialize(file)
|
4
|
+
@file = file
|
5
|
+
raise_if_blank_file
|
6
|
+
end
|
7
|
+
|
8
|
+
def make
|
9
|
+
geometry = GeometryParser.new(geometry_string.strip).make
|
10
|
+
geometry || raise(Errors::NotIdentifiedByImageMagickError.new)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def geometry_string
|
16
|
+
begin
|
17
|
+
silence_stream(STDERR) do
|
18
|
+
Paperclip.run("identify", "-format '%wx%h,%[exif:orientation]' :file", :file => "#{path}[0]")
|
19
|
+
end
|
20
|
+
rescue Cocaine::ExitStatusError
|
21
|
+
""
|
22
|
+
rescue Cocaine::CommandNotFoundError => e
|
23
|
+
raise_because_imagemagick_missing
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def path
|
28
|
+
@file.respond_to?(:path) ? @file.path : @file
|
29
|
+
end
|
30
|
+
|
31
|
+
def raise_if_blank_file
|
32
|
+
if path.blank?
|
33
|
+
raise Errors::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def raise_because_imagemagick_missing
|
38
|
+
raise Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Paperclip
|
2
|
+
class GeometryParser
|
3
|
+
FORMAT = /\b(\d*)x?(\d*)\b(?:,(\d?))?([\>\<\#\@\%^!])?/i
|
4
|
+
def initialize(string)
|
5
|
+
@string = string
|
6
|
+
end
|
7
|
+
|
8
|
+
def make
|
9
|
+
if match
|
10
|
+
Geometry.new(
|
11
|
+
:height => @height,
|
12
|
+
:width => @width,
|
13
|
+
:modifier => @modifier,
|
14
|
+
:orientation => @orientation
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def match
|
22
|
+
if actual_match = @string && @string.match(FORMAT)
|
23
|
+
@width = actual_match[1]
|
24
|
+
@height = actual_match[2]
|
25
|
+
@orientation = actual_match[3]
|
26
|
+
@modifier = actual_match[4]
|
27
|
+
end
|
28
|
+
actual_match
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/paperclip/helpers.rb
CHANGED
@@ -17,7 +17,7 @@ module Paperclip
|
|
17
17
|
# :expected_outcodes -> An array of integers that defines the expected exit codes
|
18
18
|
# of the binary. Defaults to [0].
|
19
19
|
#
|
20
|
-
# :log_command -> Log the command being run when set to true (defaults to
|
20
|
+
# :log_command -> Log the command being run when set to true (defaults to true).
|
21
21
|
# This will only log if logging in general is set to true as well.
|
22
22
|
#
|
23
23
|
# :swallow_stderr -> Set to true if you don't care what happens on STDERR.
|
@@ -49,13 +49,13 @@ module Paperclip
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
-
def
|
53
|
-
@
|
54
|
-
|
55
|
-
if @
|
56
|
-
log("Duplicate
|
52
|
+
def check_for_path_clash(name,path,klass)
|
53
|
+
@names_path ||= {}
|
54
|
+
default_path = path || Attachment.default_options[:path]
|
55
|
+
if @names_path[name] && @names_path[name][:path] == default_path && @names_path[name][:class] != klass && @names_path[name][:path] !~ /:class/
|
56
|
+
log("Duplicate path for #{name} with #{default_path}. This will clash with attachment defined in #{@names_path[name][:class]} class")
|
57
57
|
end
|
58
|
-
@
|
58
|
+
@names_path[name] = {:path => default_path, :class => klass}
|
59
59
|
end
|
60
60
|
|
61
61
|
def reset_duplicate_clash_check!
|
@@ -11,13 +11,29 @@ module Paperclip
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
class << self
|
15
|
+
attr_accessor :content_type_detector
|
16
|
+
end
|
17
|
+
|
14
18
|
private
|
15
19
|
|
16
20
|
def cache_current_values
|
17
21
|
@original_filename = @target.original_filename
|
18
|
-
@content_type =
|
22
|
+
@content_type = determine_content_type
|
19
23
|
@size = File.size(@target.path)
|
20
24
|
end
|
25
|
+
|
26
|
+
def content_type_detector
|
27
|
+
self.class.content_type_detector
|
28
|
+
end
|
29
|
+
|
30
|
+
def determine_content_type
|
31
|
+
content_type = @target.content_type.to_s.strip
|
32
|
+
if content_type_detector
|
33
|
+
content_type = content_type_detector.new(@target.path).detect
|
34
|
+
end
|
35
|
+
content_type
|
36
|
+
end
|
21
37
|
end
|
22
38
|
end
|
23
39
|
|