Less home code more F3
This commit is contained in:
@@ -2,13 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// ── Core ────────────────────────────────────────────────────────────
|
||||
|
||||
function app_root(): string
|
||||
{
|
||||
return dirname(__DIR__, 2);
|
||||
}
|
||||
|
||||
function app_timezone(): string
|
||||
{
|
||||
$timezone = trim((string) Base::instance()->get('app.timezone'));
|
||||
@@ -25,8 +18,6 @@ function app_is_prod(): bool
|
||||
return Base::instance()->get('app.env') === 'prod';
|
||||
}
|
||||
|
||||
// ── Fichiers et chemins ─────────────────────────────────────────────
|
||||
|
||||
function app_ensure_dir(string $path): void
|
||||
{
|
||||
if (!is_dir($path)) {
|
||||
@@ -34,28 +25,6 @@ function app_ensure_dir(string $path): void
|
||||
}
|
||||
}
|
||||
|
||||
function app_db_path(): string
|
||||
{
|
||||
return app_root() . '/db/app.sqlite';
|
||||
}
|
||||
|
||||
function app_logs_dir(): string
|
||||
{
|
||||
return app_root() . '/logs';
|
||||
}
|
||||
|
||||
function app_public_media_dir(): string
|
||||
{
|
||||
return app_root() . '/public/uploads/media';
|
||||
}
|
||||
|
||||
function app_media_url(string $fileName): string
|
||||
{
|
||||
return rtrim((string) Base::instance()->get('BASE'), '/') . '/uploads/media/' . rawurlencode($fileName);
|
||||
}
|
||||
|
||||
// ── Texte ───────────────────────────────────────────────────────────
|
||||
|
||||
function app_unique_slug(string $value, callable $exists): string
|
||||
{
|
||||
$base = Web::instance()->slug(trim($value));
|
||||
@@ -111,3 +80,131 @@ function app_format_datetime_fr(string $value): string
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
function app_trusted_proxies(): array
|
||||
{
|
||||
$value = Base::instance()->get('app.trusted_proxies');
|
||||
|
||||
if (is_array($value)) {
|
||||
$items = [];
|
||||
|
||||
array_walk_recursive($value, static function (mixed $item) use (&$items): void {
|
||||
if (is_string($item) || is_numeric($item)) {
|
||||
$items[] = (string) $item;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$raw = trim((string) $value);
|
||||
if ($raw === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = preg_split('/[\s,]+/', $raw) ?: [];
|
||||
}
|
||||
|
||||
$normalized = [];
|
||||
foreach ($items as $item) {
|
||||
foreach (preg_split('/[\s,]+/', trim((string) $item)) ?: [] as $part) {
|
||||
$part = trim($part);
|
||||
if ($part !== '') {
|
||||
$normalized[] = $part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($normalized));
|
||||
}
|
||||
|
||||
function app_is_trusted_proxy(?string $ip = null): bool
|
||||
{
|
||||
$ip = trim((string) ($ip ?? Base::instance()->get('SERVER.REMOTE_ADDR')));
|
||||
if ($ip === '' || filter_var($ip, FILTER_VALIDATE_IP) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (app_trusted_proxies() as $proxy) {
|
||||
if (app_ip_matches_proxy($ip, $proxy)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function app_request_scheme(): string
|
||||
{
|
||||
$f3 = Base::instance();
|
||||
|
||||
$https = strtolower(trim((string) $f3->get('SERVER.HTTPS')));
|
||||
if ($https !== '' && $https !== 'off' && $https !== '0') {
|
||||
return 'https';
|
||||
}
|
||||
|
||||
$scheme = strtolower(trim((string) $f3->get('SCHEME')));
|
||||
if ($scheme === 'https') {
|
||||
return 'https';
|
||||
}
|
||||
|
||||
// Derrière un reverse proxy de confiance, on accepte le proto transmis.
|
||||
if (app_is_trusted_proxy()) {
|
||||
$forwardedProto = trim((string) $f3->get('SERVER.HTTP_X_FORWARDED_PROTO'));
|
||||
if ($forwardedProto !== '') {
|
||||
$forwardedProto = strtolower(trim(explode(',', $forwardedProto)[0]));
|
||||
return $forwardedProto === 'https' ? 'https' : 'http';
|
||||
}
|
||||
|
||||
$forwarded = trim((string) $f3->get('SERVER.HTTP_FORWARDED'));
|
||||
if ($forwarded !== '' && preg_match('/(?:^|[;,]\s*)proto=(https?)/i', $forwarded, $matches) === 1) {
|
||||
return strtolower($matches[1]) === 'https' ? 'https' : 'http';
|
||||
}
|
||||
}
|
||||
|
||||
return 'http';
|
||||
}
|
||||
|
||||
function app_ip_matches_proxy(string $ip, string $proxy): bool
|
||||
{
|
||||
$proxy = trim($proxy);
|
||||
if ($proxy === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!str_contains($proxy, '/')) {
|
||||
return filter_var($proxy, FILTER_VALIDATE_IP) !== false && strcasecmp($ip, $proxy) === 0;
|
||||
}
|
||||
|
||||
[$subnet, $prefix] = explode('/', $proxy, 2);
|
||||
$subnet = trim($subnet);
|
||||
$prefix = trim($prefix);
|
||||
|
||||
$ipBinary = inet_pton($ip);
|
||||
$subnetBinary = inet_pton($subnet);
|
||||
|
||||
if ($ipBinary === false || $subnetBinary === false || strlen($ipBinary) !== strlen($subnetBinary)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ctype_digit($prefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$prefixLength = (int) $prefix;
|
||||
$maxBits = strlen($ipBinary) * 8;
|
||||
if ($prefixLength < 0 || $prefixLength > $maxBits) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fullBytes = intdiv($prefixLength, 8);
|
||||
if ($fullBytes > 0 && substr($ipBinary, 0, $fullBytes) !== substr($subnetBinary, 0, $fullBytes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$remainingBits = $prefixLength % 8;
|
||||
if ($remainingBits === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$mask = (0xFF << (8 - $remainingBits)) & 0xFF;
|
||||
|
||||
return (ord($ipBinary[$fullBytes]) & $mask) === (ord($subnetBinary[$fullBytes]) & $mask);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user