Skip to content

fix(http): correct JsonFormat string escaping and decoding#6884

Closed
waynercheung wants to merge 1 commit into
tronprotocol:developfrom
waynercheung:fix/jsonformat-name-string-escape
Closed

fix(http): correct JsonFormat string escaping and decoding#6884
waynercheung wants to merge 1 commit into
tronprotocol:developfrom
waynercheung:fix/jsonformat-name-string-escape

Conversation

@waynercheung

@waynercheung waynercheung commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

escapeBytesSelfType escaped only the double quote, so backslashes and control characters 0x00-0x1F passed through raw. Reuse escapeText for these fields, falling back to hex for malformed UTF-8, and decode escapes symmetrically on input so values survive print -> merge.

Several pre-existing defects in the same file are fixed alongside:

  • JsonGenerator.print duplicated the tail on every newline; write() now takes only the data it writes, so its length cannot disagree with the range written.
  • escapeText truncated values at U+FFFF, because CharacterIterator.DONE is that code point.
  • escapeText threw IllegalArgumentException on unpaired surrogates, which now render as ? to match protobuf's own UTF-8 substitution.
  • unescapeText rejected the RFC 8259 escaped solidus \/.
  • unescapeText accepted signed escapes such as \u+123, because Integer.parseInt allows a leading sign.

Why are these changes required?

The old output was JSON that a strict RFC 8259 parser rejects, and it was not reproduced faithfully by the lenient re-parse in Util.printTransactionToJSON. Because output escaped " while input never unescaped, values containing a quote never survived create -> sign -> broadcast; that is why both halves must land together.

This PR has been tested by:

  • Unit Tests - JsonFormatEscapeTest, 40 cases: control chars, backslash, quote, U+FFFF, supplementary plane, malformed UTF-8, unpaired surrogates, print -> merge byte round trip, inbound escape set, and a fixed-seed property sweep over all 1-byte and all 65536 2-byte inputs. Reverting JsonFormat.java to develop at e89c0d6652 fails 28 of the 40, so the suite is not vacuous.
  • Regression - the org.tron.core.services.http and org.tron.core.services.jsonrpc packages, checkstyleMain, checkstyleTest, git diff --check: all clean.
  • Manual Testing - differential runs of the same inputs against develop and against protobuf-java-util 3.25.8, the version this repo depends on.

Consensus impact: none. No consensus validation, protobuf schema, txID algorithm or historical transaction replay is touched. All changes are in the HTTP JSON conversion layer.

Follow up

Not included, each a separate compatibility decision: HTML-safe escaping of < > & = ' and of U+2028/U+2029 (protobuf-java-util 3.25.8 does both, but = and & are too common in URLs to change here), tightening the tokenizer's outer leniency (single quotes, unquoted field names, trailing commas, raw control chars), the legacy \' escape, and the bytes mapping, which stays TRON's hex / Base58Check / UTF-8 text rather than Base64.

Extra details

BREAKING CHANGE, request handling. The visible=false bytes/hex path is unchanged, but unescapeText is shared by every proto string field with no visible check, so \/ is now accepted and \u+123 rejected in both visible modes.

For visible=true name-string byte fields, valid JSON escapes are now decoded, while invalid escapes such as \d or a malformed \uXXXX are rejected instead of being stored literally. The rejection is a change in request acceptance, not only in the value stored.

raw JSON token develop builds this PR builds end-to-end result
"a\nb" 615c6e62 (a \ n b) 610a62 (a LF b) both complete, different bytes
"C:\\dir" 433a5c5c646972 (two backslashes) 433a5c646972 (one) both complete, different bytes
"a\"b" 615c2262 612262 develop fails at broadcast; this PR succeeds
"C:\dir" 433a5c646972 (stored literally) request rejected develop completes; this PR returns a parse error

The new value is what the client meant per JSON, so this is a correction. Note that the create endpoints do not themselves write to the chain: the same HTTP JSON request can construct different raw_data, which, if subsequently signed and broadcast, results in different contract bytes on chain. Downstream systems that deduplicate, compare or replay should be aware.

For affected escape-containing payloads, avoid crossing node versions within one visible=true create/sign/broadcast flow during a rolling upgrade, in either direction; payloads without escapes are unaffected.

@waynercheung waynercheung changed the title fix(http): correctly escape name-string byte fields fix(http): correct JsonFormat string escaping and decoding Jul 20, 2026
escapeBytesSelfType escaped only double quotes. Backslashes and raw
control characters could therefore produce invalid JSON, or alter the
object seen by the lenient re-parse in Util.printTransactionToJSON.

Use escapeText for valid UTF-8 and fall back to hex for malformed
UTF-8. Unescape visible name-string bytes on input so print and merge
are symmetric. Also fix newline-tail duplication in
JsonGenerator.print, U+FFFF truncation and malformed-surrogate
handling in escapeText, and support the escaped solidus while
rejecting signed Unicode escapes in unescapeText.

BREAKING CHANGE: for visible=true name-string byte fields, JSON escape
sequences are now decoded before conversion to bytes, so a request
sending "a\nb" stores one 0x0A byte instead of a backslash followed by
'n'. Such requests already complete today, they just store the
undecoded text, so upgrading changes the bytes written on chain for
clients that escape their payloads correctly. Malformed or unsupported
escapes are now rejected, while the legacy "\'" extension stays
accepted. The shared proto string parser also now accepts "\/" and
rejects malformed escapes such as "\u+123", in both visible modes; the
visible=false bytes/hex path itself is unchanged. During a rolling
upgrade, avoid crossing node versions within an affected visible=true
create/sign/broadcast flow, in either direction.
@waynercheung
waynercheung force-pushed the fix/jsonformat-name-string-escape branch from 30c457a to c34ee46 Compare July 20, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant