Fixed merge conflicts

This commit is contained in:
Alejandro Celaya 2022-02-04 18:03:29 +01:00
commit af1ae0399c
17 changed files with 61 additions and 61 deletions

View file

@ -80,7 +80,7 @@ jobs:
with: with:
php-version: ${{ matrix.php-version }} php-version: ${{ matrix.php-version }}
tools: composer tools: composer
extensions: openswoole-4.9.1, pdo_sqlsrv-5.10.0beta2 extensions: openswoole-4.9.1, pdo_sqlsrv-5.10.0
coverage: pcov coverage: pcov
ini-values: pcov.directory=module ini-values: pcov.directory=module
- run: composer install --no-interaction --prefer-dist - run: composer install --no-interaction --prefer-dist

View file

@ -21,6 +21,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
* *Nothing* * *Nothing*
## [3.0.1] - 2022-02-04
### Added
* *Nothing*
### Changed
* *Nothing*
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#1363](https://github.com/shlinkio/shlink/issues/1363) Fixed titles being resolved no matter what when `validateUrl` is not set or is explicitly set to true.
* [#1352](https://github.com/shlinkio/shlink/issues/1352) Updated to stable pdo_sqlsrv in docker image.
## [3.0.0] - 2022-01-28 ## [3.0.0] - 2022-01-28
### Added ### Added
* [#767](https://github.com/shlinkio/shlink/issues/767) Added full support to use emojis everywhere, whether it is custom slugs, titles, referrers, etc. * [#767](https://github.com/shlinkio/shlink/issues/767) Added full support to use emojis everywhere, whether it is custom slugs, titles, referrers, etc.

View file

@ -3,7 +3,7 @@ FROM php:8.1.1-alpine3.15 as base
ARG SHLINK_VERSION=latest ARG SHLINK_VERSION=latest
ENV SHLINK_VERSION ${SHLINK_VERSION} ENV SHLINK_VERSION ${SHLINK_VERSION}
ENV OPENSWOOLE_VERSION 4.9.1 ENV OPENSWOOLE_VERSION 4.9.1
ENV PDO_SQLSRV_VERSION 5.10.0beta2 ENV PDO_SQLSRV_VERSION 5.10.0
ENV MS_ODBC_SQL_VERSION 17.5.2.2 ENV MS_ODBC_SQL_VERSION 17.5.2.2
ENV LC_ALL "C" ENV LC_ALL "C"

View file

@ -128,7 +128,7 @@
], ],
"test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox", "test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox",
"test:unit:ci": "@test:unit --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml", "test:unit:ci": "@test:unit --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml",
"test:unit:pretty": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage-unit/coverage-html", "test:unit:pretty": "@test:unit --coverage-html build/coverage-unit/coverage-html",
"test:db": "@parallel test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms", "test:db": "@parallel test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
"test:db:sqlite": "APP_ENV=test php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml", "test:db:sqlite": "APP_ENV=test php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml",
"test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov --coverage-xml=build/coverage-db/coverage-xml --log-junit=build/coverage-db/junit.xml", "test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov --coverage-xml=build/coverage-db/coverage-xml --log-junit=build/coverage-db/junit.xml",

View file

@ -12,7 +12,7 @@ const MIN_SHORT_CODES_LENGTH = 4;
const DEFAULT_REDIRECT_STATUS_CODE = StatusCodeInterface::STATUS_FOUND; const DEFAULT_REDIRECT_STATUS_CODE = StatusCodeInterface::STATUS_FOUND;
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30; const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory'; const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside an html title tag const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside a html title tag
const DEFAULT_QR_CODE_SIZE = 300; const DEFAULT_QR_CODE_SIZE = 300;
const DEFAULT_QR_CODE_MARGIN = 0; const DEFAULT_QR_CODE_MARGIN = 0;
const DEFAULT_QR_CODE_FORMAT = 'png'; const DEFAULT_QR_CODE_FORMAT = 'png';

View file

@ -2,7 +2,7 @@ FROM php:8.1.1-fpm-alpine3.15
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com> MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
ENV APCU_VERSION 5.1.21 ENV APCU_VERSION 5.1.21
ENV PDO_SQLSRV_VERSION 5.10.0beta2 ENV PDO_SQLSRV_VERSION 5.10.0
ENV MS_ODBC_SQL_VERSION 17.5.2.2 ENV MS_ODBC_SQL_VERSION 17.5.2.2
RUN apk update RUN apk update

View file

@ -4,7 +4,7 @@ MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
ENV APCU_VERSION 5.1.21 ENV APCU_VERSION 5.1.21
ENV INOTIFY_VERSION 3.0.0 ENV INOTIFY_VERSION 3.0.0
ENV OPENSWOOLE_VERSION 4.9.1 ENV OPENSWOOLE_VERSION 4.9.1
ENV PDO_SQLSRV_VERSION 5.10.0beta2 ENV PDO_SQLSRV_VERSION 5.10.0
ENV MS_ODBC_SQL_VERSION 17.5.2.2 ENV MS_ODBC_SQL_VERSION 17.5.2.2
RUN apk update RUN apk update

