mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 15:23:12 +08:00
Updated logic to generate random short codes, increasing entropy
This commit is contained in:
@@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\Core\Entity;
|
||||
use Cake\Chronos\Chronos;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use PUGX\Shortid\Factory as ShortIdFactory;
|
||||
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
|
||||
use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface;
|
||||
use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver;
|
||||
@@ -20,6 +21,8 @@ use function Functional\invoke;
|
||||
|
||||
class ShortUrl extends AbstractEntity
|
||||
{
|
||||
private const BASE62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
/** @var string */
|
||||
private $longUrl;
|
||||
/** @var string */
|
||||
@@ -53,10 +56,15 @@ class ShortUrl extends AbstractEntity
|
||||
$this->validSince = $meta->getValidSince();
|
||||
$this->validUntil = $meta->getValidUntil();
|
||||
$this->maxVisits = $meta->getMaxVisits();
|
||||
$this->shortCode = $meta->getCustomSlug() ?? ''; // TODO logic to calculate short code should be passed somehow
|
||||
$this->shortCode = $meta->getCustomSlug() ?? $this->generateShortCode();
|
||||
$this->domain = ($domainResolver ?? new SimpleDomainResolver())->resolveDomain($meta->getDomain());
|
||||
}
|
||||
|
||||
private function generateShortCode(): string
|
||||
{
|
||||
return (new ShortIdFactory())->generate(6, self::BASE62)->serialize();
|
||||
}
|
||||
|
||||
public function getLongUrl(): string
|
||||
{
|
||||
return $this->longUrl;
|
||||
@@ -67,13 +75,6 @@ class ShortUrl extends AbstractEntity
|
||||
return $this->shortCode;
|
||||
}
|
||||
|
||||
// TODO Short code is currently calculated based on the ID, so a setter is needed
|
||||
public function setShortCode(string $shortCode): self
|
||||
{
|
||||
$this->shortCode = $shortCode;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDateCreated(): Chronos
|
||||
{
|
||||
return $this->dateCreated;
|
||||
|
||||
@@ -8,26 +8,12 @@ use Zend\Stdlib\AbstractOptions;
|
||||
|
||||
class UrlShortenerOptions extends AbstractOptions
|
||||
{
|
||||
public const DEFAULT_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
// phpcs:disable
|
||||
protected $__strictMode__ = false;
|
||||
// phpcs:enable
|
||||
|
||||
private $shortcodeChars = self::DEFAULT_CHARS;
|
||||
private $validateUrl = true;
|
||||
|
||||
public function getChars(): string
|
||||
{
|
||||
return $this->shortcodeChars;
|
||||
}
|
||||
|
||||
protected function setShortcodeChars(string $shortcodeChars): self
|
||||
{
|
||||
$this->shortcodeChars = empty($shortcodeChars) ? self::DEFAULT_CHARS : $shortcodeChars;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isUrlValidationEnabled(): bool
|
||||
{
|
||||
return $this->validateUrl;
|
||||
|
||||
@@ -24,17 +24,11 @@ use Shlinkio\Shlink\Core\Util\TagManagerTrait;
|
||||
use Throwable;
|
||||
|
||||
use function array_reduce;
|
||||
use function floor;
|
||||
use function fmod;
|
||||
use function preg_match;
|
||||
use function strlen;
|
||||
|
||||
class UrlShortener implements UrlShortenerInterface
|
||||
{
|
||||
use TagManagerTrait;
|
||||
|
||||
private const ID_INCREMENT = 200000;
|
||||
|
||||
/** @var ClientInterface */
|
||||
private $httpClient;
|
||||
/** @var EntityManagerInterface */
|
||||
@@ -77,16 +71,8 @@ class UrlShortener implements UrlShortenerInterface
|
||||
|
||||
// First, create the short URL with an empty short code
|
||||
$shortUrl = new ShortUrl($url, $meta, new PersistenceDomainResolver($this->em));
|
||||
$this->em->persist($shortUrl);
|
||||
$this->em->flush();
|
||||
|
||||
// Generate the short code and persist it if no custom slug was provided
|
||||
if (! $meta->hasCustomSlug()) {
|
||||
// TODO Somehow provide the logic to calculate the shortCode to avoid the need of a setter
|
||||
$shortCode = $this->convertAutoincrementIdToShortCode((float) $shortUrl->getId());
|
||||
$shortUrl->setShortCode($shortCode);
|
||||
}
|
||||
$shortUrl->setTags($this->tagNamesToEntities($this->em, $tags));
|
||||
$this->em->persist($shortUrl);
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->commit();
|
||||
@@ -155,36 +141,12 @@ class UrlShortener implements UrlShortenerInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function convertAutoincrementIdToShortCode(float $id): string
|
||||
{
|
||||
$id += self::ID_INCREMENT; // Increment the Id so that the generated shortcode is not too short
|
||||
$chars = $this->options->getChars();
|
||||
|
||||
$length = strlen($chars);
|
||||
$code = '';
|
||||
|
||||
while ($id > 0) {
|
||||
// Determine the value of the next higher character in the short code and prepend it
|
||||
$code = $chars[(int) fmod($id, $length)] . $code;
|
||||
$id = floor($id / $length);
|
||||
}
|
||||
|
||||
return $chars[(int) $id] . $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidShortCodeException
|
||||
* @throws EntityDoesNotExistException
|
||||
*/
|
||||
public function shortCodeToUrl(string $shortCode, ?string $domain = null): ShortUrl
|
||||
{
|
||||
$chars = $this->options->getChars();
|
||||
|
||||
// Validate short code format
|
||||
if (! preg_match('|[' . $chars . ']+|', $shortCode)) {
|
||||
throw InvalidShortCodeException::fromCharset($shortCode, $chars);
|
||||
}
|
||||
|
||||
/** @var ShortUrlRepository $shortUrlRepo */
|
||||
$shortUrlRepo = $this->em->getRepository(ShortUrl::class);
|
||||
$shortUrl = $shortUrlRepo->findOneByShortCode($shortCode, $domain);
|
||||
|
||||
Reference in New Issue
Block a user