miau's blog?

Perl とか PHP とか

ここ一週間くらい缶詰になって、Web アプリケーション作ってましたので、こまごましたネタを色々と。
今回も長いので注意。


■CGI.pm で日本語処理

実は今回初めて CGI.pm を使ったんですけど。
META タグで Content-Type とかちゃんと設定してやっても、なぜか文字化けする。

ちょっとググってみると、CGI.pmを使う というページに、


日本語ページ特有の問題として、文字化けがあります。
CGI.pmは最近になって日本語ページには余計なデフォルト文字コードISO-8859-1を
わざわざ送信してくれます。このまま使えばまず日本後は化けます。

とのこと。
で、書いてる通り、


header(-charset=>'Shift_JIS')

とすると、あっさり解決


■Perl で PHP のセッションを使用

PHP でログイン処理→PHP and CGI でユーザ認証/承認 なんて処理が必要になったんですが。

PHP::Session っていうモジュールがあるので、これを使うことに。

search.cpan.org: PHP::Session - read / write PHP session files

もっと具体的には、こんな感じで。(セッション変数の user_id を参照する例)


use CGI qw(:standard);
use PHP::Session;

my $sess_id = cookie('PHPSESSID');
my $session = PHP::Session->new($sess_id);

my $user_id = $session->get('user_id');

ただ、セッションが期限切れの場合は new で例外吐くみたいだから、


my $session;
eval { $session = PHP::Session->new($sess_id); }
die $@ if $@;

とかやったほうがいいかも。

ちなみに PHP::Session に関しては製作者の宮川達彦氏による発表資料が転がってたり。

Shibuya Perl Mongers : スタートアップセミナー終了レポート

このときに使った資料かな?
PHP をテンプレートエンジンとして使用、とかいうのも面白いかも。やんないけど。


■CGI でファイルのダウンロード

ファイルのダウンロード機能を備えた Web サイトとかあるわけですが、ダウンロード制限をかけたいような場合は CGI でダウンロード機能を実装したりするわけで。

たとえば、/cgi-bin/download.cgi?hoge.jpg を参照すると /var/prj/fuga/hoge.jpg を参照したいような場合。


use CGI qw(:standard);

my $rootpath = '/var/prj/fuga/'; # 対象ファイルの実ディレクトリ

# ファイル名の取得
my $q = new CGI;
my $filename = $q->query_string();
die "invalid path: $filename" if $filename =~ m#\Q../\E#; # Path Traversal 対策

my $filepath = $rootpath . $filename;

open IN, "<", $filepath or die "can't open $filepath: $!";
binmode IN;
binmode STDOUT;
print while (<IN>);
close IN;

みたいな感じでできる。

ただこれだと「名前をつけて保存」みたいなときに「download.cgi」として保存されそうなので、content-disposition ヘッダを指定してやるといいかも。(未検証)


■CGI をディレクトリに偽装

上に書いた方法だと、画像ファイルのパスが /cgi-bin/download.cgi?hoge.jpg になってしまうので、なんだか格好悪い。
ので、これを /images/hoge.jpg として取得する方法。

上のスクリプトを置いた状態で、/cgi-bin/download.cgi/hoge.jpg というリクエストを送った場合、/hoge.jpg の部分は、$ENV{PATH_INFO}(CGI.pm 使った場合は path_info()) として取得できる。

あとは、cgi-bin/download.cgi を images にリネームして、実行可能にすれば OK。
これは、httpd.conf で


<Files images>
SetHandler cgi-script
Option +ExecCGI
</Files>

みたいに設定すればいいらしい。


■Smarty の使い方とか

今回テンプレートエンジンとして Smarty を使ってるわけですが、数字の書式指定関数(修正子)が、string_format() くらいしかないっぽい。
要するに、sprintf 以上のことはできないと。

今回の要望には
・符号の表示(プラスの場合は先頭に「+」を付加)
・3 桁ごとにカンマを付加
なんてのがあって、これには対応できないわけです。

