VOYAGE GROUP エンジニアブログ

voyagegroup_techのブログ
VOYAGE GROUPエンジニアブログです。

2010年05月

あなたにも今日こそPerlの文字化けが理解できるたった一つの原則

こんにちは。ECナビラボの須藤(@ajiyoshi)と申します。

唐突です が、Perl(5.8以降)で文字化けに悩んだことはありませんか?私はあります。

そして、よくわからないまま"Perl 文字化け"などで検索すると、次のような単語が洪水のごとく溢れていて、なんだかよく分からなかったりしないでしょうか。

  • 'flagged UTF8'
  • Encode::decode
  • Encode::decode_utf8
  • Encode::encode
  • Encode::encode_utf8
  • 'Wide character in print at hoge.pl line xx.'
  • use utf8
  • open my $fh, '<:encoding(utf8)', $file
  • binmode
  • 「入り口で decode、内部でflagged UTF8、出口でencode」
  • utf8::is_utf8
  • utf8::upgrade
  • utf8::downgrade
  • utf8::encode
  • utf8::decode

さらになんだかよくわからないままEncode::decodeしたりEncode::encodeしたりutf8::is_utf8で調べた りuse utf8をつけたりはずしたりしてるうちになんか動くようになったりして分からないままそのままにしていませんか?

この記事は そういう方のために書かれています。今回こそは、ちゃんと理解してみませんか。

内部文字列とUTF8バイナリは違う

Perl で文字化けしないために必要なことは、ほぼこの原則だた1点に集約されます。

内部文字列とUTF8バイナリをきちんと区別する

「ちょっと待って!」あなたは言います。「その内部文字列とかUTF8バイナリとかってのは一体何?」

この文書で使う用語の定 義:

内部文字列
内部文字列とはPerlが内部的に保持している文字列の表現のこと。普通は'flagged UTF8'と呼ばれる。
UTF8バイナリ
UTF8バイナリとは文字列をUTF8エンコーディングにより表現したバ イナリ列のこと。

Perlの内部文字列の表現としてUTF8が採用されていることからか、ユニコード文字列として特別扱いを 受ける内部文字列は'flagged UTF8'と呼ばれます。特にPerlのユニコード事情に詳しくない人にとって、この呼び方はエンコーディ ング方式としてのUTF8そのものや、UTF8 エンコードされたバイナリ列と混同しやすいと私は思います。その上、Perlが内 部文字列をどのように表現しているのかについて、ほとんどの場合 プログラマが知る必要はないのです。無用な混乱を避けるため、この文書では「内部文字列」と呼ぶことにします。

内部文字列とUTF8バイ ナリは全く異なります。Perlはいまやユニコードを知っており、内部文字列の文字境界を知っています。length 関数に内部文字列を渡すと、文字列に含まれる文字の数が返ります。一方でlength関数にUTF8バイナリを渡すと、それがバイナリとして何バイトで表 現されるかを返します。例えば「ECナビ」は4文字ですが、それをUTF8で表現すると8バイト必要になります。

内部文字列とUTF8 バイナリは比較できません。論理的には同じ文字列を表現しようとしている内部文字列とUTF8バイナリを eq で比較しても結果は偽です。

内 部文字列とUTF8バイナリを結合しようとするのもよくないアイディアです。違うものなのだから当然ですね。sjisなデータとeuc-jp なデータをナイーブに結合しようとするとどうなるでしょうか?普通は文字化けします。内部文字列とUTF8バイナリを結合しようとすると同じように文字化 けします。おそらくあなたが目にするPerlの文字化けの半分くらいはこれが原因です。

Encode::decode - 内部文字列を作り出す

ではその内部文字列というのはどうすれば得られるのでしょうか。

それには Encode::decodeを使います。

CやJava風の型記法でシグネチャを表現すると次のようになります。

 String Encode::decode(Encoding enc, byte[] bin);
String Encode::decode_utf8(byte[] bin);

文字コードencでエンコーディングされたバイナリ配列binから、内部文字列を作り出すのがdecode()の役割です。あらかじめエンコー ディングがUTF8であると決まりきっている場合もあるため、UTF8バイナリから内部文字列を作り出す decode_utf8 も用意されています。

 use strict;
