From 32f7b4fbf6e979806dd0fc5d903c19010369178f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya <alejandrocelaya@gmail.com> Date: Thu, 15 Jul 2021 16:54:54 +0200 Subject: [PATCH] Created new middleware that redirects to short URLs with an extra path --- .../autoload/middleware-pipeline.global.php | 1 + module/Core/config/dependencies.config.php | 8 ++ .../src/Action/AbstractTrackingAction.php | 2 +- .../Helper/ShortUrlRedirectionBuilder.php | 4 +- .../ExtraPathRedirectMiddleware.php | 74 +++++++++++++++++++ 5 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 module/Core/src/ShortUrl/Middleware/ExtraPathRedirectMiddleware.php diff --git a/config/autoload/middleware-pipeline.global.php b/config/autoload/middleware-pipeline.global.php index c60e1ba7..0466ebc5 100644 --- a/config/autoload/middleware-pipeline.global.php +++ b/config/autoload/middleware-pipeline.global.php @@ -68,6 +68,7 @@ return [ // This middleware is in front of tracking actions explicitly. Putting here for orphan visits tracking IpAddress::class, Core\ErrorHandler\NotFoundTypeResolverMiddleware::class, + Core\ShortUrl\Middleware\ExtraPathRedirectMiddleware::class, Core\ErrorHandler\NotFoundTrackerMiddleware::class, Core\ErrorHandler\NotFoundRedirectHandler::class, Core\ErrorHandler\NotFoundTemplateHandler::class, diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 34de226d..baecce9f 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -55,6 +55,7 @@ return [ ShortUrl\Helper\ShortUrlTitleResolutionHelper::class => ConfigAbstractFactory::class, ShortUrl\Helper\ShortUrlRedirectionBuilder::class => ConfigAbstractFactory::class, ShortUrl\Transformer\ShortUrlDataTransformer::class => ConfigAbstractFactory::class, + ShortUrl\Middleware\ExtraPathRedirectMiddleware::class => ConfigAbstractFactory::class, Mercure\MercureUpdatesGenerator::class => ConfigAbstractFactory::class, @@ -139,6 +140,13 @@ return [ ShortUrl\Helper\ShortUrlTitleResolutionHelper::class => [Util\UrlValidator::class], ShortUrl\Helper\ShortUrlRedirectionBuilder::class => [Options\TrackingOptions::class], ShortUrl\Transformer\ShortUrlDataTransformer::class => [ShortUrl\Helper\ShortUrlStringifier::class], + ShortUrl\Middleware\ExtraPathRedirectMiddleware::class => [ + Service\ShortUrl\ShortUrlResolver::class, + Visit\VisitsTracker::class, + ShortUrl\Helper\ShortUrlRedirectionBuilder::class, + Util\RedirectResponseHelper::class, + Options\UrlShortenerOptions::class, + ], Mercure\MercureUpdatesGenerator::class => [ ShortUrl\Transformer\ShortUrlDataTransformer::class, diff --git a/module/Core/src/Action/AbstractTrackingAction.php b/module/Core/src/Action/AbstractTrackingAction.php index b0f3d6ee..554c1844 100644 --- a/module/Core/src/Action/AbstractTrackingAction.php +++ b/module/Core/src/Action/AbstractTrackingAction.php @@ -43,7 +43,7 @@ abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMet } return $this->createSuccessResp($shortUrl, $request); - } catch (ShortUrlNotFoundException $e) { + } catch (ShortUrlNotFoundException) { return $this->createErrorResp($request, $handler); } } diff --git a/module/Core/src/ShortUrl/Helper/ShortUrlRedirectionBuilder.php b/module/Core/src/ShortUrl/Helper/ShortUrlRedirectionBuilder.php index 1c45698f..43ea4993 100644 --- a/module/Core/src/ShortUrl/Helper/ShortUrlRedirectionBuilder.php +++ b/module/Core/src/ShortUrl/Helper/ShortUrlRedirectionBuilder.php @@ -28,7 +28,7 @@ class ShortUrlRedirectionBuilder implements ShortUrlRedirectionBuilderInterface ->__toString(); } - private function resolveQuery(Uri $uri, array $currentQuery): string + private function resolveQuery(Uri $uri, array $currentQuery): ?string { $hardcodedQuery = Query::parse($uri->getQuery() ?? ''); @@ -39,7 +39,7 @@ class ShortUrlRedirectionBuilder implements ShortUrlRedirectionBuilderInterface $mergedQuery = array_merge($hardcodedQuery, $currentQuery); - return empty($mergedQuery) ? '' : Query::build($mergedQuery); + return empty($mergedQuery) ? null : Query::build($mergedQuery); } private function resolvePath(Uri $uri, ?string $extraPath): string diff --git a/module/Core/src/ShortUrl/Middleware/ExtraPathRedirectMiddleware.php b/module/Core/src/ShortUrl/Middleware/ExtraPathRedirectMiddleware.php new file mode 100644 index 00000000..401c7a0a --- /dev/null +++ b/module/Core/src/ShortUrl/Middleware/ExtraPathRedirectMiddleware.php @@ -0,0 +1,74 @@ +<?php + +declare(strict_types=1); + +namespace Shlinkio\Shlink\Core\ShortUrl\Middleware; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\UriInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType; +use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; +use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; +use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; +use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface; +use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlRedirectionBuilderInterface; +use Shlinkio\Shlink\Core\Util\RedirectResponseHelperInterface; +use Shlinkio\Shlink\Core\Visit\VisitsTrackerInterface; + +use function array_pad; +use function explode; +use function sprintf; +use function trim; + +class ExtraPathRedirectMiddleware implements MiddlewareInterface +{ + public function __construct( + private ShortUrlResolverInterface $resolver, + private VisitsTrackerInterface $visitTracker, + private ShortUrlRedirectionBuilderInterface $redirectionBuilder, + private RedirectResponseHelperInterface $redirectResponseHelper, + private UrlShortenerOptions $urlShortenerOptions, + ) { + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + /** @var NotFoundType|null $notFoundType */ + $notFoundType = $request->getAttribute(NotFoundType::class); + + // We'll apply this logic only if actively opted in and current URL is potentially /{shortCode}/[...] + if (! $notFoundType?->isRegularNotFound() || ! $this->urlShortenerOptions->appendExtraPath()) { + return $handler->handle($request); + } + + $uri = $request->getUri(); + $query = $request->getQueryParams(); + [$potentialShortCode, $extraPath] = $this->resolvePotentialShortCodeAndExtraPath($uri); + $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($potentialShortCode, $uri->getAuthority()); + + try { + $shortUrl = $this->resolver->resolveEnabledShortUrl($identifier); + + // TODO Track visit + + $longUrl = $this->redirectionBuilder->buildShortUrlRedirect($shortUrl, $query, $extraPath); + return $this->redirectResponseHelper->buildRedirectResponse($longUrl); + } catch (ShortUrlNotFoundException) { + return $handler->handle($request); + } + } + + /** + * @return array{0: string, 1: string|null} + */ + private function resolvePotentialShortCodeAndExtraPath(UriInterface $uri): array + { + $pathParts = explode('/', trim($uri->getPath(), '/'), 2); + [$potentialShortCode, $extraPath] = array_pad($pathParts, 2, null); + + return [$potentialShortCode, $extraPath === null ? null : sprintf('/%s', $extraPath)]; + } +}