shrine 3.7.0 → 3.8.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 +18 -0
- data/doc/plugins/derivation_endpoint.md +13 -0
- data/doc/plugins/rack_response.md +9 -0
- data/doc/release_notes/3.7.1.md +31 -0
- data/doc/release_notes/3.8.0.md +37 -0
- data/lib/shrine/plugins/_persistence.rb +1 -1
- data/lib/shrine/plugins/data_uri.rb +1 -1
- data/lib/shrine/plugins/derivation_endpoint.rb +6 -4
- data/lib/shrine/plugins/derivatives.rb +1 -1
- data/lib/shrine/plugins/metadata_attributes.rb +1 -1
- data/lib/shrine/plugins/rack_response.rb +2 -2
- data/lib/shrine/plugins/remote_url.rb +1 -1
- data/lib/shrine/plugins/validation_helpers.rb +1 -1
- data/lib/shrine/storage/file_system.rb +11 -2
- data/lib/shrine/storage/s3.rb +5 -10
- data/lib/shrine/version.rb +1 -1
- data/shrine.gemspec +2 -2
- metadata +7 -33
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 55c3fc9bf7eb40700d2be0ee82900986c601b1b3449d5d5ee7b246546bfe5eb3
|
|
4
|
+
data.tar.gz: 11ffe3b196b79946c931f5a0e9538bda74cc4abd74711124509c76a13a5eaddc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 19585a7a2c486d43fb9be2abc84123adced4d18beebf50919938a9c631d16c0c2ecb15e777397b5b8a1642911171d328c55bf86769b9a31a4a3957cf3d8256d9
|
|
7
|
+
data.tar.gz: 1dfbb667941b5f2bfa1b035230f0b1fb809c59855fc253f8365394624cffd4e3078a0aa41188eb27c8e57be8d4243e45d5dcf56a83b4d9ea1900bf02fa340dc6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
|
+
## 3.8.0 (2026-06-24)
|
|
2
|
+
|
|
3
|
+
* `s3` – Use single-request uploads for smaller files again instead of always using multipart uploads (@janko)
|
|
4
|
+
|
|
5
|
+
* `derivatives` – Forward `Attacher#promote` options to `Attacher#upload_derivatives` (@milkcocoa)
|
|
6
|
+
|
|
7
|
+
* `file_system` – Prevent path traversal outside of the storage directory (@janko)
|
|
8
|
+
|
|
9
|
+
## 3.7.1 (2026-06-03)
|
|
10
|
+
|
|
11
|
+
* Update method signatures of some plugins to work around a Bootsnap bug causing a `wrong number of arguments` error (@janko)
|
|
12
|
+
|
|
13
|
+
* `derivation_endpoint` – Add `:format` argument to `UploadedFile#derivation_url` for path extension (@janko)
|
|
14
|
+
|
|
15
|
+
* `rack_response` – Add `:etag` argument for setting a custom `ETag` (@camilohollanda)
|
|
16
|
+
|
|
1
17
|
## 3.7.0 (2026-05-27)
|
|
2
18
|
|
|
19
|
+
* `rack_response` - Add `:etag` option for setting a custom `ETag` header (@janko)
|
|
20
|
+
|
|
3
21
|
* `download_endpoint` - Add support for expiring URLs (@davidwessman)
|
|
4
22
|
|
|
5
23
|
* `s3` - Use `TransferManager` where available instead of deprecated `upload_steam` (@danieldevlewis)
|
|
@@ -309,6 +309,19 @@ uploaded_file.derivation_url(:thumbnail, prefix: "transformations/image")
|
|
|
309
309
|
#=> ".../transformations/image/thumbnail/eyJpZCI6ImZvbyIsInN?signature=..."
|
|
310
310
|
```
|
|
311
311
|
|
|
312
|
+
## Format
|
|
313
|
+
|
|
314
|
+
Some HTTP clients and CDNs use the URL path extension to determine the content
|
|
315
|
+
type of the response. You can append a file extension to the derivation URL
|
|
316
|
+
path with the `:format` option:
|
|
317
|
+
|
|
318
|
+
```rb
|
|
319
|
+
uploaded_file.derivation_url(:thumbnail, format: "jpg")
|
|
320
|
+
#=> ".../thumbnail/eyJpZCI6ImZvbyIsInN.jpg?signature=..."
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
The extension is included in the URL signature, so it cannot be tampered with.
|
|
324
|
+
|
|
312
325
|
## Expiration
|
|
313
326
|
|
|
314
327
|
By default derivation URLs are valid indefinitely. If you want URLs to expire
|
|
@@ -95,6 +95,15 @@ headers["Content-Range"] #=> "bytes 100-200/1000"
|
|
|
95
95
|
body # partial content
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
## ETag
|
|
99
|
+
|
|
100
|
+
A custom ETag value can be provided via the `:etag` option:
|
|
101
|
+
|
|
102
|
+
```rb
|
|
103
|
+
response = uploaded_file.to_rack_response(etag: "my-custom-etag")
|
|
104
|
+
response[1]["ETag"] #=> "my-custom-etag"
|
|
105
|
+
```
|
|
106
|
+
|
|
98
107
|
## Download options
|
|
99
108
|
|
|
100
109
|
The `#to_rack_response` method will automatically open the `UploadedFile` if it
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Shrine 3.7.1
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## New features
|
|
6
|
+
|
|
7
|
+
* The `derivation_endpoint` plugin now supports adding a file extension to the derivation URL path via the `:format` option on `UploadedFile#derivation_url`. Some HTTP clients and CDNs use the URL path extension to determine the content type of the response.
|
|
8
|
+
|
|
9
|
+
```rb
|
|
10
|
+
uploaded_file.derivation_url(:thumbnail, format: "jpg")
|
|
11
|
+
#=> ".../thumbnail/eyJpZCI6ImZvbyIsInN.jpg?signature=..."
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The extension is included in the URL signature, so it cannot be tampered with.
|
|
15
|
+
|
|
16
|
+
* The `rack_response` plugin now accepts an `:etag` option for setting a custom `ETag` header, overriding the default one Shrine generates.
|
|
17
|
+
|
|
18
|
+
```rb
|
|
19
|
+
response = uploaded_file.to_rack_response(etag: "my-custom-etag")
|
|
20
|
+
response[1]["ETag"] #=> "my-custom-etag"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Bug fixes
|
|
24
|
+
|
|
25
|
+
* When initializing Shrine with the `activerecord` plugin, under Bootsnap 1.24.5 and Ruby 3.4.5 this would raise an error:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
ArgumentError: wrong number of arguments (given 2, expected 1; required keyword: plugin)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The seems to be triggered by a special combination of method signatures, where keyword arguments get converted into positional arguments under Bootsnap. To work around this issue, method signatures of some plugin methods have been updated.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Shrine 3.8.0
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Security
|
|
6
|
+
|
|
7
|
+
* The `file_system` storage now prevents path traversal outside of the storage
|
|
8
|
+
directory. Previously, an id containing `../` sequences (e.g. coming from
|
|
9
|
+
attacker-controlled data) could resolve to a location outside of the
|
|
10
|
+
configured storage directory. Now `Shrine::Error` is raised whenever an id
|
|
11
|
+
would resolve outside of the storage directory:
|
|
12
|
+
|
|
13
|
+
```rb
|
|
14
|
+
storage = Shrine::Storage::FileSystem.new("uploads")
|
|
15
|
+
storage.open("../../etc/passwd") #~> Shrine::Error
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## New features
|
|
19
|
+
|
|
20
|
+
* The `derivatives` plugin now forwards options passed to `Attacher#promote`
|
|
21
|
+
into `Attacher#upload_derivatives`. This means options such as upload options
|
|
22
|
+
reach the derivatives upload as well, not just the main file.
|
|
23
|
+
|
|
24
|
+
```rb
|
|
25
|
+
attacher.promote(upload_options: { acl: "public-read" })
|
|
26
|
+
# the same options are now forwarded when uploading derivatives
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Other improvements
|
|
30
|
+
|
|
31
|
+
* The `s3` storage again uses single-request uploads for smaller files, instead
|
|
32
|
+
of always using multipart uploads. In Shrine 3.7.0, when a `TransferManager`
|
|
33
|
+
was available, it was used for uploads of any size, but `TransferManager`
|
|
34
|
+
uploads via multipart unconditionally, which is inefficient for smaller files.
|
|
35
|
+
Now files at or below the multipart threshold (`:upload`, 15MB by default) are
|
|
36
|
+
uploaded in a single request, and only larger files use multipart upload via
|
|
37
|
+
the `TransferManager` (falling back to the older API when it's not available).
|
|
@@ -303,9 +303,9 @@ 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, metadata: [], **)
|
|
306
|
+
def call(host: nil, prefix: nil, metadata: [], format: nil, **)
|
|
307
307
|
base_url = [host, *prefix].join("/")
|
|
308
|
-
path = path_identifier(metadata:)
|
|
308
|
+
path = path_identifier(metadata:, format:)
|
|
309
309
|
|
|
310
310
|
if signer
|
|
311
311
|
url = [base_url, path].join("/")
|
|
@@ -318,12 +318,13 @@ class Shrine
|
|
|
318
318
|
|
|
319
319
|
private
|
|
320
320
|
|
|
321
|
-
def path_identifier(metadata: [])
|
|
322
|
-
[
|
|
321
|
+
def path_identifier(metadata: [], format: nil)
|
|
322
|
+
path = [
|
|
323
323
|
name,
|
|
324
324
|
*args,
|
|
325
325
|
source.urlsafe_dump(metadata:)
|
|
326
326
|
].map{|component| Rack::Utils.escape_path(component.to_s)}.join('/')
|
|
327
|
+
format ? "#{path}.#{format}" : path
|
|
327
328
|
end
|
|
328
329
|
|
|
329
330
|
def query(expires_in: nil,
|
|
@@ -386,6 +387,7 @@ class Shrine
|
|
|
386
387
|
check_expiry!(request)
|
|
387
388
|
|
|
388
389
|
name, *args, serialized_file = request.path_info.split("/")[1..-1]
|
|
390
|
+
serialized_file = serialized_file.sub(/\.\w+$/, "")
|
|
389
391
|
|
|
390
392
|
name = name.to_sym
|
|
391
393
|
uploaded_file = shrine_class::UploadedFile.urlsafe_load(serialized_file)
|
|
@@ -49,14 +49,14 @@ class Shrine
|
|
|
49
49
|
# "Content-Disposition" headers, whose values are extracted from
|
|
50
50
|
# metadata. Also returns the correct "Content-Range" header on ranged
|
|
51
51
|
# requests.
|
|
52
|
-
def rack_headers(filename: nil, type: nil, disposition: "inline", range: false)
|
|
52
|
+
def rack_headers(filename: nil, type: nil, disposition: "inline", range: false, etag: nil)
|
|
53
53
|
{
|
|
54
54
|
"Content-Length" => content_length(range),
|
|
55
55
|
"Content-Type" => content_type(type),
|
|
56
56
|
"Content-Disposition" => content_disposition(disposition, filename),
|
|
57
57
|
"Content-Range" => content_range(range),
|
|
58
58
|
"Accept-Ranges" => accept_ranges(range),
|
|
59
|
-
"ETag" => etag,
|
|
59
|
+
"ETag" => etag || self.etag,
|
|
60
60
|
}.compact
|
|
61
61
|
end
|
|
62
62
|
|
|
@@ -113,9 +113,18 @@ class Shrine
|
|
|
113
113
|
end
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
-
# Returns the full path to the file.
|
|
116
|
+
# Returns the full path to the file. Raises Shrine::Error if the id would
|
|
117
|
+
# resolve to a location outside of the storage #directory (e.g. an id
|
|
118
|
+
# containing `../` path traversal sequences in attacker-controlled data).
|
|
117
119
|
def path(id)
|
|
118
|
-
directory.join(id.gsub("/", File::SEPARATOR))
|
|
120
|
+
path = directory.join(id.gsub("/", File::SEPARATOR))
|
|
121
|
+
expanded = path.expand_path
|
|
122
|
+
|
|
123
|
+
unless expanded == directory || expanded.to_s.start_with?("#{directory}#{File::SEPARATOR}")
|
|
124
|
+
raise Shrine::Error, "path #{id.inspect} resolves outside of the storage directory"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
path
|
|
119
128
|
end
|
|
120
129
|
|
|
121
130
|
protected
|
data/lib/shrine/storage/s3.rb
CHANGED
|
@@ -230,20 +230,15 @@ class Shrine
|
|
|
230
230
|
|
|
231
231
|
private
|
|
232
232
|
|
|
233
|
-
#
|
|
234
|
-
# Uses @transfer_manager, if defined, for any size upload.
|
|
235
|
-
# Falls back to the original code using the older, now depricated
|
|
236
|
-
# AWS APIs for users of older version of the AWS Gem.
|
|
237
|
-
# for multipart uploads of large files.
|
|
233
|
+
# Uploads the file to S3. Uses multipart upload for large files.
|
|
238
234
|
def put(io, id, **)
|
|
239
|
-
if @
|
|
235
|
+
if io.respond_to?(:size) && io.size && io.size <= @multipart_threshold[:upload]
|
|
236
|
+
object(id).put(body: io, **)
|
|
237
|
+
elsif @transfer_manager # multipart upload - transfer manager
|
|
240
238
|
@transfer_manager.upload_stream(bucket: bucket.name, key: object_key(id), part_size: part_size(io), **) do |write_stream|
|
|
241
239
|
IO.copy_stream(io, write_stream)
|
|
242
240
|
end
|
|
243
|
-
|
|
244
|
-
object(id).put(body: io, **)
|
|
245
|
-
else
|
|
246
|
-
# multipart upload old API
|
|
241
|
+
else # multipart upload - before transfer manager
|
|
247
242
|
object(id).upload_stream(part_size: part_size(io), **) do |write_stream|
|
|
248
243
|
IO.copy_stream(io, write_stream)
|
|
249
244
|
end
|
data/lib/shrine/version.rb
CHANGED
data/shrine.gemspec
CHANGED
|
@@ -67,10 +67,10 @@ direct uploads for fully asynchronous user experience.
|
|
|
67
67
|
|
|
68
68
|
# for instrumentation plugin
|
|
69
69
|
gem.add_development_dependency "dry-monitor"
|
|
70
|
-
gem.add_development_dependency "activesupport", RUBY_ENGINE == "jruby" ? "~>
|
|
70
|
+
gem.add_development_dependency "activesupport", RUBY_ENGINE == "jruby" ? "~> 8.0.0" : "~> 8.1"
|
|
71
71
|
|
|
72
72
|
# for ORM plugins
|
|
73
73
|
gem.add_development_dependency "sequel"
|
|
74
|
-
gem.add_development_dependency "activerecord", RUBY_ENGINE == "jruby" ? "~>
|
|
74
|
+
gem.add_development_dependency "activerecord", RUBY_ENGINE == "jruby" ? "~> 8.0.0" : "~> 8.1"
|
|
75
75
|
gem.add_development_dependency "sqlite3", "~> 2.1" unless RUBY_ENGINE == "jruby"
|
|
76
76
|
end
|
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.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Janko Marohnić
|
|
@@ -155,20 +155,6 @@ dependencies:
|
|
|
155
155
|
- - ">="
|
|
156
156
|
- !ruby/object:Gem::Version
|
|
157
157
|
version: '0'
|
|
158
|
-
- !ruby/object:Gem::Dependency
|
|
159
|
-
name: ruby-filemagic
|
|
160
|
-
requirement: !ruby/object:Gem::Requirement
|
|
161
|
-
requirements:
|
|
162
|
-
- - "~>"
|
|
163
|
-
- !ruby/object:Gem::Version
|
|
164
|
-
version: '0.7'
|
|
165
|
-
type: :development
|
|
166
|
-
prerelease: false
|
|
167
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
168
|
-
requirements:
|
|
169
|
-
- - "~>"
|
|
170
|
-
- !ruby/object:Gem::Version
|
|
171
|
-
version: '0.7'
|
|
172
158
|
- !ruby/object:Gem::Dependency
|
|
173
159
|
name: mime-types
|
|
174
160
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -301,14 +287,14 @@ dependencies:
|
|
|
301
287
|
requirements:
|
|
302
288
|
- - "~>"
|
|
303
289
|
- !ruby/object:Gem::Version
|
|
304
|
-
version:
|
|
290
|
+
version: 8.0.0
|
|
305
291
|
type: :development
|
|
306
292
|
prerelease: false
|
|
307
293
|
version_requirements: !ruby/object:Gem::Requirement
|
|
308
294
|
requirements:
|
|
309
295
|
- - "~>"
|
|
310
296
|
- !ruby/object:Gem::Version
|
|
311
|
-
version:
|
|
297
|
+
version: 8.0.0
|
|
312
298
|
- !ruby/object:Gem::Dependency
|
|
313
299
|
name: sequel
|
|
314
300
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -329,28 +315,14 @@ dependencies:
|
|
|
329
315
|
requirements:
|
|
330
316
|
- - "~>"
|
|
331
317
|
- !ruby/object:Gem::Version
|
|
332
|
-
version:
|
|
333
|
-
type: :development
|
|
334
|
-
prerelease: false
|
|
335
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
336
|
-
requirements:
|
|
337
|
-
- - "~>"
|
|
338
|
-
- !ruby/object:Gem::Version
|
|
339
|
-
version: '8.1'
|
|
340
|
-
- !ruby/object:Gem::Dependency
|
|
341
|
-
name: sqlite3
|
|
342
|
-
requirement: !ruby/object:Gem::Requirement
|
|
343
|
-
requirements:
|
|
344
|
-
- - "~>"
|
|
345
|
-
- !ruby/object:Gem::Version
|
|
346
|
-
version: '2.1'
|
|
318
|
+
version: 8.0.0
|
|
347
319
|
type: :development
|
|
348
320
|
prerelease: false
|
|
349
321
|
version_requirements: !ruby/object:Gem::Requirement
|
|
350
322
|
requirements:
|
|
351
323
|
- - "~>"
|
|
352
324
|
- !ruby/object:Gem::Version
|
|
353
|
-
version:
|
|
325
|
+
version: 8.0.0
|
|
354
326
|
description: |
|
|
355
327
|
Shrine is a toolkit for file attachments in Ruby applications. It supports
|
|
356
328
|
uploading, downloading, processing and deleting IO objects, backed by various
|
|
@@ -482,6 +454,8 @@ files:
|
|
|
482
454
|
- doc/release_notes/3.5.0.md
|
|
483
455
|
- doc/release_notes/3.6.0.md
|
|
484
456
|
- doc/release_notes/3.7.0.md
|
|
457
|
+
- doc/release_notes/3.7.1.md
|
|
458
|
+
- doc/release_notes/3.8.0.md
|
|
485
459
|
- doc/retrieving_uploads.md
|
|
486
460
|
- doc/securing_uploads.md
|
|
487
461
|
- doc/storage/file_system.md
|