Skip to content

Commit

Permalink
fix: Fixed an issue with impropertly text-encoded characters in URLs …
Browse files Browse the repository at this point in the history
…potentially causing a db exception ([#291](#291))
  • Loading branch information
khalwat committed Mar 6, 2024
1 parent c76e522 commit 79e9d00
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 89 deletions.
135 changes: 69 additions & 66 deletions src/services/Redirects.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use craft\base\ElementInterface;
use craft\base\Plugin;
use craft\db\Query;
use craft\errors\ElementNotFoundException;
use craft\errors\SiteNotFoundException;
use craft\helpers\Db;
use craft\helpers\StringHelper;
Expand All @@ -24,9 +25,11 @@
use nystudio107\retour\events\RedirectResolvedEvent;
use nystudio107\retour\events\ResolveRedirectEvent;
use nystudio107\retour\fields\ShortLink;
use nystudio107\retour\helpers\Text as TextHelper;
use nystudio107\retour\helpers\UrlHelper;
use nystudio107\retour\models\StaticRedirects as StaticRedirectsModel;
use nystudio107\retour\Retour;
use Throwable;
use yii\base\ExitException;
use yii\base\InvalidConfigException;
use yii\base\InvalidRouteException;
Expand Down Expand Up @@ -303,7 +306,7 @@ public function findRedirectMatch(string $fullUrl, string $pathOnly, $siteId = n
$siteId = $currentSite->id;
} else {
$primarySite = Craft::$app->getSites()->primarySite;
$siteId = $primarySite->id;
$siteId = $primarySite->id;
}
}
// Try getting the full URL redirect from the cache
Expand Down Expand Up @@ -467,11 +470,11 @@ public function getStaticRedirect(string $fullUrl, string $pathOnly, $siteId, bo
'or',
['and',
['redirectSrcMatch' => 'pathonly'],
['redirectSrcUrlParsed' => $pathOnly],
['redirectSrcUrlParsed' => TextHelper::cleanupText($pathOnly)],
],
['and',
['redirectSrcMatch' => 'fullurl'],
['redirectSrcUrlParsed' => $fullUrl],
['redirectSrcUrlParsed' => TextHelper::cleanupText($fullUrl)],
],
];

Expand Down Expand Up @@ -565,38 +568,6 @@ public function getAllExactMatchRedirects(int $limit = null, int $siteId = null,
return $this->getRedirectsByMatchType($limit, $siteId, 'exactmatch', $enabledOnly);
}

/**
* @param int|null $limit
* @param int|null $siteId
* @param string $type
* @return array
*/
protected function getRedirectsByMatchType(int $limit = null, int $siteId = null, string $type, bool $enabledOnly = false): array
{
// Query the db table
$query = (new Query())
->from(['{{%retour_static_redirects}}'])
->orderBy('redirectMatchType ASC, priority ASC');

if ($siteId) {
$query
->where(['siteId' => $siteId])
->orWhere(['siteId' => null]);
}

if ($limit) {
$query->limit($limit);
}

$query->andWhere(['redirectMatchType' => $type]);

if ($enabledOnly) {
$query->andWhere(['enabled' => 1]);
}

return $query->all();
}

/**
* @param int|null $limit
* @param int|null $siteId
Expand Down Expand Up @@ -996,34 +967,6 @@ public function deleteRedirectById(int $id): int
return $result;
}

/**
* Updates an associated element short link value.
*
* @param array $redirectConfig
* @param array $existingData
*/
protected function updateAssociatedElementShortLink(array $redirectConfig, array $existingData): void
{
if (empty($redirectConfig['associatedElementId'])) {
return;
}
// Get the element and set the scenario
$associatedElement = Craft::$app->getElements()->getElementById($redirectConfig['associatedElementId']);

if (!$associatedElement) {
return;
}

$fieldUpdated = $this->setShortLinkFieldValue($associatedElement, $existingData['redirectSrcUrl'], $redirectConfig['redirectSrcUrl']);

if ($fieldUpdated) {
// Prevent element from triggering an infinite loop.
ShortLink::preventShortLinkUpdates();
Craft::$app->getElements()->saveElement($associatedElement);
ShortLink::allowShortLinkUpdates();
}
}

/**
* Save an element redirect.
*
Expand Down Expand Up @@ -1100,8 +1043,8 @@ public function getRedirectsByElementId(int $elementId, int $siteId = null)
/**
* Delete a short link by its ID.
*
* @throws \Throwable
* @throws \craft\errors\ElementNotFoundException
* @throws Throwable
* @throws ElementNotFoundException
* @throws \yii\base\Exception
*/
public function deleteShortlinkById(int $redirectId): bool
Expand Down Expand Up @@ -1286,7 +1229,7 @@ public function getRedirectByRedirectSrcUrl(string $redirectSrcUrl, int $siteId
// Query the db table
$query = (new Query())
->from(['{{%retour_static_redirects}}'])
->where(['redirectSrcUrl' => $redirectSrcUrl]);
->where(['redirectSrcUrl' => TextHelper::cleanupText($redirectSrcUrl)]);
if ($siteId) {
$query
->andWhere(['or', [
Expand Down Expand Up @@ -1321,6 +1264,66 @@ public function invalidateCaches(): void
);
}

/**
* @param int|null $limit
* @param int|null $siteId
* @param string $type
* @return array
*/
protected function getRedirectsByMatchType(int $limit = null, int $siteId = null, string $type, bool $enabledOnly = false): array
{
// Query the db table
$query = (new Query())
->from(['{{%retour_static_redirects}}'])
->orderBy('redirectMatchType ASC, priority ASC');

if ($siteId) {
$query
->where(['siteId' => $siteId])
->orWhere(['siteId' => null]);
}

if ($limit) {
$query->limit($limit);
}

$query->andWhere(['redirectMatchType' => $type]);

if ($enabledOnly) {
$query->andWhere(['enabled' => 1]);
}

return $query->all();
}

/**
* Updates an associated element short link value.
*
* @param array $redirectConfig
* @param array $existingData
*/
protected function updateAssociatedElementShortLink(array $redirectConfig, array $existingData): void
{
if (empty($redirectConfig['associatedElementId'])) {
return;
}
// Get the element and set the scenario
$associatedElement = Craft::$app->getElements()->getElementById($redirectConfig['associatedElementId']);

if (!$associatedElement) {
return;
}

$fieldUpdated = $this->setShortLinkFieldValue($associatedElement, $existingData['redirectSrcUrl'], $redirectConfig['redirectSrcUrl']);

if ($fieldUpdated) {
// Prevent element from triggering an infinite loop.
ShortLink::preventShortLinkUpdates();
Craft::$app->getElements()->saveElement($associatedElement);
ShortLink::allowShortLinkUpdates();
}
}

/**
* Find all short link fields on an element that have a matching redirect source url and update the value
*
Expand Down
47 changes: 24 additions & 23 deletions src/services/Statistics.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use craft\helpers\Db;
use craft\helpers\UrlHelper;
use DateTime;
use nystudio107\retour\helpers\Text as TextHelper;
use nystudio107\retour\models\Stats as StatsModel;
use nystudio107\retour\Retour;
use yii\db\Exception;
Expand Down Expand Up @@ -194,7 +195,7 @@ public function incrementStatistics(string $url, bool $handled = false, $siteId
// Find any existing retour_stats record
$statsConfig = (new Query())
->from(['{{%retour_stats}}'])
->where(['redirectSrcUrl' => $stats->redirectSrcUrl])
->where(['redirectSrcUrl' => TextHelper::cleanupText($stats->redirectSrcUrl)])
->one();
// If no record is found, initialize some values
if ($statsConfig === null) {
Expand Down Expand Up @@ -275,28 +276,6 @@ public function saveStatistics(array $statsConfig): void
}
}

/**
* Don't trim more than a given interval, so that performance is not affected
*
* @return bool
*/
protected function rateLimited(): bool
{
$limited = false;
$now = round(microtime(true) * 1000);
$cache = Craft::$app->getCache();
$then = $cache->get(self::LAST_STATISTICS_TRIM_CACHE_KEY);
if (($then !== false) && ($now - (int)$then < Retour::$settings->statisticsRateLimitMs)) {
$limited = true;
}
$cache->set(self::LAST_STATISTICS_TRIM_CACHE_KEY, $now, 0);

return $limited;
}

// Protected Methods
// =========================================================================

/**
* Trim the retour_stats db table based on the statsStoredLimit config.php
* setting
Expand Down Expand Up @@ -364,4 +343,26 @@ public function trimStatistics(int $limit = null): int

return $affectedRows;
}

// Protected Methods
// =========================================================================

/**
* Don't trim more than a given interval, so that performance is not affected
*
* @return bool
*/
protected function rateLimited(): bool
{
$limited = false;
$now = round(microtime(true) * 1000);
$cache = Craft::$app->getCache();
$then = $cache->get(self::LAST_STATISTICS_TRIM_CACHE_KEY);
if (($then !== false) && ($now - (int)$then < Retour::$settings->statisticsRateLimitMs)) {
$limited = true;
}
$cache->set(self::LAST_STATISTICS_TRIM_CACHE_KEY, $now, 0);

return $limited;
}
}

0 comments on commit 79e9d00

Please sign in to comment.