shrine 2.19.3 → 3.6.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 (211) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +523 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +83 -979
  5. data/doc/advantages.md +231 -204
  6. data/doc/attacher.md +304 -153
  7. data/doc/carrierwave.md +297 -226
  8. data/doc/changing_derivatives.md +308 -0
  9. data/doc/changing_location.md +103 -21
  10. data/doc/changing_storage.md +110 -0
  11. data/doc/creating_persistence_plugins.md +132 -0
  12. data/doc/creating_plugins.md +43 -23
  13. data/doc/creating_storages.md +19 -5
  14. data/doc/design.md +147 -97
  15. data/doc/direct_s3.md +38 -28
  16. data/doc/external/articles.md +63 -0
  17. data/doc/external/extensions.md +53 -0
  18. data/doc/external/misc.md +32 -0
  19. data/doc/getting_started.md +1156 -0
  20. data/doc/metadata.md +190 -109
  21. data/doc/multiple_files.md +93 -30
  22. data/doc/paperclip.md +384 -262
  23. data/doc/plugins/activerecord.md +177 -46
  24. data/doc/plugins/add_metadata.md +139 -38
  25. data/doc/plugins/atomic_helpers.md +217 -0
  26. data/doc/plugins/backgrounding.md +156 -98
  27. data/doc/plugins/cached_attachment_data.md +7 -5
  28. data/doc/plugins/column.md +121 -0
  29. data/doc/plugins/data_uri.md +23 -22
  30. data/doc/plugins/default_storage.md +36 -10
  31. data/doc/plugins/default_url.md +30 -13
  32. data/doc/plugins/delete_raw.md +4 -2
  33. data/doc/plugins/derivation_endpoint.md +186 -101
  34. data/doc/plugins/derivatives.md +839 -0
  35. data/doc/plugins/determine_mime_type.md +4 -2
  36. data/doc/plugins/download_endpoint.md +64 -8
  37. data/doc/plugins/dynamic_storage.md +5 -3
  38. data/doc/plugins/entity.md +263 -0
  39. data/doc/plugins/form_assign.md +55 -0
  40. data/doc/plugins/included.md +31 -8
  41. data/doc/plugins/infer_extension.md +21 -10
  42. data/doc/plugins/instrumentation.md +38 -16
  43. data/doc/plugins/keep_files.md +16 -17
  44. data/doc/plugins/metadata_attributes.md +42 -13
  45. data/doc/plugins/mirroring.md +118 -0
  46. data/doc/plugins/model.md +210 -0
  47. data/doc/plugins/module_include.md +4 -2
  48. data/doc/plugins/multi_cache.md +24 -0
  49. data/doc/plugins/persistence.md +101 -0
  50. data/doc/plugins/presign_endpoint.md +9 -4
  51. data/doc/plugins/pretty_location.md +16 -3
  52. data/doc/plugins/processing.md +4 -2
  53. data/doc/plugins/rack_file.md +8 -2
  54. data/doc/plugins/rack_response.md +6 -2
  55. data/doc/plugins/recache.md +4 -2
  56. data/doc/plugins/refresh_metadata.md +49 -9
  57. data/doc/plugins/remote_url.md +84 -47
  58. data/doc/plugins/remove_attachment.md +27 -6
  59. data/doc/plugins/remove_invalid.md +21 -6
  60. data/doc/plugins/restore_cached_data.md +11 -3
  61. data/doc/plugins/sequel.md +159 -35
  62. data/doc/plugins/signature.md +16 -5
  63. data/doc/plugins/store_dimensions.md +14 -2
  64. data/doc/plugins/tempfile.md +4 -2
  65. data/doc/plugins/type_predicates.md +96 -0
  66. data/doc/plugins/upload_endpoint.md +13 -13
  67. data/doc/plugins/upload_options.md +6 -4
  68. data/doc/plugins/{default_url_options.md → url_options.md} +9 -7
  69. data/doc/plugins/validation.md +97 -0
  70. data/doc/plugins/validation_helpers.md +16 -13
  71. data/doc/plugins/versions.md +15 -19
  72. data/doc/processing.md +438 -221
  73. data/doc/refile.md +188 -170
  74. data/doc/release_notes/1.0.0.md +4 -0
  75. data/doc/release_notes/1.1.0.md +6 -2
  76. data/doc/release_notes/1.2.0.md +4 -0
  77. data/doc/release_notes/1.3.0.md +4 -0
  78. data/doc/release_notes/1.4.0.md +4 -0
  79. data/doc/release_notes/1.4.1.md +4 -0
  80. data/doc/release_notes/1.4.2.md +4 -0
  81. data/doc/release_notes/2.0.0.md +4 -0
  82. data/doc/release_notes/2.0.1.md +4 -0
  83. data/doc/release_notes/2.1.0.md +5 -1
  84. data/doc/release_notes/2.1.1.md +4 -0
  85. data/doc/release_notes/2.10.0.md +4 -0
  86. data/doc/release_notes/2.10.1.md +4 -0
  87. data/doc/release_notes/2.11.0.md +4 -0
  88. data/doc/release_notes/2.12.0.md +4 -0
  89. data/doc/release_notes/2.13.0.md +4 -0
  90. data/doc/release_notes/2.14.0.md +5 -1
  91. data/doc/release_notes/2.15.0.md +11 -7
  92. data/doc/release_notes/2.16.0.md +4 -0
  93. data/doc/release_notes/2.17.0.md +4 -0
  94. data/doc/release_notes/2.18.0.md +4 -0
  95. data/doc/release_notes/2.19.0.md +6 -3
  96. data/doc/release_notes/2.2.0.md +4 -0
  97. data/doc/release_notes/2.3.0.md +4 -0
  98. data/doc/release_notes/2.3.1.md +4 -0
  99. data/doc/release_notes/2.4.0.md +4 -0
  100. data/doc/release_notes/2.4.1.md +4 -0
  101. data/doc/release_notes/2.5.0.md +4 -0
  102. data/doc/release_notes/2.6.0.md +4 -0
  103. data/doc/release_notes/2.6.1.md +4 -0
  104. data/doc/release_notes/2.7.0.md +4 -0
  105. data/doc/release_notes/2.8.0.md +4 -0
  106. data/doc/release_notes/2.9.0.md +4 -0
  107. data/doc/release_notes/3.0.0.md +981 -0
  108. data/doc/release_notes/3.0.1.md +22 -0
  109. data/doc/release_notes/3.1.0.md +73 -0
  110. data/doc/release_notes/3.2.0.md +96 -0
  111. data/doc/release_notes/3.2.1.md +31 -0
  112. data/doc/release_notes/3.2.2.md +14 -0
  113. data/doc/release_notes/3.3.0.md +105 -0
  114. data/doc/release_notes/3.4.0.md +35 -0
  115. data/doc/release_notes/3.5.0.md +63 -0
  116. data/doc/release_notes/3.6.0.md +23 -0
  117. data/doc/retrieving_uploads.md +5 -2
  118. data/doc/securing_uploads.md +60 -37
  119. data/doc/storage/file_system.md +20 -3
  120. data/doc/storage/memory.md +19 -0
  121. data/doc/storage/s3.md +122 -78
  122. data/doc/testing.md +141 -133
  123. data/doc/upgrading_to_3.md +708 -0
  124. data/doc/validation.md +54 -90
  125. data/lib/shrine/attacher.rb +292 -169
  126. data/lib/shrine/attachment.rb +13 -46
  127. data/lib/shrine/plugins/_persistence.rb +93 -0
  128. data/lib/shrine/plugins/activerecord.rb +77 -34
  129. data/lib/shrine/plugins/add_metadata.rb +25 -17
  130. data/lib/shrine/plugins/atomic_helpers.rb +119 -0
  131. data/lib/shrine/plugins/backgrounding.rb +77 -113
  132. data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
  133. data/lib/shrine/plugins/column.rb +102 -0
  134. data/lib/shrine/plugins/data_uri.rb +38 -36
  135. data/lib/shrine/plugins/default_storage.rb +45 -15
  136. data/lib/shrine/plugins/default_url.rb +12 -24
  137. data/lib/shrine/plugins/default_url_options.rb +3 -30
  138. data/lib/shrine/plugins/delete_raw.rb +10 -16
  139. data/lib/shrine/plugins/derivation_endpoint.rb +130 -171
  140. data/lib/shrine/plugins/derivatives.rb +645 -0
  141. data/lib/shrine/plugins/determine_mime_type.rb +9 -21
  142. data/lib/shrine/plugins/download_endpoint.rb +118 -133
  143. data/lib/shrine/plugins/dynamic_storage.rb +5 -11
  144. data/lib/shrine/plugins/entity.rb +158 -0
  145. data/lib/shrine/plugins/form_assign.rb +108 -0
  146. data/lib/shrine/plugins/included.rb +6 -6
  147. data/lib/shrine/plugins/infer_extension.rb +17 -20
  148. data/lib/shrine/plugins/instrumentation.rb +59 -43
  149. data/lib/shrine/plugins/keep_files.rb +3 -15
  150. data/lib/shrine/plugins/metadata_attributes.rb +28 -19
  151. data/lib/shrine/plugins/mirroring.rb +142 -0
  152. data/lib/shrine/plugins/model.rb +160 -0
  153. data/lib/shrine/plugins/module_include.rb +3 -3
  154. data/lib/shrine/plugins/multi_cache.rb +27 -0
  155. data/lib/shrine/plugins/presign_endpoint.rb +27 -28
  156. data/lib/shrine/plugins/pretty_location.rb +15 -9
  157. data/lib/shrine/plugins/processing.rb +22 -9
  158. data/lib/shrine/plugins/rack_file.rb +2 -42
  159. data/lib/shrine/plugins/rack_response.rb +21 -10
  160. data/lib/shrine/plugins/recache.rb +6 -5
  161. data/lib/shrine/plugins/refresh_metadata.rb +13 -11
  162. data/lib/shrine/plugins/remote_url.rb +49 -49
  163. data/lib/shrine/plugins/remove_attachment.rb +12 -6
  164. data/lib/shrine/plugins/remove_invalid.rb +19 -8
  165. data/lib/shrine/plugins/restore_cached_data.rb +13 -7
  166. data/lib/shrine/plugins/sequel.rb +86 -36
  167. data/lib/shrine/plugins/signature.rb +10 -16
  168. data/lib/shrine/plugins/store_dimensions.rb +35 -40
  169. data/lib/shrine/plugins/tempfile.rb +1 -3
  170. data/lib/shrine/plugins/type_predicates.rb +113 -0
  171. data/lib/shrine/plugins/upload_endpoint.rb +28 -24
  172. data/lib/shrine/plugins/upload_options.rb +14 -15
  173. data/lib/shrine/plugins/url_options.rb +31 -0
  174. data/lib/shrine/plugins/validation.rb +80 -0
  175. data/lib/shrine/plugins/validation_helpers.rb +35 -58
  176. data/lib/shrine/plugins/versions.rb +107 -87
  177. data/lib/shrine/plugins.rb +22 -0
  178. data/lib/shrine/storage/file_system.rb +46 -64
  179. data/lib/shrine/storage/linter.rb +42 -7
  180. data/lib/shrine/storage/memory.rb +49 -0
  181. data/lib/shrine/storage/s3.rb +173 -160
  182. data/lib/shrine/uploaded_file.rb +32 -32
  183. data/lib/shrine/version.rb +3 -3
  184. data/lib/shrine.rb +87 -150
  185. data/shrine.gemspec +11 -12
  186. metadata +92 -82
  187. data/doc/migrating_storage.md +0 -76
  188. data/doc/plugins/backup.md +0 -31
  189. data/doc/plugins/copy.md +0 -24
  190. data/doc/plugins/delete_promoted.md +0 -12
  191. data/doc/plugins/direct_upload.md +0 -172
  192. data/doc/plugins/hooks.md +0 -58
  193. data/doc/plugins/logging.md +0 -42
  194. data/doc/plugins/migration_helpers.md +0 -60
  195. data/doc/plugins/moving.md +0 -19
  196. data/doc/plugins/multi_delete.md +0 -20
  197. data/doc/plugins/parallelize.md +0 -16
  198. data/doc/plugins/parsed_json.md +0 -23
  199. data/doc/regenerating_versions.md +0 -143
  200. data/lib/shrine/plugins/background_helpers.rb +0 -5
  201. data/lib/shrine/plugins/backup.rb +0 -90
  202. data/lib/shrine/plugins/copy.rb +0 -50
  203. data/lib/shrine/plugins/delete_promoted.rb +0 -20
  204. data/lib/shrine/plugins/direct_upload.rb +0 -217
  205. data/lib/shrine/plugins/hooks.rb +0 -90
  206. data/lib/shrine/plugins/logging.rb +0 -142
  207. data/lib/shrine/plugins/migration_helpers.rb +0 -70
  208. data/lib/shrine/plugins/moving.rb +0 -57
  209. data/lib/shrine/plugins/multi_delete.rb +0 -32
  210. data/lib/shrine/plugins/parallelize.rb +0 -78
  211. data/lib/shrine/plugins/parsed_json.rb +0 -29
