Skip to content

Commit f77b94d

Browse files
halibobo1205test
authored andcommitted
feat(api): sanitize HTTP API error responses
Standard HTTP error paths used to expose internal details to clients: Util.processError prefixed every message with the Java exception class name, several servlets printed raw Throwable.getMessage() directly, and the two solidity query endpoints returned bare-text error bodies. Centralize the client-facing text decision in Util.processError: * keep the raw non-blank message only for the exact runtime types JsonFormat.ParseException, ContractValidateException and MaintenanceUnavailableException; a null, empty or whitespace-only message falls back to "internal server error" * preserve the events-deprecation message only for the exact IllegalArgumentException type carrying EVENTS_DEPRECATED_MSG * write the fixed rate-limit and INVALID address messages, along with existing GetBlock validation messages, through the package-private writeAuditedError helper; these audited callers bypass exception classification, and printErrorMsg is private to the shared writer * return {"Error":"internal server error"} for every other exception, with no exception class name Client-visible changes: * all processError-based error bodies lose the "class <FQCN> : " prefix; unclassified raw messages become "internal server error" * the rate-limit rejection body becomes {"Error":"lack of computing resources"} on every endpoint extending RateLimiterServlet, including full-node, solidity and PBFT /jsonrpc * gettransactionbyid / gettransactioninfobyid on solidity return standard {"Error":...} JSON instead of bare text * validateaddress, getBrokerage and getReward replace leaked library messages in their failure branches with existing fixed texts; the "INVALID address" body is now written via writeAuditedError and loses the space after the colon * getblock keeps its exact error bodies (refactor only) Cover Solidity transaction and transaction-info GET/POST input errors, backend failures, successful lookups and missing records directly with mocked Wallet calls and in-memory requests and responses. Replace the transaction servlet tests that accidentally exercised POST in both cases, changed global stdout and used a shared temporary response file. Verify both endpoint and global rate-limit rejections across the three JSON-RPC servlet variants, including status, response body and the absence of business dispatch on rejection. HTTP status codes, success responses, request validation rules and gRPC behavior are unchanged. JSON-RPC behavior is unchanged except for the shared HTTP rate-limit response described above. Closes #6936
1 parent 4a21592 commit f77b94d

17 files changed

Lines changed: 523 additions & 262 deletions

‎framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@ private void fillResponse(boolean visible, BlockReq request, HttpServletResponse
7777
response.getWriter().println("{}");
7878
}
7979
} catch (IllegalArgumentException e) {
80-
JSONObject jsonObject = new JSONObject();
81-
jsonObject.put("Error", e.getMessage());
82-
response.getWriter().println(jsonObject.toJSONString());
80+
Util.writeAuditedError(e.getMessage(), response);
8381
}
8482
}
8583

‎framework/src/main/java/org/tron/core/services/http/GetBrokerageServlet.java‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.tron.core.services.http;
22

3-
import java.io.IOException;
43
import javax.servlet.http.HttpServletRequest;
54
import javax.servlet.http.HttpServletResponse;
65
import lombok.extern.slf4j.Slf4j;
@@ -27,12 +26,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) {
2726
}
2827
response.getWriter().println("{\"brokerage\": " + value + "}");
2928
} catch (DecoderException | IllegalArgumentException e) {
30-
try {
31-
response.getWriter()
32-
.println("{\"Error\": " + "\"INVALID address, " + e.getMessage() + "\"}");
33-
} catch (IOException ioe) {
34-
logger.debug("IOException: {}", ioe.getMessage());
35-
}
29+
Util.writeAuditedError(Util.INVALID_ADDRESS_MSG, response);
3630
} catch (Exception e) {
3731
Util.processError(e, response);
3832
}

‎framework/src/main/java/org/tron/core/services/http/GetBurnTrxServlet.java‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.tron.core.services.http;
22

3-
import java.io.IOException;
43
import javax.servlet.http.HttpServletRequest;
54
import javax.servlet.http.HttpServletResponse;
65
import lombok.extern.slf4j.Slf4j;
@@ -24,12 +23,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) {
2423
: "{\"burnTrxAmount\": " + value + "}";
2524
response.getWriter().println(out);
2625
} catch (Exception e) {
27-
logger.error("", e);
28-
try {
29-
response.getWriter().println(Util.printErrorMsg(e));
30-
} catch (IOException ioe) {
31-
logger.debug("IOException: {}", ioe.getMessage());
32-
}
26+
Util.processError(e, response);
3327
}
3428
}
3529

