SJIS環境からのクエリ文字列に対するURLDecoderでの文字化け

モバイルなどのShift_JIS環境から投げられたGETパラメータのクエリをjava.net.URLDecoder.decode(String, String)でdecodeしようとすると文字化けしてしまう場合があります。

これはURLDecoder.decodeにバグがあるというわけではなく、URLEncoder.encodeでencodeされたものを正しくdecodeするという仕様であるためです。

具体的に「http://example.com/?q=%82Q」のようなリファラからクエリ文字列を抽出するケースを考えてみます。

String arg = "%82Q";
System.out.println(URLDecoder.decode(arg, "Shift_JIS"));

上記の出力結果は文字化けを起こしてしまいます。「Q」の部分は本来「%51」のようにencodeされた状態であるべきところですが、ASCIIコード内の1byte文字としてdecodeされた状態になっているためです。これはリファラを設定してくる各サイトの実装依存なので制御できませんから、受け手側での対応が必要になってきます。

このクエリを正しくdecodeする方法として、本来の「%82%51」に戻してからdecodeするという処理の仕方が考えられます。

無数にパターンが増えていく類のものではないので、個人的には以下のような泥臭い対応でよいのではないかと考えています。

String result = getValidQueryString(arg);
System.out.println(result);
private static final Map<String, String> queryStringURLEncodeMapping = new HashMap<String, String>();
static {
	queryStringURLEncodeMapping.put("\"", "%22");
	queryStringURLEncodeMapping.put("#", "%23");
	queryStringURLEncodeMapping.put("$", "%24");
	・・・(中略)・・・
	queryStringURLEncodeMapping.put("|", "%7C");
	queryStringURLEncodeMapping.put("}", "%7D");
	queryStringURLEncodeMapping.put("~", "%7E");
}

public static String getValidQueryString(String queryString) {
	String[] tmp = queryString.split("%");
	StringBuffer sb = new StringBuffer();
	sb.append(tmp[0]);
	for(int i =1; i< tmp.length; i ++) {
		if ( tmp[i].length() <= 0 ) continue;
		if ( tmp[i].length() > 2 ) {
			sb.append("%" + tmp[i].substring(0, 2));
			String surplus = tmp[i].substring(2);
			String[] targets = surplus.split("");
			for(String each : targets) {
				if ( each != null && ! each.equals("") ) {
        				sb.append(queryStringURLEncodeMapping.get(each));
				}
			}
		} else {
			sb.append("%" + tmp[i]);
		}
	}
	return sb.toString().replaceAll("%", queryStringURLEncodeMapping.get("%"));
}