tju-yxq opened a new issue, #1424: URL: https://github.com/apache/rocketmq-dashboard/issues/1424
## Bug Report ### Before Creating the Bug Report - [x] I found a bug, not just asking a question, which should be created in [GitHub Discussions](https://github.com/apache/rocketmq/discussions). - [x] I have searched the [GitHub Issues](https://github.com/apache/rocketmq/issues) and [GitHub Discussions](https://github.com/apache/rocketmq/discussions) of this repository and believe that this is not a duplicate. - [x] I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ. ### Runtime platform environment OS: Ubuntu 20.04 / Any OS running RocketMQ Studio ### RocketMQ version branch: rocketmq-studio version: 5.3.2+ Git commit id: f727341 ### JDK Version OpenJDK 21 ### Describe the Bug `RocketMQMessageProvider.displayBody()` truncates the message body at `MAX_BODY_DISPLAY_BYTES` (64 KB) before attempting UTF-8 decoding. If the truncation point falls in the middle of a multi-byte UTF-8 character (e.g., a 3-byte CJK character or a 4-byte emoji), the decoder throws `CharacterCodingException` because `CodingErrorAction.REPORT` is set for malformed input. The body is then displayed as BASE64-encoded binary instead of readable text, even though the original message is valid UTF-8. ```java int textLength = Math.min(body.length, MAX_BODY_DISPLAY_BYTES); try { String value = StandardCharsets.UTF_8.newDecoder() .onMalformedInput(CodingErrorAction.REPORT) // ← throws on partial multi-byte char .onUnmappableCharacter(CodingErrorAction.REPORT) .decode(ByteBuffer.wrap(body, 0, textLength)) // ← truncation may split a char .toString(); return new DisplayBody(value, "UTF-8", body.length > textLength); } catch (CharacterCodingException ignored) { // Falls through to BASE64 encoding even for valid UTF-8 messages int binaryLength = Math.min(body.length, MAX_BINARY_BODY_DISPLAY_BYTES); return new DisplayBody(Base64.getEncoder().encodeToString( java.util.Arrays.copyOf(body, binaryLength)), "BASE64", body.length > binaryLength); } ``` ### Impact This affects any message body that: 1. Is larger than 64 KB (valid UTF-8 text), AND 2. Contains multi-byte characters (CJK, emoji, accented characters, etc.) near the 64 KB boundary The message will appear as garbled BASE64 text in the Studio UI instead of readable content. Users may incorrectly conclude the message is binary/corrupted. ### Steps to Reproduce 1. Produce a message with a UTF-8 text body larger than 64 KB that contains multi-byte characters (e.g., Chinese text) near the 64 KB boundary. 2. Query the message in RocketMQ Studio. 3. Observe: the body is displayed as BASE64-encoded text instead of readable UTF-8. 4. Verify: the same message body, if under 64 KB, displays correctly as UTF-8 text. ### What Did You Expect to See? The truncated body should be displayed as readable UTF-8 text, with a truncation indicator, as long as the original body is valid UTF-8. ### What Did You See Instead? The body is displayed as BASE64-encoded binary because the truncation split a multi-byte character. ### Additional Context **Affected file**: `server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMessageProvider.java`, method `displayBody()` at approximately line 410. **Fix approach**: After truncating, walk back the truncation point to the last complete UTF-8 character boundary before decoding. A UTF-8 character boundary can be detected by scanning backwards from the truncation point until a byte that does NOT match the continuation pattern `10xxxxxx` (i.e., `(byte & 0xC0) != 0x80`) is found. Then decode from 0 to that adjusted length: ```java private DisplayBody displayBody(byte[] body) { if (body == null) { return new DisplayBody(null, null, false); } int textLength = Math.min(body.length, MAX_BODY_DISPLAY_BYTES); // Walk back to the last complete UTF-8 character boundary if (textLength < body.length) { while (textLength > 0 && (body[textLength] & 0xC0) == 0x80) { textLength--; } // Also skip the leading byte of the truncated character if (textLength > 0 && (body[textLength] & 0xC0) == 0xC0) { textLength--; } } try { String value = StandardCharsets.UTF_8.newDecoder() .onMalformedInput(CodingErrorAction.REPORT) .onUnmappableCharacter(CodingErrorAction.REPORT) .decode(ByteBuffer.wrap(body, 0, textLength)) .toString(); return new DisplayBody(value, "UTF-8", body.length > textLength); } catch (CharacterCodingException ignored) { int binaryLength = Math.min(body.length, MAX_BINARY_BODY_DISPLAY_BYTES); return new DisplayBody(Base64.getEncoder().encodeToString( java.util.Arrays.copyOf(body, binaryLength)), "BASE64", body.length > binaryLength); } } ``` This adds approximately 8 lines (the boundary adjustment logic) without removing any existing code. The BASE64 fallback is preserved for truly binary content. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
