diff --git a/CHANGELOG.md b/CHANGELOG.md index 68d4473f..111dc799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,20 @@ Changelog ========= +## v6.26.4 (25 March 2024) + +### Fixes + +* Fix Unicode encoding issues when using `Exception#detailed_message` (Ruby 3.2+) + | [#817](https://github.com/bugsnag/bugsnag-ruby/pull/817) +* Fix compatibility with Ruby 3.4-dev + | [#815](https://github.com/bugsnag/bugsnag-ruby/pull/815) + | [k0kubun](https://github.com/k0kubun) + ## v6.26.3 (24 January 2024) +### Fixes + * Handle mailto links in `Cleaner#clean_url` | [#813](https://github.com/bugsnag/bugsnag-ruby/pull/813) diff --git a/VERSION b/VERSION index 7eeb87d9..30bfaeb1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.26.3 +6.26.4 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/lib/bugsnag/stacktrace.rb b/lib/bugsnag/stacktrace.rb index fd932a04..4d24d95f 100644 --- a/lib/bugsnag/stacktrace.rb +++ b/lib/bugsnag/stacktrace.rb @@ -3,7 +3,7 @@ module Bugsnag module Stacktrace # e.g. "org/jruby/RubyKernel.java:1264:in `catch'" - BACKTRACE_LINE_REGEX = /^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$/ + BACKTRACE_LINE_REGEX = /^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in [`']([^']+)')?$/ # e.g. "org.jruby.Ruby.runScript(Ruby.java:807)" JAVA_BACKTRACE_REGEX = /^(.*)\((.*)(?::([0-9]+))?\)$/ 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