※ パクレゼルヴではWeb開発エンジニアを大募集中!詳細はこちら

簡単な入力フォームこそ気を付けよう  

2010/5/31 月曜日 22:57:17

phpで作る入力フォームでやってしまいがちな事を紹介します。

入力した情報を確認画面で表示させる、というよくあるパターン。
(ベタ書きで失礼します。)

▼form.php(入力画面)

<form method="post" action="./confirm.php">
	ニックネーム: <input type="text" name="title" value="" /><br />
	<input type="submit" value="送信" />
</form>

▼confirm.php(確認画面)

< ?php
if(isset($_POST['title']) && $_POST['title']){
	echo htmlspecialchars($_POST['title']);
}else{
	echo '未入力';
}
?>

これ、一見問題無さそうですが、ある文字を入力すると未入力と表示されてしまいます。

そうです。

0(ゼロ)です。

TRUEかFALSEかで判断しているため、post送信された0はFALSEとして見なされます。

なのでこうやりましょう。

▼confirm.php(確認画面)

< ?php
if(isset($_POST['title']) && $_POST['title'] != ''){
	echo htmlspecialchars($_POST['title']);
}else{
	echo '未入力';
}
?>

どうしても「0」という名前で登録したいんだ!というお客様がいないとも限りませんからね。。

 

さて、もう一つ紹介します。

クロスサイトスクリプティング(XSS)のお話です。

今度は同じphpファイル自身にpost送信するパターン。
▼http://example.com/test.php

< ?php
// なんらかの処理
?>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
…
</form>

action先に何の迷いも無く

$_SERVER['PHP_SELF']

と書くと、実はこれが結構危ないのです。

他のhtmlファイルで下記のようなソースを書き、上記のスクリプトへのリンクを貼りましょう。
そして、クリックしてみましょう。(攻撃!)

<a href="http://example.com/test.php/%22%3E%3Cscript%3Ealert('xss');%3C/script%3E/">攻撃</a>

JavaScriptが実行され、アラートが表示されるかと思います。

URLの後半部分はURLエンコードされたものです。

$_SERVER['PHP_SELF']は、渡されたURIのホスト部分の後から、
GETクエリ部分の前まで(?の前まで)をURLデコードした形で格納します。

つまり、$_SERVER ['PHP_SELF']は、

/test.php/"><script>alert('xss');</script>/

となるので、

<form method="post" action="/test.php/"><script>alert('xss');</script>/">

となり、JavaScriptが実行されてしまうわけです。

 
では結論。
このようにしましょう。

</form><form method="post" action="">
…
</form>
<form method="post" action="<?php echo $_SERVER['SCRIPT_NAME']; ?>">
…
</form>
<form method="post" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>">
…
</form>

空にしてあげるのが一番ラクかと思います。

MoriMoriMoriMori HTMLとか, PHP

fgetcsvがphp5で文字化けする  

2010/5/31 月曜日 20:45:08

仕事でCSVを使うことがあったんですけど、その時に悩んだことを書こうと思います。

fgetcsv()という便利な関数があるんですが、php5で使うと文字化けするんですよ。
なぜ文字化けが起きるのかと言うと、
「fgetcsvはマルチバイト文字の2バイト目に’\'があると、直後のダブルクォーテーションをエスケープするという、少なくともエクセルの仕様にはない独自解釈をする。」
らしい。
訳わかんない事言ってるなーと思いますが、要はShift-JIS形式のCSVをfgetcsv()関数でと文字化けするみたいです。

いろいろと調べた結果、
「PHP 5.0 の fgetcsv() はロケールの設定に依存します。」
とのことなので、

setlocale('LC_ALL', ja_JP.EUC_JP);

でロケールを設定してあげれば直ら・・・・ない。
ぱっと見た感じ直ってるように見えるんですけど、機種依存文字が化けてしまう。

それじゃあCSVをまずUTF-8とかに変換しちゃえば解決するんですけど、実はfgetcsvぽく動いて、しかもバグがない
関数があったので、それを使うことに。

