제어문자 섞인 전문 대응: 수신은 정규화, 송신은 정제

수신측 - JacksonUtil.escapeControlChars() 적용 범위 확대
  JsonReader 에만 걸려 있어 TemplateCodeConvertAdapterErrorMsgHandler 등
  직접 readTree() 하는 경로가 "Illegal unquoted character ((CTRL-CHAR, code 10))"
  로 실패했다. JacksonUtil.readTree() 공용 진입점과 payload 를 파싱하는
  핸들러/필터에 적용한다.

송신측 - MessageUtil.stripControlChars() 신규
  TemplateAdapterErrorMsgHandler.render() 가 Matcher.quoteReplacement() 로만
  치환하고 있었다. 이것은 정규식 치환용이라 JSON/XML 이스케이프가 아니어서,
  값에 개행이 있으면 깨진 JSON 을 만들어 응답으로 내보냈다. 실제로 위 파싱
  오류의 원문도 상대 전문이 아니라 이 렌더 결과였다.

  템플릿이 JSON/XML 어느 쪽인지 알 수 없어 포맷별 이스케이프가 불가능하므로
  제어문자를 걸러낸다. 탭/개행/CR 은 공백으로, 나머지 제어문자와 DEL 은 제거.
  render() 의 스칼라 치환과 renderRow() 의 행 필드 치환에만 적용해
  템플릿 서식(들여쓰기/개행)은 보존한다.

검증: gradlew compileJava BUILD SUCCESSFUL.
      databind 2.12.7 런타임에서 0x00~0x1F 전수 + 로그 재현 케이스 통과.
This commit is contained in:
curry772
2026-08-28 14:28:01 +09:00
parent f1b67df867
commit 6dee34e085
9 changed files with 76 additions and 10 deletions
@@ -191,7 +191,9 @@ public final class JacksonUtil {
return null;
}
return objectMapper.readTree(jsonStr);
// 상대 시스템이 제어문자를 이스케이프하지 않고 보내는 경우가 있어 정규화 후 파싱한다.
// 이미 표준을 지킨 JSON 이면 원본을 그대로 반환하므로 사실상 무해하다.
return objectMapper.readTree(escapeControlChars(jsonStr));
}
public static JsonNode readTree(Object jsonData) throws JsonMappingException, JsonProcessingException {