1
0
mirror of https://github.com/e107inc/e107.git synced 2025-01-17 20:58:30 +01:00
php-e107/e107_handlers/e_file_inspector.php

247 lines
8.2 KiB
PHP
Raw Normal View History

<?php
/**
* e107 website system
*
* Copyright (C) 2008-2020 e107 Inc (e107.org)
* Released under the terms and conditions of the
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
*/
require_once("e_file_inspector_interface.php");
/**
* File Inspector
*
* Tool to validate application files for consistency by comparing hashes of files with those in a database
*/
abstract class e_file_inspector implements e_file_inspector_interface
{
2020-03-23 22:40:28 -05:00
protected $database;
protected $currentVersion;
private $validatedBitmask;
protected $defaultDirsCache;
protected $customDirsCache;
private $undeterminable = array();
2020-03-23 22:40:28 -05:00
/**
* e_file_inspector constructor
* @param string $database The database from which integrity data may be read or to which integrity data may be
* written. This should be an URL or absolute file path for most implementations.
*/
public function __construct($database)
{
$this->database = $database;
$this->loadDatabase();
$appRoot = e107::getInstance()->file_path;
$this->undeterminable = array_map(function ($path)
{
return realpath($path) ? realpath($path) : $path;
}, [
$appRoot . "e107_config.php",
$appRoot . e107::getFolder('admin') . "core_image.php",
]
);
2020-03-23 22:40:28 -05:00
}
/**
* Prepare the provided database for reading or writing
*
* Should tolerate a non-existent database and try to create it if a write operation is executed.
*
* @return void
*/
abstract public function loadDatabase();
/**
* 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();
$bits = 0x0;
$absolutePath = realpath(e_BASE . $path);
$dbChecksums = $this->getChecksums($path);
$dbChecksum = $this->getChecksum($path, $version);
$actualChecksum = !empty($dbChecksums) ? $this->checksumPath($absolutePath) : null;
if (!empty($dbChecksums)) $bits |= self::VALIDATED_PATH_KNOWN;
if ($dbChecksum !== false) $bits |= self::VALIDATED_PATH_VERSION;
if (file_exists($absolutePath)) $bits |= self::VALIDATED_FILE_EXISTS;
if (!$this->isInsecure($path)) $bits |= self::VALIDATED_FILE_SECURITY;
if ($this->isDeterminable($absolutePath)) $bits |= self::VALIDATED_HASH_CALCULABLE;
if ($actualChecksum === $dbChecksum) $bits |= self::VALIDATED_HASH_CURRENT;
foreach ($dbChecksums as $dbVersion => $dbChecksum)
{
if ($dbChecksum === $actualChecksum) $bits |= self::VALIDATED_HASH_EXISTS;
}
if ($bits + self::VALIDATED === $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 (!$this->isDeterminable($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
*/
public function getVersions($path)
{
return array_keys($this->getChecksums($path));
}
/**
* @inheritDoc
*/
public function getCurrentVersion()
{
if ($this->currentVersion) return $this->currentVersion;
$checksums = $this->getChecksums("index.php");
$versions = array_keys($checksums);
usort($versions, 'version_compare');
return $this->currentVersion = array_pop($versions);
}
/**
* 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
*/
public function isInsecure($path)
{
# TODO
return false;
}
/**
* Convert a custom site path to a default path
* @param string $path Custom path
* @return string
*/
public function customPathToDefaultPath($path)
{
if (!is_array($this->customDirsCache)) $this->populateDirsCache();
foreach ($this->customDirsCache as $dirType => $customDir)
{
if (!isset($this->defaultDirsCache[$dirType])) continue;
$defaultDir = $this->defaultDirsCache[$dirType];
if ($customDir === $defaultDir) continue;
if (substr($path, 0, strlen($customDir)) === $customDir)
$path = $defaultDir . substr($path, strlen($customDir));
}
return $path;
}
public function defaultPathToCustomPath($path)
{
if (!is_array($this->customDirsCache)) $this->populateDirsCache();
foreach ($this->customDirsCache as $dirType => $customDir)
{
if (!isset($this->defaultDirsCache[$dirType])) continue;
$defaultDir = $this->defaultDirsCache[$dirType];
if ($customDir === $defaultDir) continue;
if (substr($path, 0, strlen($defaultDir)) === $defaultDir)
$path = $customDir . substr($path, strlen($defaultDir));
}
return $path;
}
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;
}
/**
* @param $absolutePath
* @return bool
*/
private function isDeterminable($absolutePath)
{
return is_file($absolutePath) && is_readable($absolutePath) && !in_array($absolutePath, $this->undeterminable);
}
protected function populateDirsCache()
{
$this->defaultDirsCache = e107::getInstance()->defaultDirs();
$customDirs = e107::getInstance()->e107_dirs ? e107::getInstance()->e107_dirs : [];
$this->customDirsCache = array_diff_assoc($customDirs, $this->defaultDirsCache);
}
}