mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 23:33:13 +08:00
Make RedirectCondition->matchValue nullable
This commit is contained in:
@@ -39,5 +39,6 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
fieldWithUtf8Charset($builder->createField('matchValue', Types::STRING), $emConfig)
|
||||
->columnName('match_value')
|
||||
->length(512)
|
||||
->nullable()
|
||||
->build();
|
||||
};
|
||||
|
||||
26
module/Core/migrations/Version20250722060208.php
Normal file
26
module/Core/migrations/Version20250722060208.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Make the redirect_condition match_value column nullable, so that we can support new valueless-query-param and
|
||||
* any-value-query-param conditions consistently.
|
||||
*/
|
||||
final class Version20250722060208 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$schema->getTable('redirect_conditions')->getColumn('match_value')->setNotnull(false);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
||||
{
|
||||
private function __construct(
|
||||
public readonly RedirectConditionType $type,
|
||||
private readonly string $matchValue,
|
||||
private readonly string|null $matchValue = null,
|
||||
private readonly string|null $matchKey = null,
|
||||
) {
|
||||
}
|
||||
@@ -38,12 +38,12 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
||||
|
||||
public static function forAnyValueQueryParam(string $param): self
|
||||
{
|
||||
return new self(RedirectConditionType::ANY_VALUE_QUERY_PARAM, $param);
|
||||
return new self(RedirectConditionType::ANY_VALUE_QUERY_PARAM, matchKey: $param);
|
||||
}
|
||||
|
||||
public static function forValuelessQueryParam(string $param): self
|
||||
{
|
||||
return new self(RedirectConditionType::VALUELESS_QUERY_PARAM, $param);
|
||||
return new self(RedirectConditionType::VALUELESS_QUERY_PARAM, matchKey: $param);
|
||||
}
|
||||
|
||||
public static function forLanguage(string $language): self
|
||||
@@ -131,19 +131,19 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
||||
private function matchesValuelessQueryParam(ServerRequestInterface $request): bool
|
||||
{
|
||||
$query = $request->getQueryParams();
|
||||
return array_key_exists($this->matchValue, $query) && empty($query[$this->matchValue]);
|
||||
return $this->matchKey !== null && array_key_exists($this->matchKey, $query) && empty($query[$this->matchKey]);
|
||||
}
|
||||
|
||||
private function matchesAnyValueQueryParam(ServerRequestInterface $request): bool
|
||||
{
|
||||
$query = $request->getQueryParams();
|
||||
return array_key_exists($this->matchValue, $query);
|
||||
return $this->matchKey !== null && array_key_exists($this->matchKey, $query);
|
||||
}
|
||||
|
||||
private function matchesLanguage(ServerRequestInterface $request): bool
|
||||
{
|
||||
$acceptLanguage = trim($request->getHeaderLine('Accept-Language'));
|
||||
if ($acceptLanguage === '' || $acceptLanguage === '*') {
|
||||
if ($acceptLanguage === '' || $acceptLanguage === '*' || $this->matchValue === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -173,13 +173,17 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
||||
private function matchesRemoteIpAddress(ServerRequestInterface $request): bool
|
||||
{
|
||||
$remoteAddress = ipAddressFromRequest($request);
|
||||
return $remoteAddress !== null && IpAddressUtils::ipAddressMatchesGroups($remoteAddress, [$this->matchValue]);
|
||||
return (
|
||||
$this->matchValue !== null
|
||||
&& $remoteAddress !== null
|
||||
&& IpAddressUtils::ipAddressMatchesGroups($remoteAddress, [$this->matchValue])
|
||||
);
|
||||
}
|
||||
|
||||
private function matchesGeolocationCountryCode(ServerRequestInterface $request): bool
|
||||
{
|
||||
$geolocation = geolocationFromRequest($request);
|
||||
if ($geolocation === null) {
|
||||
if ($geolocation === null || $this->matchValue === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -189,7 +193,7 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
||||
private function matchesGeolocationCityName(ServerRequestInterface $request): bool
|
||||
{
|
||||
$geolocation = geolocationFromRequest($request);
|
||||
if ($geolocation === null) {
|
||||
if ($geolocation === null || $this->matchValue === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,11 +31,7 @@ enum RedirectConditionType: string
|
||||
// RedirectConditionType::LANGUAGE => TODO Validate at least format,
|
||||
RedirectConditionType::IP_ADDRESS => IpAddressUtils::isStaticIpCidrOrWildcard($value),
|
||||
RedirectConditionType::GEOLOCATION_COUNTRY_CODE => contains($value, ISO_COUNTRY_CODES),
|
||||
RedirectConditionType::QUERY_PARAM,
|
||||
RedirectConditionType::ANY_VALUE_QUERY_PARAM,
|
||||
RedirectConditionType::VALUELESS_QUERY_PARAM => $value !== '',
|
||||
// FIXME We should at least validate the value is not empty
|
||||
// default => $value !== '',
|
||||
RedirectConditionType::QUERY_PARAM => $value !== '',
|
||||
default => true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ class RedirectRulesInputFilter extends InputFilter
|
||||
]));
|
||||
$redirectConditionInputFilter->add($type);
|
||||
|
||||
$value = InputFactory::basic(self::CONDITION_MATCH_VALUE, required: true);
|
||||
$value = InputFactory::basic(self::CONDITION_MATCH_VALUE, required: true)->setAllowEmpty(true);
|
||||
$value->getValidatorChain()->attach(new Callback(
|
||||
function (string $value, array $context): bool {
|
||||
$conditionType = RedirectConditionType::tryFrom($context[self::CONDITION_TYPE]);
|
||||
|
||||
@@ -14,9 +14,9 @@ class RedirectConditionTypeTest extends TestCase
|
||||
#[Test]
|
||||
#[TestWith([RedirectConditionType::QUERY_PARAM, '', false])]
|
||||
#[TestWith([RedirectConditionType::QUERY_PARAM, 'foo', true])]
|
||||
#[TestWith([RedirectConditionType::ANY_VALUE_QUERY_PARAM, '', false])]
|
||||
#[TestWith([RedirectConditionType::ANY_VALUE_QUERY_PARAM, '', true])]
|
||||
#[TestWith([RedirectConditionType::ANY_VALUE_QUERY_PARAM, 'foo', true])]
|
||||
#[TestWith([RedirectConditionType::VALUELESS_QUERY_PARAM, '', false])]
|
||||
#[TestWith([RedirectConditionType::VALUELESS_QUERY_PARAM, '', true])]
|
||||
#[TestWith([RedirectConditionType::VALUELESS_QUERY_PARAM, 'foo', true])]
|
||||
public function isValidFailsForEmptyQueryParams(
|
||||
RedirectConditionType $conditionType,
|
||||
|
||||
Reference in New Issue
Block a user