requestAnalyzer = new PropfindRequestAnalyzer(); } /** * @Route("/dav/{access_token}/get/{uuid}/", methods={"GET", "HEAD"}, name="chill_docstore_dav_directory_get") */ public function getDirectory(StoredObject $storedObject, string $access_token): Response { if (!$this->security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject)) { throw new AccessDeniedHttpException(); } return new DavResponse( $this->engine->render('@ChillDocStore/Webdav/directory.html.twig', [ 'stored_object' => $storedObject, 'access_token' => $access_token, ]) ); } /** * @Route("/dav/{access_token}/get/{uuid}/", methods={"OPTIONS"}) */ public function optionsDirectory(StoredObject $storedObject): Response { if (!$this->security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject)) { throw new AccessDeniedHttpException(); } $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']); $response->headers->add(['Allow' => 'OPTIONS,GET,HEAD,DELETE,PROPFIND,PUT']); return $response; } /** * @Route("/dav/{access_token}/get/{uuid}/", methods={"PROPFIND"}) */ public function propfindDirectory(StoredObject $storedObject, string $access_token, Request $request): Response { if (!$this->security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject)) { throw new AccessDeniedHttpException(); } $depth = $request->headers->get('depth'); if ('0' !== $depth && '1' !== $depth) { throw new BadRequestHttpException('only 1 and 0 are accepted for Depth header'); } [$properties, $lastModified, $etag, $length] = $this->parseDavRequest($request->getContent(), $storedObject); $response = new DavResponse( $this->engine->render('@ChillDocStore/Webdav/directory_propfind.xml.twig', [ 'stored_object' => $storedObject, 'properties' => $properties, 'last_modified' => $lastModified, 'etag' => $etag, 'content_length' => $length, 'depth' => (int) $depth, 'access_token' => $access_token, ]), 207 ); $response->headers->add([ 'Content-Type' => 'text/xml', ]); return $response; } /** * @Route("/dav/{access_token}/get/{uuid}/d", name="chill_docstore_dav_document_get", methods={"GET"}) */ public function getDocument(StoredObject $storedObject): Response { if (!$this->security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject)) { throw new AccessDeniedHttpException(); } return (new DavResponse($this->storedObjectManager->read($storedObject))) ->setEtag($this->storedObjectManager->etag($storedObject)); } /** * @Route("/dav/{access_token}/get/{uuid}/d", methods={"HEAD"}) */ public function headDocument(StoredObject $storedObject): Response { if (!$this->security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject)) { throw new AccessDeniedHttpException(); } $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/{access_token}/get/{uuid}/d", methods={"OPTIONS"}) */ public function optionsDocument(StoredObject $storedObject): Response { if (!$this->security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject)) { throw new AccessDeniedHttpException(); } $response = (new DavResponse('')) ->setEtag($this->storedObjectManager->etag($storedObject)) ; $response->headers->add(['Allow' => 'OPTIONS,GET,HEAD,DELETE,PROPFIND,PUT']); return $response; } /** * @Route("/dav/{access_token}/get/{uuid}/d", methods={"PROPFIND"}) */ public function propfindDocument(StoredObject $storedObject, string $access_token, Request $request): Response { if (!$this->security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject)) { throw new AccessDeniedHttpException(); } [$properties, $lastModified, $etag, $length] = $this->parseDavRequest($request->getContent(), $storedObject); $response = new DavResponse( $this->engine->render( '@ChillDocStore/Webdav/doc_props.xml.twig', [ 'stored_object' => $storedObject, 'properties' => $properties, 'etag' => $etag, 'last_modified' => $lastModified, 'content_length' => $length, 'access_token' => $access_token, ] ), 207 ); $response ->headers->add([ 'Content-Type' => 'text/xml', ]); return $response; } /** * @Route("/dav/{access_token}/get/{uuid}/d", methods={"PUT"}) */ public function putDocument(StoredObject $storedObject, Request $request): Response { if (!$this->security->isGranted(StoredObjectRoleEnum::EDIT->value, $storedObject)) { throw new AccessDeniedHttpException(); } $this->storedObjectManager->write($storedObject, $request->getContent()); return new DavResponse('', Response::HTTP_NO_CONTENT); } /** * @return array{0: array, 1: \DateTimeInterface, 2: string, 3: int} properties, lastModified, etag, length */ private function parseDavRequest(string $content, StoredObject $storedObject): array { $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); } return [ $properties, $lastModified ?? null, $etag ?? null, $length ?? null, ]; } }