From d7e89ebdae22f69078ca7df3b2461c56c003a1b8 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 1 Dec 2018 21:38:29 +0100 Subject: [PATCH] Ensured custom slugs are case sensitive --- config/autoload/slugify.global.php | 23 +++++++++++ module/Core/config/dependencies.config.php | 25 +++++------- .../Core/src/Options/UrlShortenerOptions.php | 40 +++++++++++++++++++ module/Core/src/Service/UrlShortener.php | 37 ++++++++--------- module/Core/test/Service/UrlShortenerTest.php | 9 ++--- 5 files changed, 95 insertions(+), 39 deletions(-) create mode 100644 config/autoload/slugify.global.php create mode 100644 module/Core/src/Options/UrlShortenerOptions.php diff --git a/config/autoload/slugify.global.php b/config/autoload/slugify.global.php new file mode 100644 index 00000000..1c3a3f96 --- /dev/null +++ b/config/autoload/slugify.global.php @@ -0,0 +1,23 @@ + [ + 'lowercase' => false, + ], + + 'dependencies' => [ + 'factories' => [ + Slugify::class => ConfigAbstractFactory::class, + ], + ], + + ConfigAbstractFactory::class => [ + Slugify::class => ['config.slugify_options'], + ], + +]; diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 7042b19a..bb068e65 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -1,13 +1,12 @@ [ 'factories' => [ + NotFoundHandler::class => ConfigAbstractFactory::class, + Options\AppOptions::class => ConfigAbstractFactory::class, Options\DeleteShortUrlsOptions::class => ConfigAbstractFactory::class, Options\NotFoundShortUrlOptions::class => ConfigAbstractFactory::class, - NotFoundHandler::class => ConfigAbstractFactory::class, + Options\UrlShortenerOptions::class => ConfigAbstractFactory::class, - // Services Service\UrlShortener::class => ConfigAbstractFactory::class, Service\VisitsTracker::class => ConfigAbstractFactory::class, Service\ShortUrlService::class => ConfigAbstractFactory::class, @@ -29,11 +29,11 @@ return [ Service\Tag\TagService::class => ConfigAbstractFactory::class, Service\ShortUrl\DeleteShortUrlService::class => ConfigAbstractFactory::class, - // Middleware Action\RedirectAction::class => ConfigAbstractFactory::class, Action\PixelAction::class => ConfigAbstractFactory::class, Action\QrCodeAction::class => ConfigAbstractFactory::class, Action\PreviewAction::class => ConfigAbstractFactory::class, + Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class, ], ], @@ -44,21 +44,15 @@ return [ Options\AppOptions::class => ['config.app_options'], Options\DeleteShortUrlsOptions::class => ['config.delete_short_urls'], Options\NotFoundShortUrlOptions::class => ['config.url_shortener.not_found_short_url'], + Options\UrlShortenerOptions::class => ['config.url_shortener'], - // Services - Service\UrlShortener::class => [ - 'httpClient', - 'em', - 'config.url_shortener.validate_url', - 'config.url_shortener.shortcode_chars', - ], + Service\UrlShortener::class => ['httpClient', 'em', Options\UrlShortenerOptions::class, Slugify::class], Service\VisitsTracker::class => ['em'], Service\ShortUrlService::class => ['em'], Service\VisitService::class => ['em'], Service\Tag\TagService::class => ['em'], Service\ShortUrl\DeleteShortUrlService::class => ['em', Options\DeleteShortUrlsOptions::class], - // Middleware Action\RedirectAction::class => [ Service\UrlShortener::class, Service\VisitsTracker::class, @@ -74,6 +68,7 @@ return [ ], Action\QrCodeAction::class => [RouterInterface::class, Service\UrlShortener::class, 'Logger_Shlink'], Action\PreviewAction::class => [PreviewGenerator::class, Service\UrlShortener::class, 'Logger_Shlink'], + Middleware\QrCodeCacheMiddleware::class => [Cache::class], ], diff --git a/module/Core/src/Options/UrlShortenerOptions.php b/module/Core/src/Options/UrlShortenerOptions.php new file mode 100644 index 00000000..9ac2d0c6 --- /dev/null +++ b/module/Core/src/Options/UrlShortenerOptions.php @@ -0,0 +1,40 @@ +shortcodeChars; + } + + protected function setShortcodeChars(string $shortcodeChars): self + { + $this->shortcodeChars = empty($shortcodeChars) ? self::DEFAULT_CHARS : $shortcodeChars; + return $this; + } + + public function isUrlValidationEnabled(): bool + { + return $this->validateUrl; + } + + protected function setValidateUrl($validateUrl): self + { + $this->validateUrl = (bool) $validateUrl; + return $this; + } +} diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 4a7edcfb..c02e8f70 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; use Cake\Chronos\Chronos; -use Cocur\Slugify\Slugify; use Cocur\Slugify\SlugifyInterface; use Doctrine\ORM\EntityManagerInterface; use GuzzleHttp\ClientInterface; @@ -17,6 +16,7 @@ use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Exception\RuntimeException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; +use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Throwable; @@ -29,32 +29,29 @@ class UrlShortener implements UrlShortenerInterface { use TagManagerTrait; - public const DEFAULT_CHARS = '123456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ'; + /** @deprecated */ + public const DEFAULT_CHARS = UrlShortenerOptions::DEFAULT_CHARS; private const ID_INCREMENT = 200000; /** @var ClientInterface */ private $httpClient; /** @var EntityManagerInterface */ private $em; - /** @var string */ - private $chars; /** @var SlugifyInterface */ private $slugger; - /** @var bool */ - private $urlValidationEnabled; + /** @var UrlShortenerOptions */ + private $options; public function __construct( ClientInterface $httpClient, EntityManagerInterface $em, - $urlValidationEnabled, - $chars = self::DEFAULT_CHARS, - SlugifyInterface $slugger = null + UrlShortenerOptions $options, + SlugifyInterface $slugger ) { $this->httpClient = $httpClient; $this->em = $em; - $this->urlValidationEnabled = (bool) $urlValidationEnabled; - $this->chars = empty($chars) ? self::DEFAULT_CHARS : $chars; - $this->slugger = $slugger ?: new Slugify(); + $this->options = $options; + $this->slugger = $slugger; } /** @@ -71,7 +68,7 @@ class UrlShortener implements UrlShortenerInterface ?int $maxVisits = null ): ShortUrl { // If the URL validation is enabled, check that the URL actually exists - if ($this->urlValidationEnabled) { + if ($this->options->isUrlValidationEnabled()) { $this->checkUrlExists($url); } $customSlug = $this->processCustomSlug($customSlug); @@ -121,16 +118,18 @@ class UrlShortener implements UrlShortenerInterface private function convertAutoincrementIdToShortCode(float $id): string { $id += self::ID_INCREMENT; // Increment the Id so that the generated shortcode is not too short - $length = strlen($this->chars); + $chars = $this->options->getChars(); + + $length = strlen($chars); $code = ''; while ($id > 0) { // Determine the value of the next higher character in the short code and prepend it - $code = $this->chars[(int) fmod($id, $length)] . $code; + $code = $chars[(int) fmod($id, $length)] . $code; $id = floor($id / $length); } - return $this->chars[(int) $id] . $code; + return $chars[(int) $id] . $code; } private function processCustomSlug(?string $customSlug): ?string @@ -157,9 +156,11 @@ class UrlShortener implements UrlShortenerInterface */ public function shortCodeToUrl(string $shortCode): ShortUrl { + $chars = $this->options->getChars(); + // Validate short code format - if (! preg_match('|[' . $this->chars . ']+|', $shortCode)) { - throw InvalidShortCodeException::fromCharset($shortCode, $this->chars); + if (! preg_match('|[' . $chars . ']+|', $shortCode)) { + throw InvalidShortCodeException::fromCharset($shortCode, $chars); } /** @var ShortUrlRepository $shortUrlRepo */ diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index a820e905..19896441 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -17,6 +17,7 @@ use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; +use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Service\UrlShortener; use Zend\Diactoros\Uri; @@ -57,16 +58,12 @@ class UrlShortenerTest extends TestCase $this->setUrlShortener(false); } - /** - * @param bool $urlValidationEnabled - */ - public function setUrlShortener($urlValidationEnabled) + public function setUrlShortener(bool $urlValidationEnabled) { $this->urlShortener = new UrlShortener( $this->httpClient->reveal(), $this->em->reveal(), - $urlValidationEnabled, - UrlShortener::DEFAULT_CHARS, + new UrlShortenerOptions(['validate_url' => $urlValidationEnabled]), $this->slugger->reveal() ); }