<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>빛을 담고 세상 넓히기 &#187; 인코딩</title>
	<atom:link href="http://fantazic.com/archives/tag/%ec%9d%b8%ec%bd%94%eb%94%a9/feed" rel="self" type="application/rss+xml" />
	<link>http://fantazic.com</link>
	<description>마음의 빛으로 넓은 세상을 비추고 싶다.</description>
	<lastBuildDate>Sun, 27 Nov 2011 23:52:11 +0000</lastBuildDate>
	
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>톰캣 한글 인코딩 문제와 관련된 코드들</title>
		<link>http://fantazic.com/archives/255</link>
		<comments>http://fantazic.com/archives/255#comments</comments>
		<pubDate>Fri, 10 Jul 2009 06:03:17 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[인코딩]]></category>
		<category><![CDATA[톰캣]]></category>
		<category><![CDATA[한글]]></category>

		<guid isPermaLink="false">http://fantazic.com/?p=255</guid>
		<description><![CDATA[
Tweet

Java로 6년째 개발을 하면서 한글 문제는 더 이상 새로운 것이 없겠지 생각을 하지만 매번 알 수 없는 상황에 부딪히게 된다.
EUC-KR 기반의 아파치-톰캣 시스템에서 브라우져 주소창의 한글을 처리하려고 할 때 다음과 같은 문제가 발생했다.
주소창에 &#8216;http://v.daum.net/search?q=한글&#8217;을 직접 입력하는 경우 크게 3가지 경우가 발생했다.
1) &#8216;한글&#8217;이라는 파라미터 값이 &#8216;EUC-KR&#8217;로 URLEncoding 되서 넘어오는 경우
2) &#8216;한글&#8217;이라는 파라미터 값이 &#8216;UTF-8&#8242;로 URLEncoding 되서 [...]]]></description>
			<content:encoded><![CDATA[<div style="display:block;margin-left: 10px; margin-bottom: 10px;">
<a href="http://twitter.com/share" class="twitter-share-button" data-url="http://fantazic.com/archives/255" data-text="톰캣 한글 인코딩 문제와 관련된 코드들" data-count="horizontal" >Tweet</a><script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
</div>
<p>Java로 6년째 개발을 하면서 한글 문제는 더 이상 새로운 것이 없겠지 생각을 하지만 매번 알 수 없는 상황에 부딪히게 된다.</p>
<p>EUC-KR 기반의 아파치-톰캣 시스템에서 브라우져 주소창의 한글을 처리하려고 할 때 다음과 같은 문제가 발생했다.</p>
<p>주소창에 &#8216;http://v.daum.net/search?q=한글&#8217;을 직접 입력하는 경우 크게 3가지 경우가 발생했다.</p>
<p>1) &#8216;한글&#8217;이라는 파라미터 값이 &#8216;EUC-KR&#8217;로 URLEncoding 되서 넘어오는 경우<br />
2) &#8216;한글&#8217;이라는 파라미터 값이 &#8216;UTF-8&#8242;로 URLEncoding 되서 넘어오는 경우<br />
3) &#8216;한글&#8217;이 bytes로 변환되서 넘어오는 경우</p>
<p>이와 유사한 문제가 아파치 rewrite module을 사용할 때도 발생했다. 예를 들어 &#8216;http://v.daum.net/한글&#8217;을 &#8216;http://v.daum.net/search?q=한글&#8217;로 재작성하는 경우 &#8216;한글&#8217;이 bytes로 변환된다.</p>
<p>이에 대한 해결방법은 여러가지가 있을 수 있는데, 다음과 같은 아이디어를 기반으로 문제를 해결했다.</p>
<p>1. URLEncoding된 문자열의 헥사값을 bytes로 변환할 수 있다.<br />
2. bytes의 인코딩을 예측할 수 있다.</p>
<p>(구현된 코드는 톰캣 내부의 파라미터 처리 부분과 Daum 검색개발팀의 황재석님의 코드에 도움을 받았습니다.)</p>
<p>&#8216;getParameterSafely(request.getQueryString(), &#8220;q&#8221;);&#8217;와 같이 사용하면 됩니다. 단, 파라미터에 &#8216;%&#8217; 문자가 포함된 경우 오작동하게 됩니다.</p>
<pre>
/* public query parsing method */
public static String getParamterSafely(String queryString, String key) {
  if (StringUtils.isEmpty(queryString)) {
    return "";
  }

  try {
    String safeQueryString = urlDecodeSafely(queryString);
    return getParameter(safeQueryString, key);
  } catch (UnsupportedEncodingException e) {
    //ignore
    e.printStackTrace();
  }
  return "";
}

/* URLDecoding 수행 */
private static String urlDecodeSafely(String queryString)
  throws UnsupportedEncodingException {
  if (StringUtils.isEmpty(queryString))
    return "";
  byte[] src = queryString.getBytes("ISO-8859-1");
  byte[] bytes = urlDecode(src);
  <del datetime="2009-07-17T07:26:45+00:00">String charset = guessCharset(bytes);</del>
  String charset = CharsetDetector.detect(bytes);
  return new String(bytes, charset);
}

