tests/Unit/XmlParserTest.php
Rama main · réplica de lectura del repositorio original.
<?php
declare(strict_types=1);
/**
* Copyright 2026 codaipro — Lucky Yaduvanshi (https://luckyyaduvanshi.in)
* Original source: https://github.com/Luckyyaduvanshiofficial/php-s3
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace PhpS3\Tests\Unit;
use PhpS3\S3\Exception\S3Exception;
use PhpS3\S3\Xml\XmlParser;
use PHPUnit\Framework\TestCase;
final class XmlParserTest extends TestCase
{
public function testDeleteRequestParsesKeysAndQuietFlag(): void
{
$body = '<?xml version="1.0" encoding="UTF-8"?>'
. '<Delete><Quiet>true</Quiet>'
. '<Object><Key>a.txt</Key></Object>'
. '<Object><Key>dir/b c.txt</Key><VersionId>v1</VersionId></Object>'
. '</Delete>';
$parsed = XmlParser::deleteRequest($body);
self::assertTrue($parsed['quiet']);
self::assertSame(['a.txt', 'dir/b c.txt'], $parsed['objects']);
}
public function testDeleteRequestDefaultsToVerbose(): void
{
$parsed = XmlParser::deleteRequest('<Delete><Object><Key>x</Key></Object></Delete>');
self::assertFalse($parsed['quiet']);
self::assertSame(['x'], $parsed['objects']);
}
public function testDeleteRequestEmptyBodyThrowsMalformedXml(): void
{
$this->expectException(S3Exception::class);
$this->expectExceptionMessageMatches('/MalformedXML/');
XmlParser::deleteRequest(' ');
}
public function testDeleteRequestGarbageThrowsMalformedXml(): void
{
$this->expectException(S3Exception::class);
XmlParser::deleteRequest('<Delete><Object></Delete>');
}
public function testDeleteRequestWrongRootElementThrows(): void
{
$this->expectException(S3Exception::class);
$this->expectExceptionMessageMatches('/expected <Delete>/');
XmlParser::deleteRequest('<CompleteMultipartUpload/>');
}
public function testDeleteRequestRejectsDtdEntityExpansion(): void
{
$body = '<?xml version="1.0"?><!DOCTYPE Delete [<!ENTITY x SYSTEM "file:///etc/passwd">]>'
. '<Delete><Object><Key>&x;</Key></Object></Delete>';
// DOCTYPE is rejected outright: no entity resolution can ever run,
// and no parser quirks are reachable.
$this->expectException(S3Exception::class);
$this->expectExceptionMessageMatches('/DOCTYPE/');
XmlParser::deleteRequest($body);
}
public function testReadBodyReadsACompleteStream(): void
{
$stream = fopen('php://temp', 'r+');
fwrite($stream, '<Delete></Delete>');
rewind($stream);
try {
self::assertSame('<Delete></Delete>', XmlParser::readBody($stream));
} finally {
fclose($stream);
}
}
public function testReadBodyRejectsBodiesOverTheCap(): void
{
$stream = fopen('php://temp', 'r+');
fwrite($stream, str_repeat('a', 8 * 1024 * 1024 + 1));
rewind($stream);
try {
$this->expectException(S3Exception::class);
$this->expectExceptionMessageMatches('/too large/');
XmlParser::readBody($stream);
} finally {
fclose($stream);
}
}
public function testCompleteRequestParsesPartsAndUnquotesEtags(): void
{
$body = '<CompleteMultipartUpload>'
. '<Part><PartNumber>1</PartNumber><ETag>"abc123"</ETag></Part>'
. '<Part><PartNumber>2</PartNumber><ETag>def456</ETag></Part>'
. '</CompleteMultipartUpload>';
self::assertSame(
[
['partNumber' => 1, 'etag' => 'abc123'],
['partNumber' => 2, 'etag' => 'def456'],
],
XmlParser::completeRequest($body),
);
}
public function testCompleteRequestRejectsOutOfRangePartNumber(): void
{
$this->expectException(S3Exception::class);
$this->expectExceptionMessageMatches('/PartNumber/');
XmlParser::completeRequest(
'<CompleteMultipartUpload><Part><PartNumber>10001</PartNumber><ETag>x</ETag></Part></CompleteMultipartUpload>',
);
}
public function testCompleteRequestRejectsMissingEtag(): void
{
$this->expectException(S3Exception::class);
XmlParser::completeRequest(
'<CompleteMultipartUpload><Part><PartNumber>1</PartNumber></Part></CompleteMultipartUpload>',
);
}
}