Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like OC_Files often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use OC_Files, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 51 | class OC_Files { |
||
| 52 | const FILE = 1; |
||
| 53 | const ZIP_FILES = 2; |
||
| 54 | const ZIP_DIR = 3; |
||
| 55 | |||
| 56 | const UPLOAD_MIN_LIMIT_BYTES = 1048576; // 1 MiB |
||
| 57 | |||
| 58 | |||
| 59 | private static $multipartBoundary = ''; |
||
| 60 | |||
| 61 | /** |
||
| 62 | * @return string |
||
| 63 | */ |
||
| 64 | private static function getBoundary() { |
||
| 65 | if (empty(self::$multipartBoundary)) { |
||
| 66 | self::$multipartBoundary = md5(mt_rand()); |
||
| 67 | } |
||
| 68 | return self::$multipartBoundary; |
||
| 69 | } |
||
| 70 | |||
| 71 | /** |
||
| 72 | * @param string $filename |
||
| 73 | * @param string $name |
||
| 74 | * @param array $rangeArray ('from'=>int,'to'=>int), ... |
||
| 75 | */ |
||
| 76 | private static function sendHeaders($filename, $name, array $rangeArray) { |
||
| 77 | OC_Response::setContentDispositionHeader($name, 'attachment'); |
||
| 78 | header('Content-Transfer-Encoding: binary', true); |
||
| 79 | header('Pragma: public');// enable caching in IE |
||
| 80 | header('Expires: 0'); |
||
| 81 | header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); |
||
| 82 | $fileSize = \OC\Files\Filesystem::filesize($filename); |
||
| 83 | $type = \OC::$server->getMimeTypeDetector()->getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename)); |
||
|
|
|||
| 84 | if ($fileSize > -1) { |
||
| 85 | if (!empty($rangeArray)) { |
||
| 86 | http_response_code(206); |
||
| 87 | header('Accept-Ranges: bytes', true); |
||
| 88 | if (count($rangeArray) > 1) { |
||
| 89 | $type = 'multipart/byteranges; boundary='.self::getBoundary(); |
||
| 90 | // no Content-Length header here |
||
| 91 | } |
||
| 92 | else { |
||
| 93 | header(sprintf('Content-Range: bytes %d-%d/%d', $rangeArray[0]['from'], $rangeArray[0]['to'], $fileSize), true); |
||
| 94 | OC_Response::setContentLengthHeader($rangeArray[0]['to'] - $rangeArray[0]['from'] + 1); |
||
| 95 | } |
||
| 96 | } |
||
| 97 | else { |
||
| 98 | OC_Response::setContentLengthHeader($fileSize); |
||
| 99 | } |
||
| 100 | } |
||
| 101 | header('Content-Type: '.$type, true); |
||
| 102 | } |
||
| 103 | |||
| 104 | /** |
||
| 105 | * return the content of a file or return a zip file containing multiple files |
||
| 106 | * |
||
| 107 | * @param string $dir |
||
| 108 | * @param string $files ; separated list of files to download |
||
| 109 | * @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header |
||
| 110 | */ |
||
| 111 | public static function get($dir, $files, $params = null) { |
||
| 112 | |||
| 113 | $view = \OC\Files\Filesystem::getView(); |
||
| 114 | $getType = self::FILE; |
||
| 115 | $filename = $dir; |
||
| 116 | try { |
||
| 117 | |||
| 118 | if (is_array($files) && count($files) === 1) { |
||
| 119 | $files = $files[0]; |
||
| 120 | } |
||
| 121 | |||
| 122 | if (!is_array($files)) { |
||
| 123 | $filename = $dir . '/' . $files; |
||
| 124 | if (!$view->is_dir($filename)) { |
||
| 125 | self::getSingleFile($view, $dir, $files, is_null($params) ? array() : $params); |
||
| 126 | return; |
||
| 127 | } |
||
| 128 | } |
||
| 129 | |||
| 130 | $name = 'download'; |
||
| 131 | if (is_array($files)) { |
||
| 132 | $getType = self::ZIP_FILES; |
||
| 133 | $basename = basename($dir); |
||
| 134 | if ($basename) { |
||
| 135 | $name = $basename; |
||
| 136 | } |
||
| 137 | |||
| 138 | $filename = $dir . '/' . $name; |
||
| 139 | } else { |
||
| 140 | $filename = $dir . '/' . $files; |
||
| 141 | $getType = self::ZIP_DIR; |
||
| 142 | // downloading root ? |
||
| 143 | if ($files !== '') { |
||
| 144 | $name = $files; |
||
| 145 | } |
||
| 146 | } |
||
| 147 | |||
| 148 | self::lockFiles($view, $dir, $files); |
||
| 149 | |||
| 150 | /* Calculate filesize and number of files */ |
||
| 151 | if ($getType === self::ZIP_FILES) { |
||
| 152 | $fileInfos = array(); |
||
| 153 | $fileSize = 0; |
||
| 154 | foreach ($files as $file) { |
||
| 155 | $fileInfo = \OC\Files\Filesystem::getFileInfo($dir . '/' . $file); |
||
| 156 | $fileSize += $fileInfo->getSize(); |
||
| 157 | $fileInfos[] = $fileInfo; |
||
| 158 | } |
||
| 159 | $numberOfFiles = self::getNumberOfFiles($fileInfos); |
||
| 160 | } elseif ($getType === self::ZIP_DIR) { |
||
| 161 | $fileInfo = \OC\Files\Filesystem::getFileInfo($dir . '/' . $files); |
||
| 162 | $fileSize = $fileInfo->getSize(); |
||
| 163 | $numberOfFiles = self::getNumberOfFiles(array($fileInfo)); |
||
| 164 | } |
||
| 165 | |||
| 166 | $streamer = new Streamer(\OC::$server->getRequest(), $fileSize, $numberOfFiles); |
||
| 167 | OC_Util::obEnd(); |
||
| 168 | |||
| 169 | $streamer->sendHeaders($name); |
||
| 170 | $executionTime = (int)OC::$server->getIniWrapper()->getNumeric('max_execution_time'); |
||
| 171 | if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) { |
||
| 172 | @set_time_limit(0); |
||
| 173 | } |
||
| 174 | ignore_user_abort(true); |
||
| 175 | |||
| 176 | if ($getType === self::ZIP_FILES) { |
||
| 177 | foreach ($files as $file) { |
||
| 178 | $file = $dir . '/' . $file; |
||
| 179 | if (\OC\Files\Filesystem::is_file($file)) { |
||
| 180 | $fileSize = \OC\Files\Filesystem::filesize($file); |
||
| 181 | $fileTime = \OC\Files\Filesystem::filemtime($file); |
||
| 182 | $fh = \OC\Files\Filesystem::fopen($file, 'r'); |
||
| 183 | $streamer->addFileFromStream($fh, basename($file), $fileSize, $fileTime); |
||
| 184 | fclose($fh); |
||
| 185 | } elseif (\OC\Files\Filesystem::is_dir($file)) { |
||
| 186 | $streamer->addDirRecursive($file); |
||
| 187 | } |
||
| 188 | } |
||
| 189 | } elseif ($getType === self::ZIP_DIR) { |
||
| 190 | $file = $dir . '/' . $files; |
||
| 191 | $streamer->addDirRecursive($file); |
||
| 192 | } |
||
| 193 | $streamer->finalize(); |
||
| 194 | set_time_limit($executionTime); |
||
| 195 | self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); |
||
| 196 | } catch (\OCP\Lock\LockedException $ex) { |
||
| 197 | self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); |
||
| 198 | OC::$server->getLogger()->logException($ex); |
||
| 199 | $l = \OC::$server->getL10N('core'); |
||
| 200 | $hint = method_exists($ex, 'getHint') ? $ex->getHint() : ''; |
||
| 201 | \OC_Template::printErrorPage($l->t('File is currently busy, please try again later'), $hint, 200); |
||
| 202 | } catch (\OCP\Files\ForbiddenException $ex) { |
||
| 203 | self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); |
||
| 204 | OC::$server->getLogger()->logException($ex); |
||
| 205 | $l = \OC::$server->getL10N('core'); |
||
| 206 | \OC_Template::printErrorPage($l->t('Can\'t read file'), $ex->getMessage(), 200); |
||
| 207 | } catch (\Exception $ex) { |
||
| 208 | self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); |
||
| 209 | OC::$server->getLogger()->logException($ex); |
||
| 210 | $l = \OC::$server->getL10N('core'); |
||
| 211 | $hint = method_exists($ex, 'getHint') ? $ex->getHint() : ''; |
||
| 212 | \OC_Template::printErrorPage($l->t('Can\'t read file'), $hint, 200); |
||
| 213 | } |
||
| 214 | } |
||
| 215 | |||
| 216 | /** |
||
| 217 | * @param string $rangeHeaderPos |
||
| 218 | * @param int $fileSize |
||
| 219 | * @return array $rangeArray ('from'=>int,'to'=>int), ... |
||
| 220 | */ |
||
| 221 | private static function parseHttpRangeHeader($rangeHeaderPos, $fileSize) { |
||
| 222 | $rArray=explode(',', $rangeHeaderPos); |
||
| 223 | $minOffset = 0; |
||
| 224 | $ind = 0; |
||
| 225 | |||
| 226 | $rangeArray = array(); |
||
| 227 | |||
| 228 | foreach ($rArray as $value) { |
||
| 229 | $ranges = explode('-', $value); |
||
| 230 | if (is_numeric($ranges[0])) { |
||
| 231 | if ($ranges[0] < $minOffset) { // case: bytes=500-700,601-999 |
||
| 232 | $ranges[0] = $minOffset; |
||
| 233 | } |
||
| 234 | if ($ind > 0 && $rangeArray[$ind-1]['to']+1 == $ranges[0]) { // case: bytes=500-600,601-999 |
||
| 235 | $ind--; |
||
| 236 | $ranges[0] = $rangeArray[$ind]['from']; |
||
| 237 | } |
||
| 238 | } |
||
| 239 | |||
| 240 | if (is_numeric($ranges[0]) && is_numeric($ranges[1]) && $ranges[0] < $fileSize && $ranges[0] <= $ranges[1]) { |
||
| 241 | // case: x-x |
||
| 242 | if ($ranges[1] >= $fileSize) { |
||
| 243 | $ranges[1] = $fileSize-1; |
||
| 244 | } |
||
| 245 | $rangeArray[$ind++] = array( 'from' => $ranges[0], 'to' => $ranges[1], 'size' => $fileSize ); |
||
| 246 | $minOffset = $ranges[1] + 1; |
||
| 247 | if ($minOffset >= $fileSize) { |
||
| 248 | break; |
||
| 249 | } |
||
| 250 | } |
||
| 251 | elseif (is_numeric($ranges[0]) && $ranges[0] < $fileSize) { |
||
| 252 | // case: x- |
||
| 253 | $rangeArray[$ind++] = array( 'from' => $ranges[0], 'to' => $fileSize-1, 'size' => $fileSize ); |
||
| 254 | break; |
||
| 255 | } |
||
| 256 | elseif (is_numeric($ranges[1])) { |
||
| 257 | // case: -x |
||
| 258 | if ($ranges[1] > $fileSize) { |
||
| 259 | $ranges[1] = $fileSize; |
||
| 260 | } |
||
| 261 | $rangeArray[$ind++] = array( 'from' => $fileSize-$ranges[1], 'to' => $fileSize-1, 'size' => $fileSize ); |
||
| 262 | break; |
||
| 263 | } |
||
| 264 | } |
||
| 265 | return $rangeArray; |
||
| 266 | } |
||
| 267 | |||
| 268 | /** |
||
| 269 | * @param View $view |
||
| 270 | * @param string $name |
||
| 271 | * @param string $dir |
||
| 272 | * @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header |
||
| 273 | */ |
||
| 274 | private static function getSingleFile($view, $dir, $name, $params) { |
||
| 275 | $filename = $dir . '/' . $name; |
||
| 276 | OC_Util::obEnd(); |
||
| 277 | $view->lockFile($filename, ILockingProvider::LOCK_SHARED); |
||
| 278 | |||
| 279 | $rangeArray = array(); |
||
| 280 | |||
| 281 | if (isset($params['range']) && substr($params['range'], 0, 6) === 'bytes=') { |
||
| 282 | $rangeArray = self::parseHttpRangeHeader(substr($params['range'], 6), |
||
| 283 | \OC\Files\Filesystem::filesize($filename)); |
||
| 284 | } |
||
| 285 | |||
| 286 | if (\OC\Files\Filesystem::isReadable($filename)) { |
||
| 287 | self::sendHeaders($filename, $name, $rangeArray); |
||
| 288 | } elseif (!\OC\Files\Filesystem::file_exists($filename)) { |
||
| 289 | http_response_code(404); |
||
| 290 | $tmpl = new OC_Template('', '404', 'guest'); |
||
| 291 | $tmpl->printPage(); |
||
| 292 | exit(); |
||
| 293 | } else { |
||
| 294 | http_response_code(403); |
||
| 295 | die('403 Forbidden'); |
||
| 296 | } |
||
| 297 | if (isset($params['head']) && $params['head']) { |
||
| 298 | return; |
||
| 299 | } |
||
| 300 | if (!empty($rangeArray)) { |
||
| 301 | try { |
||
| 302 | if (count($rangeArray) == 1) { |
||
| 303 | $view->readfilePart($filename, $rangeArray[0]['from'], $rangeArray[0]['to']); |
||
| 304 | } |
||
| 305 | else { |
||
| 306 | // check if file is seekable (if not throw UnseekableException) |
||
| 307 | // we have to check it before body contents |
||
| 308 | $view->readfilePart($filename, $rangeArray[0]['size'], $rangeArray[0]['size']); |
||
| 309 | |||
| 310 | $type = \OC::$server->getMimeTypeDetector()->getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename)); |
||
| 311 | |||
| 312 | foreach ($rangeArray as $range) { |
||
| 313 | echo "\r\n--".self::getBoundary()."\r\n". |
||
| 314 | "Content-type: ".$type."\r\n". |
||
| 315 | "Content-range: bytes ".$range['from']."-".$range['to']."/".$range['size']."\r\n\r\n"; |
||
| 316 | $view->readfilePart($filename, $range['from'], $range['to']); |
||
| 317 | } |
||
| 318 | echo "\r\n--".self::getBoundary()."--\r\n"; |
||
| 319 | } |
||
| 320 | } catch (\OCP\Files\UnseekableException $ex) { |
||
| 321 | // file is unseekable |
||
| 322 | header_remove('Accept-Ranges'); |
||
| 323 | header_remove('Content-Range'); |
||
| 324 | http_response_code(200); |
||
| 325 | self::sendHeaders($filename, $name, array()); |
||
| 326 | $view->readfile($filename); |
||
| 327 | } |
||
| 328 | } |
||
| 329 | else { |
||
| 330 | $view->readfile($filename); |
||
| 331 | } |
||
| 332 | } |
||
| 333 | |||
| 334 | /** |
||
| 335 | * Returns the total (recursive) number of files and folders in the given |
||
| 336 | * FileInfos. |
||
| 337 | * |
||
| 338 | * @param \OCP\Files\FileInfo[] $fileInfos the FileInfos to count |
||
| 339 | * @return int the total number of files and folders |
||
| 340 | */ |
||
| 341 | private static function getNumberOfFiles($fileInfos) { |
||
| 342 | $numberOfFiles = 0; |
||
| 343 | |||
| 344 | $view = new View(); |
||
| 345 | |||
| 346 | while ($fileInfo = array_pop($fileInfos)) { |
||
| 347 | $numberOfFiles++; |
||
| 348 | |||
| 349 | if ($fileInfo->getType() === \OCP\Files\FileInfo::TYPE_FOLDER) { |
||
| 350 | $fileInfos = array_merge($fileInfos, $view->getDirectoryContent($fileInfo->getPath())); |
||
| 351 | } |
||
| 352 | } |
||
| 353 | |||
| 354 | return $numberOfFiles; |
||
| 355 | } |
||
| 356 | |||
| 357 | /** |
||
| 358 | * @param View $view |
||
| 359 | * @param string $dir |
||
| 360 | * @param string[]|string $files |
||
| 361 | */ |
||
| 362 | public static function lockFiles($view, $dir, $files) { |
||
| 377 | } |
||
| 378 | } |
||
| 379 | } |
||
| 380 | |||
| 381 | /** |
||
| 382 | * set the maximum upload size limit for apache hosts using .htaccess |
||
| 383 | * |
||
| 384 | * @param int $size file size in bytes |
||
| 385 | * @param array $files override '.htaccess' and '.user.ini' locations |
||
| 386 | * @return bool|int false on failure, size on success |
||
| 387 | */ |
||
| 388 | public static function setUploadLimit($size, $files = []) { |
||
| 461 | } |
||
| 462 | |||
| 463 | /** |
||
| 464 | * @param string $dir |
||
| 465 | * @param $files |
||
| 466 | * @param integer $getType |
||
| 467 | * @param View $view |
||
| 468 | * @param string $filename |
||
| 469 | */ |
||
| 470 | private static function unlockAllTheFiles($dir, $files, $getType, $view, $filename) { |
||
| 483 | } |
||
| 484 | } |
||
| 485 | |||
| 486 | } |
||
| 487 |