jr-paperclip 7.3.0

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 (200) hide show
  1. checksums.yaml +7 -0
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  4. data/.github/ISSUE_TEMPLATE/custom.md +10 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. data/.github/workflows/reviewdog.yml +23 -0
  7. data/.github/workflows/test.yml +46 -0
  8. data/.gitignore +19 -0
  9. data/.qlty/.gitignore +7 -0
  10. data/.qlty/qlty.toml +89 -0
  11. data/.rubocop.yml +1060 -0
  12. data/Appraisals +29 -0
  13. data/CONTRIBUTING.md +85 -0
  14. data/Gemfile +17 -0
  15. data/LICENSE +25 -0
  16. data/NEWS +567 -0
  17. data/README.md +1083 -0
  18. data/RELEASING.md +17 -0
  19. data/Rakefile +52 -0
  20. data/bin/console +11 -0
  21. data/features/basic_integration.feature +85 -0
  22. data/features/migration.feature +29 -0
  23. data/features/rake_tasks.feature +62 -0
  24. data/features/step_definitions/attachment_steps.rb +121 -0
  25. data/features/step_definitions/html_steps.rb +15 -0
  26. data/features/step_definitions/rails_steps.rb +271 -0
  27. data/features/step_definitions/s3_steps.rb +16 -0
  28. data/features/step_definitions/web_steps.rb +106 -0
  29. data/features/support/env.rb +12 -0
  30. data/features/support/file_helpers.rb +34 -0
  31. data/features/support/fixtures/boot_config.txt +15 -0
  32. data/features/support/fixtures/gemfile.txt +5 -0
  33. data/features/support/fixtures/preinitializer.txt +20 -0
  34. data/features/support/paths.rb +28 -0
  35. data/features/support/rails.rb +39 -0
  36. data/features/support/selectors.rb +19 -0
  37. data/features/support/webmock_setup.rb +8 -0
  38. data/gemfiles/7.0.gemfile +20 -0
  39. data/gemfiles/7.1.gemfile +20 -0
  40. data/gemfiles/7.2.gemfile +20 -0
  41. data/gemfiles/8.0.gemfile +20 -0
  42. data/gemfiles/8.1.gemfile +20 -0
  43. data/lib/generators/paperclip/USAGE +8 -0
  44. data/lib/generators/paperclip/paperclip_generator.rb +36 -0
  45. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  46. data/lib/jr-paperclip.rb +1 -0
  47. data/lib/paperclip/attachment.rb +634 -0
  48. data/lib/paperclip/attachment_registry.rb +60 -0
  49. data/lib/paperclip/callbacks.rb +42 -0
  50. data/lib/paperclip/content_type_detector.rb +85 -0
  51. data/lib/paperclip/errors.rb +34 -0
  52. data/lib/paperclip/file_command_content_type_detector.rb +28 -0
  53. data/lib/paperclip/filename_cleaner.rb +15 -0
  54. data/lib/paperclip/geometry.rb +157 -0
  55. data/lib/paperclip/geometry_detector_factory.rb +45 -0
  56. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  57. data/lib/paperclip/glue.rb +18 -0
  58. data/lib/paperclip/has_attached_file.rb +116 -0
  59. data/lib/paperclip/helpers.rb +60 -0
  60. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  61. data/lib/paperclip/interpolations.rb +205 -0
  62. data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
  63. data/lib/paperclip/io_adapters/attachment_adapter.rb +56 -0
  64. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  65. data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
  66. data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
  67. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
  68. data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
  69. data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
  70. data/lib/paperclip/io_adapters/registry.rb +36 -0
  71. data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
  72. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
  73. data/lib/paperclip/io_adapters/uri_adapter.rb +78 -0
  74. data/lib/paperclip/locales/en.yml +18 -0
  75. data/lib/paperclip/locales/fr.yml +18 -0
  76. data/lib/paperclip/locales/gd.yml +20 -0
  77. data/lib/paperclip/logger.rb +21 -0
  78. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  79. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
  80. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  81. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
  82. data/lib/paperclip/matchers.rb +64 -0
  83. data/lib/paperclip/media_type_spoof_detector.rb +93 -0
  84. data/lib/paperclip/missing_attachment_styles.rb +84 -0
  85. data/lib/paperclip/processor.rb +56 -0
  86. data/lib/paperclip/processor_helpers.rb +52 -0
  87. data/lib/paperclip/rails_environment.rb +21 -0
  88. data/lib/paperclip/railtie.rb +31 -0
  89. data/lib/paperclip/schema.rb +104 -0
  90. data/lib/paperclip/storage/filesystem.rb +99 -0
  91. data/lib/paperclip/storage/fog.rb +262 -0
  92. data/lib/paperclip/storage/s3.rb +497 -0
  93. data/lib/paperclip/storage.rb +3 -0
  94. data/lib/paperclip/style.rb +106 -0
  95. data/lib/paperclip/tempfile.rb +42 -0
  96. data/lib/paperclip/tempfile_factory.rb +22 -0
  97. data/lib/paperclip/thumbnail.rb +131 -0
  98. data/lib/paperclip/url_generator.rb +83 -0
  99. data/lib/paperclip/validators/attachment_content_type_validator.rb +95 -0
  100. data/lib/paperclip/validators/attachment_file_name_validator.rb +82 -0
  101. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
  102. data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
  103. data/lib/paperclip/validators/attachment_size_validator.rb +126 -0
  104. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
  105. data/lib/paperclip/validators.rb +73 -0
  106. data/lib/paperclip/version.rb +3 -0
  107. data/lib/paperclip.rb +215 -0
  108. data/lib/tasks/paperclip.rake +140 -0
  109. data/paperclip.gemspec +51 -0
  110. data/shoulda_macros/paperclip.rb +134 -0
  111. data/spec/database.yml +4 -0
  112. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  113. data/spec/paperclip/attachment_processing_spec.rb +79 -0
  114. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  115. data/spec/paperclip/attachment_spec.rb +1617 -0
  116. data/spec/paperclip/content_type_detector_spec.rb +58 -0
  117. data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
  118. data/spec/paperclip/filename_cleaner_spec.rb +13 -0
  119. data/spec/paperclip/geometry_detector_spec.rb +47 -0
  120. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  121. data/spec/paperclip/geometry_spec.rb +267 -0
  122. data/spec/paperclip/glue_spec.rb +63 -0
  123. data/spec/paperclip/has_attached_file_spec.rb +78 -0
  124. data/spec/paperclip/integration_spec.rb +702 -0
  125. data/spec/paperclip/interpolations_spec.rb +270 -0
  126. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
  127. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +167 -0
  128. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -0
  129. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  130. data/spec/paperclip/io_adapters/file_adapter_spec.rb +134 -0
  131. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +142 -0
  132. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  133. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  134. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  135. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  136. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  137. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +231 -0
  138. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  139. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +108 -0
  140. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  141. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  142. data/spec/paperclip/media_type_spoof_detector_spec.rb +126 -0
  143. data/spec/paperclip/meta_class_spec.rb +30 -0
  144. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
  145. data/spec/paperclip/paperclip_spec.rb +196 -0
  146. data/spec/paperclip/plural_cache_spec.rb +37 -0
  147. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  148. data/spec/paperclip/processor_spec.rb +26 -0
  149. data/spec/paperclip/rails_environment_spec.rb +30 -0
  150. data/spec/paperclip/rake_spec.rb +103 -0
  151. data/spec/paperclip/schema_spec.rb +298 -0
  152. data/spec/paperclip/storage/filesystem_spec.rb +102 -0
  153. data/spec/paperclip/storage/fog_spec.rb +606 -0
  154. data/spec/paperclip/storage/s3_live_spec.rb +188 -0
  155. data/spec/paperclip/storage/s3_spec.rb +1974 -0
  156. data/spec/paperclip/style_spec.rb +251 -0
  157. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  158. data/spec/paperclip/tempfile_spec.rb +35 -0
  159. data/spec/paperclip/thumbnail_spec.rb +504 -0
  160. data/spec/paperclip/url_generator_spec.rb +231 -0
  161. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +410 -0
  162. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +249 -0
  163. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  164. data/spec/paperclip/validators/attachment_size_validator_spec.rb +325 -0
  165. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
  166. data/spec/paperclip/validators_spec.rb +179 -0
  167. data/spec/spec_helper.rb +52 -0
  168. data/spec/support/assertions.rb +84 -0
  169. data/spec/support/fake_model.rb +24 -0
  170. data/spec/support/fake_rails.rb +12 -0
  171. data/spec/support/fixtures/12k.png +0 -0
  172. data/spec/support/fixtures/50x50.png +0 -0
  173. data/spec/support/fixtures/5k.png +0 -0
  174. data/spec/support/fixtures/animated +0 -0
  175. data/spec/support/fixtures/animated.gif +0 -0
  176. data/spec/support/fixtures/animated.unknown +0 -0
  177. data/spec/support/fixtures/aws_s3.yml +13 -0
  178. data/spec/support/fixtures/bad.png +1 -0
  179. data/spec/support/fixtures/empty.html +1 -0
  180. data/spec/support/fixtures/empty.xlsx +0 -0
  181. data/spec/support/fixtures/fog.yml +8 -0
  182. data/spec/support/fixtures/rotated.jpg +0 -0
  183. data/spec/support/fixtures/s3.yml +8 -0
  184. data/spec/support/fixtures/sample.xlsm +0 -0
  185. data/spec/support/fixtures/spaced file.jpg +0 -0
  186. data/spec/support/fixtures/spaced file.png +0 -0
  187. data/spec/support/fixtures/text.txt +1 -0
  188. data/spec/support/fixtures/twopage.pdf +0 -0
  189. data/spec/support/fixtures/uppercase.PNG +0 -0
  190. data/spec/support/matchers/accept.rb +5 -0
  191. data/spec/support/matchers/exist.rb +5 -0
  192. data/spec/support/matchers/have_column.rb +23 -0
  193. data/spec/support/mock_attachment.rb +24 -0
  194. data/spec/support/mock_interpolator.rb +24 -0
  195. data/spec/support/mock_url_generator_builder.rb +26 -0
  196. data/spec/support/model_reconstruction.rb +72 -0
  197. data/spec/support/reporting.rb +11 -0
  198. data/spec/support/test_data.rb +13 -0
  199. data/spec/support/version_helper.rb +9 -0
  200. metadata +702 -0
