mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-07 07:43:12 +08:00
Do not allow URL reserved characters in custom slugs
This commit is contained in:
@@ -10,8 +10,10 @@ use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH;
|
||||
|
||||
final class UrlShortenerOptions
|
||||
{
|
||||
/**
|
||||
* @param array{schema: ?string, hostname: ?string} $domain
|
||||
*/
|
||||
public function __construct(
|
||||
/** @var array{schema: ?string, hostname: ?string} */
|
||||
public readonly array $domain = ['schema' => null, 'hostname' => null],
|
||||
public readonly int $defaultShortCodesLength = DEFAULT_SHORT_CODES_LENGTH,
|
||||
public readonly bool $autoResolveTitles = false,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\ShortUrl\Model\Validation;
|
||||
|
||||
use Laminas\Validator\AbstractValidator;
|
||||
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
||||
|
||||
use function is_string;
|
||||
use function strpbrk;
|
||||
|
||||
class CustomSlugValidator extends AbstractValidator
|
||||
{
|
||||
private const NOT_STRING = 'NOT_STRING';
|
||||
private const CONTAINS_URL_CHARACTERS = 'CONTAINS_URL_CHARACTERS';
|
||||
|
||||
protected array $messageTemplates = [
|
||||
self::NOT_STRING => 'Provided value is not a string.',
|
||||
self::CONTAINS_URL_CHARACTERS => 'URL-reserved characters cannot be used in a custom slug.',
|
||||
];
|
||||
|
||||
private UrlShortenerOptions $options;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public static function forUrlShortenerOptions(UrlShortenerOptions $options): self
|
||||
{
|
||||
$instance = new self();
|
||||
$instance->options = $options;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public function isValid(mixed $value): bool
|
||||
{
|
||||
if ($value === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! is_string($value)) {
|
||||
$this->error(self::NOT_STRING);
|
||||
return false;
|
||||
}
|
||||
|
||||
// URL reserved characters: https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
|
||||
$reservedChars = "!*'();:@&=+$,?%#[]";
|
||||
if (! $this->options->multiSegmentSlugsEnabled) {
|
||||
// Slashes should be allowed for multi-segment slugs
|
||||
$reservedChars .= '/';
|
||||
}
|
||||
|
||||
if (strpbrk($value, $reservedChars) !== false) {
|
||||
$this->error(self::CONTAINS_URL_CHARACTERS);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -81,13 +81,15 @@ class ShortUrlInputFilter extends InputFilter
|
||||
$this->add($validUntil);
|
||||
|
||||
// The only way to enforce the NotEmpty validator to be evaluated when the key is present with an empty value
|
||||
// is by using the deprecated setContinueIfEmpty
|
||||
// is with setContinueIfEmpty
|
||||
$customSlug = $this->createInput(self::CUSTOM_SLUG, false)->setContinueIfEmpty(true);
|
||||
$customSlug->getFilterChain()->attach(new CustomSlugFilter($options));
|
||||
$customSlug->getValidatorChain()->attach(new Validator\NotEmpty([
|
||||
Validator\NotEmpty::STRING,
|
||||
Validator\NotEmpty::SPACE,
|
||||
]));
|
||||
$customSlug->getValidatorChain()
|
||||
->attach(new Validator\NotEmpty([
|
||||
Validator\NotEmpty::STRING,
|
||||
Validator\NotEmpty::SPACE,
|
||||
]))
|
||||
->attach(CustomSlugValidator::forUrlShortenerOptions($options));
|
||||
$this->add($customSlug);
|
||||
|
||||
$this->add($this->createNumericInput(self::MAX_VISITS, false));
|
||||
|
||||
Reference in New Issue
Block a user