fix: avoid StackOverflowError when parsing upload payload
This commit is contained in:
@@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
alwaysApply: true
|
|
||||||
---
|
|
||||||
|
|
||||||
Please execute the generate commit message command in Chinese.
|
|
||||||
生成 Git 提交信息(Commit Message)时,必须强制使用中文。
|
|
||||||
@@ -51,7 +51,6 @@ public class RedisClipSync {
|
|||||||
// 公共上传通道
|
// 公共上传通道
|
||||||
private static final String UPLOAD_CHANNEL = "global_clip_upload";
|
private static final String UPLOAD_CHANNEL = "global_clip_upload";
|
||||||
private static final String UNKNOWN_SLAVE_ID = "unknown";
|
private static final String UNKNOWN_SLAVE_ID = "unknown";
|
||||||
private static final Pattern UPLOAD_PAYLOAD_PATTERN = Pattern.compile("\\{\"slaveId\":\"((?:\\\\.|[^\"])*)\",\"content\":\"((?:\\\\.|[^\"])*)\",\"timestamp\":(\\d+)\\}");
|
|
||||||
private static final String MASTER_TO_SLAVE_LOG_FILE = "master_to_slave.log";
|
private static final String MASTER_TO_SLAVE_LOG_FILE = "master_to_slave.log";
|
||||||
private static final String SLAVE_TO_MASTER_LOG_FILE = "slave_to_master.log";
|
private static final String SLAVE_TO_MASTER_LOG_FILE = "slave_to_master.log";
|
||||||
private static final String LOG_ENTRY_SEPARATOR = "\n\n------------------\n\n";
|
private static final String LOG_ENTRY_SEPARATOR = "\n\n------------------\n\n";
|
||||||
@@ -551,14 +550,112 @@ public class RedisClipSync {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Matcher matcher = UPLOAD_PAYLOAD_PATTERN.matcher(message);
|
// Avoid regex here: large clipboard payloads can trigger deep backtracking and StackOverflowError.
|
||||||
if (!matcher.matches()) {
|
// We only need to parse a fixed JSON shape produced by buildUploadPayload().
|
||||||
|
try {
|
||||||
|
String trimmed = message.trim();
|
||||||
|
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String slaveIdRaw = extractJsonStringField(trimmed, "slaveId");
|
||||||
|
String contentRaw = extractJsonStringField(trimmed, "content");
|
||||||
|
// timestamp is optional for parsing; we just validate it exists and is numeric when present
|
||||||
|
if (!hasJsonNumberField(trimmed, "timestamp")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String slaveId = unescapeJson(slaveIdRaw);
|
||||||
|
String content = unescapeJson(contentRaw);
|
||||||
|
return new UploadMessage(slaveId, content);
|
||||||
|
} catch (Exception ignored) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String slaveId = unescapeJson(matcher.group(1));
|
static String extractJsonStringField(String json, String fieldName) {
|
||||||
String content = unescapeJson(matcher.group(2));
|
int namePos = indexOfJsonFieldName(json, fieldName);
|
||||||
return new UploadMessage(slaveId, content);
|
if (namePos < 0) {
|
||||||
|
throw new IllegalArgumentException("missing field: " + fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
int colon = json.indexOf(':', namePos);
|
||||||
|
if (colon < 0) {
|
||||||
|
throw new IllegalArgumentException("bad json: missing colon for " + fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = colon + 1;
|
||||||
|
while (i < json.length() && Character.isWhitespace(json.charAt(i))) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i >= json.length() || json.charAt(i) != '"') {
|
||||||
|
throw new IllegalArgumentException("bad json: expected string for " + fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseJsonStringBody(json, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean hasJsonNumberField(String json, String fieldName) {
|
||||||
|
int namePos = indexOfJsonFieldName(json, fieldName);
|
||||||
|
if (namePos < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int colon = json.indexOf(':', namePos);
|
||||||
|
if (colon < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = colon + 1;
|
||||||
|
while (i < json.length() && Character.isWhitespace(json.charAt(i))) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
int start = i;
|
||||||
|
while (i < json.length()) {
|
||||||
|
char c = json.charAt(i);
|
||||||
|
if (c < '0' || c > '9') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return i > start;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int indexOfJsonFieldName(String json, String fieldName) {
|
||||||
|
// Find: "fieldName" (as a JSON string token)
|
||||||
|
String needle = "\"" + fieldName + "\"";
|
||||||
|
return json.indexOf(needle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse JSON string starting at the opening quote and return the raw (escaped) body.
|
||||||
|
* Example input: "a\\n\"b" -> returns: a\\n\"b (without surrounding quotes)
|
||||||
|
*/
|
||||||
|
static String parseJsonStringBody(String json, int openingQuoteIndex) {
|
||||||
|
if (openingQuoteIndex < 0 || openingQuoteIndex >= json.length() || json.charAt(openingQuoteIndex) != '"') {
|
||||||
|
throw new IllegalArgumentException("bad json: expected opening quote");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder rawEscaped = new StringBuilder();
|
||||||
|
int i = openingQuoteIndex + 1;
|
||||||
|
while (i < json.length()) {
|
||||||
|
char c = json.charAt(i);
|
||||||
|
if (c == '"') {
|
||||||
|
return rawEscaped.toString();
|
||||||
|
}
|
||||||
|
if (c == '\\') {
|
||||||
|
if (i + 1 >= json.length()) {
|
||||||
|
throw new IllegalArgumentException("bad json: trailing backslash");
|
||||||
|
}
|
||||||
|
rawEscaped.append('\\').append(json.charAt(i + 1));
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rawEscaped.append(c);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("bad json: unterminated string");
|
||||||
}
|
}
|
||||||
|
|
||||||
static String escapeJson(String value) {
|
static String escapeJson(String value) {
|
||||||
|
|||||||
Reference in New Issue
Block a user