Skip to content

Commit e335cb0

Browse files
committed
Update regex to restrict words containing digit only at the beginning or the end of the ID
1 parent b554a35 commit e335cb0

File tree

1 file changed

+50
-17
lines changed

1 file changed

+50
-17
lines changed

Diff for: src/Sqids.php

+50-17
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
class Sqids implements SqidsInterface
2727
{
28+
private const MIN_LENGTH_LIMIT = 255;
29+
2830
final public const DEFAULT_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
2931
final public const DEFAULT_MIN_LENGTH = 0;
3032
final public const DEFAULT_BLOCKLIST = [
@@ -237,7 +239,7 @@ class Sqids implements SqidsInterface
237239

238240
protected MathInterface $math;
239241

240-
protected ?string $blocklistRegex = null;
242+
protected ?string $blocklistRegex;
241243

242244
/** @throws InvalidArgumentException */
243245
public function __construct(
@@ -263,25 +265,12 @@ public function __construct(
263265
throw new InvalidArgumentException('Alphabet must contain unique characters');
264266
}
265267

266-
$minLengthLimit = 255;
267-
if ($minLength < 0 || $minLength > $minLengthLimit) {
268-
throw new InvalidArgumentException(
269-
'Minimum length has to be between 0 and ' . $minLengthLimit,
270-
);
271-
}
272-
273-
$filteredBlocklist = [];
274-
foreach ($blocklist as $word) {
275-
if (strlen((string) $word) >= 3) {
276-
$filteredBlocklist[] = strtr(preg_quote((string) $word, '/'), self::LEET);
277-
}
278-
}
279-
if ($filteredBlocklist) {
280-
$this->blocklistRegex = '/(' . implode('|', $filteredBlocklist) . ')/i';
268+
if ($minLength < 0 || $minLength > self::MIN_LENGTH_LIMIT) {
269+
throw new InvalidArgumentException('Minimum length has to be between 0 and ' . self::MIN_LENGTH_LIMIT);
281270
}
282271

272+
$this->blocklistRegex = $this->buildBlocklistRegex();
283273
$this->alphabet = $this->shuffle($alphabet);
284-
$this->blocklist = $filteredBlocklist;
285274
}
286275

287276
/**
@@ -471,4 +460,48 @@ protected function getMathExtension(): MathInterface
471460

472461
throw new RuntimeException('Missing math extension for Sqids, install either bcmath or gmp.');
473462
}
463+
464+
protected function buildBlocklistRegex(): ?string
465+
{
466+
$wordsMatchingExactly = [];
467+
$wordsMatchingBeginningOrEnd = [];
468+
$wordMatchingAnywhere = [];
469+
470+
foreach ($this->blocklist as $word) {
471+
$word = (string) $word;
472+
if (strlen($word) <= 3) {
473+
$wordsMatchingExactly[] = preg_quote($word, '/');
474+
} else {
475+
$word = preg_quote($word, '/');
476+
$leet = strtr($word, self::LEET);
477+
if (!preg_match('/\d/', $word)) {
478+
$wordMatchingAnywhere[] = $word;
479+
} elseif ($leet === $word) {
480+
$wordsMatchingBeginningOrEnd[] = $word;
481+
}
482+
483+
if ($leet !== $word) {
484+
$wordsMatchingBeginningOrEnd[] = $leet;
485+
}
486+
}
487+
}
488+
489+
$regexParts = [];
490+
if ($wordsMatchingExactly) {
491+
$regexParts[] = '^(' . implode('|', $wordsMatchingExactly) . ')$';
492+
}
493+
if ($wordsMatchingBeginningOrEnd) {
494+
$regexParts[] = '^(' . implode('|', $wordsMatchingBeginningOrEnd) . ')';
495+
$regexParts[] = '(' . implode('|', $wordsMatchingBeginningOrEnd) . ')$';
496+
}
497+
if ($wordMatchingAnywhere) {
498+
$regexParts[] = '(' . implode('|', $wordMatchingAnywhere) . ')';
499+
}
500+
501+
if ($regexParts) {
502+
return '/(' . implode('|', $regexParts) . ')/i';
503+
}
504+
505+
return null;
506+
}
474507
}

0 commit comments

Comments
 (0)