PR

[PHP] 404レポート(PHP版)

以前CGI 版で404 レポートを出力するプログラムを作ったのだが、今回はPHP 版を作った。何故つくろうかと思ったかというと、WordPress などにはプラグインで機能するものがあるが、これらを利用しないサイトでも移転した際の案内を付けたかったから。

ところで、疑問に思うのは同様のプログラムを見つけることができないのは何故だろう。見つけ方が悪いのか、それとももっと高次元(Apache のログを解析するなど)で行われているのか。あるいはGoogle のウェブマスターツールなどでも404 がどこで発生しているかを見つけることはできるから、そういったものを使っているのだろうか。

WordPress などのCMS には404 時の動作を定義したり、改造したり、プラグインを導入したりすることで発見から対策までを容易にしている。今回作るプログラムは単体で動作するように設計しているので、どのサイトにもPHP が利用できる環境であれば使えると思う。

スポンサードリンク

期待する動作

  1. .htaccess で404 時に本プログラムを指定する
  2. 本プログラムはユーザ、Bot 等に対してHTTP ステータスコード 404 を返す
  3. 同時に検索語が解析でき、移転先へクエリを転送できる場合は案内する
  4. 同時に管理者に対して404 の発生を知らせる
  5. 管理者は必要に応じて .htaccess を更新し、適切なページにリダイレクトさせ404 を軽減する

バージョンアップ

公開までに少し運用して改良したところ

  • Ver.0.04- favicon やrobots などを報告しないようにする$reject 配列を追加し、合致した場合は報告しない(完全一致のみ)。
    ※Ver.0.03 でも搭載したが、ハードコーディングだったのを改善した
  • Ver.0.05- 攻撃を目的としたアクセスの多くがReferer を持っていないことから、Referer を持っていない場合に報告しないようにする設定を追加した。$need_referer をfalse にするとreject に合致しなければ全件報告するので、どんな攻撃を受けたか見ることができる。ちなみにレポートが来たということは404 で弾いたということになる。その他Ver.0.05 ではいろいろ弄ったので新たなバグを内包したかも。

動作例

コード

<?php
/* ===================================================
 *  404 Report and Suggest for mb-PHP
 * ---------------------------------------------------
 * DESCRIPTION:
 *  - output 404 message for user
 *    To display a polite message to visitors
 *
 *  - guide for new site by searched strings
 *    The destination guides to parse the search term.
 *
 *  - site administrator get error mail
 *    To send a message to the site administrator.
 *
 * USGE:
 *  write .htaccess file
 *    ErrorDocument 404 /err404.php
 *
 * LICENSE : GNU GPL
 * CODE    : UTF-8
 * LANGUAGE: PHP, ja-JP
 *
 * AUTHER
 *  COPYRIGHT (C) CASEY 2011
 *  https://pc.casey.jp/
 *
 * VERSION
 *  ver.0.01 2011.09.18 first version
 *  ver.0.02 2011.09.19 change algorithm
 *  ver.0.03 2011.09.19 add reject function
 *  ver.0.04 2011.09.19 change reject algorithm
 *  ver.0.05 2011.09.29 add need referer
 * ===================================================
 */

// ===[ settings ]====================================

// --- suggest ---
$new_url_suggest    = true;
$new_url_extract    = true;
$new_url_base        = 'http://newexample.com';
$new_url_querystr    = '/?s=';

// --- mail ------
$mail                = true;
$mail_subject        = "404 error report";
$mail_body            = "an error occurred";
$mail_addr_from        = "root@localhost";
$mail_addr_to        = "[email protected]";

// --- rejects ---
$need_referer = true;
$rejects = array(
    '/robots.txt',
    '/favicon.ico'
);

// ===[ settings end ]================================

// do not change!
$debug    = false;
$sTime    = time();
$VER    = "0.05";
$program_name  = "404 error report Ver.";
$program_name .= $VER . " on ";
$program_name .= $_SERVER['SERVER_NAME'];

// ===================================================
//    MAIN
// ===================================================
// 2011.09.19 ver.0.27

// init
if($debug){ _setDebugvalue(); }

// start output
outputHeader();
echo getHeaderHtml();

// add ver.0.05
if(!isset($_SERVER["HTTP_REFERER"])){
    if($need_referer){
        $mail = false;
    }
}else{
    $keywords    = getKeywords();

    // return suggest
    if($keywords && $new_url_suggest){
        getSuggest();
    }
}

// mail send
$mail_body = getMailbody();
if(!$debug && $mail){
    echo "<p>この問題は管理者に通知されました。</p>";
    mailSend();
}

// debug info
if($debug){ _debuginfo(); }

// print footer
echo getFooterHtml();

// end of script
exit();

// ===================================================
//    FUNCTIONS
// ===================================================

// ====================================
//    output footer html
// ====================================
// 2011.09.19 ver.0.03
function getFooterHtml(){
    $host = $_SERVER['HTTP_HOST'];

    return <<< EOH
        <div align="right">
          <a id="site-link" href="http://$host" target="_blank">$host</a>
        </div>
    </body>
    </html>
EOH;
}

// ====================================
//    output header html
// ====================================
// 2011.09.19 ver.0.03
function getHeaderHtml(){
    return <<< EOH
    <!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" dir="ltr" lang="ja">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>404 Not Found</title>
        <link rel="stylesheet" href="/err.css" type="text/css" />
    </head>
    <body id="error-page">
        <h1>404 Not Found</h1>
        <h2>ページが見つかりません</h2>
        <p>リクエストされたページが見つかりませんでした。</p>
EOH;
}

