Skip to content

Commit 3980061

Browse files
authored
Merge pull request #47943 from nextcloud/backport/47883/stable29
[stable29] fix(setup-checks): Ensure URL with webroot works
2 parents 7e2b9a0 + d126fc3 commit 3980061

File tree

8 files changed

+310
-49
lines changed

8 files changed

+310
-49
lines changed

apps/settings/lib/SetupChecks/CheckServerResponseTrait.php

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -54,85 +54,93 @@ protected function serverConfigHelp(): string {
5454
* Get all possible URLs that need to be checked for a local request test.
5555
* This takes all `trusted_domains` and the CLI overwrite URL into account.
5656
*
57-
* @param string $url The relative URL to test starting with a /
58-
* @return string[] List of possible absolute URLs
57+
* @param string $url The absolute path (absolute URL without host but with web-root) to test starting with a /
58+
* @param bool $isRootRequest Set to remove the web-root from URL and host (e.g. when requesting a path in the domain root like '/.well-known')
59+
* @return list<string> List of possible absolute URLs
5960
*/
60-
protected function getTestUrls(string $url, bool $removeWebroot): array {
61-
$testUrls = [];
61+
protected function getTestUrls(string $url, bool $isRootRequest = false): array {
62+
$url = '/' . ltrim($url, '/');
6263

6364
$webroot = rtrim($this->urlGenerator->getWebroot(), '/');
65+
if ($isRootRequest === false && $webroot !== '' && str_starts_with($url, $webroot)) {
66+
// The URL contains the web-root but also the base url does so,
67+
// so we need to remove the web-root from the URL.
68+
$url = substr($url, strlen($webroot));
69+
}
6470

65-
/* Try overwrite.cli.url first, it’s supposed to be how the server contacts itself */
66-
$cliUrl = $this->config->getSystemValueString('overwrite.cli.url', '');
71+
// Base URLs to test
72+
$baseUrls = [];
6773

74+
// Try overwrite.cli.url first, it’s supposed to be how the server contacts itself
75+
$cliUrl = $this->config->getSystemValueString('overwrite.cli.url', '');
6876
if ($cliUrl !== '') {
69-
$cliUrl = $this->normalizeUrl(
77+
// The CLI URL already contains the web-root, so we need to normalize it if requested
78+
$baseUrls[] = $this->normalizeUrl(
7079
$cliUrl,
71-
$webroot,
72-
$removeWebroot
80+
$isRootRequest
7381
);
74-
75-
$testUrls[] = $cliUrl . $url;
7682
}
7783

78-
/* Try URL generator second */
79-
$baseUrl = $this->normalizeUrl(
84+
// Try URL generator second
85+
// The base URL also contains the webroot so also normalize it
86+
$baseUrls[] = $this->normalizeUrl(
8087
$this->urlGenerator->getBaseUrl(),
81-
$webroot,
82-
$removeWebroot
88+
$isRootRequest
8389
);
8490

85-
if ($baseUrl !== $cliUrl) {
86-
$testUrls[] = $baseUrl . $url;
87-
}
88-
8991
/* Last resort: trusted domains */
90-
$hosts = $this->config->getSystemValue('trusted_domains', []);
91-
foreach ($hosts as $host) {
92+
$trustedDomains = $this->config->getSystemValue('trusted_domains', []);
93+
foreach ($trustedDomains as $host) {
9294
if (str_contains($host, '*')) {
9395
/* Ignore domains with a wildcard */
9496
continue;
9597
}
96-
$hosts[] = 'https://' . $host . $url;
97-
$hosts[] = 'http://' . $host . $url;
98+
$baseUrls[] = $this->normalizeUrl("https://$host$webroot", $isRootRequest);
99+
$baseUrls[] = $this->normalizeUrl("http://$host$webroot", $isRootRequest);
98100
}
99101

100-
return $testUrls;
102+
return array_map(fn (string $host) => $host . $url, array_values(array_unique($baseUrls)));
101103
}
102104

103105
/**
104106
* Strip a trailing slash and remove the webroot if requested.
107+
* @param string $url The URL to normalize. Should be an absolute URL containing scheme, host and optionally web-root.
108+
* @param bool $removeWebroot If set the web-root is removed from the URL and an absolute URL with only the scheme and host (optional port) is returned
105109
*/
106-
protected function normalizeUrl(string $url, string $webroot, bool $removeWebroot): string {
107-
$url = rtrim($url, '/');
108-
if ($removeWebroot && str_ends_with($url, $webroot)) {
109-
$url = substr($url, -strlen($webroot));
110+
protected function normalizeUrl(string $url, bool $removeWebroot): string {
111+
if ($removeWebroot) {
112+
$segments = parse_url($url);
113+
$port = isset($segments['port']) ? (':' . $segments['port']) : '';
114+
return $segments['scheme'] . '://' . $segments['host'] . $port;
110115
}
111116
return rtrim($url, '/');
112117
}
113118

114119
/**
115120
* Run a HTTP request to check header
116121
* @param string $method The HTTP method to use
117-
* @param string $url The relative URL to check
118-
* @param array{ignoreSSL?: bool, httpErrors?: bool, options?: array} $options Additional options, like
119-
* [
120-
* // Ignore invalid SSL certificates (e.g. self signed)
121-
* 'ignoreSSL' => true,
122-
* // Ignore requests with HTTP errors (will not yield if request has a 4xx or 5xx response)
123-
* 'httpErrors' => true,
124-
* ]
122+
* @param string $url The absolute path (URL with webroot but without host) to check, can be the output of `IURLGenerator`
123+
* @param bool $isRootRequest If set the webroot is removed from URLs to make the request target the host's root. Example usage are the /.well-known URLs in the root path.
124+
* @param array{ignoreSSL?: bool, httpErrors?: bool, options?: array} $options HTTP client related options, like
125+
* [
126+
* // Ignore invalid SSL certificates (e.g. self signed)
127+
* 'ignoreSSL' => true,
128+
* // Ignore requests with HTTP errors (will not yield if request has a 4xx or 5xx response)
129+
* 'httpErrors' => true,
130+
* // Additional options for the HTTP client (see `IClient`)
131+
* 'options' => [],
132+
* ]
125133
*
126134
* @return Generator<int, IResponse>
127135
*/
128-
protected function runRequest(string $method, string $url, array $options = [], bool $removeWebroot = false): Generator {
136+
protected function runRequest(string $method, string $url, array $options = [], bool $isRootRequest = false): Generator {
129137
$options = array_merge(['ignoreSSL' => true, 'httpErrors' => true], $options);
130138

131139
$client = $this->clientService->newClient();
132140
$requestOptions = $this->getRequestOptions($options['ignoreSSL'], $options['httpErrors']);
133141
$requestOptions = array_merge($requestOptions, $options['options'] ?? []);
134142

135-
foreach ($this->getTestUrls($url, $removeWebroot) as $testURL) {
143+
foreach ($this->getTestUrls($url, $isRootRequest) as $testURL) {
136144
try {
137145
yield $client->request($method, $testURL, $requestOptions);
138146
} catch (\Throwable $e) {
@@ -143,7 +151,7 @@ protected function runRequest(string $method, string $url, array $options = [],
143151

144152
/**
145153
* Run a HEAD request to check header
146-
* @param string $url The relative URL to check
154+
* @param string $url The relative URL to check (e.g. output of IURLGenerator)
147155
* @param bool $ignoreSSL Ignore SSL certificates
148156
* @param bool $httpErrors Ignore requests with HTTP errors (will not yield if request has a 4xx or 5xx response)
149157
* @return Generator<int, IResponse>

apps/settings/lib/SetupChecks/DataDirectoryProtected.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,8 @@ public function getName(): string {
5757
}
5858

5959
public function run(): SetupResult {
60-
$datadir = str_replace(\OC::$SERVERROOT . '/', '', $this->config->getSystemValue('datadirectory', ''));
61-
62-
$dataUrl = $this->urlGenerator->getWebroot() . '/' . $datadir . '/.ocdata';
60+
$dataDir = str_replace(\OC::$SERVERROOT . '/', '', $this->config->getSystemValueString('datadirectory', ''));
61+
$dataUrl = $this->urlGenerator->linkTo('', $dataDir . '/.ocdata');
6362

6463
$noResponse = true;
6564
foreach ($this->runHEAD($dataUrl, httpErrors:false) as $response) {

apps/settings/lib/SetupChecks/WellKnownUrls.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@ public function run(): SetupResult {
6868
['propfind', '/.well-known/carddav', [207], false],
6969
];
7070

71+
$requestOptions = ['httpErrors' => false, 'options' => ['allow_redirects' => ['track_redirects' => true]]];
7172
foreach ($urls as [$verb,$url,$validStatuses,$checkCustomHeader]) {
7273
$works = null;
73-
foreach ($this->runRequest($verb, $url, ['httpErrors' => false, 'options' => ['allow_redirects' => ['track_redirects' => true]]], removeWebroot: true) as $response) {
74+
foreach ($this->runRequest($verb, $url, $requestOptions, isRootRequest: true) as $response) {
7475
// Check that the response status matches
7576
$works = in_array($response->getStatusCode(), $validStatuses);
7677
// and (if needed) the custom Nextcloud header is set
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OCA\Settings\Tests\SetupChecks;
10+
11+
use OCA\Settings\SetupChecks\CheckServerResponseTrait;
12+
use OCP\Http\Client\IClientService;
13+
use OCP\IConfig;
14+
use OCP\IL10N;
15+
use OCP\IURLGenerator;
16+
use Psr\Log\LoggerInterface;
17+
18+
/**
19+
* Dummy implementation for CheckServerResponseTraitTest
20+
*/
21+
class CheckServerResponseTraitImplementation {
22+
23+
use CheckServerResponseTrait {
24+
CheckServerResponseTrait::getRequestOptions as public;
25+
CheckServerResponseTrait::runHEAD as public;
26+
CheckServerResponseTrait::runRequest as public;
27+
CheckServerResponseTrait::normalizeUrl as public;
28+
CheckServerResponseTrait::getTestUrls as public;
29+
}
30+
31+
public function __construct(
32+
protected IL10N $l10n,
33+
protected IConfig $config,
34+
protected IURLGenerator $urlGenerator,
35+
protected IClientService $clientService,
36+
protected LoggerInterface $logger,
37+
) {
38+
}
39+
40+
}

0 commit comments

Comments
 (0)