@@ -5,40 +5,35 @@ require "content_disposition"
5
5
 
6
6
  require "openssl"
7
7
  require "tempfile"
8
+ require "pathname"
8
9
 
9
10
  class Shrine
10
11
  module Plugins
11
- # Documentation lives in [doc/plugins/derivation_endpoint.md] on GitHub.
12
- #
13
- # [doc/plugins/derivation_endpoint.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/derivation_endpoint.md
12
+ # Documentation can be found on https://shrinerb.com/docs/plugins/derivation_endpoint
14
13
  module DerivationEndpoint
15
14
  LOG_SUBSCRIBER = -> (event) do
16
15
  Shrine.logger.info "Derivation (#{event.duration}ms) – #{{
17
- name: event[:name],
18
- args: event[:args],
16
+ name: event[:derivation].name,
17
+ args: event[:derivation].args,
19
18
  uploader: event[:uploader],
20
19
  }.inspect}"
21
20
  end
22
21
 
23
- def self.load_dependencies(uploader, opts = {})
22
+ def self.load_dependencies(uploader, **)
24
23
  uploader.plugin :rack_response
25
24
  uploader.plugin :_urlsafe_serialization
26
25
  end
27
26
 
28
- def self.configure(uploader, opts = {})
29
- uploader.opts[:derivation_endpoint_options] ||= { log_subscriber: LOG_SUBSCRIBER }
30
- uploader.opts[:derivation_endpoint_options].merge!(opts)
31
-
32
- uploader.opts[:derivation_endpoint_derivations] ||= {}
27
+ def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
28
+ uploader.opts[:derivation_endpoint] ||= { options: {}, derivations: {} }
29
+ uploader.opts[:derivation_endpoint][:options].merge!(opts)
33
30
 