use warnings;
use Encode qw(decode_utf8);

my $binText = "ECナビ";
my $strText = decode_utf8($binText);

warn length($binText);
warn length($strText);

このコードをUTF8で保存して実行すれば、すでに述べたlengthの挙動を確かめることができます。

Encode::encode - 内部文字列を特定のエンコーディングのバイナリ表現に変換する

内部文字列というのはPerl内部でのお話です。現実の要件では EUC-JPやshiftjis、UTF8など、特定のエンコーディングを使っ てテキストを書き出したり、DBに格納する必要があるでしょう。これにはEncode::encodeを使います。

 byte[] Encode::encode(Encoding enc, String str);
byte[] Encode::encode_utf8(String str);

UTF8で書いたコードで、出力はEUC-JPで行う例を示します。

 use strict;
use warnings;
use Encode qw(decode_utf8 encode);

my $binText = "ECナビ";
my $strText = decode_utf8($binText);

#出力はEUC-JPにしたい
print encode('euc-jp', $strText);

use utf8 とは何か。

use utf8とは何でしょうか?Perlのドキュメントには「スクリプトがUTF8で書かれていることをPerlに伝えるためだけに使え」とかスピリチュアル なことが書いてあります。

そういうものだとして、Perlにそれを伝えるとPerlはどう振舞うのでしょうか?

