mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-02 22:17:45 +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);
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
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