Make java UTF-8 decode more lenient (#26886)
* Start asking for replace in the java UTF logic
* Fix exception reporting: make sure exception is cleared, print out a nice stack trace
* Restyle
* Fix for compilation
* Fix signature for repolace action field
* Restyled by clang-format
---------
Co-authored-by: Andrei Litvin <andreilitvin@google.com>
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/src/lib/support/JniReferences.cpp b/src/lib/support/JniReferences.cpp
index 66c16eb..de61a98 100644
--- a/src/lib/support/JniReferences.cpp
+++ b/src/lib/support/JniReferences.cpp
@@ -402,6 +402,29 @@
jmethodID newDocoderMethod = env->GetMethodID(charSetClass, "newDecoder", "()Ljava/nio/charset/CharsetDecoder;");
jobject decoderObject = env->CallObjectMethod(charsetObject, newDocoderMethod);
+ // Even though spec requires UTF-8 strings, we have seen instances in the field of certified devices sending
+ // invalid strings like "startup?" (0x73 0x74 0x61 0x72 0x74 0x75 0x70 <0x91>) and we want to actually
+ // be lenient on those rather than failing an entire decode (which may fail an entire report for one invalid string,
+ // like in a very common 'subscribe *')
+ //
+ // As a result call:
+ // onMalformedInput(CodingErrorAction.REPLACE)
+ // onUnmappableCharacter(CodingErrorAction.REPLACE)
+ jclass codingErrorActionClass = env->FindClass("java/nio/charset/CodingErrorAction");
+ jobject replaceAction = env->GetStaticObjectField(
+ codingErrorActionClass, env->GetStaticFieldID(codingErrorActionClass, "REPLACE", "Ljava/nio/charset/CodingErrorAction;"));
+ {
+ jmethodID onMalformedInput = env->GetMethodID(charSetDocoderClass, "onMalformedInput",
+ "(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;");
+ decoderObject = env->CallObjectMethod(decoderObject, onMalformedInput, replaceAction);
+ }
+ {
+ jmethodID onUnmappableCharacter =
+ env->GetMethodID(charSetDocoderClass, "onUnmappableCharacter",
+ "(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;");
+ decoderObject = env->CallObjectMethod(decoderObject, onUnmappableCharacter, replaceAction);
+ }
+
jmethodID charSetDecodeMethod = env->GetMethodID(charSetDocoderClass, "decode", "(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;");
jobject decodeObject = env->CallObjectMethod(decoderObject, charSetDecodeMethod, jbyteBuffer);
env->DeleteLocalRef(jbyteBuffer);
@@ -409,7 +432,15 @@
// If decode exception occur, outStr will be set null.
outStr = nullptr;
- VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN);
+ if (env->ExceptionCheck())
+ {
+ // If there is an exception, decode will not fail. Instead just
+ // an error will be reported.
+ ChipLogError(Support, "Exception encountered trying to decode a UTF string.");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return CHIP_JNI_ERROR_EXCEPTION_THROWN;
+ }
jclass charBufferClass = env->FindClass("java/nio/CharBuffer");
jmethodID charBufferToString = env->GetMethodID(charBufferClass, "toString", "()Ljava/lang/String;");