Webdav: fully implements the controller and response

The controller is tested from real request scraped from apache mod_dav implementation. The requests were scraped using a wireshark-like tool. Those requests have been adapted to suit to our xml.
This commit is contained in:
2023-09-12 11:24:50 +02:00
parent 20291026fb
commit 146e0090fb
15 changed files with 1168 additions and 0 deletions

View File

@@ -0,0 +1,232 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocStoreBundle\Controller;
use Chill\DocStoreBundle\Dav\Request\PropfindRequestAnalyzer;
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\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Templating\EngineInterface;
final readonly class WebdavController
{
private PropfindRequestAnalyzer $requestAnalyzer;
public function __construct(
private \Twig\Environment $engine,
private StoredObjectManagerInterface $storedObjectManager,
) {
$this->requestAnalyzer = new PropfindRequestAnalyzer();
}
/**
* @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' => 'OPTIONS,GET,HEAD,DELETE,PROPFIND,PUT,PROPPATCH,COPY,MOVE,REPORT,PATCH,POST,TRACE']);
return $response;
}
/**
* @Route("/dav/get/{uuid}/", methods={"PROPFIND"})
*/
public function propfindDirectory(StoredObject $storedObject, Request $request): Response
{
$depth = $request->headers->get('depth');
if ("0" !== $depth && "1" !== $depth) {
throw new BadRequestHttpException("only 1 and 0 are accepted for Depth header");
}
$content = $request->getContent();
$xml = new \DOMDocument();
$xml->loadXML($content);
$properties = $this->requestAnalyzer->getRequestedProperties($xml);
$requested = array_keys(array_filter($properties, fn ($item) => true === $item));
if (
in_array('lastModified', $requested, true)
|| in_array('etag', $requested, true)
) {
$lastModified = $this->storedObjectManager->getLastModified($storedObject);
$etag = $this->storedObjectManager->etag($storedObject);
}
if (in_array('contentLength', $requested, true)) {
$length = $this->storedObjectManager->getContentLength($storedObject);
}
$response = new DavResponse(
$this->engine->render('@ChillDocStore/Webdav/directory_propfind.xml.twig', [
'stored_object' => $storedObject,
'properties' => $properties,
'last_modified' => $lastModified ?? null,
'etag' => $etag ?? null,
'content_length' => $length ?? null,
'depth' => (int) $depth
]),
207
);
$response->headers->add([
'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
{
$response = new DavResponse("");
$response->headers->add(
[
'Content-Length' => $this->storedObjectManager->getContentLength($storedObject),
'Content-Type' => $storedObject->getType(),
'Etag' => $this->storedObjectManager->etag($storedObject)
]
);
return $response;
}
/**
* @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,POST,TRACE'
]);
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);
$properties = $this->requestAnalyzer->getRequestedProperties($xml);
$requested = array_keys(array_filter($properties, fn ($item) => true === $item));
if (
in_array('lastModified', $requested, true)
|| in_array('etag', $requested, true)
) {
$lastModified = $this->storedObjectManager->getLastModified($storedObject);
$etag = $this->storedObjectManager->etag($storedObject);
}
if (in_array('contentLength', $requested, true)) {
$length = $this->storedObjectManager->getContentLength($storedObject);
}
$response = new DavResponse(
$this->engine->render(
'@ChillDocStore/Webdav/doc_props.xml.twig',
[
'stored_object' => $storedObject,
'properties' => $properties,
'etag' => $etag ?? null,
'last_modified' => $lastModified ?? null,
'content_length' => $length ?? null,
]
),
207
);
$response
->headers->add([
'Content-Type' => 'text/xml'
]);
return $response;
}
/**
* @Route("/dav/get/{uuid}/d", methods={"PUT"})
*/
public function putDocument(StoredObject $storedObject, Request $request): Response
{
$this->storedObjectManager->write($storedObject, $request->getContent());
return (new DavResponse("", Response::HTTP_NO_CONTENT));
}
}