Skip to content
isdnetworks
Go back

같은 파일인데 한쪽에서만 404였다

상품 이미지가 관리자 화면에서는 보이는데 상품 페이지에서는 깨졌다. 같은 파일을 가리키는데 한쪽만 404다.

Table of contents

Open Table of contents

증상 — 만드는 주소가 달랐다

관리자 화면의 이미지 주소를 복사해 보니 이랬다.

/upload/product/%ED%83%80%EC%9D%B4%EC%96%B4_215_60_16.jpg

상품 페이지 쪽은 이랬다.

/upload/product/타이어_215_60_16.jpg

앞은 URL 인코딩된 이름이고 뒤는 원문 그대로다. 브라우저가 알아서 맞춰 줄 것 같지만 그렇지 않았다.

읽는 코드도 통일돼 있지 않았다.

// 관리자
echo '<img src="/upload/product/' . rawurlencode($row['img']) . '">';

// 상품 페이지
echo '<img src="/upload/product/' . $row['img'] . '">';

한쪽은 rawurlencode 를 걸고 한쪽은 안 건다. 자료에 든 값은 하나인데 만들어지는 주소가 둘이다.

규격이 두 개면 어느 쪽이 맞는지가 파일마다 달라진다. 그래서 어떤 상품은 관리자에서만 보이고 어떤 상품은 상품 페이지에서만 보였다.

저장 시점에 이름이 변형됐다

업로드 처리 부분을 봤다.

$name = $_FILES['img']['name'];
move_uploaded_file($_FILES['img']['tmp_name'], $dir . $name);

브라우저가 보낸 이름을 그대로 쓴다. 문제는 브라우저마다 운영체제마다 보내는 형태가 다르다는 것이다.

디렉터리를 열어 보니 눈으로는 같아 보이는 이름이 여러 벌 있었다.

타이어_215_60_16.jpg
%ED%83%80%EC%9D%B4%EC%96%B4_215_60_16.jpg
타이어_215_60_16.jpg

셋째는 자모 분리형이다. 유니코드에는 같은 글자를 적는 방식이 둘 있는데 「상」을 한 글자로 담는 NFC 와 초성·중성·종성을 따로 담는 NFD 다.

바이트로 보면 전혀 다르다. rawurlencode 를 걸어 보면 NFC 는 %EC%83%81 이고 NFD 는 %E1%84%89%E1%85%A1%E1%86%BC 다. 화면에는 둘 다 「상」으로 보인다.

리눅스 파일시스템은 정규화를 하지 않고 바이트를 그대로 쓰며 비교도 바이트로 한다. 그러니 NFC 로 만든 경로로는 NFD 로 저장된 파일이 없는 것이 된다.

Normalizer::normalize($name, Normalizer::FORM_C) 로 받는 쪽에서 통일하는 것도 해 봤다. 그래도 이미 쌓인 것이 제각각이라 불러오는 쪽만으로는 닫히지 않았다.

이름을 만들어 저장했다

원본 이름을 그대로 쓰는 것을 그만뒀다.

$ext  = strtolower(pathinfo($_FILES['img']['name'], PATHINFO_EXTENSION));
$name = date('Ymd') . '_' . uniqid() . '.' . $ext;

move_uploaded_file($_FILES['img']['tmp_name'], $dir . $name);

uniqid 로 만든 이름이라 디스크에는 영문과 숫자만 들어간다. 어떤 환경에서 올려도 정규화 형태를 따질 일이 없어지고 이름이 겹치는 문제도 사라졌다.

원본 이름은 버리지 않고 별도 컬럼에 넣었다. 내려받을 때 원래 이름으로 보여 줄 데가 있어서다.

ALTER TABLE product_image
  ADD COLUMN origin_name VARCHAR(255) NULL AFTER file_name;

origin_name 은 표시 전용이고 디스크 경로는 file_name 만 쓴다.

확장자와 이미 쌓인 것

이름을 새로 만들면서 확장자를 그대로 옮겨 붙였는데 여기에도 구멍이 있었다. 확장자는 올리는 쪽이 정하는 문자열이라 .jpg 라고 적혀 있어도 내용이 그림이 아닐 수 있다.

$info = getimagesize($_FILES['img']['tmp_name']);
if ($info === false) {
    return '이미지 파일이 아닙니다';
}

getimagesize 로 내용을 읽어서 그림인지 본다. finfo_open(FILEINFO_MIME_TYPE) 으로 형식을 확인해 확장자를 정하면 이름과 내용이 어긋나지 않는다.

새로 올리는 것은 정리됐는데 기존 파일이 남아 있었다. 디렉터리를 훑어서 자료의 값과 대조했고 이때 양쪽을 Normalizer::FORM_C 로 맞춰 놓고 비교했다. 그러지 않으면 같은 파일이 서로 없는 것으로 잡힌다.

세 가지가 나왔다.

자료에 있고 파일도 있다    새 규격으로 이름을 바꾸고 값을 갱신
자료에 있는데 파일이 없다  목록으로 남겨서 다시 올리게 함
파일은 있는데 자료에 없다  상품이 지워진 잔여물

두 번째가 40건쯤 됐다. 인코딩이 어긋난 채로 저장돼서 어느 쪽 이름으로도 안 열리는 것들이었다.

정리


Share this post on:

Previous Post
두 가지를 한 번에 바꿨다
Next Post
응답 헤더에 남은 횟수가 있었다