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,497 @@
1
+ # coding: utf-8
2
+ module Paperclip
3
+ module Storage
4
+ # Amazon's S3 file hosting service is a scalable, easy place to store files for
5
+ # distribution. You can find out more about it at http://aws.amazon.com/s3
6
+ #
7
+ # To use Paperclip with S3, include the +aws-sdk-s3+ gem in your Gemfile:
8
+ # gem 'aws-sdk-s3'
9
+ # There are a few S3-specific options for has_attached_file:
10
+ # * +s3_credentials+: Takes a path, a File, a Hash or a Proc. The path (or File) must point
11
+ # to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon
12
+ # gives you. You can 'environment-space' this just like you do to your
13
+ # database.yml file, so different environments can use different accounts:
14
+ # development:
15
+ # access_key_id: 123...
16
+ # secret_access_key: 123...
17
+ # test:
18
+ # access_key_id: abc...
19
+ # secret_access_key: abc...
20
+ # production:
21
+ # access_key_id: 456...
22
+ # secret_access_key: 456...
23
+ # This is not required, however, and the file may simply look like this:
24
+ # access_key_id: 456...
25
+ # secret_access_key: 456...
26
+ # In which case, those access keys will be used in all environments. You can also
27
+ # put your bucket name in this file, instead of adding it to the code directly.
28
+ # This is useful when you want the same account but a different bucket for
29
+ # development versus production.
30
+ # When using a Proc it provides a single parameter which is the attachment itself. A
31
+ # method #instance is available on the attachment which will take you back to your
32
+ # code. eg.
33
+ # class User
34
+ # has_attached_file :download,
35
+ # :storage => :s3,
36
+ # :s3_credentials => Proc.new{|a| a.instance.s3_credentials }
37
+ #
38
+ # def s3_credentials
39
+ # {:bucket => "xxx", :access_key_id => "xxx", :secret_access_key => "xxx"}
40
+ # end
41
+ # end
42
+ # * +s3_permissions+: This is a String that should be one of the "canned" access
43
+ # policies that S3 provides (more information can be found here:
44
+ # http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html)
45
+ # The default for Paperclip is public-read.
46
+ #
47
+ # You can set permission on a per style bases by doing the following:
48
+ # :s3_permissions => {
49
+ # :original => "private"
50
+ # }
51
+ # Or globally:
52
+ # :s3_permissions => "private"
53
+ #
54
+ # * +s3_protocol+: The protocol for the URLs generated to your S3 assets.
55
+ # Can be either 'http', 'https', or an empty string to generate
56
+ # protocol-relative URLs. Defaults to empty string.
57
+ # * +s3_headers+: A hash of headers or a Proc. You may specify a hash such as
58
+ # {'Expires' => 1.year.from_now.httpdate}. If you use a Proc, headers are determined at
59
+ # runtime. Paperclip will call that Proc with attachment as the only argument.
60
+ # Can be defined both globally and within a style-specific hash.
61
+ # * +bucket+: This is the name of the S3 bucket that will store your files. Remember
62
+ # that the bucket must be unique across all of Amazon S3. If the bucket does not exist
63
+ # Paperclip will attempt to create it. The bucket name will not be interpolated.
64
+ # You can define the bucket as a Proc if you want to determine its name at runtime.
65
+ # Paperclip will call that Proc with attachment as the only argument.
66
+ # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
67
+ # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
68
+ # link in the +url+ entry for more information about S3 domains and buckets.
69
+ # * +s3_prefixes_in_alias+: The number of prefixes that is prepended by
70
+ # s3_host_alias. This will remove the prefixes from the path in
71
+ # :s3_alias_url url interpolation
72
+ # * +url+: There are four options for the S3 url. You can choose to have the bucket's name
73
+ # placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
74
+ # You can also specify a CNAME (which requires the CNAME to be specified as
75
+ # :s3_alias_url. You can read more about CNAMEs and S3 at
76
+ # http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html
77
+ # Normally, this won't matter in the slightest and you can leave the default (which is
78
+ # path-style, or :s3_path_url). But in some cases paths don't work and you need to use
79
+ # the domain-style (:s3_domain_url). Anything else here will be treated like path-style.
80
+ #
81
+ # Notes:
82
+ # * The value of this option is a string, not a symbol.
83
+ # <b>right:</b> <tt>":s3_domain_url"</tt>
84
+ # <b>wrong:</b> <tt>:s3_domain_url</tt>
85
+ # * If you use a CNAME for use with CloudFront, you can NOT specify https as your
86
+ # :s3_protocol;
87
+ # This is *not supported* by S3/CloudFront. Finally, when using the host
88
+ # alias, the :bucket parameter is ignored, as the hostname is used as the bucket name
89
+ # by S3. The fourth option for the S3 url is :asset_host, which uses Rails' built-in
90
+ # asset_host settings.
91
+ # * To get the full url from a paperclip'd object, use the
92
+ # image_path helper; this is what image_tag uses to generate the url for an img tag.
93
+ # * +path+: This is the key under the bucket in which the file will be stored. The
94
+ # URL will be constructed from the bucket and the path. This is what you will want
95
+ # to interpolate. Keys should be unique, like filenames, and despite the fact that
96
+ # S3 (strictly speaking) does not support directories, you can still use a / to
97
+ # separate parts of your file name.
98
+ # * +s3_host_name+: If you are using your bucket in Tokyo region
99
+ # etc, write host_name (e.g., 's3-ap-northeast-1.amazonaws.com').
100
+ # * +s3_region+: For aws-sdk-s3, s3_region is required.
101
+ # * +s3_metadata+: These key/value pairs will be stored with the
102
+ # object. This option works by prefixing each key with
103
+ # "x-amz-meta-" before sending it as a header on the object
104
+ # upload request. Can be defined both globally and within a style-specific hash.
105
+ # * +s3_storage_class+: If this option is set to
106
+ # <tt>:REDUCED_REDUNDANCY</tt>, the object will be stored using Reduced
107
+ # Redundancy Storage. RRS enables customers to reduce their
108
+ # costs by storing non-critical, reproducible data at lower
109
+ # levels of redundancy than Amazon S3's standard storage.
110
+ # * +use_accelerate_endpoint+: Use accelerate endpoint
111
+ # http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
112
+ #
113
+ # You can set storage class on a per style bases by doing the following:
114
+ # :s3_storage_class => {
115
+ # :thumb => :REDUCED_REDUNDANCY
116
+ # }
117
+ #
118
+ # Or globally:
119
+ # :s3_storage_class => :REDUCED_REDUNDANCY
120
+ #
121
+ # Other storage classes, such as <tt>:STANDARD_IA</tt>, are also available—see the
122
+ # documentation for the <tt>aws-sdk-s3</tt> gem for the full list.
123
+
124
+ module S3
125
+ def self.extended(base)
126
+ begin
127
+ require "aws-sdk-s3"
128
+ rescue LoadError => e
129
+ e.message << " (You may need to install the aws-sdk-s3 gem)"
130
+ raise e
131
+ end
132
+
133
+ base.instance_eval do
134
+ @s3_options = @options[:s3_options] || {}
135
+ @s3_permissions = set_permissions(@options[:s3_permissions])
136
+ @s3_protocol = @options[:s3_protocol] || ""
137
+ @s3_metadata = @options[:s3_metadata] || {}
138
+ @s3_headers = {}
139
+ merge_s3_headers(@options[:s3_headers], @s3_headers, @s3_metadata)
140
+
141
+ @s3_acl_enabled = @options[:s3_acl_enabled]
142
+ @s3_acl_enabled = true if @s3_acl_enabled.nil?
143
+
144
+ @s3_storage_class = set_storage_class(@options[:s3_storage_class])
145
+
146
+ @s3_server_side_encryption = "AES256"
147
+ @s3_server_side_encryption = false if @options[:s3_server_side_encryption].blank?
148
+ @s3_server_side_encryption = @options[:s3_server_side_encryption] if @s3_server_side_encryption
149
+
150
+ unless @options[:url].to_s.match(/\A:s3.*url\z/) || @options[:url] == ":asset_host"
151
+ @options[:path] = path_option.gsub(/:url/, @options[:url]).sub(/\A:rails_root\/public\/system/, "")
152
+ @options[:url] = ":s3_path_url"
153
+ end
154
+ @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)
155
+
156
+ @http_proxy = @options[:http_proxy] || nil
157
+
158
+ @use_accelerate_endpoint = @options[:use_accelerate_endpoint]
159
+ end
160
+
161
+ unless Paperclip::Interpolations.respond_to? :s3_alias_url
162
+ Paperclip.interpolates(:s3_alias_url) do |attachment, style|
163
+ protocol = attachment.s3_protocol(style, true)
164
+ host = attachment.s3_host_alias
165
+ path = attachment.path(style).
166
+ split("/")[attachment.s3_prefixes_in_alias..-1].
167
+ join("/").
168
+ sub(%r{\A/}, "")
169
+ "#{protocol}//#{host}/#{path}"
170
+ end
171
+ end
172
+ unless Paperclip::Interpolations.respond_to? :s3_path_url
173
+ Paperclip.interpolates(:s3_path_url) do |attachment, style|
174
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).sub(%r{\A/}, '')}"
175
+ end
176
+ end
177
+ unless Paperclip::Interpolations.respond_to? :s3_domain_url
178
+ Paperclip.interpolates(:s3_domain_url) do |attachment, style|
179
+ "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).sub(%r{\A/}, '')}"
180
+ end
181
+ end
182
+ unless Paperclip::Interpolations.respond_to? :asset_host
183
+ Paperclip.interpolates(:asset_host) do |attachment, style|
184
+ attachment.path(style).sub(%r{\A/}, "").to_s
185
+ end
186
+ end
187
+ end
188
+
189
+ def expiring_url(time = 3600, style_name = default_style)
190
+ if path(style_name)
191
+ base_options = { expires_in: time }
192
+ s3_object(style_name).presigned_url(
193
+ :get,
194
+ base_options.merge(s3_url_options)
195
+ ).to_s
196
+ else
197
+ url(style_name)
198
+ end
199
+ end
200
+
201
+ def s3_credentials
202
+ @s3_credentials ||= parse_credentials(@options[:s3_credentials])
203
+ end
204
+
205
+ def s3_host_name
206
+ host_name = @options[:s3_host_name]
207
+ host_name = host_name.call(self) if host_name.is_a?(Proc)
208
+
209
+ host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com"
210
+ end
211
+
212
+ def s3_region
213
+ region = @options[:s3_region]
214
+ region = region.call(self) if region.is_a?(Proc)
215
+
216
+ region || s3_credentials[:s3_region]
217
+ end
218
+
219
+ def s3_host_alias
220
+ @s3_host_alias = @options[:s3_host_alias]
221
+ @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.respond_to?(:call)
222
+ @s3_host_alias
223
+ end
224
+
225
+ def s3_prefixes_in_alias
226
+ @s3_prefixes_in_alias ||= @options[:s3_prefixes_in_alias].to_i
227
+ end
228
+
229
+ def s3_url_options
230
+ s3_url_options = @options[:s3_url_options] || {}
231
+ s3_url_options = s3_url_options.call(instance) if s3_url_options.respond_to?(:call)
232
+ s3_url_options
233
+ end
234
+
235
+ def bucket_name
236
+ @bucket = @options[:bucket] || s3_credentials[:bucket]
237
+ @bucket = @bucket.call(self) if @bucket.respond_to?(:call)
238
+ @bucket || raise(ArgumentError, "missing required :bucket option")
239
+ end
240
+
241
+ def s3_interface
242
+ @s3_interface ||= begin
243
+ config = { region: s3_region }
244
+
245
+ if using_http_proxy?
246
+
247
+ proxy_opts = { host: http_proxy_host }
248
+ proxy_opts[:port] = http_proxy_port if http_proxy_port
249
+ if http_proxy_user
250
+ userinfo = http_proxy_user.to_s
251
+ userinfo += ":#{http_proxy_password}" if http_proxy_password
252
+ proxy_opts[:userinfo] = userinfo
253
+ end
254
+ config[:proxy_uri] = URI::HTTP.build(proxy_opts)
255
+ end
256
+
257
+ config[:use_accelerate_endpoint] = use_accelerate_endpoint?
258
+
259
+ [:access_key_id, :secret_access_key, :credential_provider, :credentials].each do |opt|
260
+ config[opt] = s3_credentials[opt] if s3_credentials[opt]
261
+ end
262
+
263
+ obtain_s3_instance_for(config.merge(@s3_options))
264
+ end
265
+ end
266
+
267
+ def obtain_s3_instance_for(options)
268
+ instances = (Thread.current[:paperclip_s3_instances] ||= {})
269
+ instances[options] ||= ::Aws::S3::Resource.new(options)
270
+ end
271
+
272
+ def s3_bucket
273
+ @s3_bucket ||= s3_interface.bucket(bucket_name)
274
+ end
275
+
276
+ def style_name_as_path(style_name)
277
+ path(style_name).sub(%r{\A/}, "")
278
+ end
279
+
280
+ def s3_object(style_name = default_style)
281
+ s3_bucket.object style_name_as_path(style_name)
282
+ end
283
+
284
+ def s3_transfer_manager
285
+ @s3_transfer_manager ||= begin
286
+ if ::Aws::S3.const_defined?(:TransferManager, false)
287
+ ::Aws::S3::TransferManager.new(client: s3_interface.client)
288
+ else
289
+ nil
290
+ end
291
+ end
292
+ end
293
+
294
+ def use_accelerate_endpoint?
295
+ !!@use_accelerate_endpoint
296
+ end
297
+
298
+ def using_http_proxy?
299
+ !!@http_proxy
300
+ end
301
+
302
+ def http_proxy_host
303
+ using_http_proxy? ? @http_proxy[:host] : nil
304
+ end
305
+
306
+ def http_proxy_port
307
+ using_http_proxy? ? @http_proxy[:port] : nil
308
+ end
309
+
310
+ def http_proxy_user
311
+ using_http_proxy? ? @http_proxy[:user] : nil
312
+ end
313
+
314
+ def http_proxy_password
315
+ using_http_proxy? ? @http_proxy[:password] : nil
316
+ end
317
+
318
+ def set_permissions(permissions)
319
+ permissions = { default: permissions } unless permissions.respond_to?(:merge)
320
+ permissions.merge default: (permissions[:default] || :"public-read")
321
+ end
322
+
323
+ def set_storage_class(storage_class)
324
+ storage_class = { default: storage_class } unless storage_class.respond_to?(:merge)
325
+ storage_class
326
+ end
327
+
328
+ def parse_credentials(creds)
329
+ creds = creds.respond_to?(:call) ? creds.call(self) : creds
330
+ creds = find_credentials(creds).stringify_keys
331
+ (creds[RailsEnvironment.get] || creds).symbolize_keys
332
+ end
333
+
334
+ def exists?(style = default_style)
335
+ if original_filename
336
+ s3_object(style).exists?
337
+ else
338
+ false
339
+ end
340
+ rescue Aws::Errors::ServiceError => e
341
+ false
342
+ end
343
+
344
+ def s3_permissions(style = default_style)
345
+ s3_permissions = @s3_permissions[style] || @s3_permissions[:default]
346
+ s3_permissions = s3_permissions.call(self, style) if s3_permissions.respond_to?(:call)
347
+ s3_permissions
348
+ end
349
+
350
+ def s3_storage_class(style = default_style)
351
+ @s3_storage_class[style] || @s3_storage_class[:default]
352
+ end
353
+
354
+ def s3_protocol(style = default_style, with_colon = false)
355
+ protocol = @s3_protocol
356
+ protocol = protocol.call(style, self) if protocol.respond_to?(:call)
357
+
358
+ if with_colon && !protocol.empty?
359
+ "#{protocol}:"
360
+ else
361
+ protocol.to_s
362
+ end
363
+ end
364
+
365
+ def create_bucket
366
+ s3_interface.bucket(bucket_name).create
367
+ end
368
+
369
+ def flush_writes #:nodoc:
370
+ @queued_for_write.each do |style, file|
371
+ retries = 0
372
+ begin
373
+ log("saving #{path(style)}")
374
+ write_options = {
375
+ content_type: file.content_type,
376
+ }
377
+
378
+ if @s3_acl_enabled
379
+ write_options[:acl] = s3_permissions(style)
380
+ end
381
+
382
+ # add storage class for this style if defined
383
+ storage_class = s3_storage_class(style)
384
+ write_options.merge!(storage_class: storage_class) if storage_class
385
+
386
+ write_options[:server_side_encryption] = @s3_server_side_encryption if @s3_server_side_encryption
387
+
388
+ style_specific_options = styles[style]
389
+
390
+ if style_specific_options
391
+ if style_specific_options[:s3_headers]
392
+ merge_s3_headers(style_specific_options[:s3_headers], @s3_headers, @s3_metadata)
393
+ end
394
+ @s3_metadata.merge!(style_specific_options[:s3_metadata]) if style_specific_options[:s3_metadata]
395
+ end
396
+
397
+ write_options[:metadata] = @s3_metadata unless @s3_metadata.empty?
398
+ write_options.merge!(@s3_headers)
399
+
400
+ if s3_transfer_manager
401
+ destination = style_name_as_path(style)
402
+ s3_transfer_manager.upload_file(file.path, **write_options, bucket: bucket_name, key: destination)
403
+ else
404
+ s3_object(style).upload_file(file.path, write_options)
405
+ end
406
+ rescue ::Aws::S3::Errors::NoSuchBucket
407
+ create_bucket
408
+ retry
409
+ rescue ::Aws::S3::Errors::SlowDown
410
+ retries += 1
411
+ if retries <= 5
412
+ sleep((2**retries) * 0.5)
413
+ retry
414
+ else
415
+ raise
416
+ end
417
+ ensure
418
+ file.rewind
419
+ end
420
+ end
421
+
422
+ after_flush_writes # allows attachment to clean up temp files
423
+
424
+ @queued_for_write = {}
425
+ end
426
+
427
+ def flush_deletes #:nodoc:
428
+ @queued_for_delete.uniq.each do |path|
429
+ begin
430
+ log("deleting #{path}")
431
+ s3_bucket.object(path.sub(%r{\A/}, "")).delete
432
+ rescue Aws::Errors::ServiceError => e
433
+ # Ignore this.
434
+ end
435
+ end
436
+ @queued_for_delete = []
437
+ end
438
+
439
+ def copy_to_local_file(style, local_dest_path)
440
+ log("copying #{path(style)} to local file #{local_dest_path}")
441
+
442
+ if s3_transfer_manager
443
+ source = style_name_as_path(style)
444
+ s3_transfer_manager.download_file(local_dest_path, bucket: bucket_name, key: source)
445
+ else
446
+ s3_object(style).download_file(local_dest_path)
447
+ end
448
+ rescue Aws::Errors::ServiceError => e
449
+ warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
450
+ false
451
+ end
452
+
453
+ private
454
+
455
+ def find_credentials(creds)
456
+ case creds
457
+ when File
458
+ load_credentials_from_file(creds.path)
459
+ when String, Pathname
460
+ load_credentials_from_file(creds)
461
+ when Hash
462
+ creds
463
+ when NilClass
464
+ {}
465
+ else
466
+ raise ArgumentError, "Credentials given are not a path, file, proc, or hash."
467
+ end
468
+ end
469
+
470
+ def load_credentials_from_file(path)
471
+ if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("3.1.0")
472
+ YAML::safe_load(ERB.new(File.read(path)).result, aliases: true)
473
+ else
474
+ YAML::safe_load(ERB.new(File.read(path)).result, [], [], true)
475
+ end
476
+ end
477
+
478
+ def use_secure_protocol?(style_name)
479
+ s3_protocol(style_name) == "https"
480
+ end
481
+
482
+ def merge_s3_headers(http_headers, s3_headers, s3_metadata)
483
+ return if http_headers.nil?
484
+
485
+ http_headers = http_headers.call(instance) if http_headers.respond_to?(:call)
486
+ http_headers.inject({}) do |_headers, (name, value)|
487
+ case name.to_s
488
+ when /\Ax-amz-meta-(.*)/i
489
+ s3_metadata[$1.downcase] = value
490
+ else
491
+ s3_headers[name.to_s.downcase.sub(/\Ax-amz-/, "").tr("-", "_").to_sym] = value
492
+ end
493
+ end
494
+ end
495
+ end
496
+ end
497
+ end
@@ -0,0 +1,3 @@
1
+ require "paperclip/storage/filesystem"
2
+ require "paperclip/storage/fog"
3
+ require "paperclip/storage/s3"
@@ -0,0 +1,106 @@
1
+ module Paperclip
2
+ # The Style class holds the definition of a thumbnail style, applying
3
+ # whatever processing is required to normalize the definition and delaying
4
+ # the evaluation of block parameters until useful context is available.
5
+
6
+ class Style
7
+ attr_reader :name, :attachment, :format
8
+
9
+ # Creates a Style object. +name+ is the name of the attachment,
10
+ # +definition+ is the style definition from has_attached_file, which
11
+ # can be string, array or hash
12
+ def initialize(name, definition, attachment)
13
+ @name = name
14
+ @attachment = attachment
15
+ if definition.is_a? Hash
16
+ @geometry = definition.delete(:geometry)
17
+ @format = definition.delete(:format)
18
+ @processors = definition.delete(:processors)
19
+ @convert_options = definition.delete(:convert_options)
20
+ @source_file_options = definition.delete(:source_file_options)
21
+ @other_args = definition
22
+ elsif definition.is_a? String
23
+ @geometry = definition
24
+ @format = nil
25
+ @other_args = {}
26
+ else
27
+ @geometry, @format = [definition, nil].flatten[0..1]
28
+ @other_args = {}
29
+ end
30
+ @format = default_format if @format.blank?
31
+ end
32
+
33
+ # retrieves from the attachment the processors defined in the has_attached_file call
34
+ # (which method (in the attachment) will call any supplied procs)
35
+ # There is an important change of interface here: a style rule can set its own processors
36
+ # by default we behave as before, though.
37
+ # if a proc has been supplied, we call it here
38
+ def processors
39
+ @processors.respond_to?(:call) ? @processors.call(attachment.instance) : (@processors || attachment.processors)
40
+ end
41
+
42
+ # retrieves from the attachment the whiny setting
43
+ def whiny
44
+ attachment.whiny
45
+ end
46
+
47
+ # returns true if we're inclined to grumble
48
+ def whiny?
49
+ !!whiny
50
+ end
51
+
52
+ def convert_options
53
+ @convert_options.respond_to?(:call) ? @convert_options.call(attachment.instance) :
54
+ (@convert_options || attachment.send(:extra_options_for, name))
55
+ end
56
+
57
+ def source_file_options
58
+ @source_file_options.respond_to?(:call) ? @source_file_options.call(attachment.instance) :
59
+ (@source_file_options || attachment.send(:extra_source_file_options_for, name))
60
+ end
61
+
62
+ # returns the geometry string for this style
63
+ # if a proc has been supplied, we call it here
64
+ def geometry
65
+ @geometry.respond_to?(:call) ? @geometry.call(attachment.instance) : @geometry
66
+ end
67
+
68
+ # Supplies the hash of options that processors expect to receive as their second argument
69
+ # Arguments other than the standard geometry, format etc are just passed through from
70
+ # initialization and any procs are called here, just before post-processing.
71
+ def processor_options
72
+ args = { style: name }
73
+ @other_args.each do |k, v|
74
+ args[k] = v.respond_to?(:call) ? v.call(attachment) : v
75
+ end
76
+ [:processors, :geometry, :format, :whiny, :convert_options, :source_file_options].each do |k|
77
+ (arg = send(k)) && args[k] = arg
78
+ end
79
+ args
80
+ end
81
+
82
+ # Supports getting and setting style properties with hash notation to ensure backwards-compatibility
83
+ # eg. @attachment.styles[:large][:geometry]@ will still work
84
+ def [](key)
85
+ if [:name, :convert_options, :whiny, :processors, :geometry, :format, :animated, :source_file_options].include?(key)
86
+ send(key)
87
+ elsif defined? @other_args[key]
88
+ @other_args[key]
89
+ end
90
+ end
91
+
92
+ def []=(key, value)
93
+ if [:name, :convert_options, :whiny, :processors, :geometry, :format, :animated, :source_file_options].include?(key)
94
+ send("#{key}=".intern, value)
95
+ else
96
+ @other_args[key] = value
97
+ end
98
+ end
99
+
100
+ # defaults to default format (nil by default)
101
+ def default_format
102
+ base = attachment.options[:default_format]
103
+ base.respond_to?(:call) ? base.call(attachment, name) : base
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,42 @@
1
+ module Paperclip
2
+ # Overriding some implementation of Tempfile
3
+ class Tempfile < ::Tempfile
4
+ # Due to how ImageMagick handles its image format conversion and how
5
+ # Tempfile handles its naming scheme, it is necessary to override how
6
+ # Tempfile makes # its names so as to allow for file extensions. Idea
7
+ # taken from the comments on this blog post:
8
+ # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
9
+ #
10
+ # This is Ruby 1.9.3's implementation.
11
+ def make_tmpname(prefix_suffix, n)
12
+ if RUBY_PLATFORM =~ /java/
13
+ case prefix_suffix
14
+ when String
15
+ prefix = prefix_suffix
16
+ suffix = ""
17
+ when Array
18
+ prefix, suffix = *prefix_suffix
19
+ else
20
+ raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
21
+ end
22
+
23
+ t = Time.now.strftime("%y%m%d")
24
+ path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}#{suffix}"
25
+ else
26
+ super
27
+ end
28
+ end
29
+ end
30
+
31
+ module TempfileEncoding
32
+ # This overrides Tempfile#binmode to make sure that the extenal encoding
33
+ # for binary mode is ASCII-8BIT. This behavior is what's in CRuby, but not
34
+ # in JRuby
35
+ def binmode
36
+ set_encoding("ASCII-8BIT")
37
+ super
38
+ end
39
+ end
40
+ end
41
+
42
+ ::Tempfile.include Paperclip::TempfileEncoding if RUBY_PLATFORM =~ /java/
@@ -0,0 +1,22 @@
1
+ module Paperclip
2
+ class TempfileFactory
3
+ def generate(name = random_name)
4
+ @name = name
5
+ file = Tempfile.new([basename, extension])
6
+ file.binmode
7
+ file
8
+ end
9
+
10
+ def extension
11
+ File.extname(@name)
12
+ end
13
+
14
+ def basename
15
+ Digest::MD5.hexdigest(File.basename(@name, extension))
16
+ end
17
+
18
+ def random_name
19
+ SecureRandom.uuid
20
+ end
21
+ end
22
+ end