- 記事一覧 >
- ブログ記事
Apache mod_rewriteのRewriteRule日本語問題
はじめに
文字コード SJIS(Shift-JIS)で動作する FreeBSD のレガシーシステムのサイトにRewriteRule "^/日本語パス/(.*)" "/test.php?path=/日本語パス/" [L]
のような mod_rewrite を使った転送を行う設定を書いていたのですが、一部の日本語はうまくマッチしないことが分かりました。
今回、これを解決しましたので、紹介していきたいと思います。
解決方法は、SJIS 以外にも、UTF-8、記号にも使えます。
httpd.conf は、SJIS にして記入しています。日本語パス
の文字は、SJIS で、アクセスするときは、/%93%FA%96%7B%8C%EA%83p%83X/
のように URL エンコードされています。
設定の意味は、"^/日本語パス/(.*)"
にマッチしたら、"/test.php?path=/日本語パス/"
に読み替えるという意味です。
【検証環境】
FreeBSD13
httpd-2.4.53
php-5.6.40
この記事の内容は、上記環境でしか確認していません。
原因
うまくマッチしない原因は、SJIS の場合、一部の文字の文字コードに円記号(¥)の文字コード 5C
が含まれているからです。5C
が含まれているのは、以下のような文字になります。(一部です。)
文字 | 文字コード |
---|---|
構 | 8D5C |
十 | 8F5C |
申 | 905C |
能 | 945C |
"^/申請機能構成/(.*)" "/test.php?path=/申請機能構成/" [L]
などとしたら、5C
が3つ含まれていることになります。
解決方法
● 左側"^/申請機能構成/(.*)"
の部分を以下の法則で文字コードにエンコードするとうまくいきました。
1.URL エンコードし、%
の文字を ¥x
にする。
例:あ
→ %82%A0
→ ¥x82¥xA0
2.文字コードのアルファベットは、原則小文字(下記4のケース以外)。
例:あ
→ ¥x82¥xA0
→ ¥x82¥xa0
3.%5C
(SJIS で言う¥記号)部分は、¥x5c
ではなく、¥¥
としなくてはいけない。
例:性能
→ %90%AB%94%5C
→ ¥x90¥xab¥x94¥¥
4.開 = %8AJ
などの%が2つ現れないタイプの文字は、末尾の大文字小文字を維持しないといけない。(%8Aj
にあたる文字が別に存在している。)
例:%8AJ
→ ¥x8aJ
5.ABCDefg012 といった半角英数は、そのままで良い。
このルールに従うと、申請機能構成
は、RewriteRule "^/¥x90¥¥¥x90¥xbf¥x8b¥x40¥x94¥¥¥x8d¥¥¥x90¥xac/(.*)"
でマッチに成功します。
● 右側
マッチしたとしても今度は、右側の"/test.php?path=/申請機能構成/" [L]
で
php が $path
= 申請機能構成
を受け取るはずが、文字化けした文字列を受け取ります。
これの原因も 5C
です。
文字化けの回避方法ですが、5C
が現れる場所を知っていれば、回避できます。
申請機能構成は、
文字 | 文字コード |
---|---|
申 | 905C |
請 | 90BF |
機 | 8B40 |
能 | 945C |
構 | 8D5C |
成 | 90AC |
のため、
申の後半、能の後半、構の後半に 5C
があります。
そのため、
申の後半、能の後半、構の後半に ¥
を書くと、文字化けを回避できます。
つまり、以下の設定の場合、うまくいきます。
RewriteRule "^/\x90\\\x90\xbf\x8b\x40\x94\\\x8d\\\x90\xac/(.*)" "/test.php?path=/申\請機能\構\成/" [L]
ただ、この回避方法の場合、面倒ですので、URL エンコードしても回避できます。
ただし、URL エンコード結果の %
を ¥%
とします。
右側のルールは、それだけになります。
つまり、以下の設定の場合、うまくいきます。
RewriteRule "^/\x90\\\x90\xbf\x8b\x40\x94\\\x8d\\\x90\xac/(.*)" "/test.php?path=\%90\%5C\%90\%BF\%8B\%40\%94\%5C\%8D\%5C\%90\%AC" [L]
正しい設定を出力する php
マッチさせたい日本語パスがたくさんあると、人力で設定してては大変なので、正しい設定を出力する PHP プログラムを作成しました。
PHP のインストールが必要です。
<?php
$folders = file_get_contents("folder.list");
$folders = str_replace(array("\r\n", "\r", "\n" ), "\n", $folders);
$folders = explode("\n", $folders);
foreach ($folders as $folder) {
if (empty($folder) && $folder !== "0") {
continue;
}
$str = $folder;
$conv = "";
while ($len = mb_strlen($str, "CP932")) {
$hitomoji = mb_substr($str, 0, 1, "CP932");
if (!preg_match('/^[a-zA-Z0-9]$/', $hitomoji)) {
$urlenc = rawurlencode($hitomoji);
if (substr_count($urlenc, "%") > 1) {
$apache_chr = str_replace('\\x5c', '\\\\', strtolower(str_replace("%", '\\x', $urlenc)));
} else {
$urlenc_mae = substr($urlenc, 0, strlen($urlenc)-1);
$urlenc_ushiro = substr($urlenc, -1);
$apache_chr = str_replace('\\x5c', '\\\\', strtolower(str_replace("%", '\\x', $urlenc_mae)));
$apache_chr .= $urlenc_ushiro;
}
$conv .= $apache_chr;
} else {
$conv .= $hitomoji;
}
$str = mb_substr($str, 1, $len, "CP932");
}
$str = $folder;
$enc = rawurlencode($str);
$conv2 = "";
while ($len = mb_strlen($str, "CP932")) {
$hitomoji = mb_substr($str, 0, 1, "CP932");
if (!preg_match('/^[a-zA-Z0-9]$/', $hitomoji)) {
$urlenc = rawurlencode($hitomoji);
$apache_chr = str_replace("%", '\\%', $urlenc);
$conv2 .= $apache_chr;
} else {
$conv2 .= $hitomoji;
}
$str = mb_substr($str, 1, $len, "CP932");
}
//print("curl -k https://localhost/$enc/" . "\n");
print("RewriteRule \"^/$conv/(.*)\" \"/test.php?path=$conv2\" [L] \n");
}
使い方は、以下のようにカレントディレクトリの folder.list
にフォルダ名を書いて、実行するだけです。
この例の場合、
# vi folder.list
の文字コード設定は、SJISとします。
# vi folder.list
申請機能構成
あ
性能
# php convert.php
RewriteRule "^/\x90\\\x90\xbf\x8b\x40\x94\\\x8d\\\x90\xac/(.*)" "/test.php?path=\%90\%5C\%90\%BF\%8B\%40\%94\%5C\%8D\%5C\%90\%AC" [L]
RewriteRule "^/\x82\xa0/(.*)" "/test.php?path=\%82\%A0" [L]
RewriteRule "^/\x90\xab\x94\\/(.*)" "/test.php?path=\%90\%AB\%94\%5C" [L]
出力された内容を apache の conf に設定して、以下のように確認できます。
# vi /usr/local/apache2/htdocs/test.php
<?php
print_r($_GET);
# curl -k https://localhost/%90%5C%90%BF%8B%40%94%5C%8D%5C%90%AC/
Array
(
[path] => /申請機能構成/
)
# curl -k https://localhost/%90%AB%94%5C/
Array
(
[path] => /性能/
)
UTF-8、記号について
UTF-8、記号についても同じように変換するとうまくいきます。
ただし、UTF-8 については、変換しなくても直に書いてもうまくいきます。
RewriteRule "^/申請機能構成/(.*)" "/test.php?path=/申請機能構成/" [L]
でも良いということです。
記号の場合、/
= %2F
が使われていると、404
が返りましたが、それ以外の記号でうまくいきました。
ログを見ると、以下のログ出力になっていました。[core:info] [pid 1522:tid 34398206464] [client 127.0.0.1:49411] AH00026: found %2f (encoded '/') in URI path (/%21%23%24%25%26%27%28%20%29%2B-.%3D%40%5B%5D%5E_%60%7B%7D~%5C%2F%3A%2C%3B%2A%3F%22%3C%3E%7C), returning 404
https://localhost/!#$%&'( )+-.=@[]^_`{}~\:,;*?"<>|/
をリクエストしてみました。
RewriteRule "^/\x21\x23\x24\x25\x26\x27\x28\x20\x29\x2B-.\x3D\x40\x5B\x5D\x5E_\x60\x7B\x7D~\x5C\x3A\x2C\x3B\x2A\x3F\x22\x3C\x3E\x7C/(.*)" "/test.php?path=\%21\%23\%24\%25\%26\%27\%28\%20\%29\%2b-.\%3d\%40\%5b\%5d\%5e_\%60\%7b\%7d~\%5c\%3a\%2c\%3b\%2a\%3f\%22\%3c\%3e\%7c" [L]
# curl -k https://localhost/%21%23%24%25%26%27%28%20%29%2B-.%3D%40%5B%5D%5E_%60%7B%7D~%5C%3A%2C%3B%2A%3F%22%3C%3E%7C/
Array
(
[path] => !#$%&'( )+-.=@[]^_`{}~\:,;*?"<>|
)
検証環境構築手順
この記事用に FreeBSD13 に最初から検証環境を構築しました。 httpd-2.4.53、php-5.6.40 をインストールして全ての SJIS 文字でこの記事の内容は正しいことを確認しました。
検証環境構築手順は、以下です。
「FreeBSD-13.0-RELEASE-amd64 を VMware-workstation-12.5.5 にインストール」に従って、FreeBSD13 をインストール直後からのスタートです。
# tar zxf perl-5.34.1.tar.gz
# cd perl-5.34.1
# ./Configure -des -Dprefix=$HOME/localperl
# make && make install
# cd ../
# tar zxf apr-1.7.0.tar.gz
# tar zxf apr-util-1.6.1.tar.gz
# tar zxf pcre-8.45.tar.gz
# tar zxf httpd-2.4.53.tar.gz
# cd apr-1.7.0/
# ./configure && make && make install
# cd ../
# tar zxf expat-2.4.8.tar.gz
# cd expat-2.4.8
# ./configure && make && make install
# cd ../apr-util-1.6.1/
# ./configure --with-apr=../apr-1.7.0 && make && make install
# cd ../pcre-8.45/
# ./configure && make && make install
# cd ../httpd-2.4.53/
# ./configure --enable-rewrite --enable-so --enable-ssl && make && make install
# cd ../
ここで、pkg installでインターネット接続が必要なため、プロキシサーバーを利用
【Bシェル系の場合】
# export FTP_PROXY="http://192.168.0.158:3128"
# export HTTP_PROXY="http://192.168.0.158:3128"
# export HTTPS_PROXY="http://192.168.0.158:3128"
【Cシェル系の場合】
# setenv FTP_PROXY "http://192.168.0.158:3128"
# setenv HTTP_PROXY "http://192.168.0.158:3128"
# setenv HTTPS_PROXY "http://192.168.0.158:3128"
# pkg install libxml2
# tar zxf php-5.6.40.tar.gz
# cd php-5.6.40
# ./configure --with-apxs2=/usr/local/apache2/bin/apxs --enable-mbstring && make && make install
# cd ../
# tar zxf curl-7.83.1.tar.gz
# cd curl-7.83.1
# ./configure --with-openssl && make && make install
# vi /usr/local/apache2/htdocs/test.php
<?php
print_r($_GET);
# vi /usr/local/apache2/conf/httpd.conf
AddType application/x-httpd-php .php
追加
LoadModule ssl_module modules/mod_ssl.so
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
Include conf/extra/httpd-ssl.conf
コメントを外す
# openssl genrsa -out /usr/local/apache2/conf/server.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
................................................................................................................................................+++++
.............................+++++
e is 65537 (0x010001)
# openssl req -new -key /usr/local/apache2/conf/server.key -out ca.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Aichi
Locality Name (eg, city) []:Toyota
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:test.itccorporation.jp
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
# echo "subjectAltName=DNS:*.itccorporation.jp,IP:192.168.12.203" > san.txt
# openssl x509 -req -days 365 -in ca.csr -signkey /usr/local/apache2/conf/server.key -out /usr/local/apache2/conf/server.crt -extfile san.txt
Signature ok
subject=C = JP, ST = Aichi, L = Toyota, O = Default Company Ltd, CN = test.itccorporation.jp
Getting Private key
# chmod 600 /usr/local/apache2/conf/server.key /usr/local/apache2/conf/server.crt
# /usr/local/apache2/bin/apachectl start
# curl -k "https://localhost/test.php?path=test"
Array
(
[path] => test
)
# vi /usr/local/apache2/conf/httpd.conf
LoadModule rewrite_module modules/mod_rewrite.so
コメントを外す
# vi /usr/local/apache2/conf/extra/httpd-ssl.conf
RewriteEngine on
RewriteRule "^/aaa/(.*)" "/test.php?path=/aaa/" [L]
</VirtualHost>
# curl -k https://localhost/bbb/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body></html>
# curl -k https://localhost/aaa/
Array
(
[path] => aaa
)
文字コード設定
【Bシェル系の場合】
# export LC_ALL=ja_JP.SJIS
# export LANGUAGE=ja_JP.SJIS
# export LANG=ja_JP.SJIS
【Cシェル系の場合】
# setenv LC_ALL ja_JP.SJIS
# setenv LANGUAGE ja_JP.SJIS
# setenv LANG ja_JP.SJIS
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。