PHP Classes

File: vault/functions.php

Recommend this page to a friend!
  Classes of Caleb  >  PHP Mussel  >  vault/functions.php  >  Download  
File: vault/functions.php
Role: Example script
Content type: text/plain
Description: Example script
Class: PHP Mussel
PHP file virus scanner to detect malware
Author: By
Last change:
Date: 3 years ago
Size: 234,838 bytes
 

Contents

Class file image Download
<?php
/**
 * This file is a part of the phpMussel package.
 * Homepage: https://phpmussel.github.io/
 *
 * PHPMUSSEL COPYRIGHT 2013 AND BEYOND BY THE PHPMUSSEL TEAM.
 *
 * Authors:
 * @see PEOPLE.md
 *
 * License: GNU/GPLv2
 * @see LICENSE.txt
 *
 * This file: Functions file (last modified: 2019.04.07).
 */

/**
 * Extends compatibility with phpMussel to PHP 5.4.x by introducing some simple
 * polyfills for functions introduced with newer versions of PHP.
 */
if (substr(PHP_VERSION, 0, 4) === '5.4.') {
    require $phpMussel['Vault'] . 'php5.4.x.php';
}

/** Autoloader for phpMussel classes. */
spl_autoload_register(function ($Class) {
    $Vendor = (($Pos = strpos($Class, "\\", 1)) === false) ? '' : substr($Class, 0, $Pos);
    $File = __DIR__ . '/classes/' . ((!$Vendor || $Vendor === 'phpMussel') ? '' : $Vendor . '/') . (
        (($Pos = strrpos($Class, "\\")) === false) ? $Class : substr($Class, $Pos + 1)
    ) . '.php';
    if (is_readable($File)) {
        require $File;
    }
});

/**
 * Registers plugin closures/functions to their intended hooks.
 *
 * @param string $What The name of the closure/function to execute.
 * @param string $Where Where to execute it (the designated "plugin hook").
 * @return bool Execution failed(false)/succeeded(true).
 */
$phpMussel['Register_Hook'] = function ($What, $Where) use (&$phpMussel) {
    if (!isset($phpMussel['MusselPlugins']['hooks'], $phpMussel['MusselPlugins']['closures']) || !$What || !$Where) {
        return false;
    }
    if (!isset($phpMussel['MusselPlugins']['hooks'][$Where])) {
        $phpMussel['MusselPlugins']['hooks'][$Where] = [];
    }
    $phpMussel['MusselPlugins']['hooks'][$Where][] = $What;
    if (!function_exists($What) && isset($GLOBALS[$What]) && is_object($GLOBALS[$What])) {
        $phpMussel['MusselPlugins']['closures'][] = $What;
    }
    return true;
};

/**
 * Executes plugin closures/functions.
 *
 * @param string $HookID Where to execute it (the designated "plugin hook").
 * @return bool Execution failed(false)/succeeded(true).
 */
$phpMussel['Execute_Hook'] = function ($HookID) use (&$phpMussel) {
    if (!isset($phpMussel['MusselPlugins']['hooks'][$HookID])) {
        return false;
    }
    foreach ($phpMussel['MusselPlugins']['hooks'][$HookID] as $Registered) {
        if (isset($GLOBALS[$Registered]) && is_object($GLOBALS[$Registered])) {
            $GLOBALS[$Registered]();
        } elseif (function_exists($Registered)) {
            call_user_func($Registered);
        }
    }
    return true;
};

/**
 * Replaces encapsulated substrings within an input string with the value of
 * elements within an input array, whose keys correspond to the substrings.
 * Accepts two input parameters: An input array (1), and an input string (2).
 *
 * @param array $Needle The input array (the needle[/s]).
 * @param string $Haystack The input string (the haystack).
 * @return string The resultant string.
 */
$phpMussel['ParseVars'] = function ($Needle, $Haystack) {
    if (!is_array($Needle) || empty($Haystack)) {
        return '';
    }
    array_walk($Needle, function ($Value, $Key) use (&$Haystack) {
        if (!is_array($Value)) {
            $Haystack = str_replace('{' . $Key . '}', $Value, $Haystack);
        }
    });
    return $Haystack;
};

/**
 * Implodes multidimensional arrays.
 *
 * @param array $ar The array to be imploded.
 * @param string|array $j An optional "needle" or "joiner" to use for imploding
 *      the array. If a numeric array is used, an element of the array
 *      corresponding to the recursion depth will be used as the needle or
 *      joiner.
 * @param int $i Used by the function when calling itself recursively, for the
 *      purpose of tracking recursion depth (shouldn't be used outside the
 *      function).
 * @param bool $e Optional; When set to false, empty elements will be ignored.
 * @return string The imploded array.
 */
$phpMussel['implode_md'] = function ($ar, $j = '', $i = 0, $e = true) use (&$phpMussel) {
    if (!is_array($ar)) {
        return $ar;
    }
    $c = count($ar);
    if (!$c || is_array($i)) {
        return false;
    }
    if (is_array($j)) {
        if (!$x = $j[$i]) {
            return false;
        }
    } else {
        $x = $j;
    }
    $out = '';
    while ($c > 0) {
        $key = key($ar);
        if (is_array($ar[$key])) {
            $i++;
            $ar[$key] = $phpMussel['implode_md']($ar[$key], $j, $i);
            $i--;
        }
        if (!$out) {
            $out = $ar[$key];
        } elseif (!(!$e && empty($ar[$key]))) {
            $out .= $x . $ar[$key];
        }
        next($ar);
        $c--;
    }
    return $out;
};

/**
 * Does some simple decoding work on strings.
 *
 * @param string $str The string to be decoded.
 * @return string The decoded string.
 */
$phpMussel['prescan_decode'] = function ($str) use (&$phpMussel) {
    $nstr = html_entity_decode(urldecode(str_ireplace('&amp;#', '&#', str_ireplace('&amp;amp;', '&amp;', $str))));
    if ($nstr !== $str) {
        $nstr = $phpMussel['prescan_decode']($nstr);
    }
    return $nstr;
};

/**
 * Some simple obfuscation for potentially blocked functions; We need this to
 * avoid triggering false positives for some potentially overzealous
 * server-based security solutions that would usually flag this file as
 * malicious when they detect it containing the names of suspect functions.
 *
 * @param string $n An alias for the function that we want to call.
 * @param string $str Some data to parse to the function being called.
 * @return string The parsed data and/or decoded string (if $str is empty, the
 *      the resolved alias will be returned instead).
 */
$phpMussel['Function'] = function ($n, $str = '') {
    static $x = 'abcdefghilnorstxz12346_';
    $fList = [
        'GZ' =>
            $x[6] . $x[16] . $x[8] . $x[10] . $x[5] . $x[9] . $x[0] . $x[14] . $x[4],
        'R13' =>
            $x[13] . $x[14] . $x[12] . $x[22] . $x[12] . $x[11] . $x[14] . $x[17] . $x[19],
        'B64' =>
            $x[1] . $x[0] . $x[13] . $x[4] . $x[21] . $x[20] . $x[22] . $x[3] . $x[4] . $x[2] . $x[11] . $x[3] . $x[4],
        'HEX' =>
            $x[7] . $x[4] . $x[15] . $x[18] . $x[1] . $x[8] . $x[10]
    ];
    if (!isset($fList[$n])) {
        return '';
    }
    if (!$str || !function_exists($fList[$n])) {
        return $fList[$n];
    }
    try {
        $Return = $fList[$n]($str);
    } catch (\Exception $e) {
        $Return = '';
    }
    return $Return;
};

/**
 * Does some more complex decoding and normalisation work on strings.
 *
 * @param string $str The string to be decoded/normalised.
 * @param bool $html If true, "style" and "script" tags will be stripped from
 *      the input string (optional; defaults to false).
 * @param bool $decode If false, the input string will be normalised, but not
 *      decoded; If true, the input string will be normalised *and* decoded.
 *      Optional; Defaults to false.
 * @return string The decoded/normalised string.
 */
$phpMussel['prescan_normalise'] = function ($str, $html = false, $decode = false) use (&$phpMussel) {
    $ostr = '';
    if ($decode) {
        $ostr .= $str;
        while (true) {
            if (function_exists($phpMussel['Function']('GZ'))) {
                if ($c = preg_match_all(
                    '/(' . $phpMussel['Function']('GZ') . '\s*\(\s*["\'])(.{1,4096})(,\d)?(["\']\s*\))/i',
                $str, $matches)) {
                    for ($i = 0; $c > $i; $i++) {
                        $str = str_ireplace(
                            $matches[0][$i],
                            '"' . $phpMussel['Function']('GZ', $phpMussel['substrbl']($phpMussel['substraf']($matches[0][$i], $matches[1][$i]), $matches[4][$i])) . '"',
                            $str
                        );
                    }
                    continue;
                }
            }
            if ($c = preg_match_all(
                '/(' . $phpMussel['Function']('B64') . '|decode_base64|base64\.b64decode|atob|Base64\.decode64)(\s*' .
                '\(\s*["\'\`])([\da-z+\/]{4})*([\da-z+\/]{4}|[\da-z+\/]{3}=|[\da-z+\/]{2}==)(["\'\`]' .
                '\s*\))/i',
            $str, $matches)) {
                for ($i = 0; $c > $i; $i++) {
                    $str = str_ireplace(
                        $matches[0][$i],
                        '"' . $phpMussel['Function']('B64', $phpMussel['substrbl']($phpMussel['substraf']($matches[0][$i], $matches[1][$i] . $matches[2][$i]), $matches[5][$i])) . '"',
                        $str
                    );
                }
                continue;
            }
            if ($c = preg_match_all(
                '/(' . $phpMussel['Function']('R13') . '\s*\(\s*["\'])([^\'"\(\)]{1,4096})(["\']\s*\))/i',
            $str, $matches)) {
                for ($i = 0; $c > $i; $i++) {
                    $str = str_ireplace(
                        $matches[0][$i],
                        '"' . $phpMussel['Function']('R13', $phpMussel['substrbl']($phpMussel['substraf']($matches[0][$i], $matches[1][$i]), $matches[3][$i])) . '"',
                        $str
                    );
                }
                continue;
            }
            if ($c = preg_match_all(
                '/(' . $phpMussel['Function']('HEX') . '\s*\(\s*["\'])([\da-f]{1,4096})(["\']\s*\))/i',
            $str, $matches )) {
                for ($i = 0; $c > $i; $i++) {
                    $str = str_ireplace(
                        $matches[0][$i],
                        '"' . $phpMussel['HexSafe']($phpMussel['substrbl']($phpMussel['substraf']($matches[0][$i], $matches[1][$i]), $matches[3][$i])) . '"',
                        $str
                    );
                }
                continue;
            }
            if ($c = preg_match_all(
                '/([Uu][Nn][Pp][Aa][Cc][Kk]\s*\(\s*["\']\s*H\*\s*["\']\s*,\s*["\'])([\da-fA-F]{1,4096})(["\']\s*\))/',
            $str, $matches)) {
                for ($i = 0; $c > $i; $i++) {
                    $str = str_replace($matches[0][$i], '"' . $phpMussel['HexSafe']($phpMussel['substrbl']($phpMussel['substraf']($matches[0][$i], $matches[1][$i]), $matches[3][$i])) . '"', $str);
                }
                continue;
            }
            break;
        }
    }
    $str = preg_replace('/[^\x21-\x7e]/', '', strtolower($phpMussel['prescan_decode']($str . $ostr)));
    unset($ostr);
    if ($html) {
        $str = preg_replace([
            '@<script[^>]*?>.*?</script>@si',
            '@<[\/\!]*?[^<>]*?>@si',
            '@<style[^>]*?>.*?</style>@siU',
            '@<![\s\S]*?--[ \t\n\r]*>@'
        ], '', $str);
    }
    return trim($str);
};

/**
 * Gets substring from haystack prior to the first occurrence of needle.
 *
 * @param string $h The haystack.
 * @param string $n The needle.
 * @return string The substring.
 */
$phpMussel['substrbf'] = function ($h, $n) {
    return !$n ? '' : substr($h, 0, strpos($h, $n));
};

/**
 * Gets substring from haystack after the first occurrence of needle.
 *
 * @param string $h The haystack.
 * @param string $n The needle.
 * @return string The substring.
 */
$phpMussel['substraf'] = function ($h, $n) {
    return !$n ? '' : substr($h, strpos($h, $n) + strlen($n));
};

/**
 * Gets substring from haystack prior to the last occurrence of needle.
 *
 * @param string $h The haystack.
 * @param string $n The needle.
 * @return string The substring.
 */
$phpMussel['substrbl'] = function ($h, $n) {
    return !$n ? '' : substr($h, 0, strrpos($h, $n));
};

/**
 * Gets substring from haystack after the last occurrence of needle.
 *
 * @param string $h The haystack.
 * @param string $n The needle.
 * @return string The substring.
 */
$phpMussel['substral'] = function ($h, $n) {
    return !$n ? '' : substr($h, strrpos($h, $n) + strlen($n));
};

/**
 * This function reads files and returns the contents of those files.
 *
 * @param string $File Path and filename of the file to read.
 * @param int $s Number of blocks to read from the file (optional; can be
 *      manually specified, but it's best to just ignore it and let the
 *      function work it out for itself).
 * @param bool $PreChecked When false, checks that the file exists and is
 *      writable. Defaults to false.
 * @param int $Blocks The total size of a single block in kilobytes (optional;
 *      defaults to 128, i.e., 128KB or 131072 bytes). This can be modified by
 *      developers as per their individual needs. Generally, a smaller value
 *      will increase stability but decrease performance, whereas a larger
 *      value will increase performance but decrease stability.
 * @return string|bool Content of the file returned by the function (or false
 *      on failure).
 */
$phpMussel['ReadFile'] = function ($File, $Size = 0, $PreChecked = false, $Blocks = 128) {
    if (!$PreChecked && (!is_file($File) || !is_readable($File))) {
        return false;
    }
    /** Blocksize to bytes. */
    $Blocksize = $Blocks * 1024;
    $Filesize = filesize($File);
    if (!$Size) {
        $Size = ($Filesize && $Blocksize) ? ceil($Filesize / $Blocksize) : 0;
    }
    $Data = '';
    if ($Size > 0) {
        $Handle = fopen($File, 'rb');
        $r = 0;
        while ($r < $Size) {
            $Data .= fread($Handle, $Blocksize);
            $r++;
        }
        fclose($Handle);
    }
    return $Data ?: false;
};

/**
 * A very simple wrapper for file() that checks for the existence of files
 * before attempting to read them, in order to avoid warnings about
 * non-existent files.
 *
 * @param string $Filename Refer to the description for file().
 * @param int $Flags Refer to the description for file().
 * @param array $Context Refer to the description for file().
 * @return array|bool Same as with file(), but won't trigger warnings.
 */
$phpMussel['ReadFileAsArray'] = function ($Filename, $Flags = 0, $Context = false) {
    if (!is_readable($Filename)) {
        return false;
    }
    if (!$Context) {
        return !$Flags ? file($Filename) : file($Filename, $Flags);
    }
    return file($Filename, $Flags, $Context);
};

/**
 * Deletes expired cache entries and regenerates cache files.
 *
 * @param string $Delete Forcibly delete a specific cache entry (optional).
 * @return bool Operation succeeded (true) or failed (false).
 */
$phpMussel['CleanCache'] = function ($Delete = '') use (&$phpMussel) {
    if (!empty($phpMussel['InstanceCache']['CacheCleaned'])) {
        return true;
    }
    $phpMussel['InstanceCache']['CacheCleaned'] = true;
    $CacheFiles = [];
    $FileIndex = $phpMussel['cachePath'] . 'index.dat';
    if (!is_readable($FileIndex)) {
        return false;
    }
    $FileDataOld = $FileData = $phpMussel['ReadFile']($FileIndex);
    if (strpos($FileData, ';') !== false) {
        $FileData = explode(';', $FileData);
        foreach ($FileData as &$ThisData) {
            if (strpos($ThisData, ':') === false) {
                $ThisData = '';
                continue;
            }
            $ThisData = explode(':', $ThisData, 3);
            if (($Delete && $Delete === $ThisData[0]) || ($ThisData[1] > 0 && $phpMussel['Time'] > $ThisData[1])) {
                $FileKey = bin2hex(substr($ThisData[0], 0, 1));
                if (!isset($CacheFiles[$FileKey])) {
                    $CacheFiles[$FileKey] = !is_readable(
                        $phpMussel['cachePath'] . $FileKey . '.tmp'
                    ) ? '' : $phpMussel['ReadFile']($phpMussel['cachePath'] . $FileKey . '.tmp', 0, true);
                }
                while (strpos($CacheFiles[$FileKey], $ThisData[0] . ':') !== false) {
                    $CacheFiles[$FileKey] = str_ireplace($ThisData[0] . ':' . $phpMussel['substrbf'](
                        $phpMussel['substraf']($CacheFiles[$FileKey], $ThisData[0] . ':'), ';'
                    ) . ';', '', $CacheFiles[$FileKey]);
                }
                $ThisData = '';
                continue;
            }
            $ThisData = $ThisData[0] . ':' . $ThisData[1];
        }
        $FileData = str_replace(';;', ';', implode(';', array_filter($FileData)) . ';');
        if ($FileDataOld !== $FileData) {
            $Handle = fopen($FileIndex, 'w');
            fwrite($Handle, $FileData);
            fclose($Handle);
        }
    }
    foreach ($CacheFiles as $CacheEntryKey => $CacheEntryValue) {
        if (strlen($CacheEntryValue) < 2) {
            if (file_exists($phpMussel['cachePath'] . $CacheEntryKey . '.tmp')) {
                unlink($phpMussel['cachePath'] . $CacheEntryKey . '.tmp');
            }
            continue;
        }
        $Handle = fopen($phpMussel['cachePath'] . $CacheEntryKey . '.tmp', 'w');
        fwrite($Handle, $CacheEntryValue);
        fclose($Handle);
    }
    return true;
};

/**
 * Retrieves cache entries.
 *
 * @param string|array $Entry The name of the cache entry/entries to retrieve;
 *      Can be a string to specify a single entry, or an array of strings to
 *      specify multiple entries.
 * @return string|array Contents of the cache entry/entries.
 */
$phpMussel['FetchCache'] = function ($Entry = '') use (&$phpMussel) {
    $phpMussel['CleanCache']();
    $phpMussel['InitialiseCache']();

    /** Override if using a different preferred caching mechanism. */
    if ($phpMussel['Cache']->Using) {
        if (is_array($Entry)) {
            $Out = [];
            foreach ($Entry as $ThisKey => $ThisEntry) {
                $Out[$ThisKey] = $phpMussel['Cache']->getEntry($ThisEntry);
            }
            return $Out;
        }
        return $phpMussel['Cache']->getEntry($Entry);
    }

    /** Default process. */
    if (!$Entry) {
        return '';
    }
    if (is_array($Entry)) {
        $Out = [];
        array_walk($Entry, function ($Value, $Key) use (&$phpMussel, &$Out) {
            $Out[$Key] = $phpMussel['FetchCache']($Value);
        });
        return $Out;
    }
    $File = $phpMussel['cachePath'] . bin2hex(substr($Entry, 0, 1)) . '.tmp';
    if (!is_readable($File) || !$FileData = $phpMussel['ReadFile']($File, 0, true)) {
        return '';
    }
    if (!$Item = strpos($FileData, $Entry . ':') !== false ? $Entry . ':' . $phpMussel['substrbf'](
        $phpMussel['substraf']($FileData, $Entry . ':'), ';'
    ) . ';' : '') {
        return '';
    }
    $Expiry = $phpMussel['substrbf']($phpMussel['substraf']($Item, $Entry . ':'), ':');
    if ($Expiry > 0 && $phpMussel['Time'] > $Expiry) {
        while (strpos($FileData, $Entry . ':') !== false) {
            $FileData = str_ireplace($Item, '', $FileData);
        }
        $Handle = fopen($File, 'w');
        fwrite($Handle, $FileData);
        fclose($Handle);
        return '';
    }
    if (!$ItemData = $phpMussel['substrbf']($phpMussel['substraf']($Item, $Entry . ':' . $Expiry . ':'), ';')) {
        return '';
    }
    return $phpMussel['Function']('GZ', $phpMussel['HexSafe']($ItemData)) ?: '';
};

/**
 * Creates cache entry and saves it to the cache.
 *
 * @param string $Entry Name of the cache entry to create.
 * @param int $Expiry Unix time until the cache entry expires.
 * @param string $ItemData Contents of the cache entry.
 * @return bool This should always return true, unless something goes wrong.
 */
$phpMussel['SaveCache'] = function ($Entry = '', $Expiry = 0, $ItemData = '') use (&$phpMussel) {
    $phpMussel['CleanCache']();
    $phpMussel['InitialiseCache']();

    /** Override if using a different preferred caching mechanism. */
    if ($phpMussel['Cache']->Using) {
        if ($Expiry <= 0) {
            $Expiry = 0;
        } elseif ($Expiry > $phpMussel['Time']) {
            $Expiry = $Expiry - $phpMussel['Time'];
        }
        return $phpMussel['Cache']->setEntry($Entry, $ItemData, $Expiry);
    }

    /** Default process. */
    if (!$Entry || !$ItemData) {
        return false;
    }
    if (!$Expiry) {
        $Expiry = $phpMussel['Time'];
    }
    $File = $phpMussel['cachePath'] . bin2hex($Entry[0]) . '.tmp';
    $Data = $phpMussel['ReadFile']($File) ?: '';
    while (strpos($Data, $Entry . ':') !== false) {
        $Data = str_ireplace($Entry . ':' . $phpMussel['substrbf']($phpMussel['substraf']($Data, $Entry . ':'), ';') . ';', '', $Data);
    }
    $Data .= $Entry . ':' . $Expiry . ':' . bin2hex(gzdeflate($ItemData,9)) . ';';
    $Handle = fopen($File, 'w');
    fwrite($Handle, $Data);
    fclose($Handle);
    $IndexFile = $phpMussel['cachePath'] . 'index.dat';
    $IndexNewData = $IndexData = $phpMussel['ReadFile']($IndexFile) ?: '';
    while (strpos($IndexNewData, $Entry . ':') !== false) {
        $IndexNewData = str_ireplace($Entry . ':' . $phpMussel['substrbf']($phpMussel['substraf']($IndexNewData, $Entry . ':'), ';') . ';', '', $IndexNewData);
    }
    $IndexNewData .= $Entry . ':' . $Expiry . ';';
    if ($IndexNewData !== $IndexData) {
        $IndexHandle = fopen($IndexFile, 'w');
        fwrite($IndexHandle, $IndexNewData);
        fclose($IndexHandle);
    }
    return true;
};

/** Reads and prepares cached hash data. */
$phpMussel['PrepareHashCache'] = function () use (&$phpMussel) {
    if (!isset($phpMussel['HashCache'])) {
        $phpMussel['HashCache'] = [];
    }
    if ($phpMussel['HashCache']['Data'] = (
        $phpMussel['Config']['general']['scan_cache_expiry'] > 0
    ) ? $phpMussel['FetchCache']('HashCache') : '') {
        $phpMussel['HashCache']['Data'] = explode(';', $phpMussel['HashCache']['Data']);
        $Build = [];
        foreach ($phpMussel['HashCache']['Data'] as $CacheItem) {
            if (strpos($CacheItem, ':') !== false) {
                $CacheItem = explode(':', $CacheItem, 4);
                if ($CacheItem[1] > $phpMussel['Time']) {
                    $Build[$CacheItem[0]] = $CacheItem;
                }
            }
        }
        $phpMussel['HashCache']['Data'] = $Build;
    }
};

/**
 * Quarantines file uploads by using a key generated from your quarantine key
 * to bitshift the input string (the file uploads), appending a header with an
 * explanation of what the bitshifted data is, along with an MD5 hash checksum
 * of its non-quarantined counterpart, and then saves it all to a QFU file,
 * storing these QFU files in your quarantine directory.
 *
 * This isn't hardcore encryption, but it should be sufficient to prevent
 * accidental execution of quarantined files and to allow safe handling of
 * those files, which is the whole point of quarantining them in the first
 * place. Improvements might be made in the future.
 *
 * @param string $In The input string (the file upload / source data).
 * @param string $Key Your quarantine key.
 * @param string $IP Data origin (usually, the IP address of the uploader).
 * @param string $ID The QFU filename to use (calculated beforehand).
 * @return bool This should always return true, unless something goes wrong.
 */
$phpMussel['Quarantine'] = function ($In, $Key, $IP, $ID) use (&$phpMussel) {
    if (!$In || !$Key || !$IP || !$ID || !function_exists('gzdeflate') || (
        strlen($Key) < 128 &&
        !$Key = $phpMussel['HexSafe'](hash('sha512', $Key) . hash('whirlpool', $Key))
    )) {
        return false;
    }
    if ($phpMussel['Config']['legal']['pseudonymise_ip_addresses']) {
        $IP = $phpMussel['Pseudonymise-IP']($IP);
    }
    $k = strlen($Key);
    $FileSize = strlen($In);
    $Head = "\xa1phpMussel\x21" . $phpMussel['HexSafe'](md5($In)) . pack('l*', $FileSize) . "\x01";
    $In = gzdeflate($In, 9);
    $Out = '';
    $i = 0;
    while ($i < $FileSize) {
        for ($j = 0; $j < $k; $j++, $i++) {
            if (strlen($Out) >= $FileSize) {
                break 2;
            }
            $L = substr($In, $i, 1);
            $R = substr($Key, $j, 1);
            $Out .= ($L === false ? "\x00" : $L) ^ ($R === false ? "\x00" : $R);
        }
    }
    $Out =
        "\x2f\x3d\x3d\x20phpMussel\x20Quarantined\x20File\x20Upload\x20\x3d" .
        "\x3d\x5c\n\x7c\x20Time\x2fDate\x20Uploaded\x3a\x20" .
        str_pad($phpMussel['Time'], 18, ' ') .
        "\x7c\n\x7c\x20Uploaded\x20From\x3a\x20" . str_pad($IP, 22, ' ') .
        "\x20\x7c\n\x5c" . str_repeat("\x3d", 39) . "\x2f\n\n\n" . $Head . $Out;
    $UsedMemory = $phpMussel['MemoryUse']($phpMussel['qfuPath']);
    $UsedMemory['Size'] += strlen($Out);
    $UsedMemory['Count']++;
    if ($DeductBytes = $phpMussel['ReadBytes']($phpMussel['Config']['general']['quarantine_max_usage'])) {
        $DeductBytes = $UsedMemory['Size'] - $DeductBytes;
        $DeductBytes = ($DeductBytes > 0) ? $DeductBytes : 0;
    }
    if ($DeductFiles = $phpMussel['Config']['general']['quarantine_max_files']) {
        $DeductFiles = $UsedMemory['Count'] - $DeductFiles;
        $DeductFiles = ($DeductFiles > 0) ? $DeductFiles : 0;
    }
    if ($DeductBytes > 0 || $DeductFiles > 0) {
        $UsedMemory = $phpMussel['MemoryUse']($phpMussel['qfuPath'], $DeductBytes, $DeductFiles);
    }
    $Handle = fopen($phpMussel['qfuPath'] . $ID . '.qfu', 'a');
    fwrite($Handle, $Out);
    fclose($Handle);
    if (!$phpMussel['EOF']) {
        $phpMussel['Stats-Increment']('Web-Quarantined', 1);
    }
    return true;
};

/**
 * Calculates the total memory used by a directory, and optionally enforces
 * memory usage and number of files limits on that directory. Should be
 * regarded as part of the phpMussel quarantine functionality.
 *
 * @param string $Path The path of the directory to be checked.
 * @param int $Delete How many bytes to delete from the target directory; Omit
 *      or set to 0 to avoid deleting files on the basis of total bytes.
 * @param int $DeleteFiles How many files to delete from the target directory;
        Omit or set to 0 to avoid deleting files.
 * @return array Contains two integer elements: `Size`: The actual, total
 *      memory used by the target directory. `Count`: The total number of files
 *      found in the target directory by the time of closure exit.
 */
$phpMussel['MemoryUse'] = function ($Path, $Delete = 0, $DeleteFiles = 0) {
    $Offset = strlen($Path);
    $Files = [];
    $List = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($Path), RecursiveIteratorIterator::SELF_FIRST);
    foreach ($List as $Item => $List) {
        $File = str_replace("\\", '/', substr($Item, $Offset));
        if ($File && preg_match('~\.qfu$~i', $Item) && is_file($Item) && !is_link($Item) && is_readable($Item)) {
            $Files[$File] = filemtime($Item);
        }
    }
    unset($Item, $List, $Offset);
    $Arr = ['Size' => 0, 'Count' => 0];
    asort($Files, SORT_NUMERIC);
    foreach ($Files as $File => $Modified) {
        $File = $Path . $File;
        $Size = filesize($File);
        if (($Delete > 0 || $DeleteFiles > 0) && unlink($File)) {
            $DeleteFiles--;
            $Delete -= $Size;
            continue;
        }
        $Arr['Size'] += $Size;
        $Arr['Count']++;
    }
    return $Arr;
};

