From 16c91328112e8f8e78bab8139cce8984a1a8e434 Mon Sep 17 00:00:00 2001 From: Sergey Tarasov Date: Tue, 31 Dec 2024 23:17:11 +0200 Subject: [PATCH] Add YAML.safe_load config option (#1668) * Add specs, better error handling * Added docs, changelog, better messages --- CHANGELOG.md | 1 + docs/guides/configuration.md | 3 ++ docs/guides/i18n.md | 5 +++ lib/react_on_rails/configuration.rb | 4 ++- lib/react_on_rails/locales/base.rb | 8 ++++- .../fixtures/i18n/locales_symbols/de.yml | 2 ++ .../fixtures/i18n/locales_symbols/en.yml | 8 +++++ spec/react_on_rails/locales_to_js_spec.rb | 34 +++++++++++++++++++ 8 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 spec/react_on_rails/fixtures/i18n/locales_symbols/de.yml create mode 100644 spec/react_on_rails/fixtures/i18n/locales_symbols/en.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 0083f0ab0..aaa732e31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Changes since the last non-beta release. - Enables progressive page loading and improved performance for server-rendered React components - Added support for replaying console logs that occur during server rendering of streamed React components. This enables debugging of server-side rendering issues by capturing and displaying console output on the client and on the server output. [PR #1647](https://github.com/shakacode/react_on_rails/pull/1647) by [AbanoubGhadban](https://github.com/AbanoubGhadban). - Added support for handling errors happening during server rendering of streamed React components. It handles errors that happen during the initial render and errors that happen inside suspense boundaries. [PR #1648](https://github.com/shakacode/react_on_rails/pull/1648) by [AbanoubGhadban](https://github.com/AbanoubGhadban). + - Added support for passing options to `YAML.safe_load` when loading locale files with `config.i18n_yml_safe_load_options`. [PR #1668](https://github.com/shakacode/react_on_rails/pull/1668) by [dzirtusss](https://github.com/dzirtusss). #### Changed - Console replay script generation now awaits the render request promise before generating, allowing it to capture console logs from asynchronous operations. This requires using a version of the Node renderer that supports replaying async console logs. [PR #1649](https://github.com/shakacode/react_on_rails/pull/1649) by [AbanoubGhadban](https://github.com/AbanoubGhadban). diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index 02bda2236..757093318 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -213,6 +213,9 @@ ReactOnRails.configure do |config| # The default format is json config.i18n_output_format = 'json' + # Possible YAML.safe_load options pass-through for locales + # config.i18n_yml_safe_load_options = { permitted_classes: [Symbol] } + ################################################################################ ################################################################################ # TEST CONFIGURATION OPTIONS diff --git a/docs/guides/i18n.md b/docs/guides/i18n.md index 69c105930..d6f27d165 100644 --- a/docs/guides/i18n.md +++ b/docs/guides/i18n.md @@ -31,6 +31,11 @@ You can use [Rails internationalization (i18n)](https://guides.rubyonrails.org/i 1) run the rake task to build the translations before running the lint command or 2) to run the tests first. +4. If your locale files (or one of the gems locale files) contains unsafe YAML, you may need to configure `config.i18n_yml_safe_load_options` if you can't fix such yamls properly. + ```rb + config.i18n_yml_safe_load_options = { permitted_classes: [Symbol] } + ``` + By default, the locales are generated as JSON, but you can also generate them as JavaScript with [`react-intl`](https://formatjs.io/docs/getting-started/installation/) support: 1. Specify the i18n output format in `config/initializers/react_on_rails.rb`: diff --git a/lib/react_on_rails/configuration.rb b/lib/react_on_rails/configuration.rb index 2a468c413..a26971c30 100644 --- a/lib/react_on_rails/configuration.rb +++ b/lib/react_on_rails/configuration.rb @@ -52,6 +52,7 @@ class Configuration :generated_assets_dirs, :generated_assets_dir, :components_subdirectory, :webpack_generated_files, :rendering_extension, :build_test_command, :build_production_command, :i18n_dir, :i18n_yml_dir, :i18n_output_format, + :i18n_yml_safe_load_options, :server_render_method, :random_dom_id, :auto_load_bundle, :same_bundle_for_client_and_server, :rendering_props_extension, :make_generated_server_bundle_the_entrypoint, @@ -69,7 +70,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender rendering_extension: nil, build_test_command: nil, build_production_command: nil, defer_generated_component_packs: nil, same_bundle_for_client_and_server: nil, - i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil, + i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil, i18n_yml_safe_load_options: nil, random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil, components_subdirectory: nil, auto_load_bundle: nil, force_load: nil) self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root @@ -80,6 +81,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender self.i18n_dir = i18n_dir self.i18n_yml_dir = i18n_yml_dir self.i18n_output_format = i18n_output_format + self.i18n_yml_safe_load_options = i18n_yml_safe_load_options self.random_dom_id = random_dom_id self.prerender = prerender diff --git a/lib/react_on_rails/locales/base.rb b/lib/react_on_rails/locales/base.rb index f434d777e..335c33f15 100644 --- a/lib/react_on_rails/locales/base.rb +++ b/lib/react_on_rails/locales/base.rb @@ -115,11 +115,17 @@ def generate_translations translations = {} defaults = {} locale_files.each do |f| - translation = YAML.safe_load(File.open(f)) + safe_load_options = ReactOnRails.configuration.i18n_yml_safe_load_options || {} + translation = YAML.safe_load(File.open(f), **safe_load_options) key = translation.keys[0] val = flatten(translation[key]) translations = translations.deep_merge(key => val) defaults = defaults.deep_merge(flatten_defaults(val)) if key == default_locale + rescue Psych::Exception => e + raise ReactOnRails::Error, <<~MSG + Error parsing #{f}: #{e.message} + Consider fixing unsafe YAML or permitting with config.i18n_yml_safe_load_options + MSG end [translations.to_json, defaults.to_json] end diff --git a/spec/react_on_rails/fixtures/i18n/locales_symbols/de.yml b/spec/react_on_rails/fixtures/i18n/locales_symbols/de.yml new file mode 100644 index 000000000..fc9fa1250 --- /dev/null +++ b/spec/react_on_rails/fixtures/i18n/locales_symbols/de.yml @@ -0,0 +1,2 @@ +de: + :hello: "Hallo welt" diff --git a/spec/react_on_rails/fixtures/i18n/locales_symbols/en.yml b/spec/react_on_rails/fixtures/i18n/locales_symbols/en.yml new file mode 100644 index 000000000..ecd24dd18 --- /dev/null +++ b/spec/react_on_rails/fixtures/i18n/locales_symbols/en.yml @@ -0,0 +1,8 @@ +en: + :hello: "Hello world" + :argument: "I am %{age} years old." + :day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] + :blank: + :number: 2 + :bool: true + :float: 2.0 diff --git a/spec/react_on_rails/locales_to_js_spec.rb b/spec/react_on_rails/locales_to_js_spec.rb index e33e3d2b5..64f7f1520 100644 --- a/spec/react_on_rails/locales_to_js_spec.rb +++ b/spec/react_on_rails/locales_to_js_spec.rb @@ -96,5 +96,39 @@ module ReactOnRails it_behaves_like "locale to js" end + + describe "with symbols in yaml" do + let(:locale_dir) { File.expand_path("fixtures/i18n/locales_symbols", __dir__) } + + before do + ReactOnRails.configure do |config| + config.i18n_dir = i18n_dir + config.i18n_yml_dir = locale_dir + end + end + + after do + ReactOnRails.configure do |config| + config.i18n_dir = nil + config.i18n_yml_dir = nil + config.i18n_yml_safe_load_options = nil + end + end + + it "handles unsafe locale loading" do + ReactOnRails.configure do |config| + config.i18n_yml_safe_load_options = { permitted_classes: [Symbol] } + end + + expect { described_class.new }.not_to raise_error + end + + it "raises error with filename when not permitted" do + expect { described_class.new }.to raise_error(ReactOnRails::Error, <<~MSG) + Error parsing #{locale_dir}/de.yml: Tried to load unspecified class: Symbol + Consider fixing unsafe YAML or permitting with config.i18n_yml_safe_load_options + MSG + end + end end end