Skip to content

Commit fde73f2

Browse files
committed
Rely on PHP's type system to detect string length overflows
1 parent d2c9a09 commit fde73f2

File tree

2 files changed

+41
-7
lines changed

2 files changed

+41
-7
lines changed

src/Decoder.php

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
namespace s9e\Bencode;
99

1010
use ArrayObject;
11+
use Throwable;
12+
use TypeError;
1113
use const PHP_INT_MAX, PHP_INT_MIN, false;
1214
use function is_float, str_contains, strcmp, strlen, strspn, substr, substr_compare;
1315
use s9e\Bencode\Exceptions\ComplianceError;
@@ -38,7 +40,14 @@ class Decoder
3840
public static function decode(string $bencoded): ArrayObject|array|int|string
3941
{
4042
$decoder = new static($bencoded);
41-
$value = $decoder->decodeAnything();
43+
try
44+
{
45+
$value = $decoder->decodeAnything();
46+
}
47+
catch (TypeError $e)
48+
{
49+
throw self::convertTypeError($e, $decoder->offset);
50+
}
4251

4352
$decoder->checkCursorPosition();
4453

@@ -142,6 +151,19 @@ protected function computeSafeBoundary(): void
142151
};
143152
}
144153

154+
protected static function convertTypeError(TypeError $e, int $offset): Throwable
155+
{
156+
// A type error can occur in decodeString() if the string length exceeds an int
157+
$frame = $e->getTrace()[0];
158+
if ($frame['class'] === __CLASS__ && $frame['function'] === 'decodeString')
159+
{
160+
return new DecodingException('String length overflow', $offset - 1);
161+
}
162+
163+
// Return any other error as-is
164+
return $e;
165+
}
166+
145167
protected function decodeAnything(): ArrayObject|array|int|string
146168
{
147169
return match ($this->bencoded[$this->offset])
@@ -242,11 +264,6 @@ protected function decodeList(): array
242264
protected function decodeString(): string
243265
{
244266
$len = (int) $this->readDigits(':');
245-
if ($this->offset + $len >= PHP_INT_MAX)
246-
{
247-
throw new DecodingException('String length overflow', $this->offset - 1 - strlen((string) $len));
248-
}
249-
250267
$string = substr($this->bencoded, $this->offset, $len);
251268
$this->offset += $len;
252269

tests/Test.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPUnit\Framework\TestCase;
77
use TypeError;
88
use s9e\Bencode\Bencode;
9+
use s9e\Bencode\Decoder;
910
use s9e\Bencode\Exceptions\ComplianceError;
1011
use s9e\Bencode\Exceptions\DecodingException;
1112
use s9e\Bencode\Exceptions\EncodingException;
@@ -471,7 +472,7 @@ public function getDecodeInvalidTests()
471472
],
472473
[
473474
'l' . PHP_INT_MAX . ':xe',
474-
new DecodingException('String length overflow', 1)
475+
new DecodingException('String length overflow', strlen('l' . PHP_INT_MAX))
475476
],
476477
];
477478
}
@@ -580,8 +581,24 @@ public function testDecodeDictionaryAccess()
580581
}
581582
$this->assertSame(['bar' => 'spam', 'foo' => 42], $actual);
582583
}
584+
585+
public function testFaultyDecoder()
586+
{
587+
$this->expectException('TypeError');
588+
FaultyDecoder::decode('1:x');
589+
}
583590
}
584591

585592
class foo extends stdClass
586593
{
594+
}
595+
596+
class FaultyDecoder extends Decoder
597+
{
598+
protected function decodeString(): string
599+
{
600+
$this->offset = 1.2;
601+
602+
return '?';
603+
}
587604
}

0 commit comments

Comments
 (0)