macOS에서 파일 업로드 시 발생하는 한글 자소 분리 문제를 해결
2025. 6. 4. 22:48ㆍ프로그램/PHP 초급
문제의 주요 원인 및 버그:
- ChangeMacUTF8 함수 내 UTF-8 바이트 처리 오류:
- $c = $strName[$i].$strName[$i+1].$strName[$i+2]; // utf-8 은 3바이트. 이 부분은 한글 UTF-8 문자가 항상 3바이트라고 가정합니다. 하지만 UTF-8은 1바이트(ASCII), 2바이트(일부 유럽 문자), 3바이트(한글, 일본어, 중국어 등), 4바이트(이모지 등)까지 다양한 길이의 문자를 표현할 수 있습니다.
- $i = $i+2; // 3바이트니 +2 역시 3바이트 문자로 고정하고 인덱스를 건너뛰고 있습니다. 실제 문자의 바이트 길이를 확인하지 않고 고정적으로 3바이트씩 건너뛰면, 2바이트 또는 4바이트 문자를 만나면 잘못된 바이트를 읽어들이게 되어 문자 깨짐으로 이어집니다.
- utf8_ord() 함수는 문자의 실제 바이트 길이를 $len 변수를 통해 확인하고 있지만, ChangeMacUTF8 함수에서는 $c를 만들 때 항상 3바이트로 자르고 있습니다. 이로 인해 유효하지 않은 UTF-8 시퀀스가 utf8_ord로 넘어가 false를 반환하거나 잘못된 유니코드 값을 반환할 수 있습니다.
- mergChar 함수의 인덱스 오류 및 비한글 문자 처리 부족:
- $i = $aryChars[0] - 4352; (초성)
- $j = $aryChars[1] - 4449; (중성)
- $k = $aryChars[2] - 4519; (종성)
- 이 부분은 aryChars 배열에 초성, 중성, 종성 유니코드 값 (자소 분리된 형태)이 정확히 세 개씩 들어있을 때만 정상적으로 작동합니다.
- 만약 aryChars에 초성 하나만 있거나, 초성+중성만 있거나, 혹은 전혀 다른 유니코드 값(초성/중성/종성이 아닌 일반 한글)이 들어있을 경우, $aryChars[1]이나 $aryChars[2]에 접근할 때 "Undefined offset" 오류가 발생하거나, 잘못된 $j, $k 값을 계산하여 엉뚱한 한글 ($z)이 생성될 수 있습니다.
- if (1 < count($aryChars)), if (2 < count($aryChars)) 조건으로 인덱스 접근 오류를 부분적으로 방지하려 했지만, 이 조건이 참이 아닐 경우 $j나 $k는 0으로 초기화된 상태로 계산에 사용되므로, 예를 들어 초성만 있는 경우에도 초성+중성+종성이 모두 조합된 한글로 변환하려 시도하게 됩니다.
- 한글 자소 범위 검증 부족:
- if (4352 <= $u && 4370 >= $u) (ㄱ~ㅎ 초성 범위 확인) 부분은 초성만 확인하고 중성/종성에 대한 명확한 검증이 부족합니다. 실제로는 초성, 중성, 종성이 연속적으로 나타나야 자소 조합이 의미가 있는데, 이 로직은 aryChars에 자소 분리된 글자가 아닌 다른 한글이 들어갈 가능성을 충분히 처리하지 못합니다.
- mergChar 호출 타이밍 문제:
- ChangeMacUTF8 내에서 $o < 128 (ASCII 문자)인 경우 즉시 mergChar를 호출하고 aryChars를 비우는 로직이 있습니다. 이는 한글 자소들이 ASCII 문자 중간에 끊겨서 나타날 경우 조합되지 못하게 만들 수 있습니다.
해결 방안 및 개선된 로직 제안
macOS에서 발생하는 NFD(Normalization Form Canonical Decomposition) 형식의 한글 문제를 해결하기 위한 가장 일반적이고 권장되는 방법은 PHP의 Normalizer 클래스를 사용하는 것입니다. 이 클래스는 유니코드 표준에 따라 문자열을 정규화(Normalization)하는 기능을 제공하며, 자소 분리 문제를 가장 정확하고 효율적으로 해결할 수 있습니다.
Normalizer 클래스를 이용한 해결책 (권장):
이 방법은 별도의 복잡한 문자열 파싱 로직 없이 한 줄로 문제를 해결할 수 있습니다. PHP intl 확장이 활성화되어 있어야 합니다. (대부분의 웹 서버에 기본적으로 활성화되어 있습니다.)
PHP
<?php
// Normalizer 클래스를 사용하기 위해 intl 확장이 필요합니다.
// php.ini 에서 extension=intl 을 활성화해야 합니다.
/**
* macOS에서 NFD 형태로 분리된 한글을 NFC 형태로 정규화합니다.
*
* @param string $strName 정규화할 문자열
* @return string 정규화된 문자열
*/
function normalizeMacKorean($strName) {
if (class_exists('Normalizer')) {
// NFD(Normalization Form Canonical Decomposition)로 되어있는 것을
// NFC(Normalization Form Canonical Composition)로 변환합니다.
// NFC는 조합형 한글 (초성 + 중성 + 종성이 하나의 완성된 글자) 형태입니다.
return Normalizer::normalize($strName, Normalizer::FORM_C);
} else {
// Normalizer 클래스가 없는 경우 (intl 확장 비활성화 등)
// 경고를 반환하거나 원래 문자열을 반환합니다.
error_log("Normalizer class not found. Please enable intl extension.");
return $strName; // 또는 적절한 오류 처리
}
}
// 테스트 예시
$mac_uploaded_name = "테스트파일.txt"; // 실제로는 자소 분리된 형태로 들어왔다고 가정
// 실제 자소 분리된 문자열을 PHP로 표현하기 어려워 임의의 예시를 듭니다.
// 예를 들어, '테'가 'ㅌㅔ'로 분리되어 들어왔다고 가정하면...
// $mac_uploaded_name_nfd = "테스트파일.txt"; // 이런 식의 문자열이 됩니다. (육안으로 구분 어려움)
// 임시로 분리된 유니코드 값으로 테스트 (실제로는 파일명 문자열 자체가 NFD)
// 실제 환경에서는 Normalizer::FORM_D 로 변환 후 다시 C 로 변환하여 테스트
$test_string_nfd = Normalizer::normalize("한글파일.txt", Normalizer::FORM_D);
echo "원본 (NFD 가정): " . $test_string_nfd . "<br>";
echo "정규화 후 (NFC): " . normalizeMacKorean($test_string_nfd) . "<br>";
$original_string = "정상적인한글.jpg";
echo "정상 한글 정규화: " . normalizeMacKorean($original_string) . "<br>";
?>
설명:
- Normalizer::FORM_C (NFC): 이는 유니코드 정규화 형식 중 하나로, 한글 자소들이 조합되어 하나의 완성된 글자로 표현되는 형태입니다. macOS에서 NFD(Normalization Form Canonical Decomposition) 형태로 분리된 한글이 넘어왔을 때, 이 FORM_C로 정규화하면 원래 의도했던 완성형 한글로 되돌릴 수 있습니다.
- Normalizer::FORM_D (NFD): 자소를 분리하는 형태로, macOS 파일 시스템에서 한글 파일명이 저장되는 방식입니다. FORM_D는 디버깅이나 NFD 상태를 확인하기 위해 사용할 수 있습니다.
주의사항:
- intl 확장 활성화: PHP Normalizer 클래스를 사용하려면 php.ini 파일에서 extension=intl 라인의 주석을 해제하여 intl 확장을 활성화해야 합니다.
- 파일 시스템의 정규화: 파일명을 처리할 때 NFD -> NFC 변환이 필요한 경우는 주로 macOS에서 생성된 파일명이 다른 OS(Linux, Windows)의 파일 시스템으로 넘어갈 때 발생합니다. 파일 시스템 자체가 자동으로 정규화해주는 경우도 있지만, 안전하게 처리하려면 업로드된 파일명을 항상 NFC로 정규화하는 것이 좋습니다.
왜 기존 코드가 문제를 일으켰는지 다시 한번 강조
제공해주신 코드는 utf8_ord, utf8_chr 함수를 통해 유니코드 값을 다루고 초성, 중성, 종성을 수동으로 조합하려 했지만, 다음과 같은 근본적인 한계와 오류가 있었습니다.
- 잘못된 UTF-8 바이트 처리: ChangeMacUTF8에서 문자열을 3바이트씩만 읽으려 한 점.
- 불완전한 자소 조합 로직: mergChar 함수가 aryChars에 항상 3개의 자소(초, 중, 종)가 들어있다고 가정하여, 하나 또는 두 개의 자소만 있는 경우, 혹은 자소가 아닌 일반 한글이 들어온 경우 처리하지 못하고 오류를 유발했습니다.
- 불필요한 복잡성: Normalizer 클래스처럼 검증된 유니코드 라이브러리를 사용하지 않고 수동으로 유니코드 자소 조합을 구현하는 것은 매우 복잡하고 오류가 발생하기 쉽습니다.
따라서, Normalizer::normalize($strName, Normalizer::FORM_C)를 사용하는 것이 가장 안전하고 확실한 해결책입니다.
'프로그램 > PHP 초급' 카테고리의 다른 글
| 초를 동영상 재생 시간 (ISO 8601 형식) 으로 변환하는 함수 (0) | 2025.06.08 |
|---|---|
| 방대한 자료를 LIKE 검색을 하는데 부하를 줄일수 있는 방법 (0) | 2025.06.06 |
| PHP 함수가 있는지 체크하고 정의 하는 방법 (0) | 2025.06.04 |
| Content Security Policy (CSP)엄격하게 설정하는 방법 (0) | 2025.06.04 |
| PHP Path Combine 방법 (0) | 2025.06.04 |