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: | |