View file

@ -29,7 +29,7 @@ final class ShortUrlEdit implements TitleResolutionModelInterface
private bool $titlePropWasProvided = false; private bool $titlePropWasProvided = false;
private ?string $title = null; private ?string $title = null;
private bool $titleWasAutoResolved = false; private bool $titleWasAutoResolved = false;
private ?bool $validateUrl = null; private bool $validateUrl = false;
private bool $crawlablePropWasProvided = false; private bool $crawlablePropWasProvided = false;
private bool $crawlable = false; private bool $crawlable = false;
private bool $forwardQueryPropWasProvided = false; private bool $forwardQueryPropWasProvided = false;
@ -72,7 +72,7 @@ final class ShortUrlEdit implements TitleResolutionModelInterface
$this->validSince = parseDateField($inputFilter->getValue(ShortUrlInputFilter::VALID_SINCE)); $this->validSince = parseDateField($inputFilter->getValue(ShortUrlInputFilter::VALID_SINCE));
$this->validUntil = parseDateField($inputFilter->getValue(ShortUrlInputFilter::VALID_UNTIL)); $this->validUntil = parseDateField($inputFilter->getValue(ShortUrlInputFilter::VALID_UNTIL));
$this->maxVisits = getOptionalIntFromInputFilter($inputFilter, ShortUrlInputFilter::MAX_VISITS); $this->maxVisits = getOptionalIntFromInputFilter($inputFilter, ShortUrlInputFilter::MAX_VISITS);
$this->validateUrl = getOptionalBoolFromInputFilter($inputFilter, ShortUrlInputFilter::VALIDATE_URL); $this->validateUrl = getOptionalBoolFromInputFilter($inputFilter, ShortUrlInputFilter::VALIDATE_URL) ?? false;
$this->tags = $inputFilter->getValue(ShortUrlInputFilter::TAGS); $this->tags = $inputFilter->getValue(ShortUrlInputFilter::TAGS);
$this->title = $inputFilter->getValue(ShortUrlInputFilter::TITLE); $this->title = $inputFilter->getValue(ShortUrlInputFilter::TITLE);
$this->crawlable = $inputFilter->getValue(ShortUrlInputFilter::CRAWLABLE); $this->crawlable = $inputFilter->getValue(ShortUrlInputFilter::CRAWLABLE);
@ -166,7 +166,7 @@ final class ShortUrlEdit implements TitleResolutionModelInterface
return $copy; return $copy;
} }
public function doValidateUrl(): ?bool public function doValidateUrl(): bool
{ {
return $this->validateUrl; return $this->validateUrl;
} }

View file

@ -26,7 +26,7 @@ final class ShortUrlMeta implements TitleResolutionModelInterface
private ?bool $findIfExists = null; private ?bool $findIfExists = null;
private ?string $domain = null; private ?string $domain = null;
private int $shortCodeLength = 5; private int $shortCodeLength = 5;
private ?bool $validateUrl = null; private bool $validateUrl = false;
private ?ApiKey $apiKey = null; private ?ApiKey $apiKey = null;
private array $tags = []; private array $tags = [];
private ?string $title = null; private ?string $title = null;
@ -73,7 +73,7 @@ final class ShortUrlMeta implements TitleResolutionModelInterface
$this->customSlug = $inputFilter->getValue(ShortUrlInputFilter::CUSTOM_SLUG); $this->customSlug = $inputFilter->getValue(ShortUrlInputFilter::CUSTOM_SLUG);
$this->maxVisits = getOptionalIntFromInputFilter($inputFilter, ShortUrlInputFilter::MAX_VISITS); $this->maxVisits = getOptionalIntFromInputFilter($inputFilter, ShortUrlInputFilter::MAX_VISITS);
$this->findIfExists = $inputFilter->getValue(ShortUrlInputFilter::FIND_IF_EXISTS); $this->findIfExists = $inputFilter->getValue(ShortUrlInputFilter::FIND_IF_EXISTS);
$this->validateUrl = getOptionalBoolFromInputFilter($inputFilter, ShortUrlInputFilter::VALIDATE_URL); $this->validateUrl = getOptionalBoolFromInputFilter($inputFilter, ShortUrlInputFilter::VALIDATE_URL) ?? false;
$this->domain = $inputFilter->getValue(ShortUrlInputFilter::DOMAIN); $this->domain = $inputFilter->getValue(ShortUrlInputFilter::DOMAIN);
$this->shortCodeLength = getOptionalIntFromInputFilter( $this->shortCodeLength = getOptionalIntFromInputFilter(
$inputFilter, $inputFilter,
@ -151,7 +151,7 @@ final class ShortUrlMeta implements TitleResolutionModelInterface
return $this->shortCodeLength; return $this->shortCodeLength;
} }
public function doValidateUrl(): ?bool public function doValidateUrl(): bool
{ {
return $this->validateUrl; return $this->validateUrl;
} }

