feat(domains): Mehrere Kurzlink-Domains je Installation
short.example.de/messe und qr.example.de/messe koennen auf unterschiedliche Ziele zeigen. Die Domain ergibt sich aus dem Host-Header; Slugs sind nur noch innerhalb ihrer Domain eindeutig. Bestandslinks tragen nach der Migration den Platzhalter 0 und werden beim ersten Zugriff der aus APP_BASE_URL erzeugten Standarddomain zugeordnet. Ein Sentinel statt NULL, weil SQLite NULL-Werte in einem UNIQUE-Index als jeweils verschieden behandelt - die Eindeutigkeit waere sonst nicht durchgesetzt. Unbekannte Hosts landen auf der Standarddomain, damit bestehende Links auch beim Aufruf ueber die IP oder hinter einem Proxy funktionieren.
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
/**
|
||||
* Hilfsfunktionen fuer die Domains, unter denen Kurzlinks ausgeliefert
|
||||
* werden (z. B. "short.example.de" und "qr.example.de").
|
||||
*
|
||||
* Der Host enthaelt den Port, sofern einer angegeben ist - genau wie der
|
||||
* Host-Header der Anfrage. Dadurch laesst sich beides direkt vergleichen.
|
||||
*/
|
||||
final class ShortDomain
|
||||
{
|
||||
public const MAX_BASE_URL_LENGTH = 255;
|
||||
|
||||
/**
|
||||
* Bereitet eine Eingabe wie "qr.example.de" oder
|
||||
* "https://qr.example.de/" zu einer Basis-URL ohne Schraegstrich
|
||||
* am Ende auf.
|
||||
*/
|
||||
public static function normalizeBaseUrl(string $input): string
|
||||
{
|
||||
$input = trim($input);
|
||||
if ($input === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!preg_match('#^[a-zA-Z][a-zA-Z0-9+.-]*://#', $input)) {
|
||||
$input = 'https://' . ltrim($input, '/');
|
||||
}
|
||||
|
||||
$normalized = UrlValidator::normalize($input);
|
||||
|
||||
return rtrim($normalized, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert den Hostanteil inklusive Port, in Kleinbuchstaben.
|
||||
*/
|
||||
public static function hostOf(string $baseUrl): string
|
||||
{
|
||||
if (preg_match('#^[a-zA-Z][a-zA-Z0-9+.-]*://([^/?\#]+)#', trim($baseUrl), $matches) !== 1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$authority = $matches[1];
|
||||
|
||||
// Benutzerinfo gehoert nicht zum Host.
|
||||
$atPosition = strrpos($authority, '@');
|
||||
if ($atPosition !== false) {
|
||||
$authority = substr($authority, $atPosition + 1);
|
||||
}
|
||||
|
||||
return mb_strtolower($authority, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalisiert den Host-Header einer Anfrage fuer den Vergleich.
|
||||
*/
|
||||
public static function normalizeRequestHost(string $host): string
|
||||
{
|
||||
$host = mb_strtolower(trim($host), 'UTF-8');
|
||||
|
||||
// Standardports weglassen, damit "example.de:443" und
|
||||
// "example.de" dieselbe Domain treffen.
|
||||
return (string) preg_replace('/:(80|443)$/', '', $host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert eine Fehlermeldung oder null, wenn die Basis-URL taugt.
|
||||
*/
|
||||
public static function validationError(string $baseUrl): ?string
|
||||
{
|
||||
if (trim($baseUrl) === '') {
|
||||
return 'Bitte eine Domain angeben.';
|
||||
}
|
||||
|
||||
if (strlen($baseUrl) > self::MAX_BASE_URL_LENGTH) {
|
||||
return sprintf('Die Domain darf hoechstens %d Zeichen lang sein.', self::MAX_BASE_URL_LENGTH);
|
||||
}
|
||||
|
||||
$error = UrlValidator::validationError($baseUrl);
|
||||
if ($error !== null) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
if (self::hostOf($baseUrl) === '') {
|
||||
return 'Die Domain enthaelt keinen gueltigen Hostnamen.';
|
||||
}
|
||||
|
||||
// Query und Fragment ergeben fuer eine Basis-URL keinen Sinn.
|
||||
if (str_contains($baseUrl, '?') || str_contains($baseUrl, '#')) {
|
||||
return 'Die Domain darf keine Parameter (? oder #) enthalten.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anzeigename: Schema weglassen, wenn es https ist.
|
||||
*/
|
||||
public static function displayName(string $baseUrl): string
|
||||
{
|
||||
return (string) preg_replace('#^https://#', '', $baseUrl);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user