Moved check for URL validation config option to the UrlValidator itself

This commit is contained in:
Alejandro Celaya 2020-03-22 16:58:28 +01:00
parent d29ebb706e
commit 682a0768b7
5 changed files with 34 additions and 43 deletions

View file

@ -51,7 +51,7 @@ return [
Options\NotFoundRedirectOptions::class => ['config.not_found_redirects'], Options\NotFoundRedirectOptions::class => ['config.not_found_redirects'],
Options\UrlShortenerOptions::class => ['config.url_shortener'], Options\UrlShortenerOptions::class => ['config.url_shortener'],
Service\UrlShortener::class => [Util\UrlValidator::class, 'em', Options\UrlShortenerOptions::class], Service\UrlShortener::class => [Util\UrlValidator::class, 'em'],
Service\VisitsTracker::class => ['em', EventDispatcherInterface::class], Service\VisitsTracker::class => ['em', EventDispatcherInterface::class],
Service\ShortUrlService::class => ['em', Service\ShortUrl\ShortUrlResolver::class], Service\ShortUrlService::class => ['em', Service\ShortUrl\ShortUrlResolver::class],
Service\VisitService::class => ['em'], Service\VisitService::class => ['em'],
@ -63,7 +63,7 @@ return [
], ],
Service\ShortUrl\ShortUrlResolver::class => ['em'], Service\ShortUrl\ShortUrlResolver::class => ['em'],
Util\UrlValidator::class => ['httpClient'], Util\UrlValidator::class => ['httpClient', Options\UrlShortenerOptions::class],
Action\RedirectAction::class => [ Action\RedirectAction::class => [
Service\ShortUrl\ShortUrlResolver::class, Service\ShortUrl\ShortUrlResolver::class,

View file

@ -11,7 +11,6 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\Core\Util\TagManagerTrait;
use Shlinkio\Shlink\Core\Util\UrlValidatorInterface; use Shlinkio\Shlink\Core\Util\UrlValidatorInterface;
@ -24,17 +23,14 @@ class UrlShortener implements UrlShortenerInterface
use TagManagerTrait; use TagManagerTrait;
private EntityManagerInterface $em; private EntityManagerInterface $em;
private UrlShortenerOptions $options;
private UrlValidatorInterface $urlValidator; private UrlValidatorInterface $urlValidator;
public function __construct( public function __construct(
UrlValidatorInterface $urlValidator, UrlValidatorInterface $urlValidator,
EntityManagerInterface $em, EntityManagerInterface $em
UrlShortenerOptions $options
) { ) {
$this->urlValidator = $urlValidator; $this->urlValidator = $urlValidator;
$this->em = $em; $this->em = $em;
$this->options = $options;
} }
/** /**
@ -53,11 +49,7 @@ class UrlShortener implements UrlShortenerInterface
return $existingShortUrl; return $existingShortUrl;
} }
// If the URL validation is enabled, check that the URL actually exists $this->urlValidator->validateUrl($url);
if ($this->options->isUrlValidationEnabled()) {
$this->urlValidator->validateUrl($url);
}
$this->em->beginTransaction(); $this->em->beginTransaction();
$shortUrl = new ShortUrl($url, $meta, new PersistenceDomainResolver($this->em)); $shortUrl = new ShortUrl($url, $meta, new PersistenceDomainResolver($this->em));
$shortUrl->setTags($this->tagNamesToEntities($this->em, $tags)); $shortUrl->setTags($this->tagNamesToEntities($this->em, $tags));

View file

@ -9,16 +9,19 @@ use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\RequestOptions; use GuzzleHttp\RequestOptions;
use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
class UrlValidator implements UrlValidatorInterface, RequestMethodInterface class UrlValidator implements UrlValidatorInterface, RequestMethodInterface
{ {
private const MAX_REDIRECTS = 15; private const MAX_REDIRECTS = 15;
private ClientInterface $httpClient; private ClientInterface $httpClient;
private UrlShortenerOptions $options;
public function __construct(ClientInterface $httpClient) public function __construct(ClientInterface $httpClient, UrlShortenerOptions $options)
{ {
$this->httpClient = $httpClient; $this->httpClient = $httpClient;
$this->options = $options;
} }
/** /**
@ -26,6 +29,11 @@ class UrlValidator implements UrlValidatorInterface, RequestMethodInterface
*/ */
public function validateUrl(string $url): void public function validateUrl(string $url): void
{ {
// If the URL validation is not enabled, skip check
if (! $this->options->isUrlValidationEnabled()) {
return;
}
try { try {
$this->httpClient->request(self::METHOD_GET, $url, [ $this->httpClient->request(self::METHOD_GET, $url, [
RequestOptions::ALLOW_REDIRECTS => ['max' => self::MAX_REDIRECTS], RequestOptions::ALLOW_REDIRECTS => ['max' => self::MAX_REDIRECTS],

View file

@ -17,7 +17,6 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Core\Service\UrlShortener;
use Shlinkio\Shlink\Core\Util\UrlValidatorInterface; use Shlinkio\Shlink\Core\Util\UrlValidatorInterface;
@ -33,6 +32,10 @@ class UrlShortenerTest extends TestCase
public function setUp(): void public function setUp(): void
{ {
$this->urlValidator = $this->prophesize(UrlValidatorInterface::class); $this->urlValidator = $this->prophesize(UrlValidatorInterface::class);
$this->urlValidator->validateUrl('http://foobar.com/12345/hello?foo=bar')->will(
function (): void {
},
);
$this->em = $this->prophesize(EntityManagerInterface::class); $this->em = $this->prophesize(EntityManagerInterface::class);
$conn = $this->prophesize(Connection::class); $conn = $this->prophesize(Connection::class);
@ -50,16 +53,7 @@ class UrlShortenerTest extends TestCase
$repo->shortCodeIsInUse(Argument::cetera())->willReturn(false); $repo->shortCodeIsInUse(Argument::cetera())->willReturn(false);
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
$this->setUrlShortener(false); $this->urlShortener = new UrlShortener($this->urlValidator->reveal(), $this->em->reveal());
}
private function setUrlShortener(bool $urlValidationEnabled): void
{
$this->urlShortener = new UrlShortener(
$this->urlValidator->reveal(),
$this->em->reveal(),
new UrlShortenerOptions(['validate_url' => $urlValidationEnabled]),
);
} }
/** @test */ /** @test */
@ -119,24 +113,6 @@ class UrlShortenerTest extends TestCase
); );
} }
/** @test */
public function validatorIsCalledWhenUrlValidationIsEnabled(): void
{
$this->setUrlShortener(true);
$validateUrl = $this->urlValidator->validateUrl('http://foobar.com/12345/hello?foo=bar')->will(
function (): void {
},
);
$this->urlShortener->urlToShortCode(
new Uri('http://foobar.com/12345/hello?foo=bar'),
[],
ShortUrlMeta::createEmpty(),
);
$validateUrl->shouldHaveBeenCalledOnce();
}
/** @test */ /** @test */
public function exceptionIsThrownWhenNonUniqueSlugIsProvided(): void public function exceptionIsThrownWhenNonUniqueSlugIsProvided(): void
{ {
@ -175,6 +151,7 @@ class UrlShortenerTest extends TestCase
$findExisting->shouldHaveBeenCalledOnce(); $findExisting->shouldHaveBeenCalledOnce();
$getRepo->shouldHaveBeenCalledOnce(); $getRepo->shouldHaveBeenCalledOnce();
$this->em->persist(Argument::cetera())->shouldNotHaveBeenCalled(); $this->em->persist(Argument::cetera())->shouldNotHaveBeenCalled();
$this->urlValidator->validateUrl(Argument::cetera())->shouldNotHaveBeenCalled();
$this->assertSame($expected, $result); $this->assertSame($expected, $result);
} }

View file

@ -13,17 +13,20 @@ use PHPUnit\Framework\TestCase;
use Prophecy\Argument; use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Core\Util\UrlValidator; use Shlinkio\Shlink\Core\Util\UrlValidator;
class UrlValidatorTest extends TestCase class UrlValidatorTest extends TestCase
{ {
private UrlValidator $urlValidator; private UrlValidator $urlValidator;
private ObjectProphecy $httpClient; private ObjectProphecy $httpClient;
private UrlShortenerOptions $options;
public function setUp(): void public function setUp(): void
{ {
$this->httpClient = $this->prophesize(ClientInterface::class); $this->httpClient = $this->prophesize(ClientInterface::class);
$this->urlValidator = new UrlValidator($this->httpClient->reveal()); $this->options = new UrlShortenerOptions(['validate_url' => true]);
$this->urlValidator = new UrlValidator($this->httpClient->reveal(), $this->options);
} }
/** @test */ /** @test */
@ -52,4 +55,15 @@ class UrlValidatorTest extends TestCase
$request->shouldHaveBeenCalledOnce(); $request->shouldHaveBeenCalledOnce();
} }
/** @test */
public function noCheckIsPerformedWhenUrlValidationIsDisabled(): void
{
$request = $this->httpClient->request(Argument::cetera())->willReturn(new Response());
$this->options->validateUrl = false;
$this->urlValidator->validateUrl('');
$request->shouldNotHaveBeenCalled();
}
} }