/**
 * Checks if $Needle (string) matches (is equal or identical to) $Haystack
 * (string), or a specific substring of $Haystack, to within a specific
 * threshold of the levenshtein distance between the $Needle and the $Haystack
 * or the $Haystack substring specified.
 *
 * This function is useful for expressing the differences between two strings
 * as an integer value and for then determining whether a specific value as per
 * those differences is met.
 *
 * @param string $Needle The needle (will be matched against the $Haystack, or,
 *      if substring positions are specified, against the $Haystack substring
 *      specified).
 * @param string $Haystack The haystack (will be matched against the $Needle).
 *      Note that for the purposes of calculating the levenshtein distance, it
 *      doesn't matter which string is a $Needle and which is a $Haystack (the
 *      value should be the same if the two were reversed). However, when
 *      specifying substring positions, those substring positions are applied
 *      to the $Haystack, and not the $Needle. Note, too, that if the $Needle
 *      length is greater than the $Haystack length (after having applied the
 *      substring positions to the $Haystack), $Needle and $Haystack will be
 *      switched.
 * @param int $pos_A The initial position of the $Haystack to use for the
 *      substring, if using a substring (optional; defaults to `0`; `0` is the
 *      beginning of the $Haystack).
 * @param int $pos_Z The final position of the $Haystack to use for the
 *      substring, if using a substring (optional; defaults to `0`; `0` will
 *      instruct the function to continue to the end of the $Haystack, and
 *      thus, if both $pos_A and $pos_Z are `0`, the entire $Haystack will be
 *      used).
 * @param int $min The threshold minimum (the minimum levenshtein distance
 *      required in order for the two strings to be considered a match).
 *      Optional; Defaults to `0`. If `0` or less is specified, there is no
 *      minimum, and so, any and all strings should always match, as long as
 *      the levenshtein distance doesn't surpass the threshold maximum.
 * @param int $max The threshold maximum (the maximum levenshtein distance
 *      allowed for the two strings to be considered a match). Optional;
 *      Defaults to `-1`. If exactly `-1` is specified, there is no maximum,
 *      and so, any and all strings should always match, as long as the
 *      threshold minimum is met.
 * @param bool $bool Specifies to the function whether to return the
 *      levenshtein distance of the two strings (as an integer) or to return
 *      the results of the match (as a boolean; true for match success, false
 *      for match failure). Optional; Defaults to true. If true is specified,
 *      the function will return a boolean value (the results of the match),
 *      and if false is specified, the levenshtein distance will be returned.
 * @param bool $case Specifies to the function whether to treat the two strings
 *      as case-sensitive (when true is specified) or case-insensitive (when
 *      false is specified) when calculating the levenshtein distance.
 *      Optional; Defaults to false.
 * @param int $cost_ins The cost to apply for character/byte insertions for
 *      when calculating the levenshtein distance. Optional; Defaults to 1.
 * @param int $cost_rep The cost to apply for character/byte replacements for
 *      when calculating the levenshtein distance. Optional; Defaults to 1.
 * @param int $cost_del The cost to apply for character/byte deletions for when
 *      calculating the levenshtein distance. Optional; Defaults to 1.
 * @return bool|int The function will return either a boolean or an integer,
 *      depending on the state of $bool (but will also return false whenever an
 *      error occurs).
 */
$phpMussel['lv_match'] = function ($Needle, $Haystack, $pos_A = 0, $pos_Z = 0, $min = 0, $max = -1, $bool = true, $case = false, $cost_ins = 1, $cost_rep = 1, $cost_del = 1) {
    if (!function_exists('levenshtein') || is_array($Needle) || is_array($Haystack)) {
        return false;
    }
    $nlen = strlen($Needle);
    $pos_A = (int)$pos_A;
    $pos_Z = (int)$pos_Z;
    $min = (int)$min;
    $max = (int)$max;
    if ($pos_A !== 0 || $pos_Z !== 0) {
        $Haystack = (
            $pos_Z === 0
        ) ? substr($Haystack, $pos_A) : substr($Haystack, $pos_A, $pos_Z);
    }
    $hlen = strlen($Haystack);
    if ($nlen < 1 || $hlen < 1) {
        return $bool ? false : 0;
    }
    if ($nlen > $hlen) {
        $x = [$Needle, $nlen, $Haystack, $hlen];
        $Haystack = $x[0];
        $hlen = $x[1];
        $Needle = $x[2];
        $nlen = $x[3];
    }
    if ($cost_ins === 1 && $cost_rep === 1 && $cost_del === 1) {
        $lv = $case ? levenshtein(
            $Haystack, $Needle
        ) : levenshtein(
            strtolower($Haystack), strtolower($Needle)
        );
    } else {
        $lv = $case ? levenshtein(
            $Haystack, $Needle, $cost_ins, $cost_rep, $cost_del
        ) : levenshtein(
            strtolower($Haystack), strtolower($Needle), $cost_ins, $cost_rep, $cost_del
        );
    }
    return $bool ? (($min === 0 || $lv >= $min) && ($max === -1 || $lv <= $max)) : $lv;
};

/**
 * Returns the high and low nibbles corresponding to the first byte of the
 * input string.
 *
 * @param string $Input The input string.
 * @return array Contains two elements, both standard decimal integers; The
 *      first is the high nibble of the input string, and the second is the low
 *      nibble of the input string.
 */
$phpMussel['split_nibble'] = function ($Input) {
    $Input = bin2hex($Input);
    return [hexdec(substr($Input, 0, 1)), hexdec(substr($Input, 1, 1))];
};

/**
 * Returns a string representing the binary bits of its input, whereby each
 * byte of the output is either one or zero.
 * Output can be reversed with `$phpMussel['implode_bits']()`.
 *
 * @param string $Input The input string (see closure description above).
 * @return string The output string (see closure description above).
 */
$phpMussel['explode_bits'] = function ($Input) {
    $Out = '';
    $Len = strlen($Input);
    for ($Byte = 0; $Byte < $Len; $Byte++) {
        $Out .= str_pad(decbin(ord($Input[$Byte])), 8, '0', STR_PAD_LEFT);
    }
    return $Out;
};

/**
 * The reverse of `$phpMussel['explode_bits']()`.
 *
 * @param string $Input The input string (see closure description above).
 * @return string The output string (see closure description above).
 */
$phpMussel['implode_bits'] = function ($Input) {
    $Chunks = str_split($Input, 8);
    $Count = count($Chunks);
    for ($Out = '', $Chunk = 0; $Chunk < $Count; $Chunk++) {
        $Out .= chr(bindec($Chunks[$Chunk]));
    }
    return $Out;
};

/**
 * Expands phpMussel detection shorthand to complete identifiers, makes some
 * determinations based on those identifiers against the package
 * configuration (e.g., whether specific signatures should be weighted or
 * ignored based on those identifiers), and returns a complete signature name
 * containing all relevant identifiers.
 *
 * Originally, this function was created to allow phpMussel to partially
 * compress its signatures without jeopardising speed, performance or
 * efficiency, because by allowing phpMussel to partially compress its
 * signatures, the total signature file footprint could be reduced, thus
 * allowing the inclusion of a greater number of signatures without causing
 * excessive footprint bloat. Its purpose has expanded since then though.
 *
 * @param string $VN The signature name WITH identifiers compressed (i.e.,
 *      the shorthand version of the signature name).
 * @return string The signature name WITHOUT identifiers compressed (i.e., the
 *      identifiers have been decompressed/expanded), or the input verbatim.
 */
$phpMussel['vn_shorthand'] = function ($VN) use (&$phpMussel) {

    /** Determine whether the signature is weighted. */
    $phpMussel['InstanceCache']['weighted'] = false;

    /** Determine whether the signature should be ignored due to package configuration. */
    $phpMussel['InstanceCache']['ignoreme'] = false;

    /** Byte 0 confirms whether the signature name uses shorthand. */
    if ($VN[0] !== "\x1a") {
        return $VN;
    }

    /** Check whether shorthand data has been fetched. If it hasn't, fetch it. */
    if (!isset($phpMussel['shorthand.yaml'])) {
        if (!file_exists($phpMussel['Vault'] . 'shorthand.yaml') || !is_readable($phpMussel['Vault'] . 'shorthand.yaml')) {
            return $VN;
        }
        $phpMussel['shorthand.yaml'] = (new \Maikuolan\Common\YAML($phpMussel['ReadFile']($phpMussel['Vault'] . 'shorthand.yaml')))->Data;
    }

    /** Will be populated by the signature name. */
    $Out = '';

    /** Byte 1 contains vendor name and signature metadata information. */
    $Nibbles = $phpMussel['split_nibble']($VN[1]);

    /** Populate vendor name. */
    if (
        !empty($phpMussel['shorthand.yaml']['Vendor Shorthand'][$Nibbles[0]]) &&
        is_array($phpMussel['shorthand.yaml']['Vendor Shorthand'][$Nibbles[0]]) &&
        !empty($phpMussel['shorthand.yaml']['Vendor Shorthand'][$Nibbles[0]][$Nibbles[1]]) &&
        is_string($phpMussel['shorthand.yaml']['Vendor Shorthand'][$Nibbles[0]][$Nibbles[1]])
    ) {
        $SkipMeta = true;
        $Out .= $phpMussel['shorthand.yaml']['Vendor Shorthand'][$Nibbles[0]][$Nibbles[1]] . '-';
    } elseif (
        !empty($phpMussel['shorthand.yaml']['Vendor Shorthand'][$Nibbles[0]]) &&
        is_string($phpMussel['shorthand.yaml']['Vendor Shorthand'][$Nibbles[0]])
    ) {
        $Out .= $phpMussel['shorthand.yaml']['Vendor Shorthand'][$Nibbles[0]] . '-';
    }

    /** Populate weight options. */
    if ((
        !empty($phpMussel['shorthand.yaml']['Vendor Weight Options'][$Nibbles[0]][$Nibbles[1]]) &&
        $phpMussel['shorthand.yaml']['Vendor Weight Options'][$Nibbles[0]][$Nibbles[1]] === 'Weighted'
    ) || (
        !empty($phpMussel['shorthand.yaml']['Vendor Weight Options'][$Nibbles[0]]) &&
        $phpMussel['shorthand.yaml']['Vendor Weight Options'][$Nibbles[0]] === 'Weighted'
    )) {
        $phpMussel['InstanceCache']['weighted'] = true;
    }

    /** Populate signature metadata information. */
    if (empty($SkipMeta) && !empty($phpMussel['shorthand.yaml']['Metadata Shorthand'][$Nibbles[1]])) {
        $Out .= $phpMussel['shorthand.yaml']['Metadata Shorthand'][$Nibbles[1]] . '.';
    }

    /** Byte 2 contains vector information. */
    $Nibbles = $phpMussel['split_nibble']($VN[2]);

    /** Populate vector information. */
    if (!empty($phpMussel['shorthand.yaml']['Vector Shorthand'][$Nibbles[0]][$Nibbles[1]])) {
        $Out .= $phpMussel['shorthand.yaml']['Vector Shorthand'][$Nibbles[0]][$Nibbles[1]] . '.';
    }

    /** Byte 3 contains malware type information. */
    $Nibbles = $phpMussel['split_nibble']($VN[3]);

    /** Populate malware type information. */
    if (!empty($phpMussel['shorthand.yaml']['Malware Type Shorthand'][$Nibbles[0]][$Nibbles[1]])) {
        $Out .= $phpMussel['shorthand.yaml']['Malware Type Shorthand'][$Nibbles[0]][$Nibbles[1]] . '.';
    }

    /** Populate ignore options. */
    if (!empty($phpMussel['shorthand.yaml']['Malware Type Ignore Options'][$Nibbles[0]][$Nibbles[1]])) {
        $IgnoreOption = $phpMussel['shorthand.yaml']['Malware Type Ignore Options'][$Nibbles[0]][$Nibbles[1]];
        if (isset($phpMussel['Config']['signatures'][$IgnoreOption]) && !$phpMussel['Config']['signatures'][$IgnoreOption]) {
            $phpMussel['InstanceCache']['ignoreme'] = true;
        }
    }

    /** Return the signature name and exit the closure. */
    return $Out . substr($VN, 4);

};

/**
 * Used for performing lookups to the Google Safe Browsing API (v4).
 * @link https://developers.google.com/safe-browsing/v4/lookup-api
 *
 * @param array $urls An array of the URLs to lookup.
 * @param array $URLsNoLookup An optional array of URLs to NOT lookup.
 * @param array $DomainsNoLookup An optional array of domains to NOT lookup.
 * @return int The results of the lookup. 200 if AT LEAST ONE of the queried
 *      URLs are listed on any of Google Safe Browsing lists; 204 if NONE of
 *      the queried URLs are listed on any of Google Safe Browsing lists; 400
 *      if the request is malformed; 401 if the API key is missing or isn't
 *      authorised; 503 if the service is unavailable (e.g., if it's been
 *      throttled); 999 if something unexpected occurs (such as, for example,
 *      if a programmatic error is encountered).
 */
