DiscoveryService.php 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OC\OCS;
  8. use OCP\AppFramework\Http;
  9. use OCP\Http\Client\IClient;
  10. use OCP\Http\Client\IClientService;
  11. use OCP\ICache;
  12. use OCP\ICacheFactory;
  13. use OCP\OCS\IDiscoveryService;
  14. class DiscoveryService implements IDiscoveryService {
  15. /** @var ICache */
  16. private ICache $cache;
  17. /** @var IClient */
  18. private IClient $client;
  19. /**
  20. * @param ICacheFactory $cacheFactory
  21. * @param IClientService $clientService
  22. */
  23. public function __construct(ICacheFactory $cacheFactory,
  24. IClientService $clientService
  25. ) {
  26. $this->cache = $cacheFactory->createDistributed('ocs-discovery');
  27. $this->client = $clientService->newClient();
  28. }
  29. /**
  30. * Discover OCS end-points
  31. *
  32. * If no valid discovery data is found the defaults are returned
  33. *
  34. * @param string $remote
  35. * @param string $service the service you want to discover
  36. * @param bool $skipCache We won't check if the data is in the cache. This is useful if a background job is updating the status
  37. * @return array
  38. */
  39. public function discover(string $remote, string $service, bool $skipCache = false): array {
  40. // Check the cache first
  41. if ($skipCache === false) {
  42. $cacheData = $this->cache->get($remote . '#' . $service);
  43. if ($cacheData) {
  44. $data = json_decode($cacheData, true);
  45. if (\is_array($data)) {
  46. return $data;
  47. }
  48. }
  49. }
  50. $discoveredServices = [];
  51. // query the remote server for available services
  52. try {
  53. $response = $this->client->get($remote . '/ocs-provider/', [
  54. 'timeout' => 10,
  55. 'connect_timeout' => 10,
  56. ]);
  57. if ($response->getStatusCode() === Http::STATUS_OK) {
  58. $decodedServices = json_decode($response->getBody(), true);
  59. if (\is_array($decodedServices)) {
  60. $discoveredServices = $this->getEndpoints($decodedServices, $service);
  61. }
  62. }
  63. } catch (\Exception $e) {
  64. // if we couldn't discover the service or any end-points we return a empty array
  65. }
  66. // Write into cache
  67. $this->cache->set($remote . '#' . $service, json_encode($discoveredServices), 60 * 60 * 24);
  68. return $discoveredServices;
  69. }
  70. /**
  71. * get requested end-points from the requested service
  72. *
  73. * @param array $decodedServices
  74. * @param string $service
  75. * @return array
  76. */
  77. protected function getEndpoints(array $decodedServices, string $service): array {
  78. $discoveredServices = [];
  79. if (isset($decodedServices['services'][$service]['endpoints'])) {
  80. foreach ($decodedServices['services'][$service]['endpoints'] as $endpoint => $url) {
  81. if ($this->isSafeUrl($url)) {
  82. $discoveredServices[$endpoint] = $url;
  83. }
  84. }
  85. }
  86. return $discoveredServices;
  87. }
  88. /**
  89. * Returns whether the specified URL includes only safe characters, if not
  90. * returns false
  91. *
  92. * @param string $url
  93. * @return bool
  94. */
  95. protected function isSafeUrl(string $url): bool {
  96. return (bool)preg_match('/^[\/\.\-A-Za-z0-9]+$/', $url);
  97. }
  98. }