diff --git a/lib/Deserializer/functions.php b/lib/Deserializer/functions.php index c0ecb4d..e17006e 100644 --- a/lib/Deserializer/functions.php +++ b/lib/Deserializer/functions.php @@ -86,7 +86,9 @@ function keyValue(Reader $reader, ?string $namespace = null): array $values[$reader->localName] = $reader->parseCurrentElement()['value']; } else { $clark = $reader->getClark(); - $values[$clark] = $reader->parseCurrentElement()['value']; + if (null !== $clark) { + $values[$clark] = $reader->parseCurrentElement()['value']; + } } } else { if (!$reader->read()) { diff --git a/lib/LibXMLException.php b/lib/LibXMLException.php index 0f760c8..1020d79 100644 --- a/lib/LibXMLException.php +++ b/lib/LibXMLException.php @@ -29,7 +29,7 @@ public function __construct(/** */ protected array $errors, int $code = 0, ?\Throwable $previousException = null) { - parent::__construct($this->errors[0]->message.' on line '.$this->errors[0]->line.', column '.$this->errors[0]->column, $code, $previousException); + parent::__construct(trim($this->errors[0]->message).' on line '.$this->errors[0]->line.', column '.$this->errors[0]->column, $code, $previousException); } /** diff --git a/lib/Reader.php b/lib/Reader.php index 2d4b5c6..a08c5a9 100644 --- a/lib/Reader.php +++ b/lib/Reader.php @@ -273,7 +273,9 @@ public function parseAttributes(): array } $name = $this->getClark(); - $attributes[$name] = $this->value; + if (null !== $name) { + $attributes[$name] = $this->value; + } } else { $attributes[$this->localName] = $this->value; } diff --git a/lib/Service.php b/lib/Service.php index 747587e..f498ad0 100644 --- a/lib/Service.php +++ b/lib/Service.php @@ -109,29 +109,45 @@ public function getWriter(): Writer */ public function parse($input, ?string $contextUri = null, ?string &$rootElementName = null) { - if (!is_string($input)) { - // Unfortunately the XMLReader doesn't support streams. When it - // does, we can optimize this. + if (PHP_VERSION_ID >= 80400) { if (is_resource($input)) { - $input = (string) stream_get_contents($input); + $r = Reader::fromStream($input, null, $this->options); + } elseif (is_string($input)) { + if ('' === $input) { + throw new ParseException('The input element to parse is empty. Do not attempt to parse'); + } + $r = Reader::fromString($input, null, $this->options); } else { - // Input is not a string and not a resource. - // Therefore, it has to be a closed resource. - // Effectively empty input has been passed in. - $input = ''; + throw new ParseException('The input element to parse is empty. Do not attempt to parse'); } - } - // If input is empty, then it's safe to throw an exception - if ('' === $input) { - throw new ParseException('The input element to parse is empty. Do not attempt to parse'); - } + $r->elementMap = $this->elementMap; + $r->contextUri = $contextUri; + } else { + if (!is_string($input)) { + // Unfortunately the XMLReader doesn't support streams. When it + // does, we can optimize this. + if (is_resource($input)) { + $input = (string) stream_get_contents($input); + } else { + // Input is not a string and not a resource. + // Therefore, it has to be a closed resource. + // Effectively empty input has been passed in. + $input = ''; + } + } + // If input is empty, then it's safe to throw an exception + if ('' === $input) { + throw new ParseException('The input element to parse is empty. Do not attempt to parse'); + } - $r = $this->getReader(); - $r->contextUri = $contextUri; - $r->XML($input, null, $this->options); + $r = $this->getReader(); + $r->contextUri = $contextUri; + $r->XML($input, null, $this->options); + } $result = $r->parse(); + $rootElementName = $result['name']; return $result['value']; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ec0c143..ed7313c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -4,3 +4,5 @@ parameters: message: "#^Call to function is_null\\(\\) with null will always evaluate to true\\.$#" path: lib/Serializer/functions.php count: 1 + - message: '#Dynamic call to static method PHPUnit\\Framework\\Assert::.*\(\).#' + path: tests/Sabre/Xml/ServiceTest.php diff --git a/phpstan-rules-lt-8.4.neon b/phpstan-rules-lt-8.4.neon new file mode 100644 index 0000000..b4dba40 --- /dev/null +++ b/phpstan-rules-lt-8.4.neon @@ -0,0 +1,10 @@ +parameters: + ignoreErrors: + - + rawMessage: 'Call to an undefined static method Sabre\Xml\Reader::fromStream().' + path: lib/Service.php + count: 1 + - + rawMessage: 'Call to an undefined static method Sabre\Xml\Reader::fromString().' + path: lib/Service.php + count: 1 diff --git a/phpstan.conditional.php b/phpstan.conditional.php new file mode 100644 index 0000000..97ea005 --- /dev/null +++ b/phpstan.conditional.php @@ -0,0 +1,20 @@ +parse($input, '/sabre.io/ns'); } + public function testEmptyStream(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('This test requires PHP 8.4.'); + } + + $this->expectException(LibXMLException::class); + $this->expectExceptionMessage('Document is empty on line 1, column 1'); + + $emptyResource = fopen('php://input', 'r'); + $this->assertNotFalse($emptyResource); + $util = new Service(); + $util->parse($emptyResource, '/sabre.io/ns'); + } + #[Depends('testGetReader')] public function testParse(): void { @@ -364,9 +379,11 @@ public static function provideParseClarkNotationInput(): iterable */ public static function providesEmptyInput(): array { - $emptyResource = fopen('php://input', 'r'); $data = []; - $data[] = [$emptyResource]; + if (PHP_VERSION_ID < 80400) { + $emptyResource = fopen('php://input', 'r'); + $data[] = [$emptyResource]; + } $data[] = ['']; // Also test trying to parse a resource stream that has already been closed.