<?php

function bomfix_info()
{
    return array(
        'name'          => 'bomfix',
        'description'   => 'Find and fix UTF-8 Byte-Order-Mark and similar issues with your PHP files.',
        'website'       => '',
        'author'        => 'Andreas Klauer',
        'authorsite'    => '',
        'version'       => '1',
        'guid'          => '',
        'compatibility' => '*'
    );
}

/*
 * walk through the mybb dir and do something with the files
 *
 */
function bomfix_activate()
{
    $stack = array(MYBB_ROOT);

    echo "Analyzing PHP files in '".MYBB_ROOT."'...<br><br>";

    while(count($stack))
    {
        $dir = array_pop($stack);
        $dh = opendir($dir);

        if(!$dh)
        {
            echo "ERROR: No permission to open directory '$dir'<br>";
        }

        while(($file = readdir($dh)) !== false)
        {
            if($file == "." || $file == "..")
            {
                continue;
            }

            $file = "{$dir}{$file}";

            if(is_dir($file))
            {
                array_push($stack, "{$file}/");
                continue;
            }

            if(substr($file, -4) == ".php")
            {
                bomfix_action($file);
            }
        }
    }

    exit;
}

/*
 * do some action with the found file
 */
function bomfix_action($file)
{
    global $mybb;

    // Check for invalid whitespace in PHP files.
    $contents = file_get_contents($file);

    $fail = false;

    if(!$contents)
    {
        echo "FAIL ('$file' no read permission or empty)<br>";
        return;
    }

    if(substr($contents, 0, 6) == "<?php\n")
    {
        // echo "OK ('$file' starts with &lt;?php [UNIX newline \\n])<br>";
    }

    else if(substr($contents, 0, 7) == "<?php\r\n")
    {
        // echo "OK ('$file' starts with &lt;?php [DOS newline \\r\\n])<br>";
    }

    else if(substr($contents, 0, 6) == "<?php\r")
    {
        // echo "OK ('$file' starts with &lt;?php [MAC newline \\r])<br>";
    }

    else if(substr($contents, 0, 5) == "<?php")
    {
        $fail = true;
        echo "FAIL ('$file' starts with &lt;?php [NO NEWLINE])<br>";
    }

    else if(substr($contents, 0, 3) == "\xef\xbb\xbf")
    {
        $fail = true;
        echo "FAIL ('$file' starts with UTF-8 Byte Order Mark)<br>";

        if($mybb->input['bomfix'] == $file)
        {
            echo "BOMFIXING '$file'...<br>";
            $contents = substr($contents, 3);
            file_put_contents($file, $contents);
            $contents = file_get_contents($file);
        }
    }

    else
    {
        $fail = true;
        echo "FAIL ('$file' does not start with &lt;?php)<br>";
    }

    if(substr($contents, -3) == "?>\n")
    {
        // echo "OK ('$file' ends with ?&gt; [UNIX newline \\n])<br>";
    }

    else if(substr($contents, -2) == "?>")
    {
        // echo "OK ('$file' ends with ?&gt; [no newline])<br>";
    }

    else if(substr($contents, -4) == "?>\r\n")
    {
        // echo "OK ('$file' ends with ?&gt; [DOS newline \\r\\n])<br>";
    }

    else if(substr($contents, -3) == "?>\r")
    {
        // echo "OK ('$file ends with ?&gt; [MAC newline \\r])<br>";
    }

    else
    {
        $fail = true;
        echo "FAIL ('$file' does not end with '?>')<br>";
    }

    if($fail)
    {
        bomfix_hexdump($contents, 0, 128);
        echo "[...]";
        bomfix_hexdump($contents, strlen($contents) - (strlen($contents) % 128) - 32);
    }
}

/**
 * View any string as a hexdump.
 *
 * This is most commonly used to view binary data from streams
 * or sockets while debugging, but can be used to view any string
 * with non-viewable characters.
 *
 * @version     1.3.2
 * @author      Aidan Lister <aidan@php.net>
 * @author      Peter Waller <iridum@php.net>
 * @link        http://aidanlister.com/2004/04/viewing-binary-data-as-a-hexdump-in-php/
 * @param       string  $data        The string to be dumped
 * @param       bool    $htmloutput  Set to false for non-HTML output
 * @param       bool    $uppercase   Set to true for uppercase hex
 * @param       bool    $return      Set to true to return the dump
 */
/*
 * Plus minor modifications (offset/len parameter) and bugfix (printable ascii range)
 */
function bomfix_hexdump ($data, $offset = 0, $len = 0, $htmloutput = true, $uppercase = false, $return = false)
{
    // Init
    $hexi   = '';
    $ascii  = '';
    $dump   = ($htmloutput === true) ? '<pre>' : '';
    if($len === 0) { $len    = strlen($data); }

    // Upper or lower case hexadecimal
    $x = ($uppercase === false) ? 'x' : 'X';

    // Iterate string
    $j = 0;
    for ($i = $offset; $i < $len; $i++)
    {
        // Convert to hexidecimal
        $hexi .= sprintf("%02$x ", ord($data[$i]));

        // Replace non-viewable bytes with '.'
        if (ord($data[$i]) >= 0x20 && ord($data[$i]) <= 0x7E) {
            $ascii .= ($htmloutput === true) ?
                            htmlentities($data[$i]) :
                            $data[$i];
        } else {
            $ascii .= '.';
        }

        // Add extra column spacing
        if ($j === 7) {
            $hexi  .= ' ';
            // $ascii .= ' ';
        }

        // Add row
        if (++$j === 16 || $i === $len - 1) {
            // Join the hexi / ascii output
            $dump .= sprintf("%04$x  %-49s  %s", $offset, $hexi, $ascii);

            // Reset vars
            $hexi   = $ascii = '';
            $offset += 16;
            $j      = 0;

            // Add newline
            if ($i !== $len - 1) {
                $dump .= "\n";
            }
        }
    }

    // Finish dump
    $dump .= $htmloutput === true ?
                '</pre>' :
                '';
    $dump .= "\n";

    // Output method
    if ($return === false) {
        echo $dump;
    } else {
        return $dump;
    }
}

?>