URL 인코딩, 어디를 얼마나 해야 하나
경로와 쿼리는 규칙이 다르고, `+`는 한쪽에서만 공백입니다. 이중 인코딩과 한글 도메인의 Punycode까지 정리했습니다.
URL에 값을 넣을 때 인코딩을 어디까지 해야 하는지는 매번 애매합니다. 함수 이름이 비슷한 것이 여럿이고, 위치에 따라 규칙도 다릅니다.
인코딩하지 않아도 되는 문자
퍼센트 인코딩은 문자를 바이트로 바꾼 뒤 각 바이트를 % 두 자리 16진수로 적는 방식입니다. 한글은 UTF-8로 세 바이트이므로 한 글자가 %EC%95%88처럼 아홉 글자가 됩니다.
인코딩하지 않아도 되는 문자는 정해져 있습니다.
A-Z a-z 0-9 - . _ ~
나머지는 두 부류입니다.
- 예약 문자 —
: / ? # [ ] @ ! $ & ' ( ) * + , ; =구조를 나타내는 데 쓰입니다. 구분자로 쓸 때는 그대로 두고, 값 안에 들어갈 때는 인코딩합니다 - 그 외 — 공백, 한글, 이모지 등. 항상 인코딩합니다
핵심은 예약 문자 쪽입니다. 쿼리에서 &는 항목을 나누는 구분자이므로 그대로 둡니다. 그런데 값 안에 &가 들어간다면 %26으로 바꿔야 합니다. 그러지 않으면 값 하나가 두 개로 쪼개집니다.
값만 인코딩한다
그래서 URL 전체를 한 번에 인코딩하는 것은 대개 잘못입니다. 조립할 때 값 단위로 인코딩하고, 구분자는 직접 씁니다.
자바스크립트의 두 함수가 이 구분에 대응합니다.
encodeURIComponent()— 예약 문자까지 인코딩합니다. 값 하나를 넣을 때 씁니다encodeURI()— 예약 문자는 그대로 둡니다. 이미 완성된 URL에서 공백이나 한글만 정리할 때 씁니다
값에 encodeURI()를 쓰면 &나 =가 살아남아 구조가 깨집니다. 반대로 완성된 URL에 encodeURIComponent()를 쓰면 ://까지 인코딩되어 문자열 하나가 됩니다.
가능하면 문자열을 직접 잇기보다 URLSearchParams나 각 언어의 URL 빌더를 씁니다. 값 인코딩을 알아서 처리합니다.
공백과 +
같은 공백이 자리에 따라 다르게 표기됩니다.
- 경로에서는
%20입니다.+는 그냥 더하기 기호입니다 - 쿼리 문자열에서는
%20과+가 모두 공백으로 읽힙니다. HTML 폼이 보내는application/x-www-form-urlencoded형식이 공백을+로 적기 때문입니다
여기서 문제가 생깁니다. 쿼리 값에 진짜 더하기 기호를 넣어야 한다면 %2B로 인코딩해야 합니다. 그러지 않으면 받는 쪽에서 공백이 됩니다. 전화번호 +82 10-1234-5678이나 Base64 값(+가 들어갑니다)을 쿼리로 넘길 때 자주 겪는 일입니다.
이중 인코딩
이미 인코딩된 값을 한 번 더 인코딩하면 %가 %25로 바뀝니다.
안 → %EC%95%88
%EC%95%88 → %25EC%2595%2588
받는 쪽에서 한 번만 디코딩하면 %EC%95%88이라는 문자열이 그대로 남습니다. 값이 여러 계층(프론트엔드 → API 게이트웨이 → 백엔드)을 지날 때, 각 계층이 인코딩을 한 번씩 더 하면서 생깁니다.
%25가 보이면 이중 인코딩을 의심합니다. 규칙은 인코딩한 쪽이 한 번, 디코딩하는 쪽이 한 번입니다. 중간에서 값을 다시 조립한다면 그 계층에서 디코딩과 인코딩이 짝을 이루는지 확인합니다.
도메인은 퍼센트 인코딩을 쓰지 않는다
주소의 호스트 부분은 규칙이 다릅니다. DNS는 ASCII만 다루므로, 한글이나 다른 문자가 들어간 도메인은 Punycode로 변환됩니다.
한국.kr → xn--3e0b707e.kr
xn-- 접두사가 변환된 도메인이라는 표시입니다. 브라우저 주소창에는 원래 글자로 보여 주지만, 실제로 요청이 나갈 때는 변환된 형태입니다.
여기에 알아 둘 것이 있습니다. 서로 다른 문자 중에 화면에서 똑같아 보이는 것들이 있습니다. 키릴 문자 а와 라틴 문자 a는 눈으로 구분되지 않지만 다른 문자입니다. 이걸 이용해 유명 도메인과 똑같아 보이는 주소를 만들 수 있습니다.
브라우저는 이런 경우 주소창에 Punycode 형태를 그대로 노출하는 식으로 방어합니다. 의심스러운 주소는 Punycode로 변환해 보면 xn--로 시작하는지 바로 확인됩니다.
확인할 때
- 쿼리 값에
&,=,+,#이 들어가면 인코딩되었는지 봅니다 %25가 보이면 인코딩이 한 번 더 걸린 것입니다- 값이 잘려 들어왔다면 구분자로 해석된 문자가 있는지 봅니다
- 호스트에
xn--이 보이면 원래 어떤 글자였는지 확인합니다
tools.onuel.dev에 URL 인코딩과 디코딩이 있고, 주소를 조각별로 나눠 보여 주는 URL 파서와 Punycode 변환도 함께 있습니다.
- #URL
- #인코딩
- #웹