diff --git a/e107_handlers/e_file_inspector.php b/e107_handlers/e_file_inspector.php index fc566002c..d167aaabf 100644 --- a/e107_handlers/e_file_inspector.php +++ b/e107_handlers/e_file_inspector.php @@ -16,77 +16,81 @@ require_once("e_file_inspector_interface.php"); */ abstract class e_file_inspector implements e_file_inspector_interface { - /** - * Check the integrity of the provided path - * - * @param $path string Relative path of the file to look up - * @param $version string The desired software release to match. - * Leave blank for the current version. - * Do not prepend the version number with "v". - * @return int Validation code (see the constants of this class) - */ - public function validate($path, $version = null) - { - if ($version === null) $version = $this->getCurrentVersion(); - $absolutePath = realpath($path); - $actualChecksum = $this->checksumPath($absolutePath); - $dbChecksum = $this->getChecksum($path, $version); + private $validatedBitmask; - if ($dbChecksum === false) return self::VALIDATION_IGNORE; - if (!file_exists($absolutePath)) return self::VALIDATION_MISSING; - if ($this->isInsecure($path)) return self::VALIDATION_INSECURE; - if ($actualChecksum === false) return self::VALIDATION_INCALCULABLE; - if ($actualChecksum === $dbChecksum) return self::VALIDATION_PASS; - - foreach ($this->getChecksums($path) as $dbVersion => $dbChecksum) - { - if (version_compare($dbVersion, $version, ">=")) continue; - - if ($dbChecksum === $actualChecksum) return self::VALIDATION_OLD; - } - - return self::VALIDATION_FAIL; - } - - /** - * Get the file integrity hash for the provided path and version - * - * @param $path string Relative path of the file to look up - * @param $version string The software release version corresponding to the file hash. - * Leave blank for the current version. - * Do not prepend the version number with "v". - * @return string|bool The database hash for the path and version specified. FALSE if the record does not exist. - */ - public function getChecksum($path, $version = null) - { + /** + * Check the integrity of the provided path + * + * @param $path string Relative path of the file to look up + * @param $version string The desired software release to match. + * Leave blank for the current version. + * Do not prepend the version number with "v". + * @return int Validation code (see the constants of this class) + */ + public function validate($path, $version = null) + { if ($version === null) $version = $this->getCurrentVersion(); - $checksums = $this->getChecksums($path); - return isset($checksums[$version]) ? $checksums[$version] : false; - } - /** - * Calculate the hash of a path to compare with the hash database - * - * @param $absolutePath string Absolute path of the file to hash - * @return string|bool The actual hash for the path. FALSE if the hash was incalculable. - */ - public function checksumPath($absolutePath) - { - if (!is_file($absolutePath) || !is_readable($absolutePath)) return false; + $bits = 0x0; + $absolutePath = realpath(e_BASE . $path); + $actualChecksum = $this->checksumPath($absolutePath); + $dbChecksum = $this->getChecksum($path, $version); - return $this->checksum(file_get_contents($absolutePath)); - } + if ($dbChecksum !== false) $bits |= self::VALIDATED_RELEVANCE; + if (file_exists($absolutePath)) $bits |= self::VALIDATED_PRESENCE; + if (!$this->isInsecure($path)) $bits |= self::VALIDATED_SECURITY; + if ($actualChecksum !== false) $bits |= self::VALIDATED_DETERMINABLE; + if ($actualChecksum === $dbChecksum) $bits |= self::VALIDATED_UPTODATE; - /** - * Calculate the hash of a string, which would be used to compare with the hash database - * - * @param $content string Full content to hash - * @return string - */ - public function checksum($content) - { - return md5(str_replace(array(chr(13),chr(10)), "", $content)); - } + foreach ($this->getChecksums($path) as $dbVersion => $dbChecksum) + { + if ($dbChecksum === $actualChecksum) $bits |= self::VALIDATED_HASH; + } + + if ($bits + 0x1 === $this->getValidatedBitmask()) $bits |= self::VALIDATED; + + return $bits; + } + + /** + * Get the file integrity hash for the provided path and version + * + * @param $path string Relative path of the file to look up + * @param $version string The software release version corresponding to the file hash. + * Leave blank for the current version. + * Do not prepend the version number with "v". + * @return string|bool The database hash for the path and version specified. FALSE if the record does not exist. + */ + public function getChecksum($path, $version = null) + { + if ($version === null) $version = $this->getCurrentVersion(); + $checksums = $this->getChecksums($path); + return isset($checksums[$version]) ? $checksums[$version] : false; + } + + /** + * Calculate the hash of a path to compare with the hash database + * + * @param $absolutePath string Absolute path of the file to hash + * @return string|bool The actual hash for the path. FALSE if the hash was incalculable. + */ + public function checksumPath($absolutePath) + { + if (!is_file($absolutePath) || !is_readable($absolutePath)) return false; + + return $this->checksum(file_get_contents($absolutePath)); + } + + /** + * Calculate the hash of a string, which would be used to compare with the hash database + * + * @param $content string Full content to hash + * @return string + */ + public function checksum($content) + { + return md5(str_replace(array(chr(13), chr(10)), "", $content)); + } /** * @inheritDoc @@ -108,22 +112,22 @@ abstract class e_file_inspector implements e_file_inspector_interface } /** - * Get the matching version of the provided path - * - * Useful for looking up the versions of old files that no longer exist in the latest image - * - * @param $path string Relative path of the file to look up - * @return string|bool PHP-standardized version of the file. FALSE if there is no match. - */ - public function getVersion($path) - { - $actualChecksum = $this->checksumPath($path); - foreach ($this->getChecksums($path) as $dbVersion => $dbChecksum) - { - if ($actualChecksum === $dbChecksum) return $dbVersion; - } - return false; - } + * Get the matching version of the provided path + * + * Useful for looking up the versions of old files that no longer exist in the latest image + * + * @param $path string Relative path of the file to look up + * @return string|bool PHP-standardized version of the file. FALSE if there is no match. + */ + public function getVersion($path) + { + $actualChecksum = $this->checksumPath($path); + foreach ($this->getChecksums($path) as $dbVersion => $dbChecksum) + { + if ($actualChecksum === $dbChecksum) return $dbVersion; + } + return false; + } /** * @inheritDoc @@ -133,4 +137,18 @@ abstract class e_file_inspector implements e_file_inspector_interface # TODO return false; } + + private function getValidatedBitmask() + { + if ($this->validatedBitmask !== null) return $this->validatedBitmask; + $constants = (new ReflectionClass(self::class))->getConstants(); + $validated_constants = array_filter($constants, function ($key) + { + $str = 'VALIDATED_'; + return substr($key, 0, strlen($str)) === $str; + }, ARRAY_FILTER_USE_KEY); + + $this->validatedBitmask = (max($validated_constants) << 0x1) - 0x1; + return $this->validatedBitmask; + } } \ No newline at end of file diff --git a/e107_handlers/e_file_inspector_interface.php b/e107_handlers/e_file_inspector_interface.php index 721ca1e12..1e370b83f 100644 --- a/e107_handlers/e_file_inspector_interface.php +++ b/e107_handlers/e_file_inspector_interface.php @@ -9,34 +9,41 @@ interface e_file_inspector_interface { - /** - * The file is present, and its hash matches the specified version. - */ - const VALIDATION_PASS = 1; - /** - * The file is present, but the hash does not match the specified version. VALIDATION_OLD takes precedence. - */ - const VALIDATION_FAIL = 2; - /** - * The file is absent, but a hash exists for the specified version. - */ - const VALIDATION_MISSING = 3; - /** - * The file is present, and its hash matches a version older than the specified version. - */ - const VALIDATION_OLD = 4; - /** - * A hash cannot be determined for the provided file. - */ - const VALIDATION_INCALCULABLE = 5; - /** - * The file is present, but it should be deleted due to security concerns - */ - const VALIDATION_INSECURE = 6; - /** - * The file, present or absent, is not in this database. - */ - const VALIDATION_IGNORE = 7; + /** + * TRUE: All validations pass for the provided file. + * FALSE: One or more validations failed for the provided file. + */ + const VALIDATED = 1 << 0; + /** + * TRUE: The file path is known in this database, regardless of version. + * FALSE: The file path is not in this database. + */ + const VALIDATED_RELEVANCE = 1 << 1; + /** + * TRUE: The file exists. + * FALSE: The file doesn't exist. + */ + const VALIDATED_PRESENCE = 1 << 2; + /** + * TRUE: The file's hash matches a known version. + * FALSE: The file's hash does not match any known versions. + */ + const VALIDATED_HASH = 1 << 3; + /** + * TRUE: The file's hash matches the specified version. + * FALSE: The file's hash matches a newer or older version than the one specified. + */ + const VALIDATED_UPTODATE = 1 << 4; + /** + * TRUE: The file hash is calculable. + * FALSE: The file hash is not calculable (e.g. the core image itself, a user configuration file). + */ + const VALIDATED_DETERMINABLE = 1 << 5; + /** + * TRUE: The file is not known to be insecure. + * FALSE: The file should be deleted due to security concerns. + */ + const VALIDATED_SECURITY = 1 << 6; /** * Return an Iterator that can enumerate every path in the image database @@ -45,22 +52,22 @@ interface e_file_inspector_interface */ public function getPathIterator(); - /** - * Get all the known file integrity hashes for the provided path - * - * @param $path string Relative path of the file to look up - * @return array Associative array where the keys are the PHP-standardized versions and the values are the checksums - * for those versions. - */ - public function getChecksums($path); + /** + * Get all the known file integrity hashes for the provided path + * + * @param $path string Relative path of the file to look up + * @return array Associative array where the keys are the PHP-standardized versions and the values are the checksums + * for those versions. + */ + public function getChecksums($path); - /** - * List of versions of the provided path for which the database has hashes - * - * @param $path string Relative path of the file to look up - * @return array PHP-standardized versions. Empty if there are none. - */ - public function getVersions($path); + /** + * List of versions of the provided path for which the database has hashes + * + * @param $path string Relative path of the file to look up + * @return array PHP-standardized versions. Empty if there are none. + */ + public function getVersions($path); /** * Get the version of the software that goes with this image database. @@ -72,11 +79,11 @@ interface e_file_inspector_interface */ public function getCurrentVersion(); - /** - * Check if the file is insecure - * - * @param $path string Relative path of the file to look up - * @return bool TRUE if the file should be deleted due to known security flaws; FALSE otherwise - */ - public function isInsecure($path); + /** + * Check if the file is insecure + * + * @param $path string Relative path of the file to look up + * @return bool TRUE if the file should be deleted due to known security flaws; FALSE otherwise + */ + public function isInsecure($path); } \ No newline at end of file diff --git a/e107_tests/tests/unit/e_file_inspectorTest.php b/e107_tests/tests/unit/e_file_inspectorTest.php index 766ea187e..49808d0b9 100644 --- a/e107_tests/tests/unit/e_file_inspectorTest.php +++ b/e107_tests/tests/unit/e_file_inspectorTest.php @@ -9,33 +9,48 @@ class e_file_inspectorTest extends \Codeception\Test\Unit { - /** - * @var e_file_inspector_sqlphar - */ - private $e_integrity; + /** + * @var e_file_inspector_sqlphar + */ + private $e_integrity; - public function _before() - { - require_once(e_HANDLER."e_file_inspector_sqlphar.php"); - $this->e_integrity = new e_file_inspector_sqlphar(); - } + public function _before() + { + require_once(e_HANDLER . "e_file_inspector_sqlphar.php"); + $this->e_integrity = new e_file_inspector_sqlphar(); + } - public function testGetChecksums() - { - $checksums = $this->e_integrity->getChecksums("e107_admin/e107_update.php"); - $this->assertIsArray($checksums); - $this->assertNotEmpty($checksums); + public function testGetChecksums() + { + $checksums = $this->e_integrity->getChecksums("e107_admin/e107_update.php"); + $this->assertIsArray($checksums); + $this->assertNotEmpty($checksums); - $checksums = $this->e_integrity->getChecksums("e107_handlers/nonexistent.php"); - $this->assertIsArray($checksums); - $this->assertEmpty($checksums); - } + $checksums = $this->e_integrity->getChecksums("e107_handlers/nonexistent.php"); + $this->assertIsArray($checksums); + $this->assertEmpty($checksums); + } - public function testGetCurrentVersion() + public function testGetCurrentVersion() { $actualVersion = $this->e_integrity->getCurrentVersion(); $this->assertIsString($actualVersion); $this->assertNotEmpty($actualVersion); } + + public function testValidate() + { + $result = $this->e_integrity->validate("index.php"); + $this->assertGreaterThanOrEqual(1, $result & e_file_inspector::VALIDATED); + $this->assertGreaterThanOrEqual(1, $result & e_file_inspector::VALIDATED_RELEVANCE); + $this->assertGreaterThanOrEqual(1, $result & e_file_inspector::VALIDATED_PRESENCE); + $this->assertGreaterThanOrEqual(1, $result & e_file_inspector::VALIDATED_HASH); + $this->assertGreaterThanOrEqual(1, $result & e_file_inspector::VALIDATED_UPTODATE); + $this->assertGreaterThanOrEqual(1, $result & e_file_inspector::VALIDATED_DETERMINABLE); + $this->assertGreaterThanOrEqual(1, $result & e_file_inspector::VALIDATED_SECURITY); + + $result = $this->e_integrity->validate("file/does/not/exist.php"); + $this->assertEquals(0, $result & e_file_inspector::VALIDATED_PRESENCE); + } }