CMS 2.0 - Theme engine, logging, admin improvements
Major changes: - New ThemeManager with Twig templating and SCSS compilation - Dynamic themes system (themes/default, themes/demo) - LogManager with SQLite storage and syslog forwarding - RequestLogger with static helper methods - Admin UI overhaul (Bootstrap 5, dark mode) - Admin config page with logging and theme settings - Admin logs page with filters and search - Removed legacy Mustache templates - Removed test plugin and theme - Composer dependencies: Twig, scssphp, CommonMark, MaxMind GeoIP
This commit is contained in:
@@ -44,6 +44,79 @@ class RequestLogger
|
||||
return $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an IP matches any entry in a list of IPs/CIDR ranges.
|
||||
*
|
||||
* Supports exact IPv4/IPv6 addresses and CIDR notation (e.g. 192.168.0.0/16).
|
||||
*
|
||||
* @param string $ip The client IP to test
|
||||
* @param array $list List of IPs and/or CIDR ranges
|
||||
* @return bool True if the IP matches any entry
|
||||
*/
|
||||
public static function ipMatchesList(string $ip, array $list): bool
|
||||
{
|
||||
$ip = trim($ip);
|
||||
if ($ip === '') {
|
||||
return false;
|
||||
}
|
||||
$isV6 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
|
||||
$packed = $isV6 ? inet_pton($ip) : inet_pton($ip);
|
||||
|
||||
foreach ($list as $entry) {
|
||||
$entry = trim((string)$entry);
|
||||
if ($entry === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Exact match
|
||||
if ($entry === $ip) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// CIDR notation
|
||||
if (strpos($entry, '/') !== false) {
|
||||
[$subnet, $bits] = array_pad(explode('/', $entry, 2), 2, null);
|
||||
$subnet = trim($subnet);
|
||||
$subnetPacked = inet_pton($subnet);
|
||||
if ($subnetPacked === false || $packed === false) {
|
||||
continue;
|
||||
}
|
||||
// Ensure both are the same address family
|
||||
if (strlen($subnetPacked) !== strlen($packed)) {
|
||||
continue;
|
||||
}
|
||||
$maxBits = strlen($packed) * 8;
|
||||
$bits = (int)$bits;
|
||||
if ($bits < 0 || $bits > $maxBits) {
|
||||
continue;
|
||||
}
|
||||
if ($bits === 0) {
|
||||
return true;
|
||||
}
|
||||
$fullBytes = intdiv($bits, 8);
|
||||
$remainingBits = $bits % 8;
|
||||
$match = true;
|
||||
for ($i = 0; $i < $fullBytes; $i++) {
|
||||
if ($subnetPacked[$i] !== $packed[$i]) {
|
||||
$match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($match && $remainingBits > 0) {
|
||||
$mask = 0xFF << (8 - $remainingBits);
|
||||
if ((ord($subnetPacked[$fullBytes]) & $mask) !== (ord($packed[$fullBytes]) & $mask)) {
|
||||
$match = false;
|
||||
}
|
||||
}
|
||||
if ($match) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getClientIp(): string
|
||||
{
|
||||
$headerKeys = [
|
||||
|
||||
Reference in New Issue
Block a user