この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

Struts-2.1.8に更新したら、マルチバイト文字が文字参照になった

2010-12-17 簡単なバグが含まれていたのでサンプルコードを修正しました。

今更気がついたのですが…
Struts-2.1.8に更新したら、マルチバイト文字が文字参照になってしまいました。こんな感じ。


Before

  <input type="submit" id="list" name="action:list" value="確認"/>


After

  <input type="submit" id="list" name="action:list" value="&#25147;&#12427;"/>


大きな実害はないのですが、

  • HTMLのソースを見ても意味不明になるのでデバッグし辛い
  • HTMLのファイルサイズが大きくなる

という問題点があります。

原因を調べてみた

もしや<s:property>タグの実装が変更されたのでは?
と思い調べてみると、案の定。


(org.apache.struts2.components.Property.javaから抜粋)

private String prepare(String value) {
    String result = value;
    if (escape) {
        result = StringEscapeUtils.escapeHtml(result);  ← commonsのStringEscapeUtilsを使っている!!
    }
    if (escapeJavaScript) {
        result = StringEscapeUtils.escapeJavaScript(result);
    }
    return result;
}


ここは、以前の実装ではHTMLをエスケープする為にxworkのTextUtilsを使っていたハズなのですが、
apache-commonsのStringEscapeUtilsを使うように変更されたようです orz


apache-commonsのStringEscapeUtilsは、マルチバイト文字をほぼ全部、文字参照へ変換するという困ったちゃん。


うーん、s:propertyなんて至る所で使ってるしなぁ。
どうしよう。


(追記)とにかく回避したい場合


さて、問題のメソッドはprivateメソッドなので、どうにも手を入れようがないです。
なので、どうしても回避したいなら、

  1. org.apache.struts2.components.Property.javaをコピーして自前のタグを作る
  2. CGLIBとかを使って強引にクラスファイルを書き換える。
  3. org.apache.struts2.components.Property.javaを入れ替える。

のどちらかしかないと思います。
で、(1)は色々ファイルを用意しなければならず相当面倒だし、(2)の方法は難易度が高い。
ここでは一番簡単な(3)の方法をご紹介。


まず、
org.apache.struts2.components.Property.javaのソースをコピーしてきて
自分のプロジェクトのソースディレクトリの、"org/apache/struts2/components/"ディレクトリに配置します。


そして、問題の箇所を次の通り書き換えます。
まあ要するにcommons-langのStringEscapeUtilsを使わず自前でエスケープしているだけです。


Before

private String prepare(String value) {
	String result = value;
    if (escape) {
    	result = StringEscapeUtils.escapeHtml(result);
    }
    if (escapeJavaScript) {
    	result = StringEscapeUtils.escapeJavaScript(result);
    }
    return result;
}


After

private String prepare(String value) {
	String result = value;
    if (escape) {
        result =
            result.replaceAll("&", "&amp;").replaceAll(">", "&gt;")
                .replaceAll("\"", "&quot;").replaceAll("<", "&lt;");
    }
    if (escapeJavaScript) {
    	result = StringEscapeUtils.escapeJavaScript(result);
    }
    return result;
}


コレで終わりです。デプロイしたWebアプリのWEB-INF/classesの中に、org.apache.struts2.components.Propertyクラスが配置されていればOK。
サーブレットの仕様により、コチラのクラスのほうが優先されるため、struts2にいわば「パッチを当てた」状態になるわけです。


開発中はコレを使ってデバッグが容易な状態にしておき、リリース時には除去するという運用にすれば、より安心かもしれません。

(更に追記)

(2)の方法はこちらに載せました。
(CGLIBじゃなくてJavassistを使ってます)