diff --git a/framework/src/main/java/org/tron/core/services/http/JsonFormat.java b/framework/src/main/java/org/tron/core/services/http/JsonFormat.java index e6ccb4e4d1..f99339c5d8 100644 --- a/framework/src/main/java/org/tron/core/services/http/JsonFormat.java +++ b/framework/src/main/java/org/tron/core/services/http/JsonFormat.java @@ -43,8 +43,6 @@ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT import java.io.IOException; import java.math.BigInteger; import java.nio.CharBuffer; -import java.text.CharacterIterator; -import java.text.StringCharacterIterator; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -58,7 +56,6 @@ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT import org.tron.common.utils.Commons; import org.tron.common.utils.StringUtil; import org.tron.core.Constant; -import org.tron.json.JSON; import org.tron.protos.contract.BalanceContract; /** @@ -82,6 +79,12 @@ public class JsonFormat { = "Writing to a StringBuilder threw an IOException (should never happen)."; private static final String EXPECTED_STRING = "Expected string."; private static final String MISSING_END_QUOTE = "String missing ending quote."; + /** + * What an unpaired surrogate is rendered as. Matches the substitution Java and protobuf apply + * when encoding such a string to UTF-8, so JSON output is the same whether or not the message + * has already been through a protobuf round trip. + */ + private static final char UNPAIRED_SURROGATE_REPLACEMENT = '?'; public static final boolean ALWAYS_OUTPUT_DEFAULT_VALUE_FIELDS = true; public static final Set> MESSAGES = ImmutableSet.of( @@ -866,14 +869,16 @@ static String escapeBytesSelfType(ByteString input, final String fliedName) { } //Normal String if (HttpSelfFormatFieldName.isNameStringFormat(fliedName)) { - String result = new String(input.toByteArray()); - result = result.replaceAll("\"", "\\\\\""); - try { - JSON.parseObject("{\"key\":\"" + result + "\"}"); - return result; - } catch (Exception e) { + // Bytes that are not valid UTF-8 fall back to hex. This keeps the hex representation + // faithful (instead of lossy U+FFFD mojibake produced by a default-charset decode) and + // guarantees escapeText() never sees an unpaired surrogate. Note that hex is not byte + // round-trippable through the inbound unescapeBytesSelfType(copyFromUtf8) path. + if (!input.isValidUtf8()) { return ByteArray.toHexString(input.toByteArray()); } + // Delegate to the same JSON string escaping used for proto string fields, so backslash, + // double quote and every control char 0x00-0x1F are escaped properly. + return escapeText(input.toStringUtf8()); } //HEX return ByteArray.toHexString(input.toByteArray()); @@ -905,7 +910,8 @@ static ByteString unescapeBytes(CharSequence input) throws InvalidEscapeSequence // them. /** - * Implements JSON string escaping as specified here. + * Implements JSON string escaping as specified in + * RFC 8259 (which obsoletes RFC 4627). *