PHPではコメントの記述方法がいくつか用意されていますが、注意しないといけないことがあります。


以下のようにpreg系関数での正規表現はよく使う人もいると思いますが、


<?php
if (preg_match('/<.*?>/', "text-String")) echo "match!";
?>


もしもこのpreg行をコメントした場合にどうなるかをわたしの環境(Windows Vista xampplite..PHP5.3.0)で見ていきます。



まず行頭に「//」を使って行末までをコメントします。

<?php
// if (preg_match('/<.*?>/', "text-String")) echo "match!";
?>


この場合、PHPスクリプトを実行したときに実はコメント行以降のソースコードがダダ漏れになってしまいます。
というのもpreg関数で指定した正規表現にはPHPコードの終了を表す「?>」が含まれるからです。



<?php
// if (preg_match('/<.*?>/', "text-String")) echo "match!";
?>


この挙動は「#」による行コメントでも同様になってしまいます。
なかなか気づきにくいことなので最初なにが起きたかわかりませんでした。


<?php
//
// if (preg_match('/<.*?>/', "text-String")) echo "match!";
//
?>


のように前後にコメント行を増やしても無駄です。
ということで、次に「/* ~ */」でコメントにしてみます。



<?php
/* if (preg_match('/<.*?>/', "text-String")) echo "match!"; */
?>


こちらだと問題なく望み通りに動作してくれます。


別の解決法を書いておくと、「削除する」ってのはこの際置いといて、「?>」を正規表現のメタ文字以外は文字定数を使って「?\x3e」のように書くなんてどうでしょ。。 いやぁわかりづらくなるだけだし消すってのがちいばんスマートな解決法ですねぇ

ただ、コメントにおいてこういう挙動が起こりうるということだけでも判明したからよかったよかった。



続いて似た例ですが、以下のような置換コードがあってこれがコメントされたとするとこれまた正規表現にコメントの終端を示す「*/」が含まれることから実行エラーになってしまいます。

<?php

/*

$dir= preg_replace('/(.*\/).*/', '$1', $path);

*/

?>



したがって次のように書き替えるなどしないと思うように動いてくれません。

<?php

/*

$dir= preg_replace('/(.*\/).*$/', '$1', $path);

*/

?>



一般的なブラウザでウェブページを見る場合にはHTTPヘッダを見ることもなければ気にすることもありません。

しかしウェブ開発の際にはHTTPでの詳細なやりとりを見られたらと思う場合があったりします。


そこで少々強引に、やり取りしているヘッダ領域を覗き見してみるための仕掛けを作ってみます。


PHPではfopen(), file(), file_get_contents()などの関数を使って手軽にHTTPリクエストを発行できるようになっています。(設定されてることが前提ですが)
しかしこれらの関数ではHTTPレスポンスヘッダ領域までは取得することができません。


そこでfsockopen()という関数を使うとヘッダ領域も取得することができるようになります。
指定したウェブページを取りに行って単に表示させるだけではそれで終わってしまいますので力技で仕掛けを施します。


簡単に言うとまず指定URLページ(ヘッダ付き)を取って来て、
そのページ内のリンク先やフォーム送信先を、スクリプト経由になるよう組み替えます。
こうすることで、次にリンクを押して開いたページもスクリプト経由になりヘッダのモニタリングを継続できることになります。


以下、作成したソースコードになります。



■ブラウザでHTTPヘッダをモニタリング(showheader.php)
<?php

// スクリプトURL取得
$port= "{$_SERVER['SERVER_PORT']}";
$script_url= 'http://' . $_SERVER['SERVER_NAME'] . ($port === "80" ? '' : ":{$port}") . $_SERVER['SCRIPT_NAME'];
isset($_REQUEST['_local_url']) or $_REQUEST['_local_url']= "";



// URL指定なければ初期画面表示&処理終了
if (empty($_REQUEST['_local_url']) && empty($_REQUEST['x_local_url']))
    echo msg(form());



