paperclip-fix 4.3.7

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 (197) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.hound.yml +1066 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +22 -0
  6. data/Appraisals +11 -0
  7. data/CONTRIBUTING.md +75 -0
  8. data/Gemfile +21 -0
  9. data/LICENSE +24 -0
  10. data/NEWS +420 -0
  11. data/README.md +979 -0
  12. data/RELEASING.md +17 -0
  13. data/Rakefile +44 -0
  14. data/UPGRADING +14 -0
  15. data/cucumber/paperclip_steps.rb +6 -0
  16. data/features/basic_integration.feature +80 -0
  17. data/features/migration.feature +94 -0
  18. data/features/rake_tasks.feature +62 -0
  19. data/features/step_definitions/attachment_steps.rb +110 -0
  20. data/features/step_definitions/html_steps.rb +15 -0
  21. data/features/step_definitions/rails_steps.rb +236 -0
  22. data/features/step_definitions/s3_steps.rb +14 -0
  23. data/features/step_definitions/web_steps.rb +107 -0
  24. data/features/support/env.rb +11 -0
  25. data/features/support/fakeweb.rb +13 -0
  26. data/features/support/file_helpers.rb +34 -0
  27. data/features/support/fixtures/boot_config.txt +15 -0
  28. data/features/support/fixtures/gemfile.txt +5 -0
  29. data/features/support/fixtures/preinitializer.txt +20 -0
  30. data/features/support/paths.rb +28 -0
  31. data/features/support/rails.rb +63 -0
  32. data/features/support/selectors.rb +19 -0
  33. data/gemfiles/3.2.gemfile +19 -0
  34. data/gemfiles/4.1.gemfile +19 -0
  35. data/gemfiles/4.2.gemfile +19 -0
  36. data/lib/generators/paperclip/USAGE +8 -0
  37. data/lib/generators/paperclip/paperclip_generator.rb +30 -0
  38. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  39. data/lib/paperclip/attachment.rb +608 -0
  40. data/lib/paperclip/attachment_registry.rb +59 -0
  41. data/lib/paperclip/callbacks.rb +40 -0
  42. data/lib/paperclip/content_type_detector.rb +79 -0
  43. data/lib/paperclip/deprecations.rb +42 -0
  44. data/lib/paperclip/errors.rb +32 -0
  45. data/lib/paperclip/file_command_content_type_detector.rb +30 -0
  46. data/lib/paperclip/filename_cleaner.rb +16 -0
  47. data/lib/paperclip/geometry.rb +158 -0
  48. data/lib/paperclip/geometry_detector_factory.rb +48 -0
  49. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  50. data/lib/paperclip/glue.rb +17 -0
  51. data/lib/paperclip/has_attached_file.rb +109 -0
  52. data/lib/paperclip/helpers.rb +56 -0
  53. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  54. data/lib/paperclip/interpolations.rb +197 -0
  55. data/lib/paperclip/io_adapters/abstract_adapter.rb +47 -0
  56. data/lib/paperclip/io_adapters/attachment_adapter.rb +36 -0
  57. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  58. data/lib/paperclip/io_adapters/empty_string_adapter.rb +18 -0
  59. data/lib/paperclip/io_adapters/file_adapter.rb +22 -0
  60. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +15 -0
  61. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -0
  62. data/lib/paperclip/io_adapters/nil_adapter.rb +34 -0
  63. data/lib/paperclip/io_adapters/registry.rb +32 -0
  64. data/lib/paperclip/io_adapters/stringio_adapter.rb +33 -0
  65. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +42 -0
  66. data/lib/paperclip/io_adapters/uri_adapter.rb +63 -0
  67. data/lib/paperclip/locales/de.yml +18 -0
  68. data/lib/paperclip/locales/en.yml +18 -0
  69. data/lib/paperclip/locales/es.yml +18 -0
  70. data/lib/paperclip/locales/ja.yml +18 -0
  71. data/lib/paperclip/locales/pt-BR.yml +18 -0
  72. data/lib/paperclip/locales/zh-CN.yml +18 -0
  73. data/lib/paperclip/locales/zh-HK.yml +18 -0
  74. data/lib/paperclip/locales/zh-TW.yml +18 -0
  75. data/lib/paperclip/logger.rb +21 -0
  76. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  77. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +100 -0
  78. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  79. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +96 -0
  80. data/lib/paperclip/matchers.rb +64 -0
  81. data/lib/paperclip/media_type_spoof_detector.rb +89 -0
  82. data/lib/paperclip/missing_attachment_styles.rb +79 -0
  83. data/lib/paperclip/processor.rb +48 -0
  84. data/lib/paperclip/processor_helpers.rb +50 -0
  85. data/lib/paperclip/rails_environment.rb +25 -0
  86. data/lib/paperclip/railtie.rb +31 -0
  87. data/lib/paperclip/schema.rb +83 -0
  88. data/lib/paperclip/storage/filesystem.rb +90 -0
  89. data/lib/paperclip/storage/fog.rb +241 -0
  90. data/lib/paperclip/storage/s3.rb +440 -0
  91. data/lib/paperclip/storage.rb +3 -0
  92. data/lib/paperclip/style.rb +109 -0
  93. data/lib/paperclip/tempfile.rb +43 -0
  94. data/lib/paperclip/tempfile_factory.rb +23 -0
  95. data/lib/paperclip/thumbnail.rb +121 -0
  96. data/lib/paperclip/url_generator.rb +72 -0
  97. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  98. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  99. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  100. data/lib/paperclip/validators/attachment_presence_validator.rb +30 -0
  101. data/lib/paperclip/validators/attachment_size_validator.rb +115 -0
  102. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
  103. data/lib/paperclip/validators.rb +74 -0
  104. data/lib/paperclip/version.rb +3 -0
  105. data/lib/paperclip.rb +213 -0
  106. data/lib/tasks/paperclip.rake +127 -0
  107. data/paperclip.gemspec +51 -0
  108. data/shoulda_macros/paperclip.rb +134 -0
  109. data/spec/database.yml +4 -0
  110. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  111. data/spec/paperclip/attachment_processing_spec.rb +82 -0
  112. data/spec/paperclip/attachment_registry_spec.rb +130 -0
  113. data/spec/paperclip/attachment_spec.rb +1494 -0
  114. data/spec/paperclip/content_type_detector_spec.rb +48 -0
  115. data/spec/paperclip/deprecations_spec.rb +65 -0
  116. data/spec/paperclip/file_command_content_type_detector_spec.rb +26 -0
  117. data/spec/paperclip/filename_cleaner_spec.rb +14 -0
  118. data/spec/paperclip/geometry_detector_spec.rb +39 -0
  119. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  120. data/spec/paperclip/geometry_spec.rb +255 -0
  121. data/spec/paperclip/glue_spec.rb +44 -0
  122. data/spec/paperclip/has_attached_file_spec.rb +142 -0
  123. data/spec/paperclip/integration_spec.rb +667 -0
  124. data/spec/paperclip/interpolations_spec.rb +262 -0
  125. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +78 -0
  126. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +139 -0
  127. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +83 -0
  128. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  129. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  130. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +104 -0
  131. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  132. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  133. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  134. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  135. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  136. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +127 -0
  137. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  138. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +99 -0
  139. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  140. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  141. data/spec/paperclip/media_type_spoof_detector_spec.rb +79 -0
  142. data/spec/paperclip/meta_class_spec.rb +30 -0
  143. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  144. data/spec/paperclip/paperclip_spec.rb +222 -0
  145. data/spec/paperclip/plural_cache_spec.rb +37 -0
  146. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  147. data/spec/paperclip/processor_spec.rb +26 -0
  148. data/spec/paperclip/rails_environment_spec.rb +33 -0
  149. data/spec/paperclip/rake_spec.rb +103 -0
  150. data/spec/paperclip/schema_spec.rb +248 -0
  151. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  152. data/spec/paperclip/storage/fog_spec.rb +535 -0
  153. data/spec/paperclip/storage/s3_live_spec.rb +182 -0
  154. data/spec/paperclip/storage/s3_spec.rb +1526 -0
  155. data/spec/paperclip/style_spec.rb +255 -0
  156. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  157. data/spec/paperclip/thumbnail_spec.rb +500 -0
  158. data/spec/paperclip/url_generator_spec.rb +211 -0
  159. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  160. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  161. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  162. data/spec/paperclip/validators/attachment_size_validator_spec.rb +229 -0
  163. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  164. data/spec/paperclip/validators_spec.rb +164 -0
  165. data/spec/spec_helper.rb +43 -0
  166. data/spec/support/assertions.rb +71 -0
  167. data/spec/support/deprecations.rb +9 -0
  168. data/spec/support/fake_model.rb +25 -0
  169. data/spec/support/fake_rails.rb +12 -0
  170. data/spec/support/fixtures/12k.png +0 -0
  171. data/spec/support/fixtures/50x50.png +0 -0
  172. data/spec/support/fixtures/5k.png +0 -0
  173. data/spec/support/fixtures/animated +0 -0
  174. data/spec/support/fixtures/animated.gif +0 -0
  175. data/spec/support/fixtures/animated.unknown +0 -0
  176. data/spec/support/fixtures/bad.png +1 -0
  177. data/spec/support/fixtures/empty.html +1 -0
  178. data/spec/support/fixtures/empty.xlsx +0 -0
  179. data/spec/support/fixtures/fog.yml +8 -0
  180. data/spec/support/fixtures/rotated.jpg +0 -0
  181. data/spec/support/fixtures/s3.yml +8 -0
  182. data/spec/support/fixtures/spaced file.jpg +0 -0
  183. data/spec/support/fixtures/spaced file.png +0 -0
  184. data/spec/support/fixtures/text.txt +1 -0
  185. data/spec/support/fixtures/twopage.pdf +0 -0
  186. data/spec/support/fixtures/uppercase.PNG +0 -0
  187. data/spec/support/matchers/accept.rb +5 -0
  188. data/spec/support/matchers/exist.rb +5 -0
  189. data/spec/support/matchers/have_column.rb +23 -0
  190. data/spec/support/mock_attachment.rb +22 -0
  191. data/spec/support/mock_interpolator.rb +24 -0
  192. data/spec/support/mock_url_generator_builder.rb +27 -0
  193. data/spec/support/model_reconstruction.rb +60 -0
  194. data/spec/support/rails_helpers.rb +7 -0
  195. data/spec/support/test_data.rb +13 -0
  196. data/spec/support/version_helper.rb +9 -0
  197. metadata +606 -0
