136 lines
3.8 KiB
PHP
136 lines
3.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
namespace OCA\DAV\CalDAV\WebcalCaching;
|
|
|
|
use Exception;
|
|
use GuzzleHttp\RequestOptions;
|
|
use OCP\Http\Client\IClientService;
|
|
use OCP\Http\Client\LocalServerException;
|
|
use OCP\IAppConfig;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
class Connection {
|
|
public function __construct(
|
|
private IClientService $clientService,
|
|
private IAppConfig $config,
|
|
private LoggerInterface $logger,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* gets webcal feed from remote server
|
|
*
|
|
* @return array{data: resource, format: string}|null
|
|
*/
|
|
public function queryWebcalFeed(array $subscription): ?array {
|
|
$subscriptionId = $subscription['id'];
|
|
$url = $this->cleanURL($subscription['source']);
|
|
if ($url === null) {
|
|
return null;
|
|
}
|
|
|
|
// ICS feeds hosted on O365 can return HTTP 500 when the UA string isn't satisfactory
|
|
// Ref https://github.com/nextcloud/calendar/issues/7234
|
|
$uaString = 'Nextcloud Webcal Service';
|
|
if (parse_url($url, PHP_URL_HOST) === 'outlook.office365.com') {
|
|
// The required format/values here are not documented.
|
|
// Instead, this string based on research.
|
|
// Ref https://github.com/bitfireAT/icsx5/discussions/654#discussioncomment-14158051
|
|
$uaString = 'Nextcloud (Linux) Chrome/66';
|
|
}
|
|
|
|
$allowLocalAccess = $this->config->getValueString('dav', 'webcalAllowLocalAccess', 'no');
|
|
|
|
$params = [
|
|
'nextcloud' => [
|
|
'allow_local_address' => $allowLocalAccess === 'yes',
|
|
],
|
|
RequestOptions::HEADERS => [
|
|
'User-Agent' => $uaString,
|
|
'Accept' => 'text/calendar, application/calendar+json, application/calendar+xml',
|
|
],
|
|
'stream' => true,
|
|
];
|
|
|
|
$user = parse_url($subscription['source'], PHP_URL_USER);
|
|
$pass = parse_url($subscription['source'], PHP_URL_PASS);
|
|
if ($user !== null && $pass !== null) {
|
|
$params[RequestOptions::AUTH] = [$user, $pass];
|
|
}
|
|
|
|
try {
|
|
$client = $this->clientService->newClient();
|
|
$response = $client->get($url, $params);
|
|
} catch (LocalServerException $ex) {
|
|
$this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules", [
|
|
'exception' => $ex,
|
|
]);
|
|
return null;
|
|
} catch (Exception $ex) {
|
|
$this->logger->warning("Subscription $subscriptionId could not be refreshed due to a network error", [
|
|
'exception' => $ex,
|
|
]);
|
|
return null;
|
|
}
|
|
|
|
$contentType = $response->getHeader('Content-Type');
|
|
$contentType = explode(';', $contentType, 2)[0];
|
|
|
|
$format = match ($contentType) {
|
|
'application/calendar+json' => 'jcal',
|
|
'application/calendar+xml' => 'xcal',
|
|
default => 'ical',
|
|
};
|
|
|
|
// With 'stream' => true, getBody() returns the underlying stream resource
|
|
$stream = $response->getBody();
|
|
if (!is_resource($stream)) {
|
|
return null;
|
|
}
|
|
|
|
return ['data' => $stream, 'format' => $format];
|
|
}
|
|
|
|
/**
|
|
* This method will strip authentication information and replace the
|
|
* 'webcal' or 'webcals' protocol scheme
|
|
*
|
|
* @param string $url
|
|
* @return string|null
|
|
*/
|
|
private function cleanURL(string $url): ?string {
|
|
$parsed = parse_url($url);
|
|
if ($parsed === false) {
|
|
return null;
|
|
}
|
|
|
|
if (isset($parsed['scheme']) && $parsed['scheme'] === 'http') {
|
|
$scheme = 'http';
|
|
} else {
|
|
$scheme = 'https';
|
|
}
|
|
|
|
$host = $parsed['host'] ?? '';
|
|
$port = isset($parsed['port']) ? ':' . $parsed['port'] : '';
|
|
$path = $parsed['path'] ?? '';
|
|
$query = isset($parsed['query']) ? '?' . $parsed['query'] : '';
|
|
$fragment = isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
|
|
|
|
$cleanURL = "$scheme://$host$port$path$query$fragment";
|
|
// parse_url is giving some weird results if no url and no :// is given,
|
|
// so let's test the url again
|
|
$parsedClean = parse_url($cleanURL);
|
|
if ($parsedClean === false || !isset($parsedClean['host'])) {
|
|
return null;
|
|
}
|
|
|
|
return $cleanURL;
|
|
}
|
|
}
|