// HTTPリクエスト発行
$res= request(isset($_REQUEST['x_local_url']) ? $_REQUEST['x_local_url'] : $_REQUEST['_local_url'], $_REQUEST);



// レスポンスHTMLのhref="*"内部組み替え情報取得
$base= preg_replace('/(http:\/\/.*\/)[^\/]*/', '$1', isset($_REQUEST['x_local_url']) ? $_REQUEST['x_local_url'] : $_REQUEST['_local_url']); // base位置
substr($base, -1) !== "/" and $base.= '/';
$base2= preg_replace('/(http:\/\/.*\/).*\/.*/', '$1', $base); // baseの1つ上
$mount= preg_replace('/(http:\/\/.*?\/).*/', '$1', $base2); // マウントポイント



// 画面表示用HTMLヘッダ領域生成
$head= <<<EOT
<html><head>
<meta http-equiv="Content-type" content="text/html; charset=Shift_JIS">
EOT;
// レスポンスHTMLから<style>~</style>, <script>~</script>, <link>情報を抜き取りセット
preg_match_all('/<style.*?<\/style>\s*|<script.*?<\/script>\s*|<link.*?>\s*/is', $res[2], $match);
foreach ($match[0] as $value) $head.= $value;
// レスポンスHTML頭~<body>タグまで除去
$tail= preg_replace('/.*?<body.*?>/is', '', $res[2], 1);
// href="*"部分をスクリプト経由に組み替え
$tail= preg_replace('/(<a\s[^>]*?href="?)([^>]*?>)/ise', 'preg_match("/^(?:JavaScript:|mailto:|tel:|#)/i", "$2") ? "$1$2" : ("$1" . $script_url . "?_local_url=" . url_replace("$2"))', $tail);

$tail= preg_replace('/(<a\s[^>]*?href="?)#/ise', '"$1" . $script_url . (empty($_SERVER["QUERY_STRING"]) ? "" : "?" . $_SERVER["QUERY_STRING"]) . "#"', $tail);
// <form action="*">部分をスクリプト経由に組み替え
$tail= preg_replace('/(<form\s[^>]*?action="?)([^>"\']*)([^>]*?>)/ise', '"$1" . $script_url . "$3". "<input type=\"hidden\" name=\"x_local_url\" value=\"" . url_replace("$2") . "\"><input type=\"hidden\" name=\"x_local_method\" value=\"" . getmethod("$0") . "\">"', $tail);



// 実際にHTMLリクエストしたURLから<base href="*">作成して表示用ヘッダに追加
preg_match('/^(?:POST|GET)\s+(\S+)/', $res[0], $match);
$base= preg_replace('/^(http:\/\/.*\/)[^\/]*/', '$1', $match[1]);
$head.= "<base href=\"{$base}\">";



// 画面表示
msg($head . form($res[0] . $res[1], $match[1]) . $tail);