@@ -0,0 +1,121 @@
1
+ module Paperclip
2
+ # Handles thumbnailing images that are uploaded.
3
+ class Thumbnail < Processor
4
+
5
+ attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options,
6
+ :source_file_options, :animated, :auto_orient
7
+
8
+ # List of formats that we need to preserve animation
9
+ ANIMATED_FORMATS = %w(gif)
10
+
11
+ # Creates a Thumbnail object set to work on the +file+ given. It
12
+ # will attempt to transform the image into one defined by +target_geometry+
13
+ # which is a "WxH"-style string. +format+ will be inferred from the +file+
14
+ # unless specified. Thumbnail creation will raise no errors unless
15
+ # +whiny+ is true (which it is, by default. If +convert_options+ is
16
+ # set, the options will be appended to the convert command upon image conversion
17
+ #
18
+ # Options include:
19
+ #
20
+ # +geometry+ - the desired width and height of the thumbnail (required)
21
+ # +file_geometry_parser+ - an object with a method named +from_file+ that takes an image file and produces its geometry and a +transformation_to+. Defaults to Paperclip::Geometry
22
+ # +string_geometry_parser+ - an object with a method named +parse+ that takes a string and produces an object with +width+, +height+, and +to_s+ accessors. Defaults to Paperclip::Geometry
23
+ # +source_file_options+ - flags passed to the +convert+ command that influence how the source file is read
24
+ # +convert_options+ - flags passed to the +convert+ command that influence how the image is processed
25
+ # +whiny+ - whether to raise an error when processing fails. Defaults to true
26
+ # +format+ - the desired filename extension
27
+ # +animated+ - whether to merge all the layers in the image. Defaults to true
28
+ def initialize(file, options = {}, attachment = nil)
29
+ super
30
+
31
+ geometry = options[:geometry].to_s
32
+ @crop = geometry[-1,1] == '#'
33
+ @target_geometry = options.fetch(:string_geometry_parser, Geometry).parse(geometry)
34
+ @current_geometry = options.fetch(:file_geometry_parser, Geometry).from_file(@file)
35
+ @source_file_options = options[:source_file_options]
36
+ @convert_options = options[:convert_options]
37
+ @whiny = options.fetch(:whiny, true)
38
+ @format = options[:format]
39
+ @animated = options.fetch(:animated, true)
40
+ @auto_orient = options.fetch(:auto_orient, true)
41
+ if @auto_orient && @current_geometry.respond_to?(:auto_orient)
42
+ @current_geometry.auto_orient
43
+ end
44
+
45
+ @source_file_options = @source_file_options.split(/\s+/) if @source_file_options.respond_to?(:split)
46
+ @convert_options = @convert_options.split(/\s+/) if @convert_options.respond_to?(:split)
47
+
48
+ @current_format = File.extname(@file.path)
49
+ @basename = File.basename(@file.path, @current_format)
50
+ end
51
+
52
+ # Returns true if the +target_geometry+ is meant to crop.
53
+ def crop?
54
+ @crop
55
+ end
56
+
57
+ # Returns true if the image is meant to make use of additional convert options.
58
+ def convert_options?
59
+ !@convert_options.nil? && !@convert_options.empty?
60
+ end
61
+
62
+ # Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile
63
+ # that contains the new image.
64
+ def make
65
+ src = @file
66
+ filename = [@basename, @format ? ".#{@format}" : ""].join
67
+ dst = TempfileFactory.new.generate(filename)
68
+
69
+ begin
70
+ parameters = []
71
+ parameters << source_file_options
72
+ parameters << ":source"
73
+ parameters << transformation_command
74
+ parameters << convert_options
75
+ parameters << ":dest"
76
+
77
+ parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
78
+
79
+ success = convert(parameters, :source => "#{File.expand_path(src.path)}#{'[0]' unless animated?}", :dest => File.expand_path(dst.path))
80
+ rescue Cocaine::ExitStatusError => e
81
+ raise Paperclip::Error, "There was an error processing the thumbnail for #{@basename}" if @whiny
82
+ rescue Cocaine::CommandNotFoundError => e
83
+ raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.")
84
+ end
85
+
86
+ dst
87
+ end
88
+
89
+ # Returns the command ImageMagick's +convert+ needs to transform the image
90
+ # into the thumbnail.
91
+ def transformation_command
92
+ scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
93
+ trans = []
94
+ trans << "-coalesce" if animated?
95
+ trans << "-auto-orient" if auto_orient
96
+ trans << "-resize" << %["#{scale}"] unless scale.nil? || scale.empty?
97
+ trans << "-crop" << %["#{crop}"] << "+repage" if crop
98
+ trans << '-layers "optimize"' if animated?
99
+ trans
100
+ end
101
+
102
+ protected
103
+
104
+ # Return true if the format is animated
105
+ def animated?
106
+ @animated && (ANIMATED_FORMATS.include?(@format.to_s) || @format.blank?) && identified_as_animated?
107
+ end
108
+
109
+ # Return true if ImageMagick's +identify+ returns an animated format
110
+ def identified_as_animated?
111
+ if @identified_as_animated.nil?
112
+ @identified_as_animated = ANIMATED_FORMATS.include? identify("-format %m :file", :file => "#{@file.path}[0]").to_s.downcase.strip
113
+ end
114
+ @identified_as_animated
115
+ rescue Cocaine::ExitStatusError => e
116
+ raise Paperclip::Error, "There was an error running `identify` for #{@basename}" if @whiny
117
+ rescue Cocaine::CommandNotFoundError => e
118
+ raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,72 @@
1
+ require 'uri'
2
+
3
+ module Paperclip
4
+ class UrlGenerator
5
+ def initialize(attachment, attachment_options)
6
+ @attachment = attachment
7
+ @attachment_options = attachment_options
8
+ end
9
+
10
+ def for(style_name, options)
11
+ timestamp_as_needed(
12
+ escape_url_as_needed(
13
+ @attachment_options[:interpolator].interpolate(most_appropriate_url, @attachment, style_name),
14
+ options
15
+ ), options)
16
+ end
17
+
18
+ private
19
+
20
+ # This method is all over the place.
21
+ def default_url
22
+ if @attachment_options[:default_url].respond_to?(:call)
23
+ @attachment_options[:default_url].call(@attachment)
24
+ elsif @attachment_options[:default_url].is_a?(Symbol)
25
+ @attachment.instance.send(@attachment_options[:default_url])
26
+ else
27
+ @attachment_options[:default_url]
28
+ end
29
+ end
30
+
31
+ def most_appropriate_url
32
+ if @attachment.original_filename.nil?
33
+ default_url
34
+ else
35
+ @attachment_options[:url]
36
+ end
37
+ end
38
+
39
+ def timestamp_as_needed(url, options)
40
+ if options[:timestamp] && timestamp_possible?
41
+ delimiter_char = url.match(/\?.+=/) ? '&' : '?'
42
+ "#{url}#{delimiter_char}#{@attachment.updated_at.to_s}"
43
+ else
44
+ url
45
+ end
46
+ end
47
+
48
+ def timestamp_possible?
49
+ @attachment.respond_to?(:updated_at) && @attachment.updated_at.present?
50
+ end
51
+
52
+ def escape_url_as_needed(url, options)
53
+ if options[:escape]
54
+ escape_url(url)
55
+ else
56
+ url
57
+ end
58
+ end
59
+
60
+ def escape_url(url)
61
+ if url.respond_to?(:escape)
62
+ url.escape
63
+ else
64
+ URI.escape(url).gsub(escape_regex){|m| "%#{m.ord.to_s(16).upcase}" }
65
+ end
66
+ end
67
+
68
+ def escape_regex
69
+ /[\?\(\)\[\]\+]/
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,88 @@
1
+ module Paperclip
2
+ module Validators
3
+ class AttachmentContentTypeValidator < ActiveModel::EachValidator
4
+ def initialize(options)
5
+ options[:allow_nil] = true unless options.has_key?(:allow_nil)
6
+ super
7
+ end
8
+
9
+ def self.helper_method_name
10
+ :validates_attachment_content_type
11
+ end
12
+
13
+ def validate_each(record, attribute, value)
14
+ base_attribute = attribute.to_sym
15
+ attribute = "#{attribute}_content_type".to_sym
16
+ value = record.send :read_attribute_for_validation, attribute
17
+
18
+ return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
19
+
20
+ validate_whitelist(record, attribute, value)
21
+ validate_blacklist(record, attribute, value)
22
+
23
+ if record.errors.include? attribute
24
+ record.errors[attribute].each do |error|
25
+ record.errors.add base_attribute, error
26
+ end
27
+ end
28
+ end
29
+
30
+ def validate_whitelist(record, attribute, value)
31
+ if allowed_types.present? && allowed_types.none? { |type| type === value }
32
+ mark_invalid record, attribute, allowed_types
33
+ end
34
+ end
35
+
36
+ def validate_blacklist(record, attribute, value)
37
+ if forbidden_types.present? && forbidden_types.any? { |type| type === value }
38
+ mark_invalid record, attribute, forbidden_types
39
+ end
40
+ end
41
+
42
+ def mark_invalid(record, attribute, types)
43
+ record.errors.add attribute, :invalid, options.merge(:types => types.join(', '))
44
+ end
45
+
46
+ def allowed_types
47
+ [options[:content_type]].flatten.compact
48
+ end
49
+
50
+ def forbidden_types
51
+ [options[:not]].flatten.compact
52
+ end
53
+
54
+ def check_validity!
55
+ unless options.has_key?(:content_type) || options.has_key?(:not)
56
+ raise ArgumentError, "You must pass in either :content_type or :not to the validator"
57
+ end
58
+ end
59
+ end
60
+
61
+ module HelperMethods
62
+ # Places ActiveModel validations on the content type of the file
63
+ # assigned. The possible options are:
64
+ # * +content_type+: Allowed content types. Can be a single content type
65
+ # or an array. Each type can be a String or a Regexp. It should be
66
+ # noted that Internet Explorer uploads files with content_types that you
67
+ # may not expect. For example, JPEG images are given image/pjpeg and
68
+ # PNGs are image/x-png, so keep that in mind when determining how you
69
+ # match. Allows all by default.
70
+ # * +not+: Forbidden content types.
71
+ # * +message+: The message to display when the uploaded file has an invalid
72
+ # content type.
73
+ # * +if+: A lambda or name of an instance method. Validation will only
74
+ # be run is this lambda or method returns true.
75
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
76
+ # NOTE: If you do not specify an [attachment]_content_type field on your
77
+ # model, content_type validation will work _ONLY upon assignment_ and
78
+ # re-validation after the instance has been reloaded will always succeed.
79
+ # You'll still need to have a virtual attribute (created by +attr_accessor+)
80
+ # name +[attachment]_content_type+ to be able to use this validator.
81
+ def validates_attachment_content_type(*attr_names)
82
+ options = _merge_attributes(attr_names)
83
+ validates_with AttachmentContentTypeValidator, options.dup
84
+ validate_before_processing AttachmentContentTypeValidator, options.dup
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,80 @@
1
+ module Paperclip
2
+ module Validators
3
+ class AttachmentFileNameValidator < ActiveModel::EachValidator
4
+ def initialize(options)
5
+ options[:allow_nil] = true unless options.has_key?(:allow_nil)
6
+ super
7
+ end
8
+
9
+ def self.helper_method_name
10
+ :validates_attachment_file_name
11
+ end
12
+
13
+ def validate_each(record, attribute, value)
14
+ base_attribute = attribute.to_sym
15
+ attribute = "#{attribute}_file_name".to_sym
16
+ value = record.send :read_attribute_for_validation, attribute
17
+
18
+ return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
19
+
20
+ validate_whitelist(record, attribute, value)
21
+ validate_blacklist(record, attribute, value)
22
+
23
+ if record.errors.include? attribute
24
+ record.errors[attribute].each do |error|
25
+ record.errors.add base_attribute, error
26
+ end
27
+ end
28
+ end
29
+
30
+ def validate_whitelist(record, attribute, value)
31
+ if allowed.present? && allowed.none? { |type| type === value }
32
+ mark_invalid record, attribute, allowed
33
+ end
34
+ end
35
+
36
+ def validate_blacklist(record, attribute, value)
37
+ if forbidden.present? && forbidden.any? { |type| type === value }
38
+ mark_invalid record, attribute, forbidden
39
+ end
40
+ end
41
+
42
+ def mark_invalid(record, attribute, patterns)
43
+ record.errors.add attribute, :invalid, options.merge(:names => patterns.join(', '))
44
+ end
45
+
46
+ def allowed
47
+ [options[:matches]].flatten.compact
48
+ end
49
+
50
+ def forbidden
51
+ [options[:not]].flatten.compact
52
+ end
53
+
54
+ def check_validity!
55
+ unless options.has_key?(:matches) || options.has_key?(:not)
56
+ raise ArgumentError, "You must pass in either :matches or :not to the validator"
57
+ end
58
+ end
59
+ end
60
+
61
+ module HelperMethods
62
+ # Places ActiveModel validations on the name of the file
63
+ # assigned. The possible options are:
64
+ # * +matches+: Allowed filename patterns as Regexps. Can be a single one
65
+ # or an array.
66
+ # * +not+: Forbidden file name patterns, specified the same was as +matches+.
67
+ # * +message+: The message to display when the uploaded file has an invalid
68
+ # name.
69
+ # * +if+: A lambda or name of an instance method. Validation will only
70
+ # be run is this lambda or method returns true.
71
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
72
+ def validates_attachment_file_name(*attr_names)
73
+ options = _merge_attributes(attr_names)
74
+ validates_with AttachmentFileNameValidator, options.dup
75
+ validate_before_processing AttachmentFileNameValidator, options.dup
76
+ end
77
+ end
78
+ end
79
+ end
80
+
@@ -0,0 +1,29 @@
1
+ require 'active_model/validations/presence'
2
+
3
+ module Paperclip
4
+ module Validators
5
+ class AttachmentFileTypeIgnoranceValidator < ActiveModel::EachValidator
6
+ def validate_each(record, attribute, value)
7
+ # This doesn't do anything. It's just to mark that you don't care about
8
+ # the file_names or content_types of your incoming attachments.
9
+ end
10
+
11
+ def self.helper_method_name
12
+ :do_not_validate_attachment_file_type
13
+ end
14
+ end
15
+
16
+ module HelperMethods
17
+ # Places ActiveModel validations on the presence of a file.
18
+ # Options:
19
+ # * +if+: A lambda or name of an instance method. Validation will only
20
+ # be run if this lambda or method returns true.
21
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
22
+ def do_not_validate_attachment_file_type(*attr_names)
23
+ options = _merge_attributes(attr_names)
24
+ validates_with AttachmentFileTypeIgnoranceValidator, options.dup
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,30 @@
1
+ require 'active_model/validations/presence'
2
+
3
+ module Paperclip
4
+ module Validators
5
+ class AttachmentPresenceValidator < ActiveModel::EachValidator
6
+ def validate_each(record, attribute, value)
7
+ if record.send("#{attribute}_file_name").blank?
8
+ record.errors.add(attribute, :blank, options)
9
+ end
10
+ end
11
+
12
+ def self.helper_method_name
13
+ :validates_attachment_presence
14
+ end
15
+ end
16
+
17
+ module HelperMethods
18
+ # Places ActiveModel validations on the presence of a file.
19
+ # Options:
20
+ # * +if+: A lambda or name of an instance method. Validation will only
21
+ # be run if this lambda or method returns true.
22
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
23
+ def validates_attachment_presence(*attr_names)
24
+ options = _merge_attributes(attr_names)
25
+ validates_with AttachmentPresenceValidator, options.dup
26
+ validate_before_processing AttachmentPresenceValidator, options.dup
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,115 @@
1
+ require 'active_model/validations/numericality'
2
+
3
+ module Paperclip
4
+ module Validators
5
+ class AttachmentSizeValidator < ActiveModel::Validations::NumericalityValidator
6
+ AVAILABLE_CHECKS = [:less_than, :less_than_or_equal_to, :greater_than, :greater_than_or_equal_to]
7
+
8
+ def initialize(options)
9
+ extract_options(options)
10
+ super
11
+ end
12
+
13
+ def self.helper_method_name
14
+ :validates_attachment_size
15
+ end
16
+
17
+ def validate_each(record, attr_name, value)
18
+ base_attr_name = attr_name
19
+ attr_name = "#{attr_name}_file_size".to_sym
20
+ value = record.send(:read_attribute_for_validation, attr_name)
21
+
22
+ unless value.blank?
23
+ options.slice(*AVAILABLE_CHECKS).each do |option, option_value|
24
+ option_value = option_value.call(record) if option_value.is_a?(Proc)
25
+ option_value = extract_option_value(option, option_value)
26
+
27
+ unless value.send(CHECKS[option], option_value)
28
+ error_message_key = options[:in] ? :in_between : option
29
+ [ attr_name, base_attr_name ].each do |error_attr_name|
30
+ record.errors.add(error_attr_name, error_message_key, filtered_options(value).merge(
31
+ :min => min_value_in_human_size(record),
32
+ :max => max_value_in_human_size(record),
33
+ :count => human_size(option_value)
34
+ ))
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def check_validity!
42
+ unless (AVAILABLE_CHECKS + [:in]).any? { |argument| options.has_key?(argument) }
43
+ raise ArgumentError, "You must pass either :less_than, :greater_than, or :in to the validator"
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def extract_options(options)
50
+ if range = options[:in]
51
+ if !options[:in].respond_to?(:call)
52
+ options[:less_than_or_equal_to] = range.max
53
+ options[:greater_than_or_equal_to] = range.min
54
+ else
55
+ options[:less_than_or_equal_to] = range
56
+ options[:greater_than_or_equal_to] = range
57
+ end
58
+ end
59
+ end
60
+
61
+ def extract_option_value(option, option_value)
62
+ if option_value.is_a?(Range)
63
+ if [:less_than, :less_than_or_equal_to].include?(option)
64
+ option_value.max
65
+ else
66
+ option_value.min
67
+ end
68
+ else
69
+ option_value
70
+ end
71
+ end
72
+
73
+ def human_size(size)
74
+ if defined?(ActiveSupport::NumberHelper) # Rails 4.0+
75
+ ActiveSupport::NumberHelper.number_to_human_size(size)
76
+ else
77
+ storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
78
+ unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => size.to_i, :raise => true)
79
+ storage_units_format.gsub(/%n/, size.to_i.to_s).gsub(/%u/, unit).html_safe
80
+ end
81
+ end
82
+
83
+ def min_value_in_human_size(record)
84
+ value = options[:greater_than_or_equal_to] || options[:greater_than]
85
+ value = value.call(record) if value.respond_to?(:call)
86
+ value = value.min if value.respond_to?(:min)
87
+ human_size(value)
88
+ end
89
+
90
+ def max_value_in_human_size(record)
91
+ value = options[:less_than_or_equal_to] || options[:less_than]
92
+ value = value.call(record) if value.respond_to?(:call)
93
+ value = value.max if value.respond_to?(:max)
94
+ human_size(value)
95
+ end
96
+ end
97
+
98
+ module HelperMethods
99
+ # Places ActiveModel validations on the size of the file assigned. The
100
+ # possible options are:
101
+ # * +in+: a Range of bytes (i.e. +1..1.megabyte+),
102
+ # * +less_than+: equivalent to :in => 0..options[:less_than]
103
+ # * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
104
+ # * +message+: error message to display, use :min and :max as replacements
105
+ # * +if+: A lambda or name of an instance method. Validation will only
106
+ # be run if this lambda or method returns true.
107
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
108
+ def validates_attachment_size(*attr_names)
109
+ options = _merge_attributes(attr_names)
110
+ validates_with AttachmentSizeValidator, options.dup
111
+ validate_before_processing AttachmentSizeValidator, options.dup
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_model/validations/presence'
2
+
3
+ module Paperclip
4
+ module Validators
5
+ class MediaTypeSpoofDetectionValidator < ActiveModel::EachValidator
6
+ def validate_each(record, attribute, value)
7
+ adapter = Paperclip.io_adapters.for(value)
8
+ if Paperclip::MediaTypeSpoofDetector.using(adapter, value.original_filename, value.content_type).spoofed?
9
+ record.errors.add(attribute, :spoofed_media_type)
10
+ end
11
+ end
12
+ end
13
+
14
+ module HelperMethods
15
+ # Places ActiveModel validations on the presence of a file.
16
+ # Options:
17
+ # * +if+: A lambda or name of an instance method. Validation will only
18
+ # be run if this lambda or method returns true.
19
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
20
+ def validates_media_type_spoof_detection(*attr_names)
21
+ options = _merge_attributes(attr_names)
22
+ validates_with MediaTypeSpoofDetectionValidator, options.dup
23
+ validate_before_processing MediaTypeSpoofDetectionValidator, options.dup
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,74 @@
1
+ require 'active_model'
2
+ require 'active_support/concern'
3
+ require 'active_support/core_ext/array/wrap'
4
+ require 'paperclip/validators/attachment_content_type_validator'
5
+ require 'paperclip/validators/attachment_file_name_validator'
6
+ require 'paperclip/validators/attachment_presence_validator'
7
+ require 'paperclip/validators/attachment_size_validator'
8
+ require 'paperclip/validators/media_type_spoof_detection_validator'
9
+ require 'paperclip/validators/attachment_file_type_ignorance_validator'
10
+
11
+ module Paperclip
12
+ module Validators
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ extend HelperMethods
17
+ include HelperMethods
18
+ end
19
+
20
+ ::Paperclip::REQUIRED_VALIDATORS = [AttachmentFileNameValidator, AttachmentContentTypeValidator, AttachmentFileTypeIgnoranceValidator]
21
+
22
+ module ClassMethods
23
+ # This method is a shortcut to validator classes that is in
24
+ # "Attachment...Validator" format. It is almost the same thing as the
25
+ # +validates+ method that shipped with Rails, but this is customized to
26
+ # be using with attachment validators. This is helpful when you're using
27
+ # multiple attachment validators on a single attachment.
28
+ #
29
+ # Example of using the validator:
30
+ #
31
+ # validates_attachment :avatar, :presence => true,
32
+ # :content_type => { :content_type => "image/jpg" },
33
+ # :size => { :in => 0..10.kilobytes }
34
+ #
35
+ def validates_attachment(*attributes)
36
+ options = attributes.extract_options!.dup
37
+
38
+ Paperclip::Validators.constants.each do |constant|
39
+ if constant.to_s =~ /\AAttachment(.+)Validator\Z/
40
+ validator_kind = $1.underscore.to_sym
41
+
42
+ if options.has_key?(validator_kind)
43
+ validator_options = options.delete(validator_kind)
44
+ validator_options = {} if validator_options == true
45
+ conditional_options = options.slice(:if, :unless)
46
+ Array.wrap(validator_options).each do |local_options|
47
+ method_name = Paperclip::Validators.const_get(constant.to_s).helper_method_name
48
+ send(method_name, attributes, local_options.merge(conditional_options))
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def validate_before_processing(validator_class, options)
56
+ options = options.dup
57
+ attributes = options.delete(:attributes)
58
+ attributes.each do |attribute|
59
+ options[:attributes] = [attribute]
60
+ create_validating_before_filter(attribute, validator_class, options)
61
+ end
62
+ end
63
+
64
+ def create_validating_before_filter(attribute, validator_class, options)
65
+ if_clause = options.delete(:if)
66
+ unless_clause = options.delete(:unless)
67
+ send(:"before_#{attribute}_post_process", :if => if_clause, :unless => unless_clause) do |*args|
68
+ validator_class.new(options.dup).validate(self)
69
+ end
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ module Paperclip
2
+ VERSION = "4.3.7".freeze unless defined? Paperclip::VERSION
3
+ end