From 5690cf4d4e05eafdfd2e270bbdf1a925114d0f76 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Sun, 9 Aug 2026 23:31:35 +0200 Subject: Escape feed content with Builder rather than by hand Builder escapes by default; the three feed templates no longer call CGI.escapeHTML. This fixes two sites that never escaped at all: the tag feed's externally supplied :tag segment, interpolated into its title, self link and id, and dc:creator in the RDF template. Subscribers see one difference: quotes and apostrophes arrive raw, which is valid in element text. config/initializers/xmlparser.rb, which redefined Builder::XmlBase#_escape as the identity function, is gone. XML::Node#replace_with went with it, no callers. --- app/views/rss/tag_updates.xml.builder | 4 ++-- app/views/rss/updates.rdf.builder | 4 ++-- app/views/rss/updates.xml.builder | 6 +++--- config/initializers/xmlparser.rb | 19 ------------------- test/controllers/rss_controller_test.rb | 15 +++++++++++++++ 5 files changed, 22 insertions(+), 26 deletions(-) delete mode 100644 config/initializers/xmlparser.rb diff --git a/app/views/rss/tag_updates.xml.builder b/app/views/rss/tag_updates.xml.builder index 810ee9aa..9fde2a55 100644 --- a/app/views/rss/tag_updates.xml.builder +++ b/app/views/rss/tag_updates.xml.builder @@ -12,7 +12,7 @@ xml.feed(:xmlns => "http://www.w3.org/2005/Atom", "xml:base" => @host) do @items.each do |item| xml.entry do - xml.title(CGI.escapeHTML(item.title.to_s)) + xml.title(item.title.to_s) xml.link( :href => content_url(:page_path => item.node.unique_path), :rel => "alternate", @@ -21,7 +21,7 @@ xml.feed(:xmlns => "http://www.w3.org/2005/Atom", "xml:base" => @host) do xml.id(content_url(:page_path => item.node.feed_id)) xml.updated(item.updated_at.xmlschema) xml.published(item.published_at.xmlschema) - xml.summary(CGI.escapeHTML(item.abstract.to_s)) + xml.summary(item.abstract.to_s) end end end diff --git a/app/views/rss/updates.rdf.builder b/app/views/rss/updates.rdf.builder index b4fecdb0..699e9c87 100644 --- a/app/views/rss/updates.rdf.builder +++ b/app/views/rss/updates.rdf.builder @@ -17,9 +17,9 @@ xml.tag!("rdf:RDF", "xmlns:rdf" => "http://www.w3.org/1999/02/22-rdf-syntax-ns#" @items.each do |item| xml.item("rdf:about" => content_url(:page_path => item.node.unique_path)) do - xml.title(CGI.escapeHTML(item.title.to_s)) + xml.title(item.title.to_s) xml.link(content_url(:page_path => item.node.unique_path)) - xml.description(CGI.escapeHTML(item.abstract.to_s)) + xml.description(item.abstract.to_s) xml.tag!("dc:creator", (item.user ? item.user.login : "CCC")) xml.tag!("dc:date", item.published_at.xmlschema) end diff --git a/app/views/rss/updates.xml.builder b/app/views/rss/updates.xml.builder index a2c277d6..f09d6a74 100644 --- a/app/views/rss/updates.xml.builder +++ b/app/views/rss/updates.xml.builder @@ -12,7 +12,7 @@ xml.feed(:xmlns => "http://www.w3.org/2005/Atom", "xml:base" => @host) do @items.each do |item| xml.entry do - xml.title(CGI.escapeHTML(item.title.to_s)) + xml.title(item.title.to_s) xml.link( :href => content_url(:page_path => item.node.unique_path), :rel => "alternate", @@ -21,8 +21,8 @@ xml.feed(:xmlns => "http://www.w3.org/2005/Atom", "xml:base" => @host) do xml.id(content_url(:page_path => item.node.feed_id)) xml.updated(item.updated_at.xmlschema) xml.published(item.published_at.xmlschema) - xml.summary(CGI.escapeHTML(item.abstract.to_s)) - xml.content(CGI.escapeHTML(item.body.to_s), :type => "html") + xml.summary(item.abstract.to_s) + xml.content(item.body.to_s, :type => "html") end end diff --git a/config/initializers/xmlparser.rb b/config/initializers/xmlparser.rb deleted file mode 100644 index 1d5e06d9..00000000 --- a/config/initializers/xmlparser.rb +++ /dev/null @@ -1,19 +0,0 @@ -class XML::Node - def replace_with(other) - self.next = other - remove! - end -end - -# Builder 3.x escapes content by default. Override _escape to pass text -# through raw, preserving existing behaviour from the Rails 2 era. -# Note: require builder first to ensure XmlBase < BasicObject is already -# defined before we reopen it. -require 'builder' -module Builder - class XmlBase - def _escape(text) - text - end - end -end diff --git a/test/controllers/rss_controller_test.rb b/test/controllers/rss_controller_test.rb index 00224119..4393c5fc 100644 --- a/test/controllers/rss_controller_test.rb +++ b/test/controllers/rss_controller_test.rb @@ -41,4 +41,19 @@ class RssControllerTest < ActionController::TestCase assert_includes @response.body, "feed-inside" assert_not_includes @response.body, "feed-outside" end + + test "the feed escapes markup characters in a title" do + updates = Node.root.children.find_by(:slug => "updates") + node = updates.children.create!(:slug => "feed-escaping") + node.reload.draft.update!(:title => %{Fnord & bold "quoted"}, + :tag_list => "update") + node.publish_draft! + + get :updates, params: { :format => :xml } + + assert_response :success + assert_includes @response.body, "Fnord & <b>bold</b>" + assert_not_includes @response.body, "bold" + assert_not_includes @response.body, "&amp;" + end end -- cgit v1.3