2019-04-29 20:27:42 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2019-01-04 01:16:22 -05:00
|
|
|
require 'file_store/s3_store'
|
2018-08-16 23:41:30 -04:00
|
|
|
|
|
|
|
RSpec.describe UploadCreator do
|
2019-05-06 23:12:20 -04:00
|
|
|
fab!(:user) { Fabricate(:user) }
|
2018-08-16 23:41:30 -04:00
|
|
|
|
|
|
|
describe '#create_for' do
|
|
|
|
describe 'when upload is not an image' do
|
|
|
|
before do
|
2021-03-17 14:01:29 -04:00
|
|
|
SiteSetting.authorized_extensions = 'txt|long-FileExtension'
|
2018-08-16 23:41:30 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
let(:filename) { "utf-8.txt" }
|
|
|
|
let(:file) { file_from_fixtures(filename, "encodings") }
|
|
|
|
|
|
|
|
it 'should store the upload with the right extension' do
|
|
|
|
expect do
|
2018-08-21 12:11:01 -04:00
|
|
|
UploadCreator.new(file, "utf-8\n.txt").create_for(user.id)
|
2018-08-16 23:41:30 -04:00
|
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
|
|
|
|
upload = Upload.last
|
|
|
|
|
|
|
|
expect(upload.extension).to eq('txt')
|
|
|
|
expect(File.extname(upload.url)).to eq('.txt')
|
|
|
|
expect(upload.original_filename).to eq('utf-8.txt')
|
2018-09-20 01:33:10 -04:00
|
|
|
expect(user.user_uploads.count).to eq(1)
|
|
|
|
expect(upload.user_uploads.count).to eq(1)
|
|
|
|
|
|
|
|
user2 = Fabricate(:user)
|
|
|
|
|
|
|
|
expect do
|
|
|
|
UploadCreator.new(file, "utf-8\n.txt").create_for(user2.id)
|
2022-07-19 10:03:03 -04:00
|
|
|
end.not_to change { Upload.count }
|
2018-09-20 01:33:10 -04:00
|
|
|
|
|
|
|
expect(user.user_uploads.count).to eq(1)
|
|
|
|
expect(user2.user_uploads.count).to eq(1)
|
|
|
|
expect(upload.user_uploads.count).to eq(2)
|
2018-08-16 23:41:30 -04:00
|
|
|
end
|
2021-03-17 14:01:29 -04:00
|
|
|
|
|
|
|
let(:longextension) { "fake.long-FileExtension" }
|
|
|
|
let(:file2) { file_from_fixtures(longextension) }
|
|
|
|
|
|
|
|
it 'should truncate long extension names' do
|
|
|
|
expect do
|
|
|
|
UploadCreator.new(file2, "fake.long-FileExtension").create_for(user.id)
|
|
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
|
|
|
|
upload = Upload.last
|
|
|
|
|
|
|
|
expect(upload.extension).to eq('long-FileE')
|
|
|
|
end
|
2018-08-16 23:41:30 -04:00
|
|
|
end
|
|
|
|
|
2018-11-14 02:03:02 -05:00
|
|
|
describe 'when image is not authorized' do
|
|
|
|
describe 'when image is for site setting' do
|
|
|
|
let(:filename) { 'logo.png' }
|
|
|
|
let(:file) { file_from_fixtures(filename) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
SiteSetting.authorized_extensions = 'jpg'
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should create the right upload' do
|
|
|
|
upload = UploadCreator.new(file, filename,
|
|
|
|
for_site_setting: true
|
|
|
|
).create_for(Discourse.system_user.id)
|
|
|
|
|
|
|
|
expect(upload.persisted?).to eq(true)
|
|
|
|
expect(upload.original_filename).to eq(filename)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-08-16 23:41:30 -04:00
|
|
|
describe 'when image has the wrong extension' do
|
2018-08-19 22:18:49 -04:00
|
|
|
let(:filename) { "png_as.bin" }
|
2018-08-16 23:41:30 -04:00
|
|
|
let(:file) { file_from_fixtures(filename) }
|
|
|
|
|
|
|
|
it 'should store the upload with the right extension' do
|
|
|
|
expect do
|
2018-08-17 03:22:12 -04:00
|
|
|
UploadCreator.new(file, filename,
|
|
|
|
force_optimize: true,
|
|
|
|
type: UploadCreator::TYPES_TO_CROP.first
|
|
|
|
).create_for(user.id)
|
2018-08-16 23:41:30 -04:00
|
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
|
|
|
|
upload = Upload.last
|
|
|
|
|
|
|
|
expect(upload.extension).to eq('png')
|
|
|
|
expect(File.extname(upload.url)).to eq('.png')
|
2018-08-20 02:08:05 -04:00
|
|
|
expect(upload.original_filename).to eq('png_as.png')
|
2018-08-16 23:41:30 -04:00
|
|
|
end
|
2018-09-09 22:49:01 -04:00
|
|
|
|
2020-02-20 22:08:02 -05:00
|
|
|
describe 'for tiff format' do
|
2018-09-09 22:49:01 -04:00
|
|
|
before do
|
2020-02-20 22:08:02 -05:00
|
|
|
SiteSetting.authorized_extensions = '.tiff|.bin'
|
2018-09-09 22:49:01 -04:00
|
|
|
end
|
|
|
|
|
2020-02-20 22:08:02 -05:00
|
|
|
let(:filename) { "tiff_as.bin" }
|
2018-09-09 22:49:01 -04:00
|
|
|
let(:file) { file_from_fixtures(filename) }
|
|
|
|
|
|
|
|
it 'should not correct the coerce filename' do
|
|
|
|
expect do
|
|
|
|
UploadCreator.new(file, filename).create_for(user.id)
|
|
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
|
|
|
|
upload = Upload.last
|
|
|
|
|
|
|
|
expect(upload.extension).to eq('bin')
|
|
|
|
expect(File.extname(upload.url)).to eq('.bin')
|
2020-02-20 22:08:02 -05:00
|
|
|
expect(upload.original_filename).to eq('tiff_as.bin')
|
2018-09-09 22:49:01 -04:00
|
|
|
end
|
|
|
|
end
|
2018-08-16 23:41:30 -04:00
|
|
|
end
|
|
|
|
|
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
|
|
|
context "when image is too big" do
|
|
|
|
let(:filename) { 'logo.png' }
|
|
|
|
let(:file) { file_from_fixtures(filename) }
|
|
|
|
|
|
|
|
it "adds an error to the upload" do
|
|
|
|
SiteSetting.max_image_size_kb = 1
|
|
|
|
upload = UploadCreator.new(
|
|
|
|
file, filename, force_optimize: true
|
|
|
|
).create_for(Discourse.system_user.id)
|
|
|
|
expect(upload.errors.full_messages.first).to eq(
|
|
|
|
"#{I18n.t("upload.images.too_large_humanized", max_size: "1 KB")}"
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-01-02 01:19:52 -05:00
|
|
|
describe 'pngquant' do
|
|
|
|
let(:filename) { "pngquant.png" }
|
|
|
|
let(:file) { file_from_fixtures(filename) }
|
|
|
|
|
|
|
|
it 'should apply pngquant to optimized images' do
|
|
|
|
upload = UploadCreator.new(file, filename,
|
|
|
|
pasted: true,
|
|
|
|
force_optimize: true
|
|
|
|
).create_for(user.id)
|
|
|
|
|
|
|
|
# no optimisation possible without losing details
|
|
|
|
expect(upload.filesize).to eq(9558)
|
|
|
|
|
|
|
|
thumbnail_size = upload.get_optimized_image(upload.width, upload.height, {}).filesize
|
|
|
|
|
|
|
|
# pngquant will lose some colors causing some extra size reduction
|
|
|
|
expect(thumbnail_size).to be < 7500
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-08-16 23:41:30 -04:00
|
|
|
describe 'converting to jpeg' do
|
2020-10-23 12:38:28 -04:00
|
|
|
def image_quality(path)
|
|
|
|
local_path = File.join(Rails.root, 'public', path)
|
|
|
|
Discourse::Utils.execute_command("identify", "-format", "%Q", local_path).to_i
|
|
|
|
end
|
|
|
|
|
2018-11-20 19:00:52 -05:00
|
|
|
let(:filename) { "should_be_jpeg.png" }
|
2018-08-16 23:41:30 -04:00
|
|
|
let(:file) { file_from_fixtures(filename) }
|
|
|
|
|
2018-11-20 19:00:52 -05:00
|
|
|
let(:small_filename) { "logo.png" }
|
|
|
|
let(:small_file) { file_from_fixtures(small_filename) }
|
|
|
|
|
2021-02-12 13:37:35 -05:00
|
|
|
let(:large_filename) { "large_and_unoptimized.png" }
|
|
|
|
let(:large_file) { file_from_fixtures(large_filename) }
|
|
|
|
|
2020-10-26 15:10:19 -04:00
|
|
|
let(:animated_filename) { "animated.gif" }
|
|
|
|
let(:animated_file) { file_from_fixtures(animated_filename) }
|
|
|
|
|
2021-01-13 12:01:30 -05:00
|
|
|
let(:animated_webp_filename) { "animated.webp" }
|
|
|
|
let(:animated_webp_file) { file_from_fixtures(animated_webp_filename) }
|
|
|
|
|
2018-08-16 23:41:30 -04:00
|
|
|
before do
|
|
|
|
SiteSetting.png_to_jpg_quality = 1
|
|
|
|
end
|
|
|
|
|
2018-11-20 19:00:52 -05:00
|
|
|
it 'should not store file as jpeg if it does not meet absolute byte saving requirements' do
|
|
|
|
# logo.png is 2297 bytes, converting to jpeg saves 30% but does not meet
|
|
|
|
# the absolute savings required of 25_000 bytes, if you save less than that
|
|
|
|
# skip this
|
|
|
|
|
|
|
|
expect do
|
|
|
|
UploadCreator.new(small_file, small_filename,
|
|
|
|
pasted: true,
|
|
|
|
force_optimize: true
|
|
|
|
).create_for(user.id)
|
|
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
|
|
|
|
upload = Upload.last
|
|
|
|
|
|
|
|
expect(upload.extension).to eq('png')
|
|
|
|
expect(File.extname(upload.url)).to eq('.png')
|
|
|
|
expect(upload.original_filename).to eq('logo.png')
|
|
|
|
end
|
|
|
|
|
2018-08-16 23:41:30 -04:00
|
|
|
it 'should store the upload with the right extension' do
|
|
|
|
expect do
|
|
|
|
UploadCreator.new(file, filename,
|
|
|
|
pasted: true,
|
|
|
|
force_optimize: true
|
|
|
|
).create_for(user.id)
|
|
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
|
|
|
|
upload = Upload.last
|
|
|
|
|
2020-07-22 21:40:09 -04:00
|
|
|
expect(upload.extension).to eq('jpeg')
|
|
|
|
expect(File.extname(upload.url)).to eq('.jpeg')
|
|
|
|
expect(upload.original_filename).to eq('should_be_jpeg.jpg')
|
|
|
|
end
|
2020-10-23 12:38:28 -04:00
|
|
|
|
2021-03-16 10:44:41 -04:00
|
|
|
it "should not convert to jpeg when the image is uploaded from site setting" do
|
|
|
|
upload = UploadCreator.new(large_file, large_filename, for_site_setting: true, force_optimize: true).create_for(user.id)
|
|
|
|
|
|
|
|
expect(upload.extension).to eq('png')
|
|
|
|
expect(File.extname(upload.url)).to eq('.png')
|
|
|
|
expect(upload.original_filename).to eq('large_and_unoptimized.png')
|
|
|
|
end
|
|
|
|
|
2022-07-27 12:14:14 -04:00
|
|
|
context "with jpeg image quality settings" do
|
2020-10-26 15:10:19 -04:00
|
|
|
before do
|
|
|
|
SiteSetting.png_to_jpg_quality = 75
|
|
|
|
SiteSetting.recompress_original_jpg_quality = 40
|
|
|
|
SiteSetting.image_preview_jpg_quality = 10
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should alter the image quality' do
|
|
|
|
upload = UploadCreator.new(file, filename, force_optimize: true).create_for(user.id)
|
2020-10-23 12:38:28 -04:00
|
|
|
|
2020-10-26 15:10:19 -04:00
|
|
|
expect(image_quality(upload.url)).to eq(SiteSetting.recompress_original_jpg_quality)
|
2020-10-23 12:38:28 -04:00
|
|
|
|
2020-10-26 15:10:19 -04:00
|
|
|
upload.create_thumbnail!(100, 100)
|
|
|
|
upload.reload
|
2020-10-23 12:38:28 -04:00
|
|
|
|
2020-10-26 15:10:19 -04:00
|
|
|
expect(image_quality(upload.optimized_images.first.url)).to eq(SiteSetting.image_preview_jpg_quality)
|
|
|
|
end
|
2020-10-23 12:38:28 -04:00
|
|
|
|
2020-10-26 15:10:19 -04:00
|
|
|
it 'should not convert animated images' do
|
|
|
|
expect do
|
|
|
|
UploadCreator.new(animated_file, animated_filename,
|
|
|
|
force_optimize: true
|
|
|
|
).create_for(user.id)
|
|
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
|
|
|
|
upload = Upload.last
|
|
|
|
|
|
|
|
expect(upload.extension).to eq('gif')
|
|
|
|
expect(File.extname(upload.url)).to eq('.gif')
|
|
|
|
expect(upload.original_filename).to eq('animated.gif')
|
|
|
|
end
|
2021-01-13 12:01:30 -05:00
|
|
|
|
2022-07-27 12:14:14 -04:00
|
|
|
context "with png image quality settings" do
|
2021-02-12 13:37:35 -05:00
|
|
|
before do
|
|
|
|
SiteSetting.png_to_jpg_quality = 100
|
|
|
|
SiteSetting.recompress_original_jpg_quality = 90
|
|
|
|
SiteSetting.image_preview_jpg_quality = 10
|
|
|
|
end
|
|
|
|
|
|
|
|
it "should not convert to jpeg when png_to_jpg_quality is 100" do
|
|
|
|
upload = UploadCreator.new(large_file, large_filename, force_optimize: true).create_for(user.id)
|
|
|
|
|
|
|
|
expect(upload.extension).to eq('png')
|
|
|
|
expect(File.extname(upload.url)).to eq('.png')
|
|
|
|
expect(upload.original_filename).to eq('large_and_unoptimized.png')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-01-13 12:01:30 -05:00
|
|
|
it 'should not convert animated WEBP images' do
|
|
|
|
expect do
|
|
|
|
UploadCreator.new(animated_webp_file, animated_webp_filename,
|
|
|
|
force_optimize: true
|
|
|
|
).create_for(user.id)
|
|
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
|
|
|
|
upload = Upload.last
|
|
|
|
|
|
|
|
expect(upload.extension).to eq('webp')
|
|
|
|
expect(File.extname(upload.url)).to eq('.webp')
|
|
|
|
expect(upload.original_filename).to eq('animated.webp')
|
|
|
|
end
|
2020-10-23 12:38:28 -04:00
|
|
|
end
|
2020-07-22 21:40:09 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
describe 'converting HEIF to jpeg' do
|
|
|
|
let(:filename) { "should_be_jpeg.heic" }
|
|
|
|
let(:file) { file_from_fixtures(filename, "images") }
|
|
|
|
|
|
|
|
it 'should store the upload with the right extension' do
|
|
|
|
expect do
|
|
|
|
UploadCreator.new(file, filename).create_for(user.id)
|
|
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
|
|
|
|
upload = Upload.last
|
|
|
|
|
2018-08-16 23:41:30 -04:00
|
|
|
expect(upload.extension).to eq('jpeg')
|
|
|
|
expect(File.extname(upload.url)).to eq('.jpeg')
|
2018-11-20 19:00:52 -05:00
|
|
|
expect(upload.original_filename).to eq('should_be_jpeg.jpg')
|
2018-08-16 23:41:30 -04:00
|
|
|
end
|
|
|
|
end
|
2019-01-04 01:16:22 -05:00
|
|
|
|
2019-11-17 20:25:42 -05:00
|
|
|
describe 'secure attachments' do
|
2019-06-05 23:27:24 -04:00
|
|
|
let(:filename) { "small.pdf" }
|
|
|
|
let(:file) { file_from_fixtures(filename, "pdf") }
|
2020-02-20 18:35:16 -05:00
|
|
|
let(:opts) { { type: "composer" } }
|
2019-06-05 23:27:24 -04:00
|
|
|
|
|
|
|
before do
|
2020-09-14 07:32:25 -04:00
|
|
|
setup_s3
|
|
|
|
stub_s3_store
|
|
|
|
|
2022-09-28 19:24:33 -04:00
|
|
|
SiteSetting.secure_uploads = true
|
2019-06-05 23:27:24 -04:00
|
|
|
SiteSetting.authorized_extensions = 'pdf|svg|jpg'
|
|
|
|
end
|
|
|
|
|
2019-11-17 20:25:42 -05:00
|
|
|
it 'should mark attachments as secure' do
|
2020-02-20 18:35:16 -05:00
|
|
|
upload = UploadCreator.new(file, filename, opts).create_for(user.id)
|
2019-06-05 23:27:24 -04:00
|
|
|
stored_upload = Upload.last
|
|
|
|
|
2019-11-17 20:25:42 -05:00
|
|
|
expect(stored_upload.secure?).to eq(true)
|
2019-06-05 23:27:24 -04:00
|
|
|
end
|
|
|
|
|
2019-11-17 20:25:42 -05:00
|
|
|
it 'should not mark theme uploads as secure' do
|
2019-06-05 23:27:24 -04:00
|
|
|
fname = "custom-theme-icon-sprite.svg"
|
|
|
|
upload = UploadCreator.new(file_from_fixtures(fname), fname, for_theme: true).create_for(-1)
|
|
|
|
|
2019-11-17 20:25:42 -05:00
|
|
|
expect(upload.secure?).to eq(false)
|
2019-06-05 23:27:24 -04:00
|
|
|
end
|
2022-04-20 00:11:39 -04:00
|
|
|
|
|
|
|
it "sets a reason for the security" do
|
|
|
|
upload = UploadCreator.new(file, filename, opts).create_for(user.id)
|
|
|
|
stored_upload = Upload.last
|
|
|
|
|
|
|
|
expect(stored_upload.secure?).to eq(true)
|
|
|
|
expect(stored_upload.security_last_changed_at).not_to eq(nil)
|
|
|
|
expect(stored_upload.security_last_changed_reason).to eq("uploading via the composer | source: upload creator")
|
|
|
|
end
|
2019-06-05 23:27:24 -04:00
|
|
|
end
|
|
|
|
|
2022-07-27 12:14:14 -04:00
|
|
|
context 'when uploading to s3' do
|
2019-01-04 01:16:22 -05:00
|
|
|
let(:filename) { "should_be_jpeg.png" }
|
|
|
|
let(:file) { file_from_fixtures(filename) }
|
2019-06-05 23:27:24 -04:00
|
|
|
let(:pdf_filename) { "small.pdf" }
|
|
|
|
let(:pdf_file) { file_from_fixtures(pdf_filename, "pdf") }
|
2020-02-20 18:35:16 -05:00
|
|
|
let(:opts) { { type: "composer" } }
|
2019-01-04 01:16:22 -05:00
|
|
|
|
|
|
|
before do
|
2020-09-14 07:32:25 -04:00
|
|
|
setup_s3
|
|
|
|
stub_s3_store
|
2019-01-04 01:16:22 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'should store the file and return etag' do
|
|
|
|
expect {
|
|
|
|
UploadCreator.new(file, filename).create_for(user.id)
|
|
|
|
}.to change { Upload.count }.by(1)
|
|
|
|
|
|
|
|
upload = Upload.last
|
|
|
|
|
|
|
|
expect(upload.etag).to eq('ETag')
|
|
|
|
end
|
2019-06-05 23:27:24 -04:00
|
|
|
|
2019-11-17 20:25:42 -05:00
|
|
|
it 'should return signed URL for secure attachments in S3' do
|
2019-06-05 23:27:24 -04:00
|
|
|
SiteSetting.authorized_extensions = 'pdf'
|
2022-09-28 19:24:33 -04:00
|
|
|
SiteSetting.secure_uploads = true
|
2019-06-05 23:27:24 -04:00
|
|
|
|
2020-02-20 18:35:16 -05:00
|
|
|
upload = UploadCreator.new(pdf_file, pdf_filename, opts).create_for(user.id)
|
2019-06-05 23:27:24 -04:00
|
|
|
stored_upload = Upload.last
|
|
|
|
signed_url = Discourse.store.url_for(stored_upload)
|
|
|
|
|
2019-11-17 20:25:42 -05:00
|
|
|
expect(stored_upload.secure?).to eq(true)
|
2019-06-05 23:27:24 -04:00
|
|
|
expect(stored_upload.url).not_to eq(signed_url)
|
|
|
|
expect(signed_url).to match(/Amz-Credential/)
|
|
|
|
end
|
2019-01-04 01:16:22 -05:00
|
|
|
end
|
2020-01-15 22:50:27 -05:00
|
|
|
|
|
|
|
context "when the upload already exists based on the sha1" do
|
|
|
|
let(:filename) { "small.pdf" }
|
|
|
|
let(:file) { file_from_fixtures(filename, "pdf") }
|
|
|
|
let!(:existing_upload) { Fabricate(:upload, sha1: Upload.generate_digest(file)) }
|
|
|
|
let(:result) { UploadCreator.new(file, filename).create_for(user.id) }
|
|
|
|
|
|
|
|
it "returns the existing upload" do
|
|
|
|
expect(result).to eq(existing_upload)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not set an original_sha1 normally" do
|
|
|
|
expect(result.original_sha1).to eq(nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "creates a userupload record" do
|
|
|
|
result
|
|
|
|
expect(UserUpload.exists?(user_id: user.id, upload_id: existing_upload.id)).to eq(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when the existing upload URL is blank (it has failed)" do
|
|
|
|
before do
|
|
|
|
existing_upload.update(url: '')
|
|
|
|
end
|
|
|
|
|
|
|
|
it "destroys the existing upload" do
|
|
|
|
result
|
|
|
|
expect(Upload.find_by(id: existing_upload.id)).to eq(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-09-28 19:24:33 -04:00
|
|
|
context "when SiteSetting.secure_uploads is enabled" do
|
2020-01-15 22:50:27 -05:00
|
|
|
before do
|
2020-09-14 07:32:25 -04:00
|
|
|
setup_s3
|
|
|
|
stub_s3_store
|
|
|
|
|
2022-09-28 19:24:33 -04:00
|
|
|
SiteSetting.secure_uploads = true
|
2020-01-15 22:50:27 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it "does not return the existing upload, as duplicate uploads are allowed" do
|
|
|
|
expect(result).not_to eq(existing_upload)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-09-28 19:24:33 -04:00
|
|
|
context "with secure uploads functionality" do
|
2020-01-15 22:50:27 -05:00
|
|
|
let(:filename) { "logo.jpg" }
|
|
|
|
let(:file) { file_from_fixtures(filename) }
|
|
|
|
let(:opts) { {} }
|
|
|
|
let(:result) { UploadCreator.new(file, filename, opts).create_for(user.id) }
|
|
|
|
|
2022-09-28 19:24:33 -04:00
|
|
|
context "when SiteSetting.secure_uploads enabled" do
|
2020-01-15 22:50:27 -05:00
|
|
|
before do
|
2020-09-14 07:32:25 -04:00
|
|
|
setup_s3
|
|
|
|
stub_s3_store
|
|
|
|
|
2022-09-28 19:24:33 -04:00
|
|
|
SiteSetting.secure_uploads = true
|
2020-01-15 22:50:27 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it "sets an original_sha1 on the upload created because the sha1 column is securerandom in this case" do
|
|
|
|
expect(result.original_sha1).not_to eq(nil)
|
|
|
|
end
|
|
|
|
|
2020-01-16 22:16:27 -05:00
|
|
|
context "when uploading in a public context (theme, site setting, avatar, custom_emoji, profile_background, card_background)" do
|
|
|
|
def expect_no_public_context_uploads_to_be_secure
|
2020-01-15 22:50:27 -05:00
|
|
|
upload = UploadCreator.new(file_from_fixtures(filename), filename, for_site_setting: true).create_for(user.id)
|
|
|
|
expect(upload.secure).to eq(false)
|
|
|
|
upload.destroy!
|
|
|
|
|
2020-01-16 22:16:27 -05:00
|
|
|
upload = UploadCreator.new(file_from_fixtures(filename), filename, for_gravatar: true).create_for(user.id)
|
|
|
|
expect(upload.secure).to eq(false)
|
|
|
|
upload.destroy!
|
|
|
|
|
2020-01-15 22:50:27 -05:00
|
|
|
upload = UploadCreator.new(file_from_fixtures(filename), filename, for_theme: true).create_for(user.id)
|
|
|
|
expect(upload.secure).to eq(false)
|
|
|
|
upload.destroy!
|
|
|
|
|
|
|
|
upload = UploadCreator.new(file_from_fixtures(filename), filename, type: "avatar").create_for(user.id)
|
|
|
|
expect(upload.secure).to eq(false)
|
|
|
|
upload.destroy!
|
2020-01-16 22:16:27 -05:00
|
|
|
|
|
|
|
upload = UploadCreator.new(file_from_fixtures(filename), filename, type: "custom_emoji").create_for(user.id)
|
|
|
|
expect(upload.secure).to eq(false)
|
|
|
|
upload.destroy!
|
|
|
|
|
|
|
|
upload = UploadCreator.new(file_from_fixtures(filename), filename, type: "profile_background").create_for(user.id)
|
|
|
|
expect(upload.secure).to eq(false)
|
|
|
|
upload.destroy!
|
|
|
|
|
|
|
|
upload = UploadCreator.new(file_from_fixtures(filename), filename, type: "card_background").create_for(user.id)
|
|
|
|
expect(upload.secure).to eq(false)
|
|
|
|
upload.destroy!
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not set the upload to secure" do
|
|
|
|
expect_no_public_context_uploads_to_be_secure
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when login required" do
|
|
|
|
before do
|
|
|
|
SiteSetting.login_required = true
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not set the upload to secure" do
|
|
|
|
expect_no_public_context_uploads_to_be_secure
|
|
|
|
end
|
2020-01-15 22:50:27 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "if type of upload is in the composer" do
|
|
|
|
let(:opts) { { type: "composer" } }
|
|
|
|
it "sets the upload to secure and sets the original_sha1 column, because we don't know the context of the composer" do
|
|
|
|
expect(result.secure).to eq(true)
|
|
|
|
expect(result.original_sha1).not_to eq(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "if the upload is for a PM" do
|
|
|
|
let(:opts) { { for_private_message: true } }
|
|
|
|
it "sets the upload to secure and sets the original_sha1" do
|
|
|
|
expect(result.secure).to eq(true)
|
|
|
|
expect(result.original_sha1).not_to eq(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-01-16 22:16:27 -05:00
|
|
|
context "if the upload is for a group message" do
|
|
|
|
let(:opts) { { for_group_message: true } }
|
|
|
|
it "sets the upload to secure and sets the original_sha1" do
|
|
|
|
expect(result.secure).to eq(true)
|
|
|
|
expect(result.original_sha1).not_to eq(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-01-15 22:50:27 -05:00
|
|
|
context "if SiteSetting.login_required" do
|
|
|
|
before do
|
|
|
|
SiteSetting.login_required = true
|
|
|
|
end
|
|
|
|
it "sets the upload to secure and sets the original_sha1" do
|
|
|
|
expect(result.secure).to eq(true)
|
|
|
|
expect(result.original_sha1).not_to eq(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2020-11-11 19:22:38 -05:00
|
|
|
|
2022-07-27 12:14:14 -04:00
|
|
|
context 'with custom emojis' do
|
2020-11-11 19:22:38 -05:00
|
|
|
let(:animated_filename) { "animated.gif" }
|
|
|
|
let(:animated_file) { file_from_fixtures(animated_filename) }
|
|
|
|
|
|
|
|
it 'should not be cropped if animated' do
|
|
|
|
upload = UploadCreator.new(animated_file, animated_filename,
|
|
|
|
force_optimize: true,
|
|
|
|
type: 'custom_emoji'
|
|
|
|
).create_for(user.id)
|
|
|
|
|
|
|
|
expect(upload.animated).to eq(true)
|
|
|
|
expect(FastImage.size(Discourse.store.path_for(upload))).to eq([320, 320])
|
|
|
|
end
|
|
|
|
end
|
2021-05-19 11:24:52 -04:00
|
|
|
|
|
|
|
describe 'skip validations' do
|
|
|
|
let(:filename) { "small.pdf" }
|
|
|
|
let(:file) { file_from_fixtures(filename, "pdf") }
|
|
|
|
|
|
|
|
before do
|
|
|
|
SiteSetting.authorized_extensions = 'png|jpg'
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'creates upload when skip_validations is true' do
|
|
|
|
upload = UploadCreator.new(file, filename,
|
|
|
|
skip_validations: true
|
|
|
|
).create_for(user.id)
|
|
|
|
|
|
|
|
expect(upload.persisted?).to eq(true)
|
|
|
|
expect(upload.original_filename).to eq(filename)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not create upload when skip_validations is false' do
|
|
|
|
upload = UploadCreator.new(file, filename,
|
|
|
|
skip_validations: false
|
|
|
|
).create_for(user.id)
|
|
|
|
|
|
|
|
expect(upload.persisted?).to eq(false)
|
|
|
|
end
|
|
|
|
end
|
2018-08-16 23:41:30 -04:00
|
|
|
end
|
2019-12-11 09:28:35 -05:00
|
|
|
|
2021-12-10 14:25:50 -05:00
|
|
|
describe '#convert_favicon_to_png!' do
|
|
|
|
let(:filename) { "smallest.ico" }
|
|
|
|
let(:file) { file_from_fixtures(filename, "images") }
|
|
|
|
|
|
|
|
before do
|
|
|
|
SiteSetting.authorized_extensions = 'png|jpg|ico'
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'converts to png' do
|
|
|
|
upload = UploadCreator.new(file, filename).create_for(user.id)
|
|
|
|
|
|
|
|
expect(upload.persisted?).to eq(true)
|
|
|
|
expect(upload.extension).to eq('png')
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
2020-07-26 20:23:54 -04:00
|
|
|
describe '#clean_svg!' do
|
2020-07-09 17:02:25 -04:00
|
|
|
let(:b64) do
|
|
|
|
Base64.encode64('<svg onmouseover="alert(alert)" />')
|
|
|
|
end
|
|
|
|
|
2019-12-11 09:28:35 -05:00
|
|
|
let(:file) do
|
|
|
|
file = Tempfile.new
|
|
|
|
file.write(<<~XML)
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="200px" height="200px" onload="alert(location)">
|
2020-07-09 17:02:25 -04:00
|
|
|
<defs>
|
|
|
|
<path id="pathdef" d="m0 0h100v100h-77z" stroke="#000" />
|
|
|
|
</defs>
|
|
|
|
<g>
|
2021-11-24 23:22:43 -05:00
|
|
|
<use id="valid-use" x="123" href="#pathdef" />
|
2020-07-09 17:02:25 -04:00
|
|
|
</g>
|
|
|
|
<use id="invalid-use1" href="https://svg.example.com/evil.svg" />
|
2021-11-24 23:22:43 -05:00
|
|
|
<use id="invalid-use2" href="data:image/svg+xml;base64,#{b64}" />
|
2019-12-11 09:28:35 -05:00
|
|
|
</svg>
|
|
|
|
XML
|
|
|
|
file.rewind
|
|
|
|
file
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'removes event handlers' do
|
|
|
|
begin
|
2020-07-26 20:23:54 -04:00
|
|
|
UploadCreator.new(file, 'file.svg').clean_svg!
|
2020-07-09 17:02:25 -04:00
|
|
|
file_content = file.read
|
|
|
|
expect(file_content).not_to include('onload')
|
|
|
|
expect(file_content).to include('#pathdef')
|
|
|
|
expect(file_content).not_to include('evil.svg')
|
|
|
|
expect(file_content).not_to include(b64)
|
2019-12-11 09:28:35 -05:00
|
|
|
ensure
|
|
|
|
file.unlink
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2021-01-12 10:07:07 -05:00
|
|
|
|
FIX: Allow SVG uploads if dimensions are a fraction of a unit (#13409)
* FIX: Allow SVG uploads if dimensions are a fraction of a unit
`UploadCreator` counts the number of pixels in an file to determine if it is valid. `pixels` is calculated by multiplying the width and height of the image, as determined by FastImage.
SVG files can have their width/height expressed in a variety of different units of measurement. For example, ‘px’, ‘in’, ‘cm’, ‘mm’, ‘pt’, ‘pc’, etc are all valid within SVG files. If an image has a width of `0.5in`, FastImage may interpret this as being a width of `0`, meaning it will report the `size` as being `0`.
However, we don’t need to concern ourselves with the number of ‘pixels’ in a SVG files, as that is irrelevant for this file format, so we can skip over the check for `pixels == 0` when processing this file type.
* DEV: Speed up getting SVG dimensions
The `-ping` flag prevents the entire image from being rasterized before a result is returned. See:
https://imagemagick.org/script/command-line-options.php#ping
2021-06-17 15:56:11 -04:00
|
|
|
describe "svg sizes expressed in units other than pixels" do
|
|
|
|
let(:tiny_svg_filename) { "tiny.svg" }
|
|
|
|
let(:tiny_svg_file) { file_from_fixtures(tiny_svg_filename) }
|
2021-03-01 11:44:00 -05:00
|
|
|
|
FIX: Allow SVG uploads if dimensions are a fraction of a unit (#13409)
* FIX: Allow SVG uploads if dimensions are a fraction of a unit
`UploadCreator` counts the number of pixels in an file to determine if it is valid. `pixels` is calculated by multiplying the width and height of the image, as determined by FastImage.
SVG files can have their width/height expressed in a variety of different units of measurement. For example, ‘px’, ‘in’, ‘cm’, ‘mm’, ‘pt’, ‘pc’, etc are all valid within SVG files. If an image has a width of `0.5in`, FastImage may interpret this as being a width of `0`, meaning it will report the `size` as being `0`.
However, we don’t need to concern ourselves with the number of ‘pixels’ in a SVG files, as that is irrelevant for this file format, so we can skip over the check for `pixels == 0` when processing this file type.
* DEV: Speed up getting SVG dimensions
The `-ping` flag prevents the entire image from being rasterized before a result is returned. See:
https://imagemagick.org/script/command-line-options.php#ping
2021-06-17 15:56:11 -04:00
|
|
|
let(:massive_svg_filename) { "massive.svg" }
|
|
|
|
let(:massive_svg_file) { file_from_fixtures(massive_svg_filename) }
|
|
|
|
|
|
|
|
let(:zero_sized_svg_filename) { "zero_sized.svg" }
|
|
|
|
let(:zero_sized_svg_file) { file_from_fixtures(zero_sized_svg_filename) }
|
|
|
|
|
|
|
|
it "should be viewable when a dimension is a fraction of a unit" do
|
|
|
|
upload = UploadCreator.new(tiny_svg_file, tiny_svg_filename,
|
|
|
|
force_optimize: true,
|
|
|
|
).create_for(user.id)
|
|
|
|
|
|
|
|
expect(upload.width).to be > 50
|
|
|
|
expect(upload.height).to be > 50
|
|
|
|
|
|
|
|
expect(upload.thumbnail_width).to be <= SiteSetting.max_image_width
|
|
|
|
expect(upload.thumbnail_height).to be <= SiteSetting.max_image_height
|
|
|
|
end
|
|
|
|
|
|
|
|
it "should not be larger than the maximum thumbnail size" do
|
|
|
|
upload = UploadCreator.new(massive_svg_file, massive_svg_filename,
|
2021-03-01 11:44:00 -05:00
|
|
|
force_optimize: true,
|
|
|
|
).create_for(user.id)
|
|
|
|
|
FIX: Allow SVG uploads if dimensions are a fraction of a unit (#13409)
* FIX: Allow SVG uploads if dimensions are a fraction of a unit
`UploadCreator` counts the number of pixels in an file to determine if it is valid. `pixels` is calculated by multiplying the width and height of the image, as determined by FastImage.
SVG files can have their width/height expressed in a variety of different units of measurement. For example, ‘px’, ‘in’, ‘cm’, ‘mm’, ‘pt’, ‘pc’, etc are all valid within SVG files. If an image has a width of `0.5in`, FastImage may interpret this as being a width of `0`, meaning it will report the `size` as being `0`.
However, we don’t need to concern ourselves with the number of ‘pixels’ in a SVG files, as that is irrelevant for this file format, so we can skip over the check for `pixels == 0` when processing this file type.
* DEV: Speed up getting SVG dimensions
The `-ping` flag prevents the entire image from being rasterized before a result is returned. See:
https://imagemagick.org/script/command-line-options.php#ping
2021-06-17 15:56:11 -04:00
|
|
|
expect(upload.width).to be > 50
|
|
|
|
expect(upload.height).to be > 50
|
|
|
|
|
|
|
|
expect(upload.thumbnail_width).to be <= SiteSetting.max_image_width
|
|
|
|
expect(upload.thumbnail_height).to be <= SiteSetting.max_image_height
|
|
|
|
end
|
|
|
|
|
|
|
|
it "should handle zero dimension files" do
|
|
|
|
upload = UploadCreator.new(zero_sized_svg_file, zero_sized_svg_filename,
|
|
|
|
force_optimize: true,
|
|
|
|
).create_for(user.id)
|
|
|
|
|
|
|
|
expect(upload.width).to be > 50
|
|
|
|
expect(upload.height).to be > 50
|
|
|
|
|
|
|
|
expect(upload.thumbnail_width).to be <= SiteSetting.max_image_width
|
|
|
|
expect(upload.thumbnail_height).to be <= SiteSetting.max_image_height
|
2021-03-01 11:44:00 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-01-12 10:07:07 -05:00
|
|
|
describe '#should_downsize?' do
|
2022-07-27 12:14:14 -04:00
|
|
|
context "with GIF image" do
|
2021-01-12 10:07:07 -05:00
|
|
|
let(:gif_file) { file_from_fixtures("animated.gif") }
|
|
|
|
|
|
|
|
before do
|
|
|
|
SiteSetting.max_image_size_kb = 1
|
|
|
|
end
|
|
|
|
|
|
|
|
it "is not downsized" do
|
|
|
|
creator = UploadCreator.new(gif_file, "animated.gif")
|
|
|
|
creator.extract_image_info!
|
|
|
|
expect(creator.should_downsize?).to eq(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2021-06-15 09:10:03 -04:00
|
|
|
|
|
|
|
describe 'before_upload_creation event' do
|
|
|
|
let(:filename) { "logo.jpg" }
|
|
|
|
let(:file) { file_from_fixtures(filename) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
setup_s3
|
|
|
|
stub_s3_store
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not save the upload if an event added errors to the upload' do
|
|
|
|
error = 'This upload is invalid'
|
|
|
|
|
|
|
|
event = Proc.new do |file, is_image, upload|
|
|
|
|
upload.errors.add(:base, error)
|
|
|
|
end
|
|
|
|
|
|
|
|
DiscourseEvent.on(:before_upload_creation, &event)
|
|
|
|
|
|
|
|
created_upload = UploadCreator.new(file, filename).create_for(user.id)
|
|
|
|
|
|
|
|
expect(created_upload.persisted?).to eq(false)
|
|
|
|
expect(created_upload.errors).to contain_exactly(error)
|
|
|
|
DiscourseEvent.off(:before_upload_creation, &event)
|
|
|
|
end
|
|
|
|
end
|
2018-08-16 23:41:30 -04:00
|
|
|
end
|