function fgetcsv_reg (&$file, $length = null, $d = ',', $e = '"') {
	$d = preg_quote($d);
	$e = preg_quote($e);
	$_line = "";
	$eof = FALSE;
	while (!$eof && !feof($file)) {
		$_line .= (empty($length) ? fgets($file) : fgets($file, $length));
		$itemcnt = preg_match_all('/'.$e.'/', $_line, $dummy);
		if ($itemcnt % 2 == 0) $eof = true;
	}
	$_csv_line = preg_replace('/(?:\r\n|[\r\n])?$/', $d, trim($_line));
	$_csv_pattern = '/('.$e.'[^'.$e.']*(?:'.$e.$e.'[^'.$e.']*)*'.$e.'|[^'.$d.']*)'.$d.'/';
	preg_match_all($_csv_pattern, $_csv_line, $_csv_matches);
	$_csv_data = $_csv_matches[1];
	for($_csv_i=0;$_csv_i<count ($_csv_data);$_csv_i++){
		$_csv_data[$_csv_i]=preg_replace('/^'.$e.'(.*)'.$e.'$/s','$1',$_csv_data[$_csv_i]);
		$_csv_data[$_csv_i]=str_replace("'","\\'",$_csv_data[$_csv_i]);
		$_csv_data[$_csv_i]=str_replace($e.$e, $e, $_csv_data[$_csv_i]);
	}
	return empty($_line) ? false : $_csv_data;
}

この関数で、CSVを正規表現で切ってます。
使うときはこういう感じで

$new_data = array();
$header = array('name','zip','addr','tel','fax','manage','security','url');

$filename = 'CSVファイルパス';
$file = fopen($filename, "r");

while (($data = fgetcsv_reg($file)) !== false) {
	$_enc_to=mb_internal_encoding();
	$_enc_from=mb_detect_order();
	mb_convert_variables($_enc_to,$_enc_from,$data);
	$num = count($data);
	for ($c=0; $c < $num; $c++) {
		$new_data[$header[$c]] = $data[$c];
	}
	foreach($new_data as $key => $val){
		echo $key." : ".$val."<br />";
	}
}

こういう風に使えば、fgetcsv()関数とほぼ同じ動作をし、さらに文字化けもおきません。

こういうオリジナルの関数を自分でも作れるようになりたいですね。。

つっしー PHP, 未分類

mysqlでINは内部的に処理できない  

2010/5/26 水曜日 0:33:39

お久しぶりです。

以前、ボトルネックになっているSQLを探していたら以下のような事がありました。

EXPLAIN SELECT * FROM user_master
	WHERE user_id IN (
		SELECT user_id FROM game_user_status);

とすると

+----+--------------------+------------------+-----------------+---------------+---------+---------+------+------+-------------+
| id | select_type        | table            | type            | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+--------------------+------------------+-----------------+---------------+---------+---------+------+------+-------------+
|  1 | PRIMARY            | user_master      | ALL             | NULL          | NULL    | NULL    | NULL |   86 | Using where |
|  2 | DEPENDENT SUBQUERY | game_user_status | unique_subquery | PRIMARY       | PRIMARY | 4       | func |    1 | Using index |
+----+--------------------+------------------+-----------------+---------------+---------+---------+------+------+-------------+

86*1 
で普通に意図した結果が取得できます。

しかし、サブクエリ中でGROUP BYすると酷いことになりました。

EXPLAIN SELECT * FROM user_master
	WHERE user_id IN (
		SELECT user_id FROM game_user_status GROUP BY user_id);
+----+--------------------+------------------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type        | table            | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+--------------------+------------------+-------+---------------+---------+---------+------+------+-------------+
|  1 | PRIMARY            | user_master      | ALL   | NULL          | NULL    | NULL    | NULL |   86 | Using where |
|  2 | DEPENDENT SUBQUERY | game_user_status | index | NULL          | PRIMARY | 4       | NULL |   67 | Using index |
+----+--------------------+------------------+-------+---------------+---------+---------+------+------+-------------+

