diff --git a/lib/Service/DiscoveryService.php b/lib/Service/DiscoveryService.php
index fc9fe7950a..f8185d8957 100644
--- a/lib/Service/DiscoveryService.php
+++ b/lib/Service/DiscoveryService.php
@@ -1,29 +1,11 @@
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- *
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 Lukas Reschke
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Richdocuments\Service;
@@ -39,6 +21,18 @@
use SimpleXMLElement;
class DiscoveryService extends CachedRequestService {
+ private const XPATH_PROOF_KEY = '//proof-key[1]';
+ // XML Attribute names for proof keys
+ private const KEY_ATTR_VALUE = 'value';
+ private const KEY_ATTR_MODULUS = 'modulus';
+ private const KEY_ATTR_EXPONENT = 'exponent';
+ private const KEY_ATTR_OLDVALUE = 'oldvalue';
+ private const KEY_ATTR_OLDMODULUS = 'oldmodulus';
+ private const KEY_ATTR_OLDEXPONENT = 'oldexponent';
+
+ // Cached (per request) parsed discovery XML
+ private ?SimpleXMLElement $parsedDiscovery = null;
+
public function __construct(
private IClientService $clientService,
private ICacheFactory $cacheFactory,
@@ -57,73 +51,144 @@ public function __construct(
);
}
+ /**
+ * Send an HTTP request to the WOPI discovery endpoint and return its XML content as a string.
+ */
#[\Override]
protected function sendRequest(IClient $client): string {
$response = $client->get($this->getDiscoveryEndpoint(), $this->getDefaultRequestOptions());
return (string)$response->getBody();
}
+ /**
+ * @throws \RuntimeException if wopi_url is not configured.
+ */
private function getDiscoveryEndpoint(): string {
+ // @todo: any virtue to the use of IConfig vs IAppConfig here?
$remoteHost = $this->config->getAppValue('richdocuments', 'wopi_url');
+
+ if (empty($remoteHost)) {
+ $this->logger->error('WOPI server URL (wopi_url) is not configured.');
+ throw new \RuntimeException('WOPI server URL (wopi_url) is not configured');
+ }
+
return rtrim($remoteHost, '/') . '/hosting/discovery';
}
+ /**
+ * Returns true if a node is present in the parsed discovery XML.
+ * Note this just indicates its presence, not its validity or usability.
+ */
public function hasProofKey(): bool {
try {
- $parsed = $this->getParsed();
+ $parsed = $this->getParsedDiscoveryXml();
} catch (\Exception $e) {
+ $this->logger->debug('Error determining proof-key presence: ' . $e->getMessage());
return false;
}
- if (!$parsed->xpath('//proof-key')) {
+ $node = $parsed->xpath(self::XPATH_PROOF_KEY);
+
+ if (empty($node)) {
+ $this->logger->debug('No proof-key node found');
return false;
}
- return (bool)$parsed->xpath('//proof-key');
+ return true;
}
+ /** Retrieve the primary proof key from the parsed discovery XML.
+ *
+ * @return ProofKey|null ProofKey (if present and complete), otherwise null.
+ */
public function getProofKey(): ?ProofKey {
- try {
- $parsed = $this->getParsed();
- } catch (\Exception $e) {
- return null;
- }
-
- $publicKey = (string)$parsed->xpath('//proof-key/@value')[0];
- $modulus = (string)$parsed->xpath('//proof-key/@modulus')[0];
- $exponent = (string)$parsed->xpath('//proof-key/@exponent')[0];
-
- return new ProofKey(
- $exponent,
- $modulus,
- $publicKey
+ // consider adding success debug level logging
+ return $this->extractProofKey(
+ self::KEY_ATTR_VALUE,
+ self::KEY_ATTR_MODULUS,
+ self::KEY_ATTR_EXPONENT
);
}
+ /**
+ * Retrieve the previous/old proof key from the parsed discovery XML.
+ *
+ * @return ProofKey|null ProofKey (if present and complete), otherwise null.
+ */
public function getProofKeyOld(): ?ProofKey {
+ // consider adding success debug level logging
+ return $this->extractProofKey(
+ self::KEY_ATTR_OLDVALUE,
+ self::KEY_ATTR_OLDMODULUS,
+ self::KEY_ATTR_OLDEXPONENT
+ );
+ }
+
+ /**
+ * Retrieve the discovery XML then load/parse/return it as a SimpleXMLElement instance.
+ * Uses per request cache; retrieves from endpoint when necessary.
+ *
+ * @throws \Exception If parsing the XML fails.
+ * @return SimpleXMLElement
+ */
+ private function getParsedDiscoveryXml(): SimpleXMLElement {
+ // Try cache (per request)
+ if ($this->parsedDiscovery !== null) {
+ return $this->parsedDiscovery;
+ }
+
try {
- $parsed = $this->getParsed();
+ $xml = $this->get();
+ $this->parsedDiscovery = new SimpleXMLElement($xml);
+ // @todo: Consider registering namespace
+ return $this->parsedDiscovery;
} catch (\Exception $e) {
- return null;
+ $this->logger->error('Could not parse discovery XML: ' . $e->getMessage());
+ throw new \Exception('Could not parse discovery XML', 0, $e);
}
-
- $publicKey = (string)$parsed->xpath('//proof-key/@oldvalue')[0];
- $modulus = (string)$parsed->xpath('//proof-key/@oldmodulus')[0];
- $exponent = (string)$parsed->xpath('//proof-key/@oldexponent')[0];
-
- return new ProofKey(
- $exponent,
- $modulus,
- $publicKey
- );
}
- private function getParsed(): SimpleXMLElement {
+ /**
+ * Helper for extracting a proof key (primary or old) from the discovery XML.
+ *
+ * @param string $valAttr Attribute name for the key value.
+ * @param string $modAttr Attribute name for the modulus.
+ * @param string $expAttr Attribute name for the exponent.
+ * @return ProofKey|null
+ */
+ private function extractProofKey(
+ string $valAttr,
+ string $modAttr,
+ string $expAttr,
+ ): ?ProofKey {
try {
- return new SimpleXMLElement($this->get());
+ $xml = $this->getParsedDiscoveryXml();
+
+ $publicKeyResult = $xml->xpath(self::XPATH_PROOF_KEY . '/@' . $valAttr);
+ $publicKeyNode = (is_array($publicKeyResult) && isset($publicKeyResult[0])) ? $publicKeyResult[0] : null;
+
+ $modulusResult = $xml->xpath(self::XPATH_PROOF_KEY . '/@' . $modAttr);
+ $modulusNode = (is_array($modulusResult) && isset($modulusResult[0])) ? $modulusResult[0] : null;
+
+ $exponentResult = $xml->xpath(self::XPATH_PROOF_KEY . '/@' . $expAttr);
+ $exponentNode = (is_array($exponentResult) && isset($exponentResult[0])) ? $exponentResult[0] : null;
+
+ if ($publicKeyNode === null || $modulusNode === null || $exponentNode === null) {
+ $this->logger->warning(sprintf(
+ 'Missing proof-key attributes: value=%s, modulus=%s, exponent=%s',
+ $valAttr, $modAttr, $expAttr
+ ));
+ return null;
+ }
+
+ return new ProofKey(
+ (string)$publicKeyNode,
+ (string)$modulusNode,
+ (string)$exponentNode
+ );
} catch (\Exception $e) {
- $this->logger->error($e->getMessage());
- throw new \Exception('Could not parse discovery XML');
+ $this->logger->error('Error extracting proof key: ' . $e->getMessage());
+ return null;
}
}
}
diff --git a/lib/WOPI/ProofKey.php b/lib/WOPI/ProofKey.php
index 772e93acfb..b9230483ca 100644
--- a/lib/WOPI/ProofKey.php
+++ b/lib/WOPI/ProofKey.php
@@ -10,21 +10,21 @@
class ProofKey {
public function __construct(
- private readonly ?string $exponent,
- private readonly ?string $modulus,
private readonly ?string $value,
+ private readonly ?string $modulus,
+ private readonly ?string $exponent,
) {
}
- public function getExponent(): ?string {
- return $this->exponent;
+ public function getValue(): ?string {
+ return $this->value;
}
public function getModulus(): ?string {
return $this->modulus;
}
- public function getValue(): ?string {
- return $this->value;
+ public function getExponent(): ?string {
+ return $this->exponent;
}
}