34
- unless uploader.opts[:derivation_endpoint_options][:secret_key]
35
- fail Error, "must provide :secret_key option to derivation_endpoint plugin"
31
+ if !uploader.opts[:derivation_endpoint][:options][:secret_key] && !uploader.opts[:derivation_endpoint][:options][:signer]
32
+ fail Error, "must provide :secret_key option to derivation_endpoint plugin when no custom signer is set"
36
33
  end
37
34
 
38
35
  # instrumentation plugin integration
39
- if uploader.respond_to?(:subscribe)
40
- uploader.subscribe(:derivation, &uploader.opts[:derivation_endpoint_options][:log_subscriber])
41
- end
36
+ uploader.subscribe(:derivation, &log_subscriber) if uploader.respond_to?(:subscribe)
42
37
  end
43
38
 
44
39
  module ClassMethods
@@ -76,15 +71,15 @@ class Shrine
76
71
  # Registers a derivation block, which is called when the corresponding
77
72
  # derivation URL is requested.
78
73
  def derivation(name, &block)
79
- derivations[name] = block
74
+ derivations[name.to_sym] = block
80
75
  end
81
76
 
82
77
  def derivations
83
- opts[:derivation_endpoint_derivations]
78
+ opts[:derivation_endpoint][:derivations]
84
79
  end
