diff --git a/phpunit.xml b/phpunit.xml index 52673fe8..d606a143 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,21 +1,33 @@ - - - - - - - - - - - tests/PHPUnit - - - - - src - - + + + + + + + + + + + + tests/PHPUnit + + + + + src + + diff --git a/src/Smalot/PdfParser/Font.php b/src/Smalot/PdfParser/Font.php index 8e1fbce1..61dccdfb 100644 --- a/src/Smalot/PdfParser/Font.php +++ b/src/Smalot/PdfParser/Font.php @@ -142,6 +142,15 @@ public static function uchr($code): string // note: // $code was typed as int before, but changed in https://github.com/smalot/pdfparser/pull/623 // because in some cases uchr was called with a float instead of an integer. + // + // A float that is out of integer range (e.g. resulting from a hexdec() + // overflow) cannot be cast to int without raising a "not representable + // as int" warning on PHP 8.1+, and such a value can never be a valid + // Unicode code point, so we treat it as a missing character. + if (\is_float($code) && (!\is_finite($code) || $code < \PHP_INT_MIN || $code > \PHP_INT_MAX)) { + return self::MISSING; + } + $code = (int) $code; if (!isset(self::$uchrCache[$code])) { diff --git a/tests/PHPUnit/Unit/FontTest.php b/tests/PHPUnit/Unit/FontTest.php index f60818ff..ed3da5f9 100644 --- a/tests/PHPUnit/Unit/FontTest.php +++ b/tests/PHPUnit/Unit/FontTest.php @@ -68,4 +68,36 @@ public function testDecodeTextIssue597(): void // compare result with expected value self::assertEquals('3cc2ab083e', bin2hex($result)); } + + /** + * A CMap could contain oversized hex values. hexdec() then returns a float + * larger than PHP_INT_MAX which cannot be cast to int. On PHP 8.5 this + * cast raises a "not representable as int" warning. + * + * Since these values can not represent valid Unicode code points anyway, + * it's safe to return Font::MISSING for them. This test checks that this + * is the case. + * + * The test relies on PhpUnit's failOnWarning="true" in phpunit.xml: + * a warning would error. + * + * @see https://github.com/smalot/pdfparser/pull/623 + * @see https://github.com/smalot/pdfparser/pull/825 + */ + public function testUchrWithOutOfRangeFloat(): void + { + // a regular code point is still decoded + $this->assertEquals('A', Font::uchr(0x41)); + + // a float that fits into an integer is still cast and decoded; this is + // the reason uchr() accepts floats in the first place + $this->assertEquals('A', Font::uchr(65.0)); + + // floats that do not fit into an integer can never be a valid code + // point; the value below is produced by hexdec() of an oversized hex + // string taken from samples/bugs/Issue621.pdf + $this->assertEquals(Font::MISSING, Font::uchr(1.50646556872121E+28)); + $this->assertEquals(Font::MISSING, Font::uchr(\INF)); + $this->assertEquals(Font::MISSING, Font::uchr(\NAN)); + } }