From 27beed6a16c7b188f3c2db5498d0bc8542a7e32b Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 11 Mar 2024 12:45:48 +0000 Subject: [PATCH] Fix Unicode encoding issues with detailed_message --- CHANGELOG.md | 5 +++ lib/bugsnag/report.rb | 9 ++++- spec/report_spec.rb | 92 ++++++++++++++++++++++++++++++++++++------- 3 files changed, 90 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68d4473f..746122f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Changelog ========= +## TBD + +* Fix Unicode encoding issues when using `Exception#detailed_message` (Ruby 3.2+) + | [#817](https://github.com/bugsnag/bugsnag-ruby/pull/817) + ## v6.26.3 (24 January 2024) * Handle mailto links in `Cleaner#clean_url` diff --git a/lib/bugsnag/report.rb b/lib/bugsnag/report.rb index c2958d39..92d1b56c 100644 --- a/lib/bugsnag/report.rb +++ b/lib/bugsnag/report.rb @@ -466,8 +466,15 @@ def error_message(exception, class_name) exception.detailed_message end + # the string returned by 'detailed_message' defaults to 'ASCII_8BIT' but + # is actually UTF-8 encoded; we can't convert the encoding normally as its + # internal encoding doesn't match its actual encoding + message.force_encoding(::Encoding::UTF_8) if message.encoding == ::Encoding::ASCII_8BIT + # remove the class name to be consistent with Exception#message - message.sub(" (#{class_name})", '') + message.sub!(" (#{class_name})".encode(message.encoding), "") rescue nil + + message end def generate_raw_exceptions(exception) diff --git a/spec/report_spec.rb b/spec/report_spec.rb index 3756a741..a6c3bd5a 100644 --- a/spec/report_spec.rb +++ b/spec/report_spec.rb @@ -40,6 +40,17 @@ def detailed_message end end +class ExceptionWithDetailedMessageReturningEncodedString < Exception + def initialize(message, encoding) + super(message) + @encoding = encoding + end + + def detailed_message + "abc #{self} xyz".encode(@encoding) + end +end + shared_examples "Report or Event tests" do |class_to_test| context "metadata" do include_examples( @@ -1465,24 +1476,75 @@ def detailed_message } end - it "uses Exception#detailed_message if available" do - Bugsnag.notify(ExceptionWithDetailedMessage.new("some message")) + context "#detailed_message" do + it "uses Exception#detailed_message if available" do + Bugsnag.notify(ExceptionWithDetailedMessage.new("some message")) - expect(Bugsnag).to have_sent_notification{ |payload, headers| - exception = get_exception_from_payload(payload) - expect(exception["errorClass"]).to eq("ExceptionWithDetailedMessage") - expect(exception["message"]).to eq("some message with some extra detail") - } - end + expect(Bugsnag).to have_sent_notification{ |payload, headers| + exception = get_exception_from_payload(payload) + expect(exception["errorClass"]).to eq("ExceptionWithDetailedMessage") + expect(exception["message"]).to eq("some message with some extra detail") + } + end - it "handles implementations of Exception#detailed_message with no 'highlight' parameter" do - Bugsnag.notify(ExceptionWithDetailedMessageButNoHighlight.new("some message")) + it "handles implementations of Exception#detailed_message with no 'highlight' parameter" do + Bugsnag.notify(ExceptionWithDetailedMessageButNoHighlight.new("some message")) - expect(Bugsnag).to have_sent_notification{ |payload, headers| - exception = get_exception_from_payload(payload) - expect(exception["errorClass"]).to eq("ExceptionWithDetailedMessageButNoHighlight") - expect(exception["message"]).to eq("detail about 'some message'") - } + expect(Bugsnag).to have_sent_notification{ |payload, headers| + exception = get_exception_from_payload(payload) + expect(exception["errorClass"]).to eq("ExceptionWithDetailedMessageButNoHighlight") + expect(exception["message"]).to eq("detail about 'some message'") + } + end + + it "converts ASCII_8BIT encoding to UTF-8" do + Bugsnag.notify(Exception.new("大好き\n大好き")) + + expect(Bugsnag).to have_sent_notification{ |payload, headers| + exception = get_exception_from_payload(payload) + expect(exception["message"]).to eq("大好き\n大好き") + } + end + + it "leaves UTF-8 strings as-is" do + exception = ExceptionWithDetailedMessageButNoHighlight.new("Обичам те\n大好き") + expect(exception.detailed_message.encoding).to be(Encoding::UTF_8) + + Bugsnag.notify(exception) + + expect(Bugsnag).to have_sent_notification{ |payload, headers| + exception = get_exception_from_payload(payload) + expect(exception["message"]).to eq("detail about 'Обичам те\n大好き'") + } + end + + it "handles UTF-16 strings" do + exception = ExceptionWithDetailedMessageReturningEncodedString.new("Обичам те\n大好き", Encoding::UTF_16) + expect(exception.detailed_message.encoding).to be(Encoding::UTF_16) + + Bugsnag.notify(exception) + + expect(Bugsnag).to have_sent_notification{ |payload, headers| + exception = get_exception_from_payload(payload) + + # the exception message is converted to UTF-8 by the Cleaner + expect(exception["message"]).to eq("abc Обичам те\n大好き xyz") + } + end + + it "handles Shift JIS strings" do + exception = ExceptionWithDetailedMessageReturningEncodedString.new("大好き\n大好き", Encoding::Shift_JIS) + expect(exception.detailed_message.encoding).to be(Encoding::Shift_JIS) + + Bugsnag.notify(exception) + + expect(Bugsnag).to have_sent_notification{ |payload, headers| + exception = get_exception_from_payload(payload) + + # the exception message is converted to UTF-8 by the Cleaner + expect(exception["message"]).to eq("abc 大好き\n大好き xyz") + } + end end it "supports unix-style paths in backtraces" do