CluPilotCloud/app/Support/KnownHostnames.php

90 lines
3.3 KiB
PHP

<?php
namespace App\Support;
/**
* Die Hostnamen, die diese Installation ohnehin schon bedient.
*
* Abgeleitet, nie getippt. Genau das war der Fehler in der ersten Fassung der
* Hostnamen-Seite: sie war ein leeres Register und zeigte deshalb nichts —
* während die Installation längst ein halbes Dutzend Namen bediente, deren
* Zertifikate der Betreiber gerade sehen wollte. Eine Übersicht, die erst
* befüllt werden muss, beantwortet die Frage „was habe ich" nicht.
*
* Die Quellen sind dieselben, aus denen `routes/web.php` seine Domain-Bindungen
* baut. Steht ein Name in der Umgebung, antwortet die Anwendung darauf — und
* dann gehört er in diese Liste, ohne dass jemand ihn ein zweites Mal einträgt.
*/
final class KnownHostnames
{
/**
* @return array<string, string> hostname => Zweck ('public' | 'console')
*/
public static function all(): array
{
$found = [];
foreach ((array) config('admin_access.site_hosts', []) as $host) {
self::add($found, $host, 'public');
}
foreach ([
'admin_access.app_host',
'admin_access.status_host',
'admin_access.files_host',
] as $key) {
self::add($found, (string) config($key, ''), 'public');
}
// Die Konsole und ihre Ausweichnamen. `console`, weil sie hinter der
// Netzsperre liegen — ein Name mit dem falschen Zweck sähe hier aus wie
// eine funktionierende Seite und wäre eine offene Konsole.
foreach ((array) config('admin_access.hosts', []) as $host) {
self::add($found, $host, 'console');
}
ksort($found);
return $found;
}
private static function add(array &$found, string $host, string $purpose): void
{
$host = strtolower(trim($host));
// Kein Schema, kein Pfad, kein Port — und mindestens ein Punkt. Ein
// blanker Name wie `localhost` ist ein Entwicklungsrest und hat kein
// Zertifikat, das jemand überwachen wollte.
if ($host === '' || ! str_contains($host, '.') || preg_match('/[^a-z0-9.\-]/', $host)) {
return;
}
// KEINE IP-Adressen.
//
// `ADMIN_HOSTS` darf ausdrücklich eine nackte IP führen — sie ist der
// Wiederzugang zur Konsole, wenn ein Name einmal nicht auflöst. Aber für
// eine IP stellt Let's Encrypt kein Zertifikat aus, also steht sie in
// einer Zertifikatsübersicht nur als dauerhaft roter Eintrag, den
// niemand beheben kann.
//
// Der erste Filter verlangte bloß einen Punkt — `127.0.0.1` hat drei.
// Deshalb hier beides: die Adressprüfung UND die Bedingung, dass die
// letzte Marke keine Zahl ist. Eine Endung ist nie numerisch, und das
// fängt auch die Formen ab, die `FILTER_VALIDATE_IP` durchlässt.
if (filter_var($host, FILTER_VALIDATE_IP) !== false) {
return;
}
$letzteMarke = substr($host, strrpos($host, '.') + 1);
if ($letzteMarke === '' || ctype_digit($letzteMarke)) {
return;
}
// Konsole gewinnt gegen öffentlich, wenn ein Name in beiden Listen
// steht: die engere Aussage ist die richtige.
if (($found[$host] ?? null) !== 'console') {
$found[$host] = $purpose;
}
}
}