function getmethod($s)
{
    return preg_match('/method="?post/i', $s) ? "POST" : "GET";
}
function url_replace($s)
{
    global $base,$base2,$mount;


    if (substr($s, 0, 4) === "http") return $s;
    else if (substr($s, 0, 3) === "../") return $base2 . substr($s, 3);
    else if (substr($s, 0, 1) === "/") return $mount . substr($s, 1);
    else return $base . str_replace("./", "", $s);
}
function request($url, $data=array())
{
    $_url= parse_url($url);
    $nl= "\x0d\x0a";
    $reqdata= $method= '';


    foreach ($data as $key=>$value){
        if (preg_match('/^(?:_local_|s_pers|flt_frq\d)/', $key)) break;
        if ($key === "x_local_url") continue;
        if ($key === "x_local_method"){ $method= $value; continue; }
        empty($reqdata) or $reqdata.= '&';
        $reqdata.= $key . '=' . urlencode($value);
    }
    $method or $method= $reqdata==="" ? "GET" : "POST";
    if ($method === "GET"){
        $reqdata and $url.= "?" . $reqdata;
    }


    $res= '';
    if ($fp= fsockopen($_url['host'], 80, $errno, $errstr, 10)){
        $senddata= "{$method} {$url} HTTP/1.0{$nl}";
        $senddata.="User-Agent: My-Script/" . phpversion() . $nl;
        $senddata.="Host: {$_url['host']}{$nl}";
        $senddata.="Referer: http://" . $_url['host'] . $nl;
        $senddata.="Accept: */*{$nl}";
        $senddata.="Accept-Language: ja,en-us{$nl}";
        $senddata.="Content-Type: application/x-www-form-urlencoded{$nl}";
        $senddata.="Connection: close{$nl}";
        $method === "POST" and $senddata.="Content-Length: " . strlen($reqdata) . $nl . $nl;
        $method === "POST" and $senddata.=$reqdata . $nl;
        $senddata.= $nl;


        fputs($fp, $senddata);
        while (!feof($fp)){
            $res.= fgets($fp, 4096);
        }
        fclose($fp);
    }
    $res= mb_convert_encoding($res, "SJIS-win", "SJIS-win,UTF-8,CP51932,JIS");
    list($head, $body)= explode($nl . $nl, $res, 2);


    return array($senddata, $head, $body);
}
function msg($s)
{
    echo $s;
    exit;
}
function form($s="", $url="")
{
    $s= htmlspecialchars($s);


    return <<<EOT
</head><body>
<form name="disp" action="{$GLOBALS['script_url']}" method="post">
<textarea cols="40" rows="10" name="_local_monitor" style="width:95%">{$s}</textarea>
<input type="text" name="_local_url" value="{$url}" size="80" style="width:95%"><br>
<input type="submit" name="_local_submit" value="送信">
</form><hr>
EOT;
}


?>



[スクリプト実行例]
そろそろホンキ出す-ブラウザでHTTPヘッダのモニタリング



完全なものではありませんが、一応できたことにしておきます。

ページ表示段階で呼び出される外部ファイル(css,JavaScript,画像など)はスクリプト経由にすると大変なので<base href="*">をセットすることでお茶を濁しています。
クッキー処理までは面倒なので対応していません。



■mb系関数を使う方法


mb系関数を使う場合、文字コードを指定する文字コード変換は特に設定は要らないが、文字操作する場合には扱う文字コードを内部に知らせる設定が必要になる。
というわけで以下のような感じで設定する。

mb_internal_encoding("SJIS-win"); // シフトJISの場合
mb_internal_encoding("JIS"); // JISコードの場合
mb_internal_encoding("CP51932"); // 日本語EUCの場合
mb_internal_encoding("UTF-8"); // UTF-8の場合



[部分文字列を取り出す]

$s= "日本語テスト文字ハンカクabc!";
echo mb_substr($s, 0, 10); // 先頭10文字
echo mb_substr($s, -3); // 末尾3文字


[N文字で切り分ける]
foreach (range(0, mb_strlen($s)-1) as $i) // 1文字ずつ表示
    echo mb_substr($s, $i, 1),"<br />\n";

foreach (range(0, mb_strlen($s)-1, 3) as $i) // 3文字ずつ表示
    echo mb_substr($s, $i, 3),"<br />\n";


■正規表現を使って文字コードで区切る方法


正規表現を使う場合はもちろん内部の文字コード云々は一切関係ないので不要


[シフトJISを3文字ずつ表示させる]

preg_match_all('/(?:[\x81-\x9f\xe0-\xfc][\x40-\x7e\x80-\xfc]|[\x09\x0a\x0d\x20-\x7e\xa1-\xdf]){1,3}/', $s, $match);
foreach ($match[0] as $value)
    echo $value,"<br />\n";



[日本語EUCを1文字ずつ表示させる]

preg_match_all('/\x8e[\xa1-\xdf]|\x8f?[\xa1-\xfe][\xa1-\xfe]|[\x09\x0a\x0d\x20-\x7e]/', $s, $match);
foreach ($match[0] as $value)
    echo $value,"<br />\n";