sanitize($url); if ($sanitized !== '') { $session->set(self::SESSION_KEY, $sanitized); } } /** * Retrieve the intended URL without removing it. */ public function retrieve(SessionStoreInterface $session): ?string { $value = $session->get(self::SESSION_KEY); return is_string($value) && $value !== '' ? $value : null; } /** * Retrieve the intended URL and remove it from the session. * Returns the fallback ('admin') if no valid URL is stored. */ public function retrieveAndForget(SessionStoreInterface $session): string { $value = $this->retrieve($session); $session->remove(self::SESSION_KEY); return $value ?? self::FALLBACK; } /** * Build a login redirect URL with the return_to query parameter. */ public function buildLoginRedirect(string $loginPath, string $returnTo): string { $sanitized = $this->sanitize($returnTo); if ($sanitized === '') { return $loginPath; } $separator = str_contains($loginPath, '?') ? '&' : '?'; return $loginPath . $separator . 'return_to=' . rawurlencode($sanitized); } /** * Sanitize and validate a URL for safe redirect. * Returns empty string if the URL is not safe. */ public function sanitize(string $url): string { $url = trim($url); if ($url === '' || mb_strlen($url) > self::MAX_LENGTH) { return ''; } // Must be a relative path (starts with /) if (!str_starts_with($url, '/')) { return ''; } // Must not contain protocol indicators (prevent //evil.com or javascript:) if (str_contains($url, '://') || str_starts_with($url, '//')) { return ''; } // Strip the path to check against blocked prefixes. // URL might be /en/admin/users or /admin/users — strip locale prefix. $path = $this->extractPathWithoutLocale($url); foreach (self::BLOCKED_PREFIXES as $prefix) { if (str_starts_with($path, $prefix) || $path === rtrim($prefix, '/')) { return ''; } } return $url; } /** * Extract the path portion without query string and without locale prefix. */ private function extractPathWithoutLocale(string $url): string { // Remove query string and fragment $path = strtok($url, '?#') ?: $url; // Remove leading slash $path = ltrim($path, '/'); // Remove locale prefix (2-letter code followed by /) if (preg_match('#^[a-z]{2}/#', $path)) { $path = substr($path, 3); } return $path; } }