大きく 2点、おまけが1点あります。

  • 文字列リテラルはもはやバイナリ列ではなく内部文字列を作り出す
  • 正規表現リテラルが ユニコードを認識する
  • (おまけ)変数名などの識別子にユニコードを使える
  •  use strict;
    use Encode;

    #こいつはUTF8バイナリ
    my $binPrice = '1,234円';

    {
    use utf8;
    # 文字列リテラルは内部文字列を作り出す
    my $strPrice = '1,234円';
    # 正規表現リテラルがユニコードを認識する
    $strPrice =~ tr/0-9,/0-9,/;
    print Encode::encode_utf8($strPrice);
    }

    おまけについては特に述べません。そういうのを好む人もいれば、忌み嫌う人もいるでしょうから。

    出力ではエンコーディングを明 示しよう

    内部文字列とUTF8バイナリは違うことを覚えたあなたは文字化けを見ることも減り、use utf8 を使えばいちいちdecodeしなくても内部文字列を得られてだいぶ幸せになりました。

    ところがPerlがこんな文句を言う事があるのに 気づきました。この警告は何を意味しているのでしょうか?

    'Wide character in print at hoge.pl line xx.'

    これは、典型的には内部文字列をそのままprintに渡した場合に起きる警告です。

     use utf8;
    #'Wide character in print at hoge.pl line xx.'
    print '代表取締役丹野修一';

    内部文字列をPerlがどのように表現しているかについて、あなたが(賢明なことに)何も知らないのであれば、こんなことをしようとは思わない でしょう。だってuse utf8しているあなたのコードでは、いまや文字列リテラルは内部文字列であり、それが物理的にどういう具合に表現されているか知らないのだから、そのま まprintしたところでそれをコンソールとかDBとかブラウザとかの他の誰かが読むことができるなんてありえそうもないことです。

    書 き出すというのは、誰かに読んでもらうことを期待しているのだから、あなたと読み手との間で合意が取れているその文字エンコーディングを Perlに教えてあげるべきです。

     use utf8;
    use Encode;
    #UTF8でエンコーディングすることになっている
    print Encode::encode_utf8('代表取締役丹野修一');

    何も指定しなければ、readやprintや<STDIN>などはバイナリのまま読み書きしようとします。つまり、read や<$fh>は(内部文字列ではなく)バイナリ列を返すし、printには適切なエンコーディングで符号化されたバイナリ列を渡すべきです。 そうじゃないと、10年前に書かれていまや誰も中身を把握していないスクリプト(そいつはエクセルCSVに何かぞっとするようなことをして、データを EUC-JPに変換した上でDBに何かしてるみたいで、もちろんuse strictなんてないし、Jcode.pmがどうとか書いてあります)が、Perlを新しくした瞬間に動かなくなってしまいそうです。

    チー ム全員がPerlの文字列について正しく理解しているのでない限り私はおすすめしませんが、「このファイルハンドルから読み取るデータは必 ずUTF8なので、読み取ったデータを自動的に内部文字列に変換してほしい」「このファイルにはEUC-JPで書き出すので、内部文字列をprintした ら自動的にEUC-JPに変換した上で書き出して欲しい」などとPerlに伝えることができます。

     use strict;
    use warnings;

    my $path = shift;
    #open時にエンコーディングを指定する
    open(my $fh, '<:encoding(utf8)', $path) or die;
    #内部文字列
    my $strLine = readline($fh);
    close($fh);

    STDIN、STDOUTのようなすでに開いているファイルハンドルに同じことをするときは、binmode関数を使います。これもチーム全員 がPerlの文字列について正しく理解しているのでない限り混乱の元だと私は思いますので、サンプルコードは割愛します。

    まとめ

    ここまでくれば、モダンPerlの文字列に関する一般則、

    「入口でdecode、自分のプログラムでは内部文字列を使い、出口で encode」

    の意味も理解できたのではないでしょうか。decodeは文字コードの妥当性を検証した上で内部文字列を作りだしますの で、入り口で decodeしてやれば例えば冗長なUTF8符号化による微妙な脆弱性も防ぐことができます。

    落穂ひろい

    To use utf8 or not utf8.

    あなたはuse utf8すべきでしょうか?

    use utf8の隠れた副作用として、"あなたのチームのPerlの文字列についての理解度を試す"があると私は思います。ちゃんと分かってないと、たいてい嫌 なことがおきます。

    チームのPerl錬度が低いなら、「基本的にはuse utf8せずにどうしても必要な部分だけuse utf8する」というのもひとつのポリシーではないかと思います。

    iPad で「妄撮」をやってみました。

    こんにちは。 ECナビの子会社adingoでエンジニアをしています tatenosystem です。
    圧倒的ダメエンジニアなので、本ブログでは「ダメなネタ担当」になると思います。

    私は、先月アメリカ出張に行ったタイミングで iPad を買ってきました。
    そこでいち早く社内で iPad で遊んでいると、システムの偉い人から「iPadをネタに何かブログ書いて」という ムチャぶり 御神託がありました。

    いい機会なので、HTML5 の canvas を使って iPad向けのページを作ってみようと思います。
    私は「HTML5非対応ブラウザ」が横行しているこの世の中、HTML5 を避けていたのですが
    iPad 専用ページということであれば、HTML5 が必ず使えるので安心です。

    HTML5 canvas と iPad のタッチパネルを使った素敵なネタは……

    妄撮!!!


    グラビア界に革命を起こした「妄撮」を iPad に移植しましょう。
    いや~、我ながら「発想がダメ」ですね。。。
    いつか他の優秀なエンジニアさんに怒られる日がくると思います。

    早速 JavaScript を書いていきます。
    実をいうと似たようなプログラムを大昔 Java Applet で書いたことがあるので、それを移植すればOKです。

    iPad のJSタッチイベントは iPhone とまったく同じです。
    タッチしながら指を動かすと"touchmove"イベントが発生します。
    ですので、以下のようにイベントリスナーに登録します。

      document.addEventListener("touchmove", clearBlock, false);

    iPad 以外の HTML5 対応ブラウザでも利用できるように、通常のマウスイベントも登録しておきます。

     window.onmousemove = clearBlock;
     window.onmousedown = function(){ moveFlag = 1; }
     window.onmouseup = function(){ moveFlag = 0; }


    あとは、自作の clearBlock 関数内でタッチした部分の一部画像を違う画像に置き換えてあげればOKです。

    では、早速動かしてみましょう。
    大昔描いたイラストとか使いたかったのですが、どう考えても会社のブログでは NG なので(^^;)
    我が家のソファに居座っている「物体」に被写体になってもらいました。

    01


    で、さっそくタッチして画像を切り取ってみます。

    02


    おお!ちゃんと「妄撮」できた!

    いかん、面白い!!!


    ちなみに、動画だとこんな感じです。



    有名な絵師がコレやって公開してくれると、一気に iPad が(間違った)普及するかもしれませんねw

    サンプルURL は以下です。
    iPad 及び HTML5対応のブラウザをお持ちの方は是非遊んでみてください。

    http://tatenosystem.com/ipad/canvas/

    なかなか面白かったので、暇を見つけて今後拡張していきます。
    今回は15分位でプログラムを書いたのでかなり適当です。
    (むしろ「ぬいぐるみ」の撮影の方が時間がかかってしまいました)

    次回はもう少し真面目なネタに挑戦します。

    「iPad (Wi-Fi) + Pocket WiFi + Apple Wireless Keyboard + ISSH」でWebアプリケーション開発環境を実現する。

    こんにちは。 ECナビに新しく創設されました株 式会社genesixで プログラマをしています上杉 (@unlearned)です。

    日本でもiPadの発売日が決定!! 早速、私もiPad(Wi-Fi)を予約注文しました。 やがて訪れる便利な生活を妄想して、うずうずしているところです。

    ところで、先日アメリカ出張から帰ってきた同僚が、iPad を手に入れてきました。ECナビには、既に7台iPadがあります。

    プログラマーであればいつでもどこでもアプリ開発をしていたいですよね。 わかります。

    そこで表題にある 「iPad (Wi-Fi) + Pocket WiFi + Appleワイヤレスキーボード + iSSH」 を用いたWebアプリケーション開発環境をご提案いたします。

    ちなみにiPad (3GS)購入の方はPocket WiFiは必要ないです。

    まずはiPadと Apple Wireless Keyboard(US)をBluetooth経由で接続します。

    1. Wireless Keyboardの電源を入れてください。

    2. iPadの [設定] [一般] から [Bluetooth] を選択

    3. Bluetoothを ”オン” にする

    4. デバイスに ”Apple Wireless Keyboard” と表示される

    5. 表示された ”Apple Wireless Keyboard” をクリックしてしばらく待つ

    6. "Apple Wireless Keyboardでパスキー○○××を入力し、retunまたはenterキーを押してください" (○○○×××は6桁の数字)と表示されますので、そのようにします。

    7. ”接続済み” の表示が出る

    接続が完了すると、テキストの入力時にソフトフェアキーボードが表示されなくなります。

    さて、これでキーボードの接続は完了。

    次に必要なのはサーバ接続環境。 ここではiPad版のiSSHを利用します。 お値段は$10と、ちょっと値は張りますが躊躇無く購入します。

    では立ち上げてみましょう。

    ジャーン!!
    写真1

    これでバリバリ開発するぜー!!

    しかし、iSSHではキーボードからのalt、Ctrl、Esc、tabが効かない!!!

    orz . . . .

    他のアプリではちゃんとalt、Ctrl、Esc、tabは使えます。 iSSH の作者の方、ご対応よろしくお願いします。

    ちなみにこのシステムの優位性は重量の軽さ。

    MacBook Air 1360kg (Appleの仕様(*1)より)

    ・Apple Wireless Keyboard(US) + 単三乾電池x2 0.324kg(実測値)
    ・iPad(Wi-Fiモデル) 0.680kg(Appleの仕様(*2)より)
    合計1.004kg

    「MacBook Air」と比較して 「iPad(Wi-Fiモデル) + Apple Wireless Keybord(US)」のほうが300[g]程度軽い!!
    写真2

    ECナビでは「スマートフォン研究会」という社内サークルもあり、 有志でブログを立ち上げています。そちらもよろしく。

    スマホ研究会
    http://blog.smaho.jp/

    (*1) http://www.apple.com/jp/macbookair/specs.html
    (*2) http://www.apple.com/jp/ipad/specs/

    RATSでPHPの利用すべきでない関数をチェック

    こんにちは, 株式会社ECナビ システム本部 ECナビラボグループの春山(@haruyama)と申します.

    私は, 主にセキュリティと検索をテーマに仕事をしています. 今回はセキュリティについてです.

    先日, 他の言語については十分経験があるがPHPについてはあまり経験のない社員から,

    「PHP (5.2) で使ってはいけない関数の一覧ってどこかにありませんか」と聞かれました.

    PHPのセキュリティについては, PHP: セキュリティ - ManualPHP と Web アプリケーションのセキュリティについてのメモなどに資料があります.

    また, PHP: PHP 5.3.x で推奨されない機能 - Manual に「推奨されない関数」という項目があります.

    PHPのsplit() が推奨されないのは, split() がereg() などの POSIX 正規表現関数に属していて,

    そして POSIX 正規表現関数 がバイナリセーフでなく速度も低速であるからです.

    この知識はPHPの経験の浅い人が知ることは難しいでしょう.

    そこでPHPのコードを静的に解析して問題のある関数の利用を抽出しようと考えました.

    PHPの静的解析についてはあまり耳にしませんが, いくつかのツールがあります.

    私は前職で, PHPLintという静的解析ツールに手を入れて, 問題のある関数への警告を出していました.

    しかし, PHPLintは作者の独自のModula-2風の言語で書かれており, 改造しての利用は手軽ではありません.

    そこで, RATS - Rough Auditing Tool for Securityを利用することにしました.

    RATSは, c, c++, perl, php, python, rubyに対応した静的解析ツールです.

    データベースファイル(xml)を自分で定義することで拡張することができます.

    RATSはソースとWin32のバイナリが配布されています.

    またDebianやUbuntuにはパッケージもあります.

    ソースからのインストールは「./configure; make; make install」で可能です.

    Win32のバイナリには, The Expat XML Parserのdllファイルをパスの通った場所に置く必要があります.

    RATSのデフォルトのデータベースファイルには, PHPのsplit() についてのルールはありません.

    データベースファイル(rats-php-split.xml)を次のように作成します (ファイルの文字エンコーディングやシェルのロケールがUTF-8の場合, 問題なく日本語文字が出力されました).

    <VulnDB lang="php">
    <Vulnerability>
    <Name>split</Name>

    <Info>
    <Severity>High</Severity>
    <Description>
    split()はバイナリセーフではありません.
    また, preg_split()よりも低速です.
    preg_split()かexplode()を使用しましょう.
    </Description>
    </Info>

    </Vulnerability>

    </VulnDB>

    次のsplit() を利用しているファイル(split_test.php)にRATSを適用すると, データベースファイルで指定した警告が出ます.

    <?php
    list($user, $pass, $uid, $gid, $extra) =
    split(":", $passwd_line, 5);

    % .../rats --db rats-php-split.xml split_test.php   
    Entries in php database: 1
    Analyzing split_test.php
    split_test.php:3: High: split
    split()はバイナリセーフではありません.
    また, preg_split()よりも低速です.
    preg_split()かexplode()を使用しましょう.

    Total lines analyzed: 6
    Total time 0.000074 seconds
    81081 lines per second

    警告を行なう関数の追加は, Vulnerability要素の追加で行なえます. rats-php-deprecated.xmlが私が作成したPHP: PHP 5.3.x で推奨されない機能 - Manualで挙げられている関数に対するデータベースファイルです.

    ECナビでも, PHPを新たに始める人のコードや古いコードのチェックに利用していこうと考えています.

    以上, RATSでPHPの利用すべきでない関数をチェックでした.

    ECナビ エンジニアブログ開始します。

    皆様はじめまして。システム本部の吉村と申します。
    この度 ECナビグループ エンジニア陣でBlogを始めることになりました。

    このblogで扱う内容は以下となります。
    * システム開発、運用時のノウハウ
    * 技術セミナーの告知及び報告
    * エンジニアが面白いと感じたこと

    また、Twitterでも ecnavi_tech というアカウントでつぶやきますので、よろしければフォローしてください。

    すでにプロフェッショナルな技術者の方だけではなく、
    これから技術を学びたいと思っている方にも楽しんでいただけるような情報をお伝えしていきます。
    このblogをきっかけにECナビグループを少しでも知っていただければ幸いです。
    気長にお付き合いください。
    記事検索
    QRコード
    QRコード