TrustedDomainHelper.php 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  6. * SPDX-License-Identifier: AGPL-3.0-only
  7. */
  8. namespace OC\Security;
  9. use OC\AppFramework\Http\Request;
  10. use OCP\IConfig;
  11. use OCP\Security\ITrustedDomainHelper;
  12. class TrustedDomainHelper implements ITrustedDomainHelper {
  13. public function __construct(
  14. private IConfig $config,
  15. ) {
  16. }
  17. /**
  18. * Strips a potential port from a domain (in format domain:port)
  19. * @return string $host without appended port
  20. */
  21. private function getDomainWithoutPort(string $host): string {
  22. $pos = strrpos($host, ':');
  23. if ($pos !== false) {
  24. $port = substr($host, $pos + 1);
  25. if (is_numeric($port)) {
  26. $host = substr($host, 0, $pos);
  27. }
  28. }
  29. return $host;
  30. }
  31. /**
  32. * {@inheritDoc}
  33. */
  34. public function isTrustedUrl(string $url): bool {
  35. $parsedUrl = parse_url($url);
  36. if (empty($parsedUrl['host'])) {
  37. return false;
  38. }
  39. if (isset($parsedUrl['port']) && $parsedUrl['port']) {
  40. return $this->isTrustedDomain($parsedUrl['host'] . ':' . $parsedUrl['port']);
  41. }
  42. return $this->isTrustedDomain($parsedUrl['host']);
  43. }
  44. /**
  45. * {@inheritDoc}
  46. */
  47. public function isTrustedDomain(string $domainWithPort): bool {
  48. // overwritehost is always trusted
  49. if ($this->config->getSystemValue('overwritehost') !== '') {
  50. return true;
  51. }
  52. $domain = $this->getDomainWithoutPort($domainWithPort);
  53. // Read trusted domains from config
  54. $trustedList = $this->config->getSystemValue('trusted_domains', []);
  55. if (!is_array($trustedList)) {
  56. return false;
  57. }
  58. // Always allow access from localhost
  59. if (preg_match(Request::REGEX_LOCALHOST, $domain) === 1) {
  60. return true;
  61. }
  62. // Reject malformed domains in any case
  63. if (str_starts_with($domain, '-') || str_contains($domain, '..')) {
  64. return false;
  65. }
  66. // Match, allowing for * wildcards
  67. foreach ($trustedList as $trusted) {
  68. if (gettype($trusted) !== 'string') {
  69. break;
  70. }
  71. $regex = '/^' . implode('[-\.a-zA-Z0-9]*', array_map(function ($v) {
  72. return preg_quote($v, '/');
  73. }, explode('*', $trusted))) . '$/i';
  74. if (preg_match($regex, $domain) || preg_match($regex, $domainWithPort)) {
  75. return true;
  76. }
  77. }
  78. return false;
  79. }
  80. }