diff --git a/module/Core/src/ShortUrl/Model/Validation/ShortUrlInputFilter.php b/module/Core/src/ShortUrl/Model/Validation/ShortUrlInputFilter.php index 90b7e8e8..762a9230 100644 --- a/module/Core/src/ShortUrl/Model/Validation/ShortUrlInputFilter.php +++ b/module/Core/src/ShortUrl/Model/Validation/ShortUrlInputFilter.php @@ -12,6 +12,7 @@ use Shlinkio\Shlink\Common\Validation; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Rest\Entity\ApiKey; +use function is_string; use function preg_match; use function substr; @@ -134,7 +135,8 @@ class ShortUrlInputFilter extends InputFilter return (new Validator\ValidatorChain()) ->attach(new Validator\NotEmpty($emptyModifiers)) ->attach(new Validator\Callback( - fn (?string $value) => ($allowNull && $value === null) || preg_match(LOOSE_URI_MATCHER, $value) === 1 + // Non-strings is always allowed. Other validators will take care of those + static fn (mixed $value) => ! is_string($value) || preg_match(LOOSE_URI_MATCHER, $value) === 1, )); } } diff --git a/module/Core/test/Importer/ImportedLinksProcessorTest.php b/module/Core/test/Importer/ImportedLinksProcessorTest.php index d69d8480..95f7a7f8 100644 --- a/module/Core/test/Importer/ImportedLinksProcessorTest.php +++ b/module/Core/test/Importer/ImportedLinksProcessorTest.php @@ -67,9 +67,9 @@ class ImportedLinksProcessorTest extends TestCase public function newUrlsWithNoErrorsAreAllPersisted(): void { $urls = [ - new ImportedShlinkUrl(ImportSource::BITLY, 'foo', [], Chronos::now(), null, 'foo', null), - new ImportedShlinkUrl(ImportSource::BITLY, 'bar', [], Chronos::now(), null, 'bar', 'foo'), - new ImportedShlinkUrl(ImportSource::BITLY, 'baz', [], Chronos::now(), null, 'baz', null), + new ImportedShlinkUrl(ImportSource::BITLY, 'https://foo', [], Chronos::now(), null, 'foo', null), + new ImportedShlinkUrl(ImportSource::BITLY, 'https://bar', [], Chronos::now(), null, 'bar', 'foo'), + new ImportedShlinkUrl(ImportSource::BITLY, 'https://baz', [], Chronos::now(), null, 'baz', null), ]; $expectedCalls = count($urls); @@ -90,9 +90,9 @@ class ImportedLinksProcessorTest extends TestCase public function newUrlsWithErrorsAreSkipped(): void { $urls = [ - new ImportedShlinkUrl(ImportSource::BITLY, 'foo', [], Chronos::now(), null, 'foo', null), - new ImportedShlinkUrl(ImportSource::BITLY, 'bar', [], Chronos::now(), null, 'bar', 'foo'), - new ImportedShlinkUrl(ImportSource::BITLY, 'baz', [], Chronos::now(), null, 'baz', null), + new ImportedShlinkUrl(ImportSource::BITLY, 'https://foo', [], Chronos::now(), null, 'foo', null), + new ImportedShlinkUrl(ImportSource::BITLY, 'https://bar', [], Chronos::now(), null, 'bar', 'foo'), + new ImportedShlinkUrl(ImportSource::BITLY, 'https://baz', [], Chronos::now(), null, 'baz', null), ]; $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); @@ -126,8 +126,10 @@ class ImportedLinksProcessorTest extends TestCase $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $this->repo->expects($this->exactly(count($urls)))->method('findOneByImportedUrl')->willReturnCallback( - fn (ImportedShlinkUrl $url): ?ShortUrl - => contains(['foo', 'baz2', 'baz3'], $url->longUrl) ? ShortUrl::fromImport($url, true) : null, + fn (ImportedShlinkUrl $url): ?ShortUrl => contains( + ['https://foo', 'https://baz2', 'https://baz3'], + $url->longUrl, + ) ? ShortUrl::fromImport($url, true) : null, ); $this->shortCodeHelper->expects($this->exactly(2))->method('ensureShortCodeUniqueness')->willReturn(true); $this->em->expects($this->exactly(2))->method('persist')->with($this->isInstanceOf(ShortUrl::class)); @@ -143,11 +145,11 @@ class ImportedLinksProcessorTest extends TestCase public function nonUniqueShortCodesAreAskedToUser(): void { $urls = [ - new ImportedShlinkUrl(ImportSource::BITLY, 'foo', [], Chronos::now(), null, 'foo', null), - new ImportedShlinkUrl(ImportSource::BITLY, 'bar', [], Chronos::now(), null, 'bar', null), - new ImportedShlinkUrl(ImportSource::BITLY, 'baz', [], Chronos::now(), null, 'baz', 'foo'), - new ImportedShlinkUrl(ImportSource::BITLY, 'baz2', [], Chronos::now(), null, 'baz2', null), - new ImportedShlinkUrl(ImportSource::BITLY, 'baz3', [], Chronos::now(), null, 'baz3', 'bar'), + new ImportedShlinkUrl(ImportSource::BITLY, 'https://foo', [], Chronos::now(), null, 'foo', null), + new ImportedShlinkUrl(ImportSource::BITLY, 'https://bar', [], Chronos::now(), null, 'bar', null), + new ImportedShlinkUrl(ImportSource::BITLY, 'https://baz', [], Chronos::now(), null, 'baz', 'foo'), + new ImportedShlinkUrl(ImportSource::BITLY, 'https://baz2', [], Chronos::now(), null, 'baz2', null), + new ImportedShlinkUrl(ImportSource::BITLY, 'https://baz3', [], Chronos::now(), null, 'baz3', 'bar'), ]; $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); @@ -191,7 +193,7 @@ class ImportedLinksProcessorTest extends TestCase { $now = Chronos::now(); $createImportedUrl = static fn (array $visits) => - new ImportedShlinkUrl(ImportSource::BITLY, 's', [], $now, null, 's', null, $visits); + new ImportedShlinkUrl(ImportSource::BITLY, 'https://s', [], $now, null, 's', null, $visits); yield 'new short URL' => [$createImportedUrl([ new ImportedShlinkVisit('', '', $now, null), diff --git a/module/Core/test/ShortUrl/Entity/ShortUrlTest.php b/module/Core/test/ShortUrl/Entity/ShortUrlTest.php index 4a11d26a..bd83fd9a 100644 --- a/module/Core/test/ShortUrl/Entity/ShortUrlTest.php +++ b/module/Core/test/ShortUrl/Entity/ShortUrlTest.php @@ -98,26 +98,26 @@ class ShortUrlTest extends TestCase $shortUrl->update(ShortUrlEdition::fromRawData([ ShortUrlInputFilter::DEVICE_LONG_URLS => [ - DeviceType::ANDROID->value => 'android', - DeviceType::IOS->value => 'ios', + DeviceType::ANDROID->value => 'https://android', + DeviceType::IOS->value => 'https://ios', ], ])); self::assertEquals([ - DeviceType::ANDROID->value => 'android', - DeviceType::IOS->value => 'ios', + DeviceType::ANDROID->value => 'https://android', + DeviceType::IOS->value => 'https://ios', DeviceType::DESKTOP->value => null, ], $shortUrl->deviceLongUrls()); $shortUrl->update(ShortUrlEdition::fromRawData([ ShortUrlInputFilter::DEVICE_LONG_URLS => [ DeviceType::ANDROID->value => null, - DeviceType::DESKTOP->value => 'desktop', + DeviceType::DESKTOP->value => 'https://desktop', ], ])); self::assertEquals([ DeviceType::ANDROID->value => null, - DeviceType::IOS->value => 'ios', - DeviceType::DESKTOP->value => 'desktop', + DeviceType::IOS->value => 'https://ios', + DeviceType::DESKTOP->value => 'https://desktop', ], $shortUrl->deviceLongUrls()); $shortUrl->update(ShortUrlEdition::fromRawData([ @@ -129,7 +129,7 @@ class ShortUrlTest extends TestCase self::assertEquals([ DeviceType::ANDROID->value => null, DeviceType::IOS->value => null, - DeviceType::DESKTOP->value => 'desktop', + DeviceType::DESKTOP->value => 'https://desktop', ], $shortUrl->deviceLongUrls()); } @@ -139,7 +139,7 @@ class ShortUrlTest extends TestCase $range = range(1, 1000); // Use a "big" number to reduce false negatives $allFor = static fn (ShortUrlMode $mode): bool => every($range, static function () use ($mode): bool { $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData( - [ShortUrlInputFilter::LONG_URL => 'foo'], + [ShortUrlInputFilter::LONG_URL => 'https://foo'], new UrlShortenerOptions(mode: $mode), )); $shortCode = $shortUrl->getShortCode(); diff --git a/module/Core/test/ShortUrl/Model/ShortUrlCreationTest.php b/module/Core/test/ShortUrl/Model/ShortUrlCreationTest.php index 6712c49f..adecf9e9 100644 --- a/module/Core/test/ShortUrl/Model/ShortUrlCreationTest.php +++ b/module/Core/test/ShortUrl/Model/ShortUrlCreationTest.php @@ -33,37 +33,37 @@ class ShortUrlCreationTest extends TestCase { yield [[]]; yield [[ - ShortUrlInputFilter::LONG_URL => 'foo', + ShortUrlInputFilter::LONG_URL => 'https://foo', ShortUrlInputFilter::VALID_SINCE => '', ShortUrlInputFilter::VALID_UNTIL => '', ShortUrlInputFilter::CUSTOM_SLUG => 'foobar', ShortUrlInputFilter::MAX_VISITS => 'invalid', ]]; yield [[ - ShortUrlInputFilter::LONG_URL => 'foo', + ShortUrlInputFilter::LONG_URL => 'https://foo', ShortUrlInputFilter::VALID_SINCE => '2017', ShortUrlInputFilter::MAX_VISITS => 5, ]]; yield [[ - ShortUrlInputFilter::LONG_URL => 'foo', + ShortUrlInputFilter::LONG_URL => 'https://foo', ShortUrlInputFilter::VALID_SINCE => new stdClass(), ShortUrlInputFilter::VALID_UNTIL => 'foo', ]]; yield [[ - ShortUrlInputFilter::LONG_URL => 'foo', + ShortUrlInputFilter::LONG_URL => 'https://foo', ShortUrlInputFilter::VALID_UNTIL => 500, ShortUrlInputFilter::DOMAIN => 4, ]]; yield [[ - ShortUrlInputFilter::LONG_URL => 'foo', + ShortUrlInputFilter::LONG_URL => 'https://foo', ShortUrlInputFilter::SHORT_CODE_LENGTH => 3, ]]; yield [[ - ShortUrlInputFilter::LONG_URL => 'foo', + ShortUrlInputFilter::LONG_URL => 'https://foo', ShortUrlInputFilter::CUSTOM_SLUG => '', ]]; yield [[ - ShortUrlInputFilter::LONG_URL => 'foo', + ShortUrlInputFilter::LONG_URL => 'https://foo', ShortUrlInputFilter::CUSTOM_SLUG => ' ', ]]; yield [[ @@ -73,33 +73,36 @@ class ShortUrlCreationTest extends TestCase ShortUrlInputFilter::LONG_URL => null, ]]; yield [[ - ShortUrlInputFilter::LONG_URL => 'foo', + ShortUrlInputFilter::LONG_URL => 'missing_schema', + ]]; + yield [[ + ShortUrlInputFilter::LONG_URL => 'https://foo', ShortUrlInputFilter::DEVICE_LONG_URLS => [ 'invalid' => 'https://shlink.io', ], ]]; yield [[ - ShortUrlInputFilter::LONG_URL => 'foo', + ShortUrlInputFilter::LONG_URL => 'https://foo', ShortUrlInputFilter::DEVICE_LONG_URLS => [ DeviceType::DESKTOP->value => '', ], ]]; yield [[ - ShortUrlInputFilter::LONG_URL => 'foo', + ShortUrlInputFilter::LONG_URL => 'https://foo', ShortUrlInputFilter::DEVICE_LONG_URLS => [ DeviceType::DESKTOP->value => null, ], ]]; yield [[ - ShortUrlInputFilter::LONG_URL => 'foo', + ShortUrlInputFilter::LONG_URL => 'https://foo', ShortUrlInputFilter::DEVICE_LONG_URLS => [ DeviceType::IOS->value => ' ', ], ]]; yield [[ - ShortUrlInputFilter::LONG_URL => 'foo', + ShortUrlInputFilter::LONG_URL => 'https://foo', ShortUrlInputFilter::DEVICE_LONG_URLS => [ - DeviceType::IOS->value => 'bar', + DeviceType::IOS->value => 'https://bar', DeviceType::ANDROID->value => [], ], ]];