diff --git a/app/commands/feed/import_from_opml.rb b/app/commands/feed/import_from_opml.rb
index 16d23faf3..71457f0d2 100644
--- a/app/commands/feed/import_from_opml.rb
+++ b/app/commands/feed/import_from_opml.rb
@@ -28,8 +28,8 @@ def find_or_create_group(group_name, user)
end
def create_feed(parsed_feed, group, user)
- feed = user.feeds.where(**parsed_feed.slice(:name, :url))
- .first_or_initialize
+ name = parsed_feed[:name] && ContentSanitizer.call(parsed_feed[:name])
+ feed = user.feeds.where(name:, url: parsed_feed[:url]).first_or_initialize
find_feed_name(feed, parsed_feed)
feed.last_fetched = 1.day.ago if feed.new_record?
feed.group_id = group.id if group
diff --git a/app/views/stories/_templates.html.erb b/app/views/stories/_templates.html.erb
index 5edd72714..741ce4f7e 100644
--- a/app/views/stories/_templates.html.erb
+++ b/app/views/stories/_templates.html.erb
@@ -5,7 +5,7 @@
- {{= source }}
+ {{- source }}
diff --git a/spec/commands/feed/import_from_opml_spec.rb b/spec/commands/feed/import_from_opml_spec.rb
index d5ab46139..84fe8b0f1 100644
--- a/spec/commands/feed/import_from_opml_spec.rb
+++ b/spec/commands/feed/import_from_opml_spec.rb
@@ -61,6 +61,21 @@ def find_feed(feed_details)
end
end
+ def evil_opml
+ <<~XML
+
+
+
+ XML
+ end
+
+ it "sanitizes feed names" do
+ described_class.call(evil_opml, user: default_user)
+
+ expect(Feed.last.name).to eq('

Evil')
+ end
+
context "adding group_id for existing feeds" do
it "retains exising feeds" do
feed1 = create_feed(tmw_football)
diff --git a/spec/javascript/setup.ts b/spec/javascript/setup.ts
index 58ed016d7..9ee9a9e23 100644
--- a/spec/javascript/setup.ts
+++ b/spec/javascript/setup.ts
@@ -36,7 +36,7 @@ const templateHTML = [
'
',
"
",
' ',
- " {{= source }}",
+ " {{- source }}",
"
",
" ",
' ',
diff --git a/spec/javascript/spec/views/story_view_spec.ts b/spec/javascript/spec/views/story_view_spec.ts
index 0d5c4ec9e..42fb8ecb1 100644
--- a/spec/javascript/spec/views/story_view_spec.ts
+++ b/spec/javascript/spec/views/story_view_spec.ts
@@ -49,6 +49,16 @@ describe("StoryView", function () {
assertPropertyRendered(view.el, story, "source");
});
+ it("should escape blog title", function () {
+ const evilStory = new Story({ ...story.attributes, source: "

" });
+ const evilView = new StoryView({ model: evilStory });
+ evilView.render();
+
+ const blogTitle = evilView.el.querySelector(".blog-title");
+ expect(blogTitle.querySelector("img")).toBeNull();
+ expect(blogTitle.textContent.trim()).toBe("

");
+ });
+
it("should render story headline", function () {
assertTagExists(view.el, ".story-title");
assertPropertyRendered(view.el, story, "headline");