shrine 3.9.0 → 3.10.1
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 +12 -0
- data/doc/advantages.md +11 -11
- data/doc/design.md +1 -1
- data/doc/direct_s3.md +19 -9
- data/doc/external/extensions.md +1 -1
- data/doc/getting_started.md +4 -4
- data/doc/plugins/data_uri.md +15 -0
- data/doc/plugins/fallback_storage.md +90 -0
- data/doc/release_notes/3.10.0.md +30 -0
- data/doc/release_notes/3.10.1.md +15 -0
- data/lib/shrine/plugins/data_uri.rb +27 -2
- data/lib/shrine/plugins/fallback_storage.rb +51 -0
- data/lib/shrine/plugins/model.rb +5 -2
- data/lib/shrine/version.rb +2 -2
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '078c4711b7bd211c577666899edb233967df970a48b96d3b6239189f9c516586'
|
|
4
|
+
data.tar.gz: a46ed9608642d54c84fd80cc106e56655b4bff7439ff3cf2627bd1c199b40a15
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9526af08df019ad15b330e804d2136d0a2f876657b55018b3498ec5fd9c75b881ee1d7559fb4bd862512975d0a7439c4991d11dedf61b9c2bcc6d8c1c3ef38b6
|
|
7
|
+
data.tar.gz: af84847b2664f400250142bda5995b31d9303de422d5e73d75593ec2d9911f1cdc9e6dc27f10a3f97ab97aaefcb9ebf3f5754a35a6220501af2230dc7f7dcd6b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 3.10.1 (2026-10-10)
|
|
2
|
+
|
|
3
|
+
* `model` – Make `Attacher#write` a no-op when the attacher isn't backed by a model, fixing `remove_invalid` raising `NoMethodError` on attachers without a record (@janko)
|
|
4
|
+
|
|
5
|
+
## 3.10.0 (2026-09-20)
|
|
6
|
+
|
|
7
|
+
* Add `fallback_storage` plugin for reading files from a secondary storage when they're missing in the primary storage (@yegorov, @janko)
|
|
8
|
+
|
|
9
|
+
* `data_uri` – Add `:max_size` option for limiting the size of the data URI content, checked before the content is decoded (@jalrax, @janko)
|
|
10
|
+
|
|
11
|
+
* `data_uri` – Pass the error to the `:error_message` proc as an optional second argument (@jalrax, @janko)
|
|
12
|
+
|
|
1
13
|
## 3.9.0 (2026-07-13)
|
|
2
14
|
|
|
3
15
|
* `Shrine.find_storage` now raises `Shrine::MissingStorage` (subclass of `Shrine::Error`) when the storage isn't registered, so it can be rescued separately (@janko)
|
data/doc/advantages.md
CHANGED
|
@@ -389,19 +389,19 @@ and Active Storage.
|
|
|
389
389
|
|
|
390
390
|
Uppy supports direct uploads to [AWS S3][Uppy AwsS3] or to a [custom
|
|
391
391
|
endpoint][Uppy XHRUpload]. It also supports **resumable** uploads, either
|
|
392
|
-
[directly to S3][Uppy
|
|
392
|
+
[directly to S3][Uppy AwsS3 multipart] or via the [tus protocol][tus]. For the
|
|
393
393
|
UI you can choose from various components, ranging from a simple [status
|
|
394
394
|
bar][Uppy StatusBar] to a full-featured [dashboard][Uppy Dashboard].
|
|
395
395
|
|
|
396
396
|
Shrine provides server side components for each type of upload. They are built
|
|
397
397
|
on top of Rack, so that they can be used with any Ruby web framework.
|
|
398
398
|
|
|
399
|
-
| Uppy
|
|
400
|
-
| :---
|
|
401
|
-
| [XHRUpload][Uppy XHRUpload]
|
|
402
|
-
| [AwsS3][Uppy AwsS3]
|
|
403
|
-
| [
|
|
404
|
-
| [Tus][Uppy Tus]
|
|
399
|
+
| Uppy | Shrine |
|
|
400
|
+
| :--- | :----- |
|
|
401
|
+
| [XHRUpload][Uppy XHRUpload] | [`upload_endpoint`][upload_endpoint] |
|
|
402
|
+
| [AwsS3][Uppy AwsS3] | [`presign_endpoint`][presign_endpoint] |
|
|
403
|
+
| [AwsS3 (multipart)][Uppy AwsS3 multipart] | [`uppy-s3_multipart`][uppy-s3_multipart] |
|
|
404
|
+
| [Tus][Uppy Tus] | [`tus-ruby-server`][tus-ruby-server] |
|
|
405
405
|
|
|
406
406
|
[Rack]: https://rack.github.io
|
|
407
407
|
[Sinatra]: http://sinatrarb.com
|
|
@@ -412,7 +412,7 @@ on top of Rack, so that they can be used with any Ruby web framework.
|
|
|
412
412
|
[Sequel]: http://sequel.jeremyevans.net
|
|
413
413
|
[ROM]: http://rom-rb.org
|
|
414
414
|
[Hanami::Model]: https://github.com/hanami/model
|
|
415
|
-
[plugin system]: https://
|
|
415
|
+
[plugin system]: https://janko.io/the-plugin-system-of-sequel-and-roda/
|
|
416
416
|
[Down]: https://github.com/janko/down
|
|
417
417
|
[ContentDisposition]: https://github.com/shrinerb/content_disposition
|
|
418
418
|
[`file`]: http://linux.die.net/man/1/file
|
|
@@ -440,10 +440,10 @@ on top of Rack, so that they can be used with any Ruby web framework.
|
|
|
440
440
|
[Uppy XHRUpload]: https://uppy.io/docs/xhr-upload/
|
|
441
441
|
[Uppy AwsS3]: https://uppy.io/docs/aws-s3/
|
|
442
442
|
[Uppy Tus]: https://uppy.io/docs/tus/
|
|
443
|
-
[Uppy
|
|
443
|
+
[Uppy AwsS3 multipart]: https://uppy.io/docs/aws-s3/#shouldusemultipartfile
|
|
444
444
|
[tus]: https://tus.io
|
|
445
|
-
[Uppy StatusBar]: https://uppy.io/
|
|
446
|
-
[Uppy Dashboard]: https://uppy.io/
|
|
445
|
+
[Uppy StatusBar]: https://uppy.io/docs/status-bar/
|
|
446
|
+
[Uppy Dashboard]: https://uppy.io/docs/dashboard/
|
|
447
447
|
[tus-ruby-server]: https://github.com/janko/tus-ruby-server
|
|
448
448
|
[uppy-s3_multipart]: https://github.com/janko/uppy-s3_multipart
|
|
449
449
|
[backgrounding]: https://shrinerb.com/docs/plugins/backgrounding
|
data/doc/design.md
CHANGED
|
@@ -264,7 +264,7 @@ When a persistence plugin is loaded ([`activerecord`][activerecord],
|
|
|
264
264
|
[Using Attacher]: https://shrinerb.com/docs/attacher
|
|
265
265
|
[Notes on study of shrine implementation]: https://bibwild.wordpress.com/2018/09/12/notes-on-study-of-shrine-implementation/
|
|
266
266
|
[Creating a New Plugin]: https://shrinerb.com/docs/creating-plugins
|
|
267
|
-
[Plugin system of Sequel and Roda]: https://
|
|
267
|
+
[Plugin system of Sequel and Roda]: https://janko.io/the-plugin-system-of-sequel-and-roda/
|
|
268
268
|
[entity]: https://shrinerb.com/docs/plugins/entity
|
|
269
269
|
[model]: https://shrinerb.com/docs/plugins/model
|
|
270
270
|
[activerecord]: https://shrinerb.com/docs/plugins/activerecord
|
data/doc/direct_s3.md
CHANGED
|
@@ -174,11 +174,21 @@ and request headers.
|
|
|
174
174
|
}
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
Uppy's [AWS S3][uppy aws-s3] plugin
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
Uppy's [AWS S3][uppy aws-s3] plugin in Companion mode will then fetch these
|
|
178
|
+
parameters from `<companionEndpoint>/s3/params` and use them to upload the file
|
|
179
|
+
directly to S3:
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
uppy.use(AwsS3, {
|
|
183
|
+
companionEndpoint: '', // relative to the current host
|
|
184
|
+
shouldUseMultipart: false, // Shrine's presign endpoint only signs single-request uploads
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Once the file has been uploaded, you can generate a JSON representation of the
|
|
189
|
+
uploaded file on the client side (the S3 object key is available as
|
|
190
|
+
`response.body.key` in the `upload-success` event), and write it to the hidden
|
|
191
|
+
attachment field (or send it directly in an AJAX request).
|
|
182
192
|
|
|
183
193
|
```rb
|
|
184
194
|
{
|
|
@@ -203,8 +213,8 @@ as the [Roda][roda demo] / [Rails][rails demo] demo app for a complete example
|
|
|
203
213
|
of multiple direct S3 uploads.
|
|
204
214
|
|
|
205
215
|
Also, if you're dealing with larger files, you may want to make the uploads
|
|
206
|
-
resumable by using
|
|
207
|
-
instead, with the [uppy-s3_multipart] gem on the backend. Your back-end
|
|
216
|
+
resumable by using Uppy's [AWS S3][uppy aws-s3-multipart] plugin in multipart
|
|
217
|
+
mode instead, with the [uppy-s3_multipart] gem on the backend. Your back-end
|
|
208
218
|
implementation is similar, just using `Shrine.uppy_s3_multipart` in place of
|
|
209
219
|
`Shrine.presign_endpoint`. Instructions can be found in the [gem
|
|
210
220
|
docs][uppy-s3_multipart shrine].
|
|
@@ -396,8 +406,8 @@ setup] guide.
|
|
|
396
406
|
[rails demo]: https://github.com/erikdahlstrand/shrine-rails-example
|
|
397
407
|
[Uppy]: https://uppy.io
|
|
398
408
|
[uppy aws-s3]: https://uppy.io/docs/aws-s3/
|
|
399
|
-
[uppy aws-s3 cors]: https://uppy.io/docs/aws-s3/#
|
|
400
|
-
[uppy aws-s3-multipart]: https://uppy.io/docs/aws-s3
|
|
409
|
+
[uppy aws-s3 cors]: https://uppy.io/docs/aws-s3/#bucket-and-cors-setup
|
|
410
|
+
[uppy aws-s3-multipart]: https://uppy.io/docs/aws-s3/#shouldusemultipartfile
|
|
401
411
|
[uppy-s3_multipart]: https://github.com/janko/uppy-s3_multipart
|
|
402
412
|
[uppy-s3_multipart shrine]: https://github.com/janko/uppy-s3_multipart#shrine
|
|
403
413
|
[Amazon S3 Data Consistency Model]: http://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction.html#ConsistencyMode
|
data/doc/external/extensions.md
CHANGED
|
@@ -50,4 +50,4 @@ title: Extensions
|
|
|
50
50
|
| [faster_s3_url](https://github.com/jrochkind/faster_s3_url) | Optimized generation of public and presigned AWS S3 GET URLs |
|
|
51
51
|
| [imgproxy](https://github.com/imgproxy/imgproxy.rb) | Integration for [imgproxy](https://github.com/imgproxy/imgproxy) |
|
|
52
52
|
| [rails_admin](https://github.com/sferik/rails_admin) | Integration for [RailsAdmin](https://github.com/sferik/rails_admin) |
|
|
53
|
-
| [uppy-s3_multipart](https://github.com/janko/uppy-s3_multipart) | Integration for [Uppy AWS S3
|
|
53
|
+
| [uppy-s3_multipart](https://github.com/janko/uppy-s3_multipart) | Integration for [Uppy AWS S3](https://uppy.io/docs/aws-s3/) multipart uploads |
|
data/doc/getting_started.md
CHANGED
|
@@ -896,13 +896,13 @@ on slow and flaky internet connections.
|
|
|
896
896
|
|
|
897
897
|
#### Uppy S3 Multipart
|
|
898
898
|
|
|
899
|
-
You can achieve resumable uploads directly to S3 with
|
|
900
|
-
|
|
899
|
+
You can achieve resumable uploads directly to S3 with Uppy's [AWS S3][uppy
|
|
900
|
+
aws-s3-multipart] plugin in multipart mode, accompanied with the
|
|
901
901
|
`uppy_s3_multipart` Shrine plugin provided by the [uppy-s3_multipart] gem.
|
|
902
902
|
|
|
903
903
|
```rb
|
|
904
904
|
# Gemfile
|
|
905
|
-
gem "uppy-s3_multipart", "~>
|
|
905
|
+
gem "uppy-s3_multipart", "~> 1.3"
|
|
906
906
|
```
|
|
907
907
|
```rb
|
|
908
908
|
Shrine.plugin :uppy_s3_multipart
|
|
@@ -1108,7 +1108,7 @@ Shrine.logger.level = Logger::WARN
|
|
|
1108
1108
|
[Uppy]: https://uppy.io
|
|
1109
1109
|
[shrine-tus]: https://github.com/shrinerb/shrine-tus
|
|
1110
1110
|
[tus]: https://tus.io
|
|
1111
|
-
[uppy aws-s3-multipart]: https://uppy.io/docs/aws-s3
|
|
1111
|
+
[uppy aws-s3-multipart]: https://uppy.io/docs/aws-s3/#shouldusemultipartfile
|
|
1112
1112
|
[uppy aws-s3]: https://uppy.io/docs/aws-s3/
|
|
1113
1113
|
[uppy tus]: https://uppy.io/docs/tus/
|
|
1114
1114
|
[uppy xhr-upload]: https://uppy.io/docs/xhr-upload/
|
data/doc/plugins/data_uri.md
CHANGED
|
@@ -37,8 +37,23 @@ attachment column. You can change the default error message:
|
|
|
37
37
|
```rb
|
|
38
38
|
plugin :data_uri, error_message: "data URI was invalid"
|
|
39
39
|
plugin :data_uri, error_message: ->(uri) { I18n.t("errors.data_uri_invalid") }
|
|
40
|
+
plugin :data_uri, error_message: ->(uri, error) { I18n.t("errors.data_uri.#{error.message}") }
|
|
40
41
|
```
|
|
41
42
|
|
|
43
|
+
## Maximum size
|
|
44
|
+
|
|
45
|
+
It's a good practice to limit the maximum size of the data URI content:
|
|
46
|
+
|
|
47
|
+
```rb
|
|
48
|
+
plugin :data_uri, max_size: 10*1024*1024 # 10 MB
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Now if content bigger than 10MB is assigned, parsing will fail the same way as
|
|
52
|
+
for an invalid data URI. The size is calculated from the data URI before the
|
|
53
|
+
content is decoded, so oversized content never gets loaded into memory. It's
|
|
54
|
+
exact for base64 content, while for percent-encoded content the encoded length
|
|
55
|
+
is used, which can overestimate.
|
|
56
|
+
|
|
42
57
|
## Uploader options
|
|
43
58
|
|
|
44
59
|
Any options passed to `Attacher#assign_data_uri` will be forwarded to
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Fallback Storage
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The [`fallback_storage`][fallback_storage] plugin allows you to specify a secondary (fallback) storage that
|
|
6
|
+
Shrine will read from if a file is not found in the primary storage.
|
|
7
|
+
|
|
8
|
+
This is especially useful when setting up a staging or development environment where you want to read files
|
|
9
|
+
from production storage without replicating or copying large amounts of data locally.
|
|
10
|
+
|
|
11
|
+
```rb
|
|
12
|
+
Shrine.storages = {
|
|
13
|
+
cache: Shrine::Storage::S3.new(endpoint: "https://stage.example.com", prefix: "cache"),
|
|
14
|
+
store: Shrine::Storage::S3.new(endpoint: "https://stage.example.com"),
|
|
15
|
+
fallback: Shrine::Storage::S3.new(endpoint: "https://production.example.com"),
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Shrine.plugin :fallback_storage
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
By default, the plugin looks for a storage named `:fallback`. You can override the fallback storage
|
|
22
|
+
name by passing the `:store` option:
|
|
23
|
+
|
|
24
|
+
```rb
|
|
25
|
+
Shrine.plugin :fallback_storage, store: :production_store
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## How It Works
|
|
29
|
+
|
|
30
|
+
The plugin intercepts file existence checks, URL generation, and file reading methods on
|
|
31
|
+
`Shrine::UploadedFile`.
|
|
32
|
+
|
|
33
|
+
When performing read or check operations, Shrine will first attempt to query the main storage.
|
|
34
|
+
If the file is missing in the primary storage, it seamlessly falls back to the configured fallback storage:
|
|
35
|
+
|
|
36
|
+
* `UploadedFile#exists?` — Returns true if the file exists in the primary storage OR in the fallback storage.
|
|
37
|
+
* `UploadedFile#url` — Returns the URL from the primary storage if the file exists there,
|
|
38
|
+
otherwise generates the URL using the fallback storage.
|
|
39
|
+
* `UploadedFile#open` (and methods relying on it like `read`, `download`, etc.) — Opens the file from the
|
|
40
|
+
primary storage if present, otherwise opens it from the fallback storage.
|
|
41
|
+
|
|
42
|
+
## Write and Delete Operations
|
|
43
|
+
|
|
44
|
+
The plugin only affects read operations. Uploads, replacements, and deletions interact strictly with
|
|
45
|
+
the primary storage:
|
|
46
|
+
|
|
47
|
+
* Uploads: `Shrine.upload` writes files only to the main storage.
|
|
48
|
+
* Deletes: `UploadedFile#delete` removes the file only from the primary storage. The fallback storage remains
|
|
49
|
+
untouched.
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
# File exists only in fallback storage
|
|
53
|
+
file = Shrine.uploaded_file({ "id" => "some-id", "storage" => "store" })
|
|
54
|
+
file.exists? # => true (found in fallback)
|
|
55
|
+
|
|
56
|
+
# Deleting removes it from primary storage, but fallback remains intact
|
|
57
|
+
file.delete
|
|
58
|
+
file.exists? # => true (still found in fallback)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For additional security and reliability when pointing to a production environment, it is recommended
|
|
62
|
+
to configure the credentials for the fallback storage (e.g., S3 access keys) with read-only permissions.
|
|
63
|
+
|
|
64
|
+
## Rails example
|
|
65
|
+
|
|
66
|
+
A common use case is a staging environment that should have access to production files without storing
|
|
67
|
+
its own copies. You can configure Shrine differently per environment:
|
|
68
|
+
|
|
69
|
+
```rb
|
|
70
|
+
# config/initializers/shrine.rb
|
|
71
|
+
if Rails.env.production?
|
|
72
|
+
Shrine.storages = {
|
|
73
|
+
cache: Shrine::Storage::S3.new(endpoint: "https://production.example.com", prefix: "cache"),
|
|
74
|
+
store: Shrine::Storage::S3.new(endpoint: "https://production.example.com"),
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
Shrine.storages = {
|
|
78
|
+
cache: Shrine::Storage::S3.new(endpoint: "https://stage.example.com", prefix: "cache"),
|
|
79
|
+
store: Shrine::Storage::S3.new(endpoint: "https://stage.example.com"),
|
|
80
|
+
production: Shrine::Storage::S3.new(endpoint: "https://production.example.com"),
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Shrine.plugin :fallback_storage, store: :production
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
With this setup, staging uploads go to the staging endpoint, but any read for a missing file
|
|
88
|
+
will automatically look in the production endpoint.
|
|
89
|
+
|
|
90
|
+
[fallback_storage]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/fallback_storage.rb
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Shrine 3.10.0
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## New features
|
|
6
|
+
|
|
7
|
+
* The new `fallback_storage` plugin makes Shrine read from a secondary (fallback) storage when a file is missing in the primary storage. This is useful for staging or development environments that should read files from production without copying them. `UploadedFile#exists?`, `#url` and `#open` (and methods relying on it) fall back to the secondary storage, while uploads and deletes only affect the primary storage.
|
|
8
|
+
|
|
9
|
+
```rb
|
|
10
|
+
Shrine.storages = {
|
|
11
|
+
cache: Shrine::Storage::S3.new(endpoint: "https://stage.example.com", prefix: "cache"),
|
|
12
|
+
store: Shrine::Storage::S3.new(endpoint: "https://stage.example.com"),
|
|
13
|
+
fallback: Shrine::Storage::S3.new(endpoint: "https://production.example.com"),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
Shrine.plugin :fallback_storage # looks for the `:fallback` storage by default
|
|
17
|
+
Shrine.plugin :fallback_storage, store: :production_store # custom storage name
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
* The `data_uri` plugin now accepts a `:max_size` option for limiting the size of the data URI content. The size is calculated from the data URI before the content is decoded, so oversized content never gets loaded into memory. It's exact for base64 content, while for percent-encoded content the encoded length is used, which can overestimate. Content over the limit fails parsing the same way as an invalid data URI.
|
|
21
|
+
|
|
22
|
+
```rb
|
|
23
|
+
plugin :data_uri, max_size: 10*1024*1024 # 10 MB
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
* The `data_uri` plugin's `:error_message` proc can now optionally accept the error as a second argument, so the size limit can have its own message.
|
|
27
|
+
|
|
28
|
+
```rb
|
|
29
|
+
plugin :data_uri, error_message: ->(uri, error) { I18n.t("errors.data_uri.#{error.message}") }
|
|
30
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Shrine 3.10.1
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Bug fixes
|
|
6
|
+
|
|
7
|
+
* Since Shrine 3.9.0, the `remove_invalid` plugin would raise `NoMethodError` when validation failed on an attacher without a record and the `model` plugin was loaded (directly or through the `activerecord`/`sequel` plugins).
|
|
8
|
+
|
|
9
|
+
```rb
|
|
10
|
+
attacher = Shrine::Attacher.new
|
|
11
|
+
attacher.assign(invalid_file)
|
|
12
|
+
# NoMethodError: undefined method '=' for nil
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`Attacher#write` from the `model` plugin is now a no-op when the attacher isn't backed by a model, so the invalid file is removed and validation errors are kept as before.
|
|
@@ -17,6 +17,7 @@ class Shrine
|
|
|
17
17
|
BASE64_REGEXP = /;base64/
|
|
18
18
|
CONTENT_SEPARATOR = /,/
|
|
19
19
|
DEFAULT_CONTENT_TYPE = "text/plain"
|
|
20
|
+
BASE64_ALPHABET = "A-Za-z0-9+/"
|
|
20
21
|
|
|
21
22
|
LOG_SUBSCRIBER = -> (event) do
|
|
22
23
|
Shrine.logger.info "Data URI (#{event.duration}ms) – #{{
|
|
@@ -78,13 +79,37 @@ class Shrine
|
|
|
78
79
|
data_file
|
|
79
80
|
end
|
|
80
81
|
|
|
81
|
-
#
|
|
82
|
+
# Raises `ParseError` if the content exceeds the `:max_size` option.
|
|
83
|
+
def verify_data_size!(uri, offset, base64)
|
|
84
|
+
max_size = opts[:data_uri][:max_size]
|
|
85
|
+
return unless max_size
|
|
86
|
+
|
|
87
|
+
size = data_size(uri, offset, base64)
|
|
88
|
+
|
|
89
|
+
raise ParseError, "data URI is too large" if size > max_size
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns the size the content will have once decoded. It's exact for
|
|
93
|
+
# base64 and an upper bound for percent-encoded content.
|
|
94
|
+
def data_size(uri, offset, base64)
|
|
95
|
+
return uri.bytesize - offset unless base64
|
|
96
|
+
|
|
97
|
+
header_characters = uri.byteslice(0, offset).count(BASE64_ALPHABET)
|
|
98
|
+
content_characters = uri.count(BASE64_ALPHABET) - header_characters
|
|
99
|
+
|
|
100
|
+
content_characters * 3 / 4 # 4 characters decode into 3 bytes
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Parses the data URI string, verifies content size, and returns parts.
|
|
82
104
|
def parse_data_uri(uri)
|
|
83
105
|
scanner = StringScanner.new(uri)
|
|
84
106
|
scanner.scan(DATA_REGEXP) or raise ParseError, "data URI has invalid format"
|
|
85
107
|
media_type = scanner.scan(MEDIA_TYPE_REGEXP)
|
|
86
108
|
base64 = scanner.scan(BASE64_REGEXP)
|
|
87
109
|
scanner.scan(CONTENT_SEPARATOR) or raise ParseError, "data URI has invalid format"
|
|
110
|
+
|
|
111
|
+
verify_data_size!(scanner.string, scanner.pos, base64)
|
|
112
|
+
|
|
88
113
|
content = scanner.post_match
|
|
89
114
|
|
|
90
115
|
{ content_type: media_type, base64: !!base64, data: content }
|
|
@@ -129,7 +154,7 @@ class Shrine
|
|
|
129
154
|
# Generates an error message for failed data URI parse.
|
|
130
155
|
def data_uri_error_messsage(uri, error)
|
|
131
156
|
message = shrine_class.opts[:data_uri][:error_message]
|
|
132
|
-
message = message.call(uri) if message.respond_to?(:call)
|
|
157
|
+
message = message.call(*[uri, error].take(message.arity.abs)) if message.respond_to?(:call)
|
|
133
158
|
message || error.message
|
|
134
159
|
end
|
|
135
160
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Shrine
|
|
4
|
+
module Plugins
|
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/fallback_storage
|
|
6
|
+
module FallbackStorage
|
|
7
|
+
def self.configure(uploader, **opts)
|
|
8
|
+
uploader.opts[:fallback_storage] ||= { store: :fallback }
|
|
9
|
+
uploader.opts[:fallback_storage].merge!(opts)
|
|
10
|
+
|
|
11
|
+
uploader.find_storage(uploader.opts.dig(:fallback_storage, :store))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module FileMethods
|
|
15
|
+
def self.included(base)
|
|
16
|
+
base.class_eval do
|
|
17
|
+
alias_method :storage_exists?, :exists?
|
|
18
|
+
|
|
19
|
+
def exists?
|
|
20
|
+
super || fallback_storage.exists?(id)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def url(**)
|
|
26
|
+
if storage_exists?
|
|
27
|
+
super
|
|
28
|
+
else
|
|
29
|
+
fallback_storage.url(id, **)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def _open(**)
|
|
36
|
+
if storage_exists?
|
|
37
|
+
super
|
|
38
|
+
else
|
|
39
|
+
fallback_storage.open(id, **)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def fallback_storage
|
|
44
|
+
shrine_class.find_storage(shrine_class.opts.dig(:fallback_storage, :store))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
register_plugin(:fallback_storage, FallbackStorage)
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/shrine/plugins/model.rb
CHANGED
|
@@ -122,12 +122,15 @@ class Shrine
|
|
|
122
122
|
# Writes uploaded file data into the model.
|
|
123
123
|
def set(*)
|
|
124
124
|
result = super
|
|
125
|
-
write
|
|
125
|
+
write
|
|
126
126
|
result
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
# Writes the attachment data into the model attribute.
|
|
129
|
+
# Writes the attachment data into the model attribute. Does nothing
|
|
130
|
+
# when the attacher isn't backed by a model instance.
|
|
130
131
|
def write
|
|
132
|
+
return unless model?
|
|
133
|
+
|
|
131
134
|
column_values.each do |name, value|
|
|
132
135
|
write_attribute(name, value)
|
|
133
136
|
end
|
data/lib/shrine/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shrine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.10.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Janko Marohnić
|
|
@@ -417,6 +417,7 @@ files:
|
|
|
417
417
|
- doc/plugins/download_endpoint.md
|
|
418
418
|
- doc/plugins/dynamic_storage.md
|
|
419
419
|
- doc/plugins/entity.md
|
|
420
|
+
- doc/plugins/fallback_storage.md
|
|
420
421
|
- doc/plugins/form_assign.md
|
|
421
422
|
- doc/plugins/included.md
|
|
422
423
|
- doc/plugins/infer_extension.md
|
|
@@ -488,6 +489,8 @@ files:
|
|
|
488
489
|
- doc/release_notes/3.0.0.md
|
|
489
490
|
- doc/release_notes/3.0.1.md
|
|
490
491
|
- doc/release_notes/3.1.0.md
|
|
492
|
+
- doc/release_notes/3.10.0.md
|
|
493
|
+
- doc/release_notes/3.10.1.md
|
|
491
494
|
- doc/release_notes/3.2.0.md
|
|
492
495
|
- doc/release_notes/3.2.1.md
|
|
493
496
|
- doc/release_notes/3.2.2.md
|
|
@@ -530,6 +533,7 @@ files:
|
|
|
530
533
|
- lib/shrine/plugins/download_endpoint.rb
|
|
531
534
|
- lib/shrine/plugins/dynamic_storage.rb
|
|
532
535
|
- lib/shrine/plugins/entity.rb
|
|
536
|
+
- lib/shrine/plugins/fallback_storage.rb
|
|
533
537
|
- lib/shrine/plugins/form_assign.rb
|
|
534
538
|
- lib/shrine/plugins/included.rb
|
|
535
539
|
- lib/shrine/plugins/infer_extension.rb
|