$phpMussel['SafeBrowseLookup'] = function ($urls, $URLsNoLookup = [], $DomainsNoLookup = []) use (&$phpMussel) {
    if (empty($phpMussel['Config']['urlscanner']['google_api_key'])) {
        return 401;
    }
    /** Count and prepare the URLs. */
    if (!$c = count($urls)) {
        return 400;
    }
    for ($i = 0; $i < $c; $i++) {
        $Domain = (strpos($urls[$i], '/') !== false) ? $phpMussel['substrbf']($urls[$i], '/') : $urls[$i];
        if (!empty($URLsNoLookup[$urls[$i]]) || !empty($DomainsNoLookup[$Domain])) {
            unset($urls[$i]);
            continue;
        }
        $urls[$i] = ['url' => $urls[$i]];
    }
    sort($urls);
    /** After we've prepared the URLs, we prepare our JSON array. */
    $arr = json_encode([
        'client' => [
            'clientId' => 'phpMussel',
            'clientVersion' => $phpMussel['ScriptVersion']
        ],
        'threatInfo' => [
            'threatTypes' => [
                'THREAT_TYPE_UNSPECIFIED',
                'MALWARE',
                'SOCIAL_ENGINEERING',
                'UNWANTED_SOFTWARE',
                'POTENTIALLY_HARMFUL_APPLICATION'
            ],
            'platformTypes' => ['ANY_PLATFORM'],
            'threatEntryTypes' => ['URL'],
            'threatEntries' => $urls
        ]
    ], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);

    /** Fetch the cache entry for Google Safe Browsing, if it doesn't already exist. */
    if (!isset($phpMussel['InstanceCache']['urlscanner_google'])) {
        $phpMussel['InstanceCache']['urlscanner_google'] = $phpMussel['FetchCache']('urlscanner_google');
    }
    /** Generate new cache expiry time. */
    $newExpiry = $phpMussel['Time'] + $phpMussel['Config']['urlscanner']['cache_time'];
    /** Generate a reference for the cache entry for this lookup. */
    $cacheRef = md5($arr) . ':' . $c . ':' . strlen($arr) . ':';
    /** This will contain the lookup response. */
    $Response = '';
    /** Check if this lookup has already been performed. */
    while (strpos($phpMussel['InstanceCache']['urlscanner_google'], $cacheRef) !== false) {
        $Response = $phpMussel['substrbf']($phpMussel['substral']($phpMussel['InstanceCache']['urlscanner_google'], $cacheRef), ';');
        /** Safety mechanism. */
        if (!$Response || strpos($phpMussel['InstanceCache']['urlscanner_google'], $cacheRef . $Response . ';') === false) {
            $Response = '';
            break;
        }
        $expiry = $phpMussel['substrbf']($Response, ':');
        if ($expiry > $phpMussel['Time']) {
            $Response = $phpMussel['substraf']($Response, ':');
            break;
        }
        $phpMussel['InstanceCache']['urlscanner_google'] =
            str_ireplace($cacheRef . $Response . ';', '', $phpMussel['InstanceCache']['urlscanner_google']);
        $Response = '';
    }
    /** If this lookup has already been performed, return the results without repeating it. */
    if ($Response) {
        /** Update the cache entry for Google Safe Browsing. */
        $newExpiry = $phpMussel['SaveCache']('urlscanner_google', $newExpiry, $phpMussel['InstanceCache']['urlscanner_google']);
        if ($Response === '200') {
            /** Potentially harmful URL detected. */
            return 200;
        } elseif ($Response === '204') {
            /** Potentially harmful URL *NOT* detected. */
            return 204;
        } elseif ($Response === '400') {
            /** Bad/malformed request. */
            return 400;
        } elseif ($Response === '401') {
            /** Unauthorised (possibly a bad API key). */
            return 401;
        } elseif ($Response === '503') {
            /** Service unavailable. */
            return 503;
        }
        /** Something bad/unexpected happened. */
        return 999;
    }

    /** Prepare the URL to use with cURL. */
    $uri =
        'https://safebrowsing.googleapis.com/v4/threatMatches:find?key=' .
        $phpMussel['Config']['urlscanner']['google_api_key'];

    /** cURL stuff here. */
    $Request = curl_init($uri);
    curl_setopt($Request, CURLOPT_FRESH_CONNECT, true);
    curl_setopt($Request, CURLOPT_HEADER, false);
    curl_setopt($Request, CURLOPT_POST, true);
    /** Ensure it knows we're sending JSON data. */
    curl_setopt($Request, CURLOPT_HTTPHEADER, ['Content-type: application/json']);
    /** The Google Safe Browsing API requires HTTPS+SSL (there's no way around this). */
    curl_setopt($Request, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
    curl_setopt($Request, CURLOPT_RETURNTRANSFER, true);
    /*
     * Setting "CURLOPT_SSL_VERIFYPEER" to false can be somewhat risky due to man-in-the-middle attacks, but lookups
     * seemed to always fail when it was set to true during testing, so, for the sake of this actually working at all,
     * I'm setting it as false, but we should try to fix this in the future at some point.
     */
    curl_setopt($Request, CURLOPT_SSL_VERIFYPEER, false);
    /* We don't want to leave the client waiting for *too* long. */
    curl_setopt($Request, CURLOPT_TIMEOUT, $phpMussel['Timeout']);
    curl_setopt($Request, CURLOPT_USERAGENT, $phpMussel['ScriptUA']);
    curl_setopt($Request, CURLOPT_POSTFIELDS, $arr);

    /** Execute and get the response. */
    $Response = curl_exec($Request);
    $phpMussel['LookupCount']++;

    /** Check for errors and print to the screen if there were any. */
    if (!$Response) {
        throw new \Exception(curl_error($Request));
    }

    /** Close the cURL session. */
    curl_close($Request);

    if (strpos($Response, '"matches":') !== false) {
        /** Potentially harmful URL detected. */
        $returnVal = 200;
    } else {
        /** Potentially harmful URL *NOT* detected. */
        $returnVal = 204;
    }

    /** Update the cache entry for Google Safe Browsing. */
    $phpMussel['InstanceCache']['urlscanner_google'] .= $cacheRef . ':' . $newExpiry . ':' . $returnVal . ';';
    $newExpiry = $phpMussel['SaveCache']('urlscanner_google', $newExpiry, $phpMussel['InstanceCache']['urlscanner_google']);

    return $returnVal;
};

/**
 * Checks whether signature length is confined within an acceptable limit.
 *
 * @param int $Length
 * @return bool
 */
$phpMussel['ConfineLength'] = function ($Length) {
    return ($Length < 4 || $Length > 1024);
};

/**
 * Detection trigger closure (appends detection information). Generally called
 * from within the data handler. When as a method, should treat as private.
 */
$phpMussel['Detected'] = function (&$heur, &$lnap, &$VN, &$ofn, &$ofnSafe, &$out, &$flagged, &$MD5, &$str_len) use (&$phpMussel) {
    if (!$flagged) {
        $phpMussel['killdata'] .= $MD5 . ':' . $str_len . ':' . $ofn . "\n";
        $flagged = true;
    }
    $heur['detections']++;
    $phpMussel['InstanceCache']['detections_count']++;
    if ($phpMussel['InstanceCache']['weighted']) {
        $heur['weight']++;
        $heur['cli'] .= $lnap . sprintf(
            $phpMussel['L10N']->getString('_exclamation_final'),
            sprintf($phpMussel['L10N']->getString('detected'), $VN)
        ) . "\n";
        $heur['web'] .= sprintf(
            $phpMussel['L10N']->getString('_exclamation'),
            sprintf($phpMussel['L10N']->getString('detected'), $VN) . ' (' . $ofnSafe . ')'
        );
        return;
    }
    $out .= $lnap . sprintf(
        $phpMussel['L10N']->getString('_exclamation_final'),
        sprintf($phpMussel['L10N']->getString('detected'), $VN)
    ) . "\n";
    $phpMussel['whyflagged'] .= sprintf(
        $phpMussel['L10N']->getString('_exclamation'),
        sprintf($phpMussel['L10N']->getString('detected'), $VN) . ' (' . $ofnSafe . ')'
    );
};

/**
 * All needles must assert as the assert state being instances of haystacks.
 *
 * @param array $Haystacks The haystacks.
 * @param array $Needles The needles.
 * @param string $Padding An optional string to pad haystacks and needles.
 * @param bool $AssertState MUST (true) or must NOT (false) be an instance of.
 * @param bool $Mode ALL (false) or ANY (true) must assert.
 * @return bool True if requirement conforms; False otherwise.
 */
$phpMussel['ContainsMustAssert'] = function ($Haystacks, $Needles, $Padding = ',', $AssertState = false, $Mode = false) {
    foreach ($Haystacks as $Haystack) {
        $Haystack = $Padding . $Haystack . $Padding;
        foreach ($Needles as $Needle) {
            $Needle = $Padding . $Needle . $Padding;
            if (!$Mode) {
                if (!is_bool(strpos($Haystack, $Needle)) !== $AssertState) {
                    return false;
                }
                continue;
            }
            if (!is_bool(strpos($Haystack, $Needle)) === $AssertState) {
                return true;
            }
        }
    }
    return !$Mode;
};

/**
 * Confines a string boundary as per rules specified by parameters.
 *
 * @param string $Data The string.
 * @param string|int $Initial The start of the boundary or string initial offset value.
 * @param string|int $Terminal The end of the boundary or string terminal offset value.
 * @param array $SectionOffsets Section offset values.
 */
$phpMussel['DataConfineByOffsets'] = function (&$Data, &$Initial, &$Terminal, &$SectionOffsets) {
    if ($Initial === '*' && $Terminal === '*') {
        return;
    }
    if (substr($Initial, 0, 2) === 'SE') {
        $SectionNum = (int)substr($Initial, 2);
        $Initial = '*';
        $Terminal = '*';
        if (isset($SectionOffsets[$SectionNum][0])) {
            $Data = substr($Data, $SectionOffsets[$SectionNum][0] * 2);
        }
        if (isset($SectionOffsets[$SectionNum][1])) {
            $Data = substr($Data, 0, $SectionOffsets[$SectionNum][1] * 2);
        }
    } elseif (substr($Initial, 0, 2) === 'SL') {
        $Remainder = strlen($Initial) > 3 && substr($Initial, 2, 1) === '+' ? (substr($Initial, 3) ?: 0) : 0;
        $Initial = '*';
        $Final = count($SectionOffsets);
        if ($Final > 0 && isset($SectionOffsets[$Final - 1][0])) {
            $Data = substr($Data, ($SectionOffsets[$Final - 1][0] + $Remainder) * 2);
        }
        if ($Terminal !== '*' && $Terminal !== 'Z') {
            $Data = substr($Data, 0, $Terminal * 2);
            $Terminal = '*';
        }
    } elseif (substr($Initial, 0, 1) === 'S') {
        if (($PlusPos = strpos($Initial, '+')) !== false) {
            $SectionNum = substr($Initial, 1, $PlusPos - 1) ?: 0;
            $Remainder = substr($Initial, $PlusPos + 1) ?: 0;
        } else {
            $SectionNum = substr($Initial, 1) ?: 0;
            $Remainder = 0;
        }
        $Initial = '*';
        if (isset($SectionOffsets[$SectionNum][0])) {
            $Data = substr($Data, ($SectionOffsets[$SectionNum][0] + $Remainder) * 2);
        }
        if ($Terminal !== '*' && $Terminal !== 'Z') {
            $Data = substr($Data, 0, $Terminal * 2);
            $Terminal = '*';
        }
    } else {
        if ($Initial !== '*' && $Initial !== 'A') {
            $Data = substr($Data, $Initial * 2);
            $Initial = '*';
        }
        if ($Terminal !== '*' && $Terminal !== 'Z') {
            $Data = substr($Data, 0, $Terminal * 2);
            $Terminal = '*';
        }
    }
};

/**
 * Responsible for handling any data fed to it from the recursor. It shouldn't
 * be called manually nor from any other contexts. It takes the data given to
 * it from the recursor and checks that data against the various signatures of
 * phpMussel, before returning the results of those checks back to the
 * recursor.
 *
 * @param string $str Raw binary data to be checked, supplied by the parent
 *      closure (generally, the contents of the files to be scanned).
 * @param int $dpt Represents the current depth of recursion from which the
 *      closure has been called, used for determining how far to indent any
 *      entries generated for logging and for the display of scan results in
 *      CLI.
 * @param string $ofn Represents the "original filename" of the file being
 *      scanned (in this context, referring to the name supplied by the upload
 *      client or CLI operator, as opposed to the temporary filename assigned
 *      by the server or anything else).
 * @return array|bool Returns an array containing the results of the scan as
 *      both an integer (the first element) and as human-readable text (the
 *      second element), or returns false if any problems occur preventing the
 *      data handler from completing its normal process.
 */
$phpMussel['DataHandler'] = function ($str = '', $dpt = 0, $ofn = '') use (&$phpMussel) {
    /** If the memory cache isn't set at this point, something has gone very wrong. */
    if (!isset($phpMussel['InstanceCache'])) {
        throw new \Exception($phpMussel['L10N']->getString(
            'required_variables_not_defined'
        ) ?: '[phpMussel] Required variables aren\'t defined: Can\'t continue.');
    }

    /** Plugin hook: "DataHandler_start". */
    $phpMussel['Execute_Hook']('DataHandler_start');

    /** Identifies whether the scan target has been flagged for any reason yet. */
    $flagged = false;

    /** Increment scan depth. */
    $dpt++;

    /** Controls indenting relating to scan depth for normal logging and for CLI-mode scanning. */
    $lnap = str_pad('> ', ($dpt + 1), '-', STR_PAD_LEFT);

    /** Output variable (for when the output is a string). */
    $Out = '';

    /** There's no point bothering to scan zero-byte files. */
    if (!$str_len = strlen($str)) {
        return [1, ''];
    }

    $md5 = md5($str);
    $sha = sha1($str);
    $sha256 = hash('sha256', $str);
    $crc = hash('crc32b', $str);
    /** $fourcc: First four bytes of the scan target in hexadecimal notation. */
    $fourcc = strtolower(bin2hex(substr($str, 0, 4)));
    /** $twocc: First two bytes of the scan target in hexadecimal notation. */
    $twocc = substr($fourcc, 0, 4);
    /**
     * $CoExMeta: Contains metadata pertaining to the scan target, intended to
     * be used by the "complex extended" signatures.
     */
    $CoExMeta =
        '$ofn:' . $ofn . ';md5($ofn):' . md5($ofn) . ';$dpt:' . $dpt .
        ';$str_len:' . $str_len . ';$md5:' . $md5 . ';$sha:' . $sha .
        ';$crc:' . $crc . ';$fourcc:' . $fourcc . ';$twocc:' . $twocc . ';';

    /** Indicates whether a signature is considered a "weighted" signature. */
    $phpMussel['InstanceCache']['weighted'] = false;

    /** Variables used for weighted signatures and for heuristic analysis. */
    $heur = ['detections' => 0, 'weight' => 0, 'cli' => '', 'web' => ''];

    /** Scan target has no name? That's a little suspicious. */
    if (!$ofn) {
        $phpMussel['killdata'] .= $md5 . ':' . $str_len . ":\n";
        $phpMussel['InstanceCache']['detections_count']++;
        $Out .= $lnap . sprintf(
            $phpMussel['L10N']->getString('_exclamation_final'),
            $phpMussel['L10N']->getString('scan_missing_filename')
        ) . "\n";
        $phpMussel['whyflagged'] .= sprintf(
            $phpMussel['L10N']->getString('_exclamation'),
            $phpMussel['L10N']->getString('scan_missing_filename')
        );
        return [2, $Out];
    }
    /** URL-encoded version of the scan target name. */
    $ofnSafe = urlencode($ofn);

    /** Generate cache ID. */
    $phpMussel['HashCacheData'] = $md5 . md5($ofn);

    /** Register object scanned. */
    if (isset($phpMussel['cli_args'][1]) && $phpMussel['cli_args'][1] == 'cli_scan') {
        $phpMussel['Stats-Increment']('CLI-Scanned', 1);
    } else {
        $phpMussel['Stats-Increment']($phpMussel['EOF'] ? 'API-Scanned' : 'Web-Scanned', 1);
    }

    /**
     * Check for the existence of a cache entry corresponding to the file
     * being scanned, and if it exists, use it instead of scanning the file.
     */
    if (isset($phpMussel['HashCache']['Data'][$phpMussel['HashCacheData']])) {
        if (!$phpMussel['EOF']) {
            $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
        }
        if (!empty($phpMussel['HashCache']['Data'][$phpMussel['HashCacheData']][2])) {
            $phpMussel['InstanceCache']['detections_count']++;
            $Out .= $phpMussel['HexSafe']($phpMussel['HashCache']['Data'][$phpMussel['HashCacheData']][2]);
            if (!empty($phpMussel['HashCache']['Data'][$phpMussel['HashCacheData']][3])) {
                $phpMussel['whyflagged'] .= $phpMussel['HexSafe']($phpMussel['HashCache']['Data'][$phpMussel['HashCacheData']][3]);
            }
        }

        /** Set debug values, if this has been enabled. */
        if (isset($phpMussel['DebugArr'])) {
            $phpMussel['DebugArrKey'] = count($phpMussel['DebugArr']);
            $phpMussel['DebugArr'][$phpMussel['DebugArrKey']] = [
                'Filename' => $ofn,
                'FromCache' => true,
                'Depth' => $dpt,
                'Size' => $str_len,
                'MD5' => $md5,
                'SHA1' => $sha,
                'SHA256' => $sha256,
                'CRC32B' => $crc,
                '2CC' => $twocc,
                '4CC' => $fourcc,
                'ScanPhase' => $phpMussel['InstanceCache']['phase'],
                'Container' => $phpMussel['InstanceCache']['container'],
                'Results' => !$Out ? 1 : 2,
                'Output' => $Out
            ];
        }

        /** Object not flagged. */
        if (!$Out) {
            return [1, ''];
        }
        /** Register object flagged. */
        if (isset($phpMussel['cli_args'][1]) && $phpMussel['cli_args'][1] == 'cli_scan') {
            $phpMussel['Stats-Increment']('CLI-Flagged', 1);
        } else {
            $phpMussel['Stats-Increment']($phpMussel['EOF'] ? 'API-Flagged' : 'Web-Blocked', 1);
        }
        /** Object flagged. */
        return [2, $Out];
    }

    /** Indicates whether we're in CLI-mode. */
    $climode = ($phpMussel['Mussel_sapi'] && $phpMussel['Mussel_PHP']) ? 1 : 0;

    if (
        $phpMussel['Config']['attack_specific']['scannable_threshold'] > 0 &&
        $str_len > $phpMussel['ReadBytes']($phpMussel['Config']['attack_specific']['scannable_threshold'])
    ) {
        $str_len = $phpMussel['ReadBytes']($phpMussel['Config']['attack_specific']['scannable_threshold']);
        $str = substr($str, 0, $str_len);
        $str_cut = 1;
    } else {
        $str_cut = 0;
    }

    /** Indicates whether we need to decode the contents of the scan target. */
    $decode_or_not = ((
        $phpMussel['Config']['attack_specific']['decode_threshold'] > 0 &&
        $str_len > $phpMussel['ReadBytes']($phpMussel['Config']['attack_specific']['decode_threshold'])
    ) || $str_len < 16) ? 0 : 1;

    /** These are sometimes used by the "CoEx" ("complex extended") signatures. */
    $len_kb = ($str_len > 1024) ? 1 : 0;
    $len_hmb = ($str_len > 524288) ? 1 : 0;
    $len_mb = ($str_len > 1048576) ? 1 : 0;
    $len_hgb = ($str_len > 536870912) ? 1 : 0;
    $phase = $phpMussel['InstanceCache']['phase'];
    $container = $phpMussel['InstanceCache']['container'];
    $pdf_magic = ($fourcc == '25504446');

    /** CoEx flags for configuration directives related to signatures. */
    foreach ([
        'detect_adware',
        'detect_joke_hoax',
        'detect_pua_pup',
        'detect_packer_packed',
        'detect_shell',
        'detect_deface',
        'detect_encryption'
    ] as $Flag) {
        $$Flag = $phpMussel['Config']['signatures'][$Flag] ? 1 : 0;
    }

    /** Cleanup. */
    unset($Flag);

    /** Available if the file is a Chrome extension. */
    $CrxPubKey = empty($phpMussel['CrxPubKey']) ? '' : $phpMussel['CrxPubKey'];
    $CrxSig = empty($phpMussel['CrxSig']) ? '' : $phpMussel['CrxSig'];

    /** Get file extensions. */
    list($xt, $xts, $gzxt, $gzxts) = $phpMussel['FetchExt']($ofn);

    $CoExMeta .= '$xt:' . $xt . ';$xts:' . $xts . ';';

    /** Input ($str) as hexadecimal data. */
    $str_hex = bin2hex($str);
    $str_hex_len = $str_len * 2;

    /** Input ($str) normalised. */
    $str_norm = $phpMussel['prescan_normalise']($str, false, $decode_or_not);
    $str_norm_len = strlen($str_norm);

    /** Normalised input ($str_norm) as hexadecimal data. */
    $str_hex_norm = bin2hex($str_norm);
    $str_hex_norm_len = $str_norm_len * 2;

    /** Input ($str) normalised for HTML. */
    $str_html = $phpMussel['prescan_normalise']($str, true, $decode_or_not);
    $str_html_len = strlen($str_html);

    /** HTML normalised input ($str_html) as hexadecimal data. */
    $str_hex_html = bin2hex($str_html);
    $str_hex_html_len = $str_html_len * 2;

    /** Look for potential Linux/ELF indicators. */
    $is_elf = ($fourcc === '7f454c46' || $xt === 'elf');

    /** Look for potential graphics/image indicators. */
    $is_graphics = empty($str) ? false : $phpMussel['Indicator-Image']($xt, substr($str_hex, 0, 32));

    /** Look for potential HTML indicators. */
    $is_html = (strpos(
        ',asp*,dht*,eml*,hta*,htm*,jsp*,php*,sht*,',
        ',' . $xts . ','
    ) !== false || preg_match(
        '/3c(?:21646f6374797065|6(?:120|26f6479|8656164|8746d6c|96672616d65|96d67|f626a656374)|7(?:36372697074|461626c65|469746c65))/i',
        $str_hex_norm
    ) || preg_match(
        '/(?:6(?:26f6479|8656164|8746d6c)|7(?:36372697074|461626c65|469746c65))3e/i',
        $str_hex_norm
    ));

    /** Look for potential email indicators. */
    $is_email = (strpos(
        ',htm*,ema*,eml*,',
        ',' . $xts . ','
    ) !== false || preg_match(
        '/0a(?:4(?:36f6e74656e742d54797065|4617465|6726f6d|d6573736167652d4944|d4' .
        '94d452d56657273696f6e)|5(?:265706c792d546f|2657475726e2d50617468|3656e64' .
        '6572|375626a656374|46f|82d4d61696c6572))3a20/i',
    $str_hex) || preg_match('/0a2d2d.{32}(?:2d2d)?(?:0d)?0a/i', $str_hex));

    /** Look for potential Mach-O indicators. */
    $is_macho = preg_match('/^(?:cafe(?:babe|d00d)|c[ef]faedfe|feedfac[ef])$/', $fourcc);

    /** Look for potential PDF indicators. */
    $is_pdf = ($pdf_magic || $xt === 'pdf');

    /** Look for potential Shockwave/SWF indicators. */
    $is_swf = (
        strpos(',435753,465753,5a5753,', ',' . substr($str_hex, 0, 6) . ',') !== false ||
        strpos(',swf,swt,', ',' . $xt . ',') !== false
    );

    /** "Infectable"? Used by ClamAV General and ClamAV ASCII signatures. */
    $infectable = true;

    /** "Asciiable"? Used by all ASCII signatures. */
    $asciiable = (bool)$str_hex_norm_len;

    /** Used to identify whether to check against OLE signatures. */
    $is_ole = !empty($phpMussel['InstanceCache']['file_is_ole']) && (
        !empty($phpMussel['InstanceCache']['file_is_macro']) ||
        strpos(',bin,ole,xml,rels,', ',' . $xt . ',') !== false
    );

    /** Worked by the switch file. */
    $fileswitch = 'unassigned';
    if (!isset($phpMussel['InstanceCache']['switch.dat'])) {
        $phpMussel['InstanceCache']['switch.dat'] = $phpMussel['ReadFileAsArray']($phpMussel['sigPath'] . 'switch.dat', FILE_IGNORE_NEW_LINES);
    }
    if (!$phpMussel['InstanceCache']['switch.dat']) {
        $phpMussel['InstanceCache']['scan_errors']++;
        if (!$phpMussel['Config']['signatures']['fail_silently']) {
            if (!$flagged) {
                $phpMussel['killdata'] .= $md5 . ':' . $str_len . ":\n";
            }
            $phpMussel['whyflagged'] .= sprintf(
                $phpMussel['L10N']->getString('_exclamation'),
                $phpMussel['L10N']->getString('scan_signature_file_missing') . ' (switch.dat)'
            );
            return [-3, $lnap . sprintf(
                $phpMussel['L10N']->getString('_exclamation_final'),
                $phpMussel['L10N']->getString('scan_signature_file_missing') . ' (switch.dat)'
            ) . "\n"];
        }
    }
    foreach ($phpMussel['InstanceCache']['switch.dat'] as $ThisRule) {
        $Switch = (strpos($ThisRule, ';') === false) ? $ThisRule : $phpMussel['substral']($ThisRule, ';');
        if (strpos($Switch, '=') === false) {
            continue;
        }
        $Switch = explode('=', preg_replace('/[^\x20-\xff]/', '', $Switch));
        if (empty($Switch[0])) {
            continue;
        }
        if (empty($Switch[1])) {
            $Switch[1] = false;
        }
        $theSwitch = $Switch[0];
        $ThisRule = (strpos($ThisRule, ';') === false) ? [$ThisRule] : explode(';', $phpMussel['substrbl']($ThisRule, ';'));
        foreach ($ThisRule as $Fragment) {
            $Fragment = (strpos($Fragment, ':') === false) ? false : $phpMussel['SplitSigParts']($Fragment, 7);
            if (empty($Fragment[0])) {
                continue 2;
            }
            if ($Fragment[0] === 'LV') {
                if (!isset($Fragment[1]) || substr($Fragment[1], 0, 1) !== '$') {
                    continue 2;
                }
                $lv_haystack = substr($Fragment[1],1);
                if (!isset($$lv_haystack) || is_array($$lv_haystack)) {
                    continue 2;
                }
                $lv_haystack = $$lv_haystack;
                if ($climode) {
                    $lv_haystack = $phpMussel['substral']($phpMussel['substral']($lv_haystack, '/'), "\\");
                }
                $lv_needle = isset($Fragment[2]) ? $Fragment[2] : '';
                $pos_A = isset($Fragment[3]) ? $Fragment[3] : 0;
                $pos_Z = isset($Fragment[4]) ? $Fragment[4] : 0;
                $lv_min = isset($Fragment[5]) ? $Fragment[5] : 0;
                $lv_max = isset($Fragment[6]) ? $Fragment[6] : -1;
                if (!$phpMussel['lv_match']($lv_needle, $lv_haystack, $pos_A, $pos_Z, $lv_min, $lv_max)) {
                    continue 2;
                }
            } elseif (isset($Fragment[2])) {
                if (isset($Fragment[3])) {
                    if ($Fragment[2] === 'A') {
                        if (
                            strpos(',FD,FD-RX,FD-NORM,FD-NORM-RX,', ',' . $Fragment[0] . ',') === false || (
                                $Fragment[0] === 'FD' &&
                                strpos("\x01" . substr($str_hex, 0, $Fragment[3] * 2), "\x01" . $Fragment[1]) === false
                            ) || (
                                $Fragment[0] === 'FD-RX' &&
                                !preg_match('/\A(?:' . $Fragment[1] . ')/i', substr($str_hex, 0, $Fragment[3] * 2))
                            ) || (
                                $Fragment[0] === 'FD-NORM' &&
                                strpos("\x01" . substr($str_hex_norm, 0, $Fragment[3] * 2), "\x01" . $Fragment[1]) === false
                            ) || (
                                $Fragment[0] === 'FD-NORM-RX' &&
                                !preg_match('/\A(?:' . $Fragment[1] . ')/i', substr($str_hex_norm, 0, $Fragment[3] * 2))
                            )
                        ) {
                            continue 2;
                        }
                    } elseif (
                        strpos(',FD,FD-RX,FD-NORM,FD-NORM-RX,', ',' . $Fragment[0] . ',') === false || (
                            $Fragment[0] === 'FD' &&
                            strpos(substr($str_hex, $Fragment[2] * 2, $Fragment[3] * 2), $Fragment[1]) === false
                        ) || (
                            $Fragment[0] === 'FD-RX' &&
                            !preg_match('/(?:' . $Fragment[1] . ')/i', substr($str_hex, $Fragment[2] * 2, $Fragment[3]*2))
                        ) || (
                            $Fragment[0] === 'FD-NORM' &&
                            strpos(substr($str_hex_norm, $Fragment[2] * 2, $Fragment[3] * 2), $Fragment[1]) === false
                        ) || (
                            $Fragment[0] === 'FD-NORM-RX' &&
                            !preg_match('/(?:' . $Fragment[1] . ')/i', substr($str_hex_norm, $Fragment[2] * 2, $Fragment[3]*2))
                        )
                    ) {
                        continue 2;
                    }
                } else {
                    if ($Fragment[2] === 'A') {
                        if (
                            strpos(',FN,FD,FD-RX,FD-NORM,FD-NORM-RX,', ',' . $Fragment[0] . ',') === false || (
                                $Fragment[0] === 'FN' &&
                                !preg_match('/\A(?:' . $Fragment[1] . ')/i', $ofn)
                            ) || (
                                $Fragment[0] === 'FD' &&
                                strpos("\x01" . $str_hex, "\x01" . $Fragment[1]) === false
                            ) || (
                                $Fragment[0] === 'FD-RX' &&
                                !preg_match('/\A(?:' . $Fragment[1] . ')/i', $str_hex)
                            ) || (
                                $Fragment[0] === 'FD-NORM' &&
                                strpos("\x01" . $str_hex_norm, "\x01" . $Fragment[1]) === false
                            ) || (
                                $Fragment[0] === 'FD-NORM-RX' &&
                                !preg_match('/\A(?:' . $Fragment[1] . ')/i', $str_hex_norm)
                            )
                        ) {
                            continue 2;
                        }
                    } elseif (
                        strpos(',FD,FD-RX,FD-NORM,FD-NORM-RX,', ',' . $Fragment[0] . ',') === false || (
                            $Fragment[0] === 'FD' &&
                            strpos(substr($str_hex, $Fragment[2] * 2), $Fragment[1]) === false
                        ) || (
                            $Fragment[0] === 'FD-RX' &&
                            !preg_match('/(?:' . $Fragment[1] . ')/i', substr($str_hex, $Fragment[2] * 2))
                        ) || (
                            $Fragment[0] === 'FD-NORM' &&
                            strpos(substr($str_hex_norm, $Fragment[2] * 2), $Fragment[1]) === false
                        ) || (
                            $Fragment[0] === 'FD-NORM-RX' &&
                            !preg_match('/(?:' . $Fragment[1] . ')/i', substr($str_hex_norm, $Fragment[2] * 2))
                        )
                    ) {
                        continue 2;
                    }
                }
            } elseif (
                ($Fragment[0] === 'FN' && !preg_match('/(?:' . $Fragment[1] . ')/i', $ofn)) ||
                ($Fragment[0] === 'FS-MIN' && $str_len < $Fragment[1]) ||
                ($Fragment[0] === 'FS-MAX' && $str_len > $Fragment[1]) ||
                ($Fragment[0] === 'FD' && strpos($str_hex, $Fragment[1]) === false) ||
                ($Fragment[0] === 'FD-RX' && !preg_match('/(?:' . $Fragment[1] . ')/i', $str_hex)) ||
                ($Fragment[0] === 'FD-NORM' && strpos($str_hex_norm, $Fragment[1]) === false) ||
                ($Fragment[0] === 'FD-NORM-RX' && !preg_match('/(?:' . $Fragment[1] . ')/i', $str_hex_norm))
            ) {
                continue 2;
            } elseif (substr($Fragment[0], 0, 1) === '$') {
                $vf = substr($Fragment[0], 1);
                if (!isset($$vf) || is_array($$vf) || $$vf != $Fragment[1]) {
                    continue 2;
                }
            } elseif (substr($Fragment[0], 0, 2) === '!$') {
                $vf = substr($Fragment[0], 2);
                if (!isset($$vf) || is_array($$vf) || $$vf == $Fragment[1]) {
                    continue 2;
                }
            } elseif (strpos(',FN,FS-MIN,FS-MAX,FD,FD-RX,FD-NORM,FD-NORM-RX,', ',' . $Fragment[0] . ',') === false) {
                continue 2;
            }
        }
        if (count($Switch) > 1) {
            if ($Switch[1] === 'true') {
                $$theSwitch = true;
                continue;
            }
            if ($Switch[1] === 'false') {
                $$theSwitch = false;
                continue;
            }
            $$theSwitch = $Switch[1];
        } else {
            if (!isset($$theSwitch)) {
                $$theSwitch = true;
                continue;
            }
            $$theSwitch = (!$$theSwitch);
        }
    }
    unset($theSwitch, $Switch, $ThisRule);

    /** Section offsets. */
    $SectionOffsets = [];

    /** Confirmation of whether or not the file is a valid PE file. */
    $is_pe = false;

    /** Number of PE sections in the file. */
    $NumOfSections = 0;

    $PEFileDescription =
    $PEFileVersion =
    $PEProductName =
    $PEProductVersion =
    $PECopyright =
    $PEOriginalFilename =
    $PECompanyName = '';
    if (
        !empty($phpMussel['InstanceCache']['PE_Sectional']) ||
        !empty($phpMussel['InstanceCache']['PE_Extended']) ||
        $phpMussel['Config']['attack_specific']['corrupted_exe']
    ) {
        $PEArr = [];
        $PEArr['SectionArr'] = [];
        if ($twocc === '4d5a') {
            $PEArr['Offset'] = $phpMussel['UnpackSafe']('S', substr($str, 60, 4));
            $PEArr['Offset'] = $PEArr['Offset'][1];
            while (true) {
                $PEArr['DoScan'] = true;
                if ($PEArr['Offset'] < 1 || $PEArr['Offset'] > 16384 || $PEArr['Offset'] > $str_len) {
                    $PEArr['DoScan'] = false;
                    break;
                }
                $PEArr['Magic'] = substr($str, $PEArr['Offset'], 2);
                if ($PEArr['Magic']!=='PE') {
                    $PEArr['DoScan'] = false;
                    break;
                }
                $PEArr['Proc'] = $phpMussel['UnpackSafe']('S', substr($str, $PEArr['Offset'] + 4, 2));
                $PEArr['Proc'] = $PEArr['Proc'][1];
                if ($PEArr['Proc'] != 0x14c && $PEArr['Proc'] != 0x8664) {
                    $PEArr['DoScan'] = false;
                    break;
                }
                $PEArr['NumOfSections'] = $phpMussel['UnpackSafe']('S', substr($str, $PEArr['Offset'] + 6, 2));
                $NumOfSections = $PEArr['NumOfSections'] = $PEArr['NumOfSections'][1];
                $CoExMeta .= 'PE_Offset:' . $PEArr['Offset'] . ';PE_Proc:' . $PEArr['Proc'] . ';NumOfSections:' . $NumOfSections . ';';
                if ($NumOfSections < 1 || $NumOfSections > 40) {
                    $PEArr['DoScan'] = false;
                }
                break;
            }
            if (!$PEArr['DoScan']) {
                if ($phpMussel['Config']['attack_specific']['corrupted_exe']) {
                    if (!$flagged) {
                        $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                        $flagged = true;
                    }
                    $heur['detections']++;
                    $phpMussel['InstanceCache']['detections_count']++;
                    $Out .= $lnap . sprintf(
                        $phpMussel['L10N']->getString('_exclamation_final'),
                        $phpMussel['L10N']->getString('corrupted')
                    ) . "\n";
                    $phpMussel['whyflagged'] .= sprintf(
                        $phpMussel['L10N']->getString('_exclamation'),
                        $phpMussel['L10N']->getString('corrupted') . ' (' . $ofnSafe . ')'
                    );
                }
            } else {
                $is_pe = true;
                $asciiable = false;
                $PEArr['OptHdrSize'] = $phpMussel['UnpackSafe']('S', substr($str, $PEArr['Offset'] + 20, 2));
                $PEArr['OptHdrSize'] = $PEArr['OptHdrSize'][1];
                for ($PEArr['k'] = 0; $PEArr['k'] < $NumOfSections; $PEArr['k']++) {
                    $PEArr['SectionArr'][$PEArr['k']] = [];
                    $PEArr['SectionArr'][$PEArr['k']]['SectionHead'] =
                        substr($str, $PEArr['Offset'] + 24 + $PEArr['OptHdrSize'] + ($PEArr['k'] * 40), $NumOfSections * 40);
                    $PEArr['SectionArr'][$PEArr['k']]['SectionName'] =
                        str_ireplace("\x00", '', substr($PEArr['SectionArr'][$PEArr['k']]['SectionHead'], 0, 8));
                    $PEArr['SectionArr'][$PEArr['k']]['VirtualSize'] =
                        $phpMussel['UnpackSafe']('S', substr($PEArr['SectionArr'][$PEArr['k']]['SectionHead'], 8, 4));
                    $PEArr['SectionArr'][$PEArr['k']]['VirtualSize'] =
                        $PEArr['SectionArr'][$PEArr['k']]['VirtualSize'][1];
                    $PEArr['SectionArr'][$PEArr['k']]['VirtualAddress'] =
                        $phpMussel['UnpackSafe']('S', substr($PEArr['SectionArr'][$PEArr['k']]['SectionHead'], 12, 4));
                    $PEArr['SectionArr'][$PEArr['k']]['VirtualAddress'] =
                        $PEArr['SectionArr'][$PEArr['k']]['VirtualAddress'][1];
                    $PEArr['SectionArr'][$PEArr['k']]['SizeOfRawData'] =
                        $phpMussel['UnpackSafe']('S', substr($PEArr['SectionArr'][$PEArr['k']]['SectionHead'], 16, 4));
                    $PEArr['SectionArr'][$PEArr['k']]['SizeOfRawData'] =
                        $PEArr['SectionArr'][$PEArr['k']]['SizeOfRawData'][1];
                    $PEArr['SectionArr'][$PEArr['k']]['PointerToRawData'] =
                        $phpMussel['UnpackSafe']('S', substr($PEArr['SectionArr'][$PEArr['k']]['SectionHead'], 20, 4));
                    $PEArr['SectionArr'][$PEArr['k']]['PointerToRawData'] =
                        $PEArr['SectionArr'][$PEArr['k']]['PointerToRawData'][1];
                    $PEArr['SectionArr'][$PEArr['k']]['SectionData'] = substr(
                        $str,
                        $PEArr['SectionArr'][$PEArr['k']]['PointerToRawData'],
                        $PEArr['SectionArr'][$PEArr['k']]['SizeOfRawData']
                    );
                    $SectionOffsets[$PEArr['k']] = [
                        $PEArr['SectionArr'][$PEArr['k']]['PointerToRawData'],
                        $PEArr['SectionArr'][$PEArr['k']]['SizeOfRawData']
                    ];
                    $PEArr['SectionArr'][$PEArr['k']]['MD5'] = md5(
                        $PEArr['SectionArr'][$PEArr['k']]['SectionData']
                    );
                    $phpMussel['PEData'] .=
                        $PEArr['SectionArr'][$PEArr['k']]['SizeOfRawData'] . ':' .
                        $PEArr['SectionArr'][$PEArr['k']]['MD5'] . ':' . $ofn . '-' .
                        $PEArr['SectionArr'][$PEArr['k']]['SectionName'] . "\n";
                    $CoExMeta .=
                        'SectionName:' . $PEArr['SectionArr'][$PEArr['k']]['SectionName'] .
                        ';VirtualSize:' . $PEArr['SectionArr'][$PEArr['k']]['VirtualSize'] .
                        ';VirtualAddress:' . $PEArr['SectionArr'][$PEArr['k']]['VirtualAddress'] .
                        ';SizeOfRawData:' . $PEArr['SectionArr'][$PEArr['k']]['SizeOfRawData'] .
                        ';MD5:' . $PEArr['SectionArr'][$PEArr['k']]['MD5'] . ';';
                    $PEArr['SectionArr'][$PEArr['k']] =
                        $PEArr['SectionArr'][$PEArr['k']]['SizeOfRawData'] . ':' .
                        $PEArr['SectionArr'][$PEArr['k']]['MD5'] . ':';
                }
                if (strpos($str, "V\x00a\x00r\x00F\x00i\x00l\x00e\x00I\x00n\x00f\x00o\x00\x00\x00\x00\x00\x24") !== false) {
                    $PEArr['Parts'] = $phpMussel['substral']($str, "V\x00a\x00r\x00F\x00i\x00l\x00e\x00I\x00n\x00f\x00o\x00\x00\x00\x00\x00\x24");
                    $PEArr['FINFO'] = [];
                    foreach ([
                        ["F\x00i\x00l\x00e\x00D\x00e\x00s\x00c\x00r\x00i\x00p\x00t\x00i\x00o\x00n\x00\x00\x00", 'PEFileDescription'],
                        ["F\x00i\x00l\x00e\x00V\x00e\x00r\x00s\x00i\x00o\x00n\x00\x00\x00", 'PEFileVersion'],
                        ["P\x00r\x00o\x00d\x00u\x00c\x00t\x00N\x00a\x00m\x00e\x00\x00\x00", 'PEProductName'],
                        ["P\x00r\x00o\x00d\x00u\x00c\x00t\x00V\x00e\x00r\x00s\x00i\x00o\x00n\x00\x00\x00", 'PEProductVersion'],
                        ["L\x00e\x00g\x00a\x00l\x00C\x00o\x00p\x00y\x00r\x00i\x00g\x00h\x00t\x00\x00\x00", 'PECopyright'],
                        ["O\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00F\x00i\x00l\x00e\x00n\x00a\x00m\x00e\x00\x00\x00", 'PEOriginalFilename'],
                        ["C\x00o\x00m\x00p\x00a\x00n\x00y\x00N\x00a\x00m\x00e\x00\x00\x00", 'PECompanyName'],
                    ] as $PEVars) {
                        if (strpos($PEArr['Parts'], $PEVars[0]) !== false && (
                            ${$PEVars[1]} = trim(str_ireplace("\x00", '', $phpMussel['substrbf'](
                                $phpMussel['substral']($PEArr['Parts'], $PEVars[0]),
                                "\x00\x00\x00"
                            )))
                        )) {
                            $PEArr['FINFO'][] = '$' . $PEVars[1] . ':' . md5(${$PEVars[1]}) . ':' . strlen(${$PEVars[1]}) . ':';
                        }
                    }
                    unset($PEVars, $PEArr['Parts']);
                }
            }
        }
    }

    /** Look for potential indicators of not being HTML. */
    $is_not_html = (!$is_html && ($is_macho || $is_elf || $is_pe));

    /** Look for potential indicators of not being PHP. */
    $is_not_php = ((
        strpos(',phar,', ',' . $xt . ',') === false &&
        strpos(',php*,', ',' . $xts . ',') === false &&
        strpos(',phar,', ',' . $gzxt . ',') === false &&
        strpos(',php*,', ',' . $gzxts . ',') === false &&
        strpos($str_hex_norm, '3c3f706870') === false
    ) || $is_pe);

    /** Set debug values, if this has been enabled. */
    if (isset($phpMussel['DebugArr'])) {
        $phpMussel['DebugArrKey'] = count($phpMussel['DebugArr']);
        $phpMussel['DebugArr'][$phpMussel['DebugArrKey']] = [
            'Filename' => $ofn,
            'FromCache' => false,
            'Depth' => $dpt,
            'Size' => $str_len,
            'MD5' => $md5,
            'SHA1' => $sha,
            'SHA256' => $sha256,
            'CRC32B' => $crc,
            '2CC' => $twocc,
            '4CC' => $fourcc,
            'ScanPhase' => $phase,
            'Container' => $container,
            'FileSwitch' => $fileswitch,
            'Is_ELF' => $is_elf,
            'Is_Graphics' => $is_graphics,
            'Is_HTML' => $is_html,
            'Is_Email' => $is_email,
            'Is_MachO' => $is_macho,
            'Is_PDF' => $is_pdf,
            'Is_SWF' => $is_swf,
            'Is_PE' => $is_pe,
            'Is_Not_HTML' => $is_not_html,
            'Is_Not_PHP' => $is_not_php
        ];
        if ($is_pe) {
            $phpMussel['DebugArr'][$phpMussel['DebugArrKey']] += [
                'NumOfSections' => $NumOfSections,
                'PEFileDescription' => $PEFileDescription,
                'PEFileVersion' => $PEFileVersion,
                'PEProductName' => $PEProductName,
                'PEProductVersion' => $PEProductVersion,
                'PECopyright' => $PECopyright,
                'PEOriginalFilename' => $PEOriginalFilename,
                'PECompanyName' => $PECompanyName
            ];
        }
    }

    /** Plugin hook: "during_scan". */
    $phpMussel['Execute_Hook']('during_scan');

    /** Begin URL scanner. */
    if (
        isset($phpMussel['InstanceCache']['URL_Scanner']) ||
        !empty($phpMussel['Config']['urlscanner']['lookup_hphosts']) ||
        !empty($phpMussel['Config']['urlscanner']['google_api_key'])
    ) {
        $phpMussel['LookupCount'] = 0;
        $URLScanner = [
            'FixedSource' => preg_replace('~(data|f(ile|tps?)|https?|sftp):~i', "\x01\\1:", str_replace("\\", '/', $str_norm)) . "\x01",
            'DomainsNoLookup' => [],
            'DomainsCount' => 0,
            'Domains' => [],
            'DomainPartsNoLookup' => [],
            'DomainParts' => [],
            'Queries' => [],
            'URLsNoLookup' => [],
            'URLsCount' => 0,
            'URLs' => [],
            'URLPartsNoLookup' => [],
            'URLParts' => [],
            'TLDs' => [],
            'Iterable' => 0,
            'Matches' => []
        ];
        if (preg_match_all(
            '~(?:data|f(?:ile|tps?)|https?|sftp)://(?:www\d{0,3}\.)?([\da-z.-]{1,512})[^\da-z.-]~i',
            $URLScanner['FixedSource'],
            $URLScanner['Matches']
        )) {
            foreach ($URLScanner['Matches'][1] as $ThisURL) {
                $URLScanner['DomainParts'][$URLScanner['Iterable']] = $ThisURL;
                if (strpos($URLScanner['DomainParts'][$URLScanner['Iterable']], '.') !== false) {
                    $URLScanner['TLDs'][$URLScanner['Iterable']] = 'TLD:' . $phpMussel['substral'](
                        $URLScanner['DomainParts'][$URLScanner['Iterable']],
                        '.'
                    ) . ':';
                }
                $ThisURL = md5($ThisURL) . ':' . strlen($ThisURL) . ':';
                $URLScanner['Domains'][$URLScanner['Iterable']] = 'DOMAIN:' . $ThisURL;
                $URLScanner['DomainsNoLookup'][$URLScanner['Iterable']] = 'DOMAIN-NOLOOKUP:' . $ThisURL;
                $URLScanner['Iterable']++;
            }
        }
        $URLScanner['DomainsNoLookup'] = array_unique($URLScanner['DomainsNoLookup']);
        $URLScanner['Domains'] = array_unique($URLScanner['Domains']);
        $URLScanner['DomainParts'] = array_unique($URLScanner['DomainParts']);
        $URLScanner['TLDs'] = array_unique($URLScanner['TLDs']);
        sort($URLScanner['DomainsNoLookup']);
        sort($URLScanner['Domains']);
        sort($URLScanner['DomainParts']);
        sort($URLScanner['TLDs']);
        $URLScanner['Iterable'] = 0;
        $URLScanner['Matches'] = '';
        if (preg_match_all(
            '~(?:data|f(?:ile|tps?)|https?|sftp)://(?:www\d{0,3}\.)?([!#$&-;=?@-\[\]_a-z\~]+)[^!#$&-;=?@-\[\]_a-z\~]~i',
            $URLScanner['FixedSource'],
            $URLScanner['Matches']
        )) {
            foreach ($URLScanner['Matches'][1] as $ThisURL) {
                if (strlen($ThisURL) > 4096) {
                    $ThisURL = substr($ThisURL, 0, 4096);
                }
                $URLScanner['This'] = md5($ThisURL) . ':' . strlen($ThisURL) . ':';
                $URLScanner['URLsNoLookup'][$URLScanner['Iterable']] = 'URL-NOLOOKUP:' . $URLScanner['This'];
                $URLScanner['URLParts'][$URLScanner['Iterable']] = $ThisURL;
                $URLScanner['URLs'][$URLScanner['Iterable']] = 'URL:' . $URLScanner['This'];
                $URLScanner['Iterable']++;
                if (preg_match('/[^\da-z.-]$/i', $ThisURL)) {
                    $URLScanner['x'] = preg_replace('/[^\da-z.-]+$/i', '', $ThisURL);
                    $URLScanner['This'] = md5($URLScanner['x']) . ':' . strlen($URLScanner['x']) . ':';
                    $URLScanner['URLsNoLookup'][$URLScanner['Iterable']] = 'URL-NOLOOKUP:' . $URLScanner['This'];
                    $URLScanner['URLParts'][$URLScanner['Iterable']] = $URLScanner['x'];
                    $URLScanner['URLs'][$URLScanner['Iterable']] = 'URL:' . $URLScanner['This'];
                    $URLScanner['Iterable']++;
                }
                if (strpos($ThisURL, '?') !== false) {
                    $URLScanner['x'] = $phpMussel['substrbf']($ThisURL, '?');
                    $URLScanner['This'] = md5($URLScanner['x']) . ':' . strlen($URLScanner['x']) . ':';
                    $URLScanner['URLsNoLookup'][$URLScanner['Iterable']] = 'URL-NOLOOKUP:' . $URLScanner['This'];
                    $URLScanner['URLParts'][$URLScanner['Iterable']] = $URLScanner['x'];
                    $URLScanner['URLs'][$URLScanner['Iterable']] = 'URL:' . $URLScanner['This'];
                    $URLScanner['x'] = $phpMussel['substraf']($ThisURL, '?');
                    $URLScanner['Queries'][$URLScanner['Iterable']] = 'QUERY:' . md5($URLScanner['x']) . ':' . strlen($URLScanner['x']) . ':';
                    $URLScanner['Iterable']++;
                }
            }
            unset($URLScanner['x'], $URLScanner['This']);
        }
        unset($ThisURL, $URLScanner['Matches']);
        $URLScanner['URLsNoLookup'] = array_unique($URLScanner['URLsNoLookup']);
        $URLScanner['URLs'] = array_unique($URLScanner['URLs']);
        $URLScanner['URLParts'] = array_unique($URLScanner['URLParts']);
        $URLScanner['Queries'] = array_unique($URLScanner['Queries']);
        sort($URLScanner['URLsNoLookup']);
        sort($URLScanner['URLs']);
        sort($URLScanner['URLParts']);
        sort($URLScanner['Queries']);
    }

    /** Process non-mappable signatures. */
    foreach ([
        ['General_Command_Detections', 0],
        ['Hash', 1],
        ['PE_Sectional', 2],
        ['PE_Extended', 3],
        ['URL_Scanner', 4],
        ['Complex_Extended', 5]
    ] as $ThisConf) {

        /** Plugin hook: "new_sigfile_type". */
        $phpMussel['Execute_Hook']('new_sigfile_type');

        $SigFiles = isset($phpMussel['InstanceCache'][$ThisConf[0]]) ? explode(',', $phpMussel['InstanceCache'][$ThisConf[0]]) : [];
        foreach ($SigFiles as $SigFile) {
            if (!$SigFile) {
                continue;
            }
            if (!isset($phpMussel['InstanceCache'][$SigFile])) {
                $phpMussel['InstanceCache'][$SigFile] = $phpMussel['ReadFile']($phpMussel['sigPath'] . $SigFile);
            }

            /** Plugin hook: "new_sigfile". */
            $phpMussel['Execute_Hook']('new_sigfile');

            if (!$phpMussel['InstanceCache'][$SigFile]) {
                $phpMussel['InstanceCache']['scan_errors']++;
                if (!$phpMussel['Config']['signatures']['fail_silently']) {
                    if (!$flagged) {
                        $phpMussel['killdata'] .= $md5 . ':' . $str_len . ":\n";
                    }
                    $phpMussel['whyflagged'] .= sprintf(
                        $phpMussel['L10N']->getString('_exclamation'),
                        $phpMussel['L10N']->getString('scan_signature_file_missing') . ' (' . $SigFile . ')'
                    );
                    return [-3, $lnap . sprintf(
                        $phpMussel['L10N']->getString('_exclamation_final'),
                        $phpMussel['L10N']->getString('scan_signature_file_missing') . ' (' . $SigFile . ')'
                    ) . "\n"];
                }
            } elseif ($ThisConf[1] === 0) {
                if (substr($phpMussel['InstanceCache'][$SigFile], 0, 9) === 'phpMussel') {
                    $phpMussel['InstanceCache'][$SigFile] = substr($phpMussel['InstanceCache'][$SigFile], 11, -1);
                }
                $ArrayCSV = explode(',', $phpMussel['InstanceCache'][$SigFile]);
                foreach ($ArrayCSV as $ItemCSV) {
                    if (strpos($str_hex_norm, $ItemCSV) !== false) {
                        if (!$flagged) {
                            $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                            $flagged = true;
                        }
                        $heur['detections']++;
                        $phpMussel['InstanceCache']['detections_count']++;
                        $Out .= $lnap . sprintf(
                            $phpMussel['L10N']->getString('_exclamation'),
                            $phpMussel['L10N']->getString('scan_command_injection')
                        ) . "\n";
                        $phpMussel['whyflagged'] .= sprintf(
                            $phpMussel['L10N']->getString('_exclamation'),
                            $phpMussel['L10N']->getString('scan_command_injection') . ', \'' . $phpMussel['HexSafe']($ItemCSV) . '\' (' . $ofnSafe . ')'
                        );
                    }
                }
                unset($ItemCSV, $ArrayCSV);
            } elseif ($ThisConf[1] === 1) {
                foreach ([$md5, $sha, $sha256] as $CheckThisHash) {
                    if (strpos($phpMussel['InstanceCache'][$SigFile], "\n" . $CheckThisHash . ':' . $str_len . ':') !== false) {
                        $xSig = $phpMussel['substraf']($phpMussel['InstanceCache'][$SigFile], "\n" . $CheckThisHash . ':' . $str_len . ':');
                        if (strpos($xSig, "\n") !== false) {
                            $xSig = $phpMussel['substrbf']($xSig, "\n");
                        }
                        $xSig = $phpMussel['vn_shorthand']($xSig);
                        if (
                            strpos($phpMussel['InstanceCache']['greylist'], ',' . $xSig . ',') === false &&
                            empty($phpMussel['InstanceCache']['ignoreme'])
                        ) {
                            $phpMussel['Detected']($heur, $lnap, $xSig, $ofn, $ofnSafe, $Out, $flagged, $md5, $str_len);
                        }
                    }
                }
            } elseif ($ThisConf[1] === 2) {
                for ($PEArr['k'] = 0; $PEArr['k'] < $NumOfSections; $PEArr['k']++) {
                    if (strpos($phpMussel['InstanceCache'][$SigFile], $PEArr['SectionArr'][$PEArr['k']]) !== false) {
                        $xSig = $phpMussel['substraf']($phpMussel['InstanceCache'][$SigFile], $PEArr['SectionArr'][$PEArr['k']]);
                        if (strpos($xSig, "\n") !== false) {
                            $xSig = $phpMussel['substrbf']($xSig, "\n");
                        }
                        $xSig = $phpMussel['vn_shorthand']($xSig);
                        if (
                            strpos($phpMussel['InstanceCache']['greylist'], ',' . $xSig . ',') === false &&
                            empty($phpMussel['InstanceCache']['ignoreme'])
                        ) {
                            $phpMussel['Detected']($heur, $lnap, $xSig, $ofn, $ofnSafe, $Out, $flagged, $md5, $str_len);
                        }
                    }
                }
            } elseif ($ThisConf[1] === 3) {
                if (!empty($PEArr['FINFO'])) {
                    foreach ($PEArr['FINFO'] as $PEArr['ThisPart']) {
                        if (substr_count($phpMussel['InstanceCache'][$SigFile], $PEArr['ThisPart'])) {
                            $xSig = $phpMussel['substraf']($phpMussel['InstanceCache'][$SigFile], $PEArr['ThisPart']);
                            if (strpos($xSig, "\n") !== false) {
                                $xSig = $phpMussel['substrbf']($xSig, "\n");
                            }
                            $xSig = $phpMussel['vn_shorthand']($xSig);
                            if (
                                !substr_count($phpMussel['InstanceCache']['greylist'], ',' . $xSig . ',') &&
                                empty($phpMussel['InstanceCache']['ignoreme'])
                            ) {
                                $phpMussel['Detected']($heur, $lnap, $xSig, $ofn, $ofnSafe, $Out, $flagged, $md5, $str_len);
                            }
                        }
                    }
                }
            } elseif ($ThisConf[1] === 4) {
                foreach ([$URLScanner['DomainsNoLookup'], $URLScanner['URLsNoLookup']] as $URLScanner['ThisArr']) {
                    foreach ($URLScanner['ThisArr'] as $URLScanner['This']) {
                        if (strpos($phpMussel['InstanceCache'][$SigFile], $URLScanner['This']) !== false) {
                            $xSig = $phpMussel['substraf']($phpMussel['InstanceCache'][$SigFile], $URLScanner['This']);
                            if (strpos($xSig, "\n") !== false) {
                                $xSig = $phpMussel['substrbf']($xSig, "\n");
                            }
                            if (substr($URLScanner['This'], 0, 15) === 'DOMAIN-NOLOOKUP') {
                                $URLScanner['DomainPartsNoLookup'][$xSig] = true;
                                continue;
                            }
                            $URLScanner['URLPartsNoLookup'][$xSig] = true;
                        }
                    }
                }
                foreach ([
                    $URLScanner['TLDs'],
                    $URLScanner['Domains'],
                    $URLScanner['URLs'],
                    $URLScanner['Queries']
                ] as $URLScanner['ThisArr']) {
                    foreach ($URLScanner['ThisArr'] as $URLScanner['This']) {
                        if (substr_count($phpMussel['InstanceCache'][$SigFile], $URLScanner['This'])) {
                            $xSig = $phpMussel['substraf']($phpMussel['InstanceCache'][$SigFile], $URLScanner['This']);
                            if (strpos($xSig, "\n") !== false) {
                                $xSig = $phpMussel['substrbf']($xSig, "\n");
                            }
                            if (
                                ($xSig = $phpMussel['vn_shorthand']($xSig)) &&
                                !substr_count($phpMussel['InstanceCache']['greylist'], ',' . $xSig . ',') &&
                                empty($phpMussel['InstanceCache']['ignoreme'])
                            ) {
                                $phpMussel['Detected']($heur, $lnap, $xSig, $ofn, $ofnSafe, $Out, $flagged, $md5, $str_len);
                            }
                        }
                    }
                }
            } elseif ($ThisConf[1] === 5) {
                $SigName = '';
                foreach ([
                    'NumOfSections',
                    'PECompanyName',
                    'PECopyright',
                    'PEFileDescription',
                    'PEFileVersion',
                    'PEOriginalFilename',
                    'PEProductName',
                    'PEProductVersion',
                    'container',
                    'crc',
                    'fileswitch',
                    'fourcc',
                    'is_elf',
                    'is_email',
                    'is_graphics',
                    'is_html',
                    'is_macho',
                    'is_not_html',
                    'is_not_php',
                    'is_ole',
                    'is_pdf',
                    'is_pe',
                    'is_swf',
                    'md5',
                    'phase',
                    'sha',
                    'sha256',
                    'str_len',
                    'twocc',
                    'xt',
                    'xts'
                ] as $ThisCheckFor) {
                    if (!isset($$ThisCheckFor)) {
                        continue;
                    }
                    $ThisCheckValue = "\n$" . $ThisCheckFor . ':' . (
                        substr($ThisCheckFor, 0, 3) !== 'is_' ? $$ThisCheckFor : ($$ThisCheckFor ? '1' : '0')
                    ) . ';';
                    if (strpos($phpMussel['InstanceCache'][$SigFile], $ThisCheckValue) === false) {
                        continue;
                    }
                    $xSig = explode($ThisCheckValue, $phpMussel['InstanceCache'][$SigFile]);
                    $xSigCount = count($xSig);
                    if (isset($xSig[0])) {
                        $xSig[0] = '';
                    }
                    if ($xSigCount > 0) {
                        for ($xIter = 1; $xIter < $xSigCount; $xIter++) {
                            if (strpos($xSig[$xIter], "\n") !== false) {
                                $xSig[$xIter] = $phpMussel['substrbf']($xSig[$xIter], "\n");
                            }
                            if (strpos($xSig[$xIter], ';') !== false) {
                                if (strpos($xSig[$xIter], ':') === false) {
                                    continue;
                                }
                                $SigName = $phpMussel['vn_shorthand']($phpMussel['substral']($xSig[$xIter], ';'));
                                $xSig[$xIter] = explode(';', $phpMussel['substrbl']($xSig[$xIter], ';'));
                            } else {
                                $SigName = $phpMussel['vn_shorthand']($xSig[$xIter]);
                                $xSig[$xIter] = [];
                            }
                            foreach ($xSig[$xIter] as $ThisSigPart) {
                                if (empty($ThisSigPart)) {
                                    continue 2;
                                }
                                $ThisSigPart = $phpMussel['SplitSigParts']($ThisSigPart, 7);
                                if ($ThisSigPart[0] === 'LV') {
                                    if (!isset($ThisSigPart[1]) || substr($ThisSigPart[1], 0, 1) !== '$') {
                                        continue 2;
                                    }
                                    $lv_haystack = substr($ThisSigPart[1], 1);
                                    if (!isset($$lv_haystack) || is_array($$lv_haystack)) {
                                        continue 2;
                                    }
                                    $lv_haystack = $$lv_haystack;
                                    if ($climode) {
                                        $lv_haystack = $phpMussel['substral']($phpMussel['substral']($lv_haystack, '/'), "\\");
                                    }
                                    $lv_needle = (isset($ThisSigPart[2])) ? $ThisSigPart[2] : '';
                                    $pos_A = (isset($ThisSigPart[3])) ? $ThisSigPart[3] : 0;
                                    $pos_Z = (isset($ThisSigPart[4])) ? $ThisSigPart[4] : 0;
                                    $lv_min = (isset($ThisSigPart[5])) ? $ThisSigPart[5] : 0;
                                    $lv_max = (isset($ThisSigPart[6])) ? $ThisSigPart[6] : -1;
                                    if (!$phpMussel['lv_match']($lv_needle, $lv_haystack, $pos_A, $pos_Z, $lv_min, $lv_max)) {
                                        continue 2;
                                    }
                                    continue;
                                }
                                if (isset($ThisSigPart[2])) {
                                    if (isset($ThisSigPart[3])) {
                                        if ($ThisSigPart[2] == 'A') {
                                            if (strpos(',FD,FD-RX,FD-NORM,FD-NORM-RX,META,', ',' . $ThisSigPart[0] . ',') === false || (
                                                $ThisSigPart[0] == 'FD' &&
                                                strpos("\x01" . substr($str_hex, 0, $ThisSigPart[3] * 2), "\x01" . $ThisSigPart[1]) === false
                                            ) || (
                                                $ThisSigPart[0] == 'FD-RX' &&
                                                !preg_match('/\A(?:' . $ThisSigPart[1] . ')/i', substr($str_hex, 0, $ThisSigPart[3] * 2))
                                            ) || (
                                                $ThisSigPart[0] == 'FD-NORM' &&
                                                strpos("\x01" . substr($str_hex_norm, 0, $ThisSigPart[3] * 2), "\x01" . $ThisSigPart[1]) === false
                                            ) || (
                                                $ThisSigPart[0] == 'FD-NORM-RX' &&
                                                !preg_match('/\A(?:' . $ThisSigPart[1] . ')/i', substr($str_hex_norm, 0, $ThisSigPart[3] * 2))
                                            ) || (
                                                $ThisSigPart[0] == 'META' &&
                                                !preg_match('/\A(?:' . $ThisSigPart[1] . ')/i', substr($CoExMeta, 0, $ThisSigPart[3] * 2))
                                            )) {
                                                continue 2;
                                            }
                                            continue;
                                        }
                                        if (strpos(',FD,FD-RX,FD-NORM,FD-NORM-RX,META,', ',' . $ThisSigPart[0] . ',') === false || (
                                            $ThisSigPart[0] == 'FD' &&
                                            strpos(substr($str_hex, $ThisSigPart[2] * 2, $ThisSigPart[3] * 2), $ThisSigPart[1]) === false
                                        ) || (
                                            $ThisSigPart[0] == 'FD-RX' &&
                                            !preg_match('/(?:' . $ThisSigPart[1] . ')/i', substr($str_hex, $ThisSigPart[2] * 2, $ThisSigPart[3] * 2))
                                        ) || (
                                            $ThisSigPart[0] == 'FD-NORM' &&
                                            strpos(substr($str_hex_norm, $ThisSigPart[2] * 2, $ThisSigPart[3] * 2), $ThisSigPart[1]) === false
                                        ) || (
                                            $ThisSigPart[0] == 'FD-NORM-RX' &&
                                            !preg_match('/(?:' . $ThisSigPart[1] . ')/i', substr($str_hex_norm, $ThisSigPart[2] * 2, $ThisSigPart[3] * 2))
                                        ) || (
                                            $ThisSigPart[0] == 'META' &&
                                            !preg_match('/(?:' . $ThisSigPart[1] . ')/i', substr($CoExMeta, $ThisSigPart[2] * 2, $ThisSigPart[3] * 2))
                                        )) {
                                            continue 2;
                                        }
                                        continue;
                                    }
                                    if ($ThisSigPart[2] == 'A') {
                                        if (strpos(',FN,FD,FD-RX,FD-NORM,FD-NORM-RX,META,', ',' . $ThisSigPart[0] . ',') === false || (
                                            $ThisSigPart[0] == 'FN' &&
                                            !preg_match('/\A(?:' . $ThisSigPart[1] . ')/i', $ofn)
                                        ) || (
                                            $ThisSigPart[0] == 'FD' &&
                                            strpos("\x01" . $str_hex, "\x01" . $ThisSigPart[1]) === false
                                        ) || (
                                            $ThisSigPart[0] == 'FD-RX' &&
                                            !preg_match('/\A(?:' . $ThisSigPart[1] . ')/i', $str_hex)
                                        ) || (
                                            $ThisSigPart[0] == 'FD-NORM' &&
                                            strpos("\x01" . $str_hex_norm, "\x01" . $ThisSigPart[1]) === false
                                        ) || (
                                            $ThisSigPart[0] == 'FD-NORM-RX' &&
                                            !preg_match('/\A(?:' . $ThisSigPart[1] . ')/i', $str_hex_norm)
                                        ) || (
                                            $ThisSigPart[0] == 'META' &&
                                            !preg_match('/\A(?:' . $ThisSigPart[1] . ')/i', $CoExMeta)
                                        )) {
                                            continue 2;
                                        }
                                        continue;
                                    }
                                    if (strpos(',FD,FD-RX,FD-NORM,FD-NORM-RX,META,', ',' . $ThisSigPart[0] . ',') === false || (
                                        $ThisSigPart[0] == 'FD' &&
                                        strpos(substr($str_hex, $ThisSigPart[2] * 2), $ThisSigPart[1]) === false
                                    ) || (
                                        $ThisSigPart[0] == 'FD-RX' &&
                                        !preg_match('/(?:' . $ThisSigPart[1] . ')/i', substr($str_hex, $ThisSigPart[2] * 2))
                                    ) || (
                                        $ThisSigPart[0] == 'FD-NORM' &&
                                        strpos(substr($str_hex_norm, $ThisSigPart[2] * 2), $ThisSigPart[1]) === false
                                    ) || (
                                        $ThisSigPart[0] == 'FD-NORM-RX' &&
                                        !preg_match('/(?:' . $ThisSigPart[1] . ')/i', substr($str_hex_norm, $ThisSigPart[2] * 2))
                                    ) || (
                                        $ThisSigPart[0] == 'META' &&
                                        !preg_match('/(?:' . $ThisSigPart[1] . ')/i', substr($CoExMeta, $ThisSigPart[2] * 2))
                                    )) {
                                        continue 2;
                                    }
                                    continue;
                                }
                                if ((
                                    $ThisSigPart[0] == 'FN' &&
                                    !preg_match('/(?:' . $ThisSigPart[1] . ')/i', $ofn)
                                ) || (
                                    $ThisSigPart[0] == 'FS-MIN' &&
                                    $str_len < $ThisSigPart[1]
                                ) || (
                                    $ThisSigPart[0] == 'FS-MAX' &&
                                    $str_len > $ThisSigPart[1]
                                ) || (
                                    $ThisSigPart[0] == 'FD' &&
                                    strpos($str_hex, $ThisSigPart[1]) === false
                                ) || (
                                    $ThisSigPart[0] == 'FD-RX' &&
                                    !preg_match('/(?:' . $ThisSigPart[1] . ')/i', $str_hex)
                                ) || (
                                    $ThisSigPart[0] == 'FD-NORM' &&
                                    strpos($str_hex_norm, $ThisSigPart[1]) === false
                                ) || (
                                    $ThisSigPart[0] == 'FD-NORM-RX' &&
                                    !preg_match('/(?:' . $ThisSigPart[1] . ')/i', $str_hex_norm)
                                ) || (
                                    $ThisSigPart[0] == 'META' &&
                                    !preg_match('/(?:' . $ThisSigPart[1] . ')/i', $CoExMeta)
                                )) {
                                    continue 2;
                                }
                                if (substr($ThisSigPart[0], 0, 1) === '$') {
                                    $vf = substr($ThisSigPart[0], 1);
                                    if (!isset($$vf) || is_array($$vf) || $$vf != $ThisSigPart[1]) {
                                        continue 2;
                                    }
                                    continue;
                                }
                                if (substr($ThisSigPart[0], 0, 2) === '!$') {
                                    $vf = substr($ThisSigPart[0], 2);
                                    if (!isset($$vf) || is_array($$vf) || $$vf == $ThisSigPart[1]) {
                                        continue 2;
                                    }
                                    continue;
                                }
                                if (strpos(',FN,FS-MIN,FS-MAX,FD,FD-RX,FD-NORM,FD-NORM-RX,META,', ',' . $ThisSigPart[0] . ',') === false) {
                                    continue 2;
                                }
                            }
                            if (
                                $SigName &&
                                strpos($phpMussel['InstanceCache']['greylist'], ',' . $SigName . ',') === false &&
                                empty($phpMussel['InstanceCache']['ignoreme'])
                            ) {
                                $phpMussel['Detected']($heur, $lnap, $SigName, $ofn, $ofnSafe, $Out, $flagged, $md5, $str_len);
                            }
                        }
                    }
                }

                /** Cleanup. */
                unset($SigName, $xIter, $xSigCount, $xSig, $ThisSigPart, $ThisCheckValue, $ThisCheckFor);
            }
        }
    }

    /** Process mappable signatures. */
    foreach ([
        ['Filename', 'str_hex', 'str_hex_len', 2],
        ['Standard', 'str_hex', 'str_hex_len', 0],
        ['Normalised', 'str_hex_norm', 'str_hex_norm_len', 0],
        ['HTML', 'str_hex_html', 'str_hex_html_len', 0],
        ['Standard_RegEx', 'str_hex', 'str_hex_len', 1],
        ['Normalised_RegEx', 'str_hex_norm', 'str_hex_norm_len', 1],
        ['HTML_RegEx', 'str_hex_html', 'str_hex_html_len', 1]
    ] as $ThisConf) {
        $DataSource = $ThisConf[1];
        $DataSourceLen = $ThisConf[2];

        /** Plugin hook: "new_sigfile_type". */
        $phpMussel['Execute_Hook']('new_sigfile_type');

        $SigFiles = isset($phpMussel['InstanceCache'][$ThisConf[0]]) ? explode(',', $phpMussel['InstanceCache'][$ThisConf[0]]) : [];
        foreach ($SigFiles as $SigFile) {
            if (!$SigFile) {
                continue;
            }
            if (!isset($phpMussel['InstanceCache'][$SigFile])) {
                $phpMussel['InstanceCache'][$SigFile] = $phpMussel['ReadFileAsArray']($phpMussel['sigPath'] . $SigFile, FILE_IGNORE_NEW_LINES);
            }

            /** Plugin hook: "new_sigfile". */
            $phpMussel['Execute_Hook']('new_sigfile');

            if (!$phpMussel['InstanceCache'][$SigFile]) {
                $phpMussel['InstanceCache']['scan_errors']++;
                if (!$phpMussel['Config']['signatures']['fail_silently']) {
                    if (!$flagged) {
                        $phpMussel['killdata'] .= $md5 . ':' . $str_len . ":\n";
                    }
                    $phpMussel['whyflagged'] .= sprintf(
                        $phpMussel['L10N']->getString('_exclamation'),
                        $phpMussel['L10N']->getString('scan_signature_file_missing') . ' (' . $SigFile . ')'
                    );
                    return [-3, $lnap . sprintf(
                        $phpMussel['L10N']->getString('_exclamation_final'),
                        $phpMussel['L10N']->getString('scan_signature_file_missing') . ' (' . $SigFile . ')'
                    ) . "\n"];
                }
                continue;
            }
            $NumSigs = count($phpMussel['InstanceCache'][$SigFile]);
            for ($SigNum = 0; $SigNum < $NumSigs; $SigNum++) {
                if (!$ThisSig = $phpMussel['InstanceCache'][$SigFile][$SigNum]) {
                    continue;
                }
                if (substr($ThisSig, 0, 1) == '>') {
                    $ThisSig = explode('>', $ThisSig, 4);
                    if (!isset($ThisSig[1], $ThisSig[2], $ThisSig[3])) {
                        break;
                    }
                    $ThisSig[3] = (int)$ThisSig[3];
                    if ($ThisSig[1] == 'FN') {
                        if (!preg_match('/(?:' . $ThisSig[2] . ')/i', $ofn)) {
                            if ($ThisSig[3] <= $SigNum) {
                                break;
                            }
                            $SigNum = $ThisSig[3] - 1;
                        }
                    } elseif ($ThisSig[1] == 'FS-MIN') {
                        if ($str_len < $ThisSig[2]) {
                            if ($ThisSig[3] <= $SigNum) {
                                break;
                            }
                            $SigNum = $ThisSig[3] - 1;
                        }
                    } elseif ($ThisSig[1] == 'FS-MAX') {
                        if ($str_len > $ThisSig[2]) {
                            if ($ThisSig[3] <= $SigNum) {
                                break;
                            }
                            $SigNum = $ThisSig[3] - 1;
                        }
                    } elseif ($ThisSig[1] == 'FD') {
                        if (strpos($$DataSource, $ThisSig[2]) === false) {
                            if ($ThisSig[3] <= $SigNum) {
                                break;
                            }
                            $SigNum = $ThisSig[3] - 1;
                        }
                    } elseif ($ThisSig[1] == 'FD-RX') {
                        if (!preg_match('/(?:' . $ThisSig[2] . ')/i', $$DataSource)) {
                            if ($ThisSig[3] <= $SigNum) {
                                break;
                            }
                            $SigNum = $ThisSig[3] - 1;
                        }
                    } elseif (substr($ThisSig[1], 0, 1) == '$') {
                        $vf = substr($ThisSig[1], 1);
                        if (isset($$vf) && !is_array($$vf)) {
                            if ($$vf != $ThisSig[2]) {
                                if ($ThisSig[3] <= $SigNum) {
                                    break;
                                }
                                $SigNum = $ThisSig[3] - 1;
                            }
                            continue;
                        }
                        if ($ThisSig[3] <= $SigNum) {
                            break;
                        }
                        $SigNum = $ThisSig[3] - 1;
                    } elseif (substr($ThisSig[1], 0, 2) == '!$') {
                        $vf = substr($ThisSig[1], 2);
                        if (isset($$vf) && !is_array($$vf)) {
                            if ($$vf == $ThisSig[2]) {
                                if ($ThisSig[3] <= $SigNum) {
                                    break;
                                }
                                $SigNum = $ThisSig[3] - 1;
                            }
                            continue;
                        }
                        if ($ThisSig[3] <= $SigNum) {
                            break;
                        }
                        $SigNum = $ThisSig[3] - 1;
                    } else {
                        break;
                    }
                    continue;
                }
                if (strpos($ThisSig, ':') !== false) {
                    $VN = $phpMussel['SplitSigParts']($ThisSig);
                    if (!isset($VN[1]) || !strlen($VN[1])) {
                        continue;
                    }
                    if ($ThisConf[3] === 2) {
                        $ThisSig = preg_split('/[\x00-\x1f]+/', $VN[1], -1, PREG_SPLIT_NO_EMPTY);
                        $ThisSig = ($ThisSig === false) ? '' : implode('', $ThisSig);
                        $VN = $phpMussel['vn_shorthand']($VN[0]);
                        if (
                            $ThisSig &&
                            strpos($phpMussel['InstanceCache']['greylist'], ',' . $VN . ',') === false &&
                            empty($phpMussel['InstanceCache']['ignoreme'])
                        ) {
                            if (preg_match('/(?:' . $ThisSig . ')/i', $ofn)) {
                                $phpMussel['Detected']($heur, $lnap, $VN, $ofn, $ofnSafe, $Out, $flagged, $md5, $str_len);
                            }
                        }
                    } elseif ($ThisConf[3] === 0 || $ThisConf[3] === 1) {
                        $ThisSig = preg_split((
                            $ThisConf[3] === 0 ? '/[^\da-f>]+/i' : '/[\x00-\x1f]+/'
                        ), $VN[1], -1, PREG_SPLIT_NO_EMPTY);
                        $ThisSig = ($ThisSig === false ? '' : implode('', $ThisSig));
                        $ThisSigLen = strlen($ThisSig);
                        if ($phpMussel['ConfineLength']($ThisSigLen)) {
                            continue;
                        }
                        $xstrf = isset($VN[2]) ? $VN[2] : '*';
                        $xstrt = isset($VN[3]) ? $VN[3] : '*';
                        $VN = $phpMussel['vn_shorthand']($VN[0]);
                        $VNLC = strtolower($VN);
                        if (($is_not_php && (
                                strpos($VNLC, '-php') !== false || strpos($VNLC, '.php') !== false
                        )) || ($is_not_html && (
                                strpos($VNLC, '-htm') !== false || strpos($VNLC, '.htm') !== false
                        )) || $$DataSourceLen < $ThisSigLen) {
                            continue;
                        }
                        if (
                            strpos($phpMussel['InstanceCache']['greylist'], ',' . $VN . ',') === false &&
                            empty($phpMussel['InstanceCache']['ignoreme'])
                        ) {
                            if ($ThisConf[3] === 0) {
                                $ThisSig = strpos($ThisSig, '>') !== false ? explode('>', $ThisSig) : [$ThisSig];
                                $ThisSigCount = count($ThisSig);
                                $ThisString = $$DataSource;
                                $phpMussel['DataConfineByOffsets']($ThisString, $xstrf, $xstrt, $SectionOffsets);
                                if ($xstrf === 'A') {
                                    $ThisString = "\x01" . $ThisString;
                                    $ThisSig[0] = "\x01" . $ThisSig[0];
                                }
                                if ($xstrt === 'Z') {
                                    $ThisString .= "\x01";
                                    $ThisSig[$ThisSigCount - 1] .= "\x01";
                                }
                                for ($ThisSigi = 0; $ThisSigi < $ThisSigCount; $ThisSigi++) {
                                    if (strpos($ThisString, $ThisSig[$ThisSigi]) === false) {
                                        continue 2;
                                    }
                                    if ($ThisSigCount > 1 && strpos($ThisString, $ThisSig[$ThisSigi]) !== false) {
                                        $ThisString = $phpMussel['substraf']($ThisString, $ThisSig[$ThisSigi]);
                                    }
                                }
                            } else {
                                $ThisString = $$DataSource;
                                $phpMussel['DataConfineByOffsets']($ThisString, $xstrf, $xstrt, $SectionOffsets);
                                if ($xstrf === 'A') {
                                    if ($xstrt === 'Z') {
                                        if (!preg_match('/\A(?:' . $ThisSig . ')$/i', $ThisString)) {
                                            continue;
                                        }
                                    } elseif (!preg_match('/\A(?:' . $ThisSig . ')/i', $ThisString)) {
                                        continue;
                                    }
                                } else {
                                    if ($xstrt === 'Z') {
                                        if (!preg_match('/(?:' . $ThisSig . ')$/i', $ThisString)) {
                                            continue;
                                        }
                                    } elseif (!preg_match('/(?:' . $ThisSig . ')/i', $ThisString)) {
                                        continue;
                                    }
                                }
                            }
                            $phpMussel['Detected']($heur, $lnap, $VN, $ofn, $ofnSafe, $Out, $flagged, $md5, $str_len);
                        }
                    }
                }
            }
        }
    }

    /** Plugin hook: "before_domains_api_lookup". */
    $phpMussel['Execute_Hook']('before_domains_api_lookup');

    /** Perform API lookups for domains. */
    if (isset($URLScanner) && !$Out) {

        $URLScanner['DomainsCount'] = count($URLScanner['DomainParts']);

        /** Codeblock for performing hpHosts API lookups. */
        if ($phpMussel['Config']['urlscanner']['lookup_hphosts'] && $URLScanner['DomainsCount']) {
            /** Fetch the cache entry for hpHosts, if it doesn't already exist. */
            if (!isset($phpMussel['InstanceCache']['urlscanner_domains'])) {
                $phpMussel['InstanceCache']['urlscanner_domains'] = $phpMussel['FetchCache']('urlscanner_domains');
            }
            $URLScanner['y'] = $phpMussel['Time'] + $phpMussel['Config']['urlscanner']['cache_time'];
            $URLScanner['ScriptIdentEncoded'] = urlencode($phpMussel['ScriptIdent']);
            $URLScanner['classes'] = [
                'EMD' => "\x1a\x82\x10\x1bXXX",
                'EXP' => "\x1a\x82\x10\x16XXX",
                'GRM' => "\x1a\x82\x10\x32XXX",
                'HFS' => "\x1a\x82\x10\x32XXX",
                'PHA' => "\x1a\x82\x10\x32XXX",
                'PSH' => "\x1a\x82\x10\x31XXX"
            ];
            for ($i = 0; $i < $URLScanner['DomainsCount']; $i++) {
                if (!empty($URLScanner['DomainPartsNoLookup'][$URLScanner['DomainParts'][$i]])) {
                    continue;
                }
                if (
                    $phpMussel['Config']['urlscanner']['maximum_api_lookups'] > 0 &&
                    $phpMussel['LookupCount'] > $phpMussel['Config']['urlscanner']['maximum_api_lookups']
                ) {
                    if ($phpMussel['Config']['urlscanner']['maximum_api_lookups_response']) {
                        if (!$flagged) {
                            $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                            $flagged = true;
                        }
                        $Out .= $lnap . sprintf(
                            $phpMussel['L10N']->getString('_exclamation_final'),
                            $phpMussel['L10N']->getString('too_many_urls')
                        ) . "\n";
                        $phpMussel['whyflagged'] .= sprintf(
                            $phpMussel['L10N']->getString('_exclamation'),
                            $phpMussel['L10N']->getString('too_many_urls') . ' (' . $ofnSafe . ')'
                        );
                    }
                    break;
                }
                $URLScanner['This'] = md5($URLScanner['DomainParts'][$i]) . ':' . strlen($URLScanner['DomainParts'][$i]) . ':';
                while (substr_count($phpMussel['InstanceCache']['urlscanner_domains'], $URLScanner['This'])) {
                    $URLScanner['Class'] =
                        $phpMussel['substrbf']($phpMussel['substral']($phpMussel['InstanceCache']['urlscanner_domains'], $URLScanner['This']), ';');
                    if (!substr_count($phpMussel['InstanceCache']['urlscanner_domains'], $URLScanner['This'] . ':' . $URLScanner['Class'] . ';')) {
                        break;
                    }
                    $URLScanner['Expiry'] = (int)$phpMussel['substrbf']($URLScanner['Class'], ':');
                    if ($URLScanner['Expiry'] > $phpMussel['Time']) {
                        $URLScanner['Class'] = $phpMussel['substraf']($URLScanner['Class'], ':');
                        if (!$URLScanner['Class']) {
                            continue 2;
                        }
                        $URLScanner['Class'] = $phpMussel['vn_shorthand']($URLScanner['Class']);
                        $phpMussel['Detected']($heur, $lnap, $URLScanner['Class'], $ofn, $ofnSafe, $Out, $flagged, $md5, $str_len);
                    }
                    $phpMussel['InstanceCache']['urlscanner_domains'] =
                        str_ireplace($URLScanner['This'] . $URLScanner['Class'] . ';', '', $phpMussel['InstanceCache']['urlscanner_domains']);
                }
                $URLScanner['req'] =
                    'v=' . $URLScanner['ScriptIdentEncoded'] .
                    '&s=' . $URLScanner['DomainParts'][$i] .
                    '&class=true';
                $URLScanner['req_result'] = $phpMussel['Request'](
                    'http://verify.hosts-file.net/?' . $URLScanner['req'],
                    ['v' => $URLScanner['ScriptIdentEncoded'], 's' => $URLScanner['DomainParts'][$i], 'Class' => true],
                    12
                );
                $phpMussel['LookupCount']++;
                if (substr($URLScanner['req_result'], 0, 6) == "Listed") {
                    $URLScanner['Class'] = substr($URLScanner['req_result'], 7, 3);
                    $URLScanner['Class'] = isset($URLScanner['classes'][$URLScanner['Class']]) ?
                        $URLScanner['classes'][$URLScanner['Class']] : "\x1a\x82\x10\x3fXXX";
                    $phpMussel['InstanceCache']['urlscanner_domains'] .=
                        $URLScanner['This'] .
                        $URLScanner['y'] . ':' .
                        $URLScanner['Class'] . ';';
                    $URLScanner['Class'] = $phpMussel['vn_shorthand']($URLScanner['Class']);
                    $phpMussel['Detected']($heur, $lnap, $URLScanner['Class'], $ofn, $ofnSafe, $Out, $flagged, $md5, $str_len);
                }
                $phpMussel['InstanceCache']['urlscanner_domains'] .= $URLScanner['Domains'][$i] . $URLScanner['y'] . ':;';
            }
            $phpMussel['SaveCache']('urlscanner_domains', $URLScanner['y'], $phpMussel['InstanceCache']['urlscanner_domains']);
        }

        $URLScanner['URLsCount'] = count($URLScanner['URLParts']);

        /** Codeblock for performing Google Safe Browsing API lookups. */
        if ($phpMussel['Config']['urlscanner']['google_api_key'] && $URLScanner['URLsCount']) {
            $URLScanner['URLsChunked'] = (
                $URLScanner['URLsCount'] > 500
            ) ? array_chunk($URLScanner['URLParts'], 500) : [$URLScanner['URLParts']];
            $URLScanner['URLChunks'] = count($URLScanner['URLsChunked']);
            for ($i = 0; $i < $URLScanner['URLChunks']; $i++) {
                if (
                    $phpMussel['Config']['urlscanner']['maximum_api_lookups'] > 0 &&
                    $phpMussel['LookupCount'] > $phpMussel['Config']['urlscanner']['maximum_api_lookups']
                ) {
                    if ($phpMussel['Config']['urlscanner']['maximum_api_lookups_response']) {
                        if (!$flagged) {
                            $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                            $flagged = true;
                        }
                        $Out .= $lnap . sprintf(
                            $phpMussel['L10N']->getString('_exclamation_final'),
                            $phpMussel['L10N']->getString('too_many_urls')
                        ) . "\n";
                        $phpMussel['whyflagged'] .= sprintf(
                            $phpMussel['L10N']->getString('_exclamation'),
                            $phpMussel['L10N']->getString('too_many_urls') . ' (' . $ofnSafe . ')'
                        );
                    }
                    break;
                }
                try {
                    $URLScanner['SafeBrowseLookup'] = $phpMussel['SafeBrowseLookup'](
                        $URLScanner['URLsChunked'][$i],
                        $URLScanner['URLPartsNoLookup'],
                        $URLScanner['DomainPartsNoLookup']
                    );
                } catch (\Exception $e) {
                    throw new \Exception($e->getMessage());
                }
                if ($URLScanner['SafeBrowseLookup'] !== 204) {
                    if (!$flagged) {
                        $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                        $flagged = true;
                    }
                    $URLScanner['L10N'] = $phpMussel['L10N']->getString(
                        'SafeBrowseLookup_' . $URLScanner['SafeBrowseLookup']
                    ) ?: $phpMussel['L10N']->getString('SafeBrowseLookup_999');
                    $Out .= $lnap . sprintf(
                        $phpMussel['L10N']->getString('_exclamation_final'),
                        $URLScanner['L10N']
                    ) . "\n";
                    $phpMussel['whyflagged'] .= sprintf(
                        $phpMussel['L10N']->getString('_exclamation'),
                        $URLScanner['L10N'] . ' (' . $ofnSafe . ')'
                    );
                }
            }
        }

    }

    /** URL scanner data cleanup. */
    unset($URLScanner);

    /** Plugin hook: "before_chameleon_detections". */
    $phpMussel['Execute_Hook']('before_chameleon_detections');

    /** PHP chameleon attack detection. */
    if ($phpMussel['Config']['attack_specific']['chameleon_from_php']) {
        if ($phpMussel['ContainsMustAssert']([
            $phpMussel['Config']['attack_specific']['can_contain_php_file_extensions'],
            $phpMussel['Config']['attack_specific']['archive_file_extensions']
        ], [$xts, $gzxts, $xt, $gzxt]) && strpos($str_hex_norm, '3c3f706870') !== false) {
            if (!$flagged) {
                $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                $flagged = true;
            }
            $heur['detections']++;
            $phpMussel['InstanceCache']['detections_count']++;
            $Out .= $lnap . sprintf(
                $phpMussel['L10N']->getString('_exclamation_final'),
                sprintf($phpMussel['L10N']->getString('scan_chameleon'), 'PHP')
            ) . "\n";
            $phpMussel['whyflagged'] .= sprintf(
                $phpMussel['L10N']->getString('_exclamation'),
                sprintf($phpMussel['L10N']->getString('scan_chameleon'), 'PHP') . ' (' . $ofnSafe . ')'
            );
        }
    }

    /** Executable chameleon attack detection. */
    if ($phpMussel['Config']['attack_specific']['chameleon_from_exe']) {
        $Chameleon = '';
        if (strpos(',acm,ax,com,cpl,dll,drv,exe,ocx,rs,scr,sys,', ',' . $xt . ',') !== false) {
            if ($twocc !== '4d5a') {
                $Chameleon = 'EXE';
            }
        } elseif ($twocc === '4d5a') {
            $Chameleon = 'EXE';
        }
        if ($xt === 'elf') {
            if ($fourcc !== '7f454c46') {
                $Chameleon = 'ELF';
            }
        } elseif ($fourcc === '7f454c46') {
            $Chameleon = 'ELF';
        }
        if ($xt === 'lnk') {
            if (substr($str_hex, 0, 16) !== '4c00000001140200') {
                $Chameleon = 'LNK';
            }
        } elseif (substr($str_hex, 0, 16) === '4c00000001140200') {
            $Chameleon = 'LNK';
        }
        if ($xt === 'msi' && substr($str_hex, 0, 16) !== 'd0cf11e0a1b11ae1') {
            $Chameleon = 'MSI';
        }
        if ($Chameleon) {
            if (!$flagged) {
                $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                $flagged = true;
            }
            $heur['detections']++;
            $phpMussel['InstanceCache']['detections_count']++;
            $Out .= $lnap . sprintf(
                $phpMussel['L10N']->getString('_exclamation_final'),
                sprintf($phpMussel['L10N']->getString('scan_chameleon'), $Chameleon)
            ) . "\n";
            $phpMussel['whyflagged'] .= sprintf(
                $phpMussel['L10N']->getString('_exclamation'),
                sprintf($phpMussel['L10N']->getString('scan_chameleon'), $Chameleon) . ' (' . $ofnSafe . ')'
            );
        }
    }

    /** Archive chameleon attack detection. */
    if ($phpMussel['Config']['attack_specific']['chameleon_to_archive']) {
        $Chameleon = '';
        if ($xts === 'zip*' && $twocc !== '504b') {
            $Chameleon = 'Zip';
        } elseif ($xt === 'rar' && ($fourcc !== '52617221' && $fourcc !== '52457e5e')) {
            $Chameleon = 'Rar';
        } elseif ($xt === 'gz' && $twocc !== '1f8b') {
            $Chameleon = 'Gzip';
        } elseif ($xt === 'bz2' && substr($str_hex, 0, 6) !== '425a68') {
            $Chameleon = 'Bzip2';
        }
        if ($Chameleon) {
            if (!$flagged) {
                $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                $flagged = true;
            }
            $heur['detections']++;
            $phpMussel['InstanceCache']['detections_count']++;
            $Out .= $lnap . sprintf(
                $phpMussel['L10N']->getString('_exclamation_final'),
                sprintf($phpMussel['L10N']->getString('scan_chameleon'), $Chameleon)
            ) . "\n";
            $phpMussel['whyflagged'] .= sprintf(
                $phpMussel['L10N']->getString('_exclamation'),
                sprintf($phpMussel['L10N']->getString('scan_chameleon'), $Chameleon) . ' (' . $ofnSafe . ')'
            );
        }
    }

    /** Office document chameleon attack detection. */
    if ($phpMussel['Config']['attack_specific']['chameleon_to_doc']) {
        if (strpos(',doc,dot,pps,ppt,xla,xls,wiz,', ',' . $xt . ',') !== false) {
            if ($fourcc !== 'd0cf11e0') {
                if (!$flagged) {
                    $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                    $flagged = true;
                }
                $heur['detections']++;
                $phpMussel['InstanceCache']['detections_count']++;
                $Out .= $lnap . sprintf(
                    $phpMussel['L10N']->getString('_exclamation_final'),
                    sprintf($phpMussel['L10N']->getString('scan_chameleon'), 'Office')
                ) . "\n";
                $phpMussel['whyflagged'] .= sprintf(
                    $phpMussel['L10N']->getString('_exclamation'),
                    sprintf($phpMussel['L10N']->getString('scan_chameleon'), 'Office') . ' (' . $ofnSafe . ')'
                );
            }
        }
    }

    /** Image chameleon attack detection. */
    if ($phpMussel['Config']['attack_specific']['chameleon_to_img']) {
        $Chameleon = '';
        if (
            (($xt === 'bmp' || $xt === 'dib') && $twocc !== '424d') ||
            ($xt === 'gif' && (substr($str_hex, 0, 12) !== '474946383761' && substr($str_hex, 0, 12) !== '474946383961')) ||
            (preg_match('~j(?:fif?|if|peg?|pg)~', $xt) && substr($str_hex, 0, 6) !== 'ffd8ff') ||
            ($xt === 'jp2' && substr($str_hex, 0, 16) !== '0000000c6a502020') ||
            (($xt === 'pdd' || $xt === 'psd') && $fourcc !== '38425053') ||
            ($xt === 'png' && $fourcc !== '89504e47') ||
            ($xt === 'webp' && ($fourcc !== '52494646' || substr($str, 8, 4) !== 'WEBP')) ||
            ($xt === 'xcf' && substr($str, 0, 8) !== 'gimp xcf')
        ) {
            if (!$flagged) {
                $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                $flagged = true;
            }
            $heur['detections']++;
            $phpMussel['InstanceCache']['detections_count']++;
            $Out .= $lnap . sprintf(
                $phpMussel['L10N']->getString('_exclamation_final'),
                sprintf($phpMussel['L10N']->getString('scan_chameleon'), $phpMussel['L10N']->getString('image'))
            ) . "\n";
            $phpMussel['whyflagged'] .= sprintf(
                $phpMussel['L10N']->getString('_exclamation'),
                sprintf($phpMussel['L10N']->getString('scan_chameleon'), $phpMussel['L10N']->getString('image')) . ' (' . $ofnSafe . ')'
            );
        }
    }

    /** PDF chameleon attack detection. */
    if ($phpMussel['Config']['attack_specific']['chameleon_to_pdf']) {
        if ($xt === 'pdf' && !$pdf_magic) {
            if (!$flagged) {
                $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                $flagged = true;
            }
            $heur['detections']++;
            $phpMussel['InstanceCache']['detections_count']++;
            $Out .= $lnap . sprintf(
                $phpMussel['L10N']->getString('_exclamation_final'),
                sprintf($phpMussel['L10N']->getString('scan_chameleon'), 'PDF')
            ) . "\n";
            $phpMussel['whyflagged'] .= sprintf(
                $phpMussel['L10N']->getString('_exclamation'),
                sprintf($phpMussel['L10N']->getString('scan_chameleon'), 'PDF') . ' (' . $ofnSafe . ')'
            );
        }
    }

    /** Control character detection. */
    if ($phpMussel['Config']['attack_specific']['block_control_characters']) {
        if (preg_match('/[\x00-\x08\x0b\x0c\x0e\x1f\x7f]/i', $str)) {
            $Out .= $lnap . sprintf(
                $phpMussel['L10N']->getString('_exclamation'),
                $phpMussel['L10N']->getString('detected_control_characters')
            ) . "\n";
            $heur['detections']++;
            $phpMussel['InstanceCache']['detections_count']++;
            if (!$flagged) {
                $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                $flagged = true;
            }
            $phpMussel['whyflagged'] .= sprintf(
                $phpMussel['L10N']->getString('_exclamation'),
                $phpMussel['L10N']->getString('detected_control_characters') . ' (' . $ofnSafe . ')'
            );
        }
    }

    /**
     * If the heuristic weight of the current scan iteration exceeds the
     * heuristic threshold defined by the configuration, or if outs has already
     * been filled, dump all heuristic detections and non-heuristic detections
     * together into outs and regard the iteration as flagged.
     */
    if (
        $heur['weight'] >= $phpMussel['Config']['heuristic']['threshold'] ||
        $Out
    ) {
        $Out .= $heur['cli'];
        $phpMussel['whyflagged'] .= $heur['web'];
    }

    /** Plugin hook: "before_vt". */
    $phpMussel['Execute_Hook']('before_vt');

    /** Virus Total API integration. */
    if (
        !$Out &&
        !empty($phpMussel['Config']['virustotal']['vt_public_api_key'])
    ) {
        $DoScan = false;
        $phpMussel['Config']['virustotal']['vt_suspicion_level'] =
            (int)$phpMussel['Config']['virustotal']['vt_suspicion_level'];
        if ($phpMussel['Config']['virustotal']['vt_suspicion_level'] === 0) {
            $DoScan = ($heur['weight'] > 0);
        } elseif ($phpMussel['Config']['virustotal']['vt_suspicion_level'] === 1) {
            $DoScan = (
                $heur['weight'] > 0 ||
                $is_pe ||
                $fileswitch === 'chrome' ||
                $fileswitch === 'java' ||
                $fileswitch === 'docfile' ||
                $fileswitch === 'vt_interest'
            );
        } elseif ($phpMussel['Config']['virustotal']['vt_suspicion_level'] === 2) {
            $DoScan = true;
        }
        if ($DoScan) {
            $VTWeight = ['weight' => 0, 'cli' => '', 'web' => ''];
            if (!isset($phpMussel['InstanceCache']['vt_quota'])) {
                $phpMussel['InstanceCache']['vt_quota'] = $phpMussel['FetchCache']('vt_quota');
            }
            $x = 0;
            if (!empty($phpMussel['InstanceCache']['vt_quota'])) {
                $phpMussel['InstanceCache']['vt_quota'] = explode(';', $phpMussel['InstanceCache']['vt_quota']);
                foreach ($phpMussel['InstanceCache']['vt_quota'] as &$phpMussel['ThisQuota']) {
                    if ($phpMussel['ThisQuota'] > $phpMussel['Time']) {
                        $x++;
                    } else {
                        $phpMussel['ThisQuota'] = '';
                    }
                }
                unset($phpMussel['ThisQuota']);
                $phpMussel['InstanceCache']['vt_quota'] =
                    implode(';', $phpMussel['InstanceCache']['vt_quota']);
            }
            if ($x < $phpMussel['Config']['virustotal']['vt_quota_rate']) {
                $VTParams = [
                    'apikey' => $phpMussel['Config']['virustotal']['vt_public_api_key'],
                    'resource' => $md5
                ];
                $VTRequest = $phpMussel['Request'](
                    'http://www.virustotal.com/vtapi/v2/file/report?apikey=' .
                    urlencode($phpMussel['Config']['virustotal']['vt_public_api_key']) .
                    '&resource=' . $md5,
                $VTParams, 12);
                $VTJSON = json_decode($VTRequest, true);
                $y = $phpMussel['Time'] + ($phpMussel['Config']['virustotal']['vt_quota_time'] * 60);
                $phpMussel['InstanceCache']['vt_quota'] .= $y . ';';
                while (substr_count($phpMussel['InstanceCache']['vt_quota'], ';;')) {
                    $phpMussel['InstanceCache']['vt_quota'] = str_ireplace(';;', ';', $phpMussel['InstanceCache']['vt_quota']);
                }
                $phpMussel['SaveCache']('vt_quota', $y + 60, $phpMussel['InstanceCache']['vt_quota']);
                if (isset($VTJSON['response_code'])) {
                    $VTJSON['response_code'] = (int)$VTJSON['response_code'];
                    if (
                        isset($VTJSON['scans']) &&
                        $VTJSON['response_code'] === 1 &&
                        is_array($VTJSON['scans'])
                    ) {
                        foreach ($VTJSON['scans'] as $VTKey => $VTValue) {
                            if ($VTValue['detected'] && $VTValue['result']) {
                                $VN = $VTKey . '(VirusTotal)-' . $VTValue['result'];
                                if (
                                    strpos($phpMussel['InstanceCache']['greylist'], ',' . $VN . ',') === false &&
                                    empty($phpMussel['InstanceCache']['ignoreme'])
                                ) {
                                    if (!$flagged) {
                                        $phpMussel['killdata'] .= $md5 . ':' . $str_len . ':' . $ofn . "\n";
                                        $flagged = true;
                                    }
                                    $heur['detections']++;
                                    $phpMussel['InstanceCache']['detections_count']++;
                                    if ($phpMussel['Config']['virustotal']['vt_weighting'] > 0) {
                                        $VTWeight['weight']++;
                                        $VTWeight['web'] .= $lnap . sprintf(
                                            $phpMussel['L10N']->getString('_exclamation'),
                                            sprintf($phpMussel['L10N']->getString('detected'), $VN)
                                        ) . "\n";
                                        $VTWeight['cli'] .= sprintf(
                                            $phpMussel['L10N']->getString('_exclamation'),
                                            sprintf($phpMussel['L10N']->getString('detected'), $VN) . ' (' . $ofnSafe . ')'
                                        );
                                    } else {
                                        $Out .= $lnap . sprintf(
                                            $phpMussel['L10N']->getString('_exclamation_final'),
                                            sprintf($phpMussel['L10N']->getString('detected'), $VN)
                                        ) . "\n";
                                        $phpMussel['whyflagged'] .= sprintf(
                                            $phpMussel['L10N']->getString('_exclamation'),
                                            sprintf($phpMussel['L10N']->getString('detected'), $VN) . ' (' . $ofnSafe . ')'
                                        );
                                    }
                                }
                            }
                        }
                    }
                }
                if (
                    $VTWeight['weight'] > 0 &&
                    $VTWeight['weight'] >= $phpMussel['Config']['virustotal']['vt_weighting']
                ) {
                    $Out .= $VTWeight['web'];
                    $phpMussel['whyflagged'] .= $VTWeight['cli'];
                }
            }
        }
    }

    /** Plugin hook: "after_vt". */
    $phpMussel['Execute_Hook']('after_vt');

    if (
        isset($phpMussel['HashCacheData']) &&
        !isset($phpMussel['HashCache']['Data'][$phpMussel['HashCacheData']]) &&
        $phpMussel['Config']['general']['scan_cache_expiry'] > 0
    ) {
        if (empty($phpMussel['HashCache']['Data']) || !is_array($phpMussel['HashCache']['Data'])) {
            $phpMussel['HashCache']['Data'] = [];
        }
        $phpMussel['HashCache']['Data'][$phpMussel['HashCacheData']] = [
            $phpMussel['HashCacheData'],
            $phpMussel['Time'] + $phpMussel['Config']['general']['scan_cache_expiry'],
            (empty($Out) ? '' : bin2hex($Out)),
            (empty($phpMussel['whyflagged']) ? '' : bin2hex($phpMussel['whyflagged']))
        ];
    }

    /** Set final debug values, if this has been enabled. */
    if (isset($phpMussel['DebugArr'], $phpMussel['DebugArrKey'])) {
        $phpMussel['DebugArr'][$phpMussel['DebugArrKey']]['Results'] = !$Out ? 1 : 2;
        $phpMussel['DebugArr'][$phpMussel['DebugArrKey']]['Output'] = $Out;
    }

    if ($Out) {
        /** Register object flagged. */
        if (isset($phpMussel['cli_args'][1]) && $phpMussel['cli_args'][1] == 'cli_scan') {
            $phpMussel['Stats-Increment']('CLI-Flagged', 1);
        } else {
            $phpMussel['Stats-Increment']($phpMussel['EOF'] ? 'API-Flagged' : 'Web-Blocked', 1);
        }
    }

    /** Exit data handler. */
    return !$Out ? [1, ''] : [2, $Out];
};

/**
 * Splits a signature into its constituent parts (name, pattern, etc).
 *
 * @param string $Sig The signature.
 * @param int $Max The maximum number of parts to return (optional).
 * @return array The parts.
 */
$phpMussel['SplitSigParts'] = function ($Sig, $Max = -1) {
    return preg_split('~(?<!\?|\<)\:~', $Sig, $Max, PREG_SPLIT_NO_EMPTY);
};

/**
 * Handles scanning for files contained within archives.
 *
 * @param string $x Scan results inherited from parent in the form of a string.
 * @param int $r Scan results inherited from parent in the form of an integer.
 * @param string $Indent Line padding for the scan results.
 * @param string $ItemRef A reference to the path and original filename of the
 *      item being scanned in relation to its container and/or its hierarchy
 *      within the scan process.
 * @param string $Filename The original filename of the item being scanned.
 * @param string $Data The data to be scanned.
 * @param int $Depth The depth of the item being scanned in relation to its
 *      container and/or its hierarchy within the scan process.
 * @param string $MD5 A hash for the content, inherited from the parent.
 */
$phpMussel['MetaDataScan'] = function (&$x, &$r, $Indent, $ItemRef, $Filename, &$Data, $Depth, $MD5) use (&$phpMussel) {

    /** Plugin hook: "MetaDataScan_start". */
    $phpMussel['Execute_Hook']('MetaDataScan_start');

    /** Data is empty. Nothing to scan. Exit early. */
    if (!$Filesize = strlen($Data)) {
        return;
    }

    /** Filesize thresholds. */
    if (
        $phpMussel['Config']['files']['filesize_archives'] &&
        $phpMussel['Config']['files']['filesize_limit'] > 0 &&
        $Filesize > $phpMussel['ReadBytes']($phpMussel['Config']['files']['filesize_limit'])
    ) {
        if (!$phpMussel['Config']['files']['filesize_response']) {
            $x .=
                $Indent . $phpMussel['L10N']->getString('ok') . ' (' .
                $phpMussel['L10N']->getString('filesize_limit_exceeded') . ").\n";
            return;
        }
        $r = 2;
        $phpMussel['killdata'] .= $MD5 . ':' . $Filesize . ':' . $ItemRef . "\n";
        $phpMussel['whyflagged'] .= sprintf(
            $phpMussel['L10N']->getString('_exclamation'),
            $phpMussel['L10N']->getString('filesize_limit_exceeded') . ' (' . $ItemRef . ')'
        );
        $x .=
            $Indent . $phpMussel['L10N']->getString('filesize_limit_exceeded') .
            $phpMussel['L10N']->getString('_fullstop_final') . "\n";
        return;
    }

    /** Process filetype blacklisting, whitelisting, and greylisting. */
    if ($phpMussel['Config']['files']['filetype_archives']) {
        list($xt, $xts, $gzxt, $gzxts) = $phpMussel['FetchExt']($Filename);
        if ($phpMussel['ContainsMustAssert']([
            $phpMussel['Config']['files']['filetype_whitelist']
        ], [$xt, $xts], ',', true, true)) {
            $x .= $Indent . $phpMussel['L10N']->getString('scan_no_problems_found') . "\n";
            return;
        }
        if ($phpMussel['ContainsMustAssert']([
            $phpMussel['Config']['files']['filetype_blacklist']
        ], [$xt, $xts], ',', true, true)) {
            $r = 2;
            $phpMussel['killdata'] .= $MD5 . ':' . $Filesize . ':' . $ItemRef . "\n";
            $phpMussel['whyflagged'] .= sprintf(
                $phpMussel['L10N']->getString('_exclamation'),
                $phpMussel['L10N']->getString('filetype_blacklisted') . ' (' . $ItemRef . ')'
            );
            $x .=
                $Indent . $phpMussel['L10N']->getString('filetype_blacklisted') .
                $phpMussel['L10N']->getString('_fullstop_final') . "\n";
            return;
        }
        if (!empty($phpMussel['Config']['files']['filetype_greylist']) && $phpMussel['ContainsMustAssert']([
            $phpMussel['Config']['files']['filetype_greylist']
        ], [$xt, $xts])) {
            $r = 2;
            $phpMussel['killdata'] .= $MD5 . ':' . $Filesize . ':' . $ItemRef . "\n";
            $phpMussel['whyflagged'] .= sprintf(
                $phpMussel['L10N']->getString('_exclamation'),
                $phpMussel['L10N']->getString('filetype_blacklisted') . ' (' . $ItemRef . ')'
            );
            $x .=
                $Indent . $phpMussel['L10N']->getString('filetype_blacklisted') .
                $phpMussel['L10N']->getString('_fullstop_final') . "\n";
            return;
        }
    }

    /** Determine whether the file being scanned is a macro. */
    $phpMussel['InstanceCache']['file_is_macro'] = (
        preg_match('~vbaProject\.bin$~i', $Filename) ||
        preg_match('~^\xd0\xcf|\x00Attribut|\x01CompObj|\x05Document~', $Data)
    );

    /** Handle macro detection and blocking. */
    if ($phpMussel['Config']['attack_specific']['block_macros'] && $phpMussel['InstanceCache']['file_is_macro']) {
        $r = 2;
        $phpMussel['killdata'] .= $MD5 . ':' . $Filesize . ':' . $ItemRef . "\n";
        $phpMussel['whyflagged'] .= sprintf(
            $phpMussel['L10N']->getString('_exclamation'),
            $phpMussel['L10N']->getString('macros_not_permitted') . ' (' . $ItemRef . ')'
        );
        $x .= $Indent . $phpMussel['L10N']->getString('macros_not_permitted') . $phpMussel['L10N']->getString('_fullstop_final') . "\n";
        return;
    }

    /** Increment objects scanned count. */
    $phpMussel['InstanceCache']['objects_scanned']++;

    /** Send the scan target to the data handler. */
    try {
        $Scan = $phpMussel['DataHandler']($Data, $Depth, $Filename);
    } catch (\Exception $e) {
        throw new \Exception($e->getMessage());
    }

    /**
     * Check whether the file is compressed. If it's compressed, attempt to
     * decompress it, and then scan the decompressed version of the file. We'll
     * only bother doing this if the file hasn't already been flagged though.
     */
    if ($Scan[0] === 1) {

        /** Create a new compression object. */
        $CompressionObject = new \phpMussel\CompressionHandler\CompressionHandler($Data);

        /** Now we'll try to decompress the file. */
        if (!$CompressionResults = $CompressionObject->TryEverything()) {

            /** Success! Now we'll send it to the data handler. */
            try {
                $Scan = $phpMussel['DataHandler']($CompressionObject->Data, $Depth, $phpMussel['DropTrailingCompressionExtension']($Filename));
            } catch (\Exception $e) {
                throw new \Exception($e->getMessage());
            }

            /**
             * Replace originally scanned data with decompressed data in case
             * needed by the archive handler.
             */
            $Data = $CompressionObject->Data;

        }

        /** Cleanup. */
        unset($CompressionResults, $CompressionObject);

    }

    /** Destroy item-specific metadata set by the archive handler instance. */
    unset($phpMussel['CrxPubKey'], $phpMussel['CrxSig']);

    /** Update the results if anything bad was found and then exit. */
    if ($Scan[0] !== 1) {
        $r = $Scan[0];
        $x .= '-' . $Scan[1];
        return;
    }

    /** Or, if nothing bad was found for this entry, make a note of it. */
    $x .= $Indent . $phpMussel['L10N']->getString('scan_no_problems_found') . "\n";

};

/**
 * Looks for indicators of image files (i.e., attempts to determine whether a
 * file is an image file).
 *
 * @param string $Ext The file extension.
 * @param string $Head The file header.
 * @return bool True: Indicators found. False: Indicators not found.
 */
$phpMussel['Indicator-Image'] = function ($Ext, $Head) {
    return (
        preg_match(
            '/^(?:bm[2p]|c(d5|gm)|d(ib|w[fg]|xf)|ecw|fits|gif|img|j(f?if?|p[2s]|pe?g?2?|xr)|p(bm|cx|dd|gm|ic|n[gms]|' .
            'pm|s[dp])|s(id|v[ag])|tga|w(bmp?|ebp|mp)|x(cf|bmp))$/'
        , $Ext) ||
        preg_match(
            '/^(?:0000000c6a502020|25504446|38425053|424d|474946383[79]61|57454250|67696d7020786366|89504e47|ffd8ff)/'
        , $Head)
    );
};

/**
 * Fetches extensions data from filenames.
 *
 * @param string $ofn The original filename.
 * @return array The extensions data.
 */
$phpMussel['FetchExt'] = function ($ofn) {
    $decPos = strrpos($ofn, '.');
    $ofnLen = strlen($ofn);
    if ($decPos === false || $decPos === ($ofnLen - 1)) {
        return ['-', '-', '-', '-'];
    }
    $xt = strtolower(substr($ofn, ($decPos + 1)));
    $xts = substr($xt, 0, 3) . '*';
    if (strtolower(substr($ofn, -3)) === '.gz') {
        $ofnNoGZ = substr($ofn, 0, ($ofnLen - 3));
        $decPosNoGZ = strrpos($ofnNoGZ, '.');
        if ($decPosNoGZ !== false && $decPosNoGZ !== (strlen($ofnNoGZ) - 1)) {
            $gzxt = strtolower(substr($ofnNoGZ, ($decPosNoGZ + 1)));
            $gzxts = substr($gzxt, 0, 3) . '*';
        }
    } else {
        $gzxts = $gzxt = '-';
    }
    return [$xt, $xts, $gzxt, $gzxts];
};

/**
 * Remove occurrence of $A from leading substring of $B.
 */
$phpMussel['RemoveLeadMatch'] = function ($A, $B) {
    $LenA = strlen($A);
    $LenB = strlen($B);
    for ($Iter = 0; $Iter < $LenA && $Iter < $LenB; $Iter++) {
        $CharA = substr($A, $Iter, 1);
        $CharB = substr($B, $Iter, 1);
        if ($CharA !== $CharB) {
            break;
        }
    }
    return ($Iter === $LenB) ? '' : substr($B, $Iter);
};

/**
 * Get substring of string after final slash.
 */
$phpMussel['SubstrAfterFinalSlash'] = function ($String) {
    return strpos($String, '/') !== false ? substr($String, strrpos($String, '/') + 1) : (
        strpos($String, "\\") !== false ? substr($String, strrpos($String, "\\") + 1) : $String
    );
};

/**
 * Responsible for recursing through any files given to it to be scanned, which
 * may be necessary for the case of archives and directories. It performs the
 * preparations necessary for scanning files using the "data handler" and the
 * "meta data scan" closures. Additionally, it performs some necessary
 * whitelist, blacklist and greylist checks, filesize and file extension
 * checks, and handles the processing and extraction of files from archives,
 * fetching the files contained in archives being scanned in order to process
 * those contained files as so that they, too, may be scanned.
 *
 * When phpMussel is instructed to scan a directory or an array of multiple
 * files, the recursor is the closure function responsible for iterating
 * through that directory and/or array queued for scanning, and if necessary,
 * will recurse itself (such as for when scanning a directory containing
 * sub-directories or when scanning a multidimensional array of multiple files
 * and/or directories).
 *
 * @param string|array $f In the context of the initial file upload scanning
 *      that phpMussel performs when operating via a server, this parameter (a
 *      string) represents the "temporary filename" of the file being scanned
 *      (the temporary filename, in this context, referring to the name
 *      temporarily assigned to the file by the server upon the file being
 *      uploaded to the temporary uploads location assigned to the server).
 *      When operating in the context of CLI mode, both $f and $ofn represent
 *      the scan target, as per specified by the CLI operator; The only
 *      difference between the two is when the scan target is a directory,
 *      rather than a single file; $f will represent the full path to the file
 *      (so, directory plus filename), whereas $ofn will represent only the
 *      filename. This parameter can also accept an array of filenames.
 * @param bool $n This optional parameter is a boolean (defaults to false, but
 *      set to true during the initial scan of file uploads), indicating the
 *      format for returning the scan results. False instructs the function to
 *      return results as an integer; True instructs the function to return
 *      results as human readable text (refer to Section 3A of the README
 *      documentation, "HOW TO USE (FOR WEB SERVERS)", for more information).
 * @param bool $zz This optional parameter is a boolean (defaults to false, but
 *      set to true during the initial scan of file uploads), indicating to the
 *      function whether or not arrayed results should be imploded prior to
 *      being returned to the calling function. False instructs the function to
 *      return the arrayed results as verbatim; True instructs the function to
 *      return the arrayed results as an imploded string.
 * @param int $dpt Represents the current depth of recursion from which the
 *      function has been called. This information is used for determining how
 *      far to indent any entries generated for logging and for the display of
 *      scan results in CLI (you should never manually set this parameter
 *      yourself).
 * @param string $ofn For the file upload scanning that phpMussel normally
 *      performs by default, this parameter represents the "original filename"
 *      of the file being scanned (the original filename, in this context,
 *      referring to the name supplied by the upload client, as opposed to the
 *      temporary filename assigned by the server or anything else).
 *      When operating in the context of CLI mode, both $f and $ofn represent
 *      the scan target, as per specified by the CLI operator; The only
 *      difference between the two is when the scan target is a directory,
 *      rather than a single file; $f will represent the full path to the file
 *      (so, directory plus filename), whereas $ofn will represent only the
 *      filename.
 * @return int|string|array The scan results, returned as an array when the $f
 *      parameter is an array and when $n and/or $zz is/are false, and
 *      otherwise returned as per described by the README documentation. The
 *      function may also die the script and return nothing, if something goes
 *      wrong, such as if the function is triggered in the absence of the
 *      required $phpMussel['InstanceCache'] variable being set.
 */
$phpMussel['Recursor'] = function ($f = '', $n = false, $zz = false, $dpt = 0, $ofn = '') use (&$phpMussel) {
    if (!isset($phpMussel['InstanceCache'])) {
        throw new \Exception($phpMussel['L10N']->getString(
            'required_variables_not_defined'
        ) ?: '[phpMussel] Required variables aren\'t defined: Can\'t continue.');
    }

    /** Plugin hook: "Recursor_start". */
    $phpMussel['Execute_Hook']('Recursor_start');

    /** Prepare signature files for the scan process. */
    if (empty($phpMussel['InstanceCache']['OrganisedSigFiles'])) {
        $phpMussel['OrganiseSigFiles']();
        $phpMussel['InstanceCache']['OrganisedSigFiles'] = true;
    }

    if ($phpMussel['EOF']) {
        $phpMussel['whyflagged'] = $phpMussel['killdata'] = $phpMussel['PEData'] = '';
        if ($dpt === 0 || !isset(
            $phpMussel['InstanceCache']['objects_scanned'],
            $phpMussel['InstanceCache']['detections_count'],
            $phpMussel['InstanceCache']['scan_errors']
        )) {
            $phpMussel['InstanceCache']['objects_scanned'] = 0;
            $phpMussel['InstanceCache']['detections_count'] = 0;
            $phpMussel['InstanceCache']['scan_errors'] = 0;
        }
    } else {
        if (!isset($phpMussel['killdata'])) {
            $phpMussel['killdata'] = '';
        }
        if (!isset($phpMussel['whyflagged'])) {
            $phpMussel['whyflagged'] = '';
        }
        if (!isset($phpMussel['PEData'])) {
            $phpMussel['PEData'] = '';
        }
        if (!isset(
            $phpMussel['InstanceCache']['objects_scanned'],
            $phpMussel['InstanceCache']['detections_count'],
            $phpMussel['InstanceCache']['scan_errors']
        )) {
            $phpMussel['InstanceCache']['objects_scanned'] = 0;
            $phpMussel['InstanceCache']['detections_count'] = 0;
            $phpMussel['InstanceCache']['scan_errors'] = 0;
        }
    }

    /** Increment scan depth. */
    $dpt++;
    /** Controls indenting relating to scan depth for normal logging and for CLI-mode scanning. */
    $lnap = str_pad('> ', ($dpt + 1), '-', STR_PAD_LEFT);

    /**
     * If the scan target is an array, iterate through the array and recurse
     * the recursor with each array element.
     */
    if (is_array($f)) {
        foreach ($f as &$Current) {
            try {
                $Current = $phpMussel['Recursor']($Current, $n, false, $dpt, $Current);
            } catch (\Exception $e) {
                throw new \Exception($e->getMessage());
            }
        }
        return ($n && $zz) ? $phpMussel['implode_md']($f) : $f;
    }

    $ofn = $phpMussel['prescan_decode']($ofn);
    $ofnSafe = urlencode($ofn);

    /**
     * If the scan target is a directory, iterate through the directory
     * contents and recurse the recursor with these contents.
     */
    if (is_dir($f)) {
        if (!is_readable($f)) {
            $phpMussel['InstanceCache']['scan_errors']++;
            return !$n ? 0 : $lnap . sprintf(
                $phpMussel['L10N']->getString('_exclamation_final'),
                sprintf($phpMussel['L10N']->getString('failed_to_access'), $ofn)
            ) . "\n";
        }
        $Dir = $phpMussel['DirectoryRecursiveList']($f);
        foreach ($Dir as &$Sub) {
            try {
                $Sub = $phpMussel['Recursor']($f . '/' . $Sub, $n, false, $dpt, $Sub);
            } catch (\Exception $e) {
                throw new \Exception($e->getMessage());
            }
        }
        return ($n && $zz) ? $phpMussel['implode_md']($Dir) : $Dir;
    }

    /** Define file phase. */
    $phpMussel['InstanceCache']['phase'] = 'file';

    /** Indicates whether the scan target is a part of a container. */
    $phpMussel['InstanceCache']['container'] = 'none';

    /** Indicates whether the scan target is an OLE object. */
    $phpMussel['InstanceCache']['file_is_ole'] = false;

    /** Fetch the greylist if it hasn't already been fetched. */
    if (!isset($phpMussel['InstanceCache']['greylist'])) {
        if (!file_exists($phpMussel['Vault'] . 'greylist.csv')) {
            $phpMussel['InstanceCache']['greylist'] = ',';
            $Handle = fopen($phpMussel['Vault'] . 'greylist.csv', 'a');
            fwrite($Handle, ',');
            fclose($Handle);
        } else {
            $phpMussel['InstanceCache']['greylist'] = $phpMussel['ReadFile']($phpMussel['Vault'] . 'greylist.csv');
        }
    }

    /** Plugin hook: "before_scan". */
    $phpMussel['Execute_Hook']('before_scan');

    $fnCRC = hash('crc32b', $ofn);

    /** Kill it here if the scan target isn't a valid file. */
    if (!$f || !$d = is_file($f)) {
        return (!$n) ? 0 :
            $lnap . $phpMussel['L10N']->getString('scan_checking') . ' \'' . $ofn .
            '\' (FN: ' . $fnCRC . "):\n-" . $lnap . sprintf(
                $phpMussel['L10N']->getString('_exclamation_final'),
                $phpMussel['L10N']->getString('invalid_file')
            ) . "\n";
    }

    $fS = filesize($f);
    if ($phpMussel['Config']['files']['filesize_limit'] > 0) {
        if ($fS > $phpMussel['ReadBytes']($phpMussel['Config']['files']['filesize_limit'])) {
            if (!$phpMussel['Config']['files']['filesize_response']) {
                return (!$n) ? 1 :
                    $lnap . $phpMussel['L10N']->getString('scan_checking') . ' \'' .
                    $ofn . '\' (FN: ' . $fnCRC . "):\n-" . $lnap .
                    $phpMussel['L10N']->getString('ok') . ' (' .
                    $phpMussel['L10N']->getString('filesize_limit_exceeded') . ").\n";
            }
            $phpMussel['killdata'] .= '--FILESIZE-LIMIT--------NO-HASH-:' . $fS . ':' . $ofn . "\n";
            $phpMussel['whyflagged'] .= sprintf(
                $phpMussel['L10N']->getString('_exclamation'),
                $phpMussel['L10N']->getString('filesize_limit_exceeded') . ' (' . $ofnSafe . ')'
            );
            if ($phpMussel['Config']['general']['delete_on_sight'] && is_readable($f)) {
                unlink($f);
            }
            return (!$n) ? 2 :
                $lnap . $phpMussel['L10N']->getString('scan_checking') . ' \'' . $ofn .
                '\' (FN: ' . $fnCRC . "):\n-" . $lnap .
                $phpMussel['L10N']->getString('filesize_limit_exceeded') .
                $phpMussel['L10N']->getString('_fullstop_final') . "\n";
        }
    }
    if (!$phpMussel['Config']['attack_specific']['allow_leading_trailing_dots'] && (
        substr($ofn, 0, 1) === '.' || substr($ofn, -1) === '.'
    )) {
        $phpMussel['killdata'] .= '--FILENAME-MANIPULATION-NO-HASH-:' . $fS . ':' . $ofn . "\n";
        $phpMussel['whyflagged'] .= sprintf(
            $phpMussel['L10N']->getString('_exclamation'),
            $phpMussel['L10N']->getString('scan_filename_manipulation_detected') . ' (' . $ofnSafe . ')'
        );
        if ($phpMussel['Config']['general']['delete_on_sight'] && is_readable($f)) {
            unlink($f);
        }
        return (!$n) ? 2 :
            $lnap . $phpMussel['L10N']->getString('scan_checking') . ' \'' . $ofn .
            '\' (FN: ' . $fnCRC . "):\n-" . $lnap . sprintf(
                $phpMussel['L10N']->getString('_exclamation_final'),
                $phpMussel['L10N']->getString('scan_filename_manipulation_detected')
            ) . "\n";
    }

    /** Get file extensions. */
    list($xt, $xts, $gzxt, $gzxts) = $phpMussel['FetchExt']($ofn);

    /** Process filetype whitelisting. */
    if ($phpMussel['ContainsMustAssert']([
        $phpMussel['Config']['files']['filetype_whitelist']
    ], [$xt, $xts, $gzxt, $gzxts], ',', true, true)) {
        return (!$n) ? 1 :
            $lnap . $phpMussel['L10N']->getString('scan_checking') . ' \'' . $ofn .
            '\' (FN: ' . $fnCRC . "):\n-" . $lnap .
            $phpMussel['L10N']->getString('scan_no_problems_found') . "\n";
    }

    /** Process filetype blacklisting. */
    if ($phpMussel['ContainsMustAssert']([
        $phpMussel['Config']['files']['filetype_blacklist']
    ], [$xt, $xts, $gzxt, $gzxts], ',', true, true)) {
        $phpMussel['killdata'] .= '--FILETYPE-BLACKLISTED--NO-HASH-:' . $fS . ':' . $ofn . "\n";
        $phpMussel['whyflagged'] .= sprintf(
            $phpMussel['L10N']->getString('_exclamation'),
            $phpMussel['L10N']->getString('filetype_blacklisted') . ' (' . $ofnSafe . ')'
        );
        if ($phpMussel['Config']['general']['delete_on_sight'] && is_readable($f)) {
            unlink($f);
        }
        return (!$n) ? 2 :
            $lnap . $phpMussel['L10N']->getString('scan_checking') . ' \'' .
            $ofn . '\' (FN: ' . $fnCRC . "):\n-" . $lnap .
            $phpMussel['L10N']->getString('filetype_blacklisted') .
            $phpMussel['L10N']->getString('_fullstop_final') . "\n";
    }

    /** Process filetype greylisting (when relevant). */
    if (!empty($phpMussel['Config']['files']['filetype_greylist']) && $phpMussel['ContainsMustAssert']([
        $phpMussel['Config']['files']['filetype_greylist']
    ], [$xt, $xts, $gzxt, $gzxts])) {
        $phpMussel['killdata'] .= '----FILETYPE--NOT-GREYLISTED----:' . $fS . ':' . $ofn . "\n";
        $phpMussel['whyflagged'] .= sprintf(
            $phpMussel['L10N']->getString('_exclamation'),
            $phpMussel['L10N']->getString('filetype_blacklisted') . ' (' . $ofnSafe . ')'
        );
        if ($phpMussel['Config']['general']['delete_on_sight'] && is_readable($f)) {
            unlink($f);
        }
        return (!$n) ? 2 :
            $lnap . $phpMussel['L10N']->getString('scan_checking') . ' \'' .
            $ofn . '\' (FN: ' . $fnCRC . "):\n-" . $lnap .
            $phpMussel['L10N']->getString('filetype_blacklisted') .
            $phpMussel['L10N']->getString('_fullstop_final') . "\n";
    }

    /** Read in the file to be scanned. */
    $in = $phpMussel['ReadFile']($f, (
        $phpMussel['Config']['attack_specific']['scannable_threshold'] > 0 &&
        $fS > $phpMussel['ReadBytes']($phpMussel['Config']['attack_specific']['scannable_threshold'])
    ) ? $phpMussel['ReadBytes']($phpMussel['Config']['attack_specific']['scannable_threshold']) : $fS, true);

    /** Generate CRC for the file to be scanned. */
    $fdCRC = hash('crc32b', $in);

    /** Check for non-image items. */
    if (!empty($in) && $phpMussel['Config']['compatibility']['only_allow_images'] && !$phpMussel['Indicator-Image']($xt, bin2hex(substr($in, 0, 16)))) {
        $phpMussel['killdata'] .= md5($in) . ':' . $fS . ':' . $ofn . "\n";
        $phpMussel['whyflagged'] .= sprintf(
            $phpMussel['L10N']->getString('_exclamation'),
            $phpMussel['L10N']->getString('only_allow_images') . ' (' . $ofnSafe . ')'
        );
        if ($phpMussel['Config']['general']['delete_on_sight'] && is_readable($f)) {
            unlink($f);
        }
        return (!$n) ? 2 :
            $lnap . $phpMussel['L10N']->getString('scan_checking') . ' \'' .
            $ofn . '\' (FN: ' . $fnCRC . '; FD: ' . $fdCRC . "):\n-" .
            $lnap . $phpMussel['L10N']->getString('only_allow_images') .
            $phpMussel['L10N']->getString('_fullstop_final') . "\n";
    }

    /** Increment objects scanned count. */
    $phpMussel['InstanceCache']['objects_scanned']++;

    /** Send the scan target to the data handler. */
    try {
        $z = $phpMussel['DataHandler']($in, $dpt, $ofn);
    } catch (\Exception $e) {
        throw new \Exception($e->getMessage());
    }

    /**
     * Check whether the file is compressed. If it's compressed, attempt to
     * decompress it, and then scan the decompressed version of the file. We'll
     * only bother doing this if the file hasn't already been flagged though.
     */
    if ($z[0] === 1) {

        /** Create a new compression object. */
        $CompressionObject = new \phpMussel\CompressionHandler\CompressionHandler($in);

        /** Now we'll try to decompress the file. */
        if (!$CompressionResults = $CompressionObject->TryEverything()) {

            /** Success! Now we'll send it to the data handler. */
            try {
                $z = $phpMussel['DataHandler']($CompressionObject->Data, $dpt, $phpMussel['DropTrailingCompressionExtension']($ofn));
            } catch (\Exception $e) {
                throw new \Exception($e->getMessage());
            }

            /**
             * Replace originally scanned data with decompressed data in case
             * needed by the archive handler.
             */
            $in = $CompressionObject->Data;

        }

        /** Cleanup. */
        unset($CompressionResults, $CompressionObject);

    }

    /** Executed if there were any problems or if anything was detected. */
    if ($z[0] !== 1) {

        /** Quarantine if necessary. */
        if ($z[0] === 2) {
            if (
                $phpMussel['Config']['general']['quarantine_key'] &&
                !$phpMussel['Config']['general']['honeypot_mode'] &&
                strlen($in) < $phpMussel['ReadBytes']($phpMussel['Config']['general']['quarantine_max_filesize'])
            ) {
                $qfu =
                    $phpMussel['Time'] .
                    '-' .
                    md5($phpMussel['Config']['general']['quarantine_key'] . $fdCRC . $phpMussel['Time']);
                $phpMussel['Quarantine'](
                    $in,
                    $phpMussel['Config']['general']['quarantine_key'],
                    $_SERVER[$phpMussel['IPAddr']],
                    $qfu
                );
                $phpMussel['killdata'] .= sprintf($phpMussel['L10N']->getString('quarantined_as'), $qfu) . "\n";
            }
        }

        /** Delete if necessary. */
        if ($phpMussel['Config']['general']['delete_on_sight'] && is_readable($f)) {
            unlink($f);
        }

        /** Exit. */
        return !$n ? $z[0] : sprintf(
            '%s%s \'%s\' (FN: %s; FD: %s):%s%s',
            $lnap,
            $phpMussel['L10N']->getString('scan_checking'),
            $ofn,
            $fnCRC,
            $fdCRC,
            "\n",
            $z[1]
        );

    }

    $x = sprintf(
        '%1$s%2$s \'%3$s\' (FN: %4$s; FD: %5$s):%6$s-%1$s%7$s%6$s',
        $lnap,
        $phpMussel['L10N']->getString('scan_checking'),
        $ofn,
        $fnCRC,
        $fdCRC,
        "\n",
        $phpMussel['L10N']->getString('scan_no_problems_found')
    );

    /** Results. */
    $r = 1;

    /**
     * Begin archive phase.
     * Note: Archive phase will only occur when "check_archives" is enabled and
     * when no problems were detected with the scan target by this point.
     */
    if (
        $phpMussel['Config']['files']['check_archives'] &&
        !empty($in) &&
        $phpMussel['Config']['files']['max_recursion'] > 1
    ) {

        /** Define archive phase. */
        $phpMussel['InstanceCache']['phase'] = 'archive';

        /** In case there's any temporary files we need to delete afterwards. */
        $phpMussel['InstanceCache']['tempfilesToDelete'] = [];

        /** Begin processing archives. */
        $phpMussel['ArchiveRecursor']($x, $r, $in, (isset($CompressionResults) && !$CompressionResults) ? '' : $f, 0, urlencode($ofn));

        /** Begin deleting any temporary files that snuck through. */
        foreach ($phpMussel['InstanceCache']['tempfilesToDelete'] as $DeleteThis) {
            if (file_exists($DeleteThis)) {
                unlink($DeleteThis);
            }
        }

    }

    /** Quarantine if necessary. */
    if ($r === 2) {
        if (
            $phpMussel['Config']['general']['quarantine_key'] &&
            !$phpMussel['Config']['general']['honeypot_mode'] &&
            strlen($in) < $phpMussel['ReadBytes']($phpMussel['Config']['general']['quarantine_max_filesize'])
        ) {
            $qfu = $phpMussel['Time'] . '-' . md5(
                $phpMussel['Config']['general']['quarantine_key'] . $fdCRC . $phpMussel['Time']
            );
            $phpMussel['Quarantine'](
                $in,
                $phpMussel['Config']['general']['quarantine_key'],
                $_SERVER[$phpMussel['IPAddr']],
                $qfu
            );
            $phpMussel['killdata'] .= sprintf($phpMussel['L10N']->getString('quarantined_as'), $qfu);
        }
    }

    /** Delete if necessary. */
    if ($r !== 1 && $phpMussel['Config']['general']['delete_on_sight'] && is_readable($f)) {
        unlink($f);
    }

    /** Exit. */
    return !$n ? $r : $x;
};

/**
 * Quine detection for the archive handler.
 *
 * @param int $ScanDepth The current scan depth.
 * @param string $ParentHash Parent data hash.
 * @param int $ParentLen Parent data length.
 * @param string $ChildHash Child data hash.
 * @param int $ChildLen Child data length.
 * @return bool True when a quine is detected; False otherwise.
 */
$phpMussel['QuineDetector'] = function ($ScanDepth, $ParentHash, $ParentLen, $ChildHash, $ChildLen) use (&$phpMussel) {
    $phpMussel['Quine'][$ScanDepth - 1] = [$ParentHash, $ParentLen];
    for ($Iterate = 0; $Iterate < $ScanDepth; $Iterate++) {
        if ($phpMussel['Quine'][$Iterate][0] === $ChildHash && $phpMussel['Quine'][$Iterate][1] === $ChildLen) {
            return true;
        }
    }
    return false;
};

/**
 * Convert Chrome Extension data to standard Zip data.
 *
 * @param string $Data Referenced via the archive recursor.
 * @return bool True when conversion succeeds; False otherwise (e.g., not Crx).
 */
$phpMussel['ConvertCRX'] = function (&$Data) use (&$phpMussel) {
    if (substr($Data, 0, 4) !== 'Cr24' || strlen($Data) <= 16) {
        return false;
    }
    $CRX = ['Version' => unpack('i*', substr($Data, 4, 4))];
    if ($CRX['Version'][1] === 2) {
        $CRX['PubKeyLen'] = unpack('i*', substr($Data, 8, 4));
        $CRX['SigLen'] = unpack('i*', substr($Data, 12, 4));
        $ZipBegin = 16 + $CRX['PubKeyLen'][1] + $CRX['SigLen'][1];
        if (substr($Data, $ZipBegin, 2) === 'PK') {
            $phpMussel['CrxPubKey'] = bin2hex(substr($Data, 16, $CRX['PubKeyLen'][1]));
            $phpMussel['CrxSig'] = bin2hex(substr($Data, 16 + $CRX['PubKeyLen'][1], $CRX['SigLen'][1]));
            $Data = substr($Data, $ZipBegin);
            return true;
        }
    }
    return false;
};

/**
 * Archive recursor.
 *
 * This is where we recurse through archives during the scan.
 *
 * @param string $x Scan results inherited from parent in the form of a string.
 * @param int $r Scan results inherited from parent in the form of an integer.
 * @param string $Data The data to be scanned (preferably an archive).
 * @param string $File A path to the file, to be able to access it directly if
 *      needed (because the zip and rar classes require a file pointer).
 * @param int $ScanDepth The current scan depth (supplied during recursion).
 * @param string $ItemRef A reference to the parent container (for logging).
 */
$phpMussel['ArchiveRecursor'] = function (&$x, &$r, $Data, $File = '', $ScanDepth = 0, $ItemRef = '') use (&$phpMussel) {

    /** Plugin hook: "ArchiveRecursor_start". */
    $phpMussel['Execute_Hook']('ArchiveRecursor_start');

    /** Create quine detection array. */
    if (!$ScanDepth || !isset($phpMussel['Quine'])) {
        $phpMussel['Quine'] = [];
    }

    /** Count recursion depth. */
    $ScanDepth++;

    /** Used for CLI and logging. */
    $Indent = str_pad('> ', $ScanDepth + 1, '-', STR_PAD_LEFT);

    /** Reset container definition. */
    $phpMussel['InstanceCache']['container'] = 'none';

    /** The class to use to handle the data to be scanned. */
    $Handler = '';

    /** The type of container to be scanned (mostly just for logging). */
    $ConType = '';

    /** Check whether Crx, and convert if necessary. */
    if ($phpMussel['ConvertCRX']($Data)) {
        /** Reset the file pointer (because the content has been modified anyway). */
        $File = '';
    }

    /** Get file extensions. */
    if ($File) {
        list($xt, $xts, $gzxt, $gzxts) = $phpMussel['FetchExt']($File);
    } elseif ($Exts = $phpMussel['substral']($ItemRef, '.')) {
        list($xt, $xts, $gzxt, $gzxts) = $phpMussel['FetchExt']($Exts);
    } else {
        $xt = $xts = $gzxt = $gzxts = '';
    }

    /** Set appropriate container definitions and specify handler class. */
    if (substr($Data, 0, 2) === 'PK') {
        $Handler = 'ZipHandler';
        if ($xt === 'ole') {
            $ConType = 'OLE';
        } elseif ($xt === 'crx') {
            $ConType = 'Crx';
        } elseif ($xt === 'smpk') {
            $ConType = 'SMPTE';
        } elseif ($xt === 'xpi') {
            $ConType = 'XPInstall';
        } elseif ($xts === 'app*') {
            $ConType = 'App';
        } elseif (strpos(
            ',docm,docx,dotm,dotx,potm,potx,ppam,ppsm,ppsx,pptm,pptx,xlam,xlsb,xlsm,xlsx,xltm,xltx,',
            ',' . $xt . ','
        ) !== false) {
            $ConType = 'OpenXML';
        } elseif (strpos(
            ',odc,odf,odg,odm,odp,ods,odt,otg,oth,otp,ots,ott,',
            ',' . $xt . ','
        ) !== false || $xts === 'fod*') {
            $ConType = 'OpenDocument';
        } elseif (strpos(',opf,epub,', ',' . $xt . ',') !== false) {
            $ConType = 'EPUB';
        } else {
            $ConType = 'ZIP';
            $phpMussel['InstanceCache']['container'] = 'zipfile';
        }
        if ($ConType !== 'ZIP') {
            $phpMussel['InstanceCache']['file_is_ole'] = true;
            $phpMussel['InstanceCache']['container'] = 'pkfile';
        }
    } elseif (
        substr($Data, 257, 6) === "ustar\x00" ||
        strpos(',tar,tgz,tbz,tlz,tz,', ',' . $xt . ',') !== false
    ) {
        $Handler = 'TarHandler';
        $ConType = 'TarFile';
        $phpMussel['InstanceCache']['container'] = 'tarfile';
    } elseif (substr($Data, 0, 4) === 'Rar!' || substr($Data, 0, 4) === "\x52\x45\x7e\x5e") {
        $Handler = 'RarHandler';
        $ConType = 'RarFile';
        $phpMussel['InstanceCache']['container'] = 'rarfile';
    }

    /** Not an archive. Exit early. */
    if (!$Handler) {
        return;
    }

    /** Call the archive handler. */
    if (!class_exists('\phpMussel\ArchiveHandler\ArchiveHandler')) {
        require $phpMussel['Vault'] . 'classes/ArchiveHandler.php';
    }

    /** Hash the current input data. */
    $DataHash = md5($Data);

    /** Fetch length of current input data. */
    $DataLen = strlen($Data);

    /** Handle zip files. */
    if ($Handler === 'ZipHandler') {
        /**
         * Encryption guard.
         * See: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
         */
        if ($phpMussel['Config']['files']['block_encrypted_archives']) {
            $Bits = $phpMussel['explode_bits'](substr($Data, 6, 2));
            if ($Bits && $Bits[7]) {
                $r = -4;
                $phpMussel['killdata'] .= $DataHash . ':' . $DataLen . ':' . $ItemRef . "\n";
                $phpMussel['whyflagged'] .= sprintf(
                    $phpMussel['L10N']->getString('_exclamation'),
                    $phpMussel['L10N']->getString('encrypted_archive') . ' (' . $ItemRef . ')'
                );
                $x .= sprintf(
                    '-%1$s%2$s \'%3$s\' (FN: %4$s; FD: %5$s):%6$s--%1$s%7$s%8$s%6$s',
                    $Indent,
                    $phpMussel['L10N']->getString('scan_checking'),
                    $ItemRef,
                    hash('crc32b', $File),
                    hash('crc32b', $Data),
                    "\n",
                    $phpMussel['L10N']->getString('encrypted_archive'),
                    $phpMussel['L10N']->getString('_fullstop_final')
                );
                return;
            }
        }

        /** Guard. */
        if (!class_exists('ZipArchive')) {
            if (!$phpMussel['Config']['signatures']['fail_extensions_silently']) {
                $r = -1;
                $phpMussel['killdata'] .= $DataHash . ':' . $DataLen . ':' . $ItemRef . "\n";
                $phpMussel['whyflagged'] .= $phpMussel['L10N']->getString('scan_extensions_missing') . ' (Zip)';
                $x .= sprintf(
                    '-%1$s%2$s \'%3$s\' (FN: %4$s; FD: %5$s):%6$s--%1$s%7$s%6$s',
                    $Indent,
                    $phpMussel['L10N']->getString('scan_checking'),
                    $ItemRef,
                    hash('crc32b', $File),
                    hash('crc32b', $Data),
                    "\n",
                    $phpMussel['L10N']->getString('scan_extensions_missing') . ' (Zip)'
                );
                return;
            }
        }

        /** ZipHandler needs a file pointer. */
        if (!$File || !is_readable($File)) {
            /**
             * File pointer not available. Probably already inside an
             * archive. Let's create a temporary file for this.
             */
            $PointerObject = new \phpMussel\TemporaryFileHandler\TemporaryFileHandler($Data, $phpMussel['cachePath']);
            $Pointer = &$PointerObject->Filename;
            $phpMussel['InstanceCache']['tempfilesToDelete'][] = $Pointer;
        } else {
            /** File pointer available. Let's reference it. */
            $Pointer = &$File;
        }

        /** We have a valid a pointer. Let's instantiate the object. */
        if ($Pointer) {
            $ArchiveObject = new \phpMussel\ArchiveHandler\ZipHandler($Pointer);
        }
    }

    /** Handle tar files. */
    if ($Handler === 'TarHandler') {
        /** TarHandler can work with data directly. */
        $ArchiveObject = new \phpMussel\ArchiveHandler\TarHandler($Data);
    }

    /** Handle rar files. */
    if ($Handler === 'RarHandler') {

        /** Guard. */
        if (!class_exists('RarArchive') || !class_exists('RarEntry')) {
            if (!$phpMussel['Config']['signatures']['fail_extensions_silently']) {
                $r = -1;
                $phpMussel['killdata'] .= $DataHash . ':' . $DataLen . ':' . $ItemRef . "\n";
                $phpMussel['whyflagged'] .= $phpMussel['L10N']->getString('scan_extensions_missing') . ' (Rar)';
                $x .= sprintf(
                    '-%1$s%2$s \'%3$s\' (FN: %4$s; FD: %5$s):%6$s--%1$s%7$s%6$s',
                    $Indent,
                    $phpMussel['L10N']->getString('scan_checking'),
                    $ItemRef,
                    hash('crc32b', $File),
                    hash('crc32b', $Data),
                    "\n",
                    $phpMussel['L10N']->getString('scan_extensions_missing') . ' (Rar)'
                );
                return;
            }
        }

        /** RarHandler needs a file pointer. */
        if (!$File || !is_readable($File)) {
            /**
             * File pointer not available. Probably already inside an
             * archive. Let's create a temporary file for this.
             */
            $PointerObject = new \phpMussel\TemporaryFileHandler\TemporaryFileHandler($Data, $phpMussel['cachePath']);
            $Pointer = &$PointerObject->Filename;
            $phpMussel['InstanceCache']['tempfilesToDelete'][] = $Pointer;
        } else {
            /** File pointer available. Let's reference it. */
            $Pointer = &$File;
        }

        /** We have a valid a pointer. Let's instantiate the object. */
        if ($Pointer) {
            $ArchiveObject = new \phpMussel\ArchiveHandler\RarHandler($Pointer);
        }
    }

    /** Archive object has been instantiated. Let's proceed. */
    if (isset($ArchiveObject) && is_object($ArchiveObject)) {

        /** No errors reported. Let's try checking its contents. */
        if ($ArchiveObject->ErrorState === 0) {

            /** Used to count the number of entries processed. */
            $Processed = 0;

            /** Iterate through the archive's contents. */
            while ($ArchiveObject->EntryNext()) {

                /** Flag the archive if it exceeds the "max_files_in_archives" limit and return. */
                if (
                    $phpMussel['Config']['files']['max_files_in_archives'] > 0 &&
                    $Processed > $phpMussel['Config']['files']['max_files_in_archives']
                ) {
                    $r = 2;
                    $phpMussel['killdata'] .= $DataHash . ':' . $DataLen . ':' . $ItemRef . "\n";
                    $phpMussel['whyflagged'] .= sprintf(
                        $phpMussel['L10N']->getString('_exclamation'),
                        $phpMussel['L10N']->getString('too_many_files_in_archive') . ' (' . $ItemRef . ')'
                    );
                    $x .= sprintf(
                        '-%1$s%2$s \'%3$s\' (FN: %4$s; FD: %5$s):%6$s--%1$s%7$s%8$s%6$s',
                        $Indent,
                        $phpMussel['L10N']->getString('scan_checking'),
                        $ItemRef,
                        hash('crc32b', $File),
                        hash('crc32b', $Data),
                        "\n",
                        $phpMussel['L10N']->getString('too_many_files_in_archive'),
                        $phpMussel['L10N']->getString('_fullstop_final')
                    );
                    unset($ArchiveObject, $Pointer, $PointerObject);
                    return;
                }

                $Processed++;

                /** Encryption guard. */
                if ($phpMussel['Config']['files']['block_encrypted_archives'] && $ArchiveObject->EntryIsEncrypted()) {
                    $r = -4;
                    $phpMussel['killdata'] .= $DataHash . ':' . $DataLen . ':' . $ItemRef . "\n";
                    $phpMussel['whyflagged'] .= sprintf(
                        $phpMussel['L10N']->getString('_exclamation'),
                        $phpMussel['L10N']->getString('encrypted_archive') . ' (' . $ItemRef . ')'
                    );
                    $x .= sprintf(
                        '-%1$s%2$s \'%3$s\' (FN: %4$s; FD: %5$s):%6$s--%1$s%7$s%8$s%6$s',
                        $Indent,
                        $phpMussel['L10N']->getString('scan_checking'),
                        $ItemRef,
                        hash('crc32b', $File),
                        hash('crc32b', $Data),
                        "\n",
                        $phpMussel['L10N']->getString('encrypted_archive'),
                        $phpMussel['L10N']->getString('_fullstop_final')
                    );
                    unset($ArchiveObject, $Pointer, $PointerObject);
                    return;
                }

                /** Fetch and prepare filename. */
                if ($Filename = $ArchiveObject->EntryName()) {
                    if (strpos($Filename, "\\") !== false) {
                        $Filename = $phpMussel['substral']($Filename, "\\");
                    }
                    if (strpos($Filename, '/') !== false) {
                        $Filename = $phpMussel['substral']($Filename, '/');
                    }
                }

                /** Fetch filesize. */
                $Filesize = $ArchiveObject->EntryActualSize();

                /** Fetch content and build hashes. */
                $Content = $ArchiveObject->EntryRead($Filesize);
                $MD5 = md5($Content);
                $NameCRC32 = hash('crc32b', $Filename);
                $DataCRC32 = hash('crc32b', $Content);
                $InternalCRC = $ArchiveObject->EntryCRC();
                $ThisItemRef = $ItemRef . '>' . urlencode($Filename);

                /** Verify filesize, integrity, etc. Exit early in case of problems. */
                if ($Filesize !== strlen($Content) || ($InternalCRC &&
                    preg_replace('~^0+~', '', $DataCRC32) !== preg_replace('~^0+~', '', $InternalCRC)
                )) {
                    $r = 2;
                    $phpMussel['killdata'] .= $MD5 . ':' . $Filesize . ':' . $ThisItemRef . "\n";
                    $phpMussel['whyflagged'] .= sprintf(
                        $phpMussel['L10N']->getString('_exclamation'),
                        $phpMussel['L10N']->getString('scan_tampering') . ' (' . $ThisItemRef . ')'
                    );
                    $x .= sprintf(
                        '-%1$s%2$s \'%3$s\' (FN: %4$s; FD: %5$s):%6$s--%1$s%7$s%8$s%6$s',
                        $Indent,
                        $phpMussel['L10N']->getString('scan_checking'),
                        $ThisItemRef,
                        $NameCRC32,
                        $DataCRC32,
                        "\n",
                        $phpMussel['L10N']->getString('recursive'),
                        $phpMussel['L10N']->getString('_fullstop_final')
                    );
                    unset($ArchiveObject, $Pointer, $PointerObject);
                    return;
                }

                /** Executed if the recursion depth limit has been exceeded. */
                if ($ScanDepth > $phpMussel['Config']['files']['max_recursion']) {
                    $r = 2;
                    $phpMussel['killdata'] .= $MD5 . ':' . $Filesize . ':' . $ThisItemRef . "\n";
                    $phpMussel['whyflagged'] .= sprintf(
                        $phpMussel['L10N']->getString('_exclamation'),
                        $phpMussel['L10N']->getString('recursive') . ' (' . $ThisItemRef . ')'
                    );
                    $x .= sprintf(
                        '-%1$s%2$s \'%3$s\' (FN: %4$s; FD: %5$s):%6$s--%1$s%7$s%8$s%6$s',
                        $Indent,
                        $phpMussel['L10N']->getString('scan_checking'),
                        $ThisItemRef,
                        $NameCRC32,
                        $DataCRC32,
                        "\n",
                        $phpMussel['L10N']->getString('recursive'),
                        $phpMussel['L10N']->getString('_fullstop_final')
                    );
                    unset($ArchiveObject, $Pointer, $PointerObject);
                    return;
                }

                /** Quine detection. */
                if ($phpMussel['QuineDetector']($ScanDepth, $DataHash, $DataLen, $MD5, $Filesize)) {
                    $r = 2;
                    $phpMussel['killdata'] .= $MD5 . ':' . $Filesize . ':' . $ThisItemRef . "\n";
                    $phpMussel['whyflagged'] .= sprintf(
                        $phpMussel['L10N']->getString('_exclamation'),
                        sprintf($phpMussel['L10N']->getString('detected'), 'Quine') . ' (' . $ThisItemRef . ')'
                    );
                    $x .= sprintf(
                        '-%1$s%2$s \'%3$s\' (FN: %4$s; FD: %5$s):%6$s--%1$s%7$s%8$s%6$s',
                        $Indent,
                        $phpMussel['L10N']->getString('scan_checking'),
                        $ThisItemRef,
                        $NameCRC32,
                        $DataCRC32,
                        "\n",
                        sprintf($phpMussel['L10N']->getString('detected'), 'Quine'),
                        $phpMussel['L10N']->getString('_fullstop_final')
                    );
                    unset($ArchiveObject, $Pointer, $PointerObject);
                    return;
                }

                /** Ready to check the entry. */
                $x .= sprintf(
                    '-%1$s%2$s \'%3$s\' (FN: %4$s; FD: %5$s):%6$s',
                    $Indent,
                    $phpMussel['L10N']->getString('scan_checking'),
                    $ThisItemRef,
                    $NameCRC32,
                    $DataCRC32,
                    "\n"
                );

                /** Scan the entry. */
                try {
                    $phpMussel['MetaDataScan'](
                        $x,
                        $r,
                        '--' . $Indent,
                        $ThisItemRef,
                        $Filename,
                        $Content,
                        $ScanDepth,
                        $MD5
                    );
                } catch (\Exception $e) {
                    unset($ArchiveObject, $Pointer, $PointerObject);
                    throw new \Exception($e->getMessage());
                }

                /** If we've already found something bad, we can exit early to save time. */
                if ($r !== 1) {
                    unset($ArchiveObject, $Pointer, $PointerObject);
                    return;
                }

                /** Finally, check whether the archive entry is an archive. */
                $phpMussel['ArchiveRecursor']($x, $r, $Content, '', $ScanDepth, $ThisItemRef);

            }

        }

    }

    /** Unset order is important for temporary files to be able to be deleted properly. */
    unset($ArchiveObject, $Pointer, $PointerObject);

};

/**
 * Drops trailing extensions from filenames if the extension matches that of a
 * compression format supported by the compression handler.
 *
 * @param string $Filename The filename.
 * @return string The filename sans compression extension.
 */
$phpMussel['DropTrailingCompressionExtension'] = function ($Filename) {
    return preg_replace(['~\.t[gbl]?z[\da-z]?$~i', '~\.(?:bz2?|gz|lha|lz[fhowx])$~i'], ['.tar', ''], $Filename);
};

/**
 * Forks the PHP process when scanning in CLI mode. This ensures that if PHP
 * crashes during scanning, phpMussel can continue to scan any remaining items
 * queued for scanning (because if the parent process handles the scan queue
 * and the child process handles the actual scanning of each item queued for
 * scanning, if the child process crashes, the parent process can simply create
 * a new child process to continue iterating through the queue).
 *
 * There are some additional benefits to be had by scanning in this way, such
 * as the ability to kill a child process, responsible for the actual scanning,
 * and yet still have the results of this scanning logged (which, if killed in
 * this way prior to completing the scan, would likely involve some type of
 * error).
 *
 * @param string $f The name of the item to be scanned, with path included.
 * @param string $ofn The name of the item to be scanned, without any path
 *      included (so, just the name by itself).
 * @return string The scan results, piped back to the parent from the child
 *      process and returned to the calling function as a string.
 */
$phpMussel['Fork'] = function ($f = '', $ofn = '') use (&$phpMussel) {
    $pf = popen(
        $phpMussel['Mussel_PHP'] . ' "' . $phpMussel['Vault'] .
        '../loader.php" "cli_scan" "' . $f . '" "' . $ofn . '"',
        'r'
    );
    $s = '';
    while ($x = fgets($pf)) {
        $s .= $x;
    }
    pclose($pf);
    return $s;
};

/** Assigns an array to use for dumping scan debug information (optional). */
$phpMussel['Set-Scan-Debug-Array'] = function (&$Var) use (&$phpMussel) {
    if (isset($phpMussel['DebugArr'])) {
        unset($phpMussel['DebugArr']);
    }
    if (!is_array($Var)) {
        $Var = [];
    }
    $phpMussel['DebugArr'] = &$Var;
};

/** Destroys the scan debug array (optional). */
$phpMussel['Destroy-Scan-Debug-Array'] = function (&$Var) use (&$phpMussel) {
    unset($phpMussel['DebugArrKey'], $phpMussel['DebugArr']);
    $Var = null;
};

/**
 * The main scan closure, responsible for initialising scans in most
 * circumstances. Should generally be called whenever phpMussel is
 * required by external scripts, apps, CMS, etc.
 *
 * Please refer to Section 3A of the README documentation, "HOW TO USE (FOR WEB
 * SERVERS)", for more information.
 *
 * @param string|array $f Indicates which file, files, directory, or
 *      directories to scan (can be a string, an array, or a multidimensional
 *      array).
 * @param bool $n A boolean, indicating the format for the scan results to be
 *      returned as. False instructs the function to return the results as an
 *      integer; True instructs the function to return the results as human
 *      readable text. Optional; Defaults to false.
 * @param bool $zz A boolean, indicating to the function whether or not arrayed
 *      results should be imploded prior to being returned to the calling
 *      function. False instructs the function to return the arrayed results as
 *      verbatim; True instructs the function to return the arrayed results as
 *      an imploded string. Optional; Defaults to false.
 * @param int $dpt Represents the current depth of recursion from which the
 *      function has been called. This information is used for determining how
 *      far to indent any entries generated for logging (you should never
 *      manually set this parameter yourself).
 * @param string $ofn For the file upload scanning that phpMussel normally
 *      performs by default, this parameter represents the "original filename"
 *      of the file being scanned (the original filename, in this context,
 *      referring to the name supplied by the upload client, as opposed to the
 *      temporary filename assigned by the server or anything else).
 * @return bool|int|string|array The scan results, returned as an array when
 *      the $f parameter is an array and when $n and/or $zz is/are false, and
 *      otherwise returned as per described by the README documentation. The
 *      function may also die the script and return nothing, if something goes
 *      wrong, such as if the function is triggered in the absence of the
 *      required $phpMussel['InstanceCache'] variable being set, and may also return
 *      false, in the absence of the required $phpMussel['HashCache']['Data']
 *      variable being set.
 */
$phpMussel['Scan'] = function ($f = '', $n = false, $zz = false, $dpt = 0, $ofn = '') use (&$phpMussel) {
    if (!isset($phpMussel['InstanceCache'])) {
        throw new \Exception($phpMussel['L10N']->getString(
            'required_variables_not_defined'
        ) ?: '[phpMussel] Required variables aren\'t defined: Can\'t continue.');
    }

    /** Prepare signature files for the scan process. */
    if (empty($phpMussel['InstanceCache']['OrganisedSigFiles'])) {
        $phpMussel['OrganiseSigFiles']();
        $phpMussel['InstanceCache']['OrganisedSigFiles'] = true;
    }

    /** Initialise statistics if they've been enabled. */
    $phpMussel['Stats-Initialise']();

    if ($phpMussel['EOF']) {
        $phpMussel['PrepareHashCache']();
    }
    if (!isset($phpMussel['HashCache']['Data'])) {
        return false;
    }
    if (!$ofn) {
        $ofn = $f;
    }
    $xst = time() + ($phpMussel['Config']['general']['timeOffset'] * 60);
    $xst2822 = $phpMussel['TimeFormat']($xst, $phpMussel['Config']['general']['timeFormat']);
    try {
        $r = $phpMussel['Recursor']($f, $n, $zz, $dpt, $ofn);
    } catch (\Exception $e) {
        throw new \Exception($e->getMessage());
    }
    $xet = time() + ($phpMussel['Config']['general']['timeOffset'] * 60);
    $xet2822 = $phpMussel['TimeFormat']($xet, $phpMussel['Config']['general']['timeFormat']);

    /** Plugin hook: "after_scan". */
    $phpMussel['Execute_Hook']('after_scan');

    if ($n && !is_array($r)) {
        $r =
            $xst2822 . ' ' . $phpMussel['L10N']->getString('started') .
            $phpMussel['L10N']->getString('_fullstop_final') . "\n" .
            $r . $xet2822 . ' ' . $phpMussel['L10N']->getString('finished') .
            $phpMussel['L10N']->getString('_fullstop_final') . "\n";
        $phpMussel['WriteScanLog']($r);
    }
    if (!isset($phpMussel['SkipSerial'])) {
        $phpMussel['WriteSerial']($xst, $xet);
    }
    if ($phpMussel['EOF']) {
        if ($phpMussel['Config']['general']['scan_cache_expiry'] > 0) {
            foreach ($phpMussel['HashCache']['Data'] as &$phpMussel['ThisItem']) {
                if (is_array($phpMussel['ThisItem'])) {
                    $phpMussel['ThisItem'] = implode(':', $phpMussel['ThisItem']) . ';';
                }
            }

            /** Update hash cache. */
            $phpMussel['SaveCache'](
                'HashCache',
                $phpMussel['Time'] + $phpMussel['Config']['general']['scan_cache_expiry'],
                implode('', $phpMussel['HashCache']['Data'])
            );
            unset($phpMussel['ThisItem'], $phpMussel['HashCache']['Data']);
        }
    }

    /** Register scan event. */
    $phpMussel['Stats-Increment']($phpMussel['EOF'] ? 'API-Events' : 'Web-Events', 1);

    /** Update statistics. */
    if (!empty($phpMussel['CacheModified'])) {
        $phpMussel['Statistics'] = $phpMussel['SaveCache']('Statistics', -1, serialize($phpMussel['Statistics']));
    }

    /** Exit scan process. */
    return $r;
};

/**
 * Writes to the serialized logfile upon scan completion.
 *
 * @param string $StartTime When the scan started.
 * @param string $FinishTime When the scan finished.
 * @return bool True on success; False on failure.
 */
$phpMussel['WriteSerial'] = function ($StartTime = '', $FinishTime = '') use (&$phpMussel) {
    if ($phpMussel['Mussel_sapi']) {
        $Origin = 'CLI';
    } else {
        $Origin = $phpMussel['Config']['legal']['pseudonymise_ip_addresses'] ? $phpMussel['Pseudonymise-IP'](
            $_SERVER[$phpMussel['IPAddr']]
        ) : $_SERVER[$phpMussel['IPAddr']];
    }
    $ScanData = empty($phpMussel['whyflagged']) ? $phpMussel['L10N']->getString('data_not_available') : trim($phpMussel['whyflagged']);
    if ($phpMussel['Config']['general']['scan_log_serialized']) {
        if (!isset($phpMussel['InstanceCache']['objects_scanned'])) {
            $phpMussel['InstanceCache']['objects_scanned'] = 0;
        }
        if (!isset($phpMussel['InstanceCache']['detections_count'])) {
            $phpMussel['InstanceCache']['detections_count'] = 0;
        }
        if (!isset($phpMussel['InstanceCache']['scan_errors'])) {
            $phpMussel['InstanceCache']['scan_errors'] = 1;
        }
        $Handle = [
            'Data' => serialize([
                'start_time' => $StartTime ?: (isset($phpMussel['InstanceCache']['start_time']) ? $phpMussel['InstanceCache']['start_time'] : '-'),
                'end_time' => $FinishTime ?: (isset($phpMussel['InstanceCache']['end_time']) ? $phpMussel['InstanceCache']['end_time'] : '-'),
                'origin' => $Origin,
                'objects_scanned' => $phpMussel['InstanceCache']['objects_scanned'],
                'detections_count' => $phpMussel['InstanceCache']['detections_count'],
                'scan_errors' => $phpMussel['InstanceCache']['scan_errors'],
                'detections' => $ScanData
            ]) . "\n",
            'File' => $phpMussel['TimeFormat']($phpMussel['Time'], $phpMussel['Config']['general']['scan_log_serialized'])
        ];
        $WriteMode = (!file_exists($phpMussel['Vault'] . $Handle['File']) || (
            $phpMussel['Config']['general']['truncate'] > 0 &&
            filesize($phpMussel['Vault'] . $Handle['File']) >= $phpMussel['ReadBytes']($phpMussel['Config']['general']['truncate'])
        )) ? 'w' : 'a';
        if ($phpMussel['BuildLogPath']($Handle['File'])) {
            $Stream = fopen($phpMussel['Vault'] . $Handle['File'], $WriteMode);
            fwrite($Stream, $Handle['Data']);
            fclose($Stream);
            if ($WriteMode === 'w') {
                $phpMussel['LogRotation']($phpMussel['Config']['general']['scan_log_serialized']);
            }
            return true;
        }
    }
    return false;
};

/**
 * Writes to the standard scan log upon scan completion.
 *
 * @param string $Data What to write.
 * @return bool True on success; False on failure.
 */
$phpMussel['WriteScanLog'] = function ($Data) use (&$phpMussel) {
    if ($phpMussel['Config']['general']['scan_log']) {
        $File = $phpMussel['TimeFormat']($phpMussel['Time'], $phpMussel['Config']['general']['scan_log']);
        if (!file_exists($phpMussel['Vault'] . $File)) {
            $Data = $phpMussel['safety'] . "\n" . $Data;
        }
        $WriteMode = (!file_exists($phpMussel['Vault'] . $File) || (
            $phpMussel['Config']['general']['truncate'] > 0 &&
            filesize($phpMussel['Vault'] . $File) >= $phpMussel['ReadBytes']($phpMussel['Config']['general']['truncate'])
        )) ? 'w' : 'a';
        if ($phpMussel['BuildLogPath']($File)) {
            $Handle = fopen($phpMussel['Vault'] . $File, 'a');
            fwrite($Handle, $Data);
            fclose($Handle);
            if ($WriteMode === 'w') {
                $phpMussel['LogRotation']($phpMussel['Config']['general']['scan_log']);
            }
            return true;
        }
    }
    return false;
};

/**
 * A simple closure for replacing date/time placeholders with corresponding
 * date/time information. Used by the logfiles and some timestamps.
 *
 * @param int $Time A unix timestamp.
 * @param string|array $In An input or an array of inputs to manipulate.
 * @return string|array The adjusted input(/s).
 */
$phpMussel['TimeFormat'] = function ($Time, $In) use (&$phpMussel) {
    $Time = date('dmYHisDMP', $Time);
    $values = [
        'dd' => substr($Time, 0, 2),
        'mm' => substr($Time, 2, 2),
        'yyyy' => substr($Time, 4, 4),
        'yy' => substr($Time, 6, 2),
        'hh' => substr($Time, 8, 2),
        'ii' => substr($Time, 10, 2),
        'ss' => substr($Time, 12, 2),
        'Day' => substr($Time, 14, 3),
        'Mon' => substr($Time, 17, 3),
        'tz' => substr($Time, 20, 3) . substr($Time, 24, 2),
        't:z' => substr($Time, 20, 6)
    ];
    $values['d'] = (int)$values['dd'];
    $values['m'] = (int)$values['mm'];
    return is_array($In) ? array_map(function ($Item) use (&$values, &$phpMussel) {
        return $phpMussel['ParseVars']($values, $Item);
    }, $In) : $phpMussel['ParseVars']($values, $In);
};

/**
 * Fix incorrect typecasting for some for some variables that sometimes default
 * to strings instead of booleans or integers.
 */
$phpMussel['AutoType'] = function (&$Var, $Type = '') use (&$phpMussel) {
    if ($Type === 'string' || $Type === 'timezone') {
        $Var = (string)$Var;
    } elseif ($Type === 'int' || $Type === 'integer') {
        $Var = (int)$Var;
    } elseif ($Type === 'real' || $Type === 'double' || $Type === 'float') {
        $Var = (float)$Var;
    } elseif ($Type === 'bool' || $Type === 'boolean') {
        $Var = (strtolower($Var) !== 'false' && $Var);
    } elseif ($Type === 'kb') {
        $Var = $phpMussel['ReadBytes']($Var, 1);
    } else {
        $LVar = strtolower($Var);
        if ($LVar === 'true') {
            $Var = true;
        } elseif ($LVar === 'false') {
            $Var = false;
        } elseif ($Var !== true && $Var !== false) {
            $Var = (int)$Var;
        }
    }
};

/**
 * Used to send cURL requests.
 *
 * @param string $URI The resource to request.
 * @param array $Params (Optional) An associative array of key-value pairs to
 *      to send along with the request.
 * @return string The results of the request.
 */
$phpMussel['Request'] = function ($URI, $Params = '', $Timeout = '') use (&$phpMussel) {
    if (!$Timeout) {
        $Timeout = $phpMussel['Timeout'];
    }

    /** Initialise the cURL session. */
    $Request = curl_init($URI);

    $LCURI = strtolower($URI);
    $SSL = (substr($LCURI, 0, 6) === 'https:');

    curl_setopt($Request, CURLOPT_FRESH_CONNECT, true);
    curl_setopt($Request, CURLOPT_HEADER, false);
    if (empty($Params)) {
        curl_setopt($Request, CURLOPT_POST, false);
    } else {
        curl_setopt($Request, CURLOPT_POST, true);
        curl_setopt($Request, CURLOPT_POSTFIELDS, $Params);
    }
    if ($SSL) {
        curl_setopt($Request, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
        curl_setopt($Request, CURLOPT_SSL_VERIFYPEER, false);
    }
    curl_setopt($Request, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($Request, CURLOPT_MAXREDIRS, 1);
    curl_setopt($Request, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($Request, CURLOPT_TIMEOUT, $Timeout);
    curl_setopt($Request, CURLOPT_USERAGENT, $phpMussel['ScriptUA']);

    /** Execute and get the response. */
    $Response = curl_exec($Request);

    /** Close the cURL session. */
    curl_close($Request);

    /** Return the results of the request. */
    return $Response;
};

/**
 * Used to generate new salts when necessary, which may be occasionally used by
 * some specific optional peripheral features (note: should not be considered
 * cryptographically secure; especially so for versions of PHP < 7).
 *
 * @return string Salt.
 */
$phpMussel['GenerateSalt'] = function () {
    static $MinLen = 32;
    static $MaxLen = 72;
    static $MinChr = 1;
    static $MaxChr = 255;
    $Salt = '';
    if (function_exists('random_int')) {
        try {
            $Length = random_int($MinLen, $MaxLen);
        } catch (\Exception $e) {
            $Length = rand($MinLen, $MaxLen);
        }
    } else {
        $Length = rand($MinLen, $MaxLen);
    }
    if (function_exists('random_bytes')) {
        try {
            $Salt = random_bytes($Length);
        } catch (\Exception $e) {
            $Salt = '';
        }
    }
    if (empty($Salt)) {
        if (function_exists('random_int')) {
            try {
                for ($Index = 0; $Index < $Length; $Index++) {
                    $Salt .= chr(random_int($MinChr, $MaxChr));
                }
            } catch (\Exception $e) {
                $Salt = '';
                for ($Index = 0; $Index < $Length; $Index++) {
                    $Salt .= chr(rand($MinChr, $MaxChr));
                }
            }
        } else {
            for ($Index = 0; $Index < $Length; $Index++) {
                $Salt .= chr(rand($MinChr, $MaxChr));
            }
        }
    }
    return $Salt;
};

/**
 * Clears expired entries from a list.
 *
 * @param string $List The list to clear from.
 * @param bool $Check A flag indicating when changes have occurred.
 */
$phpMussel['ClearExpired'] = function (&$List, &$Check) use (&$phpMussel) {
    if ($List) {
        $End = 0;
        while (true) {
            $Begin = $End;
            if (!$End = strpos($List, "\n", $Begin + 1)) {
                break;
            }
            $Line = substr($List, $Begin, $End - $Begin);
            if ($Split = strrpos($Line, ',')) {
                $Expiry = (int)substr($Line, $Split + 1);
                if ($Expiry < $phpMussel['Time']) {
                    $List = str_replace($Line, '', $List);
                    $End = 0;
                    $Check = true;
                }
            }
        }
    }
};

/** Fetch information about signature files and prepare for use with the scan process. */
$phpMussel['OrganiseSigFiles'] = function () use (&$phpMussel) {

    $LastActive = $phpMussel['FetchCache']('Active') ?: '';
    if (empty($phpMussel['Config']['signatures']['Active'])) {
        if ($LastActive) {
            $phpMussel['ClearHashCache']();
        }
        return false;
    }
    if ($phpMussel['Config']['signatures']['Active'] !== $LastActive) {
        $phpMussel['ClearHashCache']();
        if (isset($phpMussel['Cache']) && $phpMussel['Cache']->Using) {
            $phpMussel['Cache']->deleteEntry('Active');
        } else {
            $phpMussel['SaveCache']('Active', -1, $phpMussel['Config']['signatures']['Active']);
        }
    }

    $Classes = [
        'General_Command_Detections',
        'Filename',
        'Hash',
        'Standard',
        'Standard_RegEx',
        'Normalised',
        'Normalised_RegEx',
        'HTML',
        'HTML_RegEx',
        'PE_Extended',
        'PE_Sectional',
        'Complex_Extended',
        'URL_Scanner'
    ];

    $List = explode(',', $phpMussel['Config']['signatures']['Active']);
    foreach ($List as $File) {
        $File = (strpos($File, ':') === false) ? $File : substr($File, strpos($File, ':') + 1);
        $Handle = fopen($phpMussel['sigPath'] . $File, 'rb');
        if (fread($Handle, 9) !== 'phpMussel') {
            fclose($Handle);
            continue;
        }
        $Class = fread($Handle, 1);
        fclose($Handle);
        $Nibbles = $phpMussel['split_nibble']($Class);
        if (!empty($Classes[$Nibbles[0]])) {
            if (!isset($phpMussel['InstanceCache'][$Classes[$Nibbles[0]]])) {
                $phpMussel['InstanceCache'][$Classes[$Nibbles[0]]] = ',';
            }
            $phpMussel['InstanceCache'][$Classes[$Nibbles[0]]] .= $File . ',';
        }
    }

};

/** A simple safety wrapper for unpack. */
$phpMussel['UnpackSafe'] = function ($Format, $Data) {
    return (strlen($Data) > 1) ? unpack($Format, $Data) : '';
};

/** A simple safety wrapper for hex2bin. */
$phpMussel['HexSafe'] = function ($Data) use (&$phpMussel) {
    return ($Data && !preg_match('/[^\da-f]/i', $Data) && !(strlen($Data) % 2)) ? $phpMussel['Function']('HEX', $Data) : '';
};

/** If input isn't an array, make it so. Remove empty elements. */
$phpMussel['Arrayify'] = function (&$Input) {
    if (!is_array($Input)) {
        $Input = [$Input];
    }
    $Input = array_filter($Input);
};

/**
 * Read byte value configuration directives as byte values.
 *
 * @param string $In Input.
 * @param int $Mode Operating mode. 0 for true byte values, 1 for validating.
 *      Default is 0.
 * @return string|int Output (depends on operating mode).
 */
$phpMussel['ReadBytes'] = function ($In, $Mode = 0) {
    if (preg_match('/[KMGT][oB]$/i', $In)) {
        $Unit = substr($In, -2, 1);
    } elseif (preg_match('/[KMGToB]$/i', $In)) {
        $Unit = substr($In, -1);
    }
    $Unit = isset($Unit) ? strtoupper($Unit) : 'K';
    $In = (float)$In;
    if ($Mode === 1) {
        return $Unit === 'B' || $Unit === 'o' ? $In . 'B' : $In . $Unit . 'B';
    }
    $Multiply = ['K' => 1024, 'M' => 1048576, 'G' => 1073741824, 'T' => 1099511627776];
    return (int)floor($In * (isset($Multiply[$Unit]) ? $Multiply[$Unit] : 1));
};

/**
 * Improved recursive directory iterator for phpMussel.
 *
 * @param string $Base Directory root.
 * @return array Directory tree.
 */
$phpMussel['DirectoryRecursiveList'] = function ($Base) {
    $Arr = [];
    $Offset = strlen($Base);
    $List = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($Base), RecursiveIteratorIterator::SELF_FIRST);
    foreach ($List as $Item => $List) {
        if (preg_match('~^(?:/\.\.|./\.|\.{3})$~', str_replace("\\", '/', substr($Item, -3))) || !is_readable($Item)) {
            continue;
        }
        $Arr[] = substr($Item, $Offset);
    }
    return $Arr;
};

/**
 * Duplication avoidance (forking the process via recursive CLI mode commands).
 *
 * @param string $Command Command with parameters for the action to be taken.
 * @param callable $Callable Executed normally when not forking the process.
 * @return string Returnable data to be echoed to the CLI output.
 */
$phpMussel['CLI-RecursiveCommand'] = function ($Command, $Callable) use (&$phpMussel) {
    $Params = substr($Command, strlen($phpMussel['cmd']) + 1);
    if (is_dir($Params)) {
        if (!is_readable($Params)) {
            return sprintf($phpMussel['L10N']->getString('failed_to_access'), $Params) . "\n";
        }
        $Decal = [':-) - (-:', ':-) \\ (-:', ':-) | (-:', ':-) / (-:'];
        $Frame = 0;
        $Terminal = $Params[strlen($Params) - 1];
        if ($Terminal !== "\\" && $Terminal !== '/') {
            $Params .= '/';
        }
        $List = $phpMussel['DirectoryRecursiveList']($Params);
        $Returnable = '';
        foreach ($List as $Item) {
            echo "\r" . $Decal[$Frame];
            $Returnable .= $phpMussel['Fork']($phpMussel['cmd'] . ' ' . $Params . $Item, $Item) . "\n";
            $Frame = $Frame < 3 ? $Frame + 1 : 0;
        }
        echo "\r         ";
        return $Returnable;
    }
    return is_file($Params) ? $Callable($Params) : sprintf($phpMussel['L10N']->getString('cli_is_not_a'), $Params) . "\n";
};

/** Handles errors (will expand this later). */
$phpMussel['ErrorHandler_1'] = function ($errno) use (&$phpMussel) {
    return;
};

/** Duplication avoidance (some file handling for honeypot functionality). */
$phpMussel['ReadFile-For-Honeypot'] = function (&$Array, $File) use (&$phpMussel) {
    if (!isset($Array['qdata'])) {
        $Array['qdata'] = '';
    }
    $Array['odata'] = $phpMussel['ReadFile']($File);
    $Array['len'] = strlen($Array['odata']);
    $Array['crc'] = hash('crc32b', $Array['odata']);
    $Array['qfile'] = $phpMussel['Time'] . '-' . md5($phpMussel['Config']['general']['quarantine_key'] . $Array['crc'] . $phpMussel['Time']);
    if ($Array['len'] > 0 && $Array['len'] < $phpMussel['ReadBytes']($phpMussel['Config']['general']['quarantine_max_filesize'])) {
        $phpMussel['Quarantine'](
            $Array['odata'],
            $phpMussel['Config']['general']['quarantine_key'],
            $_SERVER[$phpMussel['IPAddr']],
            $Array['qfile']
        );
    }
    if ($phpMussel['Config']['general']['delete_on_sight'] && is_readable($File)) {
        unlink($File);
    }
    $Array['qdata'] .= sprintf(
        'TEMP FILENAME: %1$s%6$sFILENAME: %2$s%6$sFILESIZE: %3$s%6$sMD5: %4$s%6$s%5$s',
        $File,
        urlencode($File),
        $Array['len'],
        md5($Array['odata']),
        sprintf($phpMussel['L10N']->getString('quarantined_as'), $Array['qfile']),
        "\n"
    );
};

/** Duplication avoidance (assigning kill details and unlinking files). */
$phpMussel['KillAndUnlink'] = function () use (&$phpMussel) {
    $phpMussel['killdata'] .=
        '-UPLOAD-LIMIT-EXCEEDED--NO-HASH-:' .
        $phpMussel['upload']['FilesData']['FileSet']['size'][$phpMussel['ThisIter']] . ':' .
        $phpMussel['upload']['FilesData']['FileSet']['name'][$phpMussel['ThisIter']] . "\n";
    $phpMussel['whyflagged'] .= sprintf($phpMussel['L10N']->getString('_exclamation'),
        $phpMussel['L10N']->getString('upload_limit_exceeded') .
        ' (' . $phpMussel['upload']['FilesData']['FileSet']['name'][$phpMussel['ThisIter']] . ')'
    );
    if (
        $phpMussel['Config']['general']['delete_on_sight'] &&
        is_uploaded_file($phpMussel['upload']['FilesData']['FileSet']['tmp_name'][$phpMussel['ThisIter']]) &&
        is_readable($phpMussel['upload']['FilesData']['FileSet']['tmp_name'][$phpMussel['ThisIter']]) &&
        !$phpMussel['Config']['general']['honeypot_mode']
    ) {
        unlink($phpMussel['upload']['FilesData']['FileSet']['tmp_name'][$phpMussel['ThisIter']]);
    }
};

/** Increments statistics if they've been enabled. */
$phpMussel['Stats-Increment'] = function ($Statistic, $Amount) use (&$phpMussel) {
    if ($phpMussel['Config']['general']['statistics'] && isset($phpMussel['Statistics'][$Statistic])) {
        $phpMussel['Statistics'][$Statistic] += $Amount;
        $phpMussel['CacheModified'] = true;
    }
};

/** Initialise statistics if they've been enabled. */
$phpMussel['Stats-Initialise'] = function () use (&$phpMussel) {
    if ($phpMussel['Config']['general']['statistics']) {
        $phpMussel['CacheModified'] = false;

        if ($phpMussel['Statistics'] = ($phpMussel['FetchCache']('Statistics') ?: [])) {
            if (is_string($phpMussel['Statistics'])) {
                unserialize($phpMussel['Statistics']) ?: [];
            }
        }

        if (empty($phpMussel['Statistics']['Other-Since'])) {
            $phpMussel['Statistics'] = [
                'Other-Since' => $phpMussel['Time'],
                'Web-Events' => 0,
                'Web-Scanned' => 0,
                'Web-Blocked' => 0,
                'Web-Quarantined' => 0,
                'CLI-Events' => 0,
                'CLI-Scanned' => 0,
                'CLI-Flagged' => 0,
                'API-Events' => 0,
                'API-Scanned' => 0,
                'API-Flagged' => 0
            ];
            $phpMussel['CacheModified'] = true;
        }
    }
};

/** Clears out the hash cache. */
$phpMussel['ClearHashCache'] = function () use (&$phpMussel) {
    $phpMussel['InitialiseCache']();

    /** Override if using a different preferred caching mechanism. */
    if ($phpMussel['Cache']->Using) {
        return $phpMussel['Cache']->deleteEntry('HashCache');
    }

    /** Default process. */
    $File = $phpMussel['cachePath'] . '48.tmp';
    $Data = $phpMussel['ReadFile']($File) ?: '';
    while (strpos($Data, 'HashCache:') !== false) {
        $Data = str_ireplace('HashCache:' . $phpMussel['substrbf']($phpMussel['substraf']($Data, 'HashCache:'), ';') . ';', '', $Data);
    }
    if (strlen($Data) < 2) {
        unset($File);
    } else {
        $Handle = fopen($File, 'w');
        fwrite($Handle, $Data);
        fclose($Handle);
    }
    $IndexFile = $phpMussel['cachePath'] . 'index.dat';
    $IndexNewData = $IndexData = $phpMussel['ReadFile']($IndexFile) ?: '';
    while (strpos($IndexNewData, 'HashCache:') !== false) {
        $IndexNewData = str_ireplace('HashCache:' . $phpMussel['substrbf']($phpMussel['substraf']($IndexNewData, 'HashCache:'), ';') . ';', '', $IndexNewData);
    }
    if ($IndexNewData !== $IndexData) {
        $IndexHandle = fopen($IndexFile, 'w');
        fwrite($IndexHandle, $IndexNewData);
        fclose($IndexHandle);
    }
    return true;
};

/**
 * Build directory path for logfiles.
 *
 * @param string $File The file we're building for.
 * @return bool True on success; False on failure.
 */
$phpMussel['BuildLogPath'] = function ($File) use (&$phpMussel) {
    $ThisPath = $phpMussel['Vault'];
    $File = str_replace("\\", '/', $File);
    while (strpos($File, '/') !== false) {
        $Dir = substr($File, 0, strpos($File, '/'));
        $ThisPath .= $Dir . '/';
        $File = substr($File, strlen($Dir) + 1);
        if (!file_exists($ThisPath) || !is_dir($ThisPath)) {
            if (!mkdir($ThisPath)) {
                return false;
            }
        }
    }
    return true;
};

/**
 * Checks whether the specified directory is empty.
 *
 * @param string $Directory The directory to check.
 * @return bool True if empty; False if not empty.
 */
$phpMussel['IsDirEmpty'] = function ($Directory) {
    return !((new \FilesystemIterator($Directory))->valid());
};

/**
 * Deletes empty directories (used by some front-end functions and log rotation).
 *
 * @param string $Dir The directory to delete.
 */
$phpMussel['DeleteDirectory'] = function ($Dir) use (&$phpMussel) {
    while (strrpos($Dir, '/') !== false || strrpos($Dir, "\\") !== false) {
        $Separator = (strrpos($Dir, '/') !== false) ? '/' : "\\";
        $Dir = substr($Dir, 0, strrpos($Dir, $Separator));
        if (!is_dir($phpMussel['Vault'] . $Dir) || !$phpMussel['IsDirEmpty']($phpMussel['Vault'] . $Dir)) {
            break;
        }
        rmdir($phpMussel['Vault'] . $Dir);
    }
};

/** Convert configuration directives for logfiles to regexable patterns. */
$phpMussel['BuildLogPattern'] = function ($Str, $GZ = false) {
    return '~^' . preg_replace(
        ['~\\\{(?:dd|mm|yy|hh|ii|ss)\\\}~i', '~\\\{yyyy\\\}~i', '~\\\{(?:Day|Mon)\\\}~i', '~\\\{tz\\\}~i', '~\\\{t\\\:z\\\}~i'],
        ['\d{2}', '\d{4}', '\w{3}', '.{1,2}\d{4}', '.{1,2}\d{2}\:\d{2}'],
        preg_quote(str_replace("\\", '/', $Str))
    ) . ($GZ ? '(?:\.gz)?' : '') . '$~i';
};

/**
 * GZ-compress a file (used by log rotation).
 *
 * @param string $File The file to GZ-compress.
 * @return bool True if the file exists and is readable; False otherwise.
 */
$phpMussel['GZCompressFile'] = function ($File) {
    if (!is_file($File) || !is_readable($File)) {
        return false;
    }
    static $Blocksize = 131072;
    $Filesize = filesize($File);
    $Size = ($Filesize && $Blocksize) ? ceil($Filesize / $Blocksize) : 0;
    if ($Size > 0) {
        $Handle = fopen($File, 'rb');
        $HandleGZ = gzopen($File . '.gz', 'wb');
        $Block = 0;
        while ($Block < $Size) {
            $Data = fread($Handle, $Blocksize);
            gzwrite($HandleGZ, $Data);
            $Block++;
        }
        gzclose($HandleGZ);
        fclose($Handle);
    }
    return true;
};

/**
 * Log rotation.
 *
 * @param string $Pattern What to identify logfiles by (should be supplied via the relevant logging directive).
 * @return bool False when log rotation is disabled or errors occur; True otherwise.
 */
$phpMussel['LogRotation'] = function ($Pattern) use (&$phpMussel) {
    $Action = empty($phpMussel['Config']['general']['log_rotation_action']) ? '' : $phpMussel['Config']['general']['log_rotation_action'];
    $Limit = empty($phpMussel['Config']['general']['log_rotation_limit']) ? 0 : $phpMussel['Config']['general']['log_rotation_limit'];
    if (!$Limit || ($Action !== 'Delete' && $Action !== 'Archive')) {
        return false;
    }
    $Pattern = $phpMussel['BuildLogPattern']($Pattern);
    $Arr = [];
    $Offset = strlen($phpMussel['Vault']);
    $List = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($phpMussel['Vault']), RecursiveIteratorIterator::SELF_FIRST);
    foreach ($List as $Item => $List) {
        $ItemFixed = str_replace("\\", '/', substr($Item, $Offset));
        if ($ItemFixed && preg_match($Pattern, $ItemFixed) && is_readable($Item)) {
            $Arr[$ItemFixed] = filemtime($Item);
        }
    }
    unset($ItemFixed, $List, $Offset);
    $Count = count($Arr);
    $Err = 0;
    if ($Count > $Limit) {
        asort($Arr, SORT_NUMERIC);
        foreach ($Arr as $Item => $Modified) {
            if ($Action === 'Archive') {
                $Err += !$phpMussel['GZCompressFile']($phpMussel['Vault'] . $Item);
            }
            $Err += !unlink($phpMussel['Vault'] . $Item);
            if (strpos($Item, '/') !== false) {
                $phpMussel['DeleteDirectory']($Item);
            }
            $Count--;
            if (!($Count > $Limit)) {
                break;
            }
        }
    }
    return $Err ? false : true;
};

/**
 * Pseudonymise an IP address (reduce IPv4s to /24s and IPv6s to /32s).
 *
 * @param string $IP An IP address.
 * @return string A pseudonymised IP address.
 */
$phpMussel['Pseudonymise-IP'] = function ($IP) {
    if (($CPos = strpos($IP, ':')) !== false) {
        $Parts = [(substr($IP, 0, $CPos) ?: ''), (substr($IP, $CPos +1) ?: '')];
        if (($CPos = strpos($Parts[1], ':')) !== false) {
            $Parts[1] = substr($Parts[1], 0, $CPos) ?: '';
        }
        $Parts = $Parts[0] . ':' . $Parts[1] . '::x';
        return str_replace(':::', '::', $Parts);
    }
    return preg_replace(
        '/^([01]?\d{1,2}|2[0-4]\d|25[0-5])\.([01]?\d{1,2}|2[0-4]\d|25[0-5])\.([01]?\d{1,2}|2[0-4]\d|25[0-5])\.([01]?\d{1,2}|2[0-4]\d|25[0-5])$/i',
        '\1.\2.\3.x',
        $IP
    );
};

/** Initialise cache. */
$phpMussel['InitialiseCache'] = function () use (&$phpMussel) {

    /** Exit early if already initialised. */
    if (isset($phpMussel['Cache'])) {
        return;
    }

    /** Create new cache object. */
    $phpMussel['Cache'] = new \Maikuolan\Common\Cache();
    $phpMussel['Cache']->EnableAPCu = $phpMussel['Config']['supplementary_cache_options']['enable_apcu'];
    $phpMussel['Cache']->EnableMemcached = $phpMussel['Config']['supplementary_cache_options']['enable_memcached'];
    $phpMussel['Cache']->EnableRedis = $phpMussel['Config']['supplementary_cache_options']['enable_redis'];
    $phpMussel['Cache']->EnablePDO = $phpMussel['Config']['supplementary_cache_options']['enable_pdo'];
    $phpMussel['Cache']->MemcachedHost = $phpMussel['Config']['supplementary_cache_options']['memcached_host'];
    $phpMussel['Cache']->MemcachedPort = $phpMussel['Config']['supplementary_cache_options']['memcached_port'];
    $phpMussel['Cache']->RedisHost = $phpMussel['Config']['supplementary_cache_options']['redis_host'];
    $phpMussel['Cache']->RedisPort = $phpMussel['Config']['supplementary_cache_options']['redis_port'];
    $phpMussel['Cache']->RedisTimeout = $phpMussel['Config']['supplementary_cache_options']['redis_timeout'];
    $phpMussel['Cache']->PDOdsn = $phpMussel['Config']['supplementary_cache_options']['pdo_dsn'];
    $phpMussel['Cache']->PDOusername = $phpMussel['Config']['supplementary_cache_options']['pdo_username'];
    $phpMussel['Cache']->PDOpassword = $phpMussel['Config']['supplementary_cache_options']['pdo_password'];
    $phpMussel['Cache']->connect();
};
For more information send a message to info at phpclasses dot org.