mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-25 18:45:27 -06:00
Created middleware which ensures trailing slash and multi-segment features work properly together
This commit is contained in:
@@ -43,6 +43,7 @@ return [
|
||||
ShortUrl\Helper\ShortUrlRedirectionBuilder::class => ConfigAbstractFactory::class,
|
||||
ShortUrl\Transformer\ShortUrlDataTransformer::class => ConfigAbstractFactory::class,
|
||||
ShortUrl\Middleware\ExtraPathRedirectMiddleware::class => ConfigAbstractFactory::class,
|
||||
ShortUrl\Middleware\TrimTrailingSlashMiddleware::class => ConfigAbstractFactory::class,
|
||||
|
||||
Tag\TagService::class => ConfigAbstractFactory::class,
|
||||
|
||||
@@ -154,6 +155,7 @@ return [
|
||||
Util\RedirectResponseHelper::class,
|
||||
Options\UrlShortenerOptions::class,
|
||||
],
|
||||
ShortUrl\Middleware\TrimTrailingSlashMiddleware::class => [Options\UrlShortenerOptions::class],
|
||||
|
||||
EventDispatcher\PublishingUpdatesGenerator::class => [
|
||||
ShortUrl\Transformer\ShortUrlDataTransformer::class,
|
||||
|
||||
@@ -15,6 +15,7 @@ final class UrlShortenerOptions
|
||||
public readonly bool $autoResolveTitles = false,
|
||||
public readonly bool $appendExtraPath = false,
|
||||
public readonly bool $multiSegmentSlugsEnabled = false,
|
||||
public readonly bool $trailingSlashEnabled = false,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\ShortUrl\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
||||
|
||||
use function rtrim;
|
||||
|
||||
class TrimTrailingSlashMiddleware implements MiddlewareInterface
|
||||
{
|
||||
private const SHORT_CODE_ATTR = 'shortCode';
|
||||
|
||||
public function __construct(private readonly UrlShortenerOptions $options)
|
||||
{
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
return $handler->handle($this->resolveRequest($request));
|
||||
}
|
||||
|
||||
private function resolveRequest(ServerRequestInterface $request): ServerRequestInterface
|
||||
{
|
||||
// If multi-segment slugs are enabled together with trailing slashes, the "shortCode" attribute will include
|
||||
// ending slashes that we need to trim for a proper short code matching
|
||||
|
||||
/** @var string|null $shortCode */
|
||||
$shortCode = $request->getAttribute(self::SHORT_CODE_ATTR);
|
||||
$shouldTrimSlash = $shortCode !== null && $this->options->trailingSlashEnabled;
|
||||
|
||||
return $shouldTrimSlash ? $request->withAttribute(self::SHORT_CODE_ATTR, rtrim($shortCode, '/')) : $request;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\ShortUrl\Middleware;
|
||||
|
||||
use Laminas\Diactoros\Response;
|
||||
use Laminas\Diactoros\ServerRequestFactory;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Middleware\TrimTrailingSlashMiddleware;
|
||||
|
||||
use function Functional\compose;
|
||||
use function Functional\const_function;
|
||||
|
||||
class TrimTrailingSlashMiddlewareTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private ObjectProphecy $requestHandler;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->requestHandler = $this->prophesize(RequestHandlerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideRequests
|
||||
*/
|
||||
public function returnsExpectedResponse(
|
||||
bool $trailingSlashEnabled,
|
||||
ServerRequestInterface $inputRequest,
|
||||
callable $assertions,
|
||||
): void {
|
||||
$arg = compose($assertions, const_function(true));
|
||||
|
||||
$this->requestHandler->handle(Argument::that($arg))->willReturn(new Response());
|
||||
$this->middleware($trailingSlashEnabled)->process($inputRequest, $this->requestHandler->reveal());
|
||||
}
|
||||
|
||||
public function provideRequests(): iterable
|
||||
{
|
||||
yield 'trailing slash disabled' => [
|
||||
false,
|
||||
$inputReq = ServerRequestFactory::fromGlobals(),
|
||||
function (ServerRequestInterface $request) use ($inputReq): void {
|
||||
Assert::assertSame($inputReq, $request);
|
||||
},
|
||||
];
|
||||
yield 'trailing slash enabled without shortCode attr' => [
|
||||
true,
|
||||
$inputReq = ServerRequestFactory::fromGlobals(),
|
||||
function (ServerRequestInterface $request) use ($inputReq): void {
|
||||
Assert::assertSame($inputReq, $request);
|
||||
},
|
||||
];
|
||||
yield 'trailing slash enabled with null shortCode attr' => [
|
||||
true,
|
||||
$inputReq = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', null),
|
||||
function (ServerRequestInterface $request) use ($inputReq): void {
|
||||
Assert::assertSame($inputReq, $request);
|
||||
},
|
||||
];
|
||||
yield 'trailing slash enabled with non-null shortCode attr' => [
|
||||
true,
|
||||
$inputReq = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', 'foo//'),
|
||||
function (ServerRequestInterface $request) use ($inputReq): void {
|
||||
Assert::assertNotSame($inputReq, $request);
|
||||
Assert::assertEquals('foo', $request->getAttribute('shortCode'));
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private function middleware(bool $trailingSlashEnabled = false): TrimTrailingSlashMiddleware
|
||||
{
|
||||
return new TrimTrailingSlashMiddleware(new UrlShortenerOptions(trailingSlashEnabled: $trailingSlashEnabled));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user