mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-04 06:57:42 +00:00
WIP: first tests for building dav endpoints
This commit is contained in:
parent
ae9f1e3905
commit
6893c833e4
195
src/Bundle/ChillDocStoreBundle/Controller/WebdavController.php
Normal file
195
src/Bundle/ChillDocStoreBundle/Controller/WebdavController.php
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Dav\Response\DavResponse;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||||
|
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Templating\EngineInterface;
|
||||||
|
|
||||||
|
final readonly class WebdavController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private EngineInterface $engine,
|
||||||
|
private JWTTokenManagerInterface $JWTTokenManager,
|
||||||
|
private Security $security,
|
||||||
|
private StoredObjectManagerInterface $storedObjectManager,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/dav/open/{uuid}")
|
||||||
|
*/
|
||||||
|
public function open(StoredObject $storedObject): Response
|
||||||
|
{
|
||||||
|
/*$accessToken = $this->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());
|
||||||
|
}
|
||||||
|
}
|
15
src/Bundle/ChillDocStoreBundle/Dav/Response/DavResponse.php
Normal file
15
src/Bundle/ChillDocStoreBundle/Dav/Response/DavResponse.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\Dav\Response;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class DavResponse extends Response
|
||||||
|
{
|
||||||
|
public function __construct($content = '', int $status = 200, array $headers = [])
|
||||||
|
{
|
||||||
|
parent::__construct($content, $status, $headers);
|
||||||
|
|
||||||
|
$this->headers->add(['DAV' => '1']);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory for {{ stored_object.uuid }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ absolute_url(path('chill_docstore_dav_document_get', {'uuid': stored_object.uuid })) }}">d</a></li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<d:multistatus xmlns:d="DAV:">
|
||||||
|
<d:response>
|
||||||
|
<d:href>{{ path('chill_docstore_dav_directory_get', { 'uuid': stored_object.uuid } ) }}</d:href>
|
||||||
|
<d:propstat>
|
||||||
|
<d:prop>
|
||||||
|
<d:getlastmodified>{{ last_modified.format(constant('DATE_ISO8601')) }}</d:getlastmodified>
|
||||||
|
<d:resourcetype>
|
||||||
|
<d:collection/>
|
||||||
|
</d:resourcetype>
|
||||||
|
<d:quota-used-bytes>57942738233</d:quota-used-bytes>
|
||||||
|
<d:quota-available-bytes>-3</d:quota-available-bytes>
|
||||||
|
<d:getetag>"{{ etag }}1"</d:getetag>
|
||||||
|
</d:prop>
|
||||||
|
<d:status>HTTP/1.1 200 OK</d:status>
|
||||||
|
</d:propstat>
|
||||||
|
</d:response>
|
||||||
|
<d:response>
|
||||||
|
<d:href>{{ path('chill_docstore_dav_document_get', {'uuid': stored_object.uuid}) }}</d:href>
|
||||||
|
<d:propstat>
|
||||||
|
<d:prop>
|
||||||
|
<d:getlastmodified>Wed, 04 Sep 2019 19:02:48 GMT</d:getlastmodified>
|
||||||
|
<d:getcontentlength>{{ content_length }}</d:getcontentlength>
|
||||||
|
<d:resourcetype/>
|
||||||
|
<d:getetag>"{{ etag }}"</d:getetag>
|
||||||
|
<d:getcontenttype>{{ stored_object.type }}</d:getcontenttype>
|
||||||
|
</d:prop>
|
||||||
|
<d:status>HTTP/1.1 200 OK</d:status>
|
||||||
|
</d:propstat>
|
||||||
|
</d:response>
|
||||||
|
</d:multistatus>
|
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<d:multistatus xmlns:d="DAV:">
|
||||||
|
<d:response>
|
||||||
|
<d:href>{{ path('chill_docstore_dav_document_get', {'uuid': stored_object.uuid}) }}</d:href>
|
||||||
|
<d:propstat>
|
||||||
|
<d:prop>
|
||||||
|
<d:getlastmodified>Wed, 04 Sep 2019 19:02:48 GMT</d:getlastmodified>
|
||||||
|
<d:getcontentlength>{{ content_length }}</d:getcontentlength>
|
||||||
|
<d:resourcetype/>
|
||||||
|
<d:getetag>"{{ etag }}"</d:getetag>
|
||||||
|
<d:getcontenttype>{{ stored_object.type }}</d:getcontenttype>
|
||||||
|
</d:prop>
|
||||||
|
<d:status>HTTP/1.1 200 OK</d:status>
|
||||||
|
</d:propstat>
|
||||||
|
</d:response>
|
||||||
|
</d:multistatus>
|
@ -0,0 +1,7 @@
|
|||||||
|
{% extends '@ChillMain/layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p>document uuid: {{ stored_object.uuid }}</p>
|
||||||
|
<p>{{ absolute_url(path('chill_docstore_dav_document_get', {'uuid': stored_object.uuid })) }}</p>
|
||||||
|
<a href="vnd.libreoffice.command:ofe|u|{{ absolute_url(path('chill_docstore_dav_document_get', {'uuid': stored_object.uuid })) }}">Open document</a>
|
||||||
|
{% endblock %}
|
@ -73,6 +73,62 @@ final class StoredObjectManager implements StoredObjectManagerInterface
|
|||||||
return $this->extractLastModifiedFromResponse($response);
|
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
|
public function read(StoredObject $document): string
|
||||||
{
|
{
|
||||||
$response = $this->getResponseFromCache($document);
|
$response = $this->getResponseFromCache($document);
|
||||||
@ -163,6 +219,22 @@ final class StoredObjectManager implements StoredObjectManagerInterface
|
|||||||
return $date;
|
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
|
private function fillCache(StoredObject $document): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
@ -18,6 +18,8 @@ interface StoredObjectManagerInterface
|
|||||||
{
|
{
|
||||||
public function getLastModified(StoredObject $document): DateTimeInterface;
|
public function getLastModified(StoredObject $document): DateTimeInterface;
|
||||||
|
|
||||||
|
public function getContentLength(StoredObject $document): int;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the content of a StoredObject.
|
* Get the content of a StoredObject.
|
||||||
*
|
*
|
||||||
@ -34,4 +36,6 @@ interface StoredObjectManagerInterface
|
|||||||
* @param $clearContent The content to store in clear.
|
* @param $clearContent The content to store in clear.
|
||||||
*/
|
*/
|
||||||
public function write(StoredObject $document, string $clearContent): void;
|
public function write(StoredObject $document, string $clearContent): void;
|
||||||
|
|
||||||
|
public function etag(StoredObject $document): string;
|
||||||
}
|
}
|
||||||
|
16
utils/http/docstore/dav.http
Normal file
16
utils/http/docstore/dav.http
Normal file
@ -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 }}/
|
||||||
|
|
6
utils/http/docstore/http-client.env.json
Normal file
6
utils/http/docstore/http-client.env.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"dev": {
|
||||||
|
"host": "localhost:8001",
|
||||||
|
"uuid": "0bf3b8e7-b25b-4227-aae9-a3263af0766f"
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user