Microsoft が提供している IIS 7.x 用の URL Rewrite 2.0 モジュール を使用しての日本語の書き換えがうまくいかないという話です。
なお、この問題は globalization 要素の requestEncoding 属性を Shift_JIS に設定した場合に限られます。デフォルトの UTF-8 の場合は問題ありません。
例えば、以下のように、日本語を使用した URL を書き換えるケースを考えます。
http://host/dir/あ ⇒ http://host/default.aspx?n=あ
その場合、URL Rewrite 2.0 では URL 書き換えルールは以下のようになります。
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Japanese Query String Test">
<match url="dir/(.+)$" />
<action
type="Rewrite"
url="default.aspx?n={R:1}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
上記で、{R:1} というのは正規表現の後方参照から取得される文字列で、パターン dir/(.+)$ ではカッコで囲った部分になります。例えば、"http://host/dir/xxx" という入力なら {R:1} に該当するのは "xxx" になります。(ちなみに、{R:0} は "dir/xxx" になります)
ブラウザが要求を出す際、先の記事 ブラウザによる URL のエンコーディング で書きましたように、URL を UTF-8 として URL エンコーディングします。
"あ" の UTF-8 でのバイト列は E3 81 82 ですので、"http://host/dir/あ" は "http://host/dir/%E3%81%82" にエンコーディングされてからサーバーに送信されます。
URL Rewrite モジュールでは、"http://host/dir/%E3%81%82" という入力から、正規表現パターン dir/(.+)$ の後方参照として "%E3%81%82" を取得し、それをデコーディングした文字列が {R:1} に渡される仕組みになっています。(ソースコードを見たわけではなく、検証結果での確認ですが)
問題はその際の Encoding の判定です。
requestEncoding 属性が UTF-8(デフォルト)であれば、使用されている Encoding は UTF-8 と判定され、書き換え後の URL は正しく "default.aspx?n=あ" となります。
しかし、requestEncoding 属性が Shift_JIS に設定されていると、"%E3%81%82" の Encoding は Shift_JIS と判定され、書き換え後の URL は "default.aspx.aspx?n=縺・" とクエリ文字列の部分が文字化けしてしまいます。("縺・" は Shift_JIS のバイト列で E3 81 81 45 になります)
requestEncoding 属性が Shift_JIS でこの問題を避ける方法はあるでしょうか?
試しに、"http://host/dir/あ" で "あ" の部分を Shift_JIS の URL エンコード、即ち "http://host/dir/%82%A0" としてブラウザのアドレスバーに入力してみました。
その結果が一番上の画像なのですが、結果は同じように文字化けしてしまい、やっぱりダメでした。
ブラウザからサーバーに送信される URL は "http://host/dir/%82%A0" となりますが(Fiddler2 で確認)、サーバーが受信して URL Rewrite モジュールに渡す時に "%82%A0" が "%E3%81%82" になってしまうようです。(上の画像の X-Original-URL 参照)
というわけで、Microsoft の IIS 7.x 用 URL Rewrite 2.0 モジュールを使う際は、requestEncoding 属性を UTF-8(デフォルト)に限定した方がよさそうです。
UTF-8 なら "http://host/dir/%82%A0" という Shift_JIS で URL エンコーディングされた URL も正しく "default.aspx?n=あ" に書き換えられます。
参考までに検証に使った aspx ページ(0024-QueryString.aspx)を以下に載せておきます。
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
Encoding enc = Request.ContentEncoding;
Label1.Text = "Request.ContentEncoding.EncodingName: " +
enc.EncodingName;
Label2.Text = "X-Original-URL: ";
string[] values =
Request.Headers.GetValues("X-Original-URL");
if (values != null)
{
string url = values[0];
Label2.Text = Label2.Text + url;
Label2.Text = Label2.Text + " (Url-decoded: " +
HttpUtility.UrlDecode(url, enc) + ")";
}
Label3.Text = "Request.RawUrl: " + Request.RawUrl;
Label4.Text = "Request.Url.OriginalString: " +
Request.Url.OriginalString;
string queryString = Request.QueryString["n"];
Label5.Text =
"Request.QueryString[\"n\"]: " + queryString;
if (queryString != null)
{
string s = " (byte array: ";
byte[] b = enc.GetBytes(queryString);
for (int i = 0; i < b.Length; i++)
{
s = s + String.Format("[{0:X2}]", b[i]);
}
Label5.Text = Label5.Text + s + ")";
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="Label1" runat="server"></asp:Label>
<br />
<asp:Label ID="Label2" runat="server"></asp:Label>
<br />
<asp:Label ID="Label3" runat="server"></asp:Label>
<br />
<asp:Label ID="Label4" runat="server"></asp:Label>
<br />
<asp:Label ID="Label5" runat="server"></asp:Label>
</div>
</form>
</body>
</html>