@@ -0,0 +1,205 @@
1
+ module Paperclip
2
+ # This module contains all the methods that are available for interpolation
3
+ # in paths and urls. To add your own (or override an existing one), you
4
+ # can either open this module and define it, or call the
5
+ # Paperclip.interpolates method.
6
+ module Interpolations
7
+ extend self
8
+ ID_PARTITION_LIMIT = 1_000_000_000
9
+
10
+ # Hash assignment of interpolations. Included only for compatibility,
11
+ # and is not intended for normal use.
12
+ def self.[]=(name, block)
13
+ define_method(name, &block)
14
+ @interpolators_cache = nil
15
+ end
16
+
17
+ # Hash access of interpolations. Included only for compatibility,
18
+ # and is not intended for normal use.
19
+ def self.[](name)
20
+ method(name)
21
+ end
22
+
23
+ # Returns a sorted list of all interpolations.
24
+ def self.all
25
+ instance_methods(false).sort!
26
+ end
27
+
28
+ # Perform the actual interpolation. Takes the pattern to interpolate
29
+ # and the arguments to pass, which are the attachment and style name.
30
+ # You can pass a method name on your record as a symbol, which should turn
31
+ # an interpolation pattern for Paperclip to use.
32
+ def self.interpolate(pattern, *args)
33
+ pattern = args.first.instance.send(pattern) if pattern.is_a? Symbol
34
+ result = pattern.dup
35
+ interpolators_cache.each do |method, token|
36
+ result.gsub!(token) { send(method, *args) } if result.include?(token)
37
+ end
38
+ result
39
+ end
40
+
41
+ def self.interpolators_cache
42
+ @interpolators_cache ||= all.reverse!.map! { |method| [method, ":#{method}"] }
43
+ end
44
+
45
+ def self.plural_cache
46
+ @plural_cache ||= PluralCache.new
47
+ end
48
+
49
+ # Returns the filename, the same way as ":basename.:extension" would.
50
+ def filename(attachment, style_name)
51
+ [basename(attachment, style_name), extension(attachment, style_name)].delete_if(&:empty?).join(".")
52
+ end
53
+
54
+ # Returns the interpolated URL. Will raise an error if the url itself
55
+ # contains ":url" to prevent infinite recursion. This interpolation
56
+ # is used in the default :path to ease default specifications.
57
+ def url(attachment, style_name)
58
+ if Thread.current.thread_variable_get(:kt_paperclip_no_recursion)
59
+ raise Errors::InfiniteInterpolationError
60
+ end
61
+ Thread.current.thread_variable_set(:kt_paperclip_no_recursion, true)
62
+ attachment.url(style_name, timestamp: false, escape: false)
63
+ ensure
64
+ Thread.current.thread_variable_set(:kt_paperclip_no_recursion, false)
65
+ end
66
+
67
+ # Returns the timestamp as defined by the <attachment>_updated_at field
68
+ # in the server default time zone unless :use_global_time_zone is set
69
+ # to false. Note that a Rails.config.time_zone change will still
70
+ # invalidate any path or URL that uses :timestamp. For a
71
+ # time_zone-agnostic timestamp, use #updated_at.
72
+ def timestamp(attachment, _style_name)
73
+ attachment.instance_read(:updated_at).in_time_zone(attachment.time_zone).to_s
74
+ end
75
+
76
+ # Returns an integer timestamp that is time zone-neutral, so that paths
77
+ # remain valid even if a server's time zone changes.
78
+ def updated_at(attachment, _style_name)
79
+ attachment.updated_at
80
+ end
81
+
82
+ # Returns the Rails.root constant.
83
+ def rails_root(_attachment, _style_name)
84
+ Rails.root
85
+ end
86
+
87
+ # Returns the Rails.env constant.
88
+ def rails_env(_attachment, _style_name)
89
+ Rails.env
90
+ end
91
+
92
+ # Returns the underscored, pluralized version of the class name.
93
+ # e.g. "users" for the User class.
94
+ # NOTE: The arguments need to be optional, because some tools fetch
95
+ # all class names. Calling #class will return the expected class.
96
+ def class(attachment = nil, style_name = nil)
97
+ return super() if attachment.nil? && style_name.nil?
98
+
99
+ plural_cache.underscore_and_pluralize_class(attachment.instance.class)
100
+ end
101
+
102
+ # Returns the basename of the file. e.g. "file" for "file.jpg"
103
+ def basename(attachment, _style_name)
104
+ File.basename(attachment.original_filename, ".*")
105
+ end
106
+
107
+ # Returns the extension of the file. e.g. "jpg" for "file.jpg"
108
+ # If the style has a format defined, it will return the format instead
109
+ # of the actual extension.
110
+ def extension(attachment, style_name)
111
+ ((style = attachment.styles[style_name.to_s.to_sym]) && style[:format]) ||
112
+ File.extname(attachment.original_filename).sub(/\A\.+/, "")
113
+ end
114
+
115
+ # Returns the dot+extension of the file. e.g. ".jpg" for "file.jpg"
116
+ # If the style has a format defined, it will return the format instead
117
+ # of the actual extension. If the extension is empty, no dot is added.
118
+ def dotextension(attachment, style_name)
119
+ ext = extension(attachment, style_name)
120
+ ext.empty? ? ext : ".#{ext}"
121
+ end
122
+
123
+ # Returns an extension based on the content type. e.g. "jpeg" for
124
+ # "image/jpeg". If the style has a specified format, it will override the
125
+ # content-type detection.
126
+ #
127
+ # Each mime type generally has multiple extensions associated with it, so
128
+ # if the extension from the original filename is one of these extensions,
129
+ # that extension is used, otherwise, the first in the list is used.
130
+ def content_type_extension(attachment, style_name)
131
+ mime_type = MIME::Types[attachment.content_type]
132
+ extensions_for_mime_type = if mime_type.empty?
133
+ []
134
+ else
135
+ mime_type.first.extensions
136
+ end
137
+
138
+ original_extension = extension(attachment, style_name)
139
+ style = attachment.styles[style_name.to_s.to_sym]
140
+ if style && style[:format]
141
+ style[:format].to_s
142
+ elsif extensions_for_mime_type.include? original_extension
143
+ original_extension
144
+ elsif !extensions_for_mime_type.empty?
145
+ extensions_for_mime_type.first
146
+ else
147
+ # It's possible, though unlikely, that the mime type is not in the
148
+ # database, so just use the part after the '/' in the mime type as the
149
+ # extension.
150
+ %r{/([^/]*)\z}.match(attachment.content_type)[1]
151
+ end
152
+ end
153
+
154
+ # Returns the id of the instance.
155
+ def id(attachment, _style_name)
156
+ attachment.instance.id
157
+ end
158
+
159
+ # Returns the #to_param of the instance.
160
+ def param(attachment, _style_name)
161
+ attachment.instance.to_param
162
+ end
163
+
164
+ # Returns the fingerprint of the instance.
165
+ def fingerprint(attachment, _style_name)
166
+ attachment.fingerprint
167
+ end
168
+
169
+ # Returns a the attachment hash. See Paperclip::Attachment#hash_key for
170
+ # more details.
171
+ def hash(attachment = nil, style_name = nil)
172
+ if attachment && style_name
173
+ attachment.hash_key(style_name)
174
+ else
175
+ super()
176
+ end
177
+ end
178
+
179
+ # Returns the id of the instance in a split path form. e.g. returns
180
+ # 000/001/234 for an id of 1234.
181
+ def id_partition(attachment, _style_name)
182
+ case id = attachment.instance.id
183
+ when Integer
184
+ if id < ID_PARTITION_LIMIT
185
+ ("%09d" % id).scan(/\d{3}/).join("/")
186
+ else
187
+ ("%012d" % id).scan(/\d{3}/).join("/")
188
+ end
189
+ when String
190
+ id.scan(/.{3}/).first(3).join("/")
191
+ end
192
+ end
193
+
194
+ # Returns the pluralized form of the attachment name. e.g.
195
+ # "avatars" for an attachment of :avatar
196
+ def attachment(attachment, _style_name)
197
+ plural_cache.pluralize_symbol(attachment.name)
198
+ end
199
+
200
+ # Returns the style, or the default style if nil is supplied.
201
+ def style(attachment, style_name)
202
+ style_name || attachment.default_style
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,75 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
3
+ module Paperclip
4
+ class AbstractAdapter
5
+ OS_RESTRICTED_CHARACTERS = %r{[/:]}.freeze
6
+
7
+ attr_reader :content_type, :original_filename, :size, :tempfile
8
+ delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :readbyte, :rewind, :unlink, to: :@tempfile
9
+ alias :length :size
10
+
11
+ def initialize(target, options = {})
12
+ @target = target
13
+ @options = options
14
+ end
15
+
16
+ def fingerprint
17
+ @fingerprint ||= begin
18
+ digest = @options.fetch(:hash_digest).new
19
+ File.open(path, "rb") do |f|
20
+ buf = String.new
21
+ digest.update(buf) while f.read(16384, buf)
22
+ end
23
+ digest.hexdigest
24
+ end
25
+ end
26
+
27
+ def read(length = nil, buffer = nil)
28
+ @tempfile.read(length, buffer)
29
+ end
30
+
31
+ def inspect
32
+ "#{self.class}: #{original_filename}"
33
+ end
34
+
35
+ def original_filename=(new_filename)
36
+ return unless new_filename
37
+
38
+ @original_filename = new_filename.gsub(OS_RESTRICTED_CHARACTERS, "_")
39
+ end
40
+
41
+ def nil?
42
+ false
43
+ end
44
+
45
+ def assignment?
46
+ true
47
+ end
48
+
49
+ private
50
+
51
+ def destination
52
+ @destination ||= TempfileFactory.new.generate(@original_filename.to_s)
53
+ end
54
+
55
+ def copy_to_tempfile(src)
56
+ link_or_copy_file(src.path, destination.path)
57
+ destination
58
+ end
59
+
60
+ def link_or_copy_file(src, dest)
61
+ begin
62
+ Paperclip.log("Trying to link #{src} to #{dest}")
63
+ FileUtils.ln(src, dest, force: true) # overwrite existing
64
+ rescue Errno::EXDEV, Errno::EPERM, Errno::ENOENT, Errno::EEXIST => e
65
+ Paperclip.log(
66
+ "Link failed with #{e.message}; copying link #{src} to #{dest}"
67
+ )
68
+ FileUtils.cp(src, dest)
69
+ end
70
+
71
+ @destination.close
72
+ @destination.open.binmode
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,56 @@
1
+ module Paperclip
2
+ class AttachmentAdapter < AbstractAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ Paperclip::Attachment === target || Paperclip::Style === target
6
+ end
7
+ end
8
+
9
+ def initialize(target, options = {})
10
+ super
11
+ @target, @style = case target
12
+ when Paperclip::Attachment
13
+ [target, :original]
14
+ when Paperclip::Style
15
+ [target.attachment, target.name]
16
+ end
17
+
18
+ cache_current_values
19
+ end
20
+
21
+ private
22
+
23
+ def cache_current_values
24
+ self.original_filename = @target.original_filename
25
+ @content_type = @target.content_type
26
+ @tempfile = copy_to_tempfile(@target)
27
+ @size = @tempfile.size || @target.size
28
+ end
29
+
30
+ def copy_to_tempfile(source)
31
+ if source.staged?
32
+ link_or_copy_file(source.staged_path(@style), destination.path)
33
+ else
34
+ begin
35
+ source.copy_to_local_file(@style, destination.path)
36
+
37
+ # Some sources (like S3) writes to a temporary file first for multi-part downloads then moves
38
+ # it to the final destination path. This means our destination Tempfile reference is stale
39
+ # and reads will return an empty string. For compatibility, we need to copy the contents
40
+ # of the destination file to our current Tempfile instance.
41
+ if destination.size.zero?
42
+ IO.copy_stream(destination.path, destination)
43
+ destination.rewind
44
+ end
45
+ rescue Errno::EACCES
46
+ # clean up lingering tempfile if we cannot access source file
47
+ destination.close(true)
48
+ raise
49
+ end
50
+ end
51
+ destination
52
+ end
53
+ end
54
+ end
55
+
56
+ Paperclip::AttachmentAdapter.register
@@ -0,0 +1,22 @@
1
+ module Paperclip
2
+ class DataUriAdapter < StringioAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ String === target && target =~ REGEXP
6
+ end
7
+ end
8
+
9
+ REGEXP = /\Adata:([-\w]+\/[-\w\+\.]+)?;base64,(.*)/m.freeze
10
+
11
+ def initialize(target_uri, options = {})
12
+ super(extract_target(target_uri), options)
13
+ end
14
+
15
+ private
16
+
17
+ def extract_target(uri)
18
+ data_uri_parts = uri.match(REGEXP) || []
19
+ StringIO.new(Base64.decode64(data_uri_parts[2] || ""))
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Paperclip
2
+ class EmptyStringAdapter < AbstractAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ target.is_a?(String) && target.empty?
6
+ end
7
+ end
8
+
9
+ def nil?
10
+ false
11
+ end
12
+
13
+ def assignment?
14
+ false
15
+ end
16
+ end
17
+ end
18
+
19
+ Paperclip::EmptyStringAdapter.register
@@ -0,0 +1,26 @@
1
+ module Paperclip
2
+ class FileAdapter < AbstractAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ File === target || ::Tempfile === target
6
+ end
7
+ end
8
+
9
+ def initialize(target, options = {})
10
+ super
11
+ cache_current_values
12
+ end
13
+
14
+ private
15
+
16
+ def cache_current_values
17
+ self.original_filename = @target.original_filename if @target.respond_to?(:original_filename)
18
+ self.original_filename ||= File.basename(@target.path)
19
+ @tempfile = copy_to_tempfile(@target)
20
+ @content_type = ContentTypeDetector.new(@target.path).detect
21
+ @size = File.size(@target)
22
+ end
23
+ end
24
+ end
25
+
26
+ Paperclip::FileAdapter.register
@@ -0,0 +1,16 @@
1
+ module Paperclip
2
+ class HttpUrlProxyAdapter < UriAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ String === target && target =~ REGEXP
6
+ end
7
+ end
8
+
9
+ REGEXP = /\Ahttps?:\/\//.freeze
10
+
11
+ def initialize(target, options = {})
12
+ escaped = Paperclip::UrlGenerator.escape(target)
13
+ super(URI(target == Paperclip::UrlGenerator.unescape(target) ? escaped : target), options)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module Paperclip
2
+ class IdentityAdapter < AbstractAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register Paperclip::IdentityAdapter.new do |target|
5
+ Paperclip.io_adapters.registered?(target)
6
+ end
7
+ end
8
+
9
+ def initialize; end
10
+
11
+ def new(target, _)
12
+ target
13
+ end
14
+ end
15
+ end
16
+
17
+ Paperclip::IdentityAdapter.register
@@ -0,0 +1,37 @@
1
+ module Paperclip
2
+ class NilAdapter < AbstractAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ target.nil? || ((Paperclip::Attachment === target) && !target.present?)
6
+ end
7
+ end
8
+
9
+ def initialize(_target, _options = {}); end
10
+
11
+ def original_filename
12
+ ""
13
+ end
14
+
15
+ def content_type
16
+ ""
17
+ end
18
+
19
+ def size
20
+ 0
21
+ end
22
+
23
+ def nil?
24
+ true
25
+ end
26
+
27
+ def read(*_args)
28
+ nil
29
+ end
30
+
31
+ def eof?
32
+ true
33
+ end
34
+ end
35
+ end
36
+
37
+ Paperclip::NilAdapter.register
@@ -0,0 +1,36 @@
1
+ module Paperclip
2
+ class AdapterRegistry
3
+ class NoHandlerError < Paperclip::Error; end
4
+
5
+ attr_reader :registered_handlers
6
+
7
+ def initialize
8
+ @registered_handlers = []
9
+ end
10
+
11
+ def register(handler_class, &block)
12
+ @registered_handlers << [block, handler_class]
13
+ end
14
+
15
+ def unregister(handler_class)
16
+ @registered_handlers.reject! { |_, klass| klass == handler_class }
17
+ end
18
+
19
+ def handler_for(target)
20
+ @registered_handlers.each do |tester, handler|
21
+ return handler if tester.call(target)
22
+ end
23
+ raise NoHandlerError.new("No handler found for #{target.inspect}")
24
+ end
25
+
26
+ def registered?(target)
27
+ @registered_handlers.any? do |_tester, handler|
28
+ handler === target
29
+ end
30
+ end
31
+
32
+ def for(target, options = {})
33
+ handler_for(target).new(target, options)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module Paperclip
2
+ class StringioAdapter < AbstractAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ StringIO === target
6
+ end
7
+ end
8
+
9
+ def initialize(target, options = {})
10
+ super
11
+ cache_current_values
12
+ end
13
+
14
+ attr_writer :content_type
15
+
16
+ private
17
+
18
+ def cache_current_values
19
+ self.original_filename = @target.original_filename if @target.respond_to?(:original_filename)
20
+ self.original_filename ||= "data"
21
+ @tempfile = copy_to_tempfile(@target)
22
+ @content_type = ContentTypeDetector.new(@tempfile.path).detect
23
+ @size = @target.size
24
+ end
25
+
26
+ def copy_to_tempfile(source)
27
+ while data = source.read(16 * 1024)
28
+ destination.write(data)
29
+ end
30
+ destination.rewind
31
+ destination
32
+ end
33
+ end
34
+ end
35
+
36
+ Paperclip::StringioAdapter.register
@@ -0,0 +1,44 @@
1
+ module Paperclip
2
+ class UploadedFileAdapter < AbstractAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ target.class.name.include?("UploadedFile")
6
+ end
7
+ end
8
+
9
+ def initialize(target, options = {})
10
+ super
11
+ cache_current_values
12
+
13
+ @tempfile = if @target.respond_to?(:tempfile)
14
+ copy_to_tempfile(@target.tempfile)
15
+ else
16
+ copy_to_tempfile(@target)
17
+ end
18
+ end
19
+
20
+ class << self
21
+ attr_accessor :content_type_detector
22
+ end
23
+
24
+ private
25
+
26
+ def cache_current_values
27
+ self.original_filename = @target.original_filename
28
+ @content_type = determine_content_type
29
+ @size = File.size(@target.path)
30
+ end
31
+
32
+ def content_type_detector
33
+ self.class.content_type_detector || Paperclip::ContentTypeDetector
34
+ end
35
+
36
+ def determine_content_type
37
+ content_type = @target.content_type.to_s.strip
38
+ content_type = content_type_detector.new(@target.path).detect if content_type_detector
39
+ content_type
40
+ end
41
+ end
42
+ end
43
+
44
+ Paperclip::UploadedFileAdapter.register
@@ -0,0 +1,78 @@
1
+ require "open-uri"
2
+
3
+ module Paperclip
4
+ class UriAdapter < AbstractAdapter
5
+ attr_writer :content_type
6
+
7
+ def self.register
8
+ Paperclip.io_adapters.register self do |target|
9
+ target.is_a?(URI)
10
+ end
11
+ end
12
+
13
+ def initialize(target, options = {})
14
+ super
15
+ @content = download_content
16
+ cache_current_values
17
+ @tempfile = copy_to_tempfile(@content)
18
+ end
19
+
20
+ private
21
+
22
+ def cache_current_values
23
+ self.content_type = content_type_from_content || "text/html"
24
+
25
+ self.original_filename = filename_from_content_disposition ||
26
+ filename_from_path || default_filename
27
+ @size = @content.size
28
+ end
29
+
30
+ def content_type_from_content
31
+ @content.meta["content-type"].presence
32
+ end
33
+
34
+ def filename_from_content_disposition
35
+ if @content.meta.key?("content-disposition") && @content.meta["content-disposition"].match(/filename/i)
36
+ # can include both filename and filename* values according to RCF6266. filename should come first
37
+ _, filename = @content.meta["content-disposition"].split(/filename\*?\s*=\s*/i)
38
+
39
+ # filename can be enclosed in quotes or not
40
+ matches = filename.match(/"(.*)"/)
41
+ matches ? matches[1] : filename.split(";")[0]
42
+ end
43
+ end
44
+
45
+ def filename_from_path
46
+ @target.path.split("/").last
47
+ end
48
+
49
+ def default_filename
50
+ "index.html"
51
+ end
52
+
53
+ if RUBY_VERSION < '2.5'
54
+ def download_content
55
+ options = { read_timeout: Paperclip.options[:read_timeout] }.compact
56
+
57
+ # rubocop:disable Security/Open
58
+ open(@target, options)
59
+ # rubocop:enable Security/Open
60
+ end
61
+ else
62
+ def download_content
63
+ options = { read_timeout: Paperclip.options[:read_timeout] }.compact
64
+
65
+ URI.open(@target, options)
66
+ end
67
+ end
68
+
69
+ def copy_to_tempfile(src)
70
+ while data = src.read(16 * 1024)
71
+ destination.write(data)
72
+ end
73
+ src.close
74
+ destination.rewind
75
+ destination
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,18 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ in_between: "must be in between %{min} and %{max}"
5
+ spoofed_media_type: "has contents that are not what they are reported to be"
6
+
7
+ number:
8
+ human:
9
+ storage_units:
10
+ format: "%n %u"
11
+ units:
12
+ byte:
13
+ one: "Byte"
14
+ other: "Bytes"
15
+ kb: "KB"
16
+ mb: "MB"
17
+ gb: "GB"
18
+ tb: "TB"