salebot_uploader 1.0.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 +7 -0
- data/README.md +0 -0
- data/lib/generators/templates/uploader.rb.erb +9 -0
- data/lib/generators/uploader_generator.rb +7 -0
- data/lib/salebot_uploader/compatibility/paperclip.rb +104 -0
- data/lib/salebot_uploader/downloader/base.rb +101 -0
- data/lib/salebot_uploader/downloader/remote_file.rb +68 -0
- data/lib/salebot_uploader/error.rb +8 -0
- data/lib/salebot_uploader/locale/en.yml +17 -0
- data/lib/salebot_uploader/mount.rb +446 -0
- data/lib/salebot_uploader/mounter.rb +255 -0
- data/lib/salebot_uploader/orm/activerecord.rb +68 -0
- data/lib/salebot_uploader/processing/mini_magick.rb +194 -0
- data/lib/salebot_uploader/processing/rmagick.rb +402 -0
- data/lib/salebot_uploader/processing/vips.rb +284 -0
- data/lib/salebot_uploader/processing.rb +3 -0
- data/lib/salebot_uploader/sanitized_file.rb +357 -0
- data/lib/salebot_uploader/storage/abstract.rb +41 -0
- data/lib/salebot_uploader/storage/file.rb +124 -0
- data/lib/salebot_uploader/storage/fog.rb +547 -0
- data/lib/salebot_uploader/storage.rb +3 -0
- data/lib/salebot_uploader/test/matchers.rb +398 -0
- data/lib/salebot_uploader/uploader/cache.rb +223 -0
- data/lib/salebot_uploader/uploader/callbacks.rb +33 -0
- data/lib/salebot_uploader/uploader/configuration.rb +184 -0
- data/lib/salebot_uploader/uploader/content_type_allowlist.rb +61 -0
- data/lib/salebot_uploader/uploader/content_type_denylist.rb +62 -0
- data/lib/salebot_uploader/uploader/default_url.rb +17 -0
- data/lib/salebot_uploader/uploader/dimension.rb +66 -0
- data/lib/salebot_uploader/uploader/download.rb +24 -0
- data/lib/salebot_uploader/uploader/extension_allowlist.rb +63 -0
- data/lib/salebot_uploader/uploader/extension_denylist.rb +64 -0
- data/lib/salebot_uploader/uploader/file_size.rb +43 -0
- data/lib/salebot_uploader/uploader/mountable.rb +44 -0
- data/lib/salebot_uploader/uploader/processing.rb +125 -0
- data/lib/salebot_uploader/uploader/proxy.rb +99 -0
- data/lib/salebot_uploader/uploader/remove.rb +21 -0
- data/lib/salebot_uploader/uploader/serialization.rb +28 -0
- data/lib/salebot_uploader/uploader/store.rb +142 -0
- data/lib/salebot_uploader/uploader/url.rb +44 -0
- data/lib/salebot_uploader/uploader/versions.rb +350 -0
- data/lib/salebot_uploader/uploader.rb +53 -0
- data/lib/salebot_uploader/utilities/file_name.rb +47 -0
- data/lib/salebot_uploader/utilities/uri.rb +26 -0
- data/lib/salebot_uploader/utilities.rb +7 -0
- data/lib/salebot_uploader/validations/active_model.rb +76 -0
- data/lib/salebot_uploader/version.rb +3 -0
- data/lib/salebot_uploader.rb +62 -0
- metadata +392 -0
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'salebot_uploader/downloader/base'
|
2
|
+
|
3
|
+
module SalebotUploader
|
4
|
+
|
5
|
+
module Uploader
|
6
|
+
module Configuration
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute :_storage, :_cache_storage, :instance_writer => false
|
11
|
+
|
12
|
+
add_config :root
|
13
|
+
add_config :base_path
|
14
|
+
add_config :asset_host
|
15
|
+
add_config :permissions
|
16
|
+
add_config :directory_permissions
|
17
|
+
add_config :storage_engines
|
18
|
+
add_config :store_dir
|
19
|
+
add_config :cache_dir
|
20
|
+
add_config :enable_processing
|
21
|
+
add_config :ensure_multipart_form
|
22
|
+
add_config :delete_tmp_file_after_storage
|
23
|
+
add_config :move_to_cache
|
24
|
+
add_config :move_to_store
|
25
|
+
add_config :remove_previously_stored_files_after_update
|
26
|
+
add_config :downloader
|
27
|
+
add_config :force_extension
|
28
|
+
|
29
|
+
# fog
|
30
|
+
add_deprecated_config :fog_provider
|
31
|
+
add_config :fog_attributes
|
32
|
+
add_config :fog_credentials
|
33
|
+
add_config :fog_directory
|
34
|
+
add_config :fog_public
|
35
|
+
add_config :fog_authenticated_url_expiration
|
36
|
+
add_config :fog_use_ssl_for_aws
|
37
|
+
add_config :fog_aws_accelerate
|
38
|
+
|
39
|
+
# Mounting
|
40
|
+
add_config :ignore_integrity_errors
|
41
|
+
add_config :ignore_processing_errors
|
42
|
+
add_config :ignore_download_errors
|
43
|
+
add_config :validate_integrity
|
44
|
+
add_config :validate_processing
|
45
|
+
add_config :validate_download
|
46
|
+
add_config :mount_on
|
47
|
+
add_config :cache_only
|
48
|
+
add_config :download_retry_count
|
49
|
+
add_config :download_retry_wait_time
|
50
|
+
add_config :skip_ssrf_protection
|
51
|
+
|
52
|
+
# set default values
|
53
|
+
reset_config
|
54
|
+
end
|
55
|
+
|
56
|
+
module ClassMethods
|
57
|
+
def storage(storage = nil)
|
58
|
+
case storage
|
59
|
+
when Symbol
|
60
|
+
if (storage_engine = storage_engines[storage])
|
61
|
+
self._storage = eval storage_engine
|
62
|
+
else
|
63
|
+
raise SalebotUploader::UnknownStorageError, "Unknown storage: #{storage}"
|
64
|
+
end
|
65
|
+
when nil
|
66
|
+
storage
|
67
|
+
else
|
68
|
+
self._storage = storage
|
69
|
+
end
|
70
|
+
_storage
|
71
|
+
end
|
72
|
+
alias_method :storage=, :storage
|
73
|
+
|
74
|
+
def cache_storage(storage = false)
|
75
|
+
unless storage == false
|
76
|
+
self._cache_storage = storage.is_a?(Symbol) ? eval(storage_engines[storage]) : storage
|
77
|
+
end
|
78
|
+
_cache_storage
|
79
|
+
end
|
80
|
+
alias_method :cache_storage=, :cache_storage
|
81
|
+
|
82
|
+
def add_config(name)
|
83
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
84
|
+
@#{name} = nil
|
85
|
+
|
86
|
+
def self.#{name}(value=nil)
|
87
|
+
@#{name} = value unless value.nil?
|
88
|
+
return @#{name} if self.object_id == #{self.object_id} || defined?(@#{name})
|
89
|
+
name = superclass.#{name}
|
90
|
+
return nil if name.nil? && !instance_variable_defined?(:@#{name})
|
91
|
+
@#{name} = name && !name.is_a?(Module) && !name.is_a?(Symbol) && !name.is_a?(Numeric) && !name.is_a?(TrueClass) && !name.is_a?(FalseClass) ? name.dup : name
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.#{name}=(value)
|
95
|
+
@#{name} = value
|
96
|
+
end
|
97
|
+
|
98
|
+
def #{name}=(value)
|
99
|
+
@#{name} = value
|
100
|
+
end
|
101
|
+
|
102
|
+
def #{name}
|
103
|
+
value = @#{name} if instance_variable_defined?(:@#{name})
|
104
|
+
value = self.class.#{name} unless instance_variable_defined?(:@#{name})
|
105
|
+
if value.instance_of?(Proc)
|
106
|
+
value.arity >= 1 ? value.call(self) : value.call
|
107
|
+
else
|
108
|
+
value
|
109
|
+
end
|
110
|
+
end
|
111
|
+
RUBY
|
112
|
+
end
|
113
|
+
|
114
|
+
def add_deprecated_config(name)
|
115
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
116
|
+
def self.#{name}(value=nil)
|
117
|
+
ActiveSupport::Deprecation.warn "##{name} is deprecated and has no effect"
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.#{name}=(value)
|
121
|
+
ActiveSupport::Deprecation.warn "##{name} is deprecated and has no effect"
|
122
|
+
end
|
123
|
+
|
124
|
+
def #{name}=(value)
|
125
|
+
ActiveSupport::Deprecation.warn "##{name} is deprecated and has no effect"
|
126
|
+
end
|
127
|
+
|
128
|
+
def #{name}
|
129
|
+
ActiveSupport::Deprecation.warn "##{name} is deprecated and has no effect"
|
130
|
+
end
|
131
|
+
RUBY
|
132
|
+
end
|
133
|
+
|
134
|
+
def configure
|
135
|
+
yield self
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# sets configuration back to default
|
140
|
+
#
|
141
|
+
def reset_config
|
142
|
+
configure do |config|
|
143
|
+
config.permissions = 0o644
|
144
|
+
config.directory_permissions = 0o755
|
145
|
+
config.storage_engines = {
|
146
|
+
:file => "SalebotUploader::Storage::File",
|
147
|
+
:fog => "SalebotUploader::Storage::Fog"
|
148
|
+
}
|
149
|
+
config.storage = :file
|
150
|
+
config.cache_storage = nil
|
151
|
+
config.fog_attributes = {}
|
152
|
+
config.fog_credentials = {}
|
153
|
+
config.fog_public = true
|
154
|
+
config.fog_authenticated_url_expiration = 600
|
155
|
+
config.fog_use_ssl_for_aws = true
|
156
|
+
config.fog_aws_accelerate = false
|
157
|
+
config.store_dir = 'uploads'
|
158
|
+
config.cache_dir = 'uploads/tmp'
|
159
|
+
config.delete_tmp_file_after_storage = true
|
160
|
+
config.move_to_cache = false
|
161
|
+
config.move_to_store = false
|
162
|
+
config.remove_previously_stored_files_after_update = true
|
163
|
+
config.downloader = SalebotUploader::Downloader::Base
|
164
|
+
config.force_extension = false
|
165
|
+
config.ignore_integrity_errors = true
|
166
|
+
config.ignore_processing_errors = true
|
167
|
+
config.ignore_download_errors = true
|
168
|
+
config.validate_integrity = true
|
169
|
+
config.validate_processing = true
|
170
|
+
config.validate_download = true
|
171
|
+
config.root = lambda { SalebotUploader.root }
|
172
|
+
config.base_path = SalebotUploader.base_path
|
173
|
+
config.enable_processing = true
|
174
|
+
config.ensure_multipart_form = true
|
175
|
+
config.download_retry_count = 0
|
176
|
+
config.download_retry_wait_time = 5
|
177
|
+
config.skip_ssrf_protection = false
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module ContentTypeAllowlist
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
before :cache, :check_content_type_allowlist!
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Override this method in your uploader to provide an allowlist of files content types
|
12
|
+
# which are allowed to be uploaded.
|
13
|
+
# Not only strings but Regexp are allowed as well.
|
14
|
+
#
|
15
|
+
# === Returns
|
16
|
+
#
|
17
|
+
# [NilClass, String, Regexp, Array[String, Regexp]] an allowlist of content types which are allowed to be uploaded
|
18
|
+
#
|
19
|
+
# === Examples
|
20
|
+
#
|
21
|
+
# def content_type_allowlist
|
22
|
+
# %w(text/json application/json)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# Basically the same, but using a Regexp:
|
26
|
+
#
|
27
|
+
# def content_type_allowlist
|
28
|
+
# [/(text|application)\/json/]
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
def content_type_allowlist; end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def check_content_type_allowlist!(new_file)
|
36
|
+
allowlist = content_type_allowlist
|
37
|
+
if !allowlist && respond_to?(:content_type_whitelist) && content_type_whitelist
|
38
|
+
ActiveSupport::Deprecation.warn "#content_type_whitelist is deprecated, use #content_type_allowlist instead." unless instance_variable_defined?(:@content_type_whitelist_warned)
|
39
|
+
@content_type_whitelist_warned = true
|
40
|
+
allowlist = content_type_whitelist
|
41
|
+
end
|
42
|
+
|
43
|
+
return unless allowlist
|
44
|
+
|
45
|
+
content_type = new_file.content_type
|
46
|
+
if !allowlisted_content_type?(allowlist, content_type)
|
47
|
+
raise SalebotUploader::IntegrityError, I18n.translate(:"errors.messages.content_type_allowlist_error", content_type: content_type,
|
48
|
+
allowed_types: Array(allowlist).join(", "), default: :"errors.messages.content_type_whitelist_error")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def allowlisted_content_type?(allowlist, content_type)
|
53
|
+
Array(allowlist).any? do |item|
|
54
|
+
item = Regexp.quote(item) if item.class != Regexp
|
55
|
+
content_type =~ /\A#{item}/
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end # ContentTypeAllowlist
|
60
|
+
end # Uploader
|
61
|
+
end # SalebotUploader
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module ContentTypeDenylist
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
before :cache, :check_content_type_denylist!
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Override this method in your uploader to provide a denylist of files content types
|
12
|
+
# which are not allowed to be uploaded.
|
13
|
+
# Not only strings but Regexp are allowed as well.
|
14
|
+
#
|
15
|
+
# === Returns
|
16
|
+
#
|
17
|
+
# [NilClass, String, Regexp, Array[String, Regexp]] a denylist of content types which are not allowed to be uploaded
|
18
|
+
#
|
19
|
+
# === Examples
|
20
|
+
#
|
21
|
+
# def content_type_denylist
|
22
|
+
# %w(text/json application/json)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# Basically the same, but using a Regexp:
|
26
|
+
#
|
27
|
+
# def content_type_denylist
|
28
|
+
# [/(text|application)\/json/]
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
def content_type_denylist
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def check_content_type_denylist!(new_file)
|
37
|
+
denylist = content_type_denylist
|
38
|
+
if !denylist && respond_to?(:content_type_blacklist) && content_type_blacklist
|
39
|
+
ActiveSupport::Deprecation.warn "#content_type_blacklist is deprecated, use #content_type_denylist instead." unless instance_variable_defined?(:@content_type_blacklist_warned)
|
40
|
+
@content_type_blacklist_warned = true
|
41
|
+
denylist = content_type_blacklist
|
42
|
+
end
|
43
|
+
|
44
|
+
return unless denylist
|
45
|
+
|
46
|
+
ActiveSupport::Deprecation.warn "Use of #content_type_denylist is deprecated for the security reason, use #content_type_allowlist instead to explicitly state what are safe to accept" unless instance_variable_defined?(:@content_type_denylist_warned)
|
47
|
+
@content_type_denylist_warned = true
|
48
|
+
|
49
|
+
content_type = new_file.content_type
|
50
|
+
if denylisted_content_type?(denylist, content_type)
|
51
|
+
raise SalebotUploader::IntegrityError, I18n.translate(:"errors.messages.content_type_denylist_error",
|
52
|
+
content_type: content_type, default: :"errors.messages.content_type_blacklist_error")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def denylisted_content_type?(denylist, content_type)
|
57
|
+
Array(denylist).any? { |item| content_type =~ /#{item}/ }
|
58
|
+
end
|
59
|
+
|
60
|
+
end # ContentTypeDenylist
|
61
|
+
end # Uploader
|
62
|
+
end # SalebotUploader
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module DefaultUrl
|
4
|
+
|
5
|
+
def url(*args)
|
6
|
+
super || default_url(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
##
|
10
|
+
# Override this method in your uploader to provide a default url
|
11
|
+
# in case no file has been cached/stored yet.
|
12
|
+
#
|
13
|
+
def default_url(*args); end
|
14
|
+
|
15
|
+
end # DefaultPath
|
16
|
+
end # Uploader
|
17
|
+
end # SalebotUploader
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
module SalebotUploader
|
4
|
+
module Uploader
|
5
|
+
module Dimension
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
before :cache, :check_dimensions!
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Override this method in your uploader to provide a Range of width which
|
14
|
+
# are allowed to be uploaded.
|
15
|
+
# === Returns
|
16
|
+
#
|
17
|
+
# [NilClass, Range] a width range which are permitted to be uploaded
|
18
|
+
#
|
19
|
+
# === Examples
|
20
|
+
#
|
21
|
+
# def width_range
|
22
|
+
# 1000..2000
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
def width_range; end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Override this method in your uploader to provide a Range of height which
|
29
|
+
# are allowed to be uploaded.
|
30
|
+
# === Returns
|
31
|
+
#
|
32
|
+
# [NilClass, Range] a height range which are permitted to be uploaded
|
33
|
+
#
|
34
|
+
# === Examples
|
35
|
+
#
|
36
|
+
# def height_range
|
37
|
+
# 1000..
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
def height_range; end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def check_dimensions!(new_file)
|
45
|
+
# NOTE: Skip the check for resized images
|
46
|
+
return if version_name.present?
|
47
|
+
return unless width_range || height_range
|
48
|
+
|
49
|
+
unless respond_to?(:width) || respond_to?(:height)
|
50
|
+
raise 'You need to include one of SalebotUploader::MiniMagick, SalebotUploader::RMagick, or SalebotUploader::Vips to perform image dimension validation'
|
51
|
+
end
|
52
|
+
|
53
|
+
if width_range&.begin && width < width_range.begin
|
54
|
+
raise SalebotUploader::IntegrityError, I18n.translate(:"errors.messages.min_width_error", :min_width => ActiveSupport::NumberHelper.number_to_delimited(width_range.begin))
|
55
|
+
elsif width_range&.end && width > width_range.end
|
56
|
+
raise SalebotUploader::IntegrityError, I18n.translate(:"errors.messages.max_width_error", :max_width => ActiveSupport::NumberHelper.number_to_delimited(width_range.end))
|
57
|
+
elsif height_range&.begin && height < height_range.begin
|
58
|
+
raise SalebotUploader::IntegrityError, I18n.translate(:"errors.messages.min_height_error", :min_height => ActiveSupport::NumberHelper.number_to_delimited(height_range.begin))
|
59
|
+
elsif height_range&.end && height > height_range.end
|
60
|
+
raise SalebotUploader::IntegrityError, I18n.translate(:"errors.messages.max_height_error", :max_height => ActiveSupport::NumberHelper.number_to_delimited(height_range.end))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end # Dimension
|
65
|
+
end # Uploader
|
66
|
+
end # SalebotUploader
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module Download
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
include SalebotUploader::Uploader::Callbacks
|
7
|
+
include SalebotUploader::Uploader::Configuration
|
8
|
+
include SalebotUploader::Uploader::Cache
|
9
|
+
|
10
|
+
##
|
11
|
+
# Caches the file by downloading it from the given URL, using downloader.
|
12
|
+
#
|
13
|
+
# === Parameters
|
14
|
+
#
|
15
|
+
# [url (String)] The URL where the remote file is stored
|
16
|
+
# [remote_headers (Hash)] Request headers
|
17
|
+
#
|
18
|
+
def download!(uri, remote_headers = {})
|
19
|
+
file = downloader.new(self).download(uri, remote_headers)
|
20
|
+
cache!(file)
|
21
|
+
end
|
22
|
+
end # Download
|
23
|
+
end # Uploader
|
24
|
+
end # SalebotUploader
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module ExtensionAllowlist
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
before :cache, :check_extension_allowlist!
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Override this method in your uploader to provide an allowlist of extensions which
|
12
|
+
# are allowed to be uploaded. Compares the file's extension case insensitive.
|
13
|
+
# Furthermore, not only strings but Regexp are allowed as well.
|
14
|
+
#
|
15
|
+
# When using a Regexp in the allowlist, `\A` and `\z` are automatically added to
|
16
|
+
# the Regexp expression, also case insensitive.
|
17
|
+
#
|
18
|
+
# === Returns
|
19
|
+
#
|
20
|
+
# [NilClass, String, Regexp, Array[String, Regexp]] an allowlist of extensions which are allowed to be uploaded
|
21
|
+
#
|
22
|
+
# === Examples
|
23
|
+
#
|
24
|
+
# def extension_allowlist
|
25
|
+
# %w(jpg jpeg gif png)
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# Basically the same, but using a Regexp:
|
29
|
+
#
|
30
|
+
# def extension_allowlist
|
31
|
+
# [/jpe?g/, 'gif', 'png']
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
def extension_allowlist
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def check_extension_allowlist!(new_file)
|
40
|
+
allowlist = extension_allowlist
|
41
|
+
if !allowlist && respond_to?(:extension_whitelist) && extension_whitelist
|
42
|
+
ActiveSupport::Deprecation.warn "#extension_whitelist is deprecated, use #extension_allowlist instead." unless instance_variable_defined?(:@extension_whitelist_warned)
|
43
|
+
@extension_whitelist_warned = true
|
44
|
+
allowlist = extension_whitelist
|
45
|
+
end
|
46
|
+
|
47
|
+
return unless allowlist
|
48
|
+
|
49
|
+
extension = new_file.extension.to_s
|
50
|
+
if !allowlisted_extension?(allowlist, extension)
|
51
|
+
# Look for whitelist first, then fallback to allowlist
|
52
|
+
raise SalebotUploader::IntegrityError, I18n.translate(:"errors.messages.extension_allowlist_error", extension: new_file.extension.inspect,
|
53
|
+
allowed_types: Array(allowlist).join(", "), default: :"errors.messages.extension_whitelist_error")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def allowlisted_extension?(allowlist, extension)
|
58
|
+
downcase_extension = extension.downcase
|
59
|
+
Array(allowlist).any? { |item| downcase_extension =~ /\A#{item}\z/i }
|
60
|
+
end
|
61
|
+
end # ExtensionAllowlist
|
62
|
+
end # Uploader
|
63
|
+
end # SalebotUploader
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module ExtensionDenylist
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
before :cache, :check_extension_denylist!
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Override this method in your uploader to provide a denylist of extensions which
|
12
|
+
# are prohibited to be uploaded. Compares the file's extension case insensitive.
|
13
|
+
# Furthermore, not only strings but Regexp are allowed as well.
|
14
|
+
#
|
15
|
+
# When using a Regexp in the denylist, `\A` and `\z` are automatically added to
|
16
|
+
# the Regexp expression, also case insensitive.
|
17
|
+
#
|
18
|
+
# === Returns
|
19
|
+
|
20
|
+
# [NilClass, String, Regexp, Array[String, Regexp]] a deny list of extensions which are prohibited to be uploaded
|
21
|
+
#
|
22
|
+
# === Examples
|
23
|
+
#
|
24
|
+
# def extension_denylist
|
25
|
+
# %w(swf tiff)
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# Basically the same, but using a Regexp:
|
29
|
+
#
|
30
|
+
# def extension_denylist
|
31
|
+
# [/swf/, 'tiff']
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
def extension_denylist
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def check_extension_denylist!(new_file)
|
40
|
+
denylist = extension_denylist
|
41
|
+
if !denylist && respond_to?(:extension_blacklist) && extension_blacklist
|
42
|
+
ActiveSupport::Deprecation.warn "#extension_blacklist is deprecated, use #extension_denylist instead." unless instance_variable_defined?(:@extension_blacklist_warned)
|
43
|
+
@extension_blacklist_warned = true
|
44
|
+
denylist = extension_blacklist
|
45
|
+
end
|
46
|
+
|
47
|
+
return unless denylist
|
48
|
+
|
49
|
+
ActiveSupport::Deprecation.warn "Use of #extension_denylist is deprecated for the security reason, use #extension_allowlist instead to explicitly state what are safe to accept" unless instance_variable_defined?(:@extension_denylist_warned)
|
50
|
+
@extension_denylist_warned = true
|
51
|
+
|
52
|
+
extension = new_file.extension.to_s
|
53
|
+
if denylisted_extension?(denylist, extension)
|
54
|
+
raise SalebotUploader::IntegrityError, I18n.translate(:"errors.messages.extension_denylist_error", extension: new_file.extension.inspect,
|
55
|
+
prohibited_types: Array(extension_denylist).join(", "), default: :"errors.messages.extension_blacklist_error")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def denylisted_extension?(denylist, extension)
|
60
|
+
Array(denylist).any? { |item| extension =~ /\A#{item}\z/i }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
module SalebotUploader
|
4
|
+
module Uploader
|
5
|
+
module FileSize
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
before :cache, :check_size!
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Override this method in your uploader to provide a Range of Size which
|
14
|
+
# are allowed to be uploaded.
|
15
|
+
# === Returns
|
16
|
+
#
|
17
|
+
# [NilClass, Range] a size range (in bytes) which are permitted to be uploaded
|
18
|
+
#
|
19
|
+
# === Examples
|
20
|
+
#
|
21
|
+
# def size_range
|
22
|
+
# 3256...5748
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
def size_range; end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def check_size!(new_file)
|
30
|
+
size = new_file.size
|
31
|
+
expected_size_range = size_range
|
32
|
+
if expected_size_range.is_a?(::Range)
|
33
|
+
if size < expected_size_range.min
|
34
|
+
raise SalebotUploader::IntegrityError, I18n.translate(:"errors.messages.min_size_error", :min_size => ActiveSupport::NumberHelper.number_to_human_size(expected_size_range.min))
|
35
|
+
elsif size > expected_size_range.max
|
36
|
+
raise SalebotUploader::IntegrityError, I18n.translate(:"errors.messages.max_size_error", :max_size => ActiveSupport::NumberHelper.number_to_human_size(expected_size_range.max))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end # FileSize
|
42
|
+
end # Uploader
|
43
|
+
end # SalebotUploader
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module Mountable
|
4
|
+
|
5
|
+
attr_reader :model, :mounted_as
|
6
|
+
|
7
|
+
##
|
8
|
+
# If a model is given as the first parameter, it will be stored in the
|
9
|
+
# uploader, and available through +#model+. Likewise, mounted_as stores
|
10
|
+
# the name of the column where this instance of the uploader is mounted.
|
11
|
+
# These values can then be used inside your uploader.
|
12
|
+
#
|
13
|
+
# If you do not wish to mount your uploaders with the ORM extensions in
|
14
|
+
# -more then you can override this method inside your uploader. Just be
|
15
|
+
# sure to call +super+
|
16
|
+
#
|
17
|
+
# === Parameters
|
18
|
+
#
|
19
|
+
# [model (Object)] Any kind of model object
|
20
|
+
# [mounted_as (Symbol)] The name of the column where this uploader is mounted
|
21
|
+
#
|
22
|
+
# === Examples
|
23
|
+
#
|
24
|
+
# class MyUploader < SalebotUploader::Uploader::Base
|
25
|
+
#
|
26
|
+
# def store_dir
|
27
|
+
# File.join('public', 'files', mounted_as, model.permalink)
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
def initialize(model=nil, mounted_as=nil)
|
32
|
+
@model = model
|
33
|
+
@mounted_as = mounted_as
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Returns array index of given uploader within currently mounted uploaders
|
38
|
+
#
|
39
|
+
def index
|
40
|
+
model.__send__(:_mounter, mounted_as).uploaders.index(self)
|
41
|
+
end
|
42
|
+
end # Mountable
|
43
|
+
end # Uploader
|
44
|
+
end # SalebotUploader
|