carrierwave 3.1.3 → 4.0.0.beta
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/README.md +76 -1
- data/lib/carrierwave/downloader/base.rb +35 -7
- data/lib/carrierwave/downloader/remote_file.rb +3 -1
- data/lib/carrierwave/error.rb +1 -0
- data/lib/carrierwave/locale/en.yml +1 -0
- data/lib/carrierwave/mount.rb +2 -0
- data/lib/carrierwave/mounter.rb +39 -4
- data/lib/carrierwave/orm/activerecord.rb +3 -0
- data/lib/carrierwave/processing/rmagick.rb +5 -0
- data/lib/carrierwave/sanitized_file.rb +9 -1
- data/lib/carrierwave/storage/abstract.rb +5 -0
- data/lib/carrierwave/storage/fog.rb +50 -10
- data/lib/carrierwave/test/matchers.rb +24 -0
- data/lib/carrierwave/uploader/cache.rb +10 -0
- data/lib/carrierwave/uploader/configuration.rb +1 -0
- data/lib/carrierwave/uploader/metadata.rb +100 -0
- data/lib/carrierwave/uploader/processing.rb +10 -6
- data/lib/carrierwave/uploader/proxy.rb +18 -2
- data/lib/carrierwave/uploader/store.rb +23 -1
- data/lib/carrierwave/uploader/url.rb +3 -0
- data/lib/carrierwave/uploader/versions.rb +74 -48
- data/lib/carrierwave/uploader.rb +2 -0
- data/lib/carrierwave/utilities/uri.rb +28 -6
- data/lib/carrierwave/version.rb +1 -1
- metadata +38 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 44d2471aedac47f453dcf2e0b0b3a36657ff89a1cd521875528a118cdfc465f4
|
|
4
|
+
data.tar.gz: 4de4a19f9c7b959d8bfd3658c8ee672b03227a5c74d96ec86cbca64ab692d726
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f15fe1cc5ca10c246398c23bfa6d882494434dde71f40c4d044d949fae30e0929ade0d928ae951b028d2c9821538ae3d1e18a71c43f17e7a5256ecc19f4b8ea
|
|
7
|
+
data.tar.gz: 675258a32e180790066ea27d8d12aad87e78c607e51125afc0c24fef5eab42588f7a2dba4d54967ee28b52ae7d4921bf408801ee6ab504ebac05d91ef907cdbd
|
data/README.md
CHANGED
|
@@ -378,6 +378,32 @@ class MyUploader < CarrierWave::Uploader::Base
|
|
|
378
378
|
end
|
|
379
379
|
```
|
|
380
380
|
|
|
381
|
+
`process convert: format` cannot be given a condition. The file extension follows the
|
|
382
|
+
conversion, and it has to be worked out again when the file is retrieved, where whether
|
|
383
|
+
the condition held is not known. Convert unconditionally, or give the format a version
|
|
384
|
+
of its own:
|
|
385
|
+
|
|
386
|
+
```ruby
|
|
387
|
+
version :webp, if: :convert_to_webp? do
|
|
388
|
+
process convert: :webp # unconditional within the version
|
|
389
|
+
end
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
If you need to convert on a condition and keep it on the uploader itself, take the
|
|
393
|
+
naming over so that it still follows from the identifier:
|
|
394
|
+
|
|
395
|
+
```ruby
|
|
396
|
+
process :to_jpeg, if: :heic?
|
|
397
|
+
|
|
398
|
+
def to_jpeg
|
|
399
|
+
minimagick! { |builder| builder.convert('jpg') }
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def full_filename(for_file)
|
|
403
|
+
for_file.sub(/\.heic\z/i, '.jpg')
|
|
404
|
+
end
|
|
405
|
+
```
|
|
406
|
+
|
|
381
407
|
### Nested versions
|
|
382
408
|
|
|
383
409
|
It is possible to nest versions within versions:
|
|
@@ -424,6 +450,13 @@ end
|
|
|
424
450
|
```
|
|
425
451
|
|
|
426
452
|
The `model` variable points to the instance object the uploader is attached to.
|
|
453
|
+
The condition is evaluated when the version is accessed, so it isn't called at all
|
|
454
|
+
as long as you only use the original file.
|
|
455
|
+
|
|
456
|
+
Note that `present?` on a version only tells whether a file is assigned, not whether
|
|
457
|
+
that version was actually created. Use `uploader.thumb.exists?` to ask the storage,
|
|
458
|
+
which costs a request when the storage is a remote one, or
|
|
459
|
+
[record what was stored](#recording-what-was-stored) to have the answer at hand.
|
|
427
460
|
|
|
428
461
|
### Create versions from existing versions
|
|
429
462
|
|
|
@@ -466,7 +499,44 @@ end
|
|
|
466
499
|
|
|
467
500
|
Please note that `#full_filename` mustn't be constructed based on a dynamic value
|
|
468
501
|
that can change from the time of store and time of retrieval, since it will result in
|
|
469
|
-
being unable to retrieve a file previously stored.
|
|
502
|
+
being unable to retrieve a file previously stored. Storing refuses to leave a file
|
|
503
|
+
somewhere a later request would not look, so this is caught rather than silent.
|
|
504
|
+
|
|
505
|
+
## Recording what was stored
|
|
506
|
+
|
|
507
|
+
By default CarrierWave persists the identifier alone, and works out everything else on
|
|
508
|
+
retrieval by re-running the uploader's definition and asking the storage. Which versions
|
|
509
|
+
a conditional creates is decided when the file is stored, so re-deciding it later can
|
|
510
|
+
give a different answer than what is actually there, and asking a remote storage for
|
|
511
|
+
things like the size costs a request per record.
|
|
512
|
+
|
|
513
|
+
Give the mount a column to record it in, and it stops guessing:
|
|
514
|
+
|
|
515
|
+
```ruby
|
|
516
|
+
class Event < ActiveRecord::Base
|
|
517
|
+
mount_uploader :image, ImageUploader, metadata_column: :image_metadata
|
|
518
|
+
end
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
```ruby
|
|
522
|
+
add_column :events, :image_metadata, :json
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
The column can be a `json`/`jsonb` one, a serialized one, or plain text holding JSON.
|
|
526
|
+
For `mount_uploaders` it holds one set of facts per file.
|
|
527
|
+
|
|
528
|
+
Records stored before the column was added have nothing recorded, and keep working as
|
|
529
|
+
they always have, so no backfill is needed.
|
|
530
|
+
|
|
531
|
+
Override `#build_metadata` to record your own facts:
|
|
532
|
+
|
|
533
|
+
```ruby
|
|
534
|
+
class ImageUploader < CarrierWave::Uploader::Base
|
|
535
|
+
def build_metadata
|
|
536
|
+
super.merge('width' => width, 'height' => height)
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
```
|
|
470
540
|
|
|
471
541
|
## Making uploads work across form redisplays
|
|
472
542
|
|
|
@@ -500,6 +570,11 @@ case of images, a small thumbnail would be a good indicator:
|
|
|
500
570
|
<% end %>
|
|
501
571
|
```
|
|
502
572
|
|
|
573
|
+
When the cache storage is a remote one, uploading the cached file to it is deferred until
|
|
574
|
+
it turns out to be needed beyond the current request, which reading `avatar_cache` or
|
|
575
|
+
`avatar_url` tells CarrierWave. If you carry the cache name over by other means, call
|
|
576
|
+
`user.avatar.materialize_cache!` before handing it out.
|
|
577
|
+
|
|
503
578
|
## Removing uploaded files
|
|
504
579
|
|
|
505
580
|
If you want to remove a previously uploaded file on a mounted uploader, you can
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'open-uri'
|
|
2
2
|
require 'ssrf_filter'
|
|
3
|
-
require 'addressable'
|
|
3
|
+
require 'addressable/uri'
|
|
4
4
|
require 'carrierwave/downloader/remote_file'
|
|
5
5
|
|
|
6
6
|
module CarrierWave
|
|
@@ -8,6 +8,14 @@ module CarrierWave
|
|
|
8
8
|
class Base
|
|
9
9
|
include CarrierWave::Utilities::Uri
|
|
10
10
|
|
|
11
|
+
# Leftovers from pasting, removed before parsing as in the first steps of
|
|
12
|
+
# https://url.spec.whatwg.org/#concept-basic-url-parser
|
|
13
|
+
LEADING_TRAILING_JUNK = /\A[\x00-\x20]+|[\x00-\x20]+\z/.freeze
|
|
14
|
+
EMBEDDED_JUNK = /[\t\n\r]/.freeze
|
|
15
|
+
# Matches a host name with an optional port, but not a scheme like 'http://' or 'data:'
|
|
16
|
+
HOSTISH = /\A[[:alnum:]][[:alnum:]\-._]*(?::\d+)?(?:[\/?#]|\z)/.freeze
|
|
17
|
+
DEFAULT_SCHEME = 'https'.freeze
|
|
18
|
+
|
|
11
19
|
attr_reader :uploader
|
|
12
20
|
|
|
13
21
|
def initialize(uploader)
|
|
@@ -29,7 +37,7 @@ module CarrierWave
|
|
|
29
37
|
uri = process_uri(url.to_s)
|
|
30
38
|
begin
|
|
31
39
|
if skip_ssrf_protection?(uri)
|
|
32
|
-
response = OpenURI.open_uri(
|
|
40
|
+
response = OpenURI.open_uri(uri, headers)
|
|
33
41
|
else
|
|
34
42
|
request = nil
|
|
35
43
|
if ::SsrfFilter::VERSION.to_f < 1.1
|
|
@@ -59,22 +67,42 @@ module CarrierWave
|
|
|
59
67
|
##
|
|
60
68
|
# Processes the given URL by parsing it, and escaping if necessary. Public to allow overriding.
|
|
61
69
|
#
|
|
70
|
+
# Never decodes, as that would make %2F and '/', %2B and '+' indistinguishable.
|
|
71
|
+
#
|
|
62
72
|
# === Parameters
|
|
63
73
|
#
|
|
64
74
|
# [url (String)] The URL where the remote file is stored
|
|
65
75
|
#
|
|
66
76
|
def process_uri(source)
|
|
67
|
-
uri = Addressable::URI.parse(source)
|
|
77
|
+
uri = Addressable::URI.parse(normalize_input(source))
|
|
68
78
|
uri.host = uri.normalized_host
|
|
69
|
-
|
|
70
|
-
uri.
|
|
71
|
-
uri.
|
|
72
|
-
uri.fragment = encode_non_ascii(uri.fragment) if uri.fragment
|
|
79
|
+
uri.path = sanitize_component(uri.path, SANITIZE_PATH) if uri.path
|
|
80
|
+
uri.query = sanitize_component(uri.query, SANITIZE_QUERY) if uri.query
|
|
81
|
+
uri.fragment = sanitize_component(uri.fragment, SANITIZE_FRAGMENT) if uri.fragment
|
|
73
82
|
URI.parse(uri.to_s)
|
|
74
83
|
rescue URI::InvalidURIError, Addressable::URI::InvalidURIError
|
|
75
84
|
raise CarrierWave::DownloadError, "couldn't parse URL: #{source}"
|
|
76
85
|
end
|
|
77
86
|
|
|
87
|
+
##
|
|
88
|
+
# Cleans up a URL as pasted by an end user, since `remote_#{column}_url=` is meant to be
|
|
89
|
+
# fed from a text field. Public to allow overriding.
|
|
90
|
+
#
|
|
91
|
+
# === Parameters
|
|
92
|
+
#
|
|
93
|
+
# [source (String)] The URL given by the user
|
|
94
|
+
#
|
|
95
|
+
def normalize_input(source)
|
|
96
|
+
source = source.to_s.gsub(LEADING_TRAILING_JUNK, '').gsub(EMBEDDED_JUNK, '')
|
|
97
|
+
|
|
98
|
+
return "#{DEFAULT_SCHEME}:#{source}" if source.start_with?('//')
|
|
99
|
+
# Don't promote a local path like '/etc/passwd' into a URL
|
|
100
|
+
return source if source.start_with?('/')
|
|
101
|
+
return "#{DEFAULT_SCHEME}://#{source}" if source.match?(HOSTISH)
|
|
102
|
+
|
|
103
|
+
source
|
|
104
|
+
end
|
|
105
|
+
|
|
78
106
|
##
|
|
79
107
|
# If this returns true, SSRF protection will be bypassed.
|
|
80
108
|
# You can override this if you want to allow accessing specific local URIs that are not SSRF exploitable.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module CarrierWave
|
|
2
2
|
module Downloader
|
|
3
3
|
class RemoteFile
|
|
4
|
+
include CarrierWave::Utilities::Uri
|
|
5
|
+
|
|
4
6
|
attr_reader :file, :uri
|
|
5
7
|
|
|
6
8
|
def initialize(file)
|
|
@@ -53,7 +55,7 @@ module CarrierWave
|
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
def filename_from_uri
|
|
56
|
-
|
|
58
|
+
decode_path(File.basename(uri.path))
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
def method_missing(*args, &block)
|
data/lib/carrierwave/error.rb
CHANGED
|
@@ -9,6 +9,7 @@ en:
|
|
|
9
9
|
content_type_allowlist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
|
|
10
10
|
content_type_denylist_error: "You are not allowed to upload %{content_type} files"
|
|
11
11
|
processing_error: "Failed to manipulate, maybe it is not an image?"
|
|
12
|
+
unsupported_image_format_error: "Failed to manipulate, file format is not supported"
|
|
12
13
|
min_size_error: "File size should be greater than %{min_size}"
|
|
13
14
|
max_size_error: "File size should be less than %{max_size}"
|
|
14
15
|
min_width_error: "Image width should be greater than %{min_width}px"
|
data/lib/carrierwave/mount.rb
CHANGED
|
@@ -102,6 +102,7 @@ module CarrierWave
|
|
|
102
102
|
# === Options
|
|
103
103
|
#
|
|
104
104
|
# [:mount_on => Symbol] if the name of the column to be serialized to differs you can override it using this option
|
|
105
|
+
# [:metadata_column => Symbol] the column to record what was stored in, so that it does not have to be worked out again
|
|
105
106
|
# [:ignore_integrity_errors => Boolean] if set to true, integrity errors will result in caching failing silently
|
|
106
107
|
# [:ignore_processing_errors => Boolean] if set to true, processing errors will result in caching failing silently
|
|
107
108
|
#
|
|
@@ -247,6 +248,7 @@ module CarrierWave
|
|
|
247
248
|
# === Options
|
|
248
249
|
#
|
|
249
250
|
# [:mount_on => Symbol] if the name of the column to be serialized to differs you can override it using this option
|
|
251
|
+
# [:metadata_column => Symbol] the column to record what was stored in, so that it does not have to be worked out again
|
|
250
252
|
# [:ignore_integrity_errors => Boolean] if set to true, integrity errors will result in caching failing silently
|
|
251
253
|
# [:ignore_processing_errors => Boolean] if set to true, processing errors will result in caching failing silently
|
|
252
254
|
#
|
data/lib/carrierwave/mounter.rb
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "active_support/core_ext/array/wrap"
|
|
3
|
+
|
|
1
4
|
module CarrierWave
|
|
2
5
|
|
|
3
6
|
# this is an internal class, used by CarrierWave::Mount so that
|
|
@@ -8,6 +11,10 @@ module CarrierWave
|
|
|
8
11
|
uploaders.first&.identifier
|
|
9
12
|
end
|
|
10
13
|
|
|
14
|
+
def metadata
|
|
15
|
+
uploaders.first&.build_metadata
|
|
16
|
+
end
|
|
17
|
+
|
|
11
18
|
def temporary_identifier
|
|
12
19
|
temporary_identifiers.first
|
|
13
20
|
end
|
|
@@ -18,6 +25,10 @@ module CarrierWave
|
|
|
18
25
|
uploaders.map(&:identifier).presence
|
|
19
26
|
end
|
|
20
27
|
|
|
28
|
+
def metadata
|
|
29
|
+
uploaders.map(&:build_metadata).presence
|
|
30
|
+
end
|
|
31
|
+
|
|
21
32
|
def temporary_identifier
|
|
22
33
|
temporary_identifiers.presence
|
|
23
34
|
end
|
|
@@ -63,11 +74,28 @@ module CarrierWave
|
|
|
63
74
|
[record.read_uploader(serialization_column)].flatten.reject(&:blank?)
|
|
64
75
|
end
|
|
65
76
|
|
|
77
|
+
##
|
|
78
|
+
# The stored metadata, as many as there are identifiers. Whatever the column gives
|
|
79
|
+
# is accepted, so that it can be a json column, a serialized one, or plain text.
|
|
80
|
+
#
|
|
81
|
+
def read_metadata
|
|
82
|
+
return [] unless metadata_column
|
|
83
|
+
|
|
84
|
+
value = record.read_uploader(metadata_column)
|
|
85
|
+
value = JSON.parse(value) rescue nil if value.is_a?(String)
|
|
86
|
+
Array.wrap(value)
|
|
87
|
+
end
|
|
88
|
+
|
|
66
89
|
def uploaders
|
|
67
|
-
@uploaders ||=
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
90
|
+
@uploaders ||= begin
|
|
91
|
+
metadata = read_metadata
|
|
92
|
+
read_identifiers.each_with_index.map do |identifier, index|
|
|
93
|
+
uploader = blank_uploader
|
|
94
|
+
# Has to come first, as retrieval derives the path from what was recorded
|
|
95
|
+
uploader.metadata = metadata[index]
|
|
96
|
+
uploader.retrieve_from_store!(identifier)
|
|
97
|
+
uploader
|
|
98
|
+
end
|
|
71
99
|
end
|
|
72
100
|
end
|
|
73
101
|
|
|
@@ -101,6 +129,8 @@ module CarrierWave
|
|
|
101
129
|
end
|
|
102
130
|
|
|
103
131
|
def cache_names
|
|
132
|
+
# The names are carried over to the next request, which the files have to survive
|
|
133
|
+
uploaders.each(&:materialize_cache!)
|
|
104
134
|
uploaders.map(&:cache_name).compact
|
|
105
135
|
end
|
|
106
136
|
|
|
@@ -157,6 +187,7 @@ module CarrierWave
|
|
|
157
187
|
@added_uploaders += additions
|
|
158
188
|
|
|
159
189
|
record.write_uploader(serialization_column, identifier)
|
|
190
|
+
record.write_uploader(metadata_column, metadata) if metadata_column
|
|
160
191
|
end
|
|
161
192
|
|
|
162
193
|
def urls(*args)
|
|
@@ -197,6 +228,10 @@ module CarrierWave
|
|
|
197
228
|
option(:mount_on) || column
|
|
198
229
|
end
|
|
199
230
|
|
|
231
|
+
def metadata_column
|
|
232
|
+
option(:metadata_column)
|
|
233
|
+
end
|
|
234
|
+
|
|
200
235
|
def remove_previous
|
|
201
236
|
current_paths = uploaders.map(&:path)
|
|
202
237
|
@removed_uploaders
|
|
@@ -52,6 +52,9 @@ module CarrierWave
|
|
|
52
52
|
@_mounters[:"#{column}"] = nil
|
|
53
53
|
# The attribute needs to be cleared to prevent it from picked up as identifier
|
|
54
54
|
write_uploader(_mounter(:#{column}).serialization_column, nil)
|
|
55
|
+
if (metadata_column = _mounter(:#{column}).metadata_column)
|
|
56
|
+
write_uploader(metadata_column, nil)
|
|
57
|
+
end
|
|
55
58
|
_mounter(:"#{column}").cache(old_uploaders)
|
|
56
59
|
end
|
|
57
60
|
|
|
@@ -383,6 +383,11 @@ module CarrierWave
|
|
|
383
383
|
frame = yield(*[frame, index, options].take(block.arity)) if block_given?
|
|
384
384
|
frames << frame if frame
|
|
385
385
|
end
|
|
386
|
+
|
|
387
|
+
if frames.none?
|
|
388
|
+
raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.unsupported_image_format_error")
|
|
389
|
+
end
|
|
390
|
+
|
|
386
391
|
frames.append(true) if block_given?
|
|
387
392
|
|
|
388
393
|
write_block = create_info_block(options[:write])
|
|
@@ -317,10 +317,18 @@ module CarrierWave
|
|
|
317
317
|
def declared_content_type
|
|
318
318
|
@declared_content_type ||
|
|
319
319
|
if @file.respond_to?(:content_type) && @file.content_type
|
|
320
|
-
Marcel::MimeType.for(declared_type: @file.content_type
|
|
320
|
+
Marcel::MimeType.for(declared_type: first_declared_content_type(@file.content_type))
|
|
321
321
|
end
|
|
322
322
|
end
|
|
323
323
|
|
|
324
|
+
# Clients occasionally send more than one MIME type in a single header
|
|
325
|
+
# (e.g. "image/png; text/html" or "image/png, text/html"). Marcel 2 validates
|
|
326
|
+
# the declared type against the RFC grammar and rejects such values, so pick
|
|
327
|
+
# the first media type ourselves before handing it over.
|
|
328
|
+
def first_declared_content_type(content_type)
|
|
329
|
+
content_type.to_s.strip.split(/[;,\s]/, 2).first
|
|
330
|
+
end
|
|
331
|
+
|
|
324
332
|
# Guess content type from its file extension. Limit what to be returned to prevent spoofing.
|
|
325
333
|
def guessed_safe_content_type
|
|
326
334
|
return unless path
|
|
@@ -31,6 +31,11 @@ module CarrierWave
|
|
|
31
31
|
raise NotImplementedError, "Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage."
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
# Storage engines which defer writing to the cache have to flush it here
|
|
35
|
+
def materialize_cache!(file)
|
|
36
|
+
file
|
|
37
|
+
end
|
|
38
|
+
|
|
34
39
|
def delete_dir!(path)
|
|
35
40
|
raise NotImplementedError, "Need to implement #delete_dir! if you want to use #{self.class.name} as a cache storage."
|
|
36
41
|
end
|
|
@@ -105,7 +105,8 @@ module CarrierWave
|
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
##
|
|
108
|
-
#
|
|
108
|
+
# Stages given file to be cached, deferring the upload until #materialize_cache!,
|
|
109
|
+
# as a cached file usually goes straight to the store within the same request.
|
|
109
110
|
#
|
|
110
111
|
# === Parameters
|
|
111
112
|
#
|
|
@@ -113,12 +114,35 @@ module CarrierWave
|
|
|
113
114
|
#
|
|
114
115
|
# === Returns
|
|
115
116
|
#
|
|
116
|
-
# [CarrierWave::SanitizedFile]
|
|
117
|
+
# [CarrierWave::SanitizedFile] the staged file
|
|
118
|
+
# or
|
|
119
|
+
# [CarrierWave::Storage::Fog::File] the uploaded file, with cache_only
|
|
117
120
|
#
|
|
118
121
|
def cache!(new_file)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
# With cache_only there's no store to move to, so the cache is the final location
|
|
123
|
+
return upload_to_cache(new_file) if uploader.cache_only
|
|
124
|
+
|
|
125
|
+
local_storage.cache!(new_file)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
# Uploads the staged file to the cache directory.
|
|
130
|
+
#
|
|
131
|
+
# === Parameters
|
|
132
|
+
#
|
|
133
|
+
# [file (CarrierWave::SanitizedFile)] the staged file
|
|
134
|
+
#
|
|
135
|
+
# === Returns
|
|
136
|
+
#
|
|
137
|
+
# [CarrierWave::Storage::Fog::File] the uploaded file
|
|
138
|
+
#
|
|
139
|
+
def materialize_cache!(file)
|
|
140
|
+
return file if file.is_a?(CarrierWave::Storage::Fog::File)
|
|
141
|
+
|
|
142
|
+
upload_to_cache(file).tap do
|
|
143
|
+
file.delete
|
|
144
|
+
local_storage.delete_dir!(uploader.cache_path(nil))
|
|
145
|
+
end
|
|
122
146
|
end
|
|
123
147
|
|
|
124
148
|
##
|
|
@@ -140,10 +164,12 @@ module CarrierWave
|
|
|
140
164
|
# Deletes a cache dir
|
|
141
165
|
#
|
|
142
166
|
def delete_dir!(path)
|
|
143
|
-
#
|
|
167
|
+
# Only the local cache has dirs, as there's no such things as 'empty directory'
|
|
168
|
+
local_storage.delete_dir!(path)
|
|
144
169
|
end
|
|
145
170
|
|
|
146
171
|
def clean_cache!(seconds)
|
|
172
|
+
local_storage.clean_cache!(seconds)
|
|
147
173
|
connection.directories.new(
|
|
148
174
|
:key => uploader.fog_directory,
|
|
149
175
|
:public => uploader.fog_public
|
|
@@ -296,7 +322,7 @@ module CarrierWave
|
|
|
296
322
|
return read_source_file if ::File.exist?(file_body.path)
|
|
297
323
|
|
|
298
324
|
# If the source file doesn't exist, the remote content is read
|
|
299
|
-
|
|
325
|
+
remove_instance_variable(:@file)
|
|
300
326
|
file.body
|
|
301
327
|
end
|
|
302
328
|
|
|
@@ -308,7 +334,7 @@ module CarrierWave
|
|
|
308
334
|
# [Integer] size of file body
|
|
309
335
|
#
|
|
310
336
|
def size
|
|
311
|
-
file
|
|
337
|
+
file&.content_length || 0
|
|
312
338
|
end
|
|
313
339
|
|
|
314
340
|
##
|
|
@@ -439,7 +465,7 @@ module CarrierWave
|
|
|
439
465
|
#
|
|
440
466
|
def filename(options = {})
|
|
441
467
|
return unless (file_url = url(options))
|
|
442
|
-
|
|
468
|
+
decode_path(file_url.split('?').first).gsub(/.*\/(.*?$)/, '\1')
|
|
443
469
|
end
|
|
444
470
|
|
|
445
471
|
##
|
|
@@ -512,7 +538,9 @@ module CarrierWave
|
|
|
512
538
|
# [Fog::#{provider}::File] file data from remote service
|
|
513
539
|
#
|
|
514
540
|
def file
|
|
515
|
-
@file
|
|
541
|
+
return @file if defined?(@file)
|
|
542
|
+
|
|
543
|
+
@file = directory.files.head(path)
|
|
516
544
|
end
|
|
517
545
|
|
|
518
546
|
def copy_options
|
|
@@ -554,6 +582,18 @@ module CarrierWave
|
|
|
554
582
|
end
|
|
555
583
|
end
|
|
556
584
|
|
|
585
|
+
private
|
|
586
|
+
|
|
587
|
+
def upload_to_cache(new_file)
|
|
588
|
+
f = CarrierWave::Storage::Fog::File.new(uploader, self, uploader.cache_path)
|
|
589
|
+
f.store(new_file)
|
|
590
|
+
f
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def local_storage
|
|
594
|
+
@local_storage ||= CarrierWave::Storage::File.new(uploader)
|
|
595
|
+
end
|
|
596
|
+
|
|
557
597
|
end # Fog
|
|
558
598
|
|
|
559
599
|
end # Storage
|
|
@@ -336,6 +336,8 @@ module CarrierWave
|
|
|
336
336
|
def self.load_image(filename)
|
|
337
337
|
if defined? ::MiniMagick
|
|
338
338
|
MiniMagickWrapper.new(filename)
|
|
339
|
+
elsif defined? ::Vips
|
|
340
|
+
VipsWrapper.new(filename)
|
|
339
341
|
else
|
|
340
342
|
unless defined? ::Magick
|
|
341
343
|
begin
|
|
@@ -393,6 +395,28 @@ module CarrierWave
|
|
|
393
395
|
end
|
|
394
396
|
end
|
|
395
397
|
|
|
398
|
+
class VipsWrapper # :nodoc:
|
|
399
|
+
attr_reader :image
|
|
400
|
+
|
|
401
|
+
def width
|
|
402
|
+
image.width
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def height
|
|
406
|
+
image.height
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def format
|
|
410
|
+
# This returns the name of the vips loader (e.g. pngload) as Vips
|
|
411
|
+
# doesn't do content detection.
|
|
412
|
+
image.get("vips-loader").delete_suffix("load")
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def initialize(filename)
|
|
416
|
+
@image = ::Vips::Image.new_from_file(filename)
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
396
420
|
end # Matchers
|
|
397
421
|
end # Test
|
|
398
422
|
end # CarrierWave
|
|
@@ -151,6 +151,16 @@ module CarrierWave
|
|
|
151
151
|
end
|
|
152
152
|
end
|
|
153
153
|
|
|
154
|
+
##
|
|
155
|
+
# Makes sure the cached file is written to the cache storage, which may have
|
|
156
|
+
# deferred it. Needed when the file has to outlive the current request.
|
|
157
|
+
#
|
|
158
|
+
def materialize_cache!
|
|
159
|
+
with_callbacks(:materialize_cache) do
|
|
160
|
+
@file = cache_storage.materialize_cache!(@file) if @file
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
154
164
|
##
|
|
155
165
|
# Retrieves the file with the given cache_name from the cache.
|
|
156
166
|
#
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require "active_support/core_ext/hash/keys"
|
|
2
|
+
|
|
3
|
+
module CarrierWave
|
|
4
|
+
module Uploader
|
|
5
|
+
module Metadata
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
include CarrierWave::Uploader::Versions
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# Facts about the file which were determined when it was stored, as recorded in
|
|
12
|
+
# the column given to the +metadata+ option of the mount. They are the answer to
|
|
13
|
+
# questions the storage cannot answer, like whether a conditional version was
|
|
14
|
+
# created, and they save asking it the ones it can.
|
|
15
|
+
#
|
|
16
|
+
# Returns an empty Hash when nothing was recorded, in which case the facts are
|
|
17
|
+
# derived from the file and the uploader definition as they have always been.
|
|
18
|
+
#
|
|
19
|
+
# === Returns
|
|
20
|
+
#
|
|
21
|
+
# [Hash] the recorded facts
|
|
22
|
+
#
|
|
23
|
+
def metadata
|
|
24
|
+
return @metadata || {} unless parent_version
|
|
25
|
+
|
|
26
|
+
parent_version.metadata.dig('versions', self.class.version_names.last.to_s) || {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def metadata=(metadata)
|
|
30
|
+
@metadata = metadata&.deep_stringify_keys
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# Collects the facts to record about the file being stored. Override to record
|
|
35
|
+
# your own, merging them into the result of +super+.
|
|
36
|
+
#
|
|
37
|
+
# === Returns
|
|
38
|
+
#
|
|
39
|
+
# [Hash] the facts to record
|
|
40
|
+
#
|
|
41
|
+
def build_metadata
|
|
42
|
+
{
|
|
43
|
+
'filename' => full_filename(identifier),
|
|
44
|
+
'size' => size,
|
|
45
|
+
'content_type' => content_type,
|
|
46
|
+
'versions' => active_versions.map { |name, version| [name.to_s, version.build_metadata] }.to_h
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# === Returns
|
|
52
|
+
#
|
|
53
|
+
# [Boolean] Whether what is stored gets recorded, which the mount decides
|
|
54
|
+
#
|
|
55
|
+
def metadata_recorded?
|
|
56
|
+
model.class.respond_to?(:uploader_option) &&
|
|
57
|
+
model.class.uploaders.has_key?(mounted_as) &&
|
|
58
|
+
!!model.class.uploader_option(mounted_as, :metadata_column)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def size
|
|
62
|
+
metadata['size'] || super
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def content_type
|
|
66
|
+
metadata['content_type'] || super
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# The conditions decide which versions to create, so which ones were created is
|
|
71
|
+
# settled once the file is stored. Consult the record instead of asking them
|
|
72
|
+
# again, which is not only costly but can give a different answer.
|
|
73
|
+
#
|
|
74
|
+
def version_active?(name)
|
|
75
|
+
recorded_versions = metadata['versions']
|
|
76
|
+
return super unless recorded_versions
|
|
77
|
+
|
|
78
|
+
versions.has_key?(name.to_sym) && recorded_versions.has_key?(name.to_s)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def cache!(*)
|
|
82
|
+
@metadata = nil
|
|
83
|
+
super
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def recreate_versions!(*)
|
|
87
|
+
@metadata = nil
|
|
88
|
+
super
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def full_filename(for_file)
|
|
94
|
+
return super unless for_file == identifier
|
|
95
|
+
|
|
96
|
+
metadata['filename'] || super
|
|
97
|
+
end
|
|
98
|
+
end # Metadata
|
|
99
|
+
end # Uploader
|
|
100
|
+
end # CarrierWave
|
|
@@ -67,13 +67,17 @@ module CarrierWave
|
|
|
67
67
|
new_processors.each do |processor, processor_args|
|
|
68
68
|
self.processors += [[processor, processor_args, condition, condition_type]]
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
next unless processor == :convert
|
|
71
|
+
|
|
72
|
+
# Treat :convert specially, since it should trigger the file extension change.
|
|
73
|
+
# That has to be worked out again when the file is retrieved, where whether
|
|
74
|
+
# the condition held cannot be known, so a conditional one is refused.
|
|
75
|
+
if condition
|
|
76
|
+
raise ArgumentError, "`process convert:` cannot be given `:if` or `:unless`. The file extension follows the conversion, and it has to be worked out again when the file is retrieved, where whether the condition held is not known. " \
|
|
77
|
+
"Convert unconditionally, or put the conversion in a version of its own, or take the naming over by overriding #full_filename."
|
|
76
78
|
end
|
|
79
|
+
|
|
80
|
+
force_extension processor_args
|
|
77
81
|
end
|
|
78
82
|
end
|
|
79
83
|
end # ClassMethods
|
|
@@ -5,10 +5,26 @@ module CarrierWave
|
|
|
5
5
|
##
|
|
6
6
|
# === Returns
|
|
7
7
|
#
|
|
8
|
-
# [Boolean] Whether
|
|
8
|
+
# [Boolean] Whether a file is assigned. Use #exists? to ask the storage whether
|
|
9
|
+
# the file is actually there.
|
|
9
10
|
#
|
|
10
11
|
def blank?
|
|
11
|
-
file
|
|
12
|
+
return true unless file
|
|
13
|
+
# A cache name can point at a cache which is gone, so it is verified. It is
|
|
14
|
+
# local and thus cheap, unlike asking a remote store.
|
|
15
|
+
return file.empty? if cached?
|
|
16
|
+
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# === Returns
|
|
22
|
+
#
|
|
23
|
+
# [Boolean] Whether the file exists in the storage. Asking costs a request when
|
|
24
|
+
# the storage is a remote one.
|
|
25
|
+
#
|
|
26
|
+
def exists?
|
|
27
|
+
!!file&.exists?
|
|
12
28
|
end
|
|
13
29
|
|
|
14
30
|
##
|
|
@@ -94,6 +94,7 @@ module CarrierWave
|
|
|
94
94
|
cache!(new_file) if new_file && !cached?
|
|
95
95
|
if !cache_only && @file && @cache_id
|
|
96
96
|
with_callbacks(:store, new_file) do
|
|
97
|
+
ensure_the_filename_can_be_worked_out_again
|
|
97
98
|
new_file = storage.store!(@file)
|
|
98
99
|
if delete_tmp_file_after_storage
|
|
99
100
|
@file.delete unless move_to_store
|
|
@@ -116,8 +117,10 @@ module CarrierWave
|
|
|
116
117
|
#
|
|
117
118
|
def retrieve_from_store!(identifier)
|
|
118
119
|
with_callbacks(:retrieve_from_store, identifier) do
|
|
119
|
-
|
|
120
|
+
# Has to come first, as the storage derives the path from what was recorded
|
|
121
|
+
# for the identifier
|
|
120
122
|
@identifier = identifier
|
|
123
|
+
@file = storage.retrieve!(identifier)
|
|
121
124
|
end
|
|
122
125
|
end
|
|
123
126
|
|
|
@@ -143,6 +146,25 @@ module CarrierWave
|
|
|
143
146
|
|
|
144
147
|
private
|
|
145
148
|
|
|
149
|
+
##
|
|
150
|
+
# A later request gets the identifier and nothing else, so the name the file is
|
|
151
|
+
# stored under has to follow from it. Ask a fresh uploader what it would work out,
|
|
152
|
+
# and refuse before anything is written rather than leave the file somewhere it
|
|
153
|
+
# would never be found. Versions are left out: as long as the original is found,
|
|
154
|
+
# they can be made again. So is a mount which records what it stored, which is
|
|
155
|
+
# spared having to work the name out at all.
|
|
156
|
+
#
|
|
157
|
+
def ensure_the_filename_can_be_worked_out_again
|
|
158
|
+
return if parent_version || metadata_recorded?
|
|
159
|
+
|
|
160
|
+
for_file = deduplicated_filename
|
|
161
|
+
would_look_for = self.class.new(model, mounted_as).send(:full_filename, for_file)
|
|
162
|
+
return if would_look_for == full_filename(for_file)
|
|
163
|
+
|
|
164
|
+
raise CarrierWave::FilenameNotReproducible, "The file would be stored as #{full_filename(for_file).inspect}, but a later request given only the identifier #{for_file.inspect} would look for #{would_look_for.inspect}. " \
|
|
165
|
+
"#full_filename has to follow from the identifier, as that is all such a request gets."
|
|
166
|
+
end
|
|
167
|
+
|
|
146
168
|
def full_filename(for_file)
|
|
147
169
|
forcing_extension(for_file)
|
|
148
170
|
end
|
|
@@ -15,6 +15,9 @@ module CarrierWave
|
|
|
15
15
|
# [String] the location where this file is accessible via a url
|
|
16
16
|
#
|
|
17
17
|
def url(options = {})
|
|
18
|
+
# The URL is to be fetched by a browser, which the file has to survive this request for
|
|
19
|
+
materialize_cache! if cached?
|
|
20
|
+
|
|
18
21
|
if file.respond_to?(:url)
|
|
19
22
|
tmp_url = file.method(:url).arity.zero? ? file.url : file.url(options)
|
|
20
23
|
return tmp_url if tmp_url.present?
|
|
@@ -9,6 +9,7 @@ module CarrierWave
|
|
|
9
9
|
@options = {}
|
|
10
10
|
@blocks = []
|
|
11
11
|
@klass = nil
|
|
12
|
+
@mutex = Mutex.new
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def configure(options, &block)
|
|
@@ -18,55 +19,68 @@ module CarrierWave
|
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def build(superclass)
|
|
22
|
+
# Double-checked locking pattern for thread-safe lazy initialization.
|
|
23
|
+
# Without synchronization, concurrent threads can observe a partially
|
|
24
|
+
# initialized @klass where the class is created but version_options
|
|
25
|
+
# is not yet set, causing NoMethodError in version_active?.
|
|
21
26
|
return @klass if @klass
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
|
|
28
|
+
@mutex.synchronize do
|
|
29
|
+
return @klass if @klass
|
|
30
|
+
|
|
31
|
+
klass = Class.new(superclass)
|
|
32
|
+
superclass.const_set("VersionUploader#{@name.to_s.camelize}", klass)
|
|
33
|
+
|
|
34
|
+
klass.version_names += [@name]
|
|
35
|
+
klass.versions = {}
|
|
36
|
+
klass.processors = []
|
|
37
|
+
klass.version_options = @options
|
|
38
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
39
|
+
# Define the enable_processing method for versions so they get the
|
|
40
|
+
# value from the parent class unless explicitly overwritten
|
|
41
|
+
def self.enable_processing(value=nil)
|
|
42
|
+
self.enable_processing = value if value
|
|
43
|
+
if defined?(@enable_processing) && !@enable_processing.nil?
|
|
44
|
+
@enable_processing
|
|
45
|
+
else
|
|
46
|
+
superclass.enable_processing
|
|
47
|
+
end
|
|
38
48
|
end
|
|
39
|
-
end
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
# Regardless of what is set in the parent uploader, do not enforce the
|
|
51
|
+
# move_to_cache config option on versions because it moves the original
|
|
52
|
+
# file to the version's target file.
|
|
53
|
+
#
|
|
54
|
+
# If you want to enforce this setting on versions, override this method
|
|
55
|
+
# in each version:
|
|
56
|
+
#
|
|
57
|
+
# version :thumb do
|
|
58
|
+
# def move_to_cache
|
|
59
|
+
# true
|
|
60
|
+
# end
|
|
61
|
+
# end
|
|
62
|
+
#
|
|
63
|
+
def move_to_cache
|
|
64
|
+
false
|
|
65
|
+
end
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
# Need to rely on the parent version's identifier, as versions don't have its own one.
|
|
68
|
+
def identifier
|
|
69
|
+
parent_version.identifier
|
|
70
|
+
end
|
|
71
|
+
RUBY
|
|
72
|
+
@blocks.each { |block| klass.class_eval(&block) }
|
|
73
|
+
|
|
74
|
+
# Only assign to @klass after full initialization to ensure
|
|
75
|
+
# other threads never see a partially initialized class.
|
|
76
|
+
@klass = klass
|
|
77
|
+
end
|
|
65
78
|
end
|
|
66
79
|
|
|
67
80
|
def deep_dup
|
|
68
81
|
other = dup
|
|
69
82
|
other.instance_variable_set(:@blocks, @blocks.dup)
|
|
83
|
+
other.instance_variable_set(:@mutex, Mutex.new)
|
|
70
84
|
other
|
|
71
85
|
end
|
|
72
86
|
|
|
@@ -97,6 +111,7 @@ module CarrierWave
|
|
|
97
111
|
attr_accessor :parent_version
|
|
98
112
|
|
|
99
113
|
after :cache, :cache_versions!
|
|
114
|
+
after :materialize_cache, :materialize_versions_cache!
|
|
100
115
|
after :store, :store_versions!
|
|
101
116
|
after :remove, :remove_versions!
|
|
102
117
|
after :retrieve_from_cache, :retrieve_versions_from_cache!
|
|
@@ -105,7 +120,7 @@ module CarrierWave
|
|
|
105
120
|
prepend Module.new {
|
|
106
121
|
def initialize(*)
|
|
107
122
|
super
|
|
108
|
-
@versions = nil
|
|
123
|
+
@versions = @deferred_version_retrieval = nil
|
|
109
124
|
end
|
|
110
125
|
}
|
|
111
126
|
end
|
|
@@ -170,11 +185,16 @@ module CarrierWave
|
|
|
170
185
|
# [Hash{Symbol => CarrierWave::Uploader}] a list of uploader instances
|
|
171
186
|
#
|
|
172
187
|
def versions
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
188
|
+
unless @versions
|
|
189
|
+
@versions = {}
|
|
190
|
+
self.class.versions.each do |name, version|
|
|
191
|
+
@versions[name] = version.build(self.class).new(model, mounted_as)
|
|
192
|
+
@versions[name].parent_version = self
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
if (retrieval = @deferred_version_retrieval)
|
|
196
|
+
@deferred_version_retrieval = nil
|
|
197
|
+
active_versions.each_value { |v| v.public_send(*retrieval) }
|
|
178
198
|
end
|
|
179
199
|
@versions
|
|
180
200
|
end
|
|
@@ -331,6 +351,10 @@ module CarrierWave
|
|
|
331
351
|
derived_versions.each_value { |v| v.cache!(new_file) }
|
|
332
352
|
end
|
|
333
353
|
|
|
354
|
+
def materialize_versions_cache!
|
|
355
|
+
versions.each_value(&:materialize_cache!)
|
|
356
|
+
end
|
|
357
|
+
|
|
334
358
|
def store_versions!(new_file)
|
|
335
359
|
active_versions.each_value { |v| v.store!(new_file) }
|
|
336
360
|
end
|
|
@@ -339,12 +363,14 @@ module CarrierWave
|
|
|
339
363
|
versions.each_value { |v| v.remove! }
|
|
340
364
|
end
|
|
341
365
|
|
|
366
|
+
# Retrieval is deferred until #versions is accessed, as evaluating the versions'
|
|
367
|
+
# conditions can be costly and is pointless when no version is used
|
|
342
368
|
def retrieve_versions_from_cache!(cache_name)
|
|
343
|
-
|
|
369
|
+
@deferred_version_retrieval = [:retrieve_from_cache!, cache_name]
|
|
344
370
|
end
|
|
345
371
|
|
|
346
372
|
def retrieve_versions_from_store!(identifier)
|
|
347
|
-
|
|
373
|
+
@deferred_version_retrieval = [:retrieve_from_store!, identifier]
|
|
348
374
|
end
|
|
349
375
|
|
|
350
376
|
end # Versions
|
data/lib/carrierwave/uploader.rb
CHANGED
|
@@ -15,6 +15,7 @@ require "carrierwave/uploader/file_size"
|
|
|
15
15
|
require "carrierwave/uploader/dimension"
|
|
16
16
|
require "carrierwave/uploader/processing"
|
|
17
17
|
require "carrierwave/uploader/versions"
|
|
18
|
+
require "carrierwave/uploader/metadata"
|
|
18
19
|
require "carrierwave/uploader/default_url"
|
|
19
20
|
|
|
20
21
|
require "carrierwave/uploader/serialization"
|
|
@@ -61,6 +62,7 @@ module CarrierWave
|
|
|
61
62
|
include CarrierWave::Uploader::Dimension
|
|
62
63
|
include CarrierWave::Uploader::Processing
|
|
63
64
|
include CarrierWave::Uploader::Versions
|
|
65
|
+
include CarrierWave::Uploader::Metadata
|
|
64
66
|
include CarrierWave::Uploader::DefaultUrl
|
|
65
67
|
include CarrierWave::Uploader::Serialization
|
|
66
68
|
end # Base
|
|
@@ -3,23 +3,45 @@ require 'uri'
|
|
|
3
3
|
module CarrierWave
|
|
4
4
|
module Utilities
|
|
5
5
|
module Uri
|
|
6
|
+
# For encoding raw data into a URI component, where '%' is data and becomes '%25'.
|
|
6
7
|
# based on Ruby < 2.0's URI.encode
|
|
7
8
|
PATH_SAFE = URI::RFC2396_REGEXP::PATTERN::UNRESERVED + '\/'
|
|
8
9
|
PATH_UNSAFE = Regexp.new("[^#{PATH_SAFE}]", false)
|
|
9
|
-
NON_ASCII = /[^[:ascii:]]/.freeze
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# For repairing a string which is already a URI, following RFC 3986 per component.
|
|
12
|
+
UNRESERVED = 'A-Za-z0-9\-._~'.freeze
|
|
13
|
+
SUB_DELIMS = "!$&'()*+,;=".freeze
|
|
14
|
+
PCHAR = "#{UNRESERVED}#{SUB_DELIMS}:@".freeze
|
|
15
|
+
SANITIZE_PATH = Regexp.new("[#{PCHAR}/]").freeze
|
|
16
|
+
# Also allows [ ] { } | \ ^, which browsers and URI.parse accept in a query
|
|
17
|
+
SANITIZE_QUERY = Regexp.new("[#{PCHAR}/?\\[\\]{}|\\\\^]").freeze
|
|
18
|
+
SANITIZE_FRAGMENT = Regexp.new("[#{PCHAR}/?]").freeze
|
|
12
19
|
|
|
20
|
+
PERCENT_ENCODED = /%[0-9a-fA-F]{2}/.freeze
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# Not idempotent, as '%' is escaped to '%25' every time
|
|
13
25
|
def encode_path(path)
|
|
14
26
|
URI::DEFAULT_PARSER.escape(path, PATH_UNSAFE)
|
|
15
27
|
end
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
# Only for strings not sent over the wire, like a filename to be shown to the user.
|
|
30
|
+
# CGI.unescape is for form encoding and would turn '+' into a space.
|
|
31
|
+
def decode_path(str)
|
|
32
|
+
URI::DEFAULT_PARSER.unescape(str)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Escapes only what cannot appear in the component, leaving existing %XX alone.
|
|
36
|
+
# Decoding first would make %2F and '/', %2B and '+' indistinguishable.
|
|
37
|
+
def sanitize_component(str, allowed)
|
|
38
|
+
str.gsub(/(#{PERCENT_ENCODED})|(.)/m) do
|
|
39
|
+
$1 || ($2.match?(allowed) ? $2 : escape_octets($2))
|
|
40
|
+
end
|
|
19
41
|
end
|
|
20
42
|
|
|
21
|
-
def
|
|
22
|
-
|
|
43
|
+
def escape_octets(str)
|
|
44
|
+
str.b.bytes.map { |byte| format('%%%02X', byte) }.join
|
|
23
45
|
end
|
|
24
46
|
end # Uri
|
|
25
47
|
end # Utilities
|
data/lib/carrierwave/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: carrierwave
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.0.beta
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jonas Nicklas
|
|
@@ -15,56 +15,68 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: 7.0.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
25
|
+
version: 7.0.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: activemodel
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
32
|
+
version: 7.0.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version:
|
|
39
|
+
version: 7.0.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: image_processing
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
46
|
version: '1.1'
|
|
47
|
+
- - "<"
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '3'
|
|
47
50
|
type: :runtime
|
|
48
51
|
prerelease: false
|
|
49
52
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
53
|
requirements:
|
|
51
|
-
- - "
|
|
54
|
+
- - ">="
|
|
52
55
|
- !ruby/object:Gem::Version
|
|
53
56
|
version: '1.1'
|
|
57
|
+
- - "<"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '3'
|
|
54
60
|
- !ruby/object:Gem::Dependency
|
|
55
61
|
name: marcel
|
|
56
62
|
requirement: !ruby/object:Gem::Requirement
|
|
57
63
|
requirements:
|
|
58
|
-
- - "
|
|
64
|
+
- - ">="
|
|
59
65
|
- !ruby/object:Gem::Version
|
|
60
66
|
version: 1.0.0
|
|
67
|
+
- - "<"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '3'
|
|
61
70
|
type: :runtime
|
|
62
71
|
prerelease: false
|
|
63
72
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
73
|
requirements:
|
|
65
|
-
- - "
|
|
74
|
+
- - ">="
|
|
66
75
|
- !ruby/object:Gem::Version
|
|
67
76
|
version: 1.0.0
|
|
77
|
+
- - "<"
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '3'
|
|
68
80
|
- !ruby/object:Gem::Dependency
|
|
69
81
|
name: addressable
|
|
70
82
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -239,6 +251,20 @@ dependencies:
|
|
|
239
251
|
- - ">="
|
|
240
252
|
- !ruby/object:Gem::Version
|
|
241
253
|
version: '0'
|
|
254
|
+
- !ruby/object:Gem::Dependency
|
|
255
|
+
name: ruby-vips
|
|
256
|
+
requirement: !ruby/object:Gem::Requirement
|
|
257
|
+
requirements:
|
|
258
|
+
- - ">="
|
|
259
|
+
- !ruby/object:Gem::Version
|
|
260
|
+
version: '0'
|
|
261
|
+
type: :development
|
|
262
|
+
prerelease: false
|
|
263
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
264
|
+
requirements:
|
|
265
|
+
- - ">="
|
|
266
|
+
- !ruby/object:Gem::Version
|
|
267
|
+
version: '0'
|
|
242
268
|
- !ruby/object:Gem::Dependency
|
|
243
269
|
name: rmagick
|
|
244
270
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -350,6 +376,7 @@ files:
|
|
|
350
376
|
- lib/carrierwave/uploader/extension_allowlist.rb
|
|
351
377
|
- lib/carrierwave/uploader/extension_denylist.rb
|
|
352
378
|
- lib/carrierwave/uploader/file_size.rb
|
|
379
|
+
- lib/carrierwave/uploader/metadata.rb
|
|
353
380
|
- lib/carrierwave/uploader/mountable.rb
|
|
354
381
|
- lib/carrierwave/uploader/processing.rb
|
|
355
382
|
- lib/carrierwave/uploader/proxy.rb
|
|
@@ -378,14 +405,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
378
405
|
requirements:
|
|
379
406
|
- - ">="
|
|
380
407
|
- !ruby/object:Gem::Version
|
|
381
|
-
version: 2.
|
|
408
|
+
version: 2.7.0
|
|
382
409
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
383
410
|
requirements:
|
|
384
411
|
- - ">="
|
|
385
412
|
- !ruby/object:Gem::Version
|
|
386
413
|
version: '0'
|
|
387
414
|
requirements: []
|
|
388
|
-
rubygems_version:
|
|
415
|
+
rubygems_version: 4.0.19
|
|
389
416
|
specification_version: 4
|
|
390
417
|
summary: Ruby file upload library
|
|
391
418
|
test_files: []
|