summaryrefslogtreecommitdiff
path: root/app/models/page.rb
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-09 15:29:40 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-09 15:29:40 +0200
commit9c3217df50d462d6be4399e3e654d591bf704df7 (patch)
tree627e474b273789667bca9eadffa0e7f626d002bb /app/models/page.rb
parent8310751d4db2854597d6379b24e346c65622972d (diff)
Fix malformed-HTML fallout across RSS, link rewriting, and flash messaging
Editors' TinyMCE output can contain unclosed void elements like <br>, which is valid HTML5 but invalid XML -- three different places assumed the stricter rule and broke or silently misbehaved on the looser one. The Atom feed's <content type="xhtml"> block required real, well-formed XML structure but was handed a raw, unescaped body string; switched to type="html" with CGI.escapeHTML, matching how title/summary already handle the same content. rewrite_links_in_body used libxml's strict XML parser to rewrite internal links to be locale-prefixed, which raised on exactly this class of malformed markup -- silently, since the whole method was wrapped in rescue; nil, meaning the link rewrite (not the save) quietly failed with no error anywhere. Replaced with Nokogiri's lenient HTML parser, which repairs malformed void elements rather than rejecting them; also drops the bare rescue now that the actual failure mode it was guarding against shouldn't occur, and fixes two adjacent bugs found while in this method: a typo'd /sytem/uploads/ regex that could never match, and a missing https:// exclusion alongside the existing http:// one. Also addresses stale flash messaging surfaced while testing the above: update's save confirmation was being clobbered by edit's own "locked and ready" notice on the very next request, since nothing distinguished a fresh lock acquisition from a redirect back after saving. The save confirmation now names the next step (publish from Status) and flags a stale translation if one exists, using Page#outdated_translations?, already present but previously unused by any controller.
Diffstat (limited to 'app/models/page.rb')
-rw-r--r--app/models/page.rb33
1 files changed, 12 insertions, 21 deletions
diff --git a/app/models/page.rb b/app/models/page.rb
index fff044e..1a98e08 100644
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -243,31 +243,22 @@ class Page < ApplicationRecord
243 end 243 end
244 244
245 def rewrite_links_in_body 245 def rewrite_links_in_body
246 begin 246 return unless self.body
247 if self.body
248 tmp_body = "<div>#{self.body}</div>"
249 xml_string = XML::Parser.string( tmp_body )
250 xml_doc = xml_string.parse
251 links = xml_doc.find("//a[not(starts-with(@href, 'http://'))]")
252 links = links.reject { |l| l[:href] =~ /system\/uploads/ }
253 locales = I18n.available_locales.reject {|l| l == :root}
254 247
255 links.each do |link| 248 doc = Nokogiri::HTML::DocumentFragment.parse(self.body)
256 unless locales.include? link[:href].slice(1,2).to_sym 249 locales = I18n.available_locales.reject { |l| l == :root }
257 unless link[:href] =~ /sytem\/uploads/
258 link[:href] = link[:href].sub(/^\//, "/#{I18n.locale}/")
259 end
260 end
261 end
262 250
263 tmp_body = xml_doc.to_s.gsub(/(\n\<div\>|\<\/div\>\n)/, "") 251 doc.css('a').each do |link|
264 tmp_body.gsub!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "") 252 href = link['href']
253 next unless href
254 next if href.start_with?('http://', 'https://')
255 next if href =~ /system\/uploads/
265 256
266 self.body = tmp_body 257 unless locales.include?(href.slice(1, 2)&.to_sym)
258 link['href'] = href.sub(/^\//, "/#{I18n.locale}/")
267 end 259 end
268 rescue
269 nil
270 end 260 end
271 end
272 261
262 self.body = doc.to_html
263 end
273end 264end