fix(caldav): add missing handlers

Signed-off-by: Anna Larch <anna@nextcloud.com>
This commit is contained in:
Anna Larch 2024-08-19 13:28:04 +02:00
parent 1c1f48830a
commit d30c0669a6
3 changed files with 66 additions and 108 deletions

View file

@ -9,15 +9,11 @@ declare(strict_types=1);
namespace OCA\DAV\CalDAV\WebcalCaching;
use Exception;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\RequestOptions;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\LocalServerException;
use OCP\IAppConfig;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Sabre\DAV\Xml\Property\Href;
use Sabre\VObject\Reader;
class Connection {
@ -31,107 +27,82 @@ class Connection {
/**
* gets webcal feed from remote server
*/
public function queryWebcalFeed(array $subscription, array &$mutations): ?string {
$client = $this->clientService->newClient();
$didBreak301Chain = false;
$latestLocation = null;
$handlerStack = HandlerStack::create();
$handlerStack->push(Middleware::mapRequest(function (RequestInterface $request) {
return $request
->withHeader('Accept', 'text/calendar, application/calendar+json, application/calendar+xml')
->withHeader('User-Agent', 'Nextcloud Webcal Service');
}));
$handlerStack->push(Middleware::mapResponse(function (ResponseInterface $response) use (&$didBreak301Chain, &$latestLocation) {
if (!$didBreak301Chain) {
if ($response->getStatusCode() !== 301) {
$didBreak301Chain = true;
} else {
$latestLocation = $response->getHeader('Location');
}
}
return $response;
}));
$allowLocalAccess = $this->config->getValueString('dav', 'webcalAllowLocalAccess', 'no');
public function queryWebcalFeed(array $subscription): ?string {
$subscriptionId = $subscription['id'];
$url = $this->cleanURL($subscription['source']);
if ($url === null) {
return null;
}
$allowLocalAccess = $this->config->getValueString('dav', 'webcalAllowLocalAccess', 'no');
$params = [
'nextcloud' => [
'allow_local_address' => $allowLocalAccess === 'yes',
],
RequestOptions::HEADERS => [
'User-Agent' => 'Nextcloud Webcal Service',
'Accept' => 'text/calendar, application/calendar+json, application/calendar+xml',
],
];
$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 {
$params = [
'allow_redirects' => [
'redirects' => 10
],
'handler' => $handlerStack,
'nextcloud' => [
'allow_local_address' => $allowLocalAccess === 'yes',
]
];
$user = parse_url($subscription['source'], PHP_URL_USER);
$pass = parse_url($subscription['source'], PHP_URL_PASS);
if ($user !== null && $pass !== null) {
$params['auth'] = [$user, $pass];
}
$client = $this->clientService->newClient();
$response = $client->get($url, $params);
$body = $response->getBody();
if ($latestLocation !== null) {
$mutations['{http://calendarserver.org/ns/}source'] = new Href($latestLocation);
}
$contentType = $response->getHeader('Content-Type');
$contentType = explode(';', $contentType, 2)[0];
switch ($contentType) {
case 'application/calendar+json':
try {
$jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING);
} catch (Exception $ex) {
// In case of a parsing error return null
$this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
return null;
}
return $jCalendar->serialize();
case 'application/calendar+xml':
try {
$xCalendar = Reader::readXML($body);
} catch (Exception $ex) {
// In case of a parsing error return null
$this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
return null;
}
return $xCalendar->serialize();
case 'text/calendar':
default:
try {
$vCalendar = Reader::read($body);
} catch (Exception $ex) {
// In case of a parsing error return null
$this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
return null;
}
return $vCalendar->serialize();
}
} 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;
}
$body = $response->getBody();
$contentType = $response->getHeader('Content-Type');
$contentType = explode(';', $contentType, 2)[0];
switch ($contentType) {
case 'application/calendar+json':
try {
$jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING);
} catch (Exception $ex) {
// In case of a parsing error return null
$this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
return null;
}
return $jCalendar->serialize();
case 'application/calendar+xml':
try {
$xCalendar = Reader::readXML($body);
} catch (Exception $ex) {
// In case of a parsing error return null
$this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
return null;
}
return $xCalendar->serialize();
case 'text/calendar':
default:
try {
$vCalendar = Reader::read($body);
} catch (Exception $ex) {
// In case of a parsing error return null
$this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
return null;
}
return $vCalendar->serialize();
}
}
/**

View file

@ -57,7 +57,7 @@ class RefreshWebcalService {
}
$webcalData = $this->connection->queryWebcalFeed($subscription, $mutations);
$webcalData = $this->connection->queryWebcalFeed($subscription);
if (!$webcalData) {
return;
}

View file

@ -7,7 +7,6 @@ declare(strict_types=1);
*/
namespace OCA\DAV\Tests\unit\CalDAV\WebcalCaching;
use GuzzleHttp\HandlerStack;
use OCA\DAV\CalDAV\WebcalCaching\Connection;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
@ -48,7 +47,6 @@ class ConnectionTest extends TestCase {
'source' => $source,
'lastmodified' => 0,
];
$mutation = [];
$client = $this->createMock(IClient::class);
$this->clientService->expects(self::once())
@ -69,7 +67,7 @@ class ConnectionTest extends TestCase {
->method('warning')
->with('Subscription 42 was not refreshed because it violates local access rules', ['exception' => $localServerException]);
$this->connection->queryWebcalFeed($subscription, $mutation);
$this->connection->queryWebcalFeed($subscription);
}
public function testInvalidUrl(): void {
@ -83,22 +81,14 @@ class ConnectionTest extends TestCase {
'source' => '!@#$',
'lastmodified' => 0,
];
$mutation = [];
$client = $this->createMock(IClient::class);
$this->clientService->expects(self::once())
->method('newClient')
->with()
->willReturn($client);
$this->config->expects(self::once())
->method('getValueString')
->with('dav', 'webcalAllowLocalAccess', 'no')
->willReturn('no');
$this->config->expects(self::never())
->method('getValueString');
$client->expects(self::never())
->method('get');
$this->connection->queryWebcalFeed($subscription, $mutation);
$this->connection->queryWebcalFeed($subscription);
}
@ -120,7 +110,6 @@ class ConnectionTest extends TestCase {
'source' => $url,
'lastmodified' => 0,
];
$mutation = [];
$this->clientService->expects($this->once())
->method('newClient')
@ -134,9 +123,7 @@ class ConnectionTest extends TestCase {
$client->expects($this->once())
->method('get')
->with('https://foo.bar/bla2', $this->callback(function ($obj) {
return $obj['allow_redirects']['redirects'] === 10 && $obj['handler'] instanceof HandlerStack;
}))
->with('https://foo.bar/bla2')
->willReturn($response);
$response->expects($this->once())
@ -148,9 +135,9 @@ class ConnectionTest extends TestCase {
->with('Content-Type')
->willReturn($contentType);
$this->connection->queryWebcalFeed($subscription, $mutation);
$this->connection->queryWebcalFeed($subscription);
}
public static function runLocalURLDataProvider(): array {
return [
['localhost/foo.bar'],