‎framework/src/main/java/org/tron/core/services/http/GetNodeInfoServlet.java‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.tron.core.services.http;
22

3-
import java.io.IOException;
43
import javax.servlet.http.HttpServletRequest;
54
import javax.servlet.http.HttpServletResponse;
65
import lombok.extern.slf4j.Slf4j;
@@ -24,12 +23,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) {
2423
response.getWriter().println(JSON.toJSONString(nodeInfo));
2524

2625
} catch (Exception e) {
27-
logger.error("", e);
28-
try {
29-
response.getWriter().println(Util.printErrorMsg(e));
30-
} catch (IOException ioe) {
31-
logger.debug("IOException: {}", ioe.getMessage());
32-
}
26+
Util.processError(e, response);
3327
}
3428
}
3529

‎framework/src/main/java/org/tron/core/services/http/GetPendingSizeServlet.java‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.tron.core.services.http;
22

3-
import java.io.IOException;
43
import javax.servlet.http.HttpServletRequest;
54
import javax.servlet.http.HttpServletResponse;
65
import lombok.extern.slf4j.Slf4j;
@@ -24,12 +23,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) {
2423
: "{\"pendingSize\": " + value + "}";
2524
response.getWriter().println(out);
2625
} catch (Exception e) {
27-
logger.error("", e);
28-
try {
29-
response.getWriter().println(Util.printErrorMsg(e));
30-
} catch (IOException ioe) {
31-
logger.debug("IOException: {}", ioe.getMessage());
32-
}
26+
Util.processError(e, response);
3327
}
3428
}
3529

‎framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java‎

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.tron.core.services.http;
22

3-
import java.io.IOException;
43
import javax.servlet.http.HttpServletRequest;
54
import javax.servlet.http.HttpServletResponse;
65
import lombok.extern.slf4j.Slf4j;
@@ -29,19 +28,9 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) {
2928
: "{\"reward\": " + value + "}";
3029
response.getWriter().println(out);
3130
} catch (DecoderException | IllegalArgumentException e) {
32-
try {
33-
response.getWriter()
34-
.println("{\"Error\": " + "\"INVALID address, " + e.getMessage() + "\"}");
35-
} catch (IOException ioe) {
36-
logger.debug("IOException: {}", ioe.getMessage());
37-
}
31+
Util.writeAuditedError(Util.INVALID_ADDRESS_MSG, response);
3832
} catch (Exception e) {
39-
logger.error("", e);
40-
try {
41-
response.getWriter().println(Util.printErrorMsg(e));
42-
} catch (IOException ioe) {
43-
logger.debug("IOException: {}", ioe.getMessage());
44-
}
33+
Util.processError(e, response);
4534
}
4635
}
4736

‎framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByBlockNumServlet.java‎

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.tron.core.services.http;
22

3-
import java.io.IOException;
43
import java.util.List;
54
import javax.servlet.http.HttpServletRequest;
65
import javax.servlet.http.HttpServletResponse;
@@ -52,12 +51,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) {
5251
response.getWriter().println("{}");
5352
}
5453
} catch (Exception e) {
55-
logger.debug("Exception: {}", e.getMessage());
56-
try {
57-
response.getWriter().println(Util.printErrorMsg(e));
58-
} catch (IOException ioe) {
59-
logger.debug("IOException: {}", ioe.getMessage());
60-
}
54+
Util.processError(e, response);
6155
}
6256
}
6357

@@ -75,12 +69,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
7569
response.getWriter().println("{}");
7670
}
7771
} catch (Exception e) {
78-
logger.debug("Exception: {}", e.getMessage());
79-
try {
80-
response.getWriter().println(Util.printErrorMsg(e));
81-
} catch (IOException ioe) {
82-
logger.debug("IOException: {}", ioe.getMessage());
83-
}
72+
Util.processError(e, response);
8473
}
8574
}
8675
}

