From 04b752a99fce3ffa5d1cfa20253a9fef6882f9ba Mon Sep 17 00:00:00 2001 From: Nat Date: Wed, 19 Feb 2025 17:31:04 +0800 Subject: [PATCH 1/3] FIX: Ensure translated content is safe for rendering --- .../discourse_translator/translatable.rb | 6 ++++ .../translated_content_sanitizer.rb | 18 ++++++++++++ spec/lib/translated_content_sanitizer_spec.rb | 29 +++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 lib/discourse_translator/translated_content_sanitizer.rb create mode 100644 spec/lib/translated_content_sanitizer_spec.rb diff --git a/app/models/concerns/discourse_translator/translatable.rb b/app/models/concerns/discourse_translator/translatable.rb index 9b451f8b..2866df77 100644 --- a/app/models/concerns/discourse_translator/translatable.rb +++ b/app/models/concerns/discourse_translator/translatable.rb @@ -17,8 +17,14 @@ def set_detected_locale(locale) (content_locale || build_content_locale).update!(detected_locale: locale) end + # This method is used to create a translation for a translatable (Post or Topic) and a specific locale. + # If a translation already exists for the locale, it will be updated. + # Texts are put through a Sanitizer to clean them up before saving. + # @param locale [String] the locale of the translation + # @param text [String] the translated text def set_translation(locale, text) locale = locale.to_s.gsub("_", "-") + text = DiscourseTranslator::TranslatedContentSanitizer.sanitize(self.class, text) translations.find_or_initialize_by(locale: locale).update!(translation: text) end diff --git a/lib/discourse_translator/translated_content_sanitizer.rb b/lib/discourse_translator/translated_content_sanitizer.rb new file mode 100644 index 00000000..c12e2217 --- /dev/null +++ b/lib/discourse_translator/translated_content_sanitizer.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module DiscourseTranslator + class TranslatedContentSanitizer + def self.sanitize(model, content) + case model.to_s + when "Topic" + return ERB::Util.html_escape(content) unless SiteSetting.title_fancy_entities? + Topic.fancy_title(content) + when "Post" + PrettyText.cleanup(content, {}) + else + # raise an error if the model is not supported + raise ArgumentError.new("Model not supported") + end + end + end +end diff --git a/spec/lib/translated_content_sanitizer_spec.rb b/spec/lib/translated_content_sanitizer_spec.rb new file mode 100644 index 00000000..903ae6e9 --- /dev/null +++ b/spec/lib/translated_content_sanitizer_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +describe DiscourseTranslator::TranslatedContentSanitizer do + describe "Posts" do + it "sanitizes the content" do + sanitized = + DiscourseTranslator::TranslatedContentSanitizer.sanitize( + Post, + "

Testing

This is a test post

", + ) + + expect(sanitized).to eq("

Testing

This is a test post

") + end + end + + describe "Topics" do + it "escapes and prettifies" do + sanitized = + DiscourseTranslator::TranslatedContentSanitizer.sanitize( + Topic, + "

Testing

This is a test post

", + ) + + expect(sanitized).to eq( + "<script>alert(‘test’)</script><p> <h1>Testing</h1> This is a test post</p>", + ) + end + end +end From 15c56350a3fde10da726270d78c84a7e5de09ff2 Mon Sep 17 00:00:00 2001 From: Nat Date: Tue, 25 Feb 2025 11:25:08 +0800 Subject: [PATCH 2/3] Do not escape titles as we need to keep compatibility with manual inline translations --- .../discourse_translator/translatable.rb | 2 +- .../translated_content_sanitizer.rb | 13 ++------- spec/lib/translated_content_sanitizer_spec.rb | 29 ++++--------------- 3 files changed, 9 insertions(+), 35 deletions(-) diff --git a/app/models/concerns/discourse_translator/translatable.rb b/app/models/concerns/discourse_translator/translatable.rb index 2866df77..ad8a202a 100644 --- a/app/models/concerns/discourse_translator/translatable.rb +++ b/app/models/concerns/discourse_translator/translatable.rb @@ -24,7 +24,7 @@ def set_detected_locale(locale) # @param text [String] the translated text def set_translation(locale, text) locale = locale.to_s.gsub("_", "-") - text = DiscourseTranslator::TranslatedContentSanitizer.sanitize(self.class, text) + text = DiscourseTranslator::TranslatedContentSanitizer.sanitize(text) translations.find_or_initialize_by(locale: locale).update!(translation: text) end diff --git a/lib/discourse_translator/translated_content_sanitizer.rb b/lib/discourse_translator/translated_content_sanitizer.rb index c12e2217..5e028697 100644 --- a/lib/discourse_translator/translated_content_sanitizer.rb +++ b/lib/discourse_translator/translated_content_sanitizer.rb @@ -2,17 +2,8 @@ module DiscourseTranslator class TranslatedContentSanitizer - def self.sanitize(model, content) - case model.to_s - when "Topic" - return ERB::Util.html_escape(content) unless SiteSetting.title_fancy_entities? - Topic.fancy_title(content) - when "Post" - PrettyText.cleanup(content, {}) - else - # raise an error if the model is not supported - raise ArgumentError.new("Model not supported") - end + def self.sanitize(content) + PrettyText.cleanup(content, {}) end end end diff --git a/spec/lib/translated_content_sanitizer_spec.rb b/spec/lib/translated_content_sanitizer_spec.rb index 903ae6e9..8fb4e347 100644 --- a/spec/lib/translated_content_sanitizer_spec.rb +++ b/spec/lib/translated_content_sanitizer_spec.rb @@ -1,29 +1,12 @@ # frozen_string_literal: true describe DiscourseTranslator::TranslatedContentSanitizer do - describe "Posts" do - it "sanitizes the content" do - sanitized = - DiscourseTranslator::TranslatedContentSanitizer.sanitize( - Post, - "

Testing

This is a test post

", - ) - - expect(sanitized).to eq("

Testing

This is a test post

") - end - end - - describe "Topics" do - it "escapes and prettifies" do - sanitized = - DiscourseTranslator::TranslatedContentSanitizer.sanitize( - Topic, - "

Testing

This is a test post

", - ) - - expect(sanitized).to eq( - "<script>alert(‘test’)</script><p> <h1>Testing</h1> This is a test post</p>", + it "sanitizes the content" do + sanitized = + DiscourseTranslator::TranslatedContentSanitizer.sanitize( + "

Testing

This is a test post

", ) - end + + expect(sanitized).to eq("

Testing

This is a test post

") end end From 0c49cfafd3b4a2e77d4e5e9e0c3a359704e72dad Mon Sep 17 00:00:00 2001 From: Nat Date: Tue, 25 Feb 2025 11:32:46 +0800 Subject: [PATCH 3/3] compat to the previous working version without prettytext.cleanup --- .discourse-compatibility | 1 + 1 file changed, 1 insertion(+) diff --git a/.discourse-compatibility b/.discourse-compatibility index c89970d9..99519795 100644 --- a/.discourse-compatibility +++ b/.discourse-compatibility @@ -1,3 +1,4 @@ +< 3.5.0.beta2-dev: 5f24835801fdc7cb98e1bcf42d2ab2e49e609921 < 3.5.0.beta1-dev: 7d411e458bdd449f8aead2bc07cedeb00b856798 < 3.4.0.beta3-dev: b4cf3a065884816fa3f770248c2bf908ba65d8ac < 3.4.0.beta1-dev: 5346b4bafba2c2fb817f030a473b7bbca97b909c