1<?php 2 3namespace Sabre\DAV; 4 5use Sabre\HTTP\RequestInterface; 6use Sabre\HTTP\ResponseInterface; 7use Sabre\HTTP\URLUtil; 8 9/** 10 * Temporary File Filter Plugin 11 * 12 * The purpose of this filter is to intercept some of the garbage files 13 * operation systems and applications tend to generate when mounting 14 * a WebDAV share as a disk. 15 * 16 * It will intercept these files and place them in a separate directory. 17 * these files are not deleted automatically, so it is advisable to 18 * delete these after they are not accessed for 24 hours. 19 * 20 * Currently it supports: 21 * * OS/X style resource forks and .DS_Store 22 * * desktop.ini and Thumbs.db (windows) 23 * * .*.swp (vim temporary files) 24 * * .dat.* (smultron temporary files) 25 * 26 * Additional patterns can be added, by adding on to the 27 * temporaryFilePatterns property. 28 * 29 * @copyright Copyright (C) fruux GmbH (https://fruux.com/) 30 * @author Evert Pot (http://evertpot.com/) 31 * @license http://sabre.io/license/ Modified BSD License 32 */ 33class TemporaryFileFilterPlugin extends ServerPlugin { 34 35 /** 36 * This is the list of patterns we intercept. 37 * If new patterns are added, they must be valid patterns for preg_match. 38 * 39 * @var array 40 */ 41 public $temporaryFilePatterns = [ 42 '/^\._(.*)$/', // OS/X resource forks 43 '/^.DS_Store$/', // OS/X custom folder settings 44 '/^desktop.ini$/', // Windows custom folder settings 45 '/^Thumbs.db$/', // Windows thumbnail cache 46 '/^.(.*).swp$/', // ViM temporary files 47 '/^\.dat(.*)$/', // Smultron seems to create these 48 '/^~lock.(.*)#$/', // Windows 7 lockfiles 49 ]; 50 51 /** 52 * A reference to the main Server class 53 * 54 * @var \Sabre\DAV\Server 55 */ 56 protected $server; 57 58 /** 59 * This is the directory where this plugin 60 * will store it's files. 61 * 62 * @var string 63 */ 64 private $dataDir; 65 66 /** 67 * Creates the plugin. 68 * 69 * Make sure you specify a directory for your files. If you don't, we 70 * will use PHP's directory for session-storage instead, and you might 71 * not want that. 72 * 73 * @param string|null $dataDir 74 */ 75 function __construct($dataDir = null) { 76 77 if (!$dataDir) $dataDir = ini_get('session.save_path') . '/sabredav/'; 78 if (!is_dir($dataDir)) mkdir($dataDir); 79 $this->dataDir = $dataDir; 80 81 } 82 83 /** 84 * Initialize the plugin 85 * 86 * This is called automatically be the Server class after this plugin is 87 * added with Sabre\DAV\Server::addPlugin() 88 * 89 * @param Server $server 90 * @return void 91 */ 92 function initialize(Server $server) { 93 94 $this->server = $server; 95 $server->on('beforeMethod', [$this, 'beforeMethod']); 96 $server->on('beforeCreateFile', [$this, 'beforeCreateFile']); 97 98 } 99 100 /** 101 * This method is called before any HTTP method handler 102 * 103 * This method intercepts any GET, DELETE, PUT and PROPFIND calls to 104 * filenames that are known to match the 'temporary file' regex. 105 * 106 * @param RequestInterface $request 107 * @param ResponseInterface $response 108 * @return bool 109 */ 110 function beforeMethod(RequestInterface $request, ResponseInterface $response) { 111 112 if (!$tempLocation = $this->isTempFile($request->getPath())) 113 return; 114 115 switch ($request->getMethod()) { 116 case 'GET' : 117 return $this->httpGet($request, $response, $tempLocation); 118 case 'PUT' : 119 return $this->httpPut($request, $response, $tempLocation); 120 case 'PROPFIND' : 121 return $this->httpPropfind($request, $response, $tempLocation); 122 case 'DELETE' : 123 return $this->httpDelete($request, $response, $tempLocation); 124 } 125 return; 126 127 } 128 129 /** 130 * This method is invoked if some subsystem creates a new file. 131 * 132 * This is used to deal with HTTP LOCK requests which create a new 133 * file. 134 * 135 * @param string $uri 136 * @param resource $data 137 * @param ICollection $parent 138 * @param bool $modified Should be set to true, if this event handler 139 * changed &$data. 140 * @return bool 141 */ 142 function beforeCreateFile($uri, $data, ICollection $parent, $modified) { 143 144 if ($tempPath = $this->isTempFile($uri)) { 145 146 $hR = $this->server->httpResponse; 147 $hR->setHeader('X-Sabre-Temp', 'true'); 148 file_put_contents($tempPath, $data); 149 return false; 150 } 151 return; 152 153 } 154 155 /** 156 * This method will check if the url matches the temporary file pattern 157 * if it does, it will return an path based on $this->dataDir for the 158 * temporary file storage. 159 * 160 * @param string $path 161 * @return bool|string 162 */ 163 protected function isTempFile($path) { 164 165 // We're only interested in the basename. 166 list(, $tempPath) = URLUtil::splitPath($path); 167 168 foreach ($this->temporaryFilePatterns as $tempFile) { 169 170 if (preg_match($tempFile, $tempPath)) { 171 return $this->getDataDir() . '/sabredav_' . md5($path) . '.tempfile'; 172 } 173 174 } 175 176 return false; 177 178 } 179 180 181 /** 182 * This method handles the GET method for temporary files. 183 * If the file doesn't exist, it will return false which will kick in 184 * the regular system for the GET method. 185 * 186 * @param RequestInterface $request 187 * @param ResponseInterface $hR 188 * @param string $tempLocation 189 * @return bool 190 */ 191 function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation) { 192 193 if (!file_exists($tempLocation)) return; 194 195 $hR->setHeader('Content-Type', 'application/octet-stream'); 196 $hR->setHeader('Content-Length', filesize($tempLocation)); 197 $hR->setHeader('X-Sabre-Temp', 'true'); 198 $hR->setStatus(200); 199 $hR->setBody(fopen($tempLocation, 'r')); 200 return false; 201 202 } 203 204 /** 205 * This method handles the PUT method. 206 * 207 * @param RequestInterface $request 208 * @param ResponseInterface $hR 209 * @param string $tempLocation 210 * @return bool 211 */ 212 function httpPut(RequestInterface $request, ResponseInterface $hR, $tempLocation) { 213 214 $hR->setHeader('X-Sabre-Temp', 'true'); 215 216 $newFile = !file_exists($tempLocation); 217 218 if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) { 219 throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied'); 220 } 221 222 file_put_contents($tempLocation, $this->server->httpRequest->getBody()); 223 $hR->setStatus($newFile ? 201 : 200); 224 return false; 225 226 } 227 228 /** 229 * This method handles the DELETE method. 230 * 231 * If the file didn't exist, it will return false, which will make the 232 * standard HTTP DELETE handler kick in. 233 * 234 * @param RequestInterface $request 235 * @param ResponseInterface $hR 236 * @param string $tempLocation 237 * @return bool 238 */ 239 function httpDelete(RequestInterface $request, ResponseInterface $hR, $tempLocation) { 240 241 if (!file_exists($tempLocation)) return; 242 243 unlink($tempLocation); 244 $hR->setHeader('X-Sabre-Temp', 'true'); 245 $hR->setStatus(204); 246 return false; 247 248 } 249 250 /** 251 * This method handles the PROPFIND method. 252 * 253 * It's a very lazy method, it won't bother checking the request body 254 * for which properties were requested, and just sends back a default 255 * set of properties. 256 * 257 * @param RequestInterface $request 258 * @param ResponseInterface $hR 259 * @param string $tempLocation 260 * @return bool 261 */ 262 function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation) { 263 264 if (!file_exists($tempLocation)) return; 265 266 $hR->setHeader('X-Sabre-Temp', 'true'); 267 $hR->setStatus(207); 268 $hR->setHeader('Content-Type', 'application/xml; charset=utf-8'); 269 270 $properties = [ 271 'href' => $request->getPath(), 272 200 => [ 273 '{DAV:}getlastmodified' => new Xml\Property\GetLastModified(filemtime($tempLocation)), 274 '{DAV:}getcontentlength' => filesize($tempLocation), 275 '{DAV:}resourcetype' => new Xml\Property\ResourceType(null), 276 '{' . Server::NS_SABREDAV . '}tempFile' => true, 277 278 ], 279 ]; 280 281 $data = $this->server->generateMultiStatus([$properties]); 282 $hR->setBody($data); 283 return false; 284 285 } 286 287 288 /** 289 * This method returns the directory where the temporary files should be stored. 290 * 291 * @return string 292 */ 293 protected function getDataDir() 294 { 295 return $this->dataDir; 296 } 297} 298