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