Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 44 additions & 5 deletions src/Symfony/Component/JsonPath/JsonCrawler.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ private function evaluateBracket(string $expr, mixed $value): array

// single negative index
if (preg_match('/^-\d+$/', $expr)) {
if (JsonPathUtils::hasLeadingZero($expr) || JsonPathUtils::isIntegerOverflow($expr) || '-0' === $expr) {
throw new JsonCrawlerException($expr, 'invalid index selector');
}

if (!array_is_list($value)) {
return [];
}
Expand All @@ -154,6 +158,12 @@ private function evaluateBracket(string $expr, mixed $value): array

// start and end index
if (preg_match('/^-?\d+(?:\s*,\s*-?\d+)*$/', $expr)) {
foreach (explode(',', $expr) as $exprPart) {
if (JsonPathUtils::hasLeadingZero($exprPart = trim($exprPart)) || JsonPathUtils::isIntegerOverflow($exprPart) || '-0' === $exprPart) {
throw new JsonCrawlerException($expr, 'invalid index selector');
}
}

if (!array_is_list($value)) {
return [];
}
Expand All @@ -172,17 +182,41 @@ private function evaluateBracket(string $expr, mixed $value): array
return $result;
}

if (preg_match('/^(-?\d*+)\s*+:\s*+(-?\d*+)(?:\s*+:\s*+(-?\d++))?$/', $expr, $matches)) {
if (preg_match('/^(-?\d*+)\s*+:\s*+(-?\d*+)(?:\s*+:\s*+(-?\d*+))?$/', $expr, $matches)) {
if (!array_is_list($value)) {
return [];
}

$startStr = trim($matches[1]);
$endStr = trim($matches[2]);
$stepStr = trim($matches[3] ?? '1');

if (
JsonPathUtils::hasLeadingZero($startStr)
|| JsonPathUtils::hasLeadingZero($endStr)
|| JsonPathUtils::hasLeadingZero($stepStr)
) {
throw new JsonCrawlerException($expr, 'slice selector numbers cannot have leading zeros');
}

if ('-0' === $startStr || '-0' === $endStr || '-0' === $stepStr) {
throw new JsonCrawlerException($expr, 'slice selector cannot contain negative zero');
}

if (
JsonPathUtils::isIntegerOverflow($startStr)
|| JsonPathUtils::isIntegerOverflow($endStr)
|| JsonPathUtils::isIntegerOverflow($stepStr)
) {
throw new JsonCrawlerException($expr, 'slice selector integer overflow');
}

$length = \count($value);
$start = '' !== $matches[1] ? (int) $matches[1] : null;
$end = '' !== $matches[2] ? (int) $matches[2] : null;
$step = isset($matches[3]) && '' !== $matches[3] ? (int) $matches[3] : 1;
$start = '' !== $startStr ? (int) $startStr : null;
$end = '' !== $endStr ? (int) $endStr : null;
$step = '' !== $stepStr ? (int) $stepStr : 1;

if (0 === $step || $start > $length) {
if (0 === $step) {
return [];
}

Expand All @@ -192,6 +226,11 @@ private function evaluateBracket(string $expr, mixed $value): array
if ($start < 0) {
$start = $length + $start;
}

if ($step > 0 && $start >= $length) {
return [];
}

$start = max(0, min($start, $length - 1));
}

Expand Down
35 changes: 35 additions & 0 deletions src/Symfony/Component/JsonPath/JsonPathUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,39 @@ public static function parseCommaSeparatedValues(string $expr): array

return $parts;
}

public static function hasLeadingZero(string $s): bool
{
if ('' === $s || str_starts_with($s, '-') && '' === $s = substr($s, 1)) {
return false;
}

return '0' === $s[0] && 1 < \strlen($s);
}

/**
* Safe integer range is [-(2^53) + 1, (2^53) - 1].
*
* @see https://datatracker.ietf.org/doc/rfc9535/, section 2.1
*/
public static function isIntegerOverflow(string $s): bool
{
if ('' === $s) {
return false;
}

$negative = str_starts_with($s, '-');
$maxLength = $negative ? 17 : 16;
$len = \strlen($s);

if ($len > $maxLength) {
return true;
}

if ($len < $maxLength) {
return false;
}

return $negative ? $s < '-9007199254740991' : $s > '9007199254740991';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,6 @@ final class JsonPathComplianceTestSuiteTest extends TestCase
'filter, group terms, right',
'name selector, double quotes, escaped reverse solidus',
'name selector, single quotes, escaped reverse solidus',
'slice selector, slice selector with everything omitted, long form',
'slice selector, start, min exact',
'slice selector, start, max exact',
'slice selector, end, min exact',
'slice selector, end, max exact',
'basic, descendant segment, multiple selectors',
'basic, bald descendant segment',
'filter, relative non-singular query, index, equal',
Expand Down Expand Up @@ -142,24 +137,6 @@ final class JsonPathComplianceTestSuiteTest extends TestCase
'filter, absolute non-singular query, slice, less-or-equal',
'filter, equals, special nothing',
'filter, group terms, left',
'index selector, min exact index - 1',
'index selector, max exact index + 1',
'index selector, overflowing index',
'index selector, leading 0',
'index selector, -0',
'index selector, leading -0',
'slice selector, excessively large from value with negative step',
'slice selector, step, min exact - 1',
'slice selector, step, max exact + 1',
'slice selector, overflowing to value',
'slice selector, underflowing from value',
'slice selector, overflowing from value with negative step',
'slice selector, underflowing to value with negative step',
'slice selector, overflowing step',
'slice selector, underflowing step',
'slice selector, step, leading 0',
'slice selector, step, -0',
'slice selector, step, leading -0',
];

/**
Expand Down
Loading