2008-01-18 [長年日記]

[Q3] @Messagesの仕様をちょっと変更

@Messagesがバックグラウンドスレッドから(つまり自動振り分けから)呼び出された場合に、デッドロックを回避するために、コンテキストアカウント以外のアカウントのメッセージを取得できないようにしました。

mod_rewriteで置き換えた文字列をエスケープする

mod_rewriteで以下のようなルールを定義してパスをクエリパラメータに置き換えたとします。

RewriteRule ^(.*)$ test.cgi?value=$1 [L]

ここで、http://example.com/Test%2BTestにアクセスするとCGI側からvalueの正しい値が取れず、「Test Test」になってしまいます。これは、mod_rewriteがマッチさせる前にURLを「Test+Test」にデコードするためで、この結果$1には「Test+Test」が入ります。さらに、CGI側でデコードするために「Test Test」になるわけです。

他にも、%や&など予約済み文字を含む文字列を渡すと同様の現象が起こります。Apache 2.2.6以降ならば、Bフラグを指定するとマッチした文字列をエンコードして渡してくれるようなので問題ないようです(未確認)。例えば、以下のようにします。

RewriteRule ^(.*)$ test.cgi?value=$1 [L,B]

2.2.6より前のバージョンにはBフラグがないため他の方法を取る必要があります。Webサイトを探してみると、int:escapeを使う方法が書かれていることがありますが、int:escapeは予約済み文字をエスケープしないので実際には役に立ちません。以下はうまくいかない例です。

RewriteMap escape int:escape
RewriteRule ^(.*)$ test.cgi?value=${escape:$1} [L]

int:escapeが使えないために自分で関数を用意する必要があります。関数は、標準入力から入力を受け取って標準出力に出力するプログラムとして記述します。詳細は、RewriteMapのドキュメントの、External Rewriting Programに書かれています。

例えば、Haskellで書くとこんな感じです。

module Main (main) where

import Data.Char (isAlphaNum, isAscii)
import Network.URI
import System.IO
import System.IO.Error

main :: IO ()
main = do
    mapM_ (flip hSetBuffering LineBuffering) [stdin, stdout]
    process `catch` handler
 where
     handler e | isEOFError e = return ()
               | otherwise    = ioError e

process :: IO ()
process = getLine >>= putStrLn . escapeURIComponent >> process

escapeURIComponent :: String -> String
escapeURIComponent = escapeURIString (\c -> isAlphaNum c && isAscii c)

EOFが渡されるまで、標準入力から一行読み込み、変換して標準出力に一行書き出します。

そして、mod_rewrite側の設定をします。

RewriteEngine on
RewriteLock /tmp/rewrite.lock
RewriteMap escape prg:/home/foo/bin/escape
RewriteRule ^(.*)$ test.cgi?value=${escape:$1} [L]

ちなみに、RewriteLockはバーチャルホストの設定の外側に書く必要があります。また、RewriteMapはディレクトリの設定の外側に書く必要がありますが、その前にRewriteEngineを置かないと反映されません。

参考: RE: [users@httpd] mod_rewrite Special % character problems

[]

トップ «前の日記(2007-12-31) 最新 次の日記(2008-02-01)»