From 5432ce2b0f02f10c1336e7f11a9788b40badc84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 27 Jun 2022 23:51:07 +0200 Subject: [PATCH] some fixes with wopi --- .../Service/StoredObjectManager.php | 6 +- .../src/Resources/config/services.php | 3 +- .../Service/Wopi/ChillDocumentLockManager.php | 2 +- .../src/Service/Wopi/ChillWopi.php | 99 +++++++++++++++++-- 4 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php index 7ff9b2ea8..0f867e0de 100644 --- a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php +++ b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php @@ -17,11 +17,12 @@ use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Exception\StoredObjectManagerException; use DateTimeImmutable; use DateTimeInterface; +use DateTimeZone; use RuntimeException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; use Throwable; @@ -150,7 +151,8 @@ final class StoredObjectManager implements StoredObjectManagerInterface $date = DateTimeImmutable::createFromFormat( DateTimeImmutable::RFC7231, - $lastModifiedString + $lastModifiedString, + new DateTimeZone('GMT') ); if (false === $date) { diff --git a/src/Bundle/ChillWopiBundle/src/Resources/config/services.php b/src/Bundle/ChillWopiBundle/src/Resources/config/services.php index e46c5c301..0df482135 100644 --- a/src/Bundle/ChillWopiBundle/src/Resources/config/services.php +++ b/src/Bundle/ChillWopiBundle/src/Resources/config/services.php @@ -45,8 +45,7 @@ return static function (ContainerConfigurator $container) { $services ->set(ChillDocumentLockManager::class) - ->decorate(DocumentLockManagerInterface::class) - ; + ->decorate(DocumentLockManagerInterface::class); // TODO: Move this into the async bundle (low priority) $services diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentLockManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentLockManager.php index cbd13edd4..debf63c70 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentLockManager.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentLockManager.php @@ -19,7 +19,7 @@ use RuntimeException; class ChillDocumentLockManager implements DocumentLockManagerInterface { - private const LOCK_DURATION = 60 * 30 * 1000; + private const LOCK_DURATION = 60 * 30; private int $postDeleteLockDurationMs; diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php index e17646580..72ee221d7 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php @@ -13,6 +13,7 @@ namespace Chill\WopiBundle\Service\Wopi; use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; use ChampsLibres\WopiLib\Contract\Service\WopiInterface; +use DateTimeImmutable; use DateTimeInterface; use loophp\psr17\Psr17Interface; use Psr\Cache\CacheItemPoolInterface; @@ -20,6 +21,7 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\User\UserProviderInterface; +use function strlen; final class ChillWopi implements WopiInterface { @@ -150,7 +152,97 @@ final class ChillWopi implements WopiInterface string $xWopiEditors, RequestInterface $request ): ResponseInterface { - return $this->wopi->putFile($fileId, $accessToken, $xWopiLock, $xWopiEditors, $request); + $document = $this->documentManager->findByDocumentId($fileId); + $version = $this->documentManager->getVersion($document); + + // File is unlocked, we must reject the document, except if collabora is autosaving + if (false === $this->documentManager->hasLock($document) && 'true' !== ($request->getHeader('x-lool-wopi-isexitsave') ?? ['false'])[0]) { + if (0 !== $this->documentManager->getSize($document)) { + return $this + ->psr17 + ->createResponse(409) + ->withHeader( + WopiInterface::HEADER_ITEM_VERSION, + sprintf('v%s', $version) + ); + } + } + + // File is locked, we check for the lock + if ($this->documentManager->hasLock($document)) { + if ($xWopiLock !== $currentLock = $this->documentManager->getLock($document)) { + return $this + ->psr17 + ->createResponse(409) + ->withHeader( + WopiInterface::HEADER_LOCK, + $currentLock + ) + ->withHeader( + WopiInterface::HEADER_ITEM_VERSION, + sprintf('v%s', $version) + ); + } + } + + // for collabora online editor, check timestamp if present + /* delete because it seems that collabora send always the first wopi-timestamp, not the last known one + // example: + // load the doc: the last-wopi is 12:00 in FileInfo + // save the doc: x-cool-wopi-timestamp is 12:00, but we replace the ts with the time of save (12:05) + // save the doc again: x-cool-wopi-timestamp is still 12:00... + if ($request->hasHeader('x-cool-wopi-timestamp')) { + $date = DateTimeImmutable::createFromFormat( + DateTimeImmutable::ATOM, + $request->getHeader('x-cool-wopi-timestamp')[0] + ); + + if (false === $date) { + throw new RuntimeException('Error parsing date: ' . implode('', DateTimeImmutable::getLastErrors())); + } + + if ($this->documentManager->getLastModifiedDate($document)->getTimestamp() < $date->getTimestamp()) { + return $this + ->psr17 + ->createResponse(409) + ->withHeader( + WopiInterface::HEADER_LOCK, + $currentLock + ) + ->withHeader( + WopiInterface::HEADER_ITEM_VERSION, + sprintf('v%s', $version) + ) + ->withBody( + $this->psr17->createStream( + json_encode(['COOLStatusCode' => 1010]) + ) + ); + } + } + */ + + $body = (string) $request->getBody(); + $this->documentManager->write( + $document, + [ + 'content' => $body, + 'size' => (string) strlen($body), + ] + ); + $version = $this->documentManager->getVersion($document); + + return $this + ->psr17 + ->createResponse() + ->withHeader( + WopiInterface::HEADER_LOCK, + $xWopiLock + ) + ->withHeader( + WopiInterface::HEADER_ITEM_VERSION, + sprintf('v%s', $version) + ); } public function putRelativeFile(string $fileId, string $accessToken, ?string $suggestedTarget, ?string $relativeTarget, bool $overwriteRelativeTarget, int $size, RequestInterface $request): ResponseInterface @@ -188,11 +280,6 @@ final class ChillWopi implements WopiInterface string $xWopiLock, RequestInterface $request ): ResponseInterface { - // this is because there are a delay between the request which PUT the file content, - // and the unlock: the PUT request last longer and this make the request fails, because - // the unlock terminate before. - sleep(5); - return $this->wopi->unlock($fileId, $accessToken, $xWopiLock, $request); }