// ====================================
//   mail send
// ====================================
// 2011.09.19 ver.0.03
function mailSend(){
    // init
    global    $mail_addr_to,
            $mail_subject,
            $mail_body,
            $mail_addr_from;

    // set japanese
    mb_language("japanese");
    mb_internal_encoding("UTF-8");

    // mail send
    mb_send_mail(
        $mail_addr_to,
        $mail_subject,
        $mail_body,
        "From:" . $mail_addr_from
    );
}

// ====================================
//    debug information
// ====================================
// 2011.09.19 ver.0.03
function _debuginfo(){
    // init
    global $mail_body;

    echo "[ DEBUG MODE ]<br />\n";
    $debug_html = $mail_body;
    echo preg_replace('/\n/', '<br />', $debug_html);
}

// ====================================
//    suggest links
// ====================================
// 2011.09.19 ver.0.03
function getSuggest(){
    // init
    global    $keywords,
            $new_url_base,
            $new_url_querystr;

    // from search engine
    echo "<p>もしかしたら以下に移動したかもしれません:</p>";
    echo "<ul>";
    $new_url = $new_url_base . $new_url_querystr;
    foreach($keywords as $word){
        echo '<li><a href="' . $new_url . urlencode($word) .'" target="_blank">移転先(' . $new_url_base . ')を「' . $word . "」で再検索する</a></li>";
    }
    echo "</ul>";
}

// ====================================
//    set debug mode value
// ====================================
// 2011.09.19 ver.0.03
function _setDebugvalue(){
    // add ver.0.05
    global    $need_referer,
            $mail;

    if(!isset($_SERVER["HTTP_REFERER"])){
        $_SERVER["HTTP_REFERER"] = '';
    }
}

// ====================================
//    make mail body
// ====================================
// 2011.09.19 ver.0.05
function getMailbody(){
    // init
    global    $keywords,
            $rejects,
            $program_name,
            $sTime,
            $mail;

    $mail_body  = "ErrorID: "        . $_SERVER["REQUEST_TIME"]    . "\n";
    //$mail_body .= "HTTP_HOST: "        . $_SERVER["HTTP_HOST"]        . "\n";
    //$mail_body .= "REQUEST_URI: "    . $_SERVER["REQUEST_URI"]    . "\n";
    $mail_body .= "URL: http://"    . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]    . " \n";
    if($keywords){
        if(is_array($keywords)){
            $mail_body .= "QUERY:"        . implode(",", $keywords)    . "\n";
        }else{
            $mail_body .= "QUERY:"        . $keywords                    . "\n";
        }
    }else{
        $mail_body .= "QUERY: Unknown"                                . "\n";
    }
    $mail_body .= "HTTP_REFERER: "    . $_SERVER["HTTP_REFERER"]. "\n";
    $mail_body .= "REMOTE_ADDR: "    . gethostbyaddr($_SERVER['REMOTE_ADDR']) . " (".$_SERVER['REMOTE_ADDR'].")" . "\n";
    $mail_body .= "REQUEST_TIME: "    . date('Y/m/d H:i:s', $_SERVER['REQUEST_TIME']) . "\n";

    $mail_body .= "\n" . $program_name . " (C) https://pc.casey.jp 2011 ";
    $eTime = time() - $sTime;
    $mail_body .=  " (" . $eTime . "Sec+)";

    // 2011.09.19 add reject
    // 2011.09.19 remove
    // favicon.ico
    //$pos = strpos($_SERVER["REQUEST_URI"], 'favicon.ico');
    //if($pos !== false){ $mail = false; }

    // 2011.09.19 add reject new
    if(in_array($_SERVER["REQUEST_URI"], $rejects)){
        $mail = false;
    };

    return $mail_body;
}

// ====================================
//    start output
// ====================================
// 2011.09.19 ver.0.02
function outputHeader(){
    // init
    global $debug;

    if($debug){
        header( 'HTTP/1.1 200 OK' );
    }else{
        header( 'HTTP/1.1 404 Not Found' );
    }
    header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' );
    header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
    header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
    header( 'Pragma: no-cache' );
    header( 'Content-Type: text/html; charset=utf-8' );
}

// ====================================
//    check referer
// ====================================
// 2011.09.19 ver.0.07
function getKeywords(){
    // init
    global $new_url_extract;
    $keywords = null;

    // check ref
    if(isset($_SERVER["HTTP_REFERER"])){
        // decode referer
        $_SERVER["HTTP_REFERER"] = urldecode($_SERVER["HTTP_REFERER"]);

        // check domain
        $U = @parse_url($_SERVER["HTTP_REFERER"]);

        // check search strings
        $pos = strpos($U["host"], 'google');
        if($pos !== false){
            //Google # q=
            preg_match('/q\=([^\&]+)/', $_SERVER["HTTP_REFERER"], $Qstr);
        }else{
            //Yahoo ? p=
            preg_match('/p\=([^\&]+)/', $U["query"], $Qstr);
        }

        // split keywords by space to keyword array
        if($Qstr[1]){

            // keywords extract
            if($new_url_extract){
                $keywords = preg_split('/[\s ]+/', $Qstr[1]);
            }else{
                $keywords[] = $Qstr[1];
            }
        }
    }
    return $keywords;
}

ダウンロード

調整中

参考文献

  • SEO:404 Not Found エラーは検索順位に影響するか? ::SEM R

検索語解析

メール送信

  • mb_internal_encoding
  • PHPで日本語メールを送る – 基本編 – EC studio 技術ブログ

PHP マニュアル

# PHP には何故、変数宣言の強制がないのだろう・・・?

コメント