GROUP BYすると

86*67

と、検索ロジックに大きな違いが出てきます。

で、原因は”IN”句

MySQLは内部的にINを直接処理することができないので、EXISTSに変換することでSQL的には相関のないサブクエリも相関サブクエリになってしまうのである。これがまさにMySQLのサブクエリが遅い!と言われている原因だろう。
なぜMySQLのサブクエリは遅いのか。

だからってGROUP BY使っただけでそこまでしなくても・・・

と、いう事で以下のように書き換えると解決できるようです。

EXPLAIN SELECT * FROM user_master um
WHERE EXISTS
(SELECT 1 FROM game_user_status gus WHERE um.user_id = gus.user_id);
+----+--------------------+-------+--------+---------------+---------+---------+-----------------------+------+-------------+
| id | select_type        | table | type   | possible_keys | key     | key_len | ref                   | rows | Extra       |
+----+--------------------+-------+--------+---------------+---------+---------+-----------------------+------+-------------+
|  1 | PRIMARY            | um    | ALL    | NULL          | NULL    | NULL    | NULL                  |   86 | Using where |
|  2 | DEPENDENT SUBQUERY | gus   | eq_ref | PRIMARY       | PRIMARY | 4       | warui_test.um.user_id |    1 | Using index |
+----+--------------------+-------+--------+---------------+---------+---------+-----------------------+------+-------------+

個人のブログの方でご指摘いただきました内容を解答例とさせていただいています。
安直にINを利用するのは良くないという例でした。

ちなみにgroup by してもしなくても取得結果が変わらない場合もあります。
今回の例なんかはgroup by しなくても取得結果は変わりません。

むやみやたらと色々つければいいって物でも無い いい例です。

のびーにょ MySQL, Tips, データベース

コマンド名やファイル名からパッケージを探す  

2010/5/23 日曜日 23:32:45

Linuxでパッケージを探すときに使うTips

コマンド名やファイル名がわかっていてパッケージ名が分からない時に、以下の方法でパッケージを探すことができます。

RPM系(Fedora/RHEL/CentOS/etc…)

準備

yumが使える環境であれば特に準備の必要はありません。

ファイルを探す

yum provides "*/ファイル名"

コマンドを探す

yum provides "*bin/コマンド名"

debian系(debian/Ubuntu/etc…)

準備

apt-fileがインストールされている必要があります。

sudo aptitude install apt-file
sudo apti-file update

ファイルを探す

apt-file search "ファイル名"

コマンドを探す

apt-file search "bin/コマンド名"

以上、簡単ですがパッケージを探す際の参考になれば幸いです。

マカー Linux

配られたカードで勝負するしかねぇんだ  

2010/5/6 木曜日 12:06:30

自分が普段使っているレンタルサーバーはセキュリティ云々のおかげで「.forward」ファイルを設置することが出来ない。
でも、空メールの処理を入れたいので、どうしようか悩んだ結果。
無理やりPOP3に繋いで処理すりゃいいじゃんという結論に。

今回のサーバーにはNET_POP3のPearライブラリが入っているので、これを使うことに。
入ってない場合は適当にコマンド打ってインストールでもすればいいと思うよ。

pear install Net_POP3

で、簡単に処理を書く。

require 'Net/POP3.php';

$pop3 = new Net_POP3();

$ret = $pop3->connect("host", 110);
if (!$ret){
	$pop3->disconnect();
	exit;
}

$pop3->login("id", "ps", true);

$ret = $pop3->numMsg();
if (!$ret || $ret == 0){
	$pop3->disconnect();
	exit;
}

print "$ret : メッセージ件数<br />\n";

$ret = $pop3->getListing();
$i = 1;
if ($ret !== false){
	$pop3->disconnect();
	exit;
}
foreach($ret as $key => $val){
	// 中身の処理
	$pop3->deleteMsg($val['msg_id']);
}
$pop3->disconnect();

これを適当にcronで回して処理してれば「.forward」的な処理が出来るかなー。
\e

下音タヌキ PHP