View file

@ -10,20 +10,9 @@ class UrlShortenerOptions extends AbstractOptions
{ {
protected $__strictMode__ = false; // phpcs:ignore protected $__strictMode__ = false; // phpcs:ignore
private bool $validateUrl = true;
private bool $autoResolveTitles = false; private bool $autoResolveTitles = false;
private bool $appendExtraPath = false; private bool $appendExtraPath = false;
public function isUrlValidationEnabled(): bool
{
return $this->validateUrl;
}
protected function setValidateUrl(bool $validateUrl): void
{
$this->validateUrl = $validateUrl;
}
public function autoResolveTitles(): bool public function autoResolveTitles(): bool
{ {
return $this->autoResolveTitles; return $this->autoResolveTitles;

View file

@ -10,7 +10,7 @@ interface TitleResolutionModelInterface
public function getLongUrl(): string; public function getLongUrl(): string;
public function doValidateUrl(): ?bool; public function doValidateUrl(): bool;
public function withResolvedTitle(string $title): self; public function withResolvedTitle(string $title): self;
} }

View file

@ -30,10 +30,8 @@ class UrlValidator implements UrlValidatorInterface, RequestMethodInterface
/** /**
* @throws InvalidUrlException * @throws InvalidUrlException
*/ */
public function validateUrl(string $url, ?bool $doValidate): void public function validateUrl(string $url, bool $doValidate): void
{ {
// If the URL validation is not enabled, or it was explicitly set to not validate, skip check
$doValidate = $doValidate ?? $this->options->isUrlValidationEnabled();
if (! $doValidate) { if (! $doValidate) {
return; return;
} }
@ -41,15 +39,14 @@ class UrlValidator implements UrlValidatorInterface, RequestMethodInterface
$this->validateUrlAndGetResponse($url, true); $this->validateUrlAndGetResponse($url, true);
} }
public function validateUrlWithTitle(string $url, ?bool $doValidate): ?string public function validateUrlWithTitle(string $url, bool $doValidate): ?string
{ {
$doValidate = $doValidate ?? $this->options->isUrlValidationEnabled();
if (! $doValidate && ! $this->options->autoResolveTitles()) { if (! $doValidate && ! $this->options->autoResolveTitles()) {
return null; return null;
} }
$response = $this->validateUrlAndGetResponse($url, $doValidate); $response = $this->validateUrlAndGetResponse($url, $doValidate);
if ($response === null) { if ($response === null || ! $this->options->autoResolveTitles()) {
return null; return null;
} }

View file

@ -11,10 +11,10 @@ interface UrlValidatorInterface
/** /**
* @throws InvalidUrlException * @throws InvalidUrlException
*/ */
public function validateUrl(string $url, ?bool $doValidate): void; public function validateUrl(string $url, bool $doValidate): void;
/** /**
* @throws InvalidUrlException * @throws InvalidUrlException
*/ */
public function validateUrlWithTitle(string $url, ?bool $doValidate): ?string; public function validateUrlWithTitle(string $url, bool $doValidate): ?string;
} }

View file

@ -35,10 +35,10 @@ class ShortUrlTitleResolutionHelperTest extends TestCase
ShortUrlMeta::fromRawData(['longUrl' => $longUrl, 'title' => $title]), ShortUrlMeta::fromRawData(['longUrl' => $longUrl, 'title' => $title]),
); );
$this->urlValidator->validateUrlWithTitle($longUrl, null)->shouldHaveBeenCalledTimes( $this->urlValidator->validateUrlWithTitle($longUrl, false)->shouldHaveBeenCalledTimes(
$validateWithTitleCallsNum, $validateWithTitleCallsNum,
); );
$this->urlValidator->validateUrl($longUrl, null)->shouldHaveBeenCalledTimes($validateCallsNum); $this->urlValidator->validateUrl($longUrl, false)->shouldHaveBeenCalledTimes($validateCallsNum);
} }
public function provideTitles(): iterable public function provideTitles(): iterable

View file

