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