header('content-type'))); if ($response->status !== 200 || ($type !== '' && ! str_starts_with($type, 'text/plain'))) { return null; } return self::plausible($response->body) ? $response->body : null; } /** Text in UTF-8, höchstens MAX_BYTES, mit Contact-Zeile, ohne HTML und ohne Steuerzeichen außer Zeilenenden. */ public static function plausible(string $body): bool { if ($body === '' || strlen($body) > self::MAX_BYTES || preg_match('//u', $body) !== 1) { return false; } if (preg_match('/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/', $body) === 1) { return false; } // ein HTML-Dokument (Fehlerseite, Anmeldeseite eines Proxys) – auch wenn es als text/plain kommt if (preg_match('/<\s*(?:!doctype|html|head|body|script)\b/i', $body) === 1) { return false; } return preg_match('/^contact:[ \t]*\S/mi', $body) === 1; } /** * Zeitpunkt aus der Expires-Zeile (RFC 3339, etwa 2027-03-01T00:00:00.000Z), oder null, wenn sie fehlt oder * nicht lesbar ist. */ public static function expires(string $body): ?int { if (preg_match('/^expires:[ \t]*(\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2}))[ \t]*\r?$/mi', $body, $m) !== 1) { return null; } try { return (new \DateTimeImmutable($m[1]))->getTimestamp(); } catch (\Exception) { return null; } } }