@@ -19,7 +19,10 @@ class Server
1919
2020 public const ERROR_CAN_NOT_PARSE_FOR_PATCH = 'Could not parse the requested resource for patching ' ;
2121 public const ERROR_CAN_NOT_DELETE_NON_EMPTY_CONTAINER = 'Only empty containers can be deleted, "%s" is not empty ' ;
22+ public const ERROR_CAN_NOT_PARSE_METADATA = 'Could not parse metadata for %s ' ;
23+ public const ERROR_CAN_NOT_REDIRECT_WITHOUT_URL = "Cannot create %s: no URL set " ;
2224 public const ERROR_MISSING_SPARQL_CONTENT_TYPE = 'Request is missing required Content-Type "application/sparql-update" or "application/sparql-update-single-match" ' ;
25+ public const ERROR_MULTIPLE_LINK_METADATA_FOUND = 'More than one link-metadata found for %s ' ;
2326 public const ERROR_NOT_IMPLEMENTED_SPARQL = 'SPARQL Not Implemented ' ;
2427 public const ERROR_PATH_DOES_NOT_EXIST = 'Requested path "%s" does not exist ' ;
2528 public const ERROR_PATH_EXISTS = 'Requested path "%s" already exists ' ;
@@ -538,40 +541,43 @@ private function handleReadRequest(Response $response, string $path, $contents,
538541 $ response ->getBody ()->write ($ contents );
539542 $ response = $ response ->withHeader ("Content-type " , "text/turtle " );
540543 $ response = $ response ->withStatus (200 );
541- } elseif ($ filesystem ->has ($ path ) === false ) {
544+ } elseif ($ filesystem ->has ($ path ) === false && $ this ->hasDescribedBy ($ path ) === false ) {
545+ /*/ The file does not exist and no link-metadata is present /*/
542546 $ message = vsprintf (self ::ERROR_PATH_DOES_NOT_EXIST , [$ path ]);
543547 $ response ->getBody ()->write ($ message );
544548 $ response = $ response ->withStatus (404 );
545549 } else {
546- $ mimetype = $ filesystem ->getMimetype ($ path );
547- if ($ mimetype === self ::MIME_TYPE_DIRECTORY ) {
550+ $ linkMetadataResponse = $ this ->handleLinkMetadata ($ response , $ path );
551+ if ($ linkMetadataResponse !== null ) {
552+ /*/ Link-metadata is present, return the altered response /*/
553+ $ response = $ linkMetadataResponse ;
554+ } elseif ($ filesystem ->getMimetype ($ path ) === self ::MIME_TYPE_DIRECTORY ) {
548555 $ contents = $ this ->listDirectoryAsTurtle ($ path );
549556 $ response ->getBody ()->write ($ contents );
550- $ response = $ response ->withHeader ("Content-type " , "text/turtle " );
551- $ response = $ response ->withStatus (200 );
552- } else {
553- if ($ filesystem ->asMime ($ mime )->has ($ path )) {
554- $ contents = $ filesystem ->asMime ($ mime )->read ($ path );
557+ $ response = $ response ->withHeader ("Content-type " , "text/turtle " )->withStatus (200 );
558+ } elseif ($ filesystem ->asMime ($ mime )->has ($ path )) {
559+ /*/ The file does exist and no link-metadata is present /*/
560+ $ response = $ this ->addLinkRelationHeaders ($ response , $ path , $ mime );
561+
562+ if (preg_match ('/.ttl$/ ' , $ path )) {
563+ $ mimetype = "text/turtle " ; // FIXME: teach flysystem that .ttl means text/turtle
564+ } elseif (preg_match ('/.acl$/ ' , $ path )) {
565+ $ mimetype = "text/turtle " ; // FIXME: teach flysystem that .acl files also want text/turtle
566+ } else {
567+ $ mimetype = $ filesystem ->asMime ($ mime )->getMimetype ($ path );
568+ }
555569
556- $ response = $ this -> addLinkRelationHeaders ( $ response , $ path, $ mime );
570+ $ contents = $ filesystem -> asMime ( $ mime )-> read ( $ path );
557571
558- if (preg_match ('/.ttl$/ ' , $ path )) {
559- $ mimetype = "text/turtle " ; // FIXME: teach flysystem that .ttl means text/turtle
560- } elseif (preg_match ('/.acl$/ ' , $ path )) {
561- $ mimetype = "text/turtle " ; // FIXME: teach flysystem that .acl files also want text/turtle
562- } else {
563- $ mimetype = $ filesystem ->asMime ($ mime )->getMimetype ($ path );
564- }
565- if ($ contents !== false ) {
566- $ response ->getBody ()->write ($ contents );
567- $ response = $ response ->withHeader ("Content-type " , $ mimetype );
568- $ response = $ response ->withStatus (200 );
569- }
570- } else {
571- $ message = vsprintf (self ::ERROR_PATH_DOES_NOT_EXIST , [$ path ]);
572- $ response ->getBody ()->write ($ message );
573- $ response = $ response ->withStatus (404 );
574- }
572+ if ($ contents !== false ) {
573+ $ response ->getBody ()->write ($ contents );
574+ $ response = $ response ->withHeader ("Content-type " , $ mimetype )->withStatus (200 );
575+ }
576+ } else {
577+ /*/ The file does exist in another format and no link-metadata is present /*/
578+ $ message = vsprintf (self ::ERROR_PATH_DOES_NOT_EXIST , [$ path ]);
579+ $ response ->getBody ()->write ($ message );
580+ $ response = $ response ->withStatus (404 );
575581 }
576582 }
577583
@@ -609,6 +615,10 @@ private function listDirectoryAsTurtle($path)
609615 );
610616
611617 foreach ($ listContents as $ item ) {
618+
619+ // @FIXME: $item needs to be checked for "describedby" metadata to see if they need Meta Link handling
620+ // for instance be added to the file list
621+
612622 switch ($ item ['type ' ]) {
613623 case "file " :
614624 // ACL and meta files should not be listed in directory overview
@@ -734,4 +744,110 @@ private function hasDescribedBy(string $path, $mime = null): bool
734744 {
735745 return $ this ->getDescribedByPath ($ path , $ mime ) !== '' ;
736746 }
747+
748+ // =========================================================================
749+ // @TODO: All link-metadata Response logic should probably be moved to a separate class.
750+
751+ private function handleLinkMetadata (Response $ response , string $ path )
752+ {
753+ $ returnResponse = null ;
754+
755+ // @FIXME: If a $path is a file underneath a directory that has been marked as moved, the response should also be a redirect/404/410.
756+
757+ if ($ this ->hasDescribedBy ($ path )) {
758+ $ linkMeta = $ this ->parseLinkedMetadata ($ path );
759+
760+ if (count ($ linkMeta ) > 1 ) {
761+ throw Exception::create (self ::ERROR_MULTIPLE_LINK_METADATA_FOUND , [$ path ]);
762+ }
763+
764+ $ returnResponse = $ this ->buildLinkMetadataResponse ($ linkMeta , $ response );
765+ }
766+
767+ return $ returnResponse ;
768+ }
769+
770+ private function buildLinkMetadataResponse (array $ linkMeta , Response $ response )
771+ {
772+ $ returnResponse = null ;
773+
774+ if (count ($ linkMeta ) > 0 ) {
775+ $ linkMetaType = array_key_first ($ linkMeta );
776+
777+ $ type = substr ($ linkMetaType , strrpos ($ linkMetaType , '# ' ) + 1 );
778+
779+ $ linkMetaValue = reset ($ linkMeta );
780+ $ value = array_pop ($ linkMetaValue );
781+ $ url = $ value ['value ' ] ?? null ;
782+
783+ switch ($ type ) {
784+ case 'deleted ' :
785+ $ returnResponse = $ response ->withStatus (404 );
786+ break ;
787+
788+ case 'forget ' :
789+ $ returnResponse = $ response ->withStatus (410 );
790+ break ;
791+
792+ case 'redirectPermanent ' :
793+ if ($ url === null ) {
794+ throw Exception::create (self ::ERROR_CAN_NOT_REDIRECT_WITHOUT_URL , [$ type ]);
795+ }
796+ $ returnResponse = $ response ->withHeader ('Location ' , $ url )->withStatus (308 );
797+ break ;
798+
799+ case 'redirectTemporary ' :
800+ if ($ url === null ) {
801+ throw Exception::create (self ::ERROR_CAN_NOT_REDIRECT_WITHOUT_URL , [$ type ]);
802+ }
803+ $ returnResponse = $ response ->withHeader ('Location ' , $ url )->withStatus (307 );
804+ break ;
805+
806+ default :
807+ // No (known) Link Metadata present = follow regular logic
808+ break ;
809+ }
810+ }
811+
812+ return $ returnResponse ;
813+ }
814+
815+ private function parseLinkedMetadata (string $ path )
816+ {
817+ $ linkMeta = [];
818+
819+ try {
820+ $ describedByPath = $ this ->filesystem ->getMetadata ($ path )['describedby ' ] ?? '' ;
821+ $ describedByContents = $ this ->filesystem ->read ($ describedByPath );
822+ } catch (FileNotFoundException $ e ) {
823+ // @CHECKME: If, for whatever reason, the file is not present after all... Do we care here?
824+ return $ linkMeta ;
825+ }
826+
827+ $ graph = $ this ->getGraph ();
828+
829+ try {
830+ $ graph ->parse ($ describedByContents );
831+ } catch (EasyRdf_Exception $ exception ) {
832+ throw Exception::create (self ::ERROR_CAN_NOT_PARSE_METADATA , [$ path ]);
833+ }
834+
835+ $ toRdfPhp = $ graph ->toRdfPhp ();
836+
837+ $ path = ltrim ($ path , '/ ' );
838+
839+ if (isset ($ toRdfPhp [$ path ])) {
840+ $ linkMeta = array_filter ($ toRdfPhp [$ path ], static function ($ key ) {
841+ $ uris = implode ('| ' , [
842+ 'pdsinterop.org/solid-link-metadata/links.ttl ' ,
843+ 'purl.org/pdsinterop/link-metadata ' ,
844+ ]);
845+
846+ return (bool ) preg_match ("#( {$ uris })# " ,
847+ $ key );
848+ }, ARRAY_FILTER_USE_KEY );
849+ }
850+
851+ return $ linkMeta ;
852+ }
737853}
0 commit comments