fix(core,installer): 프로세스 출력 인코딩으로 인한 JSON 빈 응답 차단

한국어 Windows 에서 whoami 가 CP949 로 출력해 계정명에 한글이 있으면
invalid UTF-8 이 응답 배열에 실리고 json_encode 가 false 를 반환한다.
echo false 는 빈 문자열이라 HTTP 200 + 빈 본문이 나가는데 예외도 로그도
남지 않아, 설치 마법사 2단계가 진행 불가 상태로 멈춘다.

2026-07 수정(7.0.2)은 로그 축 4곳만 막았다. 출처에서 정규화하고(1차),
응답 경계를 단일 헬퍼로 닫고(2차), 실패 시 문제 필드 경로를 로그에
남기도록(3차) 세 층으로 처리해 다른 경로가 열린 채 남지 않게 했다.

정규화는 복원 가능한 코드페이지 출력을 먼저 되살리므로, 종전 mb_scrub
단독 처리와 달리 한글이 U+FFFD 로 훼손되지 않는다.

같은 원인으로 한국어 Windows 에서 관리자 환경설정의 시스템 정보가
500 이 되던 것도 함께 막았다.
This commit is contained in:
HeuJung committed 2026-08-29 15:13:14 +09:00
1 parent f094a5f3fc
commit 01d319e74b
37 files changed
+1662 -277

No files matched your search

+43 -5
View File
@@ -173,6 +173,33 @@ class AddLogUtf8ScrubTest extends TestCase
$this->assertNotSame('', $encoded, '인코딩 결과 본문이 비어 있으면 안 된다.');
}
/**
* 한국어 Windows 의 CP949 출력은 U+FFFD 로 훼손되지 않고 복원되어야 한다.
*
* 이전 구현은 mb_scrub 단독이라 한글이 물음표/대체문자로 뭉개졌다.
* 로그는 운영자가 원인을 찾는 유일한 단서이므로 읽을 수 있어야 한다 (gnuboard/g7#62).
*/
#[Test]
public function addlog_restores_cp949_korean_output(): void
{
addLog('warmup');
$expected = 'it-manager\\티아모라';
$raw = (string) mb_convert_encoding($expected, 'CP949', 'UTF-8');
$this->assertFalse(mb_check_encoding($raw, 'UTF-8'), '표본은 invalid UTF-8 이어야 한다.');
addLog($raw);
$logs = getInstallationLogs();
$message = $logs[count($logs) - 1]['message'] ?? '';
$this->assertSame(
$expected,
$message,
'CP949 한글 출력은 U+FFFD 훼손 없이 원문으로 복원되어야 한다.'
);
}
#[Test]
public function addlog_preserves_valid_utf8_korean_unchanged(): void
{
@@ -225,11 +252,15 @@ class AddLogUtf8ScrubTest extends TestCase
}
/**
* progress-emitter.php 의 SseEmitter::emit 이 JSON_INVALID_UTF8_SUBSTITUTE 를 적용하는지
* progress-emitter.php 의 SseEmitter::emit 이 빈 data 라인을 송출할 수 없는지
* 소스 레벨로 회귀 검증한다 (PollingResponseFlushTest 정적 검증과 동일 방식).
*
* 가드는 인라인 플래그에서 공용 헬퍼 installer_json_encode 로 옮겨졌다.
* 헬퍼는 값을 정규화하고 substitute 를 적용하며, 실패해도 파싱 가능한 JSON 을 돌려주므로
* 빈 data 라인이 나갈 수 있는 경로가 없다. 계약은 그대로이고 수단만 단일화됐다.
*/
#[Test]
public function sse_emitter_source_uses_invalid_utf8_substitute_flag(): void
public function sse_emitter_source_routes_through_guarded_encoder(): void
{
$source = (string) file_get_contents(
dirname(__DIR__, 3).'/public/install/includes/progress-emitter.php'
@@ -242,10 +273,17 @@ class AddLogUtf8ScrubTest extends TestCase
$sseBody = substr($source, $classStart, $classEnd !== false ? $classEnd - $classStart : null);
$this->assertStringContainsString(
'JSON_INVALID_UTF8_SUBSTITUTE',
'installer_json_encode(',
$sseBody,
'SseEmitter::emit 의 json_encode 는 JSON_INVALID_UTF8_SUBSTITUTE 를 적용해 '.
'invalid UTF-8 로그로 인한 빈 SSE data 라인 송출을 차단해야 한다.'
'SseEmitter::emit 은 가드된 공용 인코더를 경유해야 한다 '.
'(invalid UTF-8 로그로 인한 빈 SSE data 라인 송출 차단).'
);
// raw json_encode 로 되돌아가면 빈 data 라인 경로가 되살아난다.
$this->assertDoesNotMatchRegularExpression(
'/(?<![A-Za-z0-9_])json_encode\\s*\\(/',
$sseBody,
'SseEmitter::emit 에 가드 없는 json_encode 가 남아 있으면 안 된다.'
);
}
}