@ -42,7 +42,7 @@ class UrlValidatorTest extends TestCase
$request->shouldBeCalledOnce(); $request->shouldBeCalledOnce();
$this->expectException(InvalidUrlException::class); $this->expectException(InvalidUrlException::class);
$this->urlValidator->validateUrl('http://foobar.com/12345/hello?foo=bar', null); $this->urlValidator->validateUrl('http://foobar.com/12345/hello?foo=bar', true);
} }
/** @test */ /** @test */
@ -65,50 +65,33 @@ class UrlValidatorTest extends TestCase
}), }),
)->willReturn(new Response()); )->willReturn(new Response());
$this->urlValidator->validateUrl($expectedUrl, null); $this->urlValidator->validateUrl($expectedUrl, true);
$request->shouldHaveBeenCalledOnce(); $request->shouldHaveBeenCalledOnce();
} }
/** /** @test */
* @test public function noCheckIsPerformedWhenUrlValidationIsDisabled(): void
* @dataProvider provideDisabledCombinations
*/
public function noCheckIsPerformedWhenUrlValidationIsDisabled(?bool $doValidate, bool $validateUrl): void
{ {
$request = $this->httpClient->request(Argument::cetera())->willReturn(new Response()); $request = $this->httpClient->request(Argument::cetera())->willReturn(new Response());
$this->options->validateUrl = $validateUrl;
$this->urlValidator->validateUrl('', $doValidate); $this->urlValidator->validateUrl('', false);
$request->shouldNotHaveBeenCalled(); $request->shouldNotHaveBeenCalled();
} }
/** /** @test */
* @test public function validateUrlWithTitleReturnsNullWhenRequestFailsAndValidationIsDisabled(): void
* @dataProvider provideDisabledCombinations {
*/
public function validateUrlWithTitleReturnsNullWhenRequestFailsAndValidationIsDisabled(
?bool $doValidate,
bool $validateUrl,
): void {
$request = $this->httpClient->request(Argument::cetera())->willThrow(ClientException::class); $request = $this->httpClient->request(Argument::cetera())->willThrow(ClientException::class);
$this->options->validateUrl = $validateUrl;
$this->options->autoResolveTitles = true; $this->options->autoResolveTitles = true;
$result = $this->urlValidator->validateUrlWithTitle('http://foobar.com/12345/hello?foo=bar', $doValidate); $result = $this->urlValidator->validateUrlWithTitle('http://foobar.com/12345/hello?foo=bar', false);
self::assertNull($result); self::assertNull($result);
$request->shouldHaveBeenCalledOnce(); $request->shouldHaveBeenCalledOnce();
} }
public function provideDisabledCombinations(): iterable
{
yield 'config is disabled and no runtime option is provided' => [null, false];
yield 'config is enabled but runtime option is disabled' => [false, true];
yield 'both config and runtime option are disabled' => [false, false];
}
/** @test */ /** @test */
public function validateUrlWithTitleReturnsNullWhenAutoResolutionIsDisabled(): void public function validateUrlWithTitleReturnsNullWhenAutoResolutionIsDisabled(): void
{ {
@ -121,6 +104,18 @@ class UrlValidatorTest extends TestCase
$request->shouldNotHaveBeenCalled(); $request->shouldNotHaveBeenCalled();
} }
/** @test */
public function validateUrlWithTitleReturnsNullWhenAutoResolutionIsDisabledAndValidationIsEnabled(): void
{
$request = $this->httpClient->request(Argument::cetera())->willReturn($this->respWithTitle());
$this->options->autoResolveTitles = false;
$result = $this->urlValidator->validateUrlWithTitle('http://foobar.com/12345/hello?foo=bar', true);
self::assertNull($result);
$request->shouldHaveBeenCalledOnce();
}
/** @test */ /** @test */
public function validateUrlWithTitleResolvesTitleWhenAutoResolutionIsEnabled(): void public function validateUrlWithTitleResolvesTitleWhenAutoResolutionIsEnabled(): void
{ {

View file

@ -230,7 +230,7 @@ class CreateShortUrlTest extends ApiTestCase
{ {
$expectedDetail = sprintf('Provided URL %s is invalid. Try with a different one.', $url); $expectedDetail = sprintf('Provided URL %s is invalid. Try with a different one.', $url);
[$statusCode, $payload] = $this->createShortUrl(['longUrl' => $url]); [$statusCode, $payload] = $this->createShortUrl(['longUrl' => $url, 'validateUrl' => true]);
self::assertEquals(self::STATUS_BAD_REQUEST, $statusCode); self::assertEquals(self::STATUS_BAD_REQUEST, $statusCode);
self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);

View file

@ -82,6 +82,7 @@ class EditShortUrlTest extends ApiTestCase
$resp = $this->callApiWithKey(self::METHOD_PATCH, $url, [RequestOptions::JSON => [ $resp = $this->callApiWithKey(self::METHOD_PATCH, $url, [RequestOptions::JSON => [
'longUrl' => $longUrl, 'longUrl' => $longUrl,
'validateUrl' => true,
]]); ]]);
self::assertEquals($expectedStatus, $resp->getStatusCode()); self::assertEquals($expectedStatus, $resp->getStatusCode());