‎framework/src/main/java/org/tron/core/services/http/RateLimiterServlet.java‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,7 @@ protected void service(HttpServletRequest req, HttpServletResponse resp)
131131
super.service(req, resp);
132132
Metrics.histogramObserve(requestTimer);
133133
} else {
134-
resp.getWriter()
135-
.println(Util.printErrorMsg(new IllegalAccessException("lack of computing resources")));
134+
Util.writeAuditedError(Util.RATE_LIMITER_ERROR_MSG, resp);
136135
}
137136
} catch (ServletException | IOException | BadMessageException e) {
138137
throw e;

‎framework/src/main/java/org/tron/core/services/http/Util.java‎

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
import org.tron.core.capsule.TransactionCapsule;
4949
import org.tron.core.config.args.Args;
5050
import org.tron.core.db.TransactionTrace;
51+
import org.tron.core.exception.ContractValidateException;
52+
import org.tron.core.exception.MaintenanceUnavailableException;
5153
import org.tron.core.services.http.JsonFormat.ParseException;
5254
import org.tron.json.JSON;
5355
import org.tron.json.JSONArray;
@@ -65,6 +67,10 @@
6567
@Slf4j(topic = "API")
6668
public class Util {
6769

70+
private static final String INTERNAL_SERVER_ERROR = "internal server error";
71+
public static final String RATE_LIMITER_ERROR_MSG = "lack of computing resources";
72+
static final String INVALID_ADDRESS_MSG = "INVALID address";
73+
6874
public static final String EVENTS_DEPRECATED_MSG =
6975
"'events' field is deprecated and no longer supported";
7076

@@ -114,12 +120,31 @@ public static String printTransactionFee(String transactionFee) {
114120
return jsonObject.toJSONString();
115121
}
116122

117-
public static String printErrorMsg(Exception e) {
123+
private static String printErrorMsg(String msg) {
118124
JSONObject jsonObject = new JSONObject();
119-
jsonObject.put("Error", e.getClass() + " : " + e.getMessage());
125+
jsonObject.put("Error", msg);
120126
return jsonObject.toJSONString();
121127
}
122128

129+
private static String clientMessage(Exception e) {
130+
if (e == null) {
131+
return INTERNAL_SERVER_ERROR;
132+
}
133+
134+
Class<?> type = e.getClass();
135+
if (type == IllegalArgumentException.class) {
136+
return EVENTS_DEPRECATED_MSG.equals(e.getMessage())
137+
? EVENTS_DEPRECATED_MSG : INTERNAL_SERVER_ERROR;
138+
}
139+
if (type == ParseException.class
140+
|| type == ContractValidateException.class
141+
|| type == MaintenanceUnavailableException.class) {
142+
String message = e.getMessage();
143+
return StringUtils.isBlank(message) ? INTERNAL_SERVER_ERROR : message;
144+
}
145+
return INTERNAL_SERVER_ERROR;
146+
}
147+
123148
public static String printBlockList(BlockList list, boolean selfType) {
124149
List<Block> blocks = list.getBlockList();
125150
JSONObject jsonObject = new JSONObject();
@@ -526,11 +551,16 @@ public static String getMemo(byte[] memo) {
526551
}
527552

528553
public static void processError(Exception e, HttpServletResponse response) {
529-
logger.debug(e.getMessage(), e);
554+
logger.debug("HTTP request failed", e);
555+
writeAuditedError(clientMessage(e), response);
556+
}
557+
558+
// Bypasses clientMessage: callers must pass audited fixed or pre-existing client texts only.
559+
static void writeAuditedError(String msg, HttpServletResponse response) {
530560
try {
531-
response.getWriter().println(Util.printErrorMsg(e));
561+
response.getWriter().println(Util.printErrorMsg(msg));
532562
} catch (IOException ioe) {
533-
logger.debug("IOException: {}", ioe.getMessage());
563+
logger.debug("Failed to write HTTP error response", ioe);
534564
}
535565
}
536566

‎framework/src/main/java/org/tron/core/services/http/ValidateAddressServlet.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ private String validAddress(String input) {
4747
}
4848
} catch (Exception e) {
4949
result = false;
50-
msg = e.getMessage();
50+
msg = "Invalid address";
5151
}
5252

5353
JSONObject jsonAddress = new JSONObject();

0 commit comments

Comments
 (0)