diff --git a/apps/settings/lib/SetupChecks/WellKnownUrls.php b/apps/settings/lib/SetupChecks/WellKnownUrls.php index 0bba3ef817582..2ab86182e4f7c 100644 --- a/apps/settings/lib/SetupChecks/WellKnownUrls.php +++ b/apps/settings/lib/SetupChecks/WellKnownUrls.php @@ -52,13 +52,16 @@ public function getName(): string { return $this->l10n->t('.well-known URLs'); } - private function checkGetUrl(string $url, array $validStatuses = [200, 404]): bool { + /** + * @param 'get'|'propfind' $verb + */ + private function checkGetUrl(string $verb, string $url, array $validStatuses, bool $checkCustomHeader): bool { $client = $this->httpClientService->newClient(); - $response = $client->get($this->urlGenerator->getAbsoluteURL($url), ['verify' => false, 'http_errors' => false]); + $response = $client->$verb($this->urlGenerator->getAbsoluteURL($url), ['verify' => false, 'http_errors' => false]); if (!in_array($response->getStatusCode(), $validStatuses)) { return false; } - if (empty($response->getHeader('X-NEXTCLOUD-WELL-KNOWN'))) { + if ($checkCustomHeader && empty($response->getHeader('X-NEXTCLOUD-WELL-KNOWN'))) { return false; } return true; @@ -69,14 +72,32 @@ public function run(): SetupResult { return SetupResult::success($this->l10n->t('`check_for_working_wellknown_setup` is set to false in your configuration, so this check was skipped.')); } try { - foreach (['/.well-known/webfinger','/.well-known/nodeinfo'] as $url) { - if (!$this->checkGetUrl($url)) { - return SetupResult::info( - $this->l10n->t('Your web server is not properly set up to resolve "%s".', [$url]), - $this->urlGenerator->linkToDocs('admin-setup-well-known-URL') - ); + $checkList = ''; + $level = 'success'; + $urls = [ + ['get', '/.well-known/webfinger', [200, 404], true], + ['get', '/.well-known/nodeinfo', [200, 404], true], + ['propfind', '/.well-known/caldav', [207], false], + ['propfind', '/.well-known/carddav', [207], false], + ]; + foreach ($urls as [$verb,$url,$validStatuses,$checkCustomHeader]) { + if (!$this->checkGetUrl($verb, $url, $validStatuses, $checkCustomHeader)) { + $level = 'info'; + $checkList .= ' - '.strtoupper($verb).' '.$url.': failure'."\n"; + } else { + $checkList .= ' - '.strtoupper($verb).' '.$url.': success'."\n"; } } + return match($level) { + 'success' => SetupResult::success( + $this->l10n->t("Your web server is correctly configured to serve `.well-known` URLs:\n%s", [$checkList]), + $this->urlGenerator->linkToDocs('admin-setup-well-known-URL') + ), + 'info' => SetupResult::info( + $this->l10n->t("Your web server is not properly set up to resolve well-known URLs:\n%s", [$checkList]), + $this->urlGenerator->linkToDocs('admin-setup-well-known-URL') + ), + }; } catch (\Exception $e) { return SetupResult::error( $this->l10n->t('Failed to test .well-known URLs: "%s".', [$e->getMessage()]), diff --git a/lib/private/Http/Client/Client.php b/lib/private/Http/Client/Client.php index 3bf43e6c07ec6..9b1e888ebe724 100644 --- a/lib/private/Http/Client/Client.php +++ b/lib/private/Http/Client/Client.php @@ -408,6 +408,42 @@ public function options(string $uri, array $options = []): IResponse { return new Response($response); } + /** + * Sends a PROPFIND request + * + * @param string $uri + * @param array $options Array such as + * 'query' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * 'timeout' => 5, + * @return IResponse + * @throws \Exception If the request could not get completed + */ + public function propfind(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); + $response = $this->client->request('propfind', $uri, $this->buildRequestOptions($options)); + $isStream = isset($options['stream']) && $options['stream']; + return new Response($response, $isStream); + } + protected function wrapGuzzlePromise(PromiseInterface $promise): IPromise { return new GuzzlePromiseAdapter( $promise, diff --git a/lib/public/Http/Client/IClient.php b/lib/public/Http/Client/IClient.php index fb1760c25f216..affc74563cd33 100644 --- a/lib/public/Http/Client/IClient.php +++ b/lib/public/Http/Client/IClient.php @@ -207,6 +207,36 @@ public function delete(string $uri, array $options = []): IResponse; */ public function options(string $uri, array $options = []): IResponse; + /** + * Sends a PROPFIND request + * @param string $uri + * @param array $options Array such as + * 'query' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IResponse + * @throws \Exception If the request could not get completed + * @since 29.0.0 + */ + public function propfind(string $uri, array $options = []): IResponse; + /** * Sends an asynchronous GET request * @param string $uri