90 lines
3.3 KiB
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;
|
|
}
|
|
}
|
|
}
|