Less home code more F3

This commit is contained in:
julien
2026-03-29 01:49:25 +01:00
parent 1c8c22e12c
commit ed6321e8f3
31 changed files with 346 additions and 189 deletions

View File

@@ -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);
}