85
80
 
86
81
  def derivation_options
87
- opts[:derivation_endpoint_options]
82
+ opts[:derivation_endpoint][:options]
88
83
  end
89
84
  end
90
85
 
@@ -127,7 +122,7 @@ class Shrine
127
122
  attr_reader :name, :args, :source, :options
128
123
 
129
124
  def initialize(name:, args:, source:, options:)
130
- @name = name
125
+ @name = name.to_sym
131
126
  @args = args
132
127
  @source = source
133
128
  @options = options
@@ -163,16 +158,22 @@ class Shrine
163
158
 
164
159
  # Uploads the derivation result to a dedicated destination on the specified
165
160
  # Shrine storage.
166
- def upload(file = nil)
167
- Derivation::Upload.new(self).call(file)
161
+ def upload(file = nil, **options)
162
+ Derivation::Upload.new(self).call(file, **options)
168
163
  end
169
164
 
170
- # Returns a Shrine::UploadedFile object pointing to the uploaded derivation
171
- # result.
165
+ # Returns a Shrine::UploadedFile object pointing to the uploaded derivative
166
+ # if it exists.
172
167
  def retrieve
173
168
  Derivation::Retrieve.new(self).call
174
169
  end
175
170
 
171
+ # Returns opened Shrine::UploadedFile object pointing to the uploaded
172
+ # derivative if it exists.
173
+ def opened
174
+ Derivation::Opened.new(self).call
175
+ end
176
+
176
177
  # Deletes the derivation result from the storage.
