| 1: | <?php
|
| 2: |
|
| 3: | |
| 4: | |
| 5: | |
| 6: | |
| 7: | |
| 8: | |
| 9: | |
| 10: | |
| 11: | |
| 12: | |
| 13: | |
| 14: | |
| 15: | |
| 16: | |
| 17: | |
| 18: | |
| 19: | |
| 20: | |
| 21: | |
| 22: | |
| 23: | |
| 24: | |
| 25: | |
| 26: | |
| 27: | |
| 28: | |
| 29: | |
| 30: | |
| 31: | |
| 32: | |
| 33: | |
| 34: | |
| 35: | |
| 36: | |
| 37: |
|
| 38: |
|
| 39: | abstract class SystemFineUploadHandler
|
| 40: | {
|
| 41: |
|
| 42: | public $allowedExtensions = array();
|
| 43: | public $allowedMimeTypes = array('(none)');
|
| 44: | public $sizeLimit = null;
|
| 45: | public $inputName = 'qqfile';
|
| 46: | public $chunksFolder = 'chunks';
|
| 47: |
|
| 48: | public $chunksCleanupProbability = 0.001;
|
| 49: | public $chunksExpireIn = 604800;
|
| 50: |
|
| 51: | protected $uploadName;
|
| 52: | public $claims;
|
| 53: |
|
| 54: | |
| 55: | |
| 56: | |
| 57: |
|
| 58: | public function __construct(\stdClass $claims)
|
| 59: | {
|
| 60: | $this->claims = $claims;
|
| 61: | }
|
| 62: |
|
| 63: | |
| 64: | |
| 65: |
|
| 66: | public function getName()
|
| 67: | {
|
| 68: | if (isset($_REQUEST['qqfilename'])) {
|
| 69: | return $_REQUEST['qqfilename'];
|
| 70: | }
|
| 71: |
|
| 72: | if (isset($_FILES[$this->inputName])) {
|
| 73: | return $_FILES[$this->inputName]['name'];
|
| 74: | }
|
| 75: | }
|
| 76: |
|
| 77: | |
| 78: | |
| 79: | |
| 80: |
|
| 81: | public function getUploadName()
|
| 82: | {
|
| 83: | return $this->uploadName;
|
| 84: | }
|
| 85: |
|
| 86: | |
| 87: | |
| 88: | |
| 89: | |
| 90: | |
| 91: | |
| 92: |
|
| 93: | public function combineChunks($uploadDirectory, $name = null)
|
| 94: | {
|
| 95: | $uuid = $_POST['qquuid'];
|
| 96: | if ($name === null) {
|
| 97: | $name = $this->getName();
|
| 98: | }
|
| 99: | $targetFolder = $this->chunksFolder.DIRECTORY_SEPARATOR.$uuid;
|
| 100: | $totalParts = isset($_REQUEST['qqtotalparts']) ? (int)$_REQUEST['qqtotalparts'] : 1;
|
| 101: |
|
| 102: | $targetPath = join(DIRECTORY_SEPARATOR, array($uploadDirectory, $uuid, $name));
|
| 103: | $this->uploadName = $name;
|
| 104: |
|
| 105: | if (!file_exists($targetPath)) {
|
| 106: | mkdir(dirname($targetPath), 0777, true);
|
| 107: | }
|
| 108: | $target = fopen($targetPath, 'wb');
|
| 109: |
|
| 110: | for ($i=0; $i<$totalParts; $i++) {
|
| 111: | $chunk = fopen($targetFolder.DIRECTORY_SEPARATOR.$i, "rb");
|
| 112: | stream_copy_to_stream($chunk, $target);
|
| 113: | fclose($chunk);
|
| 114: | }
|
| 115: |
|
| 116: |
|
| 117: | fclose($target);
|
| 118: |
|
| 119: | for ($i=0; $i<$totalParts; $i++) {
|
| 120: | unlink($targetFolder.DIRECTORY_SEPARATOR.$i);
|
| 121: | }
|
| 122: |
|
| 123: | rmdir($targetFolder);
|
| 124: |
|
| 125: | if (!is_null($this->sizeLimit) && filesize($targetPath) > $this->sizeLimit) {
|
| 126: | unlink($targetPath);
|
| 127: |
|
| 128: | header("HTTP/1.0 413 Request Entity Too Large");
|
| 129: | return array("success" => false, "uuid" => $uuid, "preventRetry" => true);
|
| 130: | }
|
| 131: |
|
| 132: | return array("success" => true, "uuid" => $uuid);
|
| 133: | }
|
| 134: |
|
| 135: | |
| 136: | |
| 137: | |
| 138: | |
| 139: | |
| 140: |
|
| 141: | public function handleUpload($uploadDirectory, $name = null)
|
| 142: | {
|
| 143: | if (is_writable($this->chunksFolder) &&
|
| 144: | 1 == mt_rand(1, 1/$this->chunksCleanupProbability)) {
|
| 145: |
|
| 146: | $this->cleanupChunks();
|
| 147: | }
|
| 148: |
|
| 149: |
|
| 150: |
|
| 151: | if ($this->toBytes(ini_get('post_max_size')) < $this->sizeLimit ||
|
| 152: | $this->toBytes(ini_get('upload_max_filesize')) < $this->sizeLimit) {
|
| 153: | $neededRequestSize = max(1, $this->sizeLimit / 1024 / 1024) . 'M';
|
| 154: | return array(
|
| 155: | 'error'=>"Server error. Increase post_max_size and upload_max_filesize to ".$neededRequestSize
|
| 156: | );
|
| 157: | }
|
| 158: |
|
| 159: | if ($this->isInaccessible($uploadDirectory)) {
|
| 160: | return array('error' => "Server error. Uploads directory isn't writable");
|
| 161: | }
|
| 162: |
|
| 163: | $type = $_SERVER['CONTENT_TYPE'];
|
| 164: | if (isset($_SERVER['HTTP_CONTENT_TYPE'])) {
|
| 165: | $type = $_SERVER['HTTP_CONTENT_TYPE'];
|
| 166: | }
|
| 167: |
|
| 168: | if (!isset($type)) {
|
| 169: | return array('error' => "No files were uploaded.");
|
| 170: | } elseif (strpos(strtolower($type), 'multipart/') !== 0) {
|
| 171: | return array(
|
| 172: | 'error' => "Server error. Not a multipart request. Please set forceMultipart to default value (true)."
|
| 173: | );
|
| 174: | }
|
| 175: |
|
| 176: |
|
| 177: | $file = $_FILES[$this->inputName];
|
| 178: | $size = $file['size'];
|
| 179: | if (isset($_REQUEST['qqtotalfilesize'])) {
|
| 180: | $size = $_REQUEST['qqtotalfilesize'];
|
| 181: | }
|
| 182: |
|
| 183: | if ($name === null) {
|
| 184: | $name = $this->getName();
|
| 185: | }
|
| 186: |
|
| 187: |
|
| 188: | if ($file['error']) {
|
| 189: | return array('error' => 'Upload Error #'.$file['error']);
|
| 190: | }
|
| 191: |
|
| 192: |
|
| 193: | if ($name === null || $name === '') {
|
| 194: | return array('error' => 'File name empty.');
|
| 195: | }
|
| 196: |
|
| 197: |
|
| 198: | if ($size == 0) {
|
| 199: | return array('error' => 'File is empty.');
|
| 200: | }
|
| 201: |
|
| 202: | if (!is_null($this->sizeLimit) && $size > $this->sizeLimit) {
|
| 203: | return array('error' => 'File is too large.', 'preventRetry' => true);
|
| 204: | }
|
| 205: |
|
| 206: |
|
| 207: | $pathinfo = pathinfo($name);
|
| 208: | $ext = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : '';
|
| 209: |
|
| 210: | if ($this->allowedExtensions
|
| 211: | && !in_array(strtolower($ext), array_map("strtolower", $this->allowedExtensions))) {
|
| 212: | $these = implode(', ', $this->allowedExtensions);
|
| 213: | return array(
|
| 214: | 'error' => 'File has an invalid extension, it should be one of '. $these . '.',
|
| 215: | 'preventRetry' => true
|
| 216: | );
|
| 217: | }
|
| 218: |
|
| 219: | $mimeType = '';
|
| 220: | if (!empty($this->allowedMimeTypes)) {
|
| 221: | $mimeType = mime_content_type($_FILES[$this->inputName]['tmp_name']);
|
| 222: | if (!in_array($mimeType, $this->allowedMimeTypes)) {
|
| 223: | return array('error' => 'File is of an invalid type.', 'preventRetry' => true);
|
| 224: | }
|
| 225: | }
|
| 226: |
|
| 227: |
|
| 228: | $totalParts = isset($_REQUEST['qqtotalparts']) ? (int)$_REQUEST['qqtotalparts'] : 1;
|
| 229: |
|
| 230: | $uuid = $_REQUEST['qquuid'];
|
| 231: | if ($totalParts > 1) {
|
| 232: |
|
| 233: |
|
| 234: | $chunksFolder = $this->chunksFolder;
|
| 235: | $partIndex = (int)$_REQUEST['qqpartindex'];
|
| 236: |
|
| 237: | if (!is_writable($chunksFolder) && !is_executable($uploadDirectory)) {
|
| 238: | return array('error' => "Server error. Chunks directory isn't writable or executable.");
|
| 239: | }
|
| 240: |
|
| 241: | $targetFolder = $this->chunksFolder.DIRECTORY_SEPARATOR.$uuid;
|
| 242: |
|
| 243: | if (!file_exists($targetFolder)) {
|
| 244: | mkdir($targetFolder, 0775, true);
|
| 245: | }
|
| 246: |
|
| 247: | $target = $targetFolder.'/'.$partIndex;
|
| 248: |
|
| 249: | $storeResult = $this->storeUploadedFile($target, $mimeType, $uuid);
|
| 250: | if (false !== $storeResult) {
|
| 251: | return $storeResult;
|
| 252: | }
|
| 253: | } else {
|
| 254: |
|
| 255: |
|
| 256: | $target = join(DIRECTORY_SEPARATOR, array($uploadDirectory, $uuid, $name));
|
| 257: |
|
| 258: | if ($target) {
|
| 259: | $this->uploadName = basename($target);
|
| 260: |
|
| 261: | $storeResult = $this->storeUploadedFile($target, $mimeType, $uuid);
|
| 262: | if (false !== $storeResult) {
|
| 263: | return $storeResult;
|
| 264: | }
|
| 265: | }
|
| 266: |
|
| 267: | return array('error'=> 'Could not save uploaded file.' .
|
| 268: | 'The upload was cancelled, or server error encountered');
|
| 269: | }
|
| 270: | }
|
| 271: |
|
| 272: | protected function storeUploadedFile($target, $mimeType, $uuid)
|
| 273: | {
|
| 274: | if (!is_dir(dirname($target))) {
|
| 275: | mkdir(dirname($target), 0775, true);
|
| 276: | }
|
| 277: | if (move_uploaded_file($_FILES[$this->inputName]['tmp_name'], $target)) {
|
| 278: | return array('success'=> true, "uuid" => $uuid);
|
| 279: | }
|
| 280: | return false;
|
| 281: | }
|
| 282: |
|
| 283: | |
| 284: | |
| 285: | |
| 286: | |
| 287: | |
| 288: |
|
| 289: | public function handleDelete($uploadDirectory, $name = null)
|
| 290: | {
|
| 291: | if ($this->isInaccessible($uploadDirectory)) {
|
| 292: | return array(
|
| 293: | 'error' => "Server error. Uploads directory isn't writable"
|
| 294: | . ((!$this->isWindows()) ? " or executable." : ".")
|
| 295: | );
|
| 296: | }
|
| 297: |
|
| 298: | $targetFolder = $uploadDirectory;
|
| 299: | $uuid = false;
|
| 300: | $method = $_SERVER["REQUEST_METHOD"];
|
| 301: | if ($method == "DELETE") {
|
| 302: | $url = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
| 303: | $tokens = explode('/', $url);
|
| 304: | $uuid = $tokens[sizeof($tokens)-1];
|
| 305: | } elseif ($method == "POST") {
|
| 306: | $uuid = $_REQUEST['qquuid'];
|
| 307: | } else {
|
| 308: | return array("success" => false,
|
| 309: | "error" => "Invalid request method! ".$method
|
| 310: | );
|
| 311: | }
|
| 312: |
|
| 313: | $target = join(DIRECTORY_SEPARATOR, array($targetFolder, $uuid));
|
| 314: |
|
| 315: | if (is_dir($target)) {
|
| 316: | $this->removeDir($target);
|
| 317: | return array("success" => true, "uuid" => $uuid);
|
| 318: | } else {
|
| 319: | return array("success" => false,
|
| 320: | "error" => "File not found! Unable to delete.".$url,
|
| 321: | "path" => $uuid
|
| 322: | );
|
| 323: | }
|
| 324: | }
|
| 325: |
|
| 326: | |
| 327: | |
| 328: | |
| 329: | |
| 330: | |
| 331: | |
| 332: | |
| 333: |
|
| 334: | protected function getUniqueTargetPath($uploadDirectory, $filename)
|
| 335: | {
|
| 336: |
|
| 337: |
|
| 338: |
|
| 339: |
|
| 340: | if (function_exists('sem_acquire')) {
|
| 341: | $lock = sem_get(ftok(__FILE__, 'u'));
|
| 342: | sem_acquire($lock);
|
| 343: | }
|
| 344: |
|
| 345: | $pathinfo = pathinfo($filename);
|
| 346: | $base = $pathinfo['filename'];
|
| 347: | $ext = isset($pathinfo['extension']) ? $pathinfo['extension'] : '';
|
| 348: | $ext = $ext == '' ? $ext : '.' . $ext;
|
| 349: |
|
| 350: | $unique = $base;
|
| 351: | $suffix = 0;
|
| 352: |
|
| 353: |
|
| 354: |
|
| 355: | while (file_exists($uploadDirectory . DIRECTORY_SEPARATOR . $unique . $ext)) {
|
| 356: | $suffix += rand(1, 999);
|
| 357: | $unique = $base.'-'.$suffix;
|
| 358: | }
|
| 359: |
|
| 360: | $result = $uploadDirectory . DIRECTORY_SEPARATOR . $unique . $ext;
|
| 361: |
|
| 362: |
|
| 363: | if (!touch($result)) {
|
| 364: |
|
| 365: | $result = false;
|
| 366: | }
|
| 367: |
|
| 368: | if (function_exists('sem_acquire')) {
|
| 369: | sem_release($lock);
|
| 370: | }
|
| 371: |
|
| 372: | return $result;
|
| 373: | }
|
| 374: |
|
| 375: | |
| 376: | |
| 377: | |
| 378: | |
| 379: | |
| 380: |
|
| 381: | protected function cleanupChunks()
|
| 382: | {
|
| 383: | foreach (scandir($this->chunksFolder) as $item) {
|
| 384: | if ($item == "." || $item == "..") {
|
| 385: | continue;
|
| 386: | }
|
| 387: |
|
| 388: | $path = $this->chunksFolder.DIRECTORY_SEPARATOR.$item;
|
| 389: |
|
| 390: | if (!is_dir($path)) {
|
| 391: | continue;
|
| 392: | }
|
| 393: |
|
| 394: | if (time() - filemtime($path) > $this->chunksExpireIn) {
|
| 395: | $this->removeDir($path);
|
| 396: | }
|
| 397: | }
|
| 398: | }
|
| 399: |
|
| 400: | |
| 401: | |
| 402: | |
| 403: | |
| 404: |
|
| 405: | protected function removeDir($dir)
|
| 406: | {
|
| 407: | foreach (scandir($dir) as $item) {
|
| 408: | if ($item == "." || $item == "..") {
|
| 409: | continue;
|
| 410: | }
|
| 411: |
|
| 412: | if (is_dir($item)) {
|
| 413: | $this->removeDir($item);
|
| 414: | } else {
|
| 415: | unlink(join(DIRECTORY_SEPARATOR, array($dir, $item)));
|
| 416: | }
|
| 417: | }
|
| 418: | rmdir($dir);
|
| 419: | }
|
| 420: |
|
| 421: | |
| 422: | |
| 423: | |
| 424: | |
| 425: |
|
| 426: | protected function toBytes($str)
|
| 427: | {
|
| 428: | $str = trim($str);
|
| 429: | $last = strtolower($str[strlen($str)-1]);
|
| 430: | if(is_numeric($last)) {
|
| 431: | $val = (int) $str;
|
| 432: | } else {
|
| 433: | $val = (int) substr($str, 0, -1);
|
| 434: | }
|
| 435: | switch ($last) {
|
| 436: | case 'g':
|
| 437: | $val *= 1024;
|
| 438: | case 'm':
|
| 439: | $val *= 1024;
|
| 440: | case 'k':
|
| 441: | $val *= 1024;
|
| 442: | }
|
| 443: | return $val;
|
| 444: | }
|
| 445: |
|
| 446: | |
| 447: | |
| 448: | |
| 449: | |
| 450: | |
| 451: | |
| 452: | |
| 453: | |
| 454: | |
| 455: | |
| 456: | |
| 457: |
|
| 458: | protected function isInaccessible($directory)
|
| 459: | {
|
| 460: | $isWin = $this->isWindows();
|
| 461: | $folderInaccessible =
|
| 462: | ($isWin) ? !is_writable($directory) : (!is_writable($directory) && !is_executable($directory));
|
| 463: | return $folderInaccessible;
|
| 464: | }
|
| 465: |
|
| 466: | |
| 467: | |
| 468: | |
| 469: | |
| 470: |
|
| 471: |
|
| 472: | protected function isWindows()
|
| 473: | {
|
| 474: | $isWin = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
|
| 475: | return $isWin;
|
| 476: | }
|
| 477: | }
|
| 478: | |