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 adviceable 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) 2007-2015 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 * @return bool 138 */ 139 function beforeCreateFile($uri, $data) { 140 141 if ($tempPath = $this->isTempFile($uri)) { 142 143 $hR = $this->server->httpResponse; 144 $hR->setHeader('X-Sabre-Temp', 'true'); 145 file_put_contents($tempPath, $data); 146 return false; 147 } 148 return; 149 150 } 151 152 /** 153 * This method will check if the url matches the temporary file pattern 154 * if it does, it will return an path based on $this->dataDir for the 155 * temporary file storage. 156 * 157 * @param string $path 158 * @return bool|string 159 */ 160 protected function isTempFile($path) { 161 162 // We're only interested in the basename. 163 list(, $tempPath) = URLUtil::splitPath($path); 164 165 foreach ($this->temporaryFilePatterns as $tempFile) { 166 167 if (preg_match($tempFile, $tempPath)) { 168 return $this->getDataDir() . '/sabredav_' . md5($path) . '.tempfile'; 169 } 170 171 } 172 173 return false; 174 175 } 176 177 178 /** 179 * This method handles the GET method for temporary files. 180 * If the file doesn't exist, it will return false which will kick in 181 * the regular system for the GET method. 182 * 183 * @param RequestInterface $request 184 * @param ResponseInterface $hR 185 * @param string $tempLocation 186 * @return bool 187 */ 188 function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation) { 189 190 if (!file_exists($tempLocation)) return; 191 192 $hR->setHeader('Content-Type', 'application/octet-stream'); 193 $hR->setHeader('Content-Length', filesize($tempLocation)); 194 $hR->setHeader('X-Sabre-Temp', 'true'); 195 $hR->setStatus(200); 196 $hR->setBody(fopen($tempLocation, 'r')); 197 return false; 198 199 } 200 201 /** 202 * This method handles the PUT method. 203 * 204 * @param RequestInterface $request 205 * @param ResponseInterface $hR 206 * @param string $tempLocation 207 * @return bool 208 */ 209 function httpPut(RequestInterface $request, ResponseInterface $hR, $tempLocation) { 210 211 $hR->setHeader('X-Sabre-Temp', 'true'); 212 213 $newFile = !file_exists($tempLocation); 214 215 if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) { 216 throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied'); 217 } 218 219 file_put_contents($tempLocation, $this->server->httpRequest->getBody()); 220 $hR->setStatus($newFile ? 201 : 200); 221 return false; 222 223 } 224 225 /** 226 * This method handles the DELETE method. 227 * 228 * If the file didn't exist, it will return false, which will make the 229 * standard HTTP DELETE handler kick in. 230 * 231 * @param RequestInterface $request 232 * @param ResponseInterface $hR 233 * @param string $tempLocation 234 * @return bool 235 */ 236 function httpDelete(RequestInterface $request, ResponseInterface $hR, $tempLocation) { 237 238 if (!file_exists($tempLocation)) return; 239 240 unlink($tempLocation); 241 $hR->setHeader('X-Sabre-Temp', 'true'); 242 $hR->setStatus(204); 243 return false; 244 245 } 246 247 /** 248 * This method handles the PROPFIND method. 249 * 250 * It's a very lazy method, it won't bother checking the request body 251 * for which properties were requested, and just sends back a default 252 * set of properties. 253 * 254 * @param RequestInterface $request 255 * @param ResponseInterface $hR 256 * @param string $tempLocation 257 * @return bool 258 */ 259 function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation) { 260 261 if (!file_exists($tempLocation)) return; 262 263 $hR->setHeader('X-Sabre-Temp', 'true'); 264 $hR->setStatus(207); 265 $hR->setHeader('Content-Type', 'application/xml; charset=utf-8'); 266 267 $properties = [ 268 'href' => $request->getPath(), 269 200 => [ 270 '{DAV:}getlastmodified' => new Xml\Property\GetLastModified(filemtime($tempLocation)), 271 '{DAV:}getcontentlength' => filesize($tempLocation), 272 '{DAV:}resourcetype' => new Xml\Property\ResourceType(null), 273 '{' . Server::NS_SABREDAV . '}tempFile' => true, 274 275 ], 276 ]; 277 278 $data = $this->server->generateMultiStatus([$properties]); 279 $hR->setBody($data); 280 return false; 281 282 } 283 284 285 /** 286 * This method returns the directory where the temporary files should be stored. 287 * 288 * @return string 289 */ 290 protected function getDataDir() 291 { 292 return $this->dataDir; 293 } 294} 295