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