require "rails_helper" require "cooked_post_processor" describe CookedPostProcessor do context ".post_process" do let(:post) { build(:post) } let(:cpp) { CookedPostProcessor.new(post) } let(:post_process) { sequence("post_process") } it "post process in sequence" do cpp.expects(:post_process_oneboxes).in_sequence(post_process) cpp.expects(:post_process_images).in_sequence(post_process) cpp.expects(:keep_reverse_index_up_to_date).in_sequence(post_process) cpp.expects(:optimize_urls).in_sequence(post_process) cpp.expects(:pull_hotlinked_images).in_sequence(post_process) cpp.post_process end end context "cooking options" do context "regular user" do let(:post) { Fabricate(:post) } it "doesn't omit nofollow" do cpp = CookedPostProcessor.new(post) expect(cpp.cooking_options[:omit_nofollow]).to eq(nil) end end context "admin user" do let(:post) { Fabricate(:post, user: Fabricate(:admin)) } it "omits nofollow" do cpp = CookedPostProcessor.new(post) expect(cpp.cooking_options[:omit_nofollow]).to eq(true) end end end context ".keep_reverse_index_up_to_date" do let(:video_upload) { Fabricate(:upload, url: '/uploads/default/1/1234567890123456.mp4') } let(:image_upload) { Fabricate(:upload, url: '/uploads/default/1/1234567890123456.jpg') } let(:audio_upload) { Fabricate(:upload, url: '/uploads/default/1/1234567890123456.ogg') } let(:attachment_upload) { Fabricate(:upload, url: '/uploads/default/1/1234567890123456.csv') } let(:raw) do <<~RAW Link RAW end let(:post) { Fabricate(:post, raw: raw) } let(:cpp) { CookedPostProcessor.new(post) } it "finds all the uploads in the post" do cpp.keep_reverse_index_up_to_date expect(PostUpload.where(post: post).map(&:upload_id).sort).to eq( [video_upload.id, image_upload.id, audio_upload.id, attachment_upload.id].sort ) end it "cleans the reverse index up for the current post" do cpp.keep_reverse_index_up_to_date post_uploads_ids = post.post_uploads.pluck(:id) cpp.keep_reverse_index_up_to_date expect(post.reload.post_uploads.pluck(:id)).to_not eq(post_uploads_ids) end end context ".post_process_images" do shared_examples "leave dimensions alone" do it "doesn't use them" do expect(cpp.html).to match(/src="http:\/\/foo.bar\/image.png" width="" height=""/) expect(cpp.html).to match(/src="http:\/\/domain.com\/picture.jpg" width="50" height="42"/) expect(cpp).to be_dirty end end context "with image_sizes" do let(:post) { Fabricate(:post_with_image_urls) } let(:cpp) { CookedPostProcessor.new(post, image_sizes: image_sizes) } before { cpp.post_process_images } context "valid" do let(:image_sizes) { { "http://foo.bar/image.png" => { "width" => 111, "height" => 222 } } } it "uses them" do expect(cpp.html).to match(/src="http:\/\/foo.bar\/image.png" width="111" height="222"/) expect(cpp.html).to match(/src="http:\/\/domain.com\/picture.jpg" width="50" height="42"/) expect(cpp).to be_dirty end end context "invalid width" do let(:image_sizes) { { "http://foo.bar/image.png" => { "width" => 0, "height" => 222 } } } include_examples "leave dimensions alone" end context "invalid height" do let(:image_sizes) { { "http://foo.bar/image.png" => { "width" => 111, "height" => 0 } } } include_examples "leave dimensions alone" end context "invalid width & height" do let(:image_sizes) { { "http://foo.bar/image.png" => { "width" => 0, "height" => 0 } } } include_examples "leave dimensions alone" end end context "with unsized images" do let(:post) { Fabricate(:post_with_unsized_images) } let(:cpp) { CookedPostProcessor.new(post) } it "adds the width and height to images that don't have them" do FastImage.expects(:size).returns([123, 456]) cpp.post_process_images expect(cpp.html).to match(/width="123" height="456"/) expect(cpp).to be_dirty end end context "with large images" do let(:upload) { Fabricate(:upload) } let(:post) { Fabricate(:post_with_large_image) } let(:cpp) { CookedPostProcessor.new(post) } before do SiteSetting.max_image_height = 2000 SiteSetting.create_thumbnails = true FastImage.expects(:size).returns([1750, 2000]) end it "generates overlay information" do Upload.expects(:get_from_url).returns(upload) OptimizedImage.expects(:resize).returns(true) FileStore::BaseStore.any_instance.expects(:get_depth_for).returns(0) cpp.post_process_images expect(cpp.html).to match_html "
" expect(cpp).to be_dirty end describe 'when image is an svg' do let(:post) do Fabricate(:post, raw: '') end it 'should not add lightbox' do cpp.post_process_images expect(cpp.html).to match_html("") end describe 'when image src is an URL' do let(:post) do Fabricate(:post, raw: '') end it 'should not add lightbox' do SiteSetting.crawl_images = true cpp.post_process_images expect(cpp.html).to match_html("") end end end end context "with tall images" do let(:upload) { Fabricate(:upload) } let(:post) { Fabricate(:post_with_large_image) } let(:cpp) { CookedPostProcessor.new(post) } before do SiteSetting.create_thumbnails = true Upload.expects(:get_from_url).returns(upload) FastImage.expects(:size).returns([860, 2000]) OptimizedImage.expects(:resize).never OptimizedImage.expects(:crop).returns(true) FileStore::BaseStore.any_instance.expects(:get_depth_for).returns(0) end it "crops the image" do cpp.post_process_images expect(cpp.html).to match /width="690" height="500">/ expect(cpp).to be_dirty end end context "with iPhone X screenshots" do let(:upload) { Fabricate(:upload) } let(:post) { Fabricate(:post_with_large_image) } let(:cpp) { CookedPostProcessor.new(post) } before do SiteSetting.create_thumbnails = true Upload.expects(:get_from_url).returns(upload) FastImage.expects(:size).returns([1125, 2436]) OptimizedImage.expects(:resize).returns(true) OptimizedImage.expects(:crop).never FileStore::BaseStore.any_instance.expects(:get_depth_for).returns(0) end it "crops the image" do cpp.post_process_images expect(cpp.html).to match_html "" expect(cpp).to be_dirty end end context "with large images when using subfolders" do let(:upload) { Fabricate(:upload) } let(:post) { Fabricate(:post_with_large_image_on_subfolder) } let(:cpp) { CookedPostProcessor.new(post) } let(:base_url) { "http://test.localhost/subfolder" } let(:base_uri) { "/subfolder" } before do SiteSetting.max_image_height = 2000 SiteSetting.create_thumbnails = true Discourse.stubs(:base_url).returns(base_url) Discourse.stubs(:base_uri).returns(base_uri) Upload.expects(:get_from_url).returns(upload) FastImage.expects(:size).returns([1750, 2000]) OptimizedImage.expects(:resize).returns(true) FileStore::BaseStore.any_instance.expects(:get_depth_for).returns(0) end it "generates overlay information" do cpp.post_process_images expect(cpp.html).to match_html "" expect(cpp).to be_dirty end it "should escape the filename" do upload.update_attributes!(original_filename: ">.png") cpp.post_process_images expect(cpp.html).to match_html "" end end context "with title" do let(:upload) { Fabricate(:upload) } let(:post) { Fabricate(:post_with_large_image_and_title) } let(:cpp) { CookedPostProcessor.new(post) } before do SiteSetting.max_image_height = 2000 SiteSetting.create_thumbnails = true Upload.expects(:get_from_url).returns(upload) FastImage.expects(:size).returns([1750, 2000]) OptimizedImage.expects(:resize).returns(true) FileStore::BaseStore.any_instance.expects(:get_depth_for).returns(0) end it "generates overlay information" do cpp.post_process_images expect(cpp.html).to match_html "" expect(cpp).to be_dirty end end context "topic image" do let(:topic) { build(:topic, id: 1) } let(:post) { Fabricate(:post_with_uploaded_image, topic: topic) } let(:cpp) { CookedPostProcessor.new(post) } it "adds a topic image if there's one in the first post" do FastImage.stubs(:size) expect(post.topic.image_url).to eq(nil) cpp.update_post_image post.topic.reload expect(post.topic.image_url).to be_present end end context "post image" do let(:reply) { Fabricate(:post_with_uploaded_image, post_number: 2) } let(:cpp) { CookedPostProcessor.new(reply) } it "adds a post image if there's one in the post" do FastImage.stubs(:size) expect(reply.image_url).to eq(nil) cpp.update_post_image reply.reload expect(reply.image_url).to be_present end end end context ".extract_images" do let(:post) { build(:post_with_plenty_of_images) } let(:cpp) { CookedPostProcessor.new(post) } it "does not extract emojis or images inside oneboxes or quotes" do expect(cpp.extract_images.length).to eq(0) end end context ".get_size_from_attributes" do let(:post) { build(:post) } let(:cpp) { CookedPostProcessor.new(post) } it "returns the size when width and height are specified" do img = { 'src' => 'http://foo.bar/image3.png', 'width' => 50, 'height' => 70 } expect(cpp.get_size_from_attributes(img)).to eq([50, 70]) end it "returns the size when width and height are floats" do img = { 'src' => 'http://foo.bar/image3.png', 'width' => 50.2, 'height' => 70.1 } expect(cpp.get_size_from_attributes(img)).to eq([50, 70]) end it "resizes when only width is specified" do img = { 'src' => 'http://foo.bar/image3.png', 'width' => 100 } SiteSetting.crawl_images = true FastImage.expects(:size).returns([200, 400]) expect(cpp.get_size_from_attributes(img)).to eq([100, 200]) end it "resizes when only height is specified" do img = { 'src' => 'http://foo.bar/image3.png', 'height' => 100 } SiteSetting.crawl_images = true FastImage.expects(:size).returns([100, 300]) expect(cpp.get_size_from_attributes(img)).to eq([33, 100]) end it "doesn't raise an error with a weird url" do img = { 'src' => nil, 'height' => 100 } SiteSetting.crawl_images = true expect(cpp.get_size_from_attributes(img)).to be_nil end end context ".get_size_from_image_sizes" do let(:post) { build(:post) } let(:cpp) { CookedPostProcessor.new(post) } it "returns the size" do image_sizes = { "http://my.discourse.org/image.png" => { "width" => 111, "height" => 222 } } expect(cpp.get_size_from_image_sizes("/image.png", image_sizes)).to eq([111, 222]) end end context ".get_size" do let(:post) { build(:post) } let(:cpp) { CookedPostProcessor.new(post) } it "ensures urls are absolute" do cpp.expects(:is_valid_image_url?).with("http://test.localhost/relative/url/image.png") cpp.get_size("/relative/url/image.png") end it "ensures urls have a default scheme" do cpp.expects(:is_valid_image_url?).with("http://schemaless.url/image.jpg") cpp.get_size("//schemaless.url/image.jpg") end it "caches the results" do SiteSetting.crawl_images = true FastImage.expects(:size).returns([200, 400]) cpp.get_size("http://foo.bar/image3.png") expect(cpp.get_size("http://foo.bar/image3.png")).to eq([200, 400]) end context "when crawl_images is disabled" do before do SiteSetting.crawl_images = false end it "doesn't call FastImage" do FastImage.expects(:size).never expect(cpp.get_size("http://foo.bar/image1.png")).to eq(nil) end it "is always allowed to crawl our own images" do store = stub store.expects(:has_been_uploaded?).returns(true) Discourse.expects(:store).returns(store) FastImage.expects(:size).returns([100, 200]) expect(cpp.get_size("http://foo.bar/image2.png")).to eq([100, 200]) end it "returns nil if FastImage can't get the original size" do Discourse.store.class.any_instance.expects(:has_been_uploaded?).returns(true) FastImage.expects(:size).returns(nil) expect(cpp.get_size("http://foo.bar/image3.png")).to eq(nil) end end end context ".is_valid_image_url?" do let(:post) { build(:post) } let(:cpp) { CookedPostProcessor.new(post) } it "validates HTTP(s) urls" do expect(cpp.is_valid_image_url?("http://domain.com")).to eq(true) expect(cpp.is_valid_image_url?("https://domain.com")).to eq(true) end it "doesn't validate other urls" do expect(cpp.is_valid_image_url?("ftp://domain.com")).to eq(false) expect(cpp.is_valid_image_url?("ftps://domain.com")).to eq(false) expect(cpp.is_valid_image_url?("/tmp/image.png")).to eq(false) expect(cpp.is_valid_image_url?("//domain.com")).to eq(false) end it "doesn't throw an exception with a bad URI" do expect(cpp.is_valid_image_url?("http://doLink
Google
text.txt (20 Bytes)
Link
Google
text.txt (20 Bytes)
Link
Google
text.txt (20 Bytes)