自分でプラグイン書けばいいんだけど、標準外の使い方になると色々と面倒なわけで。
デザイナーさんに「Smarty の基本構文くらい覚えてね」とは言えても、拡張用関数の使い方とかいちいち説明したくないし。

今回は結局 PHP 側で対処しました。
標準的なプラグインとか配布されてないのかな。STL とかそういうノリで。


■DB_DataObject

今回 DB_DataObject 使ってますが・・・OOP っぽくていい部分もあるんですけど、結構不便な面もあったりして。

・JOIN は考慮されてるけど、構文が複雑になる
・UPDATE/INSERT 時は単純な文字列 or 数字しか入れられない

とか。つまり、


INSERT INTO user
(user_id, user_name)
VALUES
((SELECT MAX(user_id) + 1 FROM user), 'hoge')

なんて処理は書けないわけです。

ということで、DB_DataObject 使ってる場合は、

・ビューを使用して、複雑な結合を書かなくていいように配慮する
・オートナンバー型のフィールドを使用して、INSERT 時に集計関数等を利用しなくていいようにする

という具合に、DB 設計をしっかりする必要がありそう。

というか、「いいかげんな DB 設計を開発者が SQL でカバーする」という形だと破綻します。
しっかりした DB 構成が基本としてあって、画面からはそれに簡単な指示を出すだけ、という設計思想があればうまく使いこなせそうです。

ちなみに、DB_DataObject の使い方は、

DB_DataObjectの使い方 : townmedia org

このへんから色々リンク辿るといい感じです。


■DB_DataObject でビューの使用

上に書いたように、DB_DataObject を有効利用するにはビューの使用が必要になってくると思うんですが、普通に createTables.php を実行した場合ビューに対応するクラスが生成されないわけで。

試しに createTables.php と同階層の Generator.php の 155 行目付近、
$this->tables = $__DB->getListOf('tables');


$this->tables = $__DB->getListOf('views');

にしたところ、エラー吐きまくったけどそれらしいファイルが生成されてました。

気づくの遅かったので実際には使ってないのですが、もしかしたら使えるかも。


■エラー画面表示

ユーザの認証に失敗した場合は、ファイルの存在を知られたくない(404 を返したい)場合。

Perl では

print CGI::header(-charset => 'Shift_JIS', -status => '404 Not Found');


PHP では

header("HTTP/1.0 404 Not Found");


みたいにすることで、HTTP のステータスとして 404 は返るんだけど、Apache 側で指定したエラーページを表示するところまではやってくれないらしい。
今回は自前でファイル読み込んで出力処理までしたけど、Apache の設定を取得する方法 or Apache に処理を投げる方法なんてのもあるんだろうか。


■Location ヘッダ

リダイレクトさせたい場合、Location ヘッダを設定したりしますが。
絶対 URL じゃないとダメなんですね。

[finite] Locationヘッダ

他人のソースばかり参考にしてたら間違った処理を覚えてしまう典型っぽい。
PHP とかそういうのも、ちゃんと基礎知識を固めておかないとなー・・・。


(2005/08/05 修正)

Directory Traversal 対策の処理がヘッポコすぎたので修正。

当初は

$filename =~ s#../##g; # Path Traversal 対策

になってたんだけど、これだと
・"." が任意の文字にマッチしてしまう
・"....//" とを渡されたら "../" が残ってしまう
と二重にヘッポコなわけで。

実際仕事で使った処理(他の人が書いた)は

die "invalid path: $filename" if $filename =~ m#\Q../\E#; # Path Traversal 対策

こんな感じ。
このほうが安全ですね。
posted at 04:37:28 on 2005-07-31 by miau - Category: Work No Trackbacks - Permalink

TrackBack

このエントリにトラックバックはありません
現在トラックバックは受け付けていません。

Comments

No comments yet

Add Comments

現在コメントは受け付けていません。
お手数ですが、 こちら のコメント欄にでも記載していただければと思います。