Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.stereotype.Component;
import org.tron.common.utils.ByteArray;
import org.tron.core.Wallet;
import org.tron.core.exception.InvalidHttpParameterException;
import org.tron.core.services.http.HttpApi;
import org.tron.core.services.http.HttpApi.Access;
import org.tron.core.services.http.HttpApi.Surface;
Expand Down Expand Up @@ -78,6 +79,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
}
String name = jsonObject.getString("name");
if (!Strings.isNullOrEmpty(name)) {
if (JsonFormat.hasUnpairedSurrogate(name)) {
throw new InvalidHttpParameterException("Invalid name: contains an unpaired surrogate");
}
smartBuilder.setName(name);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import com.google.common.base.Utf8;
import com.google.common.collect.ImmutableSet;
import com.google.protobuf.ByteString;
import com.google.protobuf.Descriptors;
Expand Down Expand Up @@ -767,7 +768,7 @@ private static Object handlePrimitive(Tokenizer tokenizer, FieldDescriptor field
break;

case STRING:
value = tokenizer.consumeString();
value = consumeUtf8String(tokenizer, field);
break;

case BYTES:
Expand Down Expand Up @@ -1079,6 +1080,28 @@ private static String replaceMalformedSurrogates(String value) {
return new String(value.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
}

/**
* Returns whether the value cannot be encoded as UTF-8, which a protobuf string field requires.
*/
public static boolean hasUnpairedSurrogate(String value) {
try {
Utf8.encodedLength(value);
return false;
} catch (IllegalArgumentException e) {
return true;
}
}

private static String consumeUtf8String(Tokenizer tokenizer, FieldDescriptor field)
throws ParseException {
String value = tokenizer.consumeString();
if (hasUnpairedSurrogate(value)) {
throw tokenizer.parseExceptionPreviousToken(
"String field " + field.getFullName() + " contains an unpaired surrogate");
}
return value;
}

/**
* Is this an octal digit.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,9 @@ private TransactionJson buildCreateSmartContractTransaction(byte[] ownerAddress,
ByteString.copyFrom(ByteArray.fromHexString(args.resolveData())));

if (StringUtils.isNotEmpty(args.getName())) {
if (JsonFormat.hasUnpairedSurrogate(args.getName())) {
throw new JsonRpcInvalidParamsException("invalid name");
}
smartBuilder.setName(args.getName());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2011,6 +2011,30 @@ public void testBuildTransactionRejectsDeeplyNestedAbi() {
Assert.assertEquals("invalid abi", e.getMessage());
}

@Test
public void testBuildTransactionRejectsUnpairedSurrogateName() {
BuildArguments args = new BuildArguments();
args.setFrom("0xabd4b9367799eaa3197fecb144eb71de1e049abc");
args.setData("60806040");
args.setName("a" + (char) 0xD800);

JsonRpcInvalidParamsException e = Assert.assertThrows(JsonRpcInvalidParamsException.class,
() -> tronJsonRpc.buildTransaction(args));
Assert.assertEquals("invalid name", e.getMessage());
}

@Test
public void testBuildTransactionRejectsUnpairedSurrogateInAbi() {
BuildArguments args = new BuildArguments();
args.setFrom("0xabd4b9367799eaa3197fecb144eb71de1e049abc");
args.setData("60806040");
args.setAbi("[{\"name\":\"\\ud800\"}]");

JsonRpcInvalidParamsException e = Assert.assertThrows(JsonRpcInvalidParamsException.class,
() -> tronJsonRpc.buildTransaction(args));
Assert.assertEquals("invalid abi", e.getMessage());
}

@Test
public void testWeb3ClientVersion() {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.tron.core.services.http.servlets;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

Expand All @@ -19,10 +20,60 @@
import org.tron.core.services.ratelimiter.RateLimiterContainer;
import org.tron.json.JSONObject;
import org.tron.protos.Protocol.Account;
import org.tron.protos.contract.SmartContractOuterClass.SmartContract;

public class JsonFormatUnicodeErrorTest {

private static final String EMOJI = new String(Character.toChars(0x1F600));
private static final String NAME_SURROGATE_ERROR =
"1:9: String field protocol.SmartContract.name contains an unpaired surrogate";

@Test
public void testUnpairedSurrogateDetection() {
assertTrue(JsonFormat.hasUnpairedSurrogate(String.valueOf((char) 0xD800)));
assertTrue(JsonFormat.hasUnpairedSurrogate(String.valueOf((char) 0xDC00)));
assertTrue(JsonFormat.hasUnpairedSurrogate("" + (char) 0xDC00 + (char) 0xD800));
assertTrue(JsonFormat.hasUnpairedSurrogate("a" + (char) 0xD83D));
assertFalse(JsonFormat.hasUnpairedSurrogate(""));
assertFalse(JsonFormat.hasUnpairedSurrogate(EMOJI));
assertFalse(JsonFormat.hasUnpairedSurrogate("" + (char) 0x4E2D + (char) 0xFFFF));
}

@Test
public void testStringFieldRejectsUnpairedSurrogates() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NIT] Please add a regression test for Util.packTransaction with an unpaired surrogate in a contract parameter, asserting the documented dropped-contract behavior. A Permission.permission_name case is optional, since it uses the same STRING path already covered here.

assertNameRejected("\\ud800");
assertNameRejected("\\udc00");
assertNameRejected("a\\udc00\\ud800");
assertNameRejected("x\\uD83D");
assertNameRejected(String.valueOf((char) 0xD800));
}

@Test
public void testStringFieldKeepsWellFormedText() throws Exception {
SmartContract.Builder builder = SmartContract.newBuilder();
JsonFormat.merge("{\"name\":\"\\ud83d\\ude00\\u4e2d\\uffff" + EMOJI + "\"}", builder, false);

assertEquals(EMOJI + (char) 0x4E2D + (char) 0xFFFF + EMOJI, builder.getName());
}

@Test
public void testDeployContractRejectsUnpairedSurrogateInAbi() throws Exception {
String body = "{\"owner_address\":\"\",\"abi\":\"[{\\\"name\\\":\\\"\\\\ud800\\\"}]\"}";

String error = requestError(new DeployContractServlet(), "/wallet/deploycontract", body);

assertEquals("1:20: String field protocol.SmartContract.ABI.Entry.name"
+ " contains an unpaired surrogate", error);
}

@Test
public void testDeployContractRejectsUnpairedSurrogateInName() throws Exception {
String body = "{\"owner_address\":\"\",\"bytecode\":\"00\",\"name\":\"\\ud800\"}";

String error = requestError(new DeployContractServlet(), "/wallet/deploycontract", body);

assertEquals("Invalid name: contains an unpaired surrogate", error);
}

@Test
public void testInvalidSupplementaryEscapeKeepsLegacyReplacement() {
Expand Down Expand Up @@ -129,6 +180,11 @@ public void testOrdinaryIntegerErrorsKeepTheirMessages() {

private static String requestError(RateLimiterServlet servlet, String path, JSONObject input)
throws Exception {
return requestError(servlet, path, input.toJSONString());
}

private static String requestError(RateLimiterServlet servlet, String path, String requestBody)
throws Exception {
CommonParameter args = Args.getInstance();
long originalMaxSize = args.getHttpMaxMessageSize();
boolean originalNonBlocking = args.isRateLimiterApiNonBlocking();
Expand All @@ -145,7 +201,7 @@ private static String requestError(RateLimiterServlet servlet, String path, JSON
args.setHttpMaxMessageSize(1_000_000L);
args.setRateLimiterApiNonBlocking(false);
server.start();
byte[] body = input.toJSONString().getBytes(StandardCharsets.UTF_8);
byte[] body = requestBody.getBytes(StandardCharsets.UTF_8);
byte[] headers = ("POST " + path + " HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: close\r\n"
Expand Down Expand Up @@ -177,6 +233,13 @@ private static String requestError(RateLimiterServlet servlet, String path, JSON
}
}

private static void assertNameRejected(String text) {
JsonFormat.ParseException error = assertThrows(JsonFormat.ParseException.class,
() -> JsonFormat.merge("{\"name\":\"" + text + "\"}", SmartContract.newBuilder(), false));

assertEquals(NAME_SURROGATE_ERROR, error.getMessage());
}

private static void assertEscapeError(String input, String expectedCharacter) {
JsonFormat.InvalidEscapeSequence error = assertThrows(JsonFormat.InvalidEscapeSequence.class,
() -> JsonFormat.unescapeText(input));
Expand Down
Loading