'
. ''
. '';
}
public function testParsesActionsAndDoesNotPermitEditingWithoutHostCapabilities(): void
{
$server = new ServerAddress('https://OFFICE.test:443/');
self::assertSame('https://office.test/hosting/discovery', $server->discoveryUrl());
$document = DiscoveryDocument::parse($this->xml(), $server);
self::assertTrue($document->hasProofKeys);
self::assertTrue($document->actions[0]->supportedBy([]));
$edit = DiscoveryDocument::parse($this->xml('edit'), $server)->actions[0];
self::assertFalse($edit->supportedBy([]));
self::assertFalse($edit->supportedBy(['locks']));
self::assertTrue($edit->supportedBy(['locks', 'update']));
$extra = DiscoveryDocument::parse($this->xml('view', requires: ' containers, future '), $server)->actions[0];
self::assertFalse($extra->supportedBy([]));
}
public function testAcceptsEuroOfficeExternalHttpLabelOnlyWithTrustedHttpsUrls(): void
{
$xml = str_replace('external-https', 'external-http', $this->xml(
url: 'https://office.test/editor?&',
));
$document = DiscoveryDocument::parse($xml, new ServerAddress('https://office.test'));
$source = 'https://app.test/wopi/files/123';
parse_str(parse_url($document->actions[0]->launchUrl($source), PHP_URL_QUERY), $parameters);
self::assertSame(['wopisrc' => $source], $parameters);
$this->expectException(InvalidArgumentException::class);
DiscoveryDocument::parse(str_replace('https://office.test', 'http://office.test', $xml), new ServerAddress('https://office.test'));
}
#[DataProvider('invalidServers')]
public function testRejectsInvalidServerAddresses(string $url): void
{
$this->expectException(InvalidArgumentException::class);
new ServerAddress($url);
}
public static function invalidServers(): array
{
return array_map(static fn ($url) => [$url], [
'http://office.test', 'file:///etc/passwd', 'https://user:pass@office.test',
'https://office.test?secret=x', 'https://office.test/#fragment',
"https://office.test/\r\n", 'https://office.test\\@other.test',
]);
}
#[DataProvider('invalidDocuments')]
public function testRejectsInvalidDiscovery(string $xml): void
{
$this->expectException(RuntimeException::class);
DiscoveryDocument::parse($xml, new ServerAddress('https://office.test'));
}
public static function invalidDocuments(): array
{
return [
[''], ['Welcome'], [''],
[']>&x;'],
[''],
[str_repeat('x', DiscoveryDocument::MAX_BYTES + 1)],
];
}
#[DataProvider('untrustedActions')]
public function testRejectsUntrustedActions(string $url): void
{
$this->expectException(InvalidArgumentException::class);
DiscoveryDocument::parse($this->xml(url: $url), new ServerAddress('https://office.test'));
}
public static function untrustedActions(): array
{
return array_map(static fn ($url) => [$url], [
'http://office.test/editor', 'https://evil.test/editor',
'https://office.test.evil.test/editor', 'https://office.test:8443/editor',
'https://user@office.test/editor', '//office.test/editor',
'javascript:alert(1)', 'https://office.test/editor#fragment',
]);
}
#[DataProvider('launchTemplates')]
public function testBuildsLaunchUrlWithoutTokenAndRemovesUnknownPlaceholders(string $template): void
{
$action = DiscoveryDocument::parse($this->xml(url: $template), new ServerAddress('https://office.test'))->actions[0];
$source = 'https://app.test/m/service_wopi/files/opaque-id';
$url = $action->launchUrl($source);
self::assertStringNotContainsString('<', $url);
self::assertStringNotContainsString('access_token', $url);
parse_str(parse_url($url, PHP_URL_QUERY), $parameters);
self::assertSame($source, $parameters['WOPISrc']);
self::assertCount(1, $parameters);
}
public static function launchTemplates(): array
{
return [
['https://office.test/editor'],
['https://office.test/editor?'],
['https://office.test/editor?'],
['https://office.test/editor?'],
];
}
public function testCommandReports404WithoutClaimingConnectionSuccess(): void
{
$client = $this->createStub(DiscoveryClient::class);
$client->method('fetch')->willThrowException(new RuntimeException('Discovery returned HTTP 404.'));
$command = new CommandTester(new CheckCommand($client));
self::assertSame(1, $command->execute(['server' => 'https://office.test']));
self::assertStringContainsString('HTTP 404', $command->getDisplay());
}
public function testCommandQualifiesViewingOnly(): void
{
$client = $this->createStub(DiscoveryClient::class);
$client->method('fetch')->willReturn(DiscoveryDocument::parse($this->xml(), new ServerAddress('https://office.test')));
$command = new CommandTester(new CheckCommand($client));
self::assertSame(0, $command->execute(['server' => 'https://office.test']));
self::assertStringContainsString('does not verify', $command->getDisplay());
}
public function testEditOnlyDiscoveryDoesNotPassReadOnlyQualification(): void
{
$client = $this->createStub(DiscoveryClient::class);
$client->method('fetch')->willReturn(DiscoveryDocument::parse($this->xml('edit'), new ServerAddress('https://office.test')));
$command = new CommandTester(new CheckCommand($client));
self::assertSame(1, $command->execute(['server' => 'https://office.test']));
}
}