177
178
  def delete
178
179
  Derivation::Delete.new(self).call
@@ -189,15 +190,14 @@ class Shrine
189
190
  option :cache_control, default: -> { default_cache_control }
190
191
  option :disposition, default: -> { "inline" }
191
192
  option :download, default: -> { true }
192
- option :download_errors, default: -> { [] }
193
193
  option :download_options, default: -> { {} }
194
194
  option :expires_in
195
195
  option :filename, default: -> { default_filename }
196
196
  option :host
197
- option :include_uploaded_file, default: -> { false }
198
197
  option :metadata, default: -> { [] }
199
198
  option :prefix
200
199
  option :secret_key
200
+ option :signer
201
201
  option :type
202
202
  option :upload, default: -> { false }
203
203
  option :upload_location, default: -> { default_upload_location }, result: -> (o) { upload_location(o) }
@@ -217,7 +217,7 @@ class Shrine
217
217
  option_definition = self.class.options.fetch(name)
218
218
 
219
219
  value = options.fetch(name) { shrine_class.derivation_options[name] }
220
- value = instance_exec(&value) if value.is_a?(Proc)
220
+ value = instance_exec(&value) if value.is_a?(Proc) && value.arity == 0
221
221
 
222
222
  if value.nil?
223
223
  default = option_definition[:default]
@@ -260,7 +260,7 @@ class Shrine
260
260
 
261
261
  # The source uploaded file storage is the default derivative storage.
262
262
  def default_upload_storage
263
- source.storage_key.to_sym
263
+ source.storage_key
264
264
  end
265
265
 
266
266
  # Allows caching for 1 year or until the URL expires.
@@ -301,20 +301,36 @@ class Shrine
301
301
  end
302
302
 
303
303
  class Derivation::Url < Derivation::Command
304
- delegate :name, :args, :source, :secret_key
304
+ delegate :name, :args, :source, :secret_key, :signer
305
305
 
306
306
  def call(host: nil, prefix: nil, **options)
307
- [host, *prefix, identifier(**options)].join("/")
307
+ base_url = [host, *prefix].join("/")
308
+ path = path_identifier(metadata: options.delete(:metadata))
309
+
310
+ if signer
311
+ url = [base_url, path].join("/")
312
+ signer.call(url, **options)
313
+ else
314
+ signed_part = signed_url("#{path}?#{query(**options)}")
315
+ [base_url, signed_part].join("/")
316
+ end
308
317
  end
309
318
 
310
319
  private
311
320
 
312
- def identifier(expires_in: nil,
313
- version: nil,
314
- type: nil,
315
- filename: nil,
316
- disposition: nil,
317
- metadata: [])
321
+ def path_identifier(metadata: [])
322
+ [
323
+ name,
324
+ *args,
325
+ source.urlsafe_dump(metadata: metadata)
326
+ ].map{|component| Rack::Utils.escape_path(component.to_s)}.join('/')
327
+ end
328
+
329
+ def query(expires_in: nil,
330
+ type: nil,
331
+ filename: nil,
332
+ disposition: nil,
333
+ version: nil)
318
334
 
319
335
  params = {}
320
336
  params[:expires_at] = (Time.now + expires_in).to_i if expires_in
@@ -323,23 +339,7 @@ class Shrine
323
339
  params[:filename] = filename if filename
324
340
  params[:disposition] = disposition if disposition
325
341
 
326
- # serializes the source uploaded file into an URL-safe format
327
- source_component = source.urlsafe_dump(metadata: metadata)
328
-
329
- # generate plain URL
330
- url = plain_url(name, *args, source_component, params)
331
-
332
- # generate signed URL
333
- signed_url(url)
334
- end
335
-
336
- def plain_url(*components, params)
337
- # When using Rack < 2, Rack::Utils#escape_path will escape '/'.
338
- # Escape each component and then join them together.
339
- path = components.map{|component| Rack::Utils.escape_path(component.to_s)}.join('/')
340
- query = Rack::Utils.build_query(params)
341
-
342
- "#{path}?#{query}"
342
+ Rack::Utils.build_query(params)
343
343
  end
344
344
 
345
345
  def signed_url(url)
@@ -365,7 +365,9 @@ class Shrine
365
365
  handle_request(request)
366
366
  end
367
367
 
