diff --git a/app/services/html/image_uri.rb b/app/services/html/image_uri.rb
new file mode 100644
index 000000000..91dc64f2a
--- /dev/null
+++ b/app/services/html/image_uri.rb
@@ -0,0 +1,43 @@
+module Html
+ class ImageUri
+ GITHUB_CAMO = {
+ scheme: "https",
+ host: "camo.githubusercontent.com"
+ }.freeze
+
+ GITHUB_BADGE = {
+ scheme: "https",
+ host: "github.com",
+ filename: "badge.svg"
+ }.freeze
+
+ attr_reader :uri, :original_source
+
+ delegate :scheme, :host, :path, to: :uri
+
+ def initialize(src)
+ @uri = URI(src)
+ @original_source = src
+ end
+
+ def allowed?
+ github_camo_user_content? || github_badge?
+ end
+
+ def github_badge?
+ scheme == GITHUB_BADGE[:scheme] &&
+ host == GITHUB_BADGE[:host] &&
+ filename == GITHUB_BADGE[:filename]
+ end
+
+ def github_camo_user_content?
+ scheme == GITHUB_CAMO[:scheme] && host == GITHUB_CAMO[:host]
+ end
+
+ private
+
+ def filename
+ File.basename path
+ end
+ end
+end
diff --git a/app/services/html/parser.rb b/app/services/html/parser.rb
index 5de8a5ac0..5d7d32b39 100644
--- a/app/services/html/parser.rb
+++ b/app/services/html/parser.rb
@@ -270,8 +270,7 @@ module Html
end
def allowed_image_host?(src)
- # GitHub camo image won't parse but should be safe to host direct
- src.start_with?("https://camo.githubusercontent.com")
+ ImageUri.new(src).allowed?
end
def user_link_if_exists(mention)
diff --git a/spec/services/html/image_uri_spec.rb b/spec/services/html/image_uri_spec.rb
new file mode 100644
index 000000000..e9f79a05c
--- /dev/null
+++ b/spec/services/html/image_uri_spec.rb
@@ -0,0 +1,36 @@
+require "rails_helper"
+
+RSpec.describe Html::ImageUri, type: :service do
+ let(:camo) { "https://camo.githubusercontent.com/64b9f7c7c5f41ec22113b61235256435cd61779a0554b0595b88b6011f94c60b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6d6d69742d61637469766974792f772f666f72656d2f666f72656d" }
+ let(:badge) { "https://github.com/forem/forem/actions/workflows/ci-cd.yml/badge.svg?branch=depfu/update/sterile-1.0.24" }
+ let(:giphy) { "https://media.giphy.com/media/3ow0TN2M8TH2aAn67F/giphy.gif" }
+ let(:other) { "https://image.com/image.jpg" }
+
+ it "can detect camo.github hosted image" do
+ image = described_class.new(camo)
+ expect(image).to be_github_camo_user_content
+
+ image = described_class.new(badge)
+ expect(image).not_to be_github_camo_user_content
+
+ image = described_class.new(giphy)
+ expect(image).not_to be_github_camo_user_content
+
+ image = described_class.new(other)
+ expect(image).not_to be_github_camo_user_content
+ end
+
+ it "can detect github hosted badge" do
+ image = described_class.new(badge)
+ expect(image).to be_github_badge
+
+ image = described_class.new(camo)
+ expect(image).not_to be_github_badge
+
+ image = described_class.new(giphy)
+ expect(image).not_to be_github_badge
+
+ image = described_class.new(other)
+ expect(image).not_to be_github_badge
+ end
+end