discourse/spec/jobs/pull_hotlinked_images_spec.rb
Martin Brennan 8ebd5edd1e
DEV: Rename secure_media to secure_uploads (#18376)
This commit renames all secure_media related settings to secure_uploads_* along with the associated functionality.

This is being done because "media" does not really cover it, we aren't just doing this for images and videos etc. but for all uploads in the site.

Additionally, in future we want to secure more types of uploads, and enable a kind of "mixed mode" where some uploads are secure and some are not, so keeping media in the name is just confusing.

This also keeps compatibility with the `secure-media-uploads` path, and changes new
secure URLs to be `secure-uploads`.

Deprecated settings:

* secure_media -> secure_uploads
* secure_media_allow_embed_images_in_emails -> secure_uploads_allow_embed_images_in_emails
* secure_media_max_email_embed_image_size_kb -> secure_uploads_max_email_embed_image_size_kb
2022-09-29 09:24:33 +10:00

637 lines
24 KiB
Ruby

# frozen_string_literal: true
RSpec.describe Jobs::PullHotlinkedImages do
let(:image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat1.png" }
let(:broken_image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat2.png" }
let(:large_image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat3.png" }
let(:encoded_image_url) { "https://example.com/אלחוט-.jpg" }
let(:png) { Base64.decode64("R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==") }
let(:large_png) { Base64.decode64("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") }
let(:upload_path) { Discourse.store.upload_path }
before do
Jobs.run_immediately!
stub_request(:get, image_url).to_return(body: png, headers: { "Content-Type" => "image/png" })
stub_request(:get, encoded_image_url).to_return(body: png, headers: { "Content-Type" => "image/png" })
stub_request(:get, broken_image_url).to_return(status: 404)
stub_request(:get, large_image_url).to_return(body: large_png, headers: { "Content-Type" => "image/png" })
stub_request(
:get,
"#{Discourse.base_url}/#{upload_path}/original/1X/f59ea56fe8ebe42048491d43a19d9f34c5d0f8dc.gif"
)
stub_request(
:get,
"#{Discourse.base_url}/#{upload_path}/original/1X/c530c06cf89c410c0355d7852644a73fc3ec8c04.png"
)
SiteSetting.download_remote_images_to_local = true
SiteSetting.max_image_size_kb = 2
SiteSetting.download_remote_images_threshold = 0
end
describe '#execute' do
before do
Jobs.run_immediately!
end
it 'does nothing if topic has been deleted' do
post = Fabricate(:post, raw: "<img src='#{image_url}'>")
post.topic.destroy!
expect do
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
end.not_to change { Upload.count }
end
it 'does nothing if there are no large images to pull' do
post = Fabricate(:post, raw: 'bob bob')
orig = post.updated_at
freeze_time 1.week.from_now
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
expect(orig).to eq_time(post.reload.updated_at)
end
it 'replaces images' do
post = Fabricate(:post, raw: "<img src='#{image_url}'>")
stub_image_size
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
.to change { Upload.count }.by(1)
.and not_change { UserHistory.count } # Should not add to the staff log
expect(post.reload.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
end
it 'enqueues raw replacement job with a delay' do
Jobs.run_later!
post = Fabricate(:post, raw: "<img src='#{image_url}'>")
stub_image_size
freeze_time
Jobs.expects(:cancel_scheduled_job).with(:update_hotlinked_raw, post_id: post.id).once
delay = SiteSetting.editing_grace_period + 1
expect_enqueued_with(job: :update_hotlinked_raw, args: { post_id: post.id }, at: Time.zone.now + delay.seconds) do
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
end
end
it 'removes downloaded images when they are no longer needed' do
post = Fabricate(:post, raw: "<img src='#{image_url}'>")
stub_image_size
post.rebake!
post.reload
expect(post.upload_references.count).to eq(1)
post.update(raw: "Post with no images")
post.rebake!
post.reload
expect(post.upload_references.count).to eq(0)
end
it 'replaces images again after edit' do
post = Fabricate(:post, raw: "<img src='#{image_url}'>")
stub_image_size
expect do
post.rebake!
end.to change { Upload.count }.by(1)
expect(post.reload.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
# Post raw is updated back to the old value (e.g. by wordpress integration)
post.update(raw: "<img src='#{image_url}'>")
expect do
post.rebake!
end.not_to change { Upload.count } # We alread have the upload
expect(post.reload.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
end
it 'replaces encoded image urls' do
post = Fabricate(:post, raw: "<img src='#{encoded_image_url}'>")
stub_image_size
expect do
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
end.to change { Upload.count }.by(1)
expect(post.reload.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
end
it 'replaces images in an anchor tag with weird indentation' do
# Skipped pending https://meta.discourse.org/t/152801
# This spec was previously passing, even though the resulting markdown was invalid
# Now the spec has been improved, and shows the issue
stub_request(:get, "http://test.localhost/uploads/short-url/z2QSs1KJWoj51uYhDjb6ifCzxH6.gif")
.to_return(status: 200, body: "")
post = Fabricate(:post, raw: <<~MD)
<h1></h1>
<a href="https://somelink.com">
<img alt="somelink" src="#{image_url}">
</a>
MD
expect do
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
end.to change { Upload.count }.by(1)
upload = post.uploads.last
expect(post.reload.raw).to eq(<<~MD.chomp)
<h1></h1>
<a href="https://somelink.com">
<img alt="somelink" src="#{upload.short_url}">
</a>
MD
end
it 'replaces correct image URL' do
url = image_url.sub("/2e/Longcat1.png", '')
post = Fabricate(:post, raw: "[Images](#{url})\n![](#{image_url})")
stub_image_size
expect do
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
end.to change { Upload.count }.by(1)
expect(post.reload.raw).to eq("[Images](#{url})\n![](#{Upload.last.short_url})")
end
it 'replaces images without protocol' do
url = image_url.sub(/^https?\:/, '')
post = Fabricate(:post, raw: "<img alt='test' src='#{url}'>")
stub_image_size
expect do
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
end.to change { Upload.count }.by(1)
expect(post.reload.raw).to eq("<img alt=\"test\" src=\"#{Upload.last.short_url}\">")
end
it 'replaces images without extension' do
url = image_url.sub(/\.[a-zA-Z0-9]+$/, '')
stub_request(:get, url).to_return(body: png, headers: { "Content-Type" => "image/png" })
post = Fabricate(:post, raw: "<img src='#{url}'>")
stub_image_size
expect do
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
end.to change { Upload.count }.by(1)
expect(post.reload.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
end
it 'replaces optimized images' do
optimized_image = Fabricate(:optimized_image)
url = "#{Discourse.base_url}#{optimized_image.url}"
stub_request(:get, url)
.to_return(status: 200, body: file_from_fixtures("smallest.png"))
post = Fabricate(:post, raw: "<img src='#{url}'>")
stub_image_size
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
.to change { Upload.count }.by(1)
upload = Upload.last
post.reload
expect(post.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
expect(post.uploads).to contain_exactly(upload)
end
it "skips editing raw for raw_html posts" do
raw = "<img src=\"#{image_url}\">"
post = Fabricate(:post, raw: raw, cook_method: Post.cook_methods[:raw_html])
stub_image_size
expect do
post.rebake!
post.reload
end.to change { Upload.count }.by(1)
expect(post.raw).to eq(raw)
end
context "when secure uploads enabled for an upload that has already been downloaded and exists" do
it "doesnt redownload the secure upload" do
setup_s3
SiteSetting.secure_uploads = true
upload = Fabricate(:secure_upload_s3, secure: true)
stub_s3(upload)
url = Upload.secure_uploads_url_from_upload_url(upload.url)
url = Discourse.base_url + url
post = Fabricate(:post, raw: "<img src='#{url}'>")
upload.update(access_control_post: post)
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
.not_to change { Upload.count }
end
context "when the upload original_sha1 is missing" do
it "redownloads the upload" do
setup_s3
SiteSetting.secure_uploads = true
upload = Fabricate(:upload_s3, secure: true)
stub_s3(upload)
Upload.stubs(:signed_url_from_secure_uploads_url).returns(upload.url)
url = Upload.secure_uploads_url_from_upload_url(upload.url)
url = Discourse.base_url + url
post = Fabricate(:post, raw: "<img src='#{url}'>")
upload.update(access_control_post: post)
FileStore::S3Store.any_instance.stubs(:store_upload).returns(upload.url)
# without this we get an infinite hang...
Post.any_instance.stubs(:trigger_post_process)
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
.to change { Upload.count }.by(1)
end
end
context "when the upload access_control_post is different to the current post" do
it "redownloads the upload" do
setup_s3
SiteSetting.secure_uploads = true
upload = Fabricate(:secure_upload_s3, secure: true)
stub_s3(upload)
Upload.stubs(:signed_url_from_secure_uploads_url).returns(upload.url)
url = Upload.secure_uploads_url_from_upload_url(upload.url)
url = Discourse.base_url + url
post = Fabricate(:post, raw: "<img src='#{url}'>")
upload.update(access_control_post: Fabricate(:post))
FileStore::S3Store.any_instance.stubs(:store_upload).returns(upload.url)
FastImage.expects(:size).returns([100, 100]).at_least_once
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
.to change { Upload.count }.by(1)
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
.not_to change { Upload.count }
end
end
end
it 'replaces markdown image' do
post = Fabricate(:post, raw: <<~MD)
[![some test](#{image_url})](https://somelink.com)
![some test](#{image_url})
![](#{image_url})
![abcde](#{image_url} 'some test')
![](#{image_url} 'some test')
MD
stub_image_size
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
.to change { Upload.count }.by(1)
post.reload
expect(post.raw).to eq(<<~MD.chomp)
[![some test](#{Upload.last.short_url})](https://somelink.com)
![some test](#{Upload.last.short_url})
![](#{Upload.last.short_url})
![abcde](#{Upload.last.short_url} 'some test')
![](#{Upload.last.short_url} 'some test')
MD
end
it 'works when invalid url in post' do
post = Fabricate(:post, raw: <<~MD)
![some test](#{image_url})
![some test 2]("#{image_url})
MD
stub_image_size
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
.to change { Upload.count }.by(1)
end
it 'replaces bbcode images' do
post = Fabricate(:post, raw: <<~MD)
[img]
#{image_url}
[/img]
[img]
#{image_url}
[/img]
MD
stub_image_size
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
.to change { Upload.count }.by(1)
post.reload
expect(post.raw).to eq(<<~MD.chomp)
![](#{Upload.last.short_url})
![](#{Upload.last.short_url})
MD
end
describe 'onebox' do
let(:media) { "File:Brisbane_May_2013201.jpg" }
let(:url) { "https://commons.wikimedia.org/wiki/#{media}" }
let(:api_url) { "https://en.wikipedia.org/w/api.php?action=query&titles=#{media}&prop=imageinfo&iilimit=50&iiprop=timestamp|user|url&iiurlwidth=500&format=json" }
before do
stub_request(:head, url)
stub_request(:get, url).to_return(body: '')
stub_request(:head, image_url)
stub_request(:get, api_url).to_return(body: "{
\"query\": {
\"pages\": {
\"-1\": {
\"title\": \"#{media}\",
\"imageinfo\": [{
\"thumburl\": \"#{image_url}\",
\"url\": \"#{image_url}\",
\"descriptionurl\": \"#{url}\"
}]
}
}
}
}")
end
it 'replaces image src' do
post = Fabricate(:post, raw: "#{url}")
stub_image_size
post.rebake!
post.reload
expect(post.cooked).to match(/<img src=.*\/uploads/)
expect(post.upload_references.count).to eq(1)
end
it 'associates uploads correctly' do
post = Fabricate(:post, raw: "#{url}")
stub_image_size
post.rebake!
post.reload
expect(post.upload_references.count).to eq(1)
post.update(raw: "no onebox")
post.rebake!
post.reload
expect(post.upload_references.count).to eq(0)
end
it 'all combinations' do
post = Fabricate(:post, raw: <<~MD)
<img src='#{image_url}'>
#{url}
<img src='#{broken_image_url}'>
<a href='#{url}'><img src='#{large_image_url}'></a>
#{image_url}
MD
stub_image_size
2.times do
post.rebake!
end
post.reload
expect(post.raw).to eq(<<~MD.chomp)
<img src="upload://z2QSs1KJWoj51uYhDjb6ifCzxH6.gif">
https://commons.wikimedia.org/wiki/File:Brisbane_May_2013201.jpg
<img src='#{broken_image_url}'>
<a href='#{url}'><img src='#{large_image_url}'></a>
![](upload://z2QSs1KJWoj51uYhDjb6ifCzxH6.gif)
MD
expect(post.cooked).to match(/<p><img src=.*\/uploads/)
expect(post.cooked).to match(/<img src=.*\/uploads.*\ class="thumbnail"/)
expect(post.cooked).to match(/<span class="broken-image/)
expect(post.cooked).to match(/<div class="large-image-placeholder">/)
end
it 'rewrites a lone onebox' do
post = Fabricate(:post, raw: <<~MD)
Onebox here:
#{image_url}
MD
stub_image_size
post.rebake!
post.reload
expect(post.raw).to eq(<<~MD.chomp)
Onebox here:
![](upload://z2QSs1KJWoj51uYhDjb6ifCzxH6.gif)
MD
expect(post.cooked).to match(/<img src=.*\/uploads/)
end
end
end
describe '#should_download_image?' do
subject { described_class.new }
describe 'when url is invalid' do
it 'should return false' do
expect(subject.should_download_image?("null")).to eq(false)
expect(subject.should_download_image?("meta.discourse.org")).to eq(false)
end
end
describe 'when url is valid' do
it 'should return true' do
expect(subject.should_download_image?("http://meta.discourse.org")).to eq(true)
expect(subject.should_download_image?("//meta.discourse.org")).to eq(true)
end
end
describe 'when url is an upload' do
it 'should return false for original' do
expect(subject.should_download_image?(Fabricate(:upload).url)).to eq(false)
end
context "when secure uploads enabled" do
it 'should return false for secure-upload url' do
setup_s3
SiteSetting.secure_uploads = true
upload = Fabricate(:upload_s3, secure: true)
stub_s3(upload)
url = Upload.secure_uploads_url_from_upload_url(upload.url)
expect(subject.should_download_image?(url)).to eq(false)
end
end
it 'should return true for optimized' do
src = Discourse.store.get_path_for_optimized_image(Fabricate(:optimized_image))
expect(subject.should_download_image?(src)).to eq(true)
end
end
it "returns false for emoji" do
src = Emoji.url_for("testemoji.png")
expect(subject.should_download_image?(src)).to eq(false)
end
it "returns false for emoji when app and S3 CDNs configured" do
setup_s3
SiteSetting.s3_cdn_url = "https://s3.cdn.com"
set_cdn_url "https://mydomain.cdn/test"
src = UrlHelper.cook_url(Emoji.url_for("testemoji.png"))
expect(subject.should_download_image?(src)).to eq(false)
end
it "returns false for emoji when emoji CDN configured" do
SiteSetting.external_emoji_url = "https://emoji.cdn.com"
src = UrlHelper.cook_url(Emoji.url_for("testemoji.png"))
expect(subject.should_download_image?(src)).to eq(false)
end
it "returns false for emoji when app, S3 *and* emoji CDNs configured" do
setup_s3
SiteSetting.s3_cdn_url = "https://s3.cdn.com"
SiteSetting.external_emoji_url = "https://emoji.cdn.com"
set_cdn_url "https://mydomain.cdn/test"
src = UrlHelper.cook_url(Emoji.url_for("testemoji.png"))
expect(subject.should_download_image?(src)).to eq(false)
end
it "returns false for plugin assets" do
src = UrlHelper.cook_url("/plugins/discourse-amazing-plugin/myasset.png")
expect(subject.should_download_image?(src)).to eq(false)
end
it "returns false for local non-uploaded files" do
src = UrlHelper.cook_url("/mycustomroute.png")
expect(subject.should_download_image?(src)).to eq(false)
end
context "when download_remote_images_to_local? is false" do
before do
SiteSetting.download_remote_images_to_local = false
end
it "still returns true for optimized" do
src = Discourse.store.get_path_for_optimized_image(Fabricate(:optimized_image))
expect(subject.should_download_image?(src)).to eq(true)
end
it 'returns false for valid remote URLs' do
expect(subject.should_download_image?("http://meta.discourse.org")).to eq(false)
end
end
end
describe "with a lightboxed image" do
fab!(:upload) { Fabricate(:upload) }
fab!(:user) { Fabricate(:user) }
before do
FastImage.expects(:size).returns([1750, 2000]).at_least_once
OptimizedImage.stubs(:resize).returns(true)
Jobs.run_immediately!
end
it 'replaces missing local uploads in lightbox link' do
post = PostCreator.create!(
user,
raw: "<img src='#{Discourse.base_url}#{upload.url}'>",
title: "Some title that is long enough"
)
expect(post.reload.cooked).to have_tag(:a, with: { class: "lightbox" })
stub_request(:get, "#{Discourse.base_url}#{upload.url}")
.to_return(status: 200, body: file_from_fixtures("smallest.png"))
upload.delete
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
.to change { Upload.count }.by(1)
post.reload
expect(post.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
expect(post.uploads.count).to eq(1)
end
it "doesn't remove optimized images from lightboxes" do
post = PostCreator.create!(
user,
raw: "![alt](#{upload.short_url})",
title: "Some title that is long enough"
)
expect(post.reload.cooked).to have_tag(:a, with: { class: "lightbox" })
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
.not_to change { Upload.count }
post.reload
expect(post.raw).to eq("![alt](#{upload.short_url})")
end
end
describe "#disable_if_low_on_disk_space" do
fab!(:post) { Fabricate(:post, created_at: 20.days.ago) }
let(:job) { Jobs::PullHotlinkedImages.new }
before do
SiteSetting.download_remote_images_to_local = true
SiteSetting.download_remote_images_threshold = 20
job.stubs(:available_disk_space).returns(50)
end
it "does nothing when there's enough disk space" do
SiteSetting.expects(:download_remote_images_to_local=).never
job.execute({ post_id: post.id })
end
context "when there's not enough disk space" do
before { SiteSetting.download_remote_images_threshold = 75 }
it "disables download_remote_images_threshold and send a notification to the admin" do
StaffActionLogger.any_instance.expects(:log_site_setting_change).once
SystemMessage.expects(:create_from_system_user).with(Discourse.site_contact_user, :download_remote_images_disabled).once
job.execute({ post_id: post.id })
expect(SiteSetting.download_remote_images_to_local).to eq(false)
end
it "doesn't disable download_remote_images_to_local if site uses S3" do
setup_s3
job.execute({ post_id: post.id })
expect(SiteSetting.download_remote_images_to_local).to eq(true)
end
end
end
def stub_s3(upload)
stub_upload(upload)
stub_request(:get, "https:" + upload.url).to_return(status: 200, body: file_from_fixtures("smallest.png"))
end
end