salebot_uploader 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|