mirror of
https://github.com/shlinkio/shlink.git
synced 2024-12-18 05:10:43 -06:00
Allow the extra path to be ignored when redirecting
This commit is contained in:
parent
e74ee793a0
commit
c65349d265
@ -6,7 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
|
|||||||
|
|
||||||
# [Unreleased]
|
# [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
* *Nothing*
|
* [#2265](https://github.com/shlinkio/shlink/issues/2265) Add a new `REDIRECT_EXTRA_PATH_MODE` option that accepts three values:
|
||||||
|
|
||||||
|
* `default`: Short URLs only match if the path matches their short code or custom slug.
|
||||||
|
* `append`: Short URLs are matched as soon as the path starts with the short code or custom slug, and the extra path is appended to the long URL before redirecting.
|
||||||
|
* `ignore`: Short URLs are matched as soon as the path starts with the short code or custom slug, and the extra path is ignored.
|
||||||
|
|
||||||
|
This option effectively replaces the old `REDIRECT_APPEND_EXTRA_PATH` option, which is now deprecated and will be removed in Shlink 5.0.0
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* * [#2281](https://github.com/shlinkio/shlink/issues/2281) Update docker image to PHP 8.4
|
* * [#2281](https://github.com/shlinkio/shlink/issues/2281) Update docker image to PHP 8.4
|
||||||
|
@ -154,8 +154,8 @@
|
|||||||
"@test:cli",
|
"@test:cli",
|
||||||
"phpcov merge build/coverage-cli --html build/coverage-cli/coverage-html && rm build/coverage-cli/*.cov"
|
"phpcov merge build/coverage-cli --html build/coverage-cli/coverage-html && rm build/coverage-cli/*.cov"
|
||||||
],
|
],
|
||||||
"swagger:validate": "php-openapi validate docs/swagger/swagger.json",
|
"swagger:validate": "@php -d error_reporting=\"E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED\" vendor/bin/php-openapi validate docs/swagger/swagger.json",
|
||||||
"swagger:inline": "php-openapi inline docs/swagger/swagger.json docs/swagger/swagger-inlined.json",
|
"swagger:inline": "@php -d error_reporting=\"E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED\" vendor/bin/php-openapi inline docs/swagger/swagger.json docs/swagger/swagger-inlined.json",
|
||||||
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
||||||
},
|
},
|
||||||
"scripts-descriptions": {
|
"scripts-descriptions": {
|
||||||
|
@ -84,7 +84,7 @@ enum EnvVars: string
|
|||||||
case IS_HTTPS_ENABLED = 'IS_HTTPS_ENABLED';
|
case IS_HTTPS_ENABLED = 'IS_HTTPS_ENABLED';
|
||||||
case DEFAULT_DOMAIN = 'DEFAULT_DOMAIN';
|
case DEFAULT_DOMAIN = 'DEFAULT_DOMAIN';
|
||||||
case AUTO_RESOLVE_TITLES = 'AUTO_RESOLVE_TITLES';
|
case AUTO_RESOLVE_TITLES = 'AUTO_RESOLVE_TITLES';
|
||||||
case REDIRECT_APPEND_EXTRA_PATH = 'REDIRECT_APPEND_EXTRA_PATH';
|
case REDIRECT_EXTRA_PATH_MODE = 'REDIRECT_EXTRA_PATH_MODE';
|
||||||
case MULTI_SEGMENT_SLUGS_ENABLED = 'MULTI_SEGMENT_SLUGS_ENABLED';
|
case MULTI_SEGMENT_SLUGS_ENABLED = 'MULTI_SEGMENT_SLUGS_ENABLED';
|
||||||
case ROBOTS_ALLOW_ALL_SHORT_URLS = 'ROBOTS_ALLOW_ALL_SHORT_URLS';
|
case ROBOTS_ALLOW_ALL_SHORT_URLS = 'ROBOTS_ALLOW_ALL_SHORT_URLS';
|
||||||
case ROBOTS_USER_AGENTS = 'ROBOTS_USER_AGENTS';
|
case ROBOTS_USER_AGENTS = 'ROBOTS_USER_AGENTS';
|
||||||
@ -92,6 +92,8 @@ enum EnvVars: string
|
|||||||
case MEMORY_LIMIT = 'MEMORY_LIMIT';
|
case MEMORY_LIMIT = 'MEMORY_LIMIT';
|
||||||
case INITIAL_API_KEY = 'INITIAL_API_KEY';
|
case INITIAL_API_KEY = 'INITIAL_API_KEY';
|
||||||
case SKIP_INITIAL_GEOLITE_DOWNLOAD = 'SKIP_INITIAL_GEOLITE_DOWNLOAD';
|
case SKIP_INITIAL_GEOLITE_DOWNLOAD = 'SKIP_INITIAL_GEOLITE_DOWNLOAD';
|
||||||
|
/** @deprecated Use REDIRECT_EXTRA_PATH */
|
||||||
|
case REDIRECT_APPEND_EXTRA_PATH = 'REDIRECT_APPEND_EXTRA_PATH';
|
||||||
|
|
||||||
public function loadFromEnv(): mixed
|
public function loadFromEnv(): mixed
|
||||||
{
|
{
|
||||||
@ -125,11 +127,13 @@ enum EnvVars: string
|
|||||||
self::DEFAULT_SHORT_CODES_LENGTH => DEFAULT_SHORT_CODES_LENGTH,
|
self::DEFAULT_SHORT_CODES_LENGTH => DEFAULT_SHORT_CODES_LENGTH,
|
||||||
self::SHORT_URL_MODE => ShortUrlMode::STRICT->value,
|
self::SHORT_URL_MODE => ShortUrlMode::STRICT->value,
|
||||||
self::IS_HTTPS_ENABLED, self::AUTO_RESOLVE_TITLES => true,
|
self::IS_HTTPS_ENABLED, self::AUTO_RESOLVE_TITLES => true,
|
||||||
self::REDIRECT_APPEND_EXTRA_PATH,
|
|
||||||
self::MULTI_SEGMENT_SLUGS_ENABLED,
|
self::MULTI_SEGMENT_SLUGS_ENABLED,
|
||||||
self::SHORT_URL_TRAILING_SLASH => false,
|
self::SHORT_URL_TRAILING_SLASH => false,
|
||||||
self::DEFAULT_DOMAIN, self::BASE_PATH => '',
|
self::DEFAULT_DOMAIN, self::BASE_PATH => '',
|
||||||
self::CACHE_NAMESPACE => 'Shlink',
|
self::CACHE_NAMESPACE => 'Shlink',
|
||||||
|
// Deprecated. In Shlink 5.0.0, add default value for REDIRECT_EXTRA_PATH_MODE
|
||||||
|
self::REDIRECT_APPEND_EXTRA_PATH => false,
|
||||||
|
// self::REDIRECT_EXTRA_PATH_MODE => ExtraPathMode::DEFAULT->value,
|
||||||
|
|
||||||
self::REDIS_PUB_SUB_ENABLED,
|
self::REDIS_PUB_SUB_ENABLED,
|
||||||
self::MATOMO_ENABLED,
|
self::MATOMO_ENABLED,
|
||||||
|
13
module/Core/src/Config/Options/ExtraPathMode.php
Normal file
13
module/Core/src/Config/Options/ExtraPathMode.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Core\Config\Options;
|
||||||
|
|
||||||
|
enum ExtraPathMode: string
|
||||||
|
{
|
||||||
|
/** URLs with extra path will not match a short URL */
|
||||||
|
case DEFAULT = 'default';
|
||||||
|
/** The extra path will be appended to the long URL */
|
||||||
|
case APPEND = 'append';
|
||||||
|
/** The extra path will be ignored */
|
||||||
|
case IGNORE = 'ignore';
|
||||||
|
}
|
@ -22,10 +22,10 @@ final readonly class UrlShortenerOptions
|
|||||||
public string $schema = 'http',
|
public string $schema = 'http',
|
||||||
public int $defaultShortCodesLength = DEFAULT_SHORT_CODES_LENGTH,
|
public int $defaultShortCodesLength = DEFAULT_SHORT_CODES_LENGTH,
|
||||||
public bool $autoResolveTitles = false,
|
public bool $autoResolveTitles = false,
|
||||||
public bool $appendExtraPath = false,
|
|
||||||
public bool $multiSegmentSlugsEnabled = false,
|
public bool $multiSegmentSlugsEnabled = false,
|
||||||
public bool $trailingSlashEnabled = false,
|
public bool $trailingSlashEnabled = false,
|
||||||
public ShortUrlMode $mode = ShortUrlMode::STRICT,
|
public ShortUrlMode $mode = ShortUrlMode::STRICT,
|
||||||
|
public ExtraPathMode $extraPathMode = ExtraPathMode::DEFAULT,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,17 +35,26 @@ final readonly class UrlShortenerOptions
|
|||||||
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH->loadFromEnv(),
|
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH->loadFromEnv(),
|
||||||
MIN_SHORT_CODES_LENGTH,
|
MIN_SHORT_CODES_LENGTH,
|
||||||
);
|
);
|
||||||
$mode = EnvVars::SHORT_URL_MODE->loadFromEnv();
|
|
||||||
|
// Deprecated. Initialize extra path from REDIRECT_APPEND_EXTRA_PATH.
|
||||||
|
$appendExtraPath = EnvVars::REDIRECT_APPEND_EXTRA_PATH->loadFromEnv();
|
||||||
|
$extraPathMode = $appendExtraPath ? ExtraPathMode::APPEND : ExtraPathMode::DEFAULT;
|
||||||
|
|
||||||
|
// If REDIRECT_EXTRA_PATH_MODE was explicitly provided, it has precedence
|
||||||
|
$extraPathModeFromEnv = EnvVars::REDIRECT_EXTRA_PATH_MODE->loadFromEnv();
|
||||||
|
if ($extraPathModeFromEnv !== null) {
|
||||||
|
$extraPathMode = ExtraPathMode::tryFrom($extraPathModeFromEnv) ?? ExtraPathMode::DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
return new self(
|
return new self(
|
||||||
defaultDomain: EnvVars::DEFAULT_DOMAIN->loadFromEnv(),
|
defaultDomain: EnvVars::DEFAULT_DOMAIN->loadFromEnv(),
|
||||||
schema: ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv()) ? 'https' : 'http',
|
schema: ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv()) ? 'https' : 'http',
|
||||||
defaultShortCodesLength: $shortCodesLength,
|
defaultShortCodesLength: $shortCodesLength,
|
||||||
autoResolveTitles: (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(),
|
autoResolveTitles: (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(),
|
||||||
appendExtraPath: (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH->loadFromEnv(),
|
|
||||||
multiSegmentSlugsEnabled: (bool) EnvVars::MULTI_SEGMENT_SLUGS_ENABLED->loadFromEnv(),
|
multiSegmentSlugsEnabled: (bool) EnvVars::MULTI_SEGMENT_SLUGS_ENABLED->loadFromEnv(),
|
||||||
trailingSlashEnabled: (bool) EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv(),
|
trailingSlashEnabled: (bool) EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv(),
|
||||||
mode: ShortUrlMode::tryFrom($mode) ?? ShortUrlMode::STRICT,
|
mode: ShortUrlMode::tryFrom(EnvVars::SHORT_URL_MODE->loadFromEnv()) ?? ShortUrlMode::STRICT,
|
||||||
|
extraPathMode: $extraPathMode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ use Psr\Http\Message\ServerRequestInterface;
|
|||||||
use Psr\Http\Message\UriInterface;
|
use Psr\Http\Message\UriInterface;
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Shlinkio\Shlink\Core\Config\Options\ExtraPathMode;
|
||||||
use Shlinkio\Shlink\Core\Config\Options\UrlShortenerOptions;
|
use Shlinkio\Shlink\Core\Config\Options\UrlShortenerOptions;
|
||||||
use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
|
use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
|
||||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||||
@ -51,7 +52,7 @@ readonly class ExtraPathRedirectMiddleware implements MiddlewareInterface
|
|||||||
|
|
||||||
private function shouldApplyLogic(NotFoundType|null $notFoundType): bool
|
private function shouldApplyLogic(NotFoundType|null $notFoundType): bool
|
||||||
{
|
{
|
||||||
if ($notFoundType === null || ! $this->urlShortenerOptions->appendExtraPath) {
|
if ($notFoundType === null || $this->urlShortenerOptions->extraPathMode === ExtraPathMode::DEFAULT) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +76,11 @@ readonly class ExtraPathRedirectMiddleware implements MiddlewareInterface
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$shortUrl = $this->resolver->resolveEnabledShortUrl($identifier);
|
$shortUrl = $this->resolver->resolveEnabledShortUrl($identifier);
|
||||||
$longUrl = $this->redirectionBuilder->buildShortUrlRedirect($shortUrl, $request, $extraPath);
|
$longUrl = $this->redirectionBuilder->buildShortUrlRedirect(
|
||||||
|
$shortUrl,
|
||||||
|
$request,
|
||||||
|
$this->urlShortenerOptions->extraPathMode === ExtraPathMode::APPEND ? $extraPath : null,
|
||||||
|
);
|
||||||
$this->requestTracker->trackIfApplicable(
|
$this->requestTracker->trackIfApplicable(
|
||||||
$shortUrl,
|
$shortUrl,
|
||||||
$request->withAttribute(REDIRECT_URL_REQUEST_ATTRIBUTE, $longUrl),
|
$request->withAttribute(REDIRECT_URL_REQUEST_ATTRIBUTE, $longUrl),
|
||||||
|
@ -11,11 +11,13 @@ use Mezzio\Router\Route;
|
|||||||
use Mezzio\Router\RouteResult;
|
use Mezzio\Router\RouteResult;
|
||||||
use PHPUnit\Framework\Attributes\DataProvider;
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
use PHPUnit\Framework\Attributes\Test;
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use PHPUnit\Framework\Attributes\TestWith;
|
||||||
use PHPUnit\Framework\MockObject\MockObject;
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
use Shlinkio\Shlink\Core\Action\RedirectAction;
|
use Shlinkio\Shlink\Core\Action\RedirectAction;
|
||||||
|
use Shlinkio\Shlink\Core\Config\Options\ExtraPathMode;
|
||||||
use Shlinkio\Shlink\Core\Config\Options\UrlShortenerOptions;
|
use Shlinkio\Shlink\Core\Config\Options\UrlShortenerOptions;
|
||||||
use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
|
use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
|
||||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||||
@ -57,8 +59,8 @@ class ExtraPathRedirectMiddlewareTest extends TestCase
|
|||||||
ServerRequestInterface $request,
|
ServerRequestInterface $request,
|
||||||
): void {
|
): void {
|
||||||
$options = new UrlShortenerOptions(
|
$options = new UrlShortenerOptions(
|
||||||
appendExtraPath: $appendExtraPath,
|
|
||||||
multiSegmentSlugsEnabled: $multiSegmentEnabled,
|
multiSegmentSlugsEnabled: $multiSegmentEnabled,
|
||||||
|
extraPathMode: $appendExtraPath ? ExtraPathMode::APPEND : ExtraPathMode::DEFAULT,
|
||||||
);
|
);
|
||||||
$this->resolver->expects($this->never())->method('resolveEnabledShortUrl');
|
$this->resolver->expects($this->never())->method('resolveEnabledShortUrl');
|
||||||
$this->requestTracker->expects($this->never())->method('trackIfApplicable');
|
$this->requestTracker->expects($this->never())->method('trackIfApplicable');
|
||||||
@ -102,12 +104,17 @@ class ExtraPathRedirectMiddlewareTest extends TestCase
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Test, DataProvider('provideResolves')]
|
#[Test]
|
||||||
|
#[TestWith(['multiSegmentEnabled' => false, 'expectedResolveCalls' => 1])]
|
||||||
|
#[TestWith(['multiSegmentEnabled' => true, 'expectedResolveCalls' => 3])]
|
||||||
public function handlerIsCalledWhenNoShortUrlIsFoundAfterExpectedAmountOfIterations(
|
public function handlerIsCalledWhenNoShortUrlIsFoundAfterExpectedAmountOfIterations(
|
||||||
bool $multiSegmentEnabled,
|
bool $multiSegmentEnabled,
|
||||||
int $expectedResolveCalls,
|
int $expectedResolveCalls,
|
||||||
): void {
|
): void {
|
||||||
$options = new UrlShortenerOptions(appendExtraPath: true, multiSegmentSlugsEnabled: $multiSegmentEnabled);
|
$options = new UrlShortenerOptions(
|
||||||
|
multiSegmentSlugsEnabled: $multiSegmentEnabled,
|
||||||
|
extraPathMode: ExtraPathMode::APPEND,
|
||||||
|
);
|
||||||
|
|
||||||
$type = $this->createMock(NotFoundType::class);
|
$type = $this->createMock(NotFoundType::class);
|
||||||
$type->method('isRegularNotFound')->willReturn(true);
|
$type->method('isRegularNotFound')->willReturn(true);
|
||||||
@ -127,11 +134,15 @@ class ExtraPathRedirectMiddlewareTest extends TestCase
|
|||||||
|
|
||||||
#[Test, DataProvider('provideResolves')]
|
#[Test, DataProvider('provideResolves')]
|
||||||
public function visitIsTrackedAndRedirectIsReturnedWhenShortUrlIsFoundAfterExpectedAmountOfIterations(
|
public function visitIsTrackedAndRedirectIsReturnedWhenShortUrlIsFoundAfterExpectedAmountOfIterations(
|
||||||
|
ExtraPathMode $extraPathMode,
|
||||||
bool $multiSegmentEnabled,
|
bool $multiSegmentEnabled,
|
||||||
int $expectedResolveCalls,
|
int $expectedResolveCalls,
|
||||||
string|null $expectedExtraPath,
|
string|null $expectedExtraPath,
|
||||||
): void {
|
): void {
|
||||||
$options = new UrlShortenerOptions(appendExtraPath: true, multiSegmentSlugsEnabled: $multiSegmentEnabled);
|
$options = new UrlShortenerOptions(
|
||||||
|
multiSegmentSlugsEnabled: $multiSegmentEnabled,
|
||||||
|
extraPathMode: $extraPathMode,
|
||||||
|
);
|
||||||
|
|
||||||
$type = $this->createMock(NotFoundType::class);
|
$type = $this->createMock(NotFoundType::class);
|
||||||
$type->method('isRegularNotFound')->willReturn(true);
|
$type->method('isRegularNotFound')->willReturn(true);
|
||||||
@ -171,8 +182,10 @@ class ExtraPathRedirectMiddlewareTest extends TestCase
|
|||||||
|
|
||||||
public static function provideResolves(): iterable
|
public static function provideResolves(): iterable
|
||||||
{
|
{
|
||||||
yield [false, 1, '/bar/baz'];
|
yield [ExtraPathMode::APPEND, false, 1, '/bar/baz'];
|
||||||
yield [true, 3, null];
|
yield [ExtraPathMode::APPEND, true, 3, null];
|
||||||
|
yield [ExtraPathMode::IGNORE, false, 1, null];
|
||||||
|
yield [ExtraPathMode::IGNORE, true, 3, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function middleware(UrlShortenerOptions|null $options = null): ExtraPathRedirectMiddleware
|
private function middleware(UrlShortenerOptions|null $options = null): ExtraPathRedirectMiddleware
|
||||||
@ -182,7 +195,7 @@ class ExtraPathRedirectMiddlewareTest extends TestCase
|
|||||||
$this->requestTracker,
|
$this->requestTracker,
|
||||||
$this->redirectionBuilder,
|
$this->redirectionBuilder,
|
||||||
$this->redirectResponseHelper,
|
$this->redirectResponseHelper,
|
||||||
$options ?? new UrlShortenerOptions(appendExtraPath: true),
|
$options ?? new UrlShortenerOptions(extraPathMode: ExtraPathMode::APPEND),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user