From 6893c833e46b69cf1e8ca5aec26c9f1b296ddaed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 12 Sep 2023 11:24:50 +0200 Subject: [PATCH] WIP: first tests for building dav endpoints --- .../Controller/WebdavController.php | 195 ++++++++++++++++++ .../Dav/Response/DavResponse.php | 15 ++ .../views/Webdav/directory.html.twig | 12 ++ .../views/Webdav/directory_propfind.xml.twig | 31 +++ .../Resources/views/Webdav/doc_props.xml.twig | 16 ++ .../views/Webdav/open_in_browser.html.twig | 7 + .../Service/StoredObjectManager.php | 72 +++++++ .../Service/StoredObjectManagerInterface.php | 4 + utils/http/docstore/dav.http | 16 ++ utils/http/docstore/http-client.env.json | 6 + 10 files changed, 374 insertions(+) create mode 100644 src/Bundle/ChillDocStoreBundle/Controller/WebdavController.php create mode 100644 src/Bundle/ChillDocStoreBundle/Dav/Response/DavResponse.php create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/directory.html.twig create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/directory_propfind.xml.twig create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/doc_props.xml.twig create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/open_in_browser.html.twig create mode 100644 utils/http/docstore/dav.http create mode 100644 utils/http/docstore/http-client.env.json diff --git a/src/Bundle/ChillDocStoreBundle/Controller/WebdavController.php b/src/Bundle/ChillDocStoreBundle/Controller/WebdavController.php new file mode 100644 index 000000000..5fd8f1ce6 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Controller/WebdavController.php @@ -0,0 +1,195 @@ +JWTTokenManager->createFromPayload($this->security->getUser(), [ + 'UserCanWrite' => true, + 'UserCanAttend' => true, + 'UserCanPresent' => true, + 'fileId' => $storedObject->getUuid(), + ]);*/ + + return new DavResponse($this->engine->render('@ChillDocStore/Webdav/open_in_browser.html.twig', [ + 'stored_object' => $storedObject, 'access_token' => '', + ])); + } + + /** + * @Route("/dav/get/{uuid}/", methods={"GET", "HEAD"}, name="chill_docstore_dav_directory_get") + */ + public function getDirectory(StoredObject $storedObject): Response + { + return new DavResponse( + $this->engine->render('@ChillDocStore/Webdav/directory.html.twig', [ + 'stored_object' => $storedObject + ]) + ); + } + + /** + * @Route("/dav/get/{uuid}/", methods={"OPTIONS"}) + */ + public function optionsDirectory(StoredObject $storedObject): Response + { + $response = (new DavResponse("")) + ->setEtag($this->storedObjectManager->etag($storedObject)) + ; + + $response->headers->add(['Allow' => /*sprintf( + '%s, %s, %s, %s, %s', + Request::METHOD_OPTIONS, + Request::METHOD_GET, + Request::METHOD_HEAD, + Request::METHOD_PUT, + 'PROPFIND' + ) */ 'OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT, PATCH +' + ]); + + $response->headers->add(['X-Tagada' => 'toin']); + + return $response; + } + + /** + * @Route("/dav/get/{uuid}/", methods={"PROPFIND"}) + */ + public function propfindDirectory(StoredObject $storedObject): Response + { + $lastModified = $this->storedObjectManager->getLastModified($storedObject); + $etag = $this->storedObjectManager->etag($storedObject); + $length = $this->storedObjectManager->getContentLength($storedObject); + + $response = new DavResponse( + $this->engine->render('@ChillDocStore/Webdav/directory_propfind.xml.twig', [ + 'stored_object' => $storedObject, + 'last_modified' => $lastModified, + 'etag' => $etag, + 'content_length' => $length, + ]), + 207 + ); + + $response->headers->replace([ + 'Content-Type' => 'text/xml' + ]); + + return $response; + } + + /** + * @Route("/dav/get/{uuid}/d", name="chill_docstore_dav_document_get", methods={"GET"}) + */ + public function getDocument(StoredObject $storedObject): Response + { + return (new DavResponse($this->storedObjectManager->read($storedObject))) + ->setEtag($this->storedObjectManager->etag($storedObject)); + } + + /** + * @Route("/dav/get/{uuid}/d", methods={"HEAD"}) + */ + public function headDocument(StoredObject $storedObject): Response + { + return (new DavResponse("")) + ->setEtag($this->storedObjectManager->etag($storedObject)); + } + + /** + * @Route("/dav/get/{uuid}/d", methods={"OPTIONS"}) + */ + public function optionsDocument(StoredObject $storedObject): Response + { + $response = (new DavResponse("")) + ->setEtag($this->storedObjectManager->etag($storedObject)) + ; + + $response->headers->add(['Allow' => /*sprintf( + '%s, %s, %s, %s, %s', + Request::METHOD_OPTIONS, + Request::METHOD_GET, + Request::METHOD_HEAD, + Request::METHOD_PUT, + 'PROPFIND' + ) */ 'OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT, PATCH +' + ]); + + $response->headers->add(['X-Tagada' => 'toin']); + + return $response; + } + + /** + * @Route("/dav/get/{uuid}/d", methods={"PROPFIND"}) + */ + public function propfindDocument(StoredObject $storedObject, Request $request): Response + { + $content = $request->getContent(); + $xml = new \DOMDocument(); + $xml->loadXml($content); + + dump($xml); + + $lastModified = $this->storedObjectManager->getLastModified($storedObject); + $etag = $this->storedObjectManager->etag($storedObject); + $length = $this->storedObjectManager->getContentLength($storedObject); + + $response = new DavResponse( + $this->engine->render( + '@ChillDocStore/Webdav/doc_props.xml.twig', + [ + 'stored_object' => $storedObject, + 'etag' => $etag, + 'last_modified' => $lastModified, + 'content_length' => $length, + ] + ), + 207 + ); + + $response + ->headers->replace([ + 'Content-Type' => 'text/xml' + ]); + + return $response; + } + + /** + * @Route("/dav/get/{uuid}/d", methods={"PUT"}) + */ + public function putDocument(StoredObject $storedObject, Request $request): Response + { + dump(substr($request->getContent(), 0, 500)); + + return (new DavResponse("")) + ->setEtag($this->storedObjectManager->etag()); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Dav/Response/DavResponse.php b/src/Bundle/ChillDocStoreBundle/Dav/Response/DavResponse.php new file mode 100644 index 000000000..ee9505e9c --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Dav/Response/DavResponse.php @@ -0,0 +1,15 @@ +headers->add(['DAV' => '1']); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/directory.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/directory.html.twig new file mode 100644 index 000000000..5a95e894a --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/directory.html.twig @@ -0,0 +1,12 @@ + + + + + Directory for {{ stored_object.uuid }} + + + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/directory_propfind.xml.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/directory_propfind.xml.twig new file mode 100644 index 000000000..a91d43b83 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/directory_propfind.xml.twig @@ -0,0 +1,31 @@ + + + + {{ path('chill_docstore_dav_directory_get', { 'uuid': stored_object.uuid } ) }} + + + {{ last_modified.format(constant('DATE_ISO8601')) }} + + + + 57942738233 + -3 + "{{ etag }}1" + + HTTP/1.1 200 OK + + + + {{ path('chill_docstore_dav_document_get', {'uuid': stored_object.uuid}) }} + + + Wed, 04 Sep 2019 19:02:48 GMT + {{ content_length }} + + "{{ etag }}" + {{ stored_object.type }} + + HTTP/1.1 200 OK + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/doc_props.xml.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/doc_props.xml.twig new file mode 100644 index 000000000..4e4039b0d --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/doc_props.xml.twig @@ -0,0 +1,16 @@ + + + + {{ path('chill_docstore_dav_document_get', {'uuid': stored_object.uuid}) }} + + + Wed, 04 Sep 2019 19:02:48 GMT + {{ content_length }} + + "{{ etag }}" + {{ stored_object.type }} + + HTTP/1.1 200 OK + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/open_in_browser.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/open_in_browser.html.twig new file mode 100644 index 000000000..37d137047 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/Webdav/open_in_browser.html.twig @@ -0,0 +1,7 @@ +{% extends '@ChillMain/layout.html.twig' %} + +{% block content %} +

document uuid: {{ stored_object.uuid }}

+

{{ absolute_url(path('chill_docstore_dav_document_get', {'uuid': stored_object.uuid })) }}

+Open document +{% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php index a1ef42ff1..e58a9011e 100644 --- a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php +++ b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php @@ -73,6 +73,62 @@ final class StoredObjectManager implements StoredObjectManagerInterface return $this->extractLastModifiedFromResponse($response); } + public function getContentLength(StoredObject $document): int + { + if ([] === $document->getKeyInfos()) { + if ($this->hasCache($document)) { + $response = $this->getResponseFromCache($document); + } else { + try { + $response = $this + ->client + ->request( + Request::METHOD_HEAD, + $this + ->tempUrlGenerator + ->generate( + Request::METHOD_HEAD, + $document->getFilename() + ) + ->url + ); + } catch (TransportExceptionInterface $exception) { + throw StoredObjectManagerException::errorDuringHttpRequest($exception); + } + } + + return $this->extractContentLengthFromResponse($response); + } + + return strlen($this->read($document)); + } + + public function etag(StoredObject $document): string + { + if ($this->hasCache($document)) { + $response = $this->getResponseFromCache($document); + } else { + try { + $response = $this + ->client + ->request( + Request::METHOD_HEAD, + $this + ->tempUrlGenerator + ->generate( + Request::METHOD_HEAD, + $document->getFilename() + ) + ->url + ); + } catch (TransportExceptionInterface $exception) { + throw StoredObjectManagerException::errorDuringHttpRequest($exception); + } + } + + return $this->extractEtagFromResponse($response); + } + public function read(StoredObject $document): string { $response = $this->getResponseFromCache($document); @@ -163,6 +219,22 @@ final class StoredObjectManager implements StoredObjectManagerInterface return $date; } + private function extractContentLengthFromResponse(ResponseInterface $response): int + { + return (((int) $response->getHeaders()['content-length'] ?? [])[0] ?? 0); + } + + private function extractEtagFromResponse(ResponseInterface $response): ?string + { + $etag = (($response->getHeaders()['etag'] ?? [])[0] ?? ''); + + if ('' === $etag) { + return md5($this->extractEtagFromResponse($response)); + } + + return $etag; + } + private function fillCache(StoredObject $document): void { try { diff --git a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php index caafb3937..ac9e698b4 100644 --- a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php +++ b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php @@ -18,6 +18,8 @@ interface StoredObjectManagerInterface { public function getLastModified(StoredObject $document): DateTimeInterface; + public function getContentLength(StoredObject $document): int; + /** * Get the content of a StoredObject. * @@ -34,4 +36,6 @@ interface StoredObjectManagerInterface * @param $clearContent The content to store in clear. */ public function write(StoredObject $document, string $clearContent): void; + + public function etag(StoredObject $document): string; } diff --git a/utils/http/docstore/dav.http b/utils/http/docstore/dav.http new file mode 100644 index 000000000..c566358cd --- /dev/null +++ b/utils/http/docstore/dav.http @@ -0,0 +1,16 @@ + +### Get a document +GET http://{{ host }}/dav/get/{{ uuid }}/d + +### OPTIONS on a document +OPTIONS http://{{ host }}/dav/get/{{ uuid }}/d + +### HEAD ona document +HEAD http://{{ host }}/dav/get/{{ uuid }}/d + +### Get the directory of a document +GET http://{{ host }}/dav/get/{{ uuid }}/ + +### Option the directory of a document +OPTIONS http://{{ host }}/dav/get/{{ uuid }}/ + diff --git a/utils/http/docstore/http-client.env.json b/utils/http/docstore/http-client.env.json new file mode 100644 index 000000000..0b78e77ed --- /dev/null +++ b/utils/http/docstore/http-client.env.json @@ -0,0 +1,6 @@ +{ + "dev": { + "host": "localhost:8001", + "uuid": "0bf3b8e7-b25b-4227-aae9-a3263af0766f" + } +}