やっぱりphpは変: parse_str編

phpにはparse_strという関数がある。
http://jp.php.net/parse_str

フォームから送信されたデータを連想配列に変換するためのものだが、
フォームから送信されていなくても、同じ形式の文字列を分解するのに使える。

でも、落とし穴。

.htaccess等で以下のような設定(PHPのデフォルト?)になっているとして、

php_flag  mbstring.encoding_translation on
php_value mbstring.http_input auto
php_value mbstring.internal_encoding UTF-8

EUC-JPで以下のようなコードを書いたとして、

$string = "今日=雨&明日=晴れ";  // EUC-JP
parse_str($string, $hash);
print_r($hash);  // UTF-8

これを実行すると、なんと、UTF-8の文字列が出てくるのだ。(PHP 5.1.6あたりで確認)

もともとphpの内部処理のために存在したルーチンをユーザ向けに提供しているのだろうか、mbstringのencoding translationとかmagic_quotes_gpcとかの処理が内部で必ず実行されてしまう。

ということで、便利な関数を使うときは、それなりの覚悟が必要かも。

ちなみに、parse_strの逆は、http_build_queryである。
http://jp.php.net/http_build_query

Webセキュアプログラマ検定

こういうの、どっかに無いかしら?

◆問1

<script ...>
<!--
  eval("hoge('${template_variable}');");
//-->
</script>

上記のテンプレートファイルの${template_variable}を
任意の変数の内容で置き換えるようなコードを書け.

※すごく駄目な例(PHP): preg_replace('/\${(\w+)}/e', '$vars[$1]', $template)

どうしても書けそうにない場合は、上記のようなテンプレートの問題を指摘し、
代替方法を提案せよ。

      • -

ちなみに、
PHPには便利な関数があるものの、この処理はものすごく書きにくい気がする。

ファイル添付でHTTP POST(PHP)

curlモジュールを使うことも検討したけど

  • データの先頭が@だったらファイル名指定として扱う

などという馬鹿げた仕様が怖くて、でも、

  • read/write callbackを使うやり方の例が見つけられず

結局、標準的な書き方で。同様の処理をしてくれるライブラリは既存だと思われる。

以前は、stream_context_createtとfopenを使っていたけれど、
この方法だとリクエストをメモリにいったん保持する必要があって、
大きなファイルを扱いづらいので、今回の方法に変更。

エラーチェック、SSL対応などは、若干省略気味。

<?php
	$url = "http://hostname/path/uploader";
	$param = array();
	$param['hoge'] = 'funi';
	$param['piyo'] = array('/path/file.jpg', 'image/jpeg', 'file.jpg');
	
	# URLを分解する(HTTPのみサポート)
	if(preg_match('{^([^:]+)://([^:/]+)(:(\d+))?(/.*)?$}', $url, $m)) {
		list($schema, $host, $port, $path) = array($m[1], $m[2], $m[4], $m[5]);
		if(!$port) {
			$port = 80;
		}
	} else {
		# wrong url
		user_error('Wrong url.', E_USER_ERROR);
	}

	# 適当に決める
	$boundary = "X".date('YmdHis')."X".date('YmdHis')."X".date('YmdHis')."X";
	
	$data = array();
	foreach($param as $name => $value) {
		$data[] = "--" . $boundary;
		if(is_array($value)) {
			if(!file_exists($value[0])) {
				user_error('File is not found.', E_USER_WARNING);
				continue;
			}
			$filename = $value[2]!='' ? $value[2] : basename($value[0]);
			$data[] = sprintf('Content-Disposition: form-data; name="%s"; filename="%s"', $name, $filename);
			$data[] = sprintf('Content-Type: %s', $value[1]);
			$data[] = "";
			$data[] = array($value[0], filesize($value[0]));
		} else {
			$data[] = sprintf('Content-Disposition: form-data; name="%s"', $name);
			$data[] = "";
			$data[] = $value;
		}
		if(strpos($data[count($data)-1], $boundary) !== FALSE) {
			# bad boundary !!
			user_error('Wrong boundary!', E_USER_ERROR);
			return FALSE;
		}
	}
	$data[] = "--" . $boundary . "--";
	
	# 長さを計算する
	$length = 0;
	foreach($data as $d) {
		$length += is_array($d) ? $d[1]+2 : strlen($d)+2;
	}
	
	# ヘッダを準備する
	if($header == false) {
		$header = array();
	}
	$header[] = "POST $path HTTP/1.0";
	$header[] = "Host: $host";
	$header[] = "Content-Type: multipart/form-data; boundary=$boundary";
	$header[] = "Content-Length: ".$length;
	
	# 接続する
	$sock = fsockopen($host, $port, $errno, $errstr, 30);
	if(!$sock) {
		# 接続失敗
		user_error('cannot connect.', E_USER_ERROR);
		return;
	}
	
	# リクエストを送信する
	foreach($header as $h) {
		fputs($sock, $h . "\r\n");
	}
	fputs($sock, "\r\n");
	foreach($data as $d) {
		if(is_array($d)) {
			# ファイル
			$fp = fopen($d[0], 'rb');
			while(!feof($fp)) {
				fputs($sock, fread($fp, 8192));
			}
			fclose($fp);
			fputs($sock, "\r\n");
		} else {
			# 文字列
			fputs($sock, $d . "\r\n");
		}
	}
	
	$header = array();	# ヘッダ配列
	$res = '';		# 本体
	while(!feof($sock)) {
		$line = fgets($sock);
		$line = rtrim($line, "\r\n");
		if(0 < strlen($line)) {
			$header[] = $line;
		} else {
			break;
		}
	}
	while(!feof($sock)) {
		$res .= fread($sock, 8192);
	}
	fclose($sock);
