@@ -169,6 +169,7 @@ public function initialize(Server $server) {
169169 $ this ->server ->on ('afterBind ' , [$ this , 'sendFileIdHeader ' ]);
170170 $ this ->server ->on ('afterWriteContent ' , [$ this , 'sendFileIdHeader ' ]);
171171 $ this ->server ->on ('method:GET ' , [$ this ,'httpGet ' ]);
172+ $ this ->server ->on ('method:PROPFIND ' , [$ this , 'httpPropFind ' ]);
172173 $ this ->server ->on ('afterMethod:GET ' , [$ this ,'afterHttpGet ' ]);
173174 $ this ->server ->on ('afterMethod:GET ' , [$ this , 'handleDownloadToken ' ]);
174175 $ this ->server ->on ('afterResponse ' , function ($ request , ResponseInterface $ response ) {
@@ -234,6 +235,76 @@ public function handleDownloadToken(RequestInterface $request, ResponseInterface
234235 }
235236 }
236237
238+ /**
239+ * Handle PROPFIND http requests for symlinks
240+ * @param RequestInterface $request
241+ * @param ResponseInterface $response
242+ */
243+ public function httpPropFind (RequestInterface $ request , ResponseInterface $ response )
244+ {
245+ // only handle symlinks
246+ // TODO: probably doesn't work because PROPFIND will be called on root dir and children are returned
247+ $ symlinkPath = $ request ->getPath ();
248+ $ fileInfo = \OC \Files \Filesystem::getView ()->getFileInfo ($ symlinkPath );
249+ if (!$ fileInfo || $ fileInfo ->getType () !== \OC \Files \FileInfo::TYPE_SYMLINK ) {
250+ return ;
251+ }
252+
253+ $ requestBody = $ request ->getBodyAsString ();
254+ if (strlen ($ requestBody )) {
255+ try {
256+ $ propFindXml = $ this ->server ->xml ->expect ('{DAV:}propfind ' , $ requestBody );
257+ } catch (\Sabre \XML \ParseException $ e ) {
258+ throw new \Sabre \DAV \Exception \BadRequest ($ e ->getMessage (), 0 , $ e );
259+ }
260+ } else {
261+ $ propFindXml = new \Sabre \DAV \Xml \Request \PropFind ();
262+ $ propFindXml ->allProp = true ;
263+ $ propFindXml ->properties = [];
264+ }
265+
266+ $ newProperties = [];
267+ foreach ($ propFindXml ->properties as $ property ) {
268+ if ($ property === self ::GETETAG_PROPERTYNAME ) {
269+ $ newProperties [$ property ] = [200 , $ fileInfo ->getETag ()];
270+ } elseif ($ property === self ::LASTMODIFIED_PROPERTYNAME ) {
271+ // TODO
272+ } elseif ($ property === self ::CREATIONDATE_PROPERTYNAME ) {
273+ // TODO
274+ } elseif ($ property === self ::UPLOAD_TIME_PROPERTYNAME ) {
275+ // TODO
276+ } elseif ($ property === self ::SIZE_PROPERTYNAME ) {
277+ $ newProperties [$ property ] = [200 , $ fileInfo ->getSize ()];
278+ } else { // TODO: handle other properties that make sense for symlinks
279+ $ newProperties [$ property ] = [404 ];
280+ }
281+ }
282+
283+ // This is a multi-status response
284+ $ response ->setStatus (207 );
285+ $ response ->setHeader ('Content-Type ' , 'application/xml; charset=utf-8 ' );
286+ $ response ->setHeader ('Vary ' , 'Brief,Prefer ' );
287+
288+ // Normally this header is only needed for OPTIONS responses, however..
289+ // iCal seems to also depend on these being set for PROPFIND. Since
290+ // this is not harmful, we'll add it.
291+ $ features = ['1 ' , '3 ' , 'extended-mkcol ' ];
292+ foreach ($ this ->server ->getPlugins () as $ plugin ) {
293+ $ features = array_merge ($ features , $ plugin ->getFeatures ());
294+ }
295+ $ response ->setHeader ('DAV ' , implode (', ' , $ features ));
296+
297+ $ prefer = $ this ->server ->getHTTPPrefer ();
298+ $ minimal = 'minimal ' === $ prefer ['return ' ];
299+
300+ $ data = $ this ->server ->generateMultiStatus ($ newProperties , $ minimal );
301+ $ response ->setBody ($ data );
302+
303+ // Sending back false will interrupt the event chain and tell the server
304+ // we've handled this method.
305+ return false ;
306+ }
307+
237308 public function httpGet (RequestInterface $ request , ResponseInterface $ response ) {
238309 // only handle symlinks
239310 $ symlinkPath = $ request ->getPath ();
0 commit comments