This fix was heavily inspired by Mastodon's fix for GHSA-9928-3cp5-93fm. So, thank you Cure53 for finding this issue, thank you Mozilla for paying Cure53 to look into it, and thanks for Mastodon for fixing it.
284 lines
8.4 KiB
Ruby
284 lines
8.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Copyright (c) 2010-2011, Diaspora Inc. This file is
|
|
# licensed under the Affero General Public License version 3 or later. See
|
|
# the COPYRIGHT file.
|
|
|
|
def with_carrierwave_processing(&block)
|
|
UnprocessedImage.enable_processing = true
|
|
val = yield
|
|
UnprocessedImage.enable_processing = false
|
|
val
|
|
end
|
|
|
|
describe Photo, :type => :model do
|
|
before do
|
|
@user = alice
|
|
@aspect = @user.aspects.first
|
|
|
|
@fixture_filename = 'button.png'
|
|
@fixture_name = File.join(File.dirname(__FILE__), '..', 'fixtures', @fixture_filename)
|
|
@fail_fixture_name = File.join(File.dirname(__FILE__), '..', 'fixtures', 'msg.xml')
|
|
|
|
@photo = @user.build_post(:photo, :user_file => File.open(@fixture_name), :to => @aspect.id)
|
|
@photo2 = @user.build_post(:photo, :user_file => File.open(@fixture_name), :to => @aspect.id)
|
|
@saved_photo = @user.build_post(:photo, :user_file => File.open(@fixture_name), :to => @aspect.id)
|
|
@saved_photo.save
|
|
end
|
|
|
|
describe 'after_create' do
|
|
it 'calls #queue_processing_job' do
|
|
expect(@photo).to receive(:queue_processing_job)
|
|
|
|
@photo.save!
|
|
end
|
|
end
|
|
|
|
it 'has a random string key' do
|
|
expect(@photo2.random_string).not_to be nil
|
|
end
|
|
|
|
describe '#diaspora_initialize' do
|
|
before do
|
|
@image = File.open(@fixture_name)
|
|
@photo = Photo.diaspora_initialize(author: @user.person, user_file: @image)
|
|
end
|
|
|
|
it 'sets the persons diaspora handle' do
|
|
expect(@photo.diaspora_handle).to eq(@user.person.diaspora_handle)
|
|
end
|
|
|
|
it 'sets the random prefix' do
|
|
photo_double = double.as_null_object
|
|
expect(photo_double).to receive(:random_string=)
|
|
allow(Photo).to receive(:new).and_return(photo_double)
|
|
|
|
Photo.diaspora_initialize(
|
|
:author => @user.person, :user_file => @image)
|
|
end
|
|
|
|
context "with user file" do
|
|
it 'builds the photo without saving' do
|
|
expect(@photo.created_at.nil?).to be true
|
|
expect(@photo.unprocessed_image.read.nil?).to be false
|
|
end
|
|
end
|
|
|
|
context "with a url" do
|
|
it 'saves the photo' do
|
|
url = "https://service.com/user/profile_image"
|
|
|
|
photo_double = double.as_null_object
|
|
expect(photo_double).to receive(:remote_unprocessed_image_url=).with(url)
|
|
allow(Photo).to receive(:new).and_return(photo_double)
|
|
|
|
Photo.diaspora_initialize(
|
|
:author => @user.person, :image_url => url)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#update_remote_path' do
|
|
before do
|
|
image = File.open(@fixture_name)
|
|
@photo = Photo.diaspora_initialize(
|
|
:author => @user.person, :user_file => image)
|
|
@photo.processed_image.store!(@photo.unprocessed_image)
|
|
@photo.save!
|
|
end
|
|
it 'sets a remote url' do
|
|
@photo.update_remote_path
|
|
|
|
expect(@photo.remote_photo_path).to include("http")
|
|
expect(@photo.remote_photo_name).to include(".png")
|
|
end
|
|
end
|
|
|
|
it 'should save a photo' do
|
|
@photo.unprocessed_image.store! File.open(@fixture_name)
|
|
expect(@photo.save).to eq(true)
|
|
|
|
binary = @photo.unprocessed_image.read.force_encoding('BINARY')
|
|
fixture_binary = File.read(@fixture_name).force_encoding('BINARY')
|
|
|
|
expect(binary).to eq(fixture_binary)
|
|
end
|
|
|
|
context 'with a saved photo' do
|
|
before do
|
|
with_carrierwave_processing do
|
|
@photo.unprocessed_image.store! File.open(@fixture_name)
|
|
end
|
|
end
|
|
|
|
it 'should have text' do
|
|
@photo.text= "cool story, bro"
|
|
expect(@photo.save).to be true
|
|
end
|
|
|
|
it 'should remove its reference in user profile if it is referred' do
|
|
@photo.save
|
|
|
|
@user.profile.image_url = @photo.url(:thumb_large)
|
|
@user.person.save
|
|
@photo.destroy
|
|
expect(Person.find(@user.person.id).profile[:image_url]).to be_nil
|
|
end
|
|
|
|
it 'should not use the imported filename as the url' do
|
|
expect(@photo.url).not_to include @fixture_filename
|
|
expect(@photo.url(:thumb_medium)).not_to include ("/" + @fixture_filename)
|
|
end
|
|
|
|
it 'should save the image dimensions' do
|
|
expect(@photo.width).to eq(40)
|
|
expect(@photo.height).to eq(40)
|
|
end
|
|
end
|
|
|
|
context 'with a saved photo containing EXIF data' do
|
|
|
|
let(:base_path) { File.dirname(__FILE__) }
|
|
let(:public_path) { File.join(base_path, "../../public/") }
|
|
let(:photo_with_exif) { File.open(File.join(base_path, "..", "fixtures", "exif.jpg")) }
|
|
|
|
after do
|
|
FileUtils.rm_r Dir.glob(File.join(public_path, "uploads/images/*"))
|
|
end
|
|
|
|
it "should preserve EXIF data in according to user preference" do
|
|
image = image_from a_photo_sent_by(alice)
|
|
|
|
expect(image.exif.length).not_to eq(0)
|
|
end
|
|
|
|
it "should not preserve EXIF in according to user preference" do
|
|
image = image_from a_photo_sent_by(bob)
|
|
|
|
expect(image.exif.length).to eq(0)
|
|
end
|
|
|
|
def a_photo_sent_by(user)
|
|
photo = user.build_post(:photo, user_file: photo_with_exif, to: @aspect.id)
|
|
|
|
with_carrierwave_processing do
|
|
photo.unprocessed_image.store! photo_with_exif
|
|
photo.save
|
|
end
|
|
|
|
photo
|
|
end
|
|
|
|
def image_from(photo)
|
|
photo_path = File.join(public_path, photo.unprocessed_image.store_dir, photo.unprocessed_image.filename)
|
|
MiniMagick::Image.new(photo_path)
|
|
end
|
|
end
|
|
|
|
describe 'non-image files' do
|
|
it 'should not store' do
|
|
file = File.open(@fail_fixture_name)
|
|
expect {
|
|
@photo.unprocessed_image.store! file
|
|
}.to raise_error CarrierWave::IntegrityError
|
|
end
|
|
|
|
end
|
|
|
|
describe "remote photos" do
|
|
it "should set the remote_photo on marshalling" do
|
|
url = @saved_photo.url
|
|
thumb_url = @saved_photo.url :thumb_medium
|
|
|
|
@saved_photo.height = 42
|
|
@saved_photo.width = 23
|
|
|
|
federation_photo = Diaspora::Federation::Entities.photo(@saved_photo)
|
|
|
|
@saved_photo.destroy
|
|
|
|
Diaspora::Federation::Receive.photo(federation_photo)
|
|
|
|
new_photo = Photo.find_by(guid: @saved_photo.guid)
|
|
expect(new_photo.url).to eq(url)
|
|
expect(new_photo.url(:thumb_medium)).to eq(thumb_url)
|
|
end
|
|
end
|
|
|
|
describe '#queue_processing_job' do
|
|
it 'should queue a job to process the images' do
|
|
expect(Workers::ProcessPhoto).to receive(:perform_async).with(@photo.id)
|
|
@photo.queue_processing_job
|
|
end
|
|
end
|
|
|
|
context "deletion" do
|
|
before do
|
|
@status_message = @user.build_post(:status_message, :text => "", :to => @aspect.id)
|
|
@status_message.photos << @photo2
|
|
@status_message.save
|
|
@status_message.reload
|
|
end
|
|
|
|
it 'is deleted with parent status message' do
|
|
expect {
|
|
@status_message.destroy
|
|
}.to change(Photo, :count).by(-1)
|
|
end
|
|
|
|
it 'will delete parent status message if message is otherwise empty' do
|
|
expect {
|
|
@photo2.destroy
|
|
}.to change(StatusMessage, :count).by(-1)
|
|
end
|
|
|
|
it 'will not delete parent status message if message had other content' do
|
|
@status_message.text = "Some text"
|
|
@status_message.save
|
|
@status_message.reload
|
|
|
|
expect {
|
|
@photo2.status_message.reload
|
|
@photo2.destroy
|
|
}.to_not change(StatusMessage, :count)
|
|
end
|
|
end
|
|
|
|
describe "#visible" do
|
|
context "with a current user" do
|
|
it "calls photos_from" do
|
|
expect(@user).to receive(:photos_from).with(@user.person, limit: :all, max_time: nil).and_call_original
|
|
Photo.visible(@user, @user.person)
|
|
end
|
|
|
|
it "does not contain pending photos" do
|
|
pending_photo = @user.post(:photo, pending: true, user_file: File.open(photo_fixture_name), to: @aspect)
|
|
expect(Photo.visible(@user, @user.person).ids).not_to include(pending_photo.id)
|
|
end
|
|
end
|
|
|
|
context "without a current user" do
|
|
it "returns all public photos" do
|
|
expect(Photo).to receive(:where).with(author_id: @user.person.id, public: true).and_call_original
|
|
Photo.visible(nil, @user.person)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with a maliciously crafted image" do
|
|
let(:base_path) { File.dirname(__FILE__) }
|
|
let(:public_path) { File.join(base_path, "../../public/") }
|
|
let(:evil_image) { File.open(File.join(base_path, "..", "fixtures", "evil-image.ps.png")) }
|
|
|
|
it "fails to process a PostScript file camouflaged as a PNG" do
|
|
photo = bob.build_post(:photo, user_file: evil_image, to: @aspect.id)
|
|
|
|
expect {
|
|
with_carrierwave_processing do
|
|
photo.unprocessed_image.store! evil_image
|
|
photo.save
|
|
end
|
|
}.to raise_error(CarrierWave::ProcessingError)
|
|
end
|
|
end
|
|
end
|