/* 헥사코드를 bytes로 변환 */
private static byte[] urlDecode(byte[] data)
  throws UnsupportedEncodingException {
  int ix = 0;
  int ox = 0;
  while (ix < data.length) {
    byte c = data[ix++];
    switch ((char) c) {
      case '+':
        data[ox++] = (byte) ' ';
        break;
      case '%':
        data[ox++] = (byte) ((convertHexDigit(data[ix++]) << 4)
          + convertHexDigit(data[ix++]));
        break;
      default:
        data[ox++] = c;
    }
  }

  return ArrayUtils.subarray(data, 0, ox);
}

/* 헥사코드 변환 */
private static byte convertHexDigit(byte b) {
  if ((b >= '0') &#038;&#038; (b <= '9'))
    return (byte) (b - '0');
  if ((b >= 'a') &#038;&#038; (b <= 'f'))
    return (byte) (b - 'a' + 10);
  if ((b >= 'A') &#038;&#038; (b <= 'F'))
    return (byte) (b - 'A' + 10);
  return 0;
}

<del datetime="2009-07-17T07:26:45+00:00">/* 재석님의 캐릭터셋 추측하는 메소드 */
private static String guessCharset(byte[] bytes) {
  try {
    CharsetDecoder decoder = Charset.forName("MS949").newDecoder();
    ByteBuffer bb = ByteBuffer.wrap(bytes);
    decoder.decode(bb);
    bb.clear();
    return "MS949";
  } catch (Exception e) {
    return "UTF-8";
  }
}</del>

/* 쿼리 문자열에서 특정 키의 값을 가져온다. */
private static String getParameter(String safeQueryString, String key) {
  String parameters[] = safeQueryString.split("&#038;");
  if (parameters != null) {
    for (String parameter : parameters) {
      if (parameter.startsWith(key + "=")) {
        return parameter.replaceFirst(key + "=", "");
      }
    }
  }
  return "";
}
</pre>
<p><a href="http://dmlim.egloos.com/3966342">임동문님의 블로그</a>를 참고해서 캐릭터셋 판별 부분을 보완했습니다.</p>
<pre>
public class CharsetDetector {
  private static final String DEFAULT_CHARSET = "MS949";
  private static final int MS949_NORMAL = 0;
  private static final int MS949_2BYTE = 1;
  private static final int MS949_KSC_2BYTE = 2;
  private static final int UTF8_NORMAL = 0;
  private static final int UTF8_2BYTE = 1;
  private static final int UTF8_3BYTE = 2;
  private static final int UTF8_4BYTE = 3;

  public static String detect(byte[] bytes) {
    if (isUTF8(bytes))
      return "UTF-8";
    if (isMS949(bytes))
      return "MS949";
    return DEFAULT_CHARSET;
  }

  public static boolean isMS949(byte[] bytes) {
    int status = MS949_NORMAL;
    for (int ch : bytes) {
      if (status < 0)
        return false;

      switch (status) {
        case MS949_NORMAL:
          if (ch < 0x80)
            status = MS949_NORMAL;
          else if (0x81 <= ch &#038;&#038; ch <= 0xc5)
            status = MS949_2BYTE;
          else if (0xc5 < ch &#038;&#038; ch <= 0xfe)
            status = MS949_KSC_2BYTE;
          else
            status = -1;
          break;
        case MS949_2BYTE:
          if ((0x41 <= ch &#038;&#038; ch <= 0x5a) || (0x61 <= ch &#038;&#038; ch <= 0x7a)
            || (0x81 <= ch &#038;&#038; ch <= 0xfe))
            status = MS949_NORMAL;
          else
            status = -1;
          break;
        case MS949_KSC_2BYTE:
          if (0xa1 <= ch &#038;&#038; ch <= 0xfe)
            status = MS949_NORMAL;
          else
            status = -1;
          break;
        default:
          break;
      }
    }
    return true;
  }

  public static boolean isUTF8(byte[] bytes) {
    int status = UTF8_NORMAL;

    for (int ch : bytes) {
      if (status < 0)
        return false;

      switch (status) {
        case UTF8_NORMAL:
          if ((ch &#038; 0x80) == 0)
            status = UTF8_NORMAL;
          else if (((ch &#038; 0xe0) ^ 0xc0) == 0)
            status = UTF8_2BYTE;
          else if (((ch &#038; 0xf0) ^ 0xe0) == 0)
            status = UTF8_3BYTE;
          else if (((ch &#038; 0xf8) ^ 0xf0) == 0)
            status = UTF8_4BYTE;
          else
            status = -1;
          break;
        case UTF8_2BYTE:
          if (((ch &#038; 0xc0) ^ 0x80) == 0)
            status = UTF8_NORMAL;
          else
            status = -1;
          break;
        case UTF8_3BYTE:
          if (((ch &#038; 0xc0) ^ 0x80) == 0)
            status = UTF8_2BYTE;
          else
            status = -1;
          break;
        case UTF8_4BYTE:
          if (((ch &#038; 0xc0) ^ 0x80) == 0)
            status = UTF8_3BYTE;
          else
            status = -1;
          break;
        default:
          break;
      }
    }
    return true;
  }
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/255/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