368
- headers["Content-Length"] ||= body.map(&:bytesize).inject(0, :+).to_s
368
+ headers = Rack::Headers[headers] if Rack.release >= "3"
369
+ headers["Content-Length"] ||= body.respond_to?(:bytesize) ? body.bytesize.to_s :
370
+ body.map(&:bytesize).inject(0, :+).to_s
369
371
 
370
372
  [status, headers, body]
371
373
  end
@@ -380,7 +382,7 @@ class Shrine
380
382
  # Returns "404 Not Found" if derivation block is not defined, or if source
381
383
  # file was not found on the storage.
382
384
  def handle_request(request)
383
- verify_signature!(request)
385
+ verify_signature!(request) if secret_key
384
386
  check_expiry!(request)
385
387
 
386
388
  name, *args, serialized_file = request.path_info.split("/")[1..-1]
@@ -393,16 +395,17 @@ class Shrine
393
395
  options[:type] = request.params["type"] if request.params["type"]
394
396
  options[:disposition] = request.params["disposition"] if request.params["disposition"]
395
397
  options[:filename] = request.params["filename"] if request.params["filename"]
398
+ options[:version] = request.params["version"] if request.params["version"]
396
399
  options[:expires_in] = expires_in(request) if request.params["expires_at"]
397
400
 
398
401
  derivation = uploaded_file.derivation(name, *args, **options)
399
402
 
400
403
  begin
401
404
  status, headers, body = derivation.response(request.env)
402
- rescue Derivation::NotFound
403
- error!(404, "Unknown derivation \"#{name}\"")
404
405
  rescue Derivation::SourceNotFound
405
406
  error!(404, "Source file not found")
407
+ rescue Derivation::NotFound
408
+ error!(404, "Unknown derivation \"#{name}\"")
406
409
  end
407
410
 
408
411
  # tell clients to cache the derivation result if it was successful
@@ -476,23 +479,22 @@ class Shrine
476
479
  file_response(derivative, env)
477
480
  end
478
481
 
479
- # Generates a Rack response triple from a local file using `Rack::File`.
480
- # Fills in `Content-Type` and `Content-Disposition` response headers from
481
- # derivation options and file extension of the derivation result.
482
+ # Generates a Rack response triple from a local file. Fills in
483
+ # `Content-Type` and `Content-Disposition` response headers from derivation
484
+ # options and file extension of the derivation result.
482
485
  def file_response(file, env)
483
- response = rack_file_response(file.path, env)
484
-
485
- status = response[0]
486
+ status, headers, body = rack_file_response(file.path, env)
486
487
 
488
+ headers = Rack::Headers[headers] if Rack.release >= "3"
487
489
  headers = {
488
- "Content-Type" => type || response[1]["Content-Type"],
489
- "Content-Length" => response[1]["Content-Length"],
490
+ "Content-Type" => type || headers["Content-Type"],
491
+ "Content-Length" => headers["Content-Length"],
490
492
  "Content-Disposition" => content_disposition(file),
491
- "Content-Range" => response[1]["Content-Range"],
493
+ "Content-Range" => headers["Content-Range"],
492
494
  "Accept-Ranges" => "bytes",
493
495
  }.compact
494
496
 
495
- body = Rack::BodyProxy.new(response[2]) { File.delete(file.path) }
497
+ body = Rack::BodyProxy.new(body) { File.delete(file.path) }
496
498
 
497
499
  file.close
498
500
 
@@ -504,28 +506,23 @@ class Shrine
504
506
  # uploads the result. If the derivation result is already uploaded, uses
505
507
  # the `rack_response` plugin to generate a Rack response triple.
506
508
  def upload_response(env)
507
- uploaded_file = derivation.retrieve
509
+ uploaded_file = upload_redirect ? derivation.retrieve : derivation.opened
508
510
 
509
511
  unless uploaded_file
510
512
  derivative = derivation.generate
511
- uploaded_file = derivation.upload(derivative)
513
+ uploaded_file = derivation.upload(derivative, delete: upload_redirect)
512
514
  end
513
515
 
514
516
  if upload_redirect
515
- # we don't need the local derivation result here
516
- if derivative
517
- derivative.close
518
- File.delete(derivative.path)
519
- end
520
-
521
- redirect_url = uploaded_file.url(upload_redirect_url_options)
517
+ redirect_url = uploaded_file.url(**upload_redirect_url_options)
518
+ headers = { "Location" => redirect_url }
519
+ headers = Rack::Headers[headers] if Rack.release >= "3"
522
520
 
