diff --git a/module/Core/src/Validation/ShortUrlMetaInputFilter.php b/module/Core/src/Validation/ShortUrlMetaInputFilter.php index a71b47fc..068cf831 100644 --- a/module/Core/src/Validation/ShortUrlMetaInputFilter.php +++ b/module/Core/src/Validation/ShortUrlMetaInputFilter.php @@ -50,9 +50,7 @@ class ShortUrlMetaInputFilter extends InputFilter $this->add($this->createBooleanInput(self::FIND_IF_EXISTS, false)); $domain = $this->createInput(self::DOMAIN, false); - $domain->getValidatorChain()->attach(new Validator\Hostname([ - 'allow' => Validator\Hostname::ALLOW_DNS | Validator\Hostname::ALLOW_LOCAL, - ])); + $domain->getValidatorChain()->attach(new Validation\HostAndPortValidator()); $this->add($domain); } } diff --git a/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php index aca6e0d8..3b0a0b61 100644 --- a/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php @@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl; use Cake\Chronos\Chronos; use Psr\Http\Message\ServerRequestInterface as Request; use Shlinkio\Shlink\Core\Exception\InvalidArgumentException; +use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Model\CreateShortUrlData; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Zend\Diactoros\Uri; @@ -29,18 +30,20 @@ class CreateShortUrlAction extends AbstractCreateShortUrlAction throw new InvalidArgumentException('A URL was not provided'); } - return new CreateShortUrlData( - new Uri($postData['longUrl']), - (array) ($postData['tags'] ?? []), - ShortUrlMeta::createFromParams( + try { + $meta = ShortUrlMeta::createFromParams( $this->getOptionalDate($postData, 'validSince'), $this->getOptionalDate($postData, 'validUntil'), $postData['customSlug'] ?? null, $postData['maxVisits'] ?? null, $postData['findIfExists'] ?? null, $postData['domain'] ?? null - ) - ); + ); + + return new CreateShortUrlData(new Uri($postData['longUrl']), (array) ($postData['tags'] ?? []), $meta); + } catch (ValidationException $e) { + throw new InvalidArgumentException('Provided meta data is not valid', -1, $e); + } } private function getOptionalDate(array $postData, string $fieldName): ?Chronos diff --git a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php index 8b00229e..cea67f0f 100644 --- a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php @@ -15,6 +15,7 @@ use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Rest\Action\ShortUrl\CreateShortUrlAction; use Shlinkio\Shlink\Rest\Util\RestUtils; +use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\ServerRequest; use Zend\Diactoros\Uri; @@ -50,8 +51,8 @@ class CreateShortUrlActionTest extends TestCase { $shortUrl = new ShortUrl(''); $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), Argument::cetera()) - ->willReturn($shortUrl) - ->shouldBeCalledOnce(); + ->willReturn($shortUrl) + ->shouldBeCalledOnce(); $request = (new ServerRequest())->withParsedBody([ 'longUrl' => 'http://www.domain.com/foo/bar', @@ -65,8 +66,8 @@ class CreateShortUrlActionTest extends TestCase public function anInvalidUrlReturnsError(): void { $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), Argument::cetera()) - ->willThrow(InvalidUrlException::class) - ->shouldBeCalledOnce(); + ->willThrow(InvalidUrlException::class) + ->shouldBeCalledOnce(); $request = (new ServerRequest())->withParsedBody([ 'longUrl' => 'http://www.domain.com/foo/bar', @@ -76,6 +77,35 @@ class CreateShortUrlActionTest extends TestCase $this->assertTrue(strpos($response->getBody()->getContents(), RestUtils::INVALID_URL_ERROR) > 0); } + /** + * @test + * @dataProvider provideInvalidDomains + */ + public function anInvalidDomainReturnsError(string $domain): void + { + $shortUrl = new ShortUrl(''); + $urlToShortCode = $this->urlShortener->urlToShortCode(Argument::cetera())->willReturn($shortUrl); + + $request = (new ServerRequest())->withParsedBody([ + 'longUrl' => 'http://www.domain.com/foo/bar', + 'domain' => $domain, + ]); + /** @var JsonResponse $response */ + $response = $this->action->handle($request); + $payload = $response->getPayload(); + + $this->assertEquals(400, $response->getStatusCode()); + $this->assertEquals(RestUtils::INVALID_ARGUMENT_ERROR, $payload['error']); + $urlToShortCode->shouldNotHaveBeenCalled(); + } + + public function provideInvalidDomains(): iterable + { + yield ['localhost:80000']; + yield ['127.0.0.1']; + yield ['???/&%$&']; + } + /** @test */ public function nonUniqueSlugReturnsError(): void {