1. 記事一覧 >
  2. ブログ記事
category logo

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 があります。
そのため、
申の後半、能の後半、構の後半に ¥ を書くと、文字化けを回避できます。


つまり、以下の設定の場合、うまくいきます。

例(日本語はSJISとします。)
RewriteRule "^/\x90\\\x90\xbf\x8b\x40\x94\\\x8d\\\x90\xac/(.*)" "/test.php?path=/申\請機能\構\成/" [L]

ただ、この回避方法の場合、面倒ですので、URL エンコードしても回避できます。
ただし、URL エンコード結果の %¥% とします。
右側のルールは、それだけになります。


つまり、以下の設定の場合、うまくいきます。

例(日本語はSJISとします。)
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 のインストールが必要です。

convert.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
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
test.php
<?php
print_r($_GET);
# vi /usr/local/apache2/conf/httpd.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
httpd.conf
LoadModule rewrite_module modules/mod_rewrite.so
コメントを外す
# vi /usr/local/apache2/conf/extra/httpd-ssl.conf
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
loading...