|
1 |
| -<?php |
2 |
| - |
3 |
| -/** |
4 |
| - * Do not use or reference this directly from your client-side code. |
5 |
| - * Instead, this should be required via the endpoint.php or endpoint-cors.php |
6 |
| - * file(s). |
7 |
| - */ |
8 |
| - |
9 |
| -class UploadHandler { |
10 |
| - |
11 |
| - public $allowedExtensions = array(); |
12 |
| - public $sizeLimit = null; |
13 |
| - public $inputName = 'qqfile'; |
14 |
| - public $chunksFolder = 'chunks'; |
15 |
| - |
16 |
| - public $chunksCleanupProbability = 0.001; // Once in 1000 requests on avg |
17 |
| - public $chunksExpireIn = 604800; // One week |
18 |
| - |
19 |
| - protected $uploadName; |
20 |
| - |
21 |
| - function __construct(){ |
22 |
| - $this->sizeLimit = $this->toBytes(ini_get('upload_max_filesize')); |
23 |
| - } |
24 |
| - |
25 |
| - /** |
26 |
| - * Get the original filename |
27 |
| - */ |
28 |
| - public function getName(){ |
29 |
| - if (isset($_REQUEST['qqfilename'])) |
30 |
| - return $_REQUEST['qqfilename']; |
31 |
| - |
32 |
| - if (isset($_FILES[$this->inputName])) |
33 |
| - return $_FILES[$this->inputName]['name']; |
34 |
| - } |
35 |
| - |
36 |
| - /** |
37 |
| - * Get the name of the uploaded file |
38 |
| - */ |
39 |
| - public function getUploadName(){ |
40 |
| - return $this->uploadName; |
41 |
| - } |
42 |
| - |
43 |
| - public function combineChunks($uploadDirectory) { |
44 |
| - $uuid = $_POST['qquuid']; |
45 |
| - $name = $this->getName(); |
46 |
| - $targetFolder = $this->chunksFolder.DIRECTORY_SEPARATOR.$uuid; |
47 |
| - $totalParts = isset($_REQUEST['qqtotalparts']) ? (int)$_REQUEST['qqtotalparts'] : 1; |
48 |
| - |
49 |
| - $target = join(DIRECTORY_SEPARATOR, array($uploadDirectory, $uuid, $name)); |
50 |
| - $this->uploadName = $name; |
51 |
| - |
52 |
| - if (!file_exists($target)){ |
53 |
| - mkdir(dirname($target)); |
54 |
| - } |
55 |
| - $target = fopen($target, 'wb'); |
56 |
| - |
57 |
| - for ($i=0; $i<$totalParts; $i++){ |
58 |
| - $chunk = fopen($targetFolder.DIRECTORY_SEPARATOR.$i, "rb"); |
59 |
| - stream_copy_to_stream($chunk, $target); |
60 |
| - fclose($chunk); |
61 |
| - } |
62 |
| - |
63 |
| - // Success |
64 |
| - fclose($target); |
65 |
| - |
66 |
| - for ($i=0; $i<$totalParts; $i++){ |
67 |
| - unlink($targetFolder.DIRECTORY_SEPARATOR.$i); |
68 |
| - } |
69 |
| - |
70 |
| - rmdir($targetFolder); |
71 |
| - |
72 |
| - return array("success" => true, "uuid" => $uuid); |
73 |
| - } |
74 |
| - |
75 |
| - /** |
76 |
| - * Process the upload. |
77 |
| - * @param string $uploadDirectory Target directory. |
78 |
| - * @param string $name Overwrites the name of the file. |
79 |
| - */ |
80 |
| - public function handleUpload($uploadDirectory, $name = null){ |
81 |
| - |
82 |
| - if (is_writable($this->chunksFolder) && |
83 |
| - 1 == mt_rand(1, 1/$this->chunksCleanupProbability)){ |
84 |
| - |
85 |
| - // Run garbage collection |
86 |
| - $this->cleanupChunks(); |
87 |
| - } |
88 |
| - |
89 |
| - // Check that the max upload size specified in class configuration does not |
90 |
| - // exceed size allowed by server config |
91 |
| - if ($this->toBytes(ini_get('post_max_size')) < $this->sizeLimit || |
92 |
| - $this->toBytes(ini_get('upload_max_filesize')) < $this->sizeLimit){ |
93 |
| - $size = max(1, $this->sizeLimit / 1024 / 1024) . 'M'; |
94 |
| - return array('error'=>"Server error. Increase post_max_size and upload_max_filesize to ".$size); |
95 |
| - } |
96 |
| - |
97 |
| - if ($this->isInaccessible($uploadDirectory)){ |
98 |
| - return array('error' => "Server error. Uploads directory isn't writable"); |
99 |
| - } |
100 |
| - |
101 |
| - $type = $_SERVER['CONTENT_TYPE']; |
102 |
| - if (isset($_SERVER['HTTP_CONTENT_TYPE'])) { |
103 |
| - $type = $_SERVER['HTTP_CONTENT_TYPE']; |
104 |
| - } |
105 |
| - |
106 |
| - if(!isset($type)) { |
107 |
| - return array('error' => "No files were uploaded."); |
108 |
| - } else if (strpos(strtolower($type), 'multipart/') !== 0){ |
109 |
| - return array('error' => "Server error. Not a multipart request. Please set forceMultipart to default value (true)."); |
110 |
| - } |
111 |
| - |
112 |
| - // Get size and name |
113 |
| - $file = $_FILES[$this->inputName]; |
114 |
| - $size = $file['size']; |
115 |
| - |
116 |
| - if ($name === null){ |
117 |
| - $name = $this->getName(); |
118 |
| - } |
119 |
| - |
120 |
| - // Validate name |
121 |
| - if ($name === null || $name === ''){ |
122 |
| - return array('error' => 'File name empty.'); |
123 |
| - } |
124 |
| - |
125 |
| - // Validate file size |
126 |
| - if ($size == 0){ |
127 |
| - return array('error' => 'File is empty.'); |
128 |
| - } |
129 |
| - |
130 |
| - if ($size > $this->sizeLimit){ |
131 |
| - return array('error' => 'File is too large.'); |
132 |
| - } |
133 |
| - |
134 |
| - // Validate file extension |
135 |
| - $pathinfo = pathinfo($name); |
136 |
| - $ext = isset($pathinfo['extension']) ? $pathinfo['extension'] : ''; |
137 |
| - |
138 |
| - if($this->allowedExtensions && !in_array(strtolower($ext), array_map("strtolower", $this->allowedExtensions))){ |
139 |
| - $these = implode(', ', $this->allowedExtensions); |
140 |
| - return array('error' => 'File has an invalid extension, it should be one of '. $these . '.'); |
141 |
| - } |
142 |
| - |
143 |
| - // Save a chunk |
144 |
| - $totalParts = isset($_REQUEST['qqtotalparts']) ? (int)$_REQUEST['qqtotalparts'] : 1; |
145 |
| - |
146 |
| - $uuid = $_REQUEST['qquuid']; |
147 |
| - if ($totalParts > 1){ |
148 |
| - # chunked upload |
149 |
| - |
150 |
| - $chunksFolder = $this->chunksFolder; |
151 |
| - $partIndex = (int)$_REQUEST['qqpartindex']; |
152 |
| - |
153 |
| - if (!is_writable($chunksFolder) && !is_executable($uploadDirectory)){ |
154 |
| - return array('error' => "Server error. Chunks directory isn't writable or executable."); |
155 |
| - } |
156 |
| - |
157 |
| - $targetFolder = $this->chunksFolder.DIRECTORY_SEPARATOR.$uuid; |
158 |
| - |
159 |
| - if (!file_exists($targetFolder)){ |
160 |
| - mkdir($targetFolder); |
161 |
| - } |
162 |
| - |
163 |
| - $target = $targetFolder.'/'.$partIndex; |
164 |
| - $success = move_uploaded_file($_FILES[$this->inputName]['tmp_name'], $target); |
165 |
| - |
166 |
| - return array("success" => true, "uuid" => $uuid); |
167 |
| - |
168 |
| - } |
169 |
| - else { |
170 |
| - # non-chunked upload |
171 |
| - |
172 |
| - $target = join(DIRECTORY_SEPARATOR, array($uploadDirectory, $uuid, $name)); |
173 |
| - //$target = $this->getUniqueTargetPath($uploadDirectory, $name); |
174 |
| - |
175 |
| - if ($target){ |
176 |
| - $this->uploadName = basename($target); |
177 |
| - |
178 |
| - if (!is_dir(dirname($target))){ |
179 |
| - mkdir(dirname($target)); |
180 |
| - } |
181 |
| - if (move_uploaded_file($file['tmp_name'], $target)){ |
182 |
| - return array('success'=> true, "uuid" => $uuid); |
183 |
| - } |
184 |
| - } |
185 |
| - |
186 |
| - return array('error'=> 'Could not save uploaded file.' . |
187 |
| - 'The upload was cancelled, or server error encountered'); |
188 |
| - } |
189 |
| - } |
190 |
| - |
191 |
| - /** |
192 |
| - * Process a delete. |
193 |
| - * @param string $uploadDirectory Target directory. |
194 |
| - * @params string $name Overwrites the name of the file. |
195 |
| - * |
196 |
| - */ |
197 |
| - public function handleDelete($uploadDirectory, $name=null) |
198 |
| - { |
199 |
| - if ($this->isInaccessible($uploadDirectory)) { |
200 |
| - return array('error' => "Server error. Uploads directory isn't writable" . ((!$this->isWindows()) ? " or executable." : ".")); |
201 |
| - } |
202 |
| - |
203 |
| - $targetFolder = $uploadDirectory; |
204 |
| - $url = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); |
205 |
| - $tokens = explode('/', $url); |
206 |
| - $uuid = $tokens[sizeof($tokens)-1]; |
207 |
| - |
208 |
| - $target = join(DIRECTORY_SEPARATOR, array($targetFolder, $uuid)); |
209 |
| - |
210 |
| - // print_r($target); |
211 |
| - if (is_dir($target)){ |
212 |
| - $this->removeDir($target); |
213 |
| - return array("success" => true, "uuid" => $uuid); |
214 |
| - } else { |
215 |
| - return array("success" => false, |
216 |
| - "error" => "File not found! Unable to delete.".$url, |
217 |
| - "path" => $uuid |
218 |
| - ); |
219 |
| - } |
220 |
| - |
221 |
| - } |
222 |
| - |
223 |
| - /** |
224 |
| - * Returns a path to use with this upload. Check that the name does not exist, |
225 |
| - * and appends a suffix otherwise. |
226 |
| - * @param string $uploadDirectory Target directory |
227 |
| - * @param string $filename The name of the file to use. |
228 |
| - */ |
229 |
| - protected function getUniqueTargetPath($uploadDirectory, $filename) |
230 |
| - { |
231 |
| - // Allow only one process at the time to get a unique file name, otherwise |
232 |
| - // if multiple people would upload a file with the same name at the same time |
233 |
| - // only the latest would be saved. |
234 |
| - |
235 |
| - if (function_exists('sem_acquire')){ |
236 |
| - $lock = sem_get(ftok(__FILE__, 'u')); |
237 |
| - sem_acquire($lock); |
238 |
| - } |
239 |
| - |
240 |
| - $pathinfo = pathinfo($filename); |
241 |
| - $base = $pathinfo['filename']; |
242 |
| - $ext = isset($pathinfo['extension']) ? $pathinfo['extension'] : ''; |
243 |
| - $ext = $ext == '' ? $ext : '.' . $ext; |
244 |
| - |
245 |
| - $unique = $base; |
246 |
| - $suffix = 0; |
247 |
| - |
248 |
| - // Get unique file name for the file, by appending random suffix. |
249 |
| - |
250 |
| - while (file_exists($uploadDirectory . DIRECTORY_SEPARATOR . $unique . $ext)){ |
251 |
| - $suffix += rand(1, 999); |
252 |
| - $unique = $base.'-'.$suffix; |
253 |
| - } |
254 |
| - |
255 |
| - $result = $uploadDirectory . DIRECTORY_SEPARATOR . $unique . $ext; |
256 |
| - |
257 |
| - // Create an empty target file |
258 |
| - if (!touch($result)){ |
259 |
| - // Failed |
260 |
| - $result = false; |
261 |
| - } |
262 |
| - |
263 |
| - if (function_exists('sem_acquire')){ |
264 |
| - sem_release($lock); |
265 |
| - } |
266 |
| - |
267 |
| - return $result; |
268 |
| - } |
269 |
| - |
270 |
| - /** |
271 |
| - * Deletes all file parts in the chunks folder for files uploaded |
272 |
| - * more than chunksExpireIn seconds ago |
273 |
| - */ |
274 |
| - protected function cleanupChunks(){ |
275 |
| - foreach (scandir($this->chunksFolder) as $item){ |
276 |
| - if ($item == "." || $item == "..") |
277 |
| - continue; |
278 |
| - |
279 |
| - $path = $this->chunksFolder.DIRECTORY_SEPARATOR.$item; |
280 |
| - |
281 |
| - if (!is_dir($path)) |
282 |
| - continue; |
283 |
| - |
284 |
| - if (time() - filemtime($path) > $this->chunksExpireIn){ |
285 |
| - $this->removeDir($path); |
286 |
| - } |
287 |
| - } |
288 |
| - } |
289 |
| - |
290 |
| - /** |
291 |
| - * Removes a directory and all files contained inside |
292 |
| - * @param string $dir |
293 |
| - */ |
294 |
| - protected function removeDir($dir){ |
295 |
| - foreach (scandir($dir) as $item){ |
296 |
| - if ($item == "." || $item == "..") |
297 |
| - continue; |
298 |
| - |
299 |
| - if (is_dir($item)){ |
300 |
| - $this->removeDir($item); |
301 |
| - } else { |
302 |
| - unlink(join(DIRECTORY_SEPARATOR, array($dir, $item))); |
303 |
| - } |
304 |
| - |
305 |
| - } |
306 |
| - rmdir($dir); |
307 |
| - } |
308 |
| - |
309 |
| - /** |
310 |
| - * Converts a given size with units to bytes. |
311 |
| - * @param string $str |
312 |
| - */ |
313 |
| - protected function toBytes($str){ |
314 |
| - $val = trim($str); |
315 |
| - $last = strtolower($str[strlen($str)-1]); |
316 |
| - switch($last) { |
317 |
| - case 'g': $val *= 1024; |
318 |
| - case 'm': $val *= 1024; |
319 |
| - case 'k': $val *= 1024; |
320 |
| - } |
321 |
| - return $val; |
322 |
| - } |
323 |
| - |
324 |
| - /** |
325 |
| - * Determines whether a directory can be accessed. |
326 |
| - * |
327 |
| - * is_executable() is not reliable on Windows prior PHP 5.0.0 |
328 |
| - * (http://www.php.net/manual/en/function.is-executable.php) |
329 |
| - * The following tests if the current OS is Windows and if so, merely |
330 |
| - * checks if the folder is writable; |
331 |
| - * otherwise, it checks additionally for executable status (like before). |
332 |
| - * |
333 |
| - * @param string $directory The target directory to test access |
334 |
| - */ |
335 |
| - protected function isInaccessible($directory) { |
336 |
| - $isWin = $this->isWindows(); |
337 |
| - $folderInaccessible = ($isWin) ? !is_writable($directory) : ( !is_writable($directory) && !is_executable($directory) ); |
338 |
| - return $folderInaccessible; |
339 |
| - } |
340 |
| - |
341 |
| - /** |
342 |
| - * Determines is the OS is Windows or not |
343 |
| - * |
344 |
| - * @return boolean |
345 |
| - */ |
346 |
| - |
347 |
| - protected function isWindows() { |
348 |
| - $isWin = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); |
349 |
| - return $isWin; |
350 |
| - } |
351 |
| - |
352 |
| -} |
| 1 | +The traditional PHP server example has moved to a new repository: https://github.com/FineUploader/php-traditional-server. |
| 2 | +The server code is also available on packagist via composer under the name "fineuploader/php-traditional-server". |
0 commit comments