?>

iframeとCSRF

クロスドメインでごにょごにょしたい、とか考えて、iframeを使っていたところ、
iframeに対してcss(position: absoluteとかoverflow: hiddenとか)をうまくつかうと、
他サイトの特定部分(座標指定)を埋め込むことが容易に出来てしまう。


CSRF対策として、処理前ページにhidden tokenを埋め込んでいたとしても、
処理前ページをiframe内に表示させて、巧妙に実行ボタンを押させるという
仕掛けを作ると、成功確率は高くないかもしれないが、CSRFっぽいことが
できてしまうのではないだろうか?


ログイン直後から、ずっとtokenをhidden等で引きずり回していれば、
回避できるような気がするが、そういう実装はかなり面倒だろう。


さて、どうしたものか。

OperaでappendChild

cloneNode(true)とかでAタグ要素を複製し、
href属性をセットしてからappendChildするような場合、
Operaで正しく動作させるためには
appendChildしてからsetAttributeする必要がある。
逆順だと、DOM的にはsetされているけれど、ブラウザの挙動としてはset以前の状態、
となってしまう。

これで3時間くらい消費...

Operaのエンジンが速いことと関係しているのかしらん?

Wikiばな

この手のは、かなり久しぶり。面識ある人0人。

進行は、手慣れたもんで、全体も、各テーブルも、とってもスムーズ。
2次会への誘導も、とってもいい感じ。

Wiki」といっても、

というものから

  • 企業内での情報共有に(MLに加えて)Wikiも使う

というものまで、あるいは、

  • 目的が明確でメンバが地理的に分散しているプロジェクトの基盤としてWikiを使う

など、幅が広い。運営側の目標も違えば、参加者の数も違う。
そのあたりの前提を明確化してから議論するべきだったように思うが、
そうできなかったのは、やや残念。

テクニカルな話題としては、

  • Wiki連携のための標準形式の必要性
  • OpenID利用によるアカウンティング&SPAM排除
  • ストレージ/レンダラーの分離
  • WYSIWYGも使えるけどWiki記法も使えるinterface
  • Wikiに対するタグ付け、付箋貼り付け
  • 実用的な検索機能
  • Freeze(Snapshot)機能
  • URL長いのは嫌い

ソーシャルな話題としては、

  • Wikiリテラシー教育
  • コミュニケーションか、コラボレーションか、パブリッシュか
  • 他メディアとの比較、棲み分け
  • Wikiでなければできないことは何か
  • Wikiの定義は、人によってかなり違うのではないか
  • ケータイ世代向けWiki
  • 見てもらうまでの壁、書いてもらうまでの壁
  • 自主性よりも、ファシリテーション

等が気になった。

バッテリーレベルを調べるスクリプト

どうも最近、バッテリーが50%あたりから5%あたりまで一気に減るので、
しばらく計ってみることにする。が、手頃なものが見つけられないので。

cygwinperl 5.8.7で動作確認

#!/usr/bin/perl

use strict;
use Win32::API;

if($ARGV[0] eq '--loop') {
	$| = 1;
	while(1) {
		get_battery_info();
		sleep(60);
	}
} else {
	get_battery_info();
}

sub get_battery_info {
	my $getInfo = new Win32::API("kernel32", "GetSystemPowerStatus", 'P', '');
	
	my $data = ' ' x 12;
	$getInfo->Call($data);
	
	my($ac, $bf, $blp, $xx) = unpack('C*', substr($data, 0, 4));
	
	my %ac = (0=>'OK', 1=>'NG');
	$ac = $ac{$ac} || '?';
	
	my @bf_str = qw(High Low Critical Charging x x x NoBattery);
	my @bf_dat = unpack('B8', $bf);
	$bf = join(' ', map {$bf_dat[$_]?$bf_str[$_]:''} (0 .. 7));
	
	my @now = localtime();
	printf("%04u-%02u-%02u %02u:%02u:%02u ", 1900+$now[5], 1+$now[4], @now[3,2,1,0]);
	print "AC: $ac\tBat: $bf\tBatLife: $blp %\n";
}