はてなキーワード: ECHOとは
/** * そっとうに。 * * そっとうにを置く。 * * @param string $str * @return string * @see <a href="http://vipsister23.com/archives/5204131.html">とある小学生が書いた詩が哲学的過ぎる件</a> */ function sottoUni($str) { return 'そっと' .PHP_EOL . $str.'に' .PHP_EOL .'うに' .PHP_EOL; } $arr = array('せなか','わたげ','たにま'); foreach($arr as $d) { echo sottoUni($d); }
http://anond.hatelabo.jp/20070508170219
もっと上手いやり方があるんだろうなぁ。
$i3=1; $i5=1; $flag=0; for($n=1;$n<101;$n++){ if($i3==3 && $i5==5){ echo "FizzBuzz¥n"; $i3=1; $i5=1; }else{ if($i3==3){ echo "Fizz¥n"; $i3=1; $flag=1; }else{ $i3++; } if($i5==5){ echo "Buzz¥n"; $i5=1; $flag = 1; }else{ $i5++; } if($flag==0){ echo $n."¥n"; } $flag = 0; } }
※一部全角化(&¥<)
HTMLはわかるけど、サーバーサイドはお遊びでphpを触ったぐらいだったので、会員制でデータをためこむサイト作りに初めて挑戦した。
今回重視したのは、「いかに個人情報をお漏らししないようにして、万が一漏らしても被害を少なくするか」ということ。
世の中、有償サービスでもパスワードを平文で保存してるサービスが意外と多いらしいので、流出した時のリスクを少しでも減らせる対策として書きます。
サーバー:ロケットネットのキャンペーンにでレンタルサーバ年1000円ポッキリプラン クライアント側の処理:HTML+CSS+jQuery(とプラグインもろもろ) サーバ側の処理:PHP Webサーバー:Apache データベース:MySQL
俺も巻き込まれたところでは、サミータウンがメールアドレスとパスワードセットでお漏らししてお詫びに1ヶ月無料なにそれこわい。
サミータウンだけならまだいいけど、メアドとパスワードを他のサービスで共通化して使ってる情弱なので、
共通化してメアドとパスワードをどこかのサービスが一箇所でも漏らすと、ヤフオクID乗っ取り事件みたいなことになる。
http://internet.watch.impress.co.jp/cda/news/2008/09/26/20967.html
俺だってできれば人様のメールアドレスとパスワードとか預かりたくない。
万が一、肉親のメールドレスを発見してパスワードにrapemeとか入ってたら明日からどういう顔すればいいかわからない。
ググってみてもどこにも情報のってない。うーん困った。ダメもとで「個人情報ってどうやって保存したらいいんだろう。。。」
って、twitterでつぶやいたら、「住所とかは可逆暗号化でいいけど、パスワードはハッシュで不可逆化しないとだめだよ!」
「住所とかは可逆暗号化でいいけど、パスワードはハッシュで不可逆化しないとだめだよ!」
何のことかわからなったので、調べてみると、
・ハッシュ=ハッシュ値を使った、元のデータに戻せない暗号化方式
うーん。。。よくわからん。。。
電話番号とか住所は、第三者が使用する情報なので、可逆が必要。パスワードは、認証にしか使わないので、
ハッシュ値の結果が一致すれば元のデータがわからなくてもOK、という方式なのでこういった暗号の使い分けをする。
●可逆暗号のイメージ(もとにもどせる) 暗号化キーは開発者が指定する。 090-xxxx-xxxx →(暗号化)→ !'&amp;%($% →(復号化)→ 090-xxxx-xxxx ●ハッシュのイメージ(もとにもどせない) 登録password(DBに保存)→(ハッシュ値抽出)→!"$#'$#=" ログインpassword →(ハッシュ値抽出)→!"$#'$#=" ※二つのハッシュ値が合っていれば、パスワード一致として認証する。
今回はMySQLの関数で実現した。encode関数で暗号化して、decode関数でもとに戻す。
例えばtel_noという項目だけあるテーブルがあるとすると、
//データベースに保存する時 insert into テーブル名 (tel_no) values (encode(tel_no,'暗号化キー')); //データベースから取得する時 select decode(tel_no,'暗号化キー') from テーブル名;
これで、データベース格納時は暗号化(バイナリ化)されて、データベースから取り出してHTML表示する時に復号化はされる。
<ユーザ登録時>
$password=(フォームから取得) $hash=hash('sha512',$password) //ユーザ登録時は、ここで生成した$hashをデータベースにぶっこむ。
ユーザ認証時は、入力されたパスワードと、データベースのパスワードが一致するかチェック。
//フォームから入力されたパスワード $input_password=(フォームから取得) $input_hash=hash('sha512',$input_password); //MySQLに保存されたパスワードを取得(略) $db_hash==(データベースから取得) //判定 if($input_hash==$db_hash) echo 'ログインしますよ!'; //ここにログイン処理を書く else die('メアドとパスワードがあってないよ!');
これでもしSQLインジェクションとかでデータが流出しても、ハッシュ暗号のパスワードに関してはまず解析されないはず。。。
可逆暗号のデータもphp側の暗号化キーが盗まれない限りバレない。。。はず。。。
何でもかんでも暗号化するとコードが煩雑になるし、パフォーマンスにも影響でそうなので、
住所データの都道府県とか、漏れても良いような情報は暗号化しませんでした!!
個人情報保護法 2条による定義 「個人情報」とは、生存する個人に関する情報であって、当該情報に含まれる氏名、生年月日その他の記述等により特定の個人を識別することができるもの(他の情報と容易に照合することができ、それにより特定の個人を識別することができることとなるものを含む。)をいう。
これで、もし漏れても、俺、ウンコ漏らして臭いけど、パンツから出てないからいいよね?というレベルにはなった。はず。
万が一漏れても大丈夫!と書いたけど、そもそも漏らすなというお話になる。色々調べた結果、以下の対策をほどこした。
・当初jQuery側でSQL組み立ててPHPに渡してたので、これだと任意のSQLが実行できて漏らし放題なのでやめる。
・GETとかPOSTでDBに渡すパラメータを扱ってる場合、ちゃんとエスケープする。
例えばログイン認証するPHPで、GETメソッドでフォームからデータを取得するような場合、
$id=$_GET['id'] $pwd=$_GET['pwd'] $sql="select * from ユーザーテーブル where uid='$id' and pwd='$pwd'
とかやってると、login.php?id=admin'&pwd=' OR '1'='1とかパラメータを渡されるとあら不思議!
select *from ユーザテーブル where uid='root' and pwd='' or 1=1
で、誰でもログイン出来ちゃう!ので、mysql_real_escape_stringでエスケープしたり、渡されたパラメータが想定した値かどうか(例えば数値かどうか、とか)のチェックをいれたりする。
・保存するデータにタグやJavascriptを埋め込まれないように、保存されたデータを出力する場合はPHP側でhtmlspecialchars関数使ってエスケープするようにする。
こんな感じでお漏らし対策をした。間違いがあったら教えて欲しい。
ちなみに出来上がったサイトはこれ。
というわけでサンプルコード書きなおしてみた。
public class Main { public static void main(String[] args) { int count= args.length==0?100000:Integer.parseInt(args[0]); long start=System.currentTimeMillis(); Main m=new Main(); double result=0; for(int i=0;i<count;i++){ result+=m.test((double)(0.1f*i)); } System.out.println(result); System.out.println(String.format("COMPLETE! %d msec",System.currentTimeMillis()-start)); } private double product=0; public double test(double rad){ return this.product+=Math.tan(rad); } }
<?php class Test{ private $product; public function test($rad) { return $this->product+=tan($rad); } } $count =is_null($argv[1])?100000:(int)$argv[1]; $start=microtime(true); $t=new Test(); $result=0; for($i=0;$i<$count;$i++){ $result+=$t->test(0.1*$i); } echo "{$result}\n"; printf("COMPLETE! %f msec", (microtime(true)-$start)*1000);
$java -jar test.jar 100000 -7.524990063072938E9 COMPLETE! 31 msec
$ php -f test.php 100000 -11900078829.3 COMPLETE! 209.314108 msec
うむ。有意な差はあるっぽいな。(結果が違い過ぎてるのはなんか問題があるかもしれん)
多分、変数の型が出鱈目で計算されるようなもんだとPHPは遅くなるのだと思われ。javaは強い型制約があるから、同じ型同士の計算だと速いとか?
サンプルコード。
package test; public class Main { public static void main(String[] args) { int count= args[0]==null?10000:Integer.parseInt(args[0]); long start=System.currentTimeMillis(); Main m=new Main(); for(int i=0;i<count;i++){ System.out.println(m.test()); } System.out.println(String.format("COMPLETE! %d msec",System.currentTimeMillis()-start)); } public String test(){ return "ぽぽぽぽーん"; } }
<?php class Test{ public function test(){ return "ぽぽぽぽーん"; } } $count =is_null($argv[1])?10000:(int)$argv[1]; $start=microtime(true); $t=new Test(); for($i=0;$i<$count;$i++){ echo $t->test()."\n"; } printf("COMPLETE! %f msec", (microtime(true)-$start)*1000);
ぽぽぽ...
COMPLETE! 1008 msec
ぽぽぽ...
COMPLETE! 988.869190 msec
どういうことなのか説明してもらおうか?
http://1-byte.jp/2011/03/20/20_tips_you_need_to_learn_to_become_a_better_php_programmer/
良いPHPerだって?そんなものは丸めてゴミ箱にでも捨ててしまった方が資源の再利用になる分いくらかマシだ。
つまり俺たちがしなくちゃならないことは「より良いPHPerにならないため」に何ができるかってことなのさ。
それじゃ、始めよう。
?>なんて使っちゃいけない。そう俺たちはBAD PHPer。
無駄なホワイトスペースの出力に悩まされるくらいなら対称性なんて丸めてゴミ箱にでも捨てた方がまだマシだ。非対称性こそが賛美。
require_once("config.php");
未だにこんなことやってるやつがいるのかいベイベー。絶対にダメだ。この一行を見たら俺は悶絶する。
ダメだ、早く何とかしないと。
大抵このconfig.phpの中身はこうなっている。見て絶望だ。
$hoge_path = ''; if (!LOCAL) { define('FOO_FLAG', 1); if (HONBAN) { define('HOGE_FLAG', 1); } else if (TEST) { define('HOGE_FLAG', 2); } } else { $hoge_path = '/local'; define('FOO_FLAG', 2); define('HOGE_FLAG', 3); } define('HOGE_URL', $hoge_path.'/hoge/');
こういうのが延々と続くわけだ。もういやだ。もう見たくない。
本番環境とテスト環境でどういう値の違いがあるのか、ローカル環境だとどうなるのか、まったく把握できる気がしない。
なまじPHPな設定ファイルのせいで、処理をついつい書いてしまう。そしてどんどん複雑になってしまう。
やはり設定データは基本的にYAML等のデータしか定義できない形式のもので用意すべきだ。そして環境ごとに設定ファイルを分けるべきである。
そうすることで何にどういう違いがあるのかすぐにわかるし、diffすれば一度にすべて把握することができる。
# 本番環境設定ファイル foo_flag: 1 hoge_flag: 1 hoge_url: '/hoge/'
# テスト環境設定ファイル foo_flag: 1 hoge_flag: 2 hoge_url: '/hoge/'
# ローカル環境設定ファイル foo_flag: 2 hoge_flag: 3 hoge_url: '/local/hoge/'
// ここで後の処理のためにhogeメソッドを呼び出しておく $q->foo(); // $a['foo']はここに来る時点で真のはず // 2010-03-10 判定がおかしいので修正 // 2010-06-21 やっぱり値が入ってる方が正しい if ( !isset($hoge[0]) ) { }
コメントは保守されない。そう、それは真実。こんなコメントを発見したら即効削除しよう。コメントは基本信じるな。
俺たちにちょっとしたヒントと大きな損害を与えてくれる、それがコメントの役割なのだ。
わかる。いいたい事はとてもわかる。俺たちはしばしばインデントにスペースを使うはずだ。一方でIDEのしっかりした言語ではタブも使うことがある。しかし悪いことに、両者を混同しているプログラマも一定数いるのだ。
タブを画面上で認識しにくいエディタが世の中には存在する(何とは言わないが)
そして画面上で認識しにくいことを理由にタブを気にしないプログラマがいる。
この二つの条件が重なると、タブとスペースの交じり合ったインデントが完成する。もうぐちゃぐちゃだ。これは永遠に続く戦いだ。
私たちが勝利を掴むためにできることなどせいぜい、常にスペースしか使わない。タブを見つけたらその都度スペースに変換する。そういった地道な活動が明日へとつながるのだ。
われわれがプログラムをするとき、何に一番時間がかかってるか。実は変数の命名なのである。ここで拘り過ぎて時間をかけ過ぎては何も進まない。
御託はイイからさっさと書け、だ。しかしとはいっても変数名は重要。日頃からどういうときにどんな名前を使うかを決めておくといい。
そして変数名に型はまったく必要ない。型宣言のないPHPにおいて、型の変数名をつけること自体ナンセンスだ。
$iNumber = 'aaa';
になんの意味もない。コメントを信じるなでも言ったが、これはプログラマを混乱させるだけの害悪なものだ。
変数を使う前に初期化するのは、警告を出さないという意味でも良い癖だ。しかし具体的にどこでやるかが問題だ。
$foo = null; $foo = $q->foo();
こんな初期化に意味はない。よくあるのはやはり、if文で値を振り分けるケースだろう
$foo = null; if ( $hoge ) { $foo = 1; } else if ( $bar ) { $foo = 2; }
このときの初期化はとても有効だ。もしnullの初期化を忘れたまま$fooを使うと警告が出るが、ちゃんと初期化してるので出ない。基本中の基本だ。
function getStatus() { $bReturn = false; if ($i == 2) $bReturn = true; return $bReturn; }(中略)
もし、何かしらの理由で、あなたの書いたif文が間違っていたら?
この書き方をしていれば、間違った値に対して、常にfalseが返る。
私たちが、PHPでsensitiveなデータを取り扱うなら、正しいデータが入力されるまでは、動かないコードを書くべきだ。
trueとfalseの条件がいまいち明確ではないが、本当に動かないコードを書けというのであれば以下のようにすべきだ
function getStatus() { $bReturn = false; if ($i == 2) $bReturn = true; else if ($i == 1) $bReturn = false; else throw new Exception("bad status! $i"); return $bReturn; }
中途半端にfalseを返して生存させる必要性はまったくない。今すぐ死ね!
連想配列のキーを指定する場合だけ定数と間違わないようにクオートで囲まなければならない。そして逆に定数を使いたい場合はクオートで囲ってはいけない。
更に後世のプログラマが処理を見たときに、定数が使いたかったのか、文字列が使いたかったのかを明確にしたい場合はconstantを使うと良い。
// 定数のFOOを使うよということが明確になる print $a[constant('FOO')];
もし、文字列を変数の値と一緒に出力するとき、PHPではコンマの代わりにprintfを使うことが使える。
printf( “Hello, my name is %s“, $sName);
以下の代わりに上記のコードを使う。
echo “Hello, my name is “, $sName;
出力すべき変数が増えれば増えるほど、有効になっていく。とにかく迷ったならば、printfを使え、だ。
三項演算子はとても有効だ。しかし優先順位に難があるせいで、三項演算子をネストしようとすると以下のようなコードになってしまう
$n = (($i == 1) ? 2 : (($i == 2) ? 3 :$i));
括弧だらけで読みにくいったらありゃしない。三項演算子を使うなら一回まで。約束守れないやつは丸めてゴミ箱にでも捨てちまえ。
if ( $flag ) { }
仕様をちゃんと把握しているなら真偽値のチェックなどこれで十分。
もし事前にbool型だというのが確定してるのなら「$flag === true」を使えばいい。
インクリメント、デクリメント演算子は前に付くか後ろに付くかで意味が変わるので慣れるまでは非常にややこしい。
わけがわからなくなるくらいなら初めから使わないほうが良い。見極められないなら使うな。それがPHPerなのだ。
文句なしだ。これは文句がない。
他にも色々あるので覚えておこう
$a %= 1; $a &= 1; $a |= 1; $a ^= 1; $a <<= 1; $a >>= 1;
てっとり早く画面に表示する際にpreはよく使うが、デザインの関係上画面の文字が見えないときがある。
なのでdivを使って以下のようにしとくと便利だろう。
function p($var) {
echo "<div align='left' style='background-color:white;color:black;'><pre>";
print_r($var);
echo "</pre></div>";
}
君らが通常作るアプリケーションなんぞに、定数なんぞ必要ない。いいか、もう一度言う、お前ら程度のもんが、定数使おう何ぞ、おこがましいわ!
大丈夫。なんでもかんでも定数にする必要はない。結局設定ファイルに定数をずらずら作りまくってわけがわからなくなってるパターンが多い。
貴様みたいなもんに、定数は制御できん。いいか設定ファイルはYAML等のデータで持つようにし、その連想配列のデータ構造を一つ持ってるだけで定数の変わりになる。
このメリットに比べれば、定数だと書き換えられなくて良いという利点などこの歯のカスほどのものだ。そんなものは丸めてゴミ箱へ捨ててしまうといい。
認識を改めろ。俺たちはより良いPHPerにならないために努力している。
class Request { private $parameters; private $method; function __construct () { $this->method = $_SERVER['REQUEST_METHOD']; if ( strtoupper($this->method) === 'POST' ) { $this->parameters = $_POST; } else { $this->parameters = $_GET; } } function param ($key) { return isset($this->parameters[$key]) ? $this->parameters[$key] : null; } }
これだけでもいい。たったこれだけでもとても便利だ。ここから拡張してGETやPOSTを明示的に取るメソッドとかも作ってみるといい。自分の手を動かすのだ!
例が良くない。こんなのは引数が20個ある関数から、setを20回呼ぶオブジェクトに変わっただけではないか。
そもそもこの20個の引数とはなんなのか。何かのデータ構造なんであれば連想配列にして引数一つとして渡すべきだし、それぞれまったく異なる用途の変数なのであればWindowsプログラミングじゃあるまいし、20個も引数取る時点で設計が間違えている。
何がいいたいか。別に関数でもオブジェクトでもどっちでもいいということだ。
そんなことで悩んでる暇があったら設計を見直せ。
スキあらば自分自身を返せ。スキあらばオブジェクトを返せ。配列はArrayObjectのARRAY_AS_PROPSで返せ。
ひたすらメソッドチェイン。来る日も来る日もメソッドチェイン。とにかくメソッドチェインを使い続けろ。そこに未来はある。
どんなコードも繰り返すな。もし、少しでも同じコードを書いていたなら、それは関数に置き換えてしまえ。
・・・と、いうのはやめなさい。
一見同じように見えた処理でも前後の流れでまったく違うものということが往々にしてある。
まとめ方にも問題があるケースもある。何でもかんでも関数化すると、関数が膨大に増えていく。君は見たことがあるだろうか。common.phpやfunction.phpの恐ろしさを。
確かに細かく関数化はされているが、適切に関数化していないのである。結合度が非常に高い。なんでもかんでも盲目的にまとめれば良いという話ではないのだ!
あまりに極度に意識しすぎると、プログラムそのものができなくなる。そういう状態に陥る。
気を抜いて。そう気を抜いて。所詮あなたのコードなんてすぐに消えてなくなるよ。きっともっと偉い人が作り直すよ。だからまずは思うが侭にやるといい。
結合度を減らすというのは非常に難しい。何度も何度も失敗し続けて、ようやくここは分けた方が良かったんだなと気付く。次に活かそうと心に決める。そしてまた同じ過ちを繰り返していくわけだ。
まずは実装することだ。これが一番の早道だ。まずはがっつり結合した関数をあえて作るといい。何も考えずに作ろう。
そしてその後に、一部分使いまわしたいとおもうことがあるはずだ。その時に関数に切り出そう。それを繰り返すといい。そのうち初めから分けた方が良いと気付く。
何事も経験が必要である!経験を積まないプログラマは丸めてゴミ箱に捨ててしまえ。
さて、先の例で言うならば、私ならadd_result_outputという関数を作ってしまうだろう。だって、addとresultを連続して呼ぶのはめんどくさいんだもん。一連の流れをいつも使うのなら、その流れをやってくれる関数を作ればいいじゃないか。
function add_result_output ($iVar, $iVar2) { $r = add($iVar, $iVar2); echo result($r); }
もっと言えばクラス化してしまってもいいかもしれない。どんな感じになるかは君の手を動かして確認しよう!
このTipsはとてもわかりにくく、ニッチ過ぎる部分も多いかもしれない。
あくまでも「より良いPHPerにならないための20Tips」なのだ。
君はこの記事を鵜呑みにしてはならない。PHPをPHPと見抜けないPHPerはPHPを使うのは難しい。
もし、あなたがPHPプログラマなら、公式のPHPドキュメントはあなたのケツの穴を拭くための紙になるだろう。
私は、それぞれのセクションを眺めて、各関数でどんなことが出来るかなんぞ、歯クソのゴミ程に役に立たないとおもっている。動けばいい。はは。
あなたは、PHPで用意された既製関数で多くのことが実現できることに、(俺の仕事を減らすなと)驚くはずだ。
この記事があなたの役に立たない事を。
ふざけんな!
この記事に書かれている内容は、丸めてゴミ箱に捨てた方が良いレベルです。
ひーほー。いやぁさてさて一体このコード中に何度create_functionで匿名関数が生成されたのかふと気になったあなたのためにこんな関数を作ってみたよ!
function get_lambda_functions () {
$i = 1;
$funcs = array();
while(function_exists("\0lambda_$i")){
$funcs []= "\0lambda_$i";
$i++;
}
return $funcs;
}
$a = create_function('','echo 100;');
$a = create_function('','echo 200;');
$a = create_function('','echo 300;');
print_r(get_lambda_functions());
Array
(
[0] => lambda_1
[1] => lambda_2
[2] => lambda_3
)
ね。凄いでしょ。あとはforeachとかで存在する全ての匿名関数を実行したりすると面白いかもネ!
foreach ( get_lambda_functions() as $lambda ) { $lambda(); }
ぼく「えっ」
PHP「$a="0x0A";++$aは11になりますが」
ぼく「いえ"0x0B"です」
PHP「えっ」
ぼく「えっ」
PHP「まだインクリメントしたことがないということでしょうか」
ぼく「えっ」
PHP「えっ」
ぼく「変化するってことですか」
PHP「なにがですか」
ぼく「型が」
PHP「ああ文字列でも整数っぽい文字列なら自動で型変換されますよ」
ぼく「そうなんだすごい」
PHP「ではインクリメントいたしましょうか11ですよ」
ぼく「でも"0x0A"は明示的にキャストしたら0になりますよね」
PHP「えっ」
ぼく「えっ」
PHP「ああ16進数のことなら10進数に自動で変換してから演算するんですよ」
ぼく「なにそれこわい」
PHP「for($i="0w9Z";$i<12;$i++){echo $i;}と書くと $i="0w9Z", "0x0A", 11と変化します」
ぼく「なにそれきもちわるい」
PHP「えっ」
ぼく「えっ」
諸氏は、下記のような事をどうしているのだろうか。
・ただし、時刻の登録にマウスを操作するような煩雑さは、断じて許容できない
・常駐すんなボケ
とりあえず、if文なんて高尚なものを使ったことなかったけど、バッチファイルでやってみた。
@echo off REM 1-31の日付でしか登録できない低能アラーム REM 時刻は必ず入力されるものとみなす REM よって、組み合わせはDAY×MESGのみで考える。 REM 変数の初期化 SET yotei_day = 0 SET yotei_mesg = "" SET /P yotei_time="アラームを表示する時刻 :" SET /P yotei_day="アラームを表示する日付(1-31で指定、省略した場合は今日) :" SET /P yotei_mesg="表示するメッセージ(省略時は、予定チェック) :" REM 条件分岐。バッチのelseはしょぼすぎる。複数条件指定できない?? if "%yotei_day%" == "0" goto :NO_DAY if "%yotei_mesg%" == "" goto :NO_MESG REM 指定したもの -> DAY,MESG echo %yotei_day%日の%yotei_time%に通知します' at %yotei_time% /NEXT:%yotei_day% net send pc_Name "%yotei_mesg%" goto :SLEEP :NO_DAY REM 分岐: + NO_MESG if "%yotei_mesg%" == "" goto :NO_MESG_NO_MESG REM 指定したもの -> MESG echo 次の%yotei_time%'に通知します' at %yotei_time% net send pc_Name "%yotei_mesg%" goto :SLEEP :NO_MESG REM 分岐: + NO_DAY if "%yotei_day%" == "0" goto :NO_DAY_NO_MESG REM 指定したもの -> DAY echo %yotei_day%日の%yotei_time%に通知します' at %yotei_time% /NEXT:%yotei_day% net send pc_Name "予定チェック" goto :SLEEP :NO_DAY_NO_MESG REM 指定したもの -> なし(時刻のみ) echo %yotei_day%日の%yotei_time%に通知します' at %yotei_time% net send pc_Name "予定チェック" goto :SLEEP REM 終了 :SLEEP ping 127.0.0.1 -n 2 > nul:
これに適当な名前をつけて、ランチャのfenrirで起動させる。
キーボードのみの操作で済むので、とても快適ではあるものの、見ての通りnet sendを使うため、
Windows messenger serviceを起動させるという、常駐ソフトの方がマシな本末転倒なウンコーな一品である。
ActiveDirectoryとかグループポリシーでmessenger制限されてたら使えないし。
VBやWSHなら色々できそうだけど、これ以上機能はいらんのよね。いっそ、メッセージはtxtに書き込んで、それを開くだけにするか・・・・。
### しかし「>」を表示させるのに、数値参照文字じゃないとダメとか・・・。
### &#62;を半角にしたら>になりますよっと。。。
http://anond.hatelabo.jp/20081020054933
1つめ!!
1つは、タグをエスケープしわすれている箇所がある点
↑これは勉強になる!実際にやってみます!
変更前
//変更前 echo $server_id."/".$id."_".$secret."_s.jpg' border=0 title=$title alt=$title />"; echo $server_id."/".$id."_".$secret."_s.jpg' border=0 title=nl2br(htmlspecialchars($title, ENT_QUOTES)) alt=nl2br(htmlspecialchars($title, ENT_QUOTES)) />"; //変更後
2つめ!!
もう1つが、htmlの属性値(alt=ナントカとか)をクオートでくくってない点
↑これは勉強になる!実際にやってみます!
変更前
//変更前 echo $server_id."/".$id."_".$secret."_s.jpg' border=0 title=$title alt=$title />"; $title = nl2br(htmlspecialchars($title, ENT_QUOTES)); echo $server_id."/".$id."_".$secret."_s.jpg' border=0 title='\"$title\"' alt='\"$title\"' />"; //変更後
このソースがの中で書かれてるのでちゃんち「"」の前の「\」をつけて「\"」って言うのが忘れかけてて危なかったです!(えへ)
完璧やないかーい!
http://flickr2.in/fli.html?data=%3Cscript%3Ejavascript%3Aalert(%27xss%27)%3B%3C%2Fscript%3E
ちゃんとソースも
<a href='http://farm4.static.flickr.com/3022/2930297659_dc20386697.jpg' rel=lightbox><img src='http://farm4.static.flickr.com/3022/2930297659_dc20386697_s.jpg' border=0 title='"XSS session 1"' alt='"XSS session 1"' /></a>
とうまく表示されてます!
本当に有難う御座います!!
簡単に言うと
↓
nl2br(htmlspecialchars($hensuu, ENT_QUOTES)) と出力のところで囲んであげて
↓
"nl2br(htmlspecialchars($hensuu, ENT_QUOTES))" 更に"とかで囲んであげる
簡単に言うと、この数十個の文字で変数で囲んということだったのですか??
この数文字の魔法を教えてもらってたらすぐ実践してたんですか・・・。
でも、もしかしたら実はもっとXSSの脆弱性って色々深い事があるんですかね!?
それだけエガミくんの生み出すものが注目されているってことだよ。目立つとどうしても悪い人も寄ってきちゃうから困るよね。
なるほど!!!こういってもらえると、凄く嬉しいです!!!
はいこんにちは! Hamachiya2だよ。
alertでなくなったね。こんな短時間ですごい。
エガミくん飲み込みはやい感じだね…。
ええと、あとは、下の方の画像で、どうもマーキータグ(marquee)が埋め込まれてるみたいってことだよね。
うん。もう一回、htmlソースを表示ってして確認してみたよ。
こんなのが埋め込まれてた。
<img src='http://farm4.static.flickr.com/3120/2784053843_b7a7d07c9a_s.jpg' border=0 title=<marquee>test XSS</marquee> alt=<marquee>test XSS</marquee> />
これは問題点が二つあってね、
1つは、タグをエスケープしわすれている箇所がある点
もう1つが、htmlの属性値(alt=ナントカとか)をクオートでくくってない点
この二つを直していこうか!
phpのプログラムの中のどこかで、<img>タグを出そうとしている部分があるはずだよ。
まずはそれを探そう。
そしたらきっと、その部分は、imgにphpの変数を色々埋め込んで出そうとしているはず。
たとえばこんな風に。
echo "<img src='xxxxxx" . $hensuu1 "' title=" . $hensuu2 . " />";
これの$hennsuuも全てhtmlspecialchars()してあげる感じかな。
echo "<img src='xxxxxx" . htmlspecialchars($hensuu1, ENT_QUOTES) "' title=" . htmlspecialchars($hensuu2, ENT_QUOTES) . " />";
こうだね。
そうすればmarqueeタグが埋め込まれていても、
<img src="xxxxxxxx" title=<marquee>test xss</marquee> />
こんな風にmarqueeとかがタグじゃなくなるので防げる。
でも完全じゃないんだこれ。
さっきも言った、属性値のクオートが足りてないから、ちょっと工夫すればxssやられちゃう。
詳細は長くなるので今は省くけど。
だから上の対処に加えて、titleとかaltとかの中身が、htmlでみた時に、ダブルクオートかシングルクオートで
くくられているようにしてやれば、いい感じになるよ!
<img src="xxxxx" title=ぺろぺろ alt=ぺろぺろ />
こうじゃなくて
<img src="xxxxx" title="ぺろぺろ" alt="ぺろぺろ" />
こうなるようにしよう。
(追記)
ちなみに、何で悪い大人の人はXSSの脆弱性を突いてきて悪い事をするんですかー??
それだけエガミくんの生み出すものが注目されているってことだよ。
目立つとどうしても悪い人も寄ってきちゃうから困るよね。
http://anond.hatelabo.jp/20081020051835
ブラウザから「htmlのソースを表示」ってしてみてくれるかな。
とアドバイスを貰って
http://flickr2.in/fli.html?data=%3Cscript%3Ejavascript%3Aalert(%27xss%27)%3B%3C%2Fscript%3E
のソースを見てみたよ!
<title> htmlspecialchars(<script>javascript:alert('xss');</script>, ENT_QUOTES)の画像一覧 - flickr2.in </title>
あ・・・!さっきのhtmlspecialcharsがうまくいってなかったんだ・・・!
なるほど、じゃぁ今後は絶対
エラーが出ている → エラーの出ているページのソースで何処が問題が確認
と言う事を心がけます!
勉強になるなぁ・・・。
<title> <?php $query = $_GET["data"]; echo nl2br(htmlspecialchars($query, ENT_QUOTES)); echo "の画像一覧"; ?> - flickr2.in </title>
↑のように何箇所が出力箇所があって、全部しらみつぶしに直してみたよ!
一箇所だけ直してて、他は・・・となってたんだけど先生に教えてもらった方法を使ったら
凄く簡単に問題点が分かって凄く早く問題解決できた!
http://flickr2.in/fli.html?data=%3Cscript%3Ejavascript%3Aalert(%27xss%27)%3B%3C%2Fscript%3E
わーいわーい!!!!
<img src='http://farm4.static.flickr.com/3135/2896115083_333bcb8862_s.jpg' border=0 title=test pilot of a show alt=test pilot of a show />
の箇所が治ってない!!!
え・・・!?コレはどういう事!?
なるほどーーー!!!徹夜ですかー!
さすが先生!!!参考になりやす!
ちなみに、何で悪い大人の人はXSSの脆弱性を突いてきて悪い事をするんですかー??
凄く、僕は寂しいです。
http://anond.hatelabo.jp/20081020045037
早速お返事有難う御座います!id:hiroyukiegamiです!
なるほど!前回
$Hamachiya2 = htmlspecialchars($_GET["data"], ENT_QUOTES); //←ここを追加$params = array('api_key' => 'フリッカーのAPIキー',
'method' => 'flickr.photos.search',
'text' => $Hamachiya2,
と$_GET のところ(入力)で何かしようとしてたけど
”レスポンスのコーナーのところ、htmlに変数埋め込んで echo してるとこ。”
が大事なんですね!勉強になります!
先生からの説明で
その際に注意すべきは、htmlの属性内(alt=ナントカとか、src=ナントカとか)に変数を埋め込んでいる場合は、ちゃんとクオートの類もエスケープする感じ?
とあって、うーんどういう意味だろうとグーグル先生に聞いてみたよ!
クオート(正確にはクォーロみたいですね!) ・・・ クォートとは、文字が通常有する特別な意味を奪うことである。
つまり”とか’の事ですね!勉強になります。
今回echoで実際に書き出している部分は
$query = $_GET["data"];echo "$query";
でした!
じゃぁ、ここをこうすればいいのかな??
$query = $_GET["data"];echo nl2br(htmlspecialchars($query, ENT_QUOTES));
http://php.benscom.com/manual/ja/function.nl2br.php
nl2br ・・・ nl2br()関数は、改行文字(\n、\r、\n\rなど)を
タグに置き換えます。
うーん、、、ではid:zapa氏の荒らしURLをみてみよう・・・。
http://flickr2.in/fli.html?data=%3Cscript%3Ejavascript%3Aalert(%27xss%27)%3B%3C%2Fscript%3E
id:Hamachiya2先生!全然解決されないよ(涙)
今回nl2brタグで改行を回避したけど・・・これじゃダメみたいですね・・・!
<script>javascript%3Aalert('xss')%3B<%2Fscript>
・・・!!!
先生!僕は大きな間違いをしていたようです!
htmlspecialcharsとかnl2brタグも大事だけど、、、これじゃ解決できないみたい!!!!
これって、どうしたらいいんですか!?ヘルプミー!!!
なんとか直さないと・・・。
どうにか解決方法はありますかね??
はい! こんにちは! Hamachiya2ですよ!
いま、エガミくんの書き込みみながら、ざくっとソースみてみたよー。
XSSの対策ってね、ぼくもよくわかってないけど、
「出力時にエスケープする」っていうのが定石らしいよ。
でもエガミくんのやろうとしたのは「入力時のエスケープ」だね。
だから $_GET のところ(入力)で何かをするのではなくて…、
レスポンスのコーナーのところ。htmlに変数埋め込んで echo してるとこ。
そこの全ての変数をエスケープしちゃう方がいい感じかな。
その際に注意すべきは、htmlの属性内(alt=ナントカとか、src=ナントカとか)に変数を埋め込んでいる場合は、ちゃんとクオートの類もエスケープする感じ?
echo '<img src="' . $hensuu . '" alt="ぺろぺろ" />';
とかなら、$hensuu はダブルクオートもエスケープだよ。
あ、htmlspecialchars ってダブルクオートはデフォルトで「"」に変換されるんだっけ?
ちょっと試してみてね。
もし↓こんな風に、htmlの属性のクオートにシングルクオートを使ってる場合だと…
echo "<img src='" . $hensuu . '" alt='ぺろぺろ' />";
これは
echo "<img src='" . htmlspecialchars($hensuu, ENT_QUOTES) . '" alt='ぺろぺろ' />";
こうする感じかな?
あと、サンプルコードには含まれていなかったけど、
本番の方だと、htmlのheadの中でも変数つかってるよね。
たとえば、xxxで検索すると、titleタグやメタタグにもxxxが入ってくる。
そのあたりも、とりあえず「表示しようとしてる箇所」の「表示する一歩手前」で全てエスケープしてやればいいと思うよ。
もしかしたら言ってること間違ってるかもしれないけど、
その時はきっと誰かが突っ込んでくれるはずー。
追記
あと、寂しいことってなに?
せっかく書いたから匿名でのせてみるよ
使い方は
必要なものを gem で取ってくるにはこうすればいいよ
長すぎてelispが消えたから続きがあるよ
@echo off setlocal set WD=%~dp0 cd /d %WD% ruby get_movies.rb ruby get_images.rb ruby create_m3u.rb
user: ユーザID password: パスワード ids_file: ids.txt done_file: ids_done.txt movies_dir: movies log4r_config: pre_config: global: INFO loggers: - name: app type: Log4r::Logger level: INFO outputters: - STDOUT - FILE outputters: - name: STDOUT type: Log4r::StdoutOutputter formatter: type: Log4r::PatternFormatter pattern: "%d [%l] %C - %M" date_pattern: "%H:%M:%S" - name: FILE type: Log4r::FileOutputter filename: "#{LOGDIR}/sangels.log" formatter: type: Log4r::PatternFormatter pattern: "%d [%l] %C - %M" date_pattern: "%Y-%m-%d %H:%M:%S"
require 'fileutils' require 'logger' require 'mechanize' BASEDIR = File.dirname($0) require "#{BASEDIR}/util" require "#{BASEDIR}/sangels" $config = load_config(BASEDIR) prepare_logger(BASEDIR) $log = new_logger("get_movies") WWW::Mechanize.log = new_logger("mechanize") WGet.log = $log class IDFile def initialize(file) @file = file unless File.exist?(@file) Fileutils.touch(@file) end end def ids(contains_comment = nil) File.open(@file) {|io| io.to_a.map {|x| x.chomp }.select {|x| if x.empty? nil elsif contains_comment true else not /^\s*\#/ =~ x end } } end def add(id) ids = ids(true) unless ids.any? {|x| x == id} write(ids + [id]) end end def delete(id) ids = ids(true) if ids.any? {|x| x == id} write(ids - [id]) end end def write(ids) File.open(@file, "w") {|io| ids.each {|x| io.puts x} } end end $log.info("BEGIN #{$0} ================") exit_code = 0 begin ids_file = IDFile.new($config.ids_file) done_file = IDFile.new($config.done_file) movies_dir = $config.movies_dir wget = WGet.new sangels = SAngels.new sangels.login($config.user, $config.password) ids_file.ids.each {|id| begin movies = sangels.movies(id) rescue SAngels::Movies::InvalidMoviesError $log.warn("invalid movie id: #{id}") next end dir = File.expand_path(id, movies_dir) movies.each {|link| wget.retrieve(link.href, dir) } expected = movies.movie_links.map{|x| File.basename(x.href)} actual = Dir.glob("#{dir}/*").map {|x| File.basename(x)} if (expected - actual).empty? done_file.add(id) ids_file.delete(id) end } rescue => e $log.error(e) exit_code = 1 end $log.info("END #{$0} (#{exit_code}) ================") exit exit_code
require 'fileutils' require 'logger' require 'mechanize' require 'ostruct' BASEDIR = File.dirname($0) require "#{BASEDIR}/util" require "#{BASEDIR}/sangels" $config = load_config(BASEDIR) prepare_logger(BASEDIR) $log = new_logger("get_images") WWW::Mechanize.log = new_logger("mechanize") WGet.log = $log $log.info("BEGIN #{$0} ================") exit_code = 0 begin movies_dir = $config.movies_dir sangels = SAngels.new sangels.login($config.user, $config.password) thumbnails = sangels.thumbnails Dir.glob("#{movies_dir}/*").each {|dir| next unless File.directory? dir id = File.basename(dir) url = thumbnails.url(id) unless url $log.warn("#{id} is not found") next end path = File.expand_path("00_thumbnail#{File.extname(url)}", dir) next if File.exist? path $log.info("retrieving #{url}") thumbnail = thumbnails.get_file(id) File.open(path, "wb") {|io| io.write(thumbnail)} } rescue => e $log.error(e) exit_code = 1 end $log.info("END #{$0} (#{exit_code}) ================") exit exit_code
BASEDIR = File.dirname($0) require "#{BASEDIR}/util" $config = load_config(BASEDIR) movies_dir = $config.movies_dir Dir.glob("#{movies_dir}/*") {|dir| next unless File.directory? dir name = File.basename(dir) files = Dir.glob("#{dir}/*.wmv").sort File.open("#{movies_dir}/#{name}.m3u", "w") {|io| files.each {|file| io.puts "#{name}/#{File.basename(file)}" } } File.open("#{dir}/00_movies.m3u", "w") {|io| files.each {|file| io.puts "#{File.basename(file)}" } } }
require 'mechanize' require 'hpricot' BASEDIR = File.dirname($0) require "#{BASEDIR}/util" class SAngels HOST = "real2.s-angels.com" LOGIN_URL = "http://#{HOST}/member/" INFO_URL = "http://#{HOST}/teigaku/item.php" THUMBNAILS_URL = "http://#{HOST}/teigaku/" THUMBNAIL_URL = "http://#{HOST}/images/default/thumb/" def initialize() @agent = WWW::Mechanize.new end def login(user, password) login_form = @agent.get(LOGIN_URL).forms.find {|form| form.fields.any? {|field| field.name == "frmLoginid"} } login_form.frmLoginid = user login_form.frmPw = password @agent.submit(login_form) end def movies(id, no_validate = nil) Movies.new(@agent, id, !no_validate) end def thumbnails Thumbnails.new(@agent) end class Thumbnails def initialize(agent) @agent = agent doc = Hpricot(@agent.get_file(THUMBNAILS_URL)) elems = doc.search("div[@class=realthum]/a") @links = Hash( elems.map {|elem| href = elem["href"] id = $1 if /ID=(.+)/ =~ href url = elem.search("img")[0]["src"] [id, url] }) end def get_file(id) @agent.get_file(url(id)) end def url(id) @links[id] end def exist?(id) url(id) end end class Movies class InvalidMoviesError < StandardError end def initialize(agent, id, no_validate) @agent = agent @id = id if !no_validate && !valid? raise InvalidMoviesError end end def info_page_url "#{INFO_URL}?ID=#{@id}" end def info_page @agent.get(info_page_url) end def movies_page @agent.click(info_page.links.find {|link| /P=10/ =~ link.href}) end def movie_links movies_page.links.select {|link| /wmv$/ =~ link.href }.sort {|a, b| File.basename(a.href) <=> File.basename(b.href) } end def valid? info_page.uri.to_s == info_page_url end def each(&block) orig_links = movie_links orig_links.each {|orig_link| link = movie_links.find {|l| File.basename(l.href) == File.basename(orig_link.href)} block.call(link) } end end end
require 'log4r' require 'log4r/yamlconfigurator' require 'singleton' require 'fileutils' require 'ostruct' def Hash(a) Hash[*a.flatten] end def load_config(basedir) OpenStruct.new(File.open("#{basedir}/config.yaml") {|io| YAML.load(io)}) end def new_logger(name) Log4r::Logger.new("app::#{name}") end def prepare_logger(basedir, logdir = nil) logdir ||= basedir Log4r::YamlConfigurator["LOGDIR"] = logdir Log4r::YamlConfigurator.load_yaml_file("#{basedir}/config.yaml") end class NullObject include Singleton def method_missing(message, *arg) NullObject.singleton end end class WGet class << self attr_accessor :log def initialize super @log = NullObject.singleton end end def log self.class.log end def retrieve(url, dir) FileUtils.mkdir_p(dir) file = File.expand_path(File.basename(url), dir) if File.exist?(file) log.info("already retrieved #{url}") return true end tmp = "#{file}.part" log.info("retrieving #{url}") ret = system("wget", "-c", "-O", tmp, url) if ret log.info("retrieving succeeded #{url}") File.rename(tmp, file) else if $? == 0x020000 # Ctrl-C exit($?) else log.error("retrieving failure #{url} (#{$?})") end end return ret end end
今更だけど60行で作るPHP用テンプレートエンジンをクラス化した。
<?php class NoSixTemplate{ protected $template_dir = null; protected $template = null; protected $context = array(); function __construct($filename = null, $directory = null){ $this->set_template($filename); $this->set_dir($directory); } function set_template($filename){ $this->template = $filename; } function set_dir($directory){ if(substr($directory, -1) != '/') $directory .= '/'; $this->template_dir = $directory; } function set_data($context_key, $context_data, $overwrite = false){ if(empty($this->context[$context_key]) || $overwrite){ $this->context[$context_key] = $context_data; }else{ if(is_array($context_data) && is_array($this->context[$context_key])){ $this->context[$context_key] = array_merge($this->context[$context_key], $context_data); }else{ $this->context[$context_key] .= $context_data; } } } function reset($context_key = null){ if(is_null($context_key)){ $this->context = array(); }else{ $this->context[$context_key] = null; } } function convert_template(){ $filename = $this->template_dir.$this->template; $cachename = $filename.'.cache'; if(!file_exists($cachename) || filemtime($cachename) < filemtime($filename)){ $s = file_get_contents($filename); $s = $this->convert_string($s); if(is_writable($cachename) || is_writable($this->template_dir)){ file_put_contents($cachename, $s); } } return $cachename; } function convert_string($s) { $s = preg_replace('/^<\?xml/', '<<?php ?>?xml', $s); $s = preg_replace('/#\{(.*?)\}/', '<?php echo $1; ?>', $s); $s = preg_replace('/%\{(.*?)\}/', '<?php echo htmlspecialchars($1,ENT_QUOTES); ?>', $s); return $s; } function display(){ $cache = $this->convert_template(); extract($this->context); include($cache); } } ?>
<?php require_once('NoSixTemplate.php'); $template = new NoSixTemplate('template.php', 'template_directory'); $template->set_data('title', 'Example'); $template->set_data('list', array(10,'<A&B>',NULL)); $template->display(); ?>
#! /bin/csh -f ##処理選択 echo "処理を選んでください" echo "1 Dir生成" echo "2 Dir削除" echo " " echo -n "No入力 1 or 2 :" set num <$ ##入力エラー処理 ##1でない or 2でない場合はエラー出してシェル再読み込み if [ $num != 1 -a $num != 2 ] ; then echo "入力エラー。1か2を入力してください" select.csh fi ##以下numは1か2 ##実行許可入力 if [ $num = 1 ] ; then echo "1 Dir生成を実行します Y or N" echo -n "Y or N を入力してください" set Ans <$ fi if [ $num = 2 ] ; then echo "2 Dir削除を実行します Y or N" echo -n "Y or N を入力してください" set Ans <$ fi ##入力エラー処理 ##Yでない or Nでない場合はエラーを出してシェル再読み込み if [ $Ans != Y -a $Ans != N ] ; then echo "入力エラー。YかNを入力してください" select.csh fi ##以下AnsはYかN ##シェル選択&実行 if [$num =1 -a Ans = Y] ; then source kadai2.csh echo "Dir生成が終了しました" fi if [$num =2 -a Ans = Y] ; then source del.csh echo "Dir削除が終了しました" fi
前の60行テンプレートエンジンを改良して、レイアウトテンプレート機能を追加してみた(それでも全部で90行)。
レイアウトテンプレート機能とは、例えば個別のテンプレートが<table>...</table>を出力して、それをレイアウトテンプレートが<html><body>...</body></html>で囲って出力するとかそんなの。
詳しくは終わりの方のサンプルをみてくれ。
これは Ruby on Rails(とその仲間たち)にある便利機能のひとつ。
ついでにいうとSmartyにはない機能のひとつ。
今まで知らなかった人はぜひ試してくれ。チョー便利だから。
前回はたくさんのブックマークありがと。
コメントで「男前テンプレート」と名前がついてたので、勝手に採用。
あと、これ以上の機能追加はしないので、各自勝手に改造して使ってくれ(そのためにコメントをつけてるから)。何でも人任せにするな。
コード:
<?php /* * OtokomaeTemplate.php -- レイアウトテンプレートに対応した90行のテンプレートエンジン * * - レイアウトテンプレート中で echo $_content; とすると中身が表示される。 * - テンプレート中で設定した変数をレイアウトテンプレートで使うことが可能。 * - レイアウトテンプレート名をテンプレート側で指定することも可能。 * - 使い方: * require_once('OtokomaeTemplate.php'); * $TEMPLATE_DIR = 'templates'; // 省略可、パーミッションに注意 * $LAYOUT_TEMPLATE = 'layout.php'; // 省略可 * $context = array('title'=>'Example', * 'list'=>array(10,'<A&B>',NULL)); * include_template('template.php', $context); * - 要 PHP 5.1 or later * - ライセンス: public domain (自由に改造してね) */ /* * 設定用のグローバル変数 */ $TEMPLATE_DIR = NULL; /* テンプレートを探すディレクトリ */ $LAYOUT_TEMPLATE = NULL; /* レイアウトテンプレートのファイル名 */ /* * テンプレートを読み込んで実行する。 * $_context は変数名をキー、値を要素とする連想配列。 * $_layout はレイアウトテンプレートのファイル名。 * - NULL または省略した場合は $LAYOUT_TEMPLATE を使う。 * - FALSE ならレイアウトテンプレートを使わない。 * - $_context['_layout'] = '...'; とすればテンプレート側でも指定可能。 */ function include_template($_filename, $_context, $_layout=NULL) { global $LAYOUT_TEMPLATE; $_content = render_template($_filename, $_context); if (@$_context['_layout'] !== NULL) // テンプレート側で指定された場合は $_layout = $_context['_layout']; // それを使う。 elseif ($_layout === NULL) // 引数で指定されなかった場合は $_layout = $LAYOUT_TEMPLATE; // デフォルトのファイル名を使う。 if ($_layout) { $_context['_content'] = $_content; // レイアウトテンプレート中で使う変数 $_content = render_template($_layout, $_context); } echo $_content; // or return $_content; } /* * テンプレートを読み込んで実行し、その結果を文字列で返す。 * include_template() の実体。 */ function render_template($_filename, &$_context) { $_cachename = convert_template($_filename); extract($_context); // 連想配列をローカル変数に展開 ob_start(); include($_cachename); // テンプレートを読み込んで実行 return ob_get_clean(); } /* * テンプレートファイルを読み込み、convert_string() で置換してから * キャッシュファイルに書き込む。読み込み時のロックは省略。 * (file_get_contents() もファイルロックできるようにしてほしいなあ。) */ function convert_template($filename) { global $TEMPLATE_DIR; if (! file_exists($filename) && $TEMPLATE_DIR) $filename = "$TEMPLATE_DIR/$filename"; $cachename = $filename . '.cache'; if (! file_exists($cachename) || filemtime($cachename) < filemtime($filename)) { $s = file_get_contents($filename); $s = convert_string($s); file_put_contents($cachename, $s, LOCK_EX); // LOCK_EX サポートは 5.1.0 から } return $cachename; } /* * テンプレートの中身を置換する。 * - '#{...}' を 'echo ...;' に置換 * - '%{...}' を 'echo htmlspecialchars(...);' に置換 * - ついでにXML宣言も置換 */ function convert_string($s) { $s = preg_replace('/^<\?xml/', '<<?php ?>?xml', $s); $s = preg_replace('/#\{(.*?)\}/', '<?php echo $1; ?>', $s); $s = preg_replace('/%\{(.*?)\}/', '<?php echo htmlspecialchars($1); ?>', $s); return $s; } ?>
<?php require_once('OtokomaeTemplate.php'); $TEMPLATE_DIR = 'templates'; $LAYOUT_TEMPLATE = 'layout.php'; $context = array('list'=>array(10,'<A&B>',NULL)); include_template('template.php', $context); ?>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <body> <h1>%{$title}</h1> <div id="maincontent"> <!-- テンプレートの内容 --> <?php echo $_content; ?> <!-- /テンプレートの内容 --> </div> </body> </html>
<?php // レイアウトテンプレート名をテンプレート中で指定する場合 ?> <?php //$_context['_layout'] = 'mylayout.php'; ?> <?php // レイアウトで使用する変数をテンプレート中で指定する場合 ?> <?php $_context['title'] = 'レイアウトのサンプル'; ?> <table> <?php foreach ($list as $i=>$item): ?> <tr bgcolor="#{$i % 2 ? '#FFCCCC' : '#CCCCFF'}"> <td>#{$i}</td> <td>%{$item}</td> </tr> <?php endforeach ?> </table>
出力例:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <body> <h1>レイアウトのサンプル</h1> <div id="maincontent"> <!-- テンプレートの内容 --> <table> <tr bgcolor="#CCCCFF"> <td>0</td> <td>10</td> </tr> <tr bgcolor="#FFCCCC"> <td>1</td> <td><A&B></td> </tr> <tr bgcolor="#CCCCFF"> <td>2</td> <td></td> </tr> </table> <!-- /テンプレートの内容 --> </div> </body> </html>
いくつか補足:
http://anond.hatelabo.jp/20071030034313 の二番煎じ
あまりのアホさに、作ってて気が狂いかけた
方針
using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Reflection; using Microsoft.CSharp; delegate void ConvertTemplateDelegate(TextWriter tw, Dictionary<object, object> args); static class TemplateGenerator { public static ConvertTemplateDelegate Generate(string code) { CompilerParameters param = new CompilerParameters(); param.GenerateInMemory = true; param.ReferencedAssemblies.Add("System.Web.dll"); CompilerResults rs = new CSharpCodeProvider().CompileAssemblyFromSource(param, ParseTemplate(code)); if (0 < rs.Errors.Count) { StringWriter sw = new StringWriter(); sw.WriteLine("Compile Error..."); foreach (CompilerError err in rs.Errors) sw.WriteLine(err.ToString()); throw new Exception(sw.ToString()); } return (ConvertTemplateDelegate) Delegate.CreateDelegate(typeof(ConvertTemplateDelegate), rs.CompiledAssembly.GetType("Template", true).GetMethod("Convert")); } private static string ParseTemplate(string code) { using (StringWriter sw = new StringWriter()) { sw.WriteLine("using System; using System.Collections.Generic; using System.IO; using System.Web;"); sw.WriteLine("public static class Template {"); sw.WriteLine("public static void Convert(TextWriter tw, Dictionary<object, object> args) {"); int index = 0; while (0 <= index && index < code.Length) { int i = code.IndexOf("<%", index); sw.WriteLine("tw.Write(\"{0}\");", EscapeString(i < 0 ? code.Substring(index) : code.Substring(index, i - index))); if (0 <= i) { i += 2; int i2 = code.IndexOf("%>", i); if (0 <= i2) { string cc = code.Substring(i, i2 - i); if (cc.StartsWith("=")) sw.WriteLine("tw.Write(HttpUtility.HtmlEncode(\"\"+({0})));", cc.Substring(1)); else sw.WriteLine(cc); i = i2 + 2; } } index = i; } sw.WriteLine("}}"); return sw.ToString(); } } private static string EscapeString(string code) { return code.Replace("\\", "\\e").Replace("\"", "\\\"").Replace("\t", "\\t").Replace("\n", "\\n").Replace("\r", "\\r").Replace("\\e", "\\\\"); } }
サンプル C# コード。ためしにテンプレートから Xml 生成して、標準出力してみる。
class Program { static void Main(string[] args) { ConvertTemplateDelegate func = TemplateGenerator.Generate(TemplateEngine.Resource1.template); using (StringWriter sw = new StringWriter()) { Dictionary<object, object> arg = new Dictionary<object, object>(); arg["title"] = "template sample"; arg["data"] = new string[] { "foo", "fooo", "<strong>foooooooooo!</strong>" }; func(sw, arg); Console.WriteLine(sw); } } }
サンプルテンプレート
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title><%= args["title"] %></title> </head> <body> <h1><%= args["title"] %></h1> <table> <% string[] data = (string[]) args["data"]; %> <% for(int i = 0; i < data.Length; i++) { %> <tr bgcolor="<%= i % 2 == 0 ? "#FFCCCC" : "#CCCCFF" %>"> <td><%= i %></td> <td><%= data[i] %></td> </tr> <% } %> </table> </body> </html>
出力例
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>template sample</title> </head> <body> <h1>template sample</h1> <table> <tr bgcolor="#FFCCCC"> <td>0</td> <td>foo</td> </tr> <tr bgcolor="#CCCCFF"> <td>1</td> <td>fooo</td> </tr> <tr bgcolor="#FFCCCC"> <td>2</td> <td><strong>foooooooooo!</strong></td> </tr> </table> </body> </html>
CodeDom 使って動的コンパイル……って、このコードのままだとセキュリティ的に大問題な気がするな。
素直に ASP.NET 使ったほうが楽だと直感した。
あと EscapeString すっごく自信ない。たぶん修正が必要だと思うw