PHPの内部エンコーディングでUTF-8を使う

PHPの内部エンコーディングでUTF-8を使う

第四企画 坂井 潔

 

日本語などASCII以外の文字集合を使う場合は、そのシステムでどの文字セットをメインに使用するか、ということが最初に決めるべき事項の1つです。代表的なものにShift_JIS(SJIS、SJIS-win)、EUC-JP(eucJP-win)、UTF-8、UTF-16などがあります。

ただShift_JISなど、バックスラッシュ(≒エスケープ記号)が通常の文字の2バイト目として含まれてれる可能性のある文字エンコーディングでは、正規表現関数(preg_*** あるいは eregなど)や、マルチバイト文字列関数(mb_***)などの関数が正しく動かないケースや、各種エスケープ処理が意図した通りには動作してくれないことが多々あります。

また、PHPの内部エンコーディングをPostgreSQLで使用する文字セットと揃えておくことで、変換にかかるサーバ上のコストや変換で生じる脆弱性を低減することができます。ですので(PostgreSQLのサーバ側で使用できないUTF-16を除くと)可能なかぎりUTF-8を、少なくともEUC-JPを使い、PostgreSQLサーバの文字セットと揃えるようにしましょう。

※使用しているPHPでサポートする文字エンコーディングはmb_list_encodings関数で確認できます。
※PostgreSQLサーバとしてサポートされる文字セットは、この表の「サーバ?」が「はい」の文字セットです。

それぞれの文字コードの確認・設定方保

PostgreSQL / PHP / ブラウザ(ユーザエージェント)それぞれでの文字コードの設定方法や確認方法は以下のとおりです。

  PostgreSQL PHP User Agent(ブラウザ)
確認 $ psql -l
psql内で=> \l
SQLとして SHOW server_encoding;
組み込み関数 mb_internal_encoding(); 直接HTMLのソースを(表示のエンコーディングを変えて)見ることで確認
設定 createdb時に-Eオプションで設定 php.ini / .htaccess / 組み込み関数などで設定

例)

  • php_value mbstring.internal_encoding UTF-8
  • mb_internal_encoding('UTF-8');
など
HTTPヘッダにContent-Type: text/html; charset=****で指定
もしくは同等のmetaタグで指定

 

PHPでのUTF-8設定方法

PHPで内部エンコーディングやブラウザ(ユーザエージェント)への出力の両方でUTF-8を使用する場合には、例えば以下のような設定ができます。

# php.iniなどの場合
mbstring.http_input = UTF-8
mbstring.encoding_translation = On
mbstring.internal_encoding = UTF-8
mbstring.substitute_character = "?"
default_charset = UTF-8
# .htaccessなどの場合
php_value mbstring.http_input UTF-8
php_flag mbstring.encoding_translation On
php_value mbstring.internal_encoding UTF-8
php_value mbstring.substitute_character "?"
php_value default_charset UTF-8

http_inputに想定できる文字エンコーディングを、encoding_translationにOnを設定することで、PHPのスクリプトにパラメータが渡される前に、internal_encodingで設定したエンコーディングに変換してくれます。上記の設定では、入力時に一旦UTF-8からUTF-8に変換され、その際にUTF-8として無効な文字列を"?"に置き換えてくれるので、セキュリティ的に危険な文字列が混入せず安心です。

EUC-JPで統一したければ、http_input、internal_encodingに「EUC-JP」あるいは「eucJP-win」を、default_charsetに「EUC-JP」を設定します。

また、ここで設定した内部エンコーディング(internal_encoding)とPostgreSQLサーバの文字セットを揃えておきましょう。

※default_charsetは、単純にHTTPヘッダ"Content-Type: text/html; charset=****"の****の部分に埋め込まれる文字列です。PHP内部エンコーディングではないので気をつけましょう。またこの項目はHTMLヘッダには必ずIANAに登録されている文字セット(「UTF-8」「EUC-JP」「Shift_JIS」など)を指定してください。「utf8」「eucJP-win」「SJIS」などは文字化け、あるいはセキュリティホールの原因になり得ます。

古い携帯端末など、どうしてもSJISで出力しなくてはならないユーザエージェントには、php.iniや.htaccessなどで、例えば以下のような設定をしてください。

# php.iniなどの場合
mbstring.http_input = UTF-8,SJIS-win
mbstring.encoding_translation = On
mbstring.internal_encoding = UTF-8
mbstring.substitute_character = "?"
output_handler = "mb_output_handler"
# .htaccessなどの場合
php_value mbstring.http_input UTF-8,SJIS-win
php_flag mbstring.encoding_translation On
php_value mbstring.internal_encoding UTF-8
php_value mbstring.substitute_character "?"
php_value output_handler "mb_output_handler"

上記の例ではUTF-8に変換され、PHPスクリプト内部はこの文字エンコーディングで動作します。ユーザエージェントや$_SERVER['HTTP_ACCEPT_CHARSET']の値などを判定し、UTF-8が解せるブラウザであればUTF-8で、どうしてもShift_JISの必要があれば、SJIS-winに変換して出力してください。

<?php

//出力文字エンコーディング判定処理

if($must_be_sjis) {
    mb_http_output('SJIS-win');
    ob_start('mb_output_handler'); // php.ini、.htaccessに移動
    header('Content-Type: text/html; charset=Shift_JIS');
    // または
    header('Content-Type: application/xhtml+xml; charset=Shift_JIS');
} else {
    mb_http_output('UTF-8');
    ob_start('mb_output_handler'); // php.ini、.htaccessに移動
    header('Content-type: text/html; charset=UTF-8');
}

//実際の出力処理

?>

ただし上記の例では、PHPによる入力パラメータの文字エンコーディングの自動判定が行われるため、判定ミスの可能性も否めません。もし可能であれば、SJIS専用のディレクトリなどを作りhttp_inputやdefault_charsetをSJIS-win、Shift_JISに固定してしまうもの1つの方法です。あるいは、入力パラメータの変換に関する設定はせず、ユーザエージェントや$_SERVER['HTTP_ACCEPT_CHARSET']の値などを判定した後に、手動で変換するという方法もあります。

※絵文字のマッピングはUTF-8とShift_JISとでは異なっていることが多いようです。あるいは単純に、各社キャリアへの絵文字の相互変換などが必要な場合もあるでしょう。このような場合は、文字エンコーディングに気をつけて変換を行ってください。
※PHP内部で、テキストファイルの読み込みや、外部URLへのアクセス、あるいは非常に古いバージョンのPostgreSQLへのアクセスなど、UTF-8として妥当でない文字列が読み込まれる可能性がある場合、XSSの脆弱性が発生し得ます。そのような時は、mb_check_encoding関数でのチェックや、mb_convert_encoding関数などでの変換を行ってください。

(2011年5月2日 公開)
(2012年1月14日 修正)