generated from Nodarx/template
63 lines
2.8 KiB
PHP
63 lines
2.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace KTXM\ServiceWopi\Discovery;
|
|
|
|
use DOMDocument;
|
|
use DOMElement;
|
|
use DOMXPath;
|
|
use RuntimeException;
|
|
|
|
final readonly class DiscoveryDocument
|
|
{
|
|
public const MAX_BYTES = 2 * 1024 * 1024;
|
|
|
|
/** @param list<Action> $actions */
|
|
private function __construct(public array $actions, public bool $hasProofKeys) {}
|
|
|
|
public static function parse(string $xml, ServerAddress $server): self
|
|
{
|
|
if ($xml === '' || strlen($xml) > self::MAX_BYTES || preg_match('/<!\s*(DOCTYPE|ENTITY)/i', $xml)) {
|
|
throw new RuntimeException('Discovery XML is empty, oversized, or contains a forbidden document type/entity.');
|
|
}
|
|
$previous = libxml_use_internal_errors(true);
|
|
try {
|
|
$document = new DOMDocument();
|
|
if (!$document->loadXML($xml, LIBXML_NONET | LIBXML_NOBLANKS)
|
|
|| $document->doctype !== null || $document->documentElement?->nodeName !== 'wopi-discovery') {
|
|
throw new RuntimeException('The server did not return valid WOPI discovery XML.');
|
|
}
|
|
$xpath = new DOMXPath($document);
|
|
$nodes = $xpath->query('/wopi-discovery/net-zone[@name="external-https"]/app/action');
|
|
// Euro-Office behind TLS termination can retain this zone label while
|
|
// advertising HTTPS action URLs. Validate every URL independently.
|
|
if ($nodes->length === 0) {
|
|
$nodes = $xpath->query('/wopi-discovery/net-zone[@name="external-http"]/app/action');
|
|
}
|
|
$actions = [];
|
|
foreach ($nodes as $node) {
|
|
if (!$node instanceof DOMElement) { continue; }
|
|
$name = $node->getAttribute('name');
|
|
$extension = strtolower($node->getAttribute('ext'));
|
|
if (!in_array($name, ['view', 'edit'], true) || !preg_match('/^[a-z0-9]+$/D', $extension)) {
|
|
continue;
|
|
}
|
|
$url = $node->getAttribute('urlsrc');
|
|
$server->assertTrustedAction($url);
|
|
$requirements = preg_split('/\s*,\s*/', trim($node->getAttribute('requires')), -1, PREG_SPLIT_NO_EMPTY);
|
|
$actions[] = new Action($extension, $name, $url, $requirements);
|
|
}
|
|
if ($actions === []) {
|
|
throw new RuntimeException('Discovery has no external view/edit actions. Check WOPI enablement and forwarded HTTPS headers.');
|
|
}
|
|
$proof = $xpath->query('/wopi-discovery/proof-key')->item(0);
|
|
return new self($actions, $proof instanceof DOMElement
|
|
&& $proof->getAttribute('modulus') !== '' && $proof->getAttribute('exponent') !== '');
|
|
} finally {
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($previous);
|
|
}
|
|
}
|
|
}
|