2019-05-02 18:17:27 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-04-14 16:55:57 -04:00
|
|
|
require_dependency "file_helper"
|
|
|
|
|
|
|
|
module Validators; end
|
|
|
|
|
2019-10-02 00:01:53 -04:00
|
|
|
class UploadValidator < ActiveModel::Validator
|
2014-04-14 16:55:57 -04:00
|
|
|
|
|
|
|
def validate(upload)
|
2017-06-12 16:41:29 -04:00
|
|
|
# staff can upload any file in PM
|
2018-11-14 02:03:02 -05:00
|
|
|
if (upload.for_private_message && SiteSetting.allow_staff_to_upload_any_file_in_pm)
|
2017-06-12 16:41:29 -04:00
|
|
|
return true if upload.user&.staff?
|
|
|
|
end
|
|
|
|
|
2020-07-26 20:23:54 -04:00
|
|
|
# check the attachment blocklist
|
2017-06-12 16:41:29 -04:00
|
|
|
if upload.for_group_message && SiteSetting.allow_all_attachments_for_group_messages
|
2020-07-26 20:23:54 -04:00
|
|
|
return upload.original_filename =~ SiteSetting.blocked_attachment_filenames_regex
|
2016-06-27 16:26:05 -04:00
|
|
|
end
|
2016-02-29 16:39:24 -05:00
|
|
|
|
2014-07-14 21:15:17 -04:00
|
|
|
extension = File.extname(upload.original_filename)[1..-1] || ""
|
2014-04-14 16:55:57 -04:00
|
|
|
|
2018-11-14 02:03:02 -05:00
|
|
|
if upload.for_site_setting &&
|
|
|
|
upload.user&.staff? &&
|
|
|
|
FileHelper.is_supported_image?(upload.original_filename)
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2019-07-30 23:16:03 -04:00
|
|
|
if upload.for_gravatar &&
|
|
|
|
FileHelper.supported_gravatar_extensions.include?(extension)
|
|
|
|
|
|
|
|
maximum_image_file_size(upload)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2022-04-20 00:11:39 -04:00
|
|
|
return true if changing_upload_security?(upload)
|
|
|
|
|
2014-04-14 16:55:57 -04:00
|
|
|
if is_authorized?(upload, extension)
|
2018-09-09 22:22:45 -04:00
|
|
|
if FileHelper.is_supported_image?(upload.original_filename)
|
2014-04-14 16:55:57 -04:00
|
|
|
authorized_image_extension(upload, extension)
|
|
|
|
maximum_image_file_size(upload)
|
|
|
|
else
|
|
|
|
authorized_attachment_extension(upload, extension)
|
|
|
|
maximum_attachment_file_size(upload)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-04-20 00:11:39 -04:00
|
|
|
# this should only be run on existing records, and covers cases of
|
|
|
|
# upload.update_secure_status being run outside of the creation flow,
|
|
|
|
# where some cases e.g. have exemptions on the extension enforcement
|
|
|
|
def changing_upload_security?(upload)
|
|
|
|
!upload.new_record? && \
|
|
|
|
upload.changed_attributes.keys.all? do |attribute|
|
|
|
|
%w(secure security_last_changed_at security_last_changed_reason).include?(attribute)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-04-14 16:55:57 -04:00
|
|
|
def is_authorized?(upload, extension)
|
2018-02-19 04:44:24 -05:00
|
|
|
extension_authorized?(upload, extension, authorized_extensions(upload))
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def authorized_image_extension(upload, extension)
|
2018-02-19 04:44:24 -05:00
|
|
|
extension_authorized?(upload, extension, authorized_images(upload))
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def maximum_image_file_size(upload)
|
|
|
|
maximum_file_size(upload, "image")
|
|
|
|
end
|
|
|
|
|
|
|
|
def authorized_attachment_extension(upload, extension)
|
2018-02-19 04:44:24 -05:00
|
|
|
extension_authorized?(upload, extension, authorized_attachments(upload))
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def maximum_attachment_file_size(upload)
|
|
|
|
maximum_file_size(upload, "attachment")
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2018-02-19 04:44:24 -05:00
|
|
|
def extensions_to_set(exts)
|
|
|
|
extensions = Set.new
|
2014-04-14 16:55:57 -04:00
|
|
|
|
2018-02-19 04:44:24 -05:00
|
|
|
exts
|
2016-10-20 13:53:41 -04:00
|
|
|
.gsub(/[\s\.]+/, "")
|
|
|
|
.downcase
|
2014-04-14 16:55:57 -04:00
|
|
|
.split("|")
|
2018-02-19 04:44:24 -05:00
|
|
|
.each { |extension| extensions << extension unless extension.include?("*") }
|
|
|
|
|
|
|
|
extensions
|
|
|
|
end
|
2014-04-14 16:55:57 -04:00
|
|
|
|
2018-02-19 04:44:24 -05:00
|
|
|
def authorized_extensions(upload)
|
2018-04-19 07:30:31 -04:00
|
|
|
extensions = if upload.for_theme
|
|
|
|
SiteSetting.theme_authorized_extensions
|
|
|
|
elsif upload.for_export
|
|
|
|
SiteSetting.export_authorized_extensions
|
|
|
|
else
|
|
|
|
SiteSetting.authorized_extensions
|
|
|
|
end
|
2018-02-19 04:44:24 -05:00
|
|
|
extensions_to_set(extensions)
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
|
2017-05-09 17:20:28 -04:00
|
|
|
def authorized_images(upload)
|
2018-09-09 22:03:44 -04:00
|
|
|
authorized_extensions(upload) & FileHelper.supported_images
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
|
2017-05-09 17:20:28 -04:00
|
|
|
def authorized_attachments(upload)
|
2018-09-09 22:03:44 -04:00
|
|
|
authorized_extensions(upload) - FileHelper.supported_images
|
2014-04-29 13:12:35 -04:00
|
|
|
end
|
|
|
|
|
2017-05-09 17:20:28 -04:00
|
|
|
def authorizes_all_extensions?(upload)
|
2018-02-19 04:44:24 -05:00
|
|
|
if upload.user&.staff?
|
|
|
|
return true if SiteSetting.authorized_extensions_for_staff.include?("*")
|
|
|
|
end
|
2018-04-19 07:30:31 -04:00
|
|
|
extensions = if upload.for_theme
|
|
|
|
SiteSetting.theme_authorized_extensions
|
|
|
|
elsif upload.for_export
|
|
|
|
SiteSetting.export_authorized_extensions
|
|
|
|
else
|
|
|
|
SiteSetting.authorized_extensions
|
|
|
|
end
|
2017-05-09 17:20:28 -04:00
|
|
|
extensions.include?("*")
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
|
2018-02-19 04:44:24 -05:00
|
|
|
def extension_authorized?(upload, extension, extensions)
|
2017-05-09 17:20:28 -04:00
|
|
|
return true if authorizes_all_extensions?(upload)
|
2014-04-29 13:12:35 -04:00
|
|
|
|
2018-02-19 04:44:24 -05:00
|
|
|
staff_extensions = Set.new
|
|
|
|
if upload.user&.staff?
|
|
|
|
staff_extensions = extensions_to_set(SiteSetting.authorized_extensions_for_staff)
|
|
|
|
return true if staff_extensions.include?(extension.downcase)
|
|
|
|
end
|
|
|
|
|
2014-04-29 13:12:35 -04:00
|
|
|
unless authorized = extensions.include?(extension.downcase)
|
2018-02-19 04:44:24 -05:00
|
|
|
message = I18n.t("upload.unauthorized", authorized_extensions: (extensions | staff_extensions).to_a.join(", "))
|
2014-04-14 16:55:57 -04:00
|
|
|
upload.errors.add(:original_filename, message)
|
|
|
|
end
|
2014-04-29 13:12:35 -04:00
|
|
|
|
2014-04-14 16:55:57 -04:00
|
|
|
authorized
|
|
|
|
end
|
|
|
|
|
|
|
|
def maximum_file_size(upload, type)
|
2018-07-31 01:45:31 -04:00
|
|
|
max_size_kb = if upload.for_export
|
|
|
|
SiteSetting.max_export_file_size_kb
|
|
|
|
else
|
2019-05-06 21:00:09 -04:00
|
|
|
SiteSetting.get("max_#{type}_size_kb")
|
2018-07-31 01:45:31 -04:00
|
|
|
end
|
2019-05-06 21:27:05 -04:00
|
|
|
|
2015-05-03 13:26:54 -04:00
|
|
|
max_size_bytes = max_size_kb.kilobytes
|
2014-04-29 13:12:35 -04:00
|
|
|
|
2015-05-03 13:26:54 -04:00
|
|
|
if upload.filesize > max_size_bytes
|
FEATURE: Humanize file size error messages (#14398)
The file size error messages for max_image_size_kb and
max_attachment_size_kb are shown to the user in the KB
format, regardless of how large the limit is. Since we
are going to support uploading much larger files soon,
this KB-based limit soon becomes unfriendly to the end
user.
For example, if the max attachment size is set to 512000
KB, this is what the user sees:
> Sorry, the file you are trying to upload is too big (maximum
size is 512000KB)
This makes the user do math. In almost all file explorers that
a regular user would be familiar width, the file size is shown
in a format based on the maximum increment (e.g. KB, MB, GB).
This commit changes the behaviour to output a humanized file size
instead of the raw KB. For the above example, it would now say:
> Sorry, the file you are trying to upload is too big (maximum
size is 512 MB)
This humanization also handles decimals, e.g. 1536KB = 1.5 MB
2021-09-21 17:59:45 -04:00
|
|
|
message = I18n.t(
|
|
|
|
"upload.#{type}s.too_large_humanized",
|
|
|
|
max_size: ActiveSupport::NumberHelper.number_to_human_size(max_size_bytes)
|
|
|
|
)
|
2014-04-14 16:55:57 -04:00
|
|
|
upload.errors.add(:filesize, message)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|