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: class SystemFineUploadHandler
40: {
41:
42: public $allowedExtensions = array();
43: public $allowedMimeTypes = array();
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: $val = trim($str);
429: $last = strtolower($str[strlen($str)-1]);
430: switch ($last) {
431: case 'g':
432: $val *= 1024;
433: case 'm':
434: $val *= 1024;
435: case 'k':
436: $val *= 1024;
437: }
438: return $val;
439: }
440:
441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452:
453: protected function isInaccessible($directory)
454: {
455: $isWin = $this->isWindows();
456: $folderInaccessible =
457: ($isWin) ? !is_writable($directory) : (!is_writable($directory) && !is_executable($directory));
458: return $folderInaccessible;
459: }
460:
461: 462: 463: 464: 465:
466:
467: protected function isWindows()
468: {
469: $isWin = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
470: return $isWin;
471: }
472: }
473: