shrine 3.5.0 → 3.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/doc/changing_derivatives.md +2 -1
- data/doc/changing_location.md +17 -5
- data/doc/getting_started.md +4 -2
- data/doc/plugins/derivation_endpoint.md +2 -1
- data/doc/plugins/derivatives.md +2 -1
- data/doc/plugins/download_endpoint.md +16 -4
- data/doc/plugins/refresh_metadata.md +20 -0
- data/doc/plugins/signature.md +8 -6
- data/doc/processing.md +5 -3
- data/doc/release_notes/3.6.0.md +23 -0
- data/doc/release_notes/3.7.0.md +75 -0
- data/doc/storage/s3.md +10 -0
- data/lib/shrine/attacher.rb +28 -21
- data/lib/shrine/attachment.rb +2 -2
- data/lib/shrine/plugins/_urlsafe_serialization.rb +4 -4
- data/lib/shrine/plugins/add_metadata.rb +2 -4
- data/lib/shrine/plugins/atomic_helpers.rb +7 -7
- data/lib/shrine/plugins/backgrounding.rb +9 -9
- data/lib/shrine/plugins/column.rb +6 -4
- data/lib/shrine/plugins/default_url.rb +4 -4
- data/lib/shrine/plugins/delete_raw.rb +2 -2
- data/lib/shrine/plugins/derivation_endpoint.rb +37 -34
- data/lib/shrine/plugins/derivatives.rb +5 -1
- data/lib/shrine/plugins/download_endpoint.rb +65 -11
- data/lib/shrine/plugins/entity.rb +7 -7
- data/lib/shrine/plugins/infer_extension.rb +1 -1
- data/lib/shrine/plugins/instrumentation.rb +8 -8
- data/lib/shrine/plugins/mirroring.rb +10 -10
- data/lib/shrine/plugins/model.rb +9 -9
- data/lib/shrine/plugins/presign_endpoint.rb +13 -10
- data/lib/shrine/plugins/pretty_location.rb +2 -2
- data/lib/shrine/plugins/processing.rb +3 -3
- data/lib/shrine/plugins/rack_file.rb +2 -2
- data/lib/shrine/plugins/rack_response.rb +10 -4
- data/lib/shrine/plugins/refresh_metadata.rb +6 -6
- data/lib/shrine/plugins/remote_url.rb +3 -3
- data/lib/shrine/plugins/restore_cached_data.rb +3 -3
- data/lib/shrine/plugins/signature.rb +2 -2
- data/lib/shrine/plugins/store_dimensions.rb +2 -2
- data/lib/shrine/plugins/upload_endpoint.rb +7 -5
- data/lib/shrine/plugins/upload_options.rb +1 -1
- data/lib/shrine/plugins/validation.rb +8 -8
- data/lib/shrine/plugins/versions.rb +10 -10
- data/lib/shrine/plugins.rb +6 -14
- data/lib/shrine/storage/file_system.rb +4 -17
- data/lib/shrine/storage/linter.rb +8 -8
- data/lib/shrine/storage/memory.rb +1 -3
- data/lib/shrine/storage/s3.rb +53 -38
- data/lib/shrine/uploaded_file.rb +21 -18
- data/lib/shrine/version.rb +1 -1
- data/lib/shrine.rb +18 -18
- data/shrine.gemspec +8 -8
- metadata +31 -26
|
@@ -67,19 +67,19 @@ class Shrine
|
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
# Does a background promote if promote block was registered.
|
|
70
|
-
def promote_cached(**
|
|
70
|
+
def promote_cached(**)
|
|
71
71
|
if promote? && promote_block
|
|
72
|
-
promote_background
|
|
72
|
+
promote_background(**)
|
|
73
73
|
else
|
|
74
74
|
super
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# Calls the registered promote block.
|
|
79
|
-
def promote_background(**
|
|
79
|
+
def promote_background(**)
|
|
80
80
|
fail Error, "promote block is not registered" unless promote_block
|
|
81
81
|
|
|
82
|
-
background_block(promote_block, **
|
|
82
|
+
background_block(promote_block, **)
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
# Does a background destroy if destroy block was registered.
|
|
@@ -92,19 +92,19 @@ class Shrine
|
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
# Calls the registered destroy block.
|
|
95
|
-
def destroy_background(**
|
|
95
|
+
def destroy_background(**)
|
|
96
96
|
fail Error, "destroy block is not registered" unless destroy_block
|
|
97
97
|
|
|
98
|
-
background_block(destroy_block, **
|
|
98
|
+
background_block(destroy_block, **)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
private
|
|
102
102
|
|
|
103
|
-
def background_block(block, **
|
|
103
|
+
def background_block(block, **)
|
|
104
104
|
if block.arity == 1
|
|
105
|
-
block.call(self, **
|
|
105
|
+
block.call(self, **)
|
|
106
106
|
else
|
|
107
|
-
instance_exec(
|
|
107
|
+
instance_exec(**, &block)
|
|
108
108
|
end
|
|
109
109
|
end
|
|
110
110
|
end
|
|
@@ -16,8 +16,8 @@ class Shrine
|
|
|
16
16
|
# from a database record column.
|
|
17
17
|
#
|
|
18
18
|
# Attacher.from_column('{"id":"...","storage":"...","metadata":{...}}')
|
|
19
|
-
def from_column(data, **
|
|
20
|
-
attacher = new(**
|
|
19
|
+
def from_column(data, **)
|
|
20
|
+
attacher = new(**)
|
|
21
21
|
attacher.load_column(data)
|
|
22
22
|
attacher
|
|
23
23
|
end
|
|
@@ -28,8 +28,8 @@ class Shrine
|
|
|
28
28
|
attr_reader :column_serializer
|
|
29
29
|
|
|
30
30
|
# Allows overriding the default column serializer.
|
|
31
|
-
def initialize(column_serializer: shrine_class.opts[:column][:serializer], **
|
|
32
|
-
super(**
|
|
31
|
+
def initialize(column_serializer: shrine_class.opts[:column][:serializer], **)
|
|
32
|
+
super(**)
|
|
33
33
|
@column_serializer = column_serializer
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -75,6 +75,8 @@ class Shrine
|
|
|
75
75
|
# Attacher.deserialize_column(nil)
|
|
76
76
|
# #=> nil
|
|
77
77
|
def deserialize_column(data)
|
|
78
|
+
return nil if data == ""
|
|
79
|
+
|
|
78
80
|
if column_serializer && data && !data.is_a?(Hash)
|
|
79
81
|
column_serializer.load(data)
|
|
80
82
|
else
|
|
@@ -16,16 +16,16 @@ class Shrine
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
module AttacherMethods
|
|
19
|
-
def url(**
|
|
20
|
-
super || default_url(**
|
|
19
|
+
def url(**)
|
|
20
|
+
super || default_url(**)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
|
-
def default_url(**
|
|
25
|
+
def default_url(**)
|
|
26
26
|
return unless default_url_block
|
|
27
27
|
|
|
28
|
-
url = instance_exec(
|
|
28
|
+
url = instance_exec(**, &default_url_block)
|
|
29
29
|
|
|
30
30
|
[*default_url_host, url].join
|
|
31
31
|
end
|
|
@@ -15,8 +15,8 @@ class Shrine
|
|
|
15
15
|
private
|
|
16
16
|
|
|
17
17
|
# Deletes the file that was uploaded, unless it's an UploadedFile.
|
|
18
|
-
def _upload(io, delete: delete_raw?, **
|
|
19
|
-
super(io, delete
|
|
18
|
+
def _upload(io, delete: delete_raw?, **)
|
|
19
|
+
super(io, delete:, **)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def delete_raw?
|
|
@@ -48,7 +48,7 @@ class Shrine
|
|
|
48
48
|
# It uses a trick where it removes the derivation path prefix from the
|
|
49
49
|
# path info before calling the Rack app, which is what web framework
|
|
50
50
|
# routers do before they're calling a mounted Rack app.
|
|
51
|
-
def derivation_response(env, **
|
|
51
|
+
def derivation_response(env, **)
|
|
52
52
|
script_name = env["SCRIPT_NAME"]
|
|
53
53
|
path_info = env["PATH_INFO"]
|
|
54
54
|
|
|
@@ -61,7 +61,7 @@ class Shrine
|
|
|
61
61
|
env["SCRIPT_NAME"] += match.to_s
|
|
62
62
|
env["PATH_INFO"] = match.post_match
|
|
63
63
|
|
|
64
|
-
derivation_endpoint(**
|
|
64
|
+
derivation_endpoint(**).call(env)
|
|
65
65
|
ensure
|
|
66
66
|
env["SCRIPT_NAME"] = script_name
|
|
67
67
|
env["PATH_INFO"] = path_info
|
|
@@ -87,15 +87,15 @@ class Shrine
|
|
|
87
87
|
# Generates a URL to a derivation with the receiver as the source file.
|
|
88
88
|
# Any arguments provided will be included in the URL and passed to the
|
|
89
89
|
# derivation block. Accepts additional URL options.
|
|
90
|
-
def derivation_url(name,
|
|
91
|
-
derivation(name, *
|
|
90
|
+
def derivation_url(name, *, **)
|
|
91
|
+
derivation(name, *).url(**)
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
# Calls the specified derivation with the receiver as the source file,
|
|
95
95
|
# returning a Rack response triple. The derivation endpoint ultimately
|
|
96
96
|
# calls this method.
|
|
97
|
-
def derivation_response(name,
|
|
98
|
-
derivation(name,
|
|
97
|
+
def derivation_response(name, *, env:, **)
|
|
98
|
+
derivation(name, *, **).response(env)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
# Returns a Shrine::Derivation object created from the provided
|
|
@@ -129,14 +129,14 @@ class Shrine
|
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
# Returns an URL to the derivation.
|
|
132
|
-
def url(**
|
|
132
|
+
def url(**)
|
|
133
133
|
Derivation::Url.new(self).call(
|
|
134
134
|
host: option(:host),
|
|
135
135
|
prefix: option(:prefix),
|
|
136
136
|
expires_in: option(:expires_in),
|
|
137
137
|
version: option(:version),
|
|
138
138
|
metadata: option(:metadata),
|
|
139
|
-
|
|
139
|
+
**,
|
|
140
140
|
)
|
|
141
141
|
end
|
|
142
142
|
|
|
@@ -158,8 +158,8 @@ class Shrine
|
|
|
158
158
|
|
|
159
159
|
# Uploads the derivation result to a dedicated destination on the specified
|
|
160
160
|
# Shrine storage.
|
|
161
|
-
def upload(file = nil, **
|
|
162
|
-
Derivation::Upload.new(self).call(file, **
|
|
161
|
+
def upload(file = nil, **)
|
|
162
|
+
Derivation::Upload.new(self).call(file, **)
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
# Returns a Shrine::UploadedFile object pointing to the uploaded derivative
|
|
@@ -184,7 +184,7 @@ class Shrine
|
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
def self.option(name, default: nil, result: nil)
|
|
187
|
-
options[name] = { default
|
|
187
|
+
options[name] = { default:, result: }
|
|
188
188
|
end
|
|
189
189
|
|
|
190
190
|
option :cache_control, default: -> { default_cache_control }
|
|
@@ -303,15 +303,15 @@ class Shrine
|
|
|
303
303
|
class Derivation::Url < Derivation::Command
|
|
304
304
|
delegate :name, :args, :source, :secret_key, :signer
|
|
305
305
|
|
|
306
|
-
def call(host: nil, prefix: nil, **
|
|
306
|
+
def call(host: nil, prefix: nil, metadata: [], **)
|
|
307
307
|
base_url = [host, *prefix].join("/")
|
|
308
|
-
path = path_identifier(metadata:
|
|
308
|
+
path = path_identifier(metadata:)
|
|
309
309
|
|
|
310
310
|
if signer
|
|
311
311
|
url = [base_url, path].join("/")
|
|
312
|
-
signer.call(url, **
|
|
312
|
+
signer.call(url, **)
|
|
313
313
|
else
|
|
314
|
-
signed_part = signed_url("#{path}?#{query(**
|
|
314
|
+
signed_part = signed_url("#{path}?#{query(**)}")
|
|
315
315
|
[base_url, signed_part].join("/")
|
|
316
316
|
end
|
|
317
317
|
end
|
|
@@ -322,7 +322,7 @@ class Shrine
|
|
|
322
322
|
[
|
|
323
323
|
name,
|
|
324
324
|
*args,
|
|
325
|
-
source.urlsafe_dump(metadata:
|
|
325
|
+
source.urlsafe_dump(metadata:)
|
|
326
326
|
].map{|component| Rack::Utils.escape_path(component.to_s)}.join('/')
|
|
327
327
|
end
|
|
328
328
|
|
|
@@ -365,7 +365,9 @@ class Shrine
|
|
|
365
365
|
handle_request(request)
|
|
366
366
|
end
|
|
367
367
|
|
|
368
|
-
headers[
|
|
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
|
|
@@ -481,19 +483,18 @@ class Shrine
|
|
|
481
483
|
# `Content-Type` and `Content-Disposition` response headers from derivation
|
|
482
484
|
# options and file extension of the derivation result.
|
|
483
485
|
def file_response(file, env)
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
status = response[0]
|
|
486
|
+
status, headers, body = rack_file_response(file.path, env)
|
|
487
487
|
|
|
488
|
+
headers = Rack::Headers[headers] if Rack.release >= "3"
|
|
488
489
|
headers = {
|
|
489
|
-
"Content-Type" => type ||
|
|
490
|
-
"Content-Length" =>
|
|
490
|
+
"Content-Type" => type || headers["Content-Type"],
|
|
491
|
+
"Content-Length" => headers["Content-Length"],
|
|
491
492
|
"Content-Disposition" => content_disposition(file),
|
|
492
|
-
"Content-Range" =>
|
|
493
|
+
"Content-Range" => headers["Content-Range"],
|
|
493
494
|
"Accept-Ranges" => "bytes",
|
|
494
495
|
}.compact
|
|
495
496
|
|
|
496
|
-
body = Rack::BodyProxy.new(
|
|
497
|
+
body = Rack::BodyProxy.new(body) { File.delete(file.path) }
|
|
497
498
|
|
|
498
499
|
file.close
|
|
499
500
|
|
|
@@ -514,8 +515,10 @@ class Shrine
|
|
|
514
515
|
|
|
515
516
|
if upload_redirect
|
|
516
517
|
redirect_url = uploaded_file.url(**upload_redirect_url_options)
|
|
518
|
+
headers = { "Location" => redirect_url }
|
|
519
|
+
headers = Rack::Headers[headers] if Rack.release >= "3"
|
|
517
520
|
|
|
518
|
-
[302,
|
|
521
|
+
[302, headers, []]
|
|
519
522
|
else
|
|
520
523
|
if derivative && File.exist?(derivative.path)
|
|
521
524
|
file_response(derivative, env)
|
|
@@ -555,7 +558,7 @@ class Shrine
|
|
|
555
558
|
filename = self.filename
|
|
556
559
|
filename += File.extname(file.path) if File.extname(filename).empty?
|
|
557
560
|
|
|
558
|
-
ContentDisposition.format(disposition
|
|
561
|
+
ContentDisposition.format(disposition:, filename:)
|
|
559
562
|
end
|
|
560
563
|
end
|
|
561
564
|
|
|
@@ -594,9 +597,9 @@ class Shrine
|
|
|
594
597
|
end
|
|
595
598
|
|
|
596
599
|
# Calls the derivation block.
|
|
597
|
-
def derive(*
|
|
600
|
+
def derive(*)
|
|
598
601
|
instrument_derivation do
|
|
599
|
-
derivation.instance_exec(
|
|
602
|
+
derivation.instance_exec(*, &derivation_block)
|
|
600
603
|
end
|
|
601
604
|
end
|
|
602
605
|
|
|
@@ -604,7 +607,7 @@ class Shrine
|
|
|
604
607
|
def instrument_derivation(&block)
|
|
605
608
|
return yield unless shrine_class.respond_to?(:instrument)
|
|
606
609
|
|
|
607
|
-
shrine_class.instrument(:derivation, { derivation:
|
|
610
|
+
shrine_class.instrument(:derivation, { derivation: }, &block)
|
|
608
611
|
end
|
|
609
612
|
|
|
610
613
|
# Massages the derivation result, ensuring it's opened in binary mode,
|
|
@@ -643,22 +646,22 @@ class Shrine
|
|
|
643
646
|
# Uploads the derivation result to the dedicated location on the storage.
|
|
644
647
|
# If a file object is given, uploads that to the storage, otherwise calls
|
|
645
648
|
# the derivation block and uploads the result.
|
|
646
|
-
def call(derivative = nil, **
|
|
649
|
+
def call(derivative = nil, **)
|
|
647
650
|
if derivative
|
|
648
|
-
upload(derivative, **
|
|
651
|
+
upload(derivative, **)
|
|
649
652
|
else
|
|
650
|
-
upload(derivation.generate, delete: true, **
|
|
653
|
+
upload(derivation.generate, delete: true, **)
|
|
651
654
|
end
|
|
652
655
|
end
|
|
653
656
|
|
|
654
657
|
private
|
|
655
658
|
|
|
656
|
-
def upload(io, **
|
|
659
|
+
def upload(io, **)
|
|
657
660
|
shrine_class.upload io, upload_storage,
|
|
658
661
|
location: upload_location,
|
|
659
662
|
upload_options: upload_options,
|
|
660
663
|
action: :derivation,
|
|
661
|
-
**
|
|
664
|
+
**
|
|
662
665
|
end
|
|
663
666
|
end
|
|
664
667
|
|
|
@@ -459,9 +459,13 @@ class Shrine
|
|
|
459
459
|
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
|
460
460
|
# attacher.change(file)
|
|
461
461
|
# attacher.derivatives #=> {}
|
|
462
|
+
#
|
|
463
|
+
# # With keep_derivatives: true
|
|
464
|
+
# attacher.change(file)
|
|
465
|
+
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
|
462
466
|
def change(*)
|
|
463
467
|
result = super
|
|
464
|
-
set_derivatives({})
|
|
468
|
+
set_derivatives({}) unless shrine_class.derivatives_options[:keep_derivatives]
|
|
465
469
|
result
|
|
466
470
|
end
|
|
467
471
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'base64'
|
|
5
|
+
|
|
3
6
|
class Shrine
|
|
4
7
|
module Plugins
|
|
5
8
|
# Documentation can be found on https://shrinerb.com/docs/plugins/download_endpoint
|
|
@@ -16,11 +19,11 @@ class Shrine
|
|
|
16
19
|
|
|
17
20
|
module ClassMethods
|
|
18
21
|
# Returns the Rack application that retrieves requested files.
|
|
19
|
-
def download_endpoint(**
|
|
22
|
+
def download_endpoint(**)
|
|
20
23
|
Shrine::DownloadEndpoint.new(
|
|
21
24
|
shrine_class: self,
|
|
22
25
|
**opts[:download_endpoint],
|
|
23
|
-
|
|
26
|
+
**,
|
|
24
27
|
)
|
|
25
28
|
end
|
|
26
29
|
|
|
@@ -30,7 +33,7 @@ class Shrine
|
|
|
30
33
|
# It uses a trick where it removes the download path prefix from the
|
|
31
34
|
# path info before calling the Rack app, which is what web framework
|
|
32
35
|
# routers do before they're calling a mounted Rack app.
|
|
33
|
-
def download_response(env, **
|
|
36
|
+
def download_response(env, **)
|
|
34
37
|
script_name = env["SCRIPT_NAME"]
|
|
35
38
|
path_info = env["PATH_INFO"]
|
|
36
39
|
|
|
@@ -43,7 +46,7 @@ class Shrine
|
|
|
43
46
|
env["SCRIPT_NAME"] += match.to_s
|
|
44
47
|
env["PATH_INFO"] = match.post_match
|
|
45
48
|
|
|
46
|
-
download_endpoint(**
|
|
49
|
+
download_endpoint(**).call(env)
|
|
47
50
|
ensure
|
|
48
51
|
env["SCRIPT_NAME"] = script_name
|
|
49
52
|
env["PATH_INFO"] = path_info
|
|
@@ -53,8 +56,8 @@ class Shrine
|
|
|
53
56
|
|
|
54
57
|
module FileMethods
|
|
55
58
|
# Returns file URL on the download endpoint.
|
|
56
|
-
def download_url(**
|
|
57
|
-
FileUrl.new(self).call(**
|
|
59
|
+
def download_url(**)
|
|
60
|
+
FileUrl.new(self).call(**)
|
|
58
61
|
end
|
|
59
62
|
end
|
|
60
63
|
|
|
@@ -65,14 +68,36 @@ class Shrine
|
|
|
65
68
|
@file = file
|
|
66
69
|
end
|
|
67
70
|
|
|
68
|
-
def call(host: self.host)
|
|
69
|
-
[
|
|
71
|
+
def call(host: self.host, expires_in: nil)
|
|
72
|
+
path = file.urlsafe_dump(metadata: %w[filename size mime_type])
|
|
73
|
+
|
|
74
|
+
query = signature_as_query(path: path, expires_in: expires_in)
|
|
75
|
+
|
|
76
|
+
path = [host, *prefix, path].join("/")
|
|
77
|
+
path += "?#{query}" if query
|
|
78
|
+
path
|
|
70
79
|
end
|
|
71
80
|
|
|
72
81
|
protected
|
|
73
82
|
|
|
74
|
-
def path
|
|
75
|
-
|
|
83
|
+
def signature_as_query(path:, expires_in:)
|
|
84
|
+
expires_in = default_expires_in if expires_in.nil?
|
|
85
|
+
raise(Error, "secret_key is required for expiring URLs") if !secret_key && expires_in
|
|
86
|
+
raise(Error, "expires_in is required for expiring URLs") if secret_key && !expires_in
|
|
87
|
+
|
|
88
|
+
return nil unless expires_in
|
|
89
|
+
|
|
90
|
+
expires_at = (Time.now + expires_in).to_i
|
|
91
|
+
signature = OpenSSL::HMAC.digest(
|
|
92
|
+
OpenSSL::Digest::SHA256.new,
|
|
93
|
+
secret_key,
|
|
94
|
+
"#{path}--#{expires_at}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
Rack::Utils.build_query(
|
|
98
|
+
signature: Base64.urlsafe_encode64(signature),
|
|
99
|
+
expires_at: expires_at
|
|
100
|
+
)
|
|
76
101
|
end
|
|
77
102
|
|
|
78
103
|
def host
|
|
@@ -83,6 +108,14 @@ class Shrine
|
|
|
83
108
|
options[:prefix]
|
|
84
109
|
end
|
|
85
110
|
|
|
111
|
+
def default_expires_in
|
|
112
|
+
options[:expires_in]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def secret_key
|
|
116
|
+
options[:secret_key]
|
|
117
|
+
end
|
|
118
|
+
|
|
86
119
|
def options
|
|
87
120
|
file.shrine_class.opts[:download_endpoint]
|
|
88
121
|
end
|
|
@@ -113,7 +146,9 @@ class Shrine
|
|
|
113
146
|
handle_request(request)
|
|
114
147
|
end
|
|
115
148
|
|
|
116
|
-
headers[
|
|
149
|
+
headers = Rack::Headers[headers] if Rack.release >= "3"
|
|
150
|
+
headers["Content-Length"] ||= body.respond_to?(:bytesize) ? body.bytesize.to_s :
|
|
151
|
+
body.map(&:bytesize).inject(0, :+).to_s
|
|
117
152
|
|
|
118
153
|
[status, headers, body]
|
|
119
154
|
end
|
|
@@ -127,6 +162,9 @@ class Shrine
|
|
|
127
162
|
|
|
128
163
|
def handle_request(request)
|
|
129
164
|
_, serialized, * = request.path_info.split("/")
|
|
165
|
+
signature, expires_at = request.params.values_at("signature", "expires_at")
|
|
166
|
+
|
|
167
|
+
check_signature!(serialized, signature, expires_at) if @secret_key
|
|
130
168
|
|
|
131
169
|
uploaded_file = get_uploaded_file(serialized)
|
|
132
170
|
|
|
@@ -187,6 +225,22 @@ class Shrine
|
|
|
187
225
|
bad_request!("Invalid serialized file")
|
|
188
226
|
end
|
|
189
227
|
|
|
228
|
+
def check_signature!(serialized, signature, expires_at)
|
|
229
|
+
if expires_at && expires_at.to_i < Time.now.to_i
|
|
230
|
+
error!(400, "URL has expired")
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
calculated_signature = OpenSSL::HMAC.digest(
|
|
234
|
+
OpenSSL::Digest::SHA256.new,
|
|
235
|
+
@secret_key,
|
|
236
|
+
"#{serialized}--#{expires_at}"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if !Rack::Utils.secure_compare(signature, Base64.urlsafe_encode64(calculated_signature))
|
|
240
|
+
error!(403, "Signature does not match")
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
190
244
|
def not_found!
|
|
191
245
|
error!(404, "File Not Found")
|
|
192
246
|
end
|
|
@@ -10,7 +10,7 @@ class Shrine
|
|
|
10
10
|
|
|
11
11
|
module AttachmentMethods
|
|
12
12
|
# Defines instance methods on initialization.
|
|
13
|
-
def initialize(name, **
|
|
13
|
+
def initialize(name, **)
|
|
14
14
|
super
|
|
15
15
|
|
|
16
16
|
define_entity_methods(name)
|
|
@@ -59,16 +59,16 @@ class Shrine
|
|
|
59
59
|
|
|
60
60
|
# Returns the class attacher instance with loaded entity. It's not
|
|
61
61
|
# memoized because the entity object could be frozen.
|
|
62
|
-
def attacher(record, **
|
|
63
|
-
attacher = class_attacher(**
|
|
62
|
+
def attacher(record, **)
|
|
63
|
+
attacher = class_attacher(**)
|
|
64
64
|
attacher.load_entity(record, @name)
|
|
65
65
|
attacher
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
# Creates an instance of the corresponding attacher class with set
|
|
69
69
|
# name.
|
|
70
|
-
def class_attacher(**
|
|
71
|
-
attacher = shrine_class::Attacher.new(**@options, **
|
|
70
|
+
def class_attacher(**)
|
|
71
|
+
attacher = shrine_class::Attacher.new(**@options, **)
|
|
72
72
|
attacher.instance_variable_set(:@name, @name)
|
|
73
73
|
attacher
|
|
74
74
|
end
|
|
@@ -81,8 +81,8 @@ class Shrine
|
|
|
81
81
|
#
|
|
82
82
|
# attacher = Attacher.from_entity(photo, :image)
|
|
83
83
|
# attacher.file #=> #<Shrine::UploadedFile>
|
|
84
|
-
def from_entity(record, name, **
|
|
85
|
-
attacher = new(**
|
|
84
|
+
def from_entity(record, name, **)
|
|
85
|
+
attacher = new(**)
|
|
86
86
|
attacher.load_entity(record, name)
|
|
87
87
|
attacher
|
|
88
88
|
end
|
|
@@ -169,14 +169,14 @@ class Shrine
|
|
|
169
169
|
end
|
|
170
170
|
end
|
|
171
171
|
|
|
172
|
-
def library_send(method_name,
|
|
172
|
+
def library_send(method_name, ...)
|
|
173
173
|
case notifications.to_s
|
|
174
174
|
when /Dry::Monitor::Notifications/
|
|
175
|
-
send(:"dry_monitor_#{method_name}",
|
|
175
|
+
send(:"dry_monitor_#{method_name}", ...)
|
|
176
176
|
when /ActiveSupport::Notifications/
|
|
177
|
-
send(:"active_support_#{method_name}",
|
|
177
|
+
send(:"active_support_#{method_name}", ...)
|
|
178
178
|
else
|
|
179
|
-
notifications.send(method_name,
|
|
179
|
+
notifications.send(method_name, ...)
|
|
180
180
|
end
|
|
181
181
|
end
|
|
182
182
|
end
|
|
@@ -224,14 +224,14 @@ class Shrine
|
|
|
224
224
|
event.duration.to_i
|
|
225
225
|
end
|
|
226
226
|
|
|
227
|
-
def library_send(method_name,
|
|
227
|
+
def library_send(method_name, ...)
|
|
228
228
|
case event.class.name
|
|
229
229
|
when "ActiveSupport::Notifications::Event"
|
|
230
|
-
send(:"active_support_#{method_name}",
|
|
230
|
+
send(:"active_support_#{method_name}", ...)
|
|
231
231
|
when "Dry::Events::Event"
|
|
232
|
-
send(:"dry_events_#{method_name}",
|
|
232
|
+
send(:"dry_events_#{method_name}", ...)
|
|
233
233
|
else
|
|
234
|
-
event.send(method_name,
|
|
234
|
+
event.send(method_name, ...)
|
|
235
235
|
end
|
|
236
236
|
end
|
|
237
237
|
end
|
|
@@ -51,9 +51,9 @@ class Shrine
|
|
|
51
51
|
|
|
52
52
|
module InstanceMethods
|
|
53
53
|
# Mirrors upload to other mirror storages.
|
|
54
|
-
def upload(io, mirror: true, **
|
|
55
|
-
file = super(io, **
|
|
56
|
-
file.trigger_mirror_upload(**
|
|
54
|
+
def upload(io, mirror: true, **)
|
|
55
|
+
file = super(io, **)
|
|
56
|
+
file.trigger_mirror_upload(**) if mirror
|
|
57
57
|
file
|
|
58
58
|
end
|
|
59
59
|
end
|
|
@@ -61,31 +61,31 @@ class Shrine
|
|
|
61
61
|
module FileMethods
|
|
62
62
|
# Mirrors upload if mirrors are defined. Calls mirror block if
|
|
63
63
|
# registered, otherwise mirrors synchronously.
|
|
64
|
-
def trigger_mirror_upload(**
|
|
64
|
+
def trigger_mirror_upload(**)
|
|
65
65
|
return unless shrine_class.mirrors[storage_key] && shrine_class.mirror_upload?
|
|
66
66
|
|
|
67
67
|
if shrine_class.mirror_upload_block
|
|
68
|
-
mirror_upload_background(**
|
|
68
|
+
mirror_upload_background(**)
|
|
69
69
|
else
|
|
70
|
-
mirror_upload(**
|
|
70
|
+
mirror_upload(**)
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
# Calls mirror upload block.
|
|
75
|
-
def mirror_upload_background(**
|
|
75
|
+
def mirror_upload_background(**)
|
|
76
76
|
fail Error, "mirror upload block is not registered" unless shrine_class.mirror_upload_block
|
|
77
77
|
|
|
78
|
-
shrine_class.mirror_upload_block.call(self, **
|
|
78
|
+
shrine_class.mirror_upload_block.call(self, **)
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
# Uploads the file to each mirror storage.
|
|
82
|
-
def mirror_upload(**
|
|
82
|
+
def mirror_upload(**)
|
|
83
83
|
previously_opened = opened?
|
|
84
84
|
|
|
85
85
|
each_mirror do |mirror|
|
|
86
86
|
rewind if opened?
|
|
87
87
|
|
|
88
|
-
shrine_class.upload(self, mirror,
|
|
88
|
+
shrine_class.upload(self, mirror, **, location: id, close: false, action: :mirror)
|
|
89
89
|
end
|
|
90
90
|
ensure
|
|
91
91
|
if opened? && !previously_opened
|
data/lib/shrine/plugins/model.rb
CHANGED
|
@@ -18,8 +18,8 @@ class Shrine
|
|
|
18
18
|
#
|
|
19
19
|
# Shrine::Attachment(:image) # model (default)
|
|
20
20
|
# Shrine::Attachment(:image, model: false) # entity
|
|
21
|
-
def initialize(name, model: true, **
|
|
22
|
-
super(name, **
|
|
21
|
+
def initialize(name, model: true, **)
|
|
22
|
+
super(name, **)
|
|
23
23
|
@model = model
|
|
24
24
|
end
|
|
25
25
|
|
|
@@ -82,16 +82,16 @@ class Shrine
|
|
|
82
82
|
#
|
|
83
83
|
# attacher = Attacher.from_model(photo, :image)
|
|
84
84
|
# attacher.file #=> #<Shrine::UploadedFile>
|
|
85
|
-
def from_model(record, name, **
|
|
86
|
-
attacher = new(**
|
|
85
|
+
def from_model(record, name, **)
|
|
86
|
+
attacher = new(**)
|
|
87
87
|
attacher.load_model(record, name)
|
|
88
88
|
attacher
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
module AttacherMethods
|
|
93
|
-
def initialize(model_cache: shrine_class.opts[:model][:cache], **
|
|
94
|
-
super(**
|
|
93
|
+
def initialize(model_cache: shrine_class.opts[:model][:cache], **)
|
|
94
|
+
super(**)
|
|
95
95
|
@model_cache = model_cache
|
|
96
96
|
@model = nil
|
|
97
97
|
end
|
|
@@ -111,11 +111,11 @@ class Shrine
|
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
# Called by the attachment attribute setter on the model.
|
|
114
|
-
def model_assign(value, **
|
|
114
|
+
def model_assign(value, **)
|
|
115
115
|
if model_cache?
|
|
116
|
-
assign(value, **
|
|
116
|
+
assign(value, **)
|
|
117
117
|
else
|
|
118
|
-
attach(value, **
|
|
118
|
+
attach(value, **)
|
|
119
119
|
end
|
|
120
120
|
end
|
|
121
121
|
|