523
- [302, { "Location" => redirect_url }, []]
521
+ [302, headers, []]
524
522
  else
525
523
  if derivative && File.exist?(derivative.path)
526
524
  file_response(derivative, env)
527
525
  else
528
- uploaded_file.open(**upload_open_options)
529
526
  uploaded_file.to_rack_response(
530
527
  type: type,
531
528
  disposition: disposition,
@@ -536,16 +533,22 @@ class Shrine
536
533
  end
537
534
  end
538
535
 
539
- # We call `Rack::File` with no default `Content-Type`, and make sure we
536
+ # We call `Rack::Files` with no default `Content-Type`, and make sure we
540
537
  # stay compatible with both Rack 2.x and 1.6.x.
541
538
  def rack_file_response(path, env)
542
- server = Rack::File.new("", {}, nil)
539
+ if Rack.release >= "2.1"
540
+ server = Rack::Files.new("", {}, nil)
541
+ else
542
+ server = Rack::File.new("", {}, nil)
543
+ end
543
544
 
544
545
  if Rack.release > "2"
545
546
  server.serving(Rack::Request.new(env), path)
546
547
  else
548
+ # :nocov:
547
549
  server.path = path
548
550
  server.serving(env)
551
+ # :nocov:
549
552
  end
550
553
  end
551
554
 
@@ -572,9 +575,7 @@ class Shrine
572
575
  end
573
576
 
574
577
  class Derivation::Generate < Derivation::Command
575
- delegate :name, :args, :source,
576
- :download, :download_errors, :download_options,
577
- :include_uploaded_file
578
+ delegate :name, :args, :source, :download, :download_options
578
579
 
579
580
  def call(file = nil)
580
581
  derivative = generate(file)
@@ -589,22 +590,16 @@ class Shrine
589
590
  # file.
590
591
  def generate(file)
591
592
  if download
592
- with_downloaded(file) do |file|
593
- if include_uploaded_file
594
- derive(file, source, *args)
595
- else
596
- derive(file, *args)
597
- end
598
- end
593
+ with_downloaded(file) { |file| derive(file, *args) }
599
594
  else
600
- derive(source, *args)
595
+ derive(*args)
601
596
  end
602
597
  end
603
598
 
604
599
  # Calls the derivation block.
605
600
  def derive(*args)
606
601
  instrument_derivation do
607
- uploader.instance_exec(*args, &derivation_block)
602
+ derivation.instance_exec(*args, &derivation_block)
608
603
  end
609
604
  end
610
605
 
@@ -612,63 +607,37 @@ class Shrine
612
607
  def instrument_derivation(&block)
613
608
  return yield unless shrine_class.respond_to?(:instrument)
614
609
 
615
- shrine_class.instrument(
616
- :derivation,
617
- name: derivation.name,
618
- args: derivation.args,
619
- derivation: derivation,
620
- &block
621
- )
610
+ shrine_class.instrument(:derivation, { derivation: derivation }, &block)
622
611
  end
623
612
 
624
613
  # Massages the derivation result, ensuring it's opened in binary mode,
625
614
  # rewinded and flushed to disk.
626
- def normalize(derivative)
627
- if derivative.is_a?(Tempfile)
628
- derivative.open
629
- elsif derivative.is_a?(File)
630
- derivative.close
631
- derivative = File.open(derivative.path)
632
- elsif derivative.is_a?(String)
633
- derivative = File.open(derivative)
634
- elsif defined?(Pathname) && derivative.is_a?(Pathname)
635
- derivative = derivative.open
636
- else
637
- fail Error, "unexpected derivation result: #{derivation.inspect} (expected File, Tempfile, String, or Pathname object)"
615
+ def normalize(file)
616
+ unless file.is_a?(File) || file.is_a?(Tempfile)
617
+ fail Error, "expected File or Tempfile object as derivation result, got #{file.inspect}"
638
618
  end
639
619
 
640
- derivative.binmode
641
- derivative
620
+ file.open if file.is_a?(Tempfile) # refresh file descriptor
621
+ file.binmode # ensure binary mode
622
+ file
642
623
  end
643
624
 
644
625
  def with_downloaded(file, &block)
645
- if file
646
- yield file
647
- else
648
- download_source(&block)
649
- end
626
+ return yield(file) if file
627
+
628
+ download_source(&block)
650
629
  end
651
630
 
652
631
  # Downloads the source uploaded file from the storage.
653
- def download_source
654
- begin
655
- file = source.download(**download_options)
656
- rescue *download_errors
657
- raise Derivation::SourceNotFound, "source file \"#{source.id}\" was not found on storage :#{source.storage_key}"
658
- end
659
-
660
- yield file
661
- ensure
662
- file.close! if file
632
+ def download_source(&block)
633
+ source.download(**download_options, &block)
634
+ rescue Shrine::FileNotFound
635
+ raise Derivation::SourceNotFound, "source file \"#{source.id}\" was not found on storage :#{source.storage_key}"
663
636
  end
664
637
 
665
638
  def derivation_block
666
639
  shrine_class.derivations[name] or fail Derivation::NotFound, "derivation #{name.inspect} is not defined"
667
640
  end
668
-
669
- def uploader
670
- source.uploader
671
- end
672
641
  end
673
642
 
674
643
  class Derivation::Upload < Derivation::Command
@@ -677,59 +646,49 @@ class Shrine
677
646
  # Uploads the derivation result to the dedicated location on the storage.
678
647
  # If a file object is given, uploads that to the storage, otherwise calls
679
648
  # the derivation block and uploads the result.
680
- def call(derivative = nil)
681
- with_derivative(derivative) do |uploadable|
682
- uploader.upload uploadable,
683
- location: upload_location,
684
- upload_options: upload_options
685
- end
686
- end
687
-
688
- private
689
-
690
- def with_derivative(derivative)
649
+ def call(derivative = nil, **options)
691
650
  if derivative
692
- begin
693
- # we want to keep the provided file open and rewinded
694
- File.open(derivative.path, binmode: true) do |file|
695
- yield file
696
- end
697
- ensure
698
- # close the file handler if the file was deleted during upload
699
- derivative.close if !File.exist?(derivative.path)
700
- end
651
+ upload(derivative, **options)
701
652
  else
702
- # generate the derivative and delete it afterwards
703
- begin
704
- file = derivation.generate
705
- yield file
706
- ensure
707
- file.close
708
- File.delete(file.path)
709
- end
653
+ upload(derivation.generate, delete: true, **options)
710
654
  end
711
655
  end
712
656
 
713
- def uploader
714
- shrine_class.new(upload_storage)
657
+ private
658
+
659
+ def upload(io, **options)
660
+ shrine_class.upload io, upload_storage,
661
+ location: upload_location,
662
+ upload_options: upload_options,
663
+ action: :derivation,
664
+ **options
715
665
  end
716
666
  end
717
667
 
718
668
  class Derivation::Retrieve < Derivation::Command
719
- delegate :upload_location, :upload_storage
669
+ delegate :upload_storage, :upload_location
720
670
 
721
- # Returns a Shrine::UploadedFile object pointing to the uploaded derivation
722
- # result it exists on the storage.
671
+ # Returns a Shrine::UploadedFile object pointing to the uploaded derivative
672
+ # if it exists on the storage.
723
673
  def call
724
- uploaded_file = shrine_class::UploadedFile.new(
725
- "storage" => upload_storage.to_s,
726
- "id" => upload_location,
727
- )
728
-
674
+ uploaded_file = shrine_class.uploaded_file(storage: upload_storage, id: upload_location)
729
675
  uploaded_file if uploaded_file.exists?
730
676
  end
731
677
  end
732
678
 
679
+ class Derivation::Opened < Derivation::Command
680
+ delegate :upload_storage, :upload_location, :upload_open_options
681
+
682
+ # Returns opened Shrine::UploadedFile object pointing to the uploaded if
683
+ # it exists on the storage.
684
+ def call
685
+ uploaded_file = shrine_class.uploaded_file(storage: upload_storage, id: upload_location)
686
+ uploaded_file.open(**upload_open_options)
687
+ uploaded_file
688
+ rescue Shrine::FileNotFound
689
+ end
690
+ end
691
+
733
692
  class Derivation::Delete < Derivation::Command
734
693
  delegate :upload_location, :upload_storage
735
694
 
@@ -784,7 +743,7 @@ class Shrine
784
743
  def verify_signature(string, signature)
785
744
  if signature.nil?
786
745
  fail InvalidSignature, "missing \"signature\" param"
787
- elsif signature != generate_signature(string)
746
+ elsif !Rack::Utils.secure_compare(signature, generate_signature(string))
788
747
  fail InvalidSignature, "provided signature does not match the calculated signature"
789
748
  end
790
749
  end