CakePHPのURLエンコード

CakePHPではapp/webroot/.htaccess

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]

という記述があり、オリジナルのURLを$_GET['url']にいれた状態でRouterに渡す。
ここで、問題が発生するのがオリジナルのURLに+や%40などのエスケープされた文字が入っていた場合、mod_rewrite上で案エスケープされてしまう為、元の文字列が取得できない。


もっともシンプルな解決法はApacheの2.2.6以降に準備されているRewrite RuleのBフラグ。

'B' (escape backreferences)
Apache has to unescape URLs before mapping them, so backreferences will be unescaped at the time they are applied. Using the B flag, non-alphanumeric characters in backreferences will be escaped. For example, consider the rule:

RewriteRule ^(.*)$ index.php?show=$1
This will map /C++ to index.php?show=/C++. But it will also map /C%2b%2b to index.php?show=/C++, because the %2b has been unescaped. With the B flag, it will instead map to index.php?show=/C%2b%2b.

This escaping is particularly necessary in a proxy situation, when the backend may break if presented with an unescaped URL.

とあるように、先ほどの.htaccessの最後の行を

RewriteRule ^(.*)$ index.php?url=$1 [QSA,L,B]

とすればエスケープしてくれる。


残念ながらApahceを2.2.6以上にできない場合はhttpd.confを触れれば
下記の様な解決法も。
httpd.confで

RewriteLock /tmp/httpd.rewrite.lock
RewriteMap urlencode prg:/etc/httpd/conf.d/bin/urlencode.pl

としておいて.htaccess

 RewriteRule ^(.*)$ index.php?url=${urlencode:$1} [QSA,L]

RewriteMapで外部スクリプトエンコードさせるわけです

今回は手っ取り早く下記Perlスクリプトを試しましたが速度的にはオススメできません

#!/usr/bin/perl
$| = 1;
while (<STDIN>) {
  s/\n$//;
  s/([^\w ])/'%'.unpack('H2', $1)/eg;
  tr/ /+/;
  print "$_\n";
}