Keep similar-ai from tripping the global circuit on a lone URL 502, clamp Qdrant search to 100, and add Server-Timing plus slow-request logging. Studio shared props, Academy S3 exists caching, heat chunking, and Redis/scheduler hygiene stay in this rollout.
97 lines
2.4 KiB
PHP
97 lines
2.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\Http;
|
|
|
|
final class HttpUriNormalizer
|
|
{
|
|
public function normalize(string $uri): string
|
|
{
|
|
$path = parse_url($uri, PHP_URL_PATH);
|
|
if (! is_string($path) || $path === '') {
|
|
$path = $uri;
|
|
}
|
|
|
|
$path = '/'.ltrim($path, '/');
|
|
if ($path !== '/') {
|
|
$path = rtrim($path, '/');
|
|
}
|
|
|
|
if (preg_match('#^/@([^/]+)(/.*)?$#', $path, $matches) === 1) {
|
|
$rest = $matches[2] ?? '';
|
|
|
|
return '/@{user}'.$this->normalizeSegments($rest);
|
|
}
|
|
|
|
return $this->normalizeSegments($path);
|
|
}
|
|
|
|
private function normalizeSegments(string $path): string
|
|
{
|
|
if ($path === '' || $path === '/') {
|
|
return $path === '' ? '' : '/';
|
|
}
|
|
|
|
$segments = explode('/', $path);
|
|
$normalized = [];
|
|
|
|
foreach ($segments as $index => $segment) {
|
|
if ($segment === '') {
|
|
$normalized[] = '';
|
|
continue;
|
|
}
|
|
|
|
$normalized[] = $this->normalizeSegment($segment, $index, $segments);
|
|
}
|
|
|
|
$result = implode('/', $normalized);
|
|
|
|
return $result === '' ? '/' : $result;
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $segments
|
|
*/
|
|
private function normalizeSegment(string $segment, int $index, array $segments): string
|
|
{
|
|
if (ctype_digit($segment)) {
|
|
return '{id}';
|
|
}
|
|
|
|
if (preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $segment) === 1) {
|
|
return '{uuid}';
|
|
}
|
|
|
|
if (preg_match('/^[0-9a-f]{32,64}$/i', $segment) === 1) {
|
|
return '{hash}';
|
|
}
|
|
|
|
if ($this->isArtworkPublicSlug($segment, $index, $segments)) {
|
|
return '{slug}';
|
|
}
|
|
|
|
return $segment;
|
|
}
|
|
|
|
/**
|
|
* /art/{id}/{slug} stays distinct from /art/{id}/similar and other actions.
|
|
*
|
|
* @param array<int, string> $segments
|
|
*/
|
|
private function isArtworkPublicSlug(string $segment, int $index, array $segments): bool
|
|
{
|
|
if ($index !== 3 || ($segments[1] ?? '') !== 'art') {
|
|
return false;
|
|
}
|
|
|
|
if (! ctype_digit((string) ($segments[2] ?? ''))) {
|
|
return false;
|
|
}
|
|
|
|
$actions = ['similar', 'view', 'download', 'edit', 'comments', 'favourite', 'favorite'];
|
|
|
|
return ! in_array(strtolower($segment), $actions, true);
|
|
}
|
|
}
|