달력

122024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

개요

    * utf-8 은 유니코드를 위한 가변길이 문자 인코딩 방식중 하나로 켄 톰프슨과 롭 파이크가 만들었다.
    * 유니코드 한문자를 표시하기 위해 1byte~4byte 까지 가변적으로 표현한다.
    * U+0000부터 U+007F 범위에 있는 아스키 문자들은 UTF-8에서 1바이트만으로 표시된다. 4바이트로 표현되는 문자는 모두 기본 다중언어 평면(BMP) 바깥의 유니코드 문자이며, 거의 사용되지 않는다.

구조

    * U+007F(0~127) 까지의 문자는 7비트 아스키 문자와 동일하게 표시된다.
    * U+007F 이후의 문자는 4byte 까지의 비트 패턴으로 표시되며 7비트 아스키 문자와 혼동되지 않게 하기 위해 모든 바이트의 최상위 비트는 1 이다.

코드 범위         UTF-16BE 표현         UTF-8 표현         설명
000000 ~00007F         00000000 0xxxxxxx         0xxxxxxx         아스키와 동일한 범위
000080 ~0007FF         00000xxx xxxxxxxx         110xxxxx 10xxxxxx         첫바이트는 110 으로 시작하고, 나머지 바이트는 10으로 시작함
000800 ~00FFFF         xxxxxxxx xxxxxxxx         1110xxxx 10xxxxxx 10xxxxxx         첫바이트는 1110으로 시작하고 , 나머지는 10으로 시작함
010000 ~10FFFF         110110yy yyxxxxxx 110111xx xxxxxxxx         11110zzz 10zzxxxx 10xxxxxx 10xxxxxx         UTF-16 "surrogate pair" 영역(yyyy=zzzzz -1)


utf-8 인코딩 예제

'위'(한글) --> U+C7404(유니코드)
U+0800부터 U+FFFF 사이의 영역에 있으므로, 표에 따라 1110xxxx 10xxxxxx 10xxxxxx 형식으로 인코딩
C704(16진수) --> 1100-0111-0000-0100(2진수) --> 11101100 10011100 10000100(utf-8 인코딩)
결과적으로 이 문자는 3바이트로 인코딩 되면 16진수로 표기하면 EC 9C 84가 된다.

(첫 128 문자는 1바이트로 표시되고, 그 다음 1920 문자 (판독 기호가 붙은 라틴 문자, 그리스 문자, 키릴 문자, 콥트 문자, 아르메니아 문자,
히브리 문자, 아랍 문자)는 2바이트로 표시되며, 나머지 문자들 중 BMP 안에 들어 있는 것은 3바이트, 아닌 것은 4바이트로 표시된다.)

관련자료

    * ksc5601 문자셋
    * cp949변환테이블

utf-8 체크

1. 입력된 문자열이 utf-8로 인코딩 되었는지 하기 위해서는 utf-8 의 인코딩 패턴을 조사한다. 즉 바이트 배열의 패턴이 utf-8 인코딩 패턴(1110xxxx 10xxxxxx 10xxxxxx)과 맞는지를 비교한다.
2. 000080 ~0007FF 범위의 110xxxxx 10xxxxxx 패턴 일 경우엔 중복되는 코드값으로 인해 utf-8 인코딩인지 아닌지 체크하는것은 사실상 불가능하다.

110xxxxx 10xxxxxx 패턴의 utf-8 판독

1. 110xxxxx 10xxxxxx 패턴의 코드일 경우 utf-8로 인코딩 되었는지 아닌지를 확인하는것은 사실상 불가능하다.
하지만 utf-8로 인코딩 되면 아스키 영역은 1byte 한글은 3byte 내지는 4byte를 사용하므로 단순하게 생각하여 2 바이트 110xxxxx 10xxxxxx 패턴의 경우는 utf-8 인코딩이 아님을 유추할수 있다.
2. 판독 기호가 붙은 라틴 문자, 그리스 문자, 키릴 문자, 콥트 문자, 아르메니아 문자, 히브리 문자, 아랍 문자 등이 입력되면 판독 오류가 발생하겠지만 이는 무시해도 좋을듯 하다.
3. Null 값의 경우 자바에서는 변형된 utf-8 인코딩 방식에 따라 1byte가 아닌 2byte(11000000 10000000)로 표기하므로 110xxxxx 10xxxxxx 의 경우 null 인지 비교해야 정확한 판독이 가능하지만 입력된 값의 인코딩여부를 판독하므로 상황에 따라서는 무시해도 괜찮을듯 하다.
4. CJK 2바이트+한글 2바이트 가 '1110xxxx 10xxxxxx 10xxxxxx 0xxxxxxx' 와 같은 패턴으로 utf-8 3바이트 와 아스키1바이트 조합과 유사할 경우 앞자리 3바이트를 유니코드 형태로 역치환 해서 000800 ~00FFFF 범위가 맞는지 체크한다.

utf-8 인코딩 체크 예제

1. '위'.getBytes("ISO-8859-1") : -20 , -100, -124
2. unsigned byte = 236, 156, 132
3. 16진수 : EC , 9C, 84
4. 2진수 : 11101100 10011100 10000100
5. urf-8 패턴제거: 00001100 00011100 00000100
6. 유니코드 타입으로 치환 : 00001100<<12 + 0011100<<6 + 00000100 = 11000000 00000100
7. 16진수 : 000800 <= 00E704 =< 00FFFF

package util;

import java.io.UnsupportedEncodingException;

/**
*  <pre>
*  작성자 : 이종희 (qola@naver.com)
*  JAlbum.com (http://jalbum.net/download.jsp?ref=list) 소스 참고함.  
*  <b>특정 문자열이 utf-8 인코딩인지 아닌지 체크한다.</b>
*
*  utf-8 인코딩 패턴인  110xxxxx 10xxxxxx , 1110xxxx 10xxxxxx 10xxxxxx 인지 비교한다.
*  2바이트 (110xxxxx 10xxxxxx) 패턴의 유니 코드 중복으로 인해 100% 검증할수 없지만
*  한글의 경우 3byte 로 표기 되므로  2바이트 패턴일 경우 utf-8 인코딩이 아닌것으로 간주한다.
*  
*  따라서 000080 ~0007FF 영역의 라틴 문자, 그리스 문자, 키릴 문자, 콥트 문자,
*  아르메니아 문자, 히브리 문자, 아랍 문자 등은 utf-8 인코딩을 비교할수 없다.
*  
*  수정된 utf-8의 의한 null값 \u0000 은 11000000 10000000 로 표기되지만 무시하기로 한다.
*  
*  </pre>
*/

public class UTFUtil {
          
       
        public static boolean isUTF8(String str) throws Exception{
                byte[] bytes=str.getBytes("ISO-8859-1");
                return isUTF8(bytes,0,bytes.length);
        }
          
        public static boolean isUTF8(byte[] buf, int offset, int length) {

               boolean yesItIs = false;
               for (int i=offset; i<offset+length; i++) {
                  if ((buf[i] & 0xC0) == 0xC0) { // 11xxxxxx 패턴 인지 체크
                     int nBytes;
                     for (nBytes=2; nBytes<8; nBytes++) {
                        int mask = 1 << (7-nBytes);
                        if ((buf[i] & mask) == 0) break;
                     }
                   

                      //CJK영역이나 아스키 영역의 경우 110xxxxx 10xxxxxx 패턴으로 올수 없다.
                     if(nBytes==2) return false;
                    
                     // Check that the following bytes begin with 0b10xxxxxx
                     for (int j=1; j<nBytes; j++) {
                        if (i+j >= length || (buf[i+j] & 0xC0) != 0x80) return false;
                     }
               
                if(nBytes==3){
                        // 유니코드 형태로 역치환 해서 0x0800 ~ 0xFFFF 사이의 영역인지 체크한다.
                    char c = (char) (((buf[i] & 0x0f) << 12) + ((buf[i+1] & 0x3F) << 6) + (buf[i+2] & 0x3F));
                    if(!(c >= 0x0800 && c <= 0xFFFF)){
                        return false;
                    }                               
                }
                       
                     yesItIs = true;
                  }
               }
               return yesItIs;
         }
           
}

한글 URL 사용시 문제 발생 케이스

    * IE에는 유니코드 지원을 위해 기본적으로 URL을 UTF-8 로 전송해 주지만 옵션 사항이므로 사용자 마다 환경이 다를수 있다.
    * utf-8로 다시 보내는 기능을 제공하지 않는 브라우저도 존재한다.
    * RSS 리더와 같은 별도의 클라이언트를 사용할경우 서버인코딩, 클라이언트, 브라우저 인코딩 이 모두 일치하여야만 한다.

해결책

    * REQUEST 요청시 URL이 UTF-8 인코딩 방식인지 아닌지를 체크하여 UTF-8 방식이면 "ISO-8859-1 --> UTF-8" 형태로 인코딩 변환을 해주고 그렇지 않으면 "ISO-8859-1 --> MS949" 로 인코딩 변환을 시켜준다.

구현 예제

    * APACHE MOD_REWRITE
          o 아래 예와 같이 MOD_REWRITE를 이용해 특정 패턴의 URL을 REWRITE 시킨다.

            RewriteEngine   on
                # // http://tag.naver.com/tag2/한글 --> http://tag.naver.com/tag/index.jsp?tag=한글
                RewriteRule ^/tag2/(.+)$  /tag/index.jsp?tag=$1 [PT]

    * JSP 샘플
          o isUTF8() 체크 메소드를 이용해 파라메터의 인코딩을 각각 처리해준다.

            <%@page language="java" contentType="text/html;charset=utf-8"%>
            <%@page import="java.net.URLEncoder, util.*" %>

            <html>
            <head>
                    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
            </head>

            <body>
              index.jsp... <br>
             
              <%
                String uri=request.getRequestURL().toString();
                      String tag=request.getParameter("tag");
                      byte[] tagBytes=tag.getBytes("ISO-8859-1");
                     
                      out.println("byte[]===");
                              for(int i=0;i<tagBytes.length;i++){
                                      out.println(tagBytes[i]+",");
                              }
                      out.println("<br>");  
                     
                      boolean isUTF8=UTFUtil.isUTF8(tag);
                     
                      if(isUTF8) {
                              out.println(util.Enco.toUtf(tag));
                              out.println("<br>");
                              out.println(isUTF8);
                             
                      }else{
                              out.println(new String(tag.getBytes("ISO-8859-1"),"ms949"));
                              out.println("<br>");
                              out.println(isUTF8);
                      }
              %>
             
              <a href="http://tag_test.com/tag2/한글">http://tag_test.com/tag2/한글</a><br>
              <a href="http://tag_test.com/tag2/코리아">http://tag_test.com/tag2/코리아</a><br>
              <a href="http://tag_test.com/tag2/위">http://tag_test.com/tag2/위</a>
            </body>
            </html>


이종희 : jonghee.lee@gmail.com
        http://www.jongsclub.com
Posted by 알 수 없는 사용자
|