Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.06% |
152 / 155 |
|
87.50% |
14 / 16 |
CRAP | |
0.00% |
0 / 1 |
filespec | |
98.06% |
152 / 155 |
|
87.50% |
14 / 16 |
81 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
set_upload_ary | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
8 | |||
set_upload_namespace | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
init_error | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
set_error | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
clean_filename | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
8 | |||
get | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
is_image | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
is_uploaded | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
8 | |||
remove | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
get_extension | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
get_mimetype | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
get_filesize | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
check_content | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 | |||
move_file | |
98.25% |
56 / 57 |
|
0.00% |
0 / 1 |
29 | |||
additional_checks | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
6 |
1 | <?php |
2 | /** |
3 | * |
4 | * This file is part of the phpBB Forum Software package. |
5 | * |
6 | * @copyright (c) phpBB Limited <https://www.phpbb.com> |
7 | * @license GNU General Public License, version 2 (GPL-2.0) |
8 | * |
9 | * For full copyright and license information, please see |
10 | * the docs/CREDITS.txt file. |
11 | * |
12 | */ |
13 | |
14 | namespace phpbb\files; |
15 | |
16 | use phpbb\language\language; |
17 | |
18 | /** |
19 | * Responsible for holding all file relevant information, as well as doing file-specific operations. |
20 | * The {@link fileupload fileupload class} can be used to upload several files, each of them being this object to operate further on. |
21 | * |
22 | * @deprecated 4.0.0 Use phpbb\files\filespec_storage instead |
23 | */ |
24 | class filespec |
25 | { |
26 | /** @var string File name */ |
27 | protected $filename = ''; |
28 | |
29 | /** @var string Real name of file */ |
30 | protected $realname = ''; |
31 | |
32 | /** @var string Upload name of file */ |
33 | protected $uploadname = ''; |
34 | |
35 | /** @var string Mimetype of file */ |
36 | protected $mimetype = ''; |
37 | |
38 | /** @var string File extension */ |
39 | protected $extension = ''; |
40 | |
41 | /** @var int File size */ |
42 | protected $filesize = 0; |
43 | |
44 | /** @var int Width of file */ |
45 | protected $width = 0; |
46 | |
47 | /** @var int Height of file */ |
48 | protected $height = 0; |
49 | |
50 | /** @var array Image info including type and size */ |
51 | protected $image_info = array(); |
52 | |
53 | /** @var string Destination file name */ |
54 | protected $destination_file = ''; |
55 | |
56 | /** @var string Destination file path */ |
57 | protected $destination_path = ''; |
58 | |
59 | /** @var bool Whether file was moved */ |
60 | protected $file_moved = false; |
61 | |
62 | /** @var bool Whether file is local */ |
63 | protected $local = false; |
64 | |
65 | /** @var bool Class initialization flag */ |
66 | protected $class_initialized = false; |
67 | |
68 | /** @var array Error array */ |
69 | public $error = array(); |
70 | |
71 | /** @var upload Instance of upload class */ |
72 | public $upload; |
73 | |
74 | /** @var \phpbb\filesystem\filesystem_interface */ |
75 | protected $filesystem; |
76 | |
77 | /** @var \bantu\IniGetWrapper\IniGetWrapper ini_get() wrapper class */ |
78 | protected $php_ini; |
79 | |
80 | /** @var \FastImageSize\FastImageSize */ |
81 | protected $imagesize; |
82 | |
83 | /** @var language Language class */ |
84 | protected $language; |
85 | |
86 | /** @var string phpBB root path */ |
87 | protected $phpbb_root_path; |
88 | |
89 | /** @var \phpbb\plupload\plupload|null The plupload object */ |
90 | protected $plupload; |
91 | |
92 | /** @var \phpbb\mimetype\guesser|null phpBB Mimetype guesser */ |
93 | protected $mimetype_guesser; |
94 | |
95 | /** |
96 | * File upload class |
97 | * |
98 | * @param \phpbb\filesystem\filesystem_interface $phpbb_filesystem Filesystem |
99 | * @param language $language Language |
100 | * @param \bantu\IniGetWrapper\IniGetWrapper $php_ini ini_get() wrapper |
101 | * @param \FastImageSize\FastImageSize $imagesize Imagesize class |
102 | * @param string $phpbb_root_path phpBB root path |
103 | * @param \phpbb\mimetype\guesser|null $mimetype_guesser Mime type guesser |
104 | * @param \phpbb\plupload\plupload|null $plupload Plupload |
105 | */ |
106 | public function __construct(\phpbb\filesystem\filesystem_interface $phpbb_filesystem, language $language, \bantu\IniGetWrapper\IniGetWrapper $php_ini, \FastImageSize\FastImageSize $imagesize, $phpbb_root_path, \phpbb\mimetype\guesser $mimetype_guesser = null, \phpbb\plupload\plupload $plupload = null) |
107 | { |
108 | $this->filesystem = $phpbb_filesystem; |
109 | $this->language = $language; |
110 | $this->php_ini = $php_ini; |
111 | $this->imagesize = $imagesize; |
112 | $this->phpbb_root_path = $phpbb_root_path; |
113 | $this->plupload = $plupload; |
114 | $this->mimetype_guesser = $mimetype_guesser; |
115 | } |
116 | |
117 | /** |
118 | * Set upload ary |
119 | * |
120 | * @param array $upload_ary Upload ary |
121 | * |
122 | * @return filespec This instance of the filespec class |
123 | */ |
124 | public function set_upload_ary($upload_ary) |
125 | { |
126 | if (!isset($upload_ary) || !count($upload_ary)) |
127 | { |
128 | return $this; |
129 | } |
130 | |
131 | $this->class_initialized = true; |
132 | $this->filename = $upload_ary['tmp_name']; |
133 | $this->filesize = $upload_ary['size']; |
134 | $name = $upload_ary['name']; |
135 | $name = trim(utf8_basename($name)); |
136 | $this->realname = $this->uploadname = $name; |
137 | $this->mimetype = $upload_ary['type']; |
138 | |
139 | // Opera adds the name to the mime type |
140 | $this->mimetype = ($this->mimetype && str_contains($this->mimetype, '; name')) ? str_replace(strstr($this->mimetype, '; name'), '', $this->mimetype) : $this->mimetype; |
141 | |
142 | if (!$this->mimetype) |
143 | { |
144 | $this->mimetype = 'application/octet-stream'; |
145 | } |
146 | |
147 | $this->extension = strtolower(self::get_extension($this->realname)); |
148 | |
149 | // Try to get real filesize from temporary folder (not always working) ;) |
150 | $this->filesize = ($this->get_filesize($this->filename)) ?: $this->filesize; |
151 | |
152 | $this->width = $this->height = 0; |
153 | $this->file_moved = false; |
154 | |
155 | $this->local = (isset($upload_ary['local_mode'])) ? true : false; |
156 | |
157 | return $this; |
158 | } |
159 | |
160 | /** |
161 | * Set the upload namespace |
162 | * |
163 | * @param upload $namespace Instance of upload class |
164 | * |
165 | * @return filespec This instance of the filespec class |
166 | */ |
167 | public function set_upload_namespace($namespace) |
168 | { |
169 | $this->upload = $namespace; |
170 | |
171 | return $this; |
172 | } |
173 | |
174 | /** |
175 | * Check if class members were not properly initialised yet |
176 | * |
177 | * @return bool True if there was an init error, false if not |
178 | */ |
179 | public function init_error() |
180 | { |
181 | return !$this->class_initialized; |
182 | } |
183 | |
184 | /** |
185 | * Set error in error array |
186 | * |
187 | * @param mixed $error Content for error array |
188 | * |
189 | * @return \phpbb\files\filespec This instance of the filespec class |
190 | */ |
191 | public function set_error($error) |
192 | { |
193 | $this->error[] = $error; |
194 | |
195 | return $this; |
196 | } |
197 | |
198 | /** |
199 | * Cleans destination filename |
200 | * |
201 | * @param string $mode Either real, unique, or unique_ext. Real creates a |
202 | * realname, filtering some characters, lowering every |
203 | * character. Unique creates a unique filename. |
204 | * @param string $prefix Prefix applied to filename |
205 | * @param string $user_id The user_id is only needed for when cleaning a user's avatar |
206 | */ |
207 | public function clean_filename($mode = 'unique', $prefix = '', $user_id = '') |
208 | { |
209 | if ($this->init_error()) |
210 | { |
211 | return; |
212 | } |
213 | |
214 | switch ($mode) |
215 | { |
216 | case 'real': |
217 | // Remove every extension from filename (to not let the mime bug being exposed) |
218 | if (strpos($this->realname, '.') !== false) |
219 | { |
220 | $this->realname = substr($this->realname, 0, strpos($this->realname, '.')); |
221 | } |
222 | |
223 | // Replace any chars which may cause us problems with _ |
224 | $bad_chars = array("'", "\\", ' ', '/', ':', '*', '?', '"', '<', '>', '|'); |
225 | |
226 | $this->realname = rawurlencode(str_replace($bad_chars, '_', strtolower($this->realname))); |
227 | $this->realname = preg_replace("/%(\w{2})/", '_', $this->realname); |
228 | |
229 | $this->realname = $prefix . $this->realname . '.' . $this->extension; |
230 | break; |
231 | |
232 | case 'unique': |
233 | $this->realname = $prefix . md5(unique_id()); |
234 | break; |
235 | |
236 | case 'avatar': |
237 | $this->extension = strtolower($this->extension); |
238 | $this->realname = $prefix . $user_id . '.' . $this->extension; |
239 | |
240 | break; |
241 | |
242 | case 'unique_ext': |
243 | default: |
244 | $this->realname = $prefix . md5(unique_id()) . '.' . $this->extension; |
245 | } |
246 | } |
247 | |
248 | /** |
249 | * Get property from file object |
250 | * |
251 | * @param string $property Name of property |
252 | * |
253 | * @return mixed Content of property |
254 | */ |
255 | public function get($property) |
256 | { |
257 | if ($this->init_error() || !isset($this->$property)) |
258 | { |
259 | return false; |
260 | } |
261 | |
262 | return $this->$property; |
263 | } |
264 | |
265 | /** |
266 | * Check if file is an image (mime type) |
267 | * |
268 | * @return bool true if it is an image, false if not |
269 | */ |
270 | public function is_image() |
271 | { |
272 | return (strpos($this->mimetype, 'image/') === 0); |
273 | } |
274 | |
275 | /** |
276 | * Check if the file got correctly uploaded |
277 | * |
278 | * @return bool true if it is a valid upload, false if not |
279 | */ |
280 | public function is_uploaded() |
281 | { |
282 | $is_plupload = $this->plupload && $this->plupload->is_active(); |
283 | |
284 | if (!$this->local && !$is_plupload && !is_uploaded_file($this->filename)) |
285 | { |
286 | return false; |
287 | } |
288 | |
289 | if (($this->local || $is_plupload) && !file_exists($this->filename)) |
290 | { |
291 | return false; |
292 | } |
293 | |
294 | return true; |
295 | } |
296 | |
297 | /** |
298 | * Remove file |
299 | */ |
300 | public function remove() |
301 | { |
302 | if ($this->file_moved) |
303 | { |
304 | @unlink($this->destination_file); |
305 | } |
306 | } |
307 | |
308 | /** |
309 | * Get file extension |
310 | * |
311 | * @param string $filename Filename that needs to be checked |
312 | * |
313 | * @return string Extension of the supplied filename |
314 | */ |
315 | public static function get_extension($filename) |
316 | { |
317 | $filename = utf8_basename($filename); |
318 | |
319 | if (strpos($filename, '.') === false) |
320 | { |
321 | return ''; |
322 | } |
323 | |
324 | $filename = explode('.', $filename); |
325 | return array_pop($filename); |
326 | } |
327 | |
328 | /** |
329 | * Get mime type |
330 | * |
331 | * @param string $filename Filename that needs to be checked |
332 | * @return string Mime type of supplied filename or empty string if mimetype could not be guessed |
333 | */ |
334 | public function get_mimetype($filename) |
335 | { |
336 | if ($this->mimetype_guesser !== null) |
337 | { |
338 | $mimetype = $this->mimetype_guesser->guess($filename, $this->uploadname); |
339 | |
340 | if ($mimetype !== 'application/octet-stream') |
341 | { |
342 | $this->mimetype = $mimetype; |
343 | } |
344 | } |
345 | |
346 | return $this->mimetype ?: ''; |
347 | } |
348 | |
349 | /** |
350 | * Get file size |
351 | * |
352 | * @param string $filename File name of file to check |
353 | * |
354 | * @return int File size |
355 | */ |
356 | public function get_filesize($filename) |
357 | { |
358 | return @filesize($filename); |
359 | } |
360 | |
361 | |
362 | /** |
363 | * Check the first 256 bytes for forbidden content |
364 | * |
365 | * @param array $disallowed_content Array containg disallowed content |
366 | * |
367 | * @return bool False if disallowed content found, true if not |
368 | */ |
369 | public function check_content($disallowed_content) |
370 | { |
371 | if (empty($disallowed_content)) |
372 | { |
373 | return true; |
374 | } |
375 | |
376 | $fp = @fopen($this->filename, 'rb'); |
377 | |
378 | if ($fp !== false) |
379 | { |
380 | $ie_mime_relevant = fread($fp, 256); |
381 | fclose($fp); |
382 | foreach ($disallowed_content as $forbidden) |
383 | { |
384 | if (stripos($ie_mime_relevant, '<' . $forbidden) !== false) |
385 | { |
386 | return false; |
387 | } |
388 | } |
389 | } |
390 | return true; |
391 | } |
392 | |
393 | /** |
394 | * Move file to destination folder |
395 | * The phpbb_root_path variable will be applied to the destination path |
396 | * |
397 | * @param string $destination Destination path, for example $config['avatar_path'] |
398 | * @param bool $overwrite If set to true, an already existing file will be overwritten |
399 | * @param bool $skip_image_check If set to true, the check for the file to be a valid image is skipped |
400 | * @param string|bool $chmod Permission mask for chmodding the file after a successful move. |
401 | * The mode entered here reflects the mode defined by {@link \phpbb\filesystem\filesystem::phpbb_chmod()} |
402 | * |
403 | * @return bool True if file was moved, false if not |
404 | * @access public |
405 | */ |
406 | public function move_file($destination, $overwrite = false, $skip_image_check = false, $chmod = false) |
407 | { |
408 | if (count($this->error)) |
409 | { |
410 | return false; |
411 | } |
412 | |
413 | $chmod = ($chmod === false) ? \phpbb\filesystem\filesystem_interface::CHMOD_READ | \phpbb\filesystem\filesystem_interface::CHMOD_WRITE : $chmod; |
414 | |
415 | // We need to trust the admin in specifying valid upload directories and an attacker not being able to overwrite it... |
416 | $this->destination_path = $this->phpbb_root_path . $destination; |
417 | |
418 | // Check if the destination path exist... |
419 | if (!file_exists($this->destination_path)) |
420 | { |
421 | @unlink($this->filename); |
422 | return false; |
423 | } |
424 | |
425 | $upload_mode = ($this->php_ini->getBool('open_basedir')) ? 'move' : 'copy'; |
426 | $upload_mode = ($this->local) ? 'local' : $upload_mode; |
427 | $this->destination_file = $this->destination_path . '/' . utf8_basename($this->realname); |
428 | |
429 | // Check if the file already exist, else there is something wrong... |
430 | if (file_exists($this->destination_file) && !$overwrite) |
431 | { |
432 | @unlink($this->filename); |
433 | $this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); |
434 | $this->file_moved = false; |
435 | return false; |
436 | } |
437 | else |
438 | { |
439 | if (file_exists($this->destination_file)) |
440 | { |
441 | @unlink($this->destination_file); |
442 | } |
443 | |
444 | switch ($upload_mode) |
445 | { |
446 | case 'copy': |
447 | |
448 | if (!@copy($this->filename, $this->destination_file)) |
449 | { |
450 | if (!@move_uploaded_file($this->filename, $this->destination_file)) |
451 | { |
452 | $this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); |
453 | } |
454 | } |
455 | |
456 | break; |
457 | |
458 | case 'move': |
459 | |
460 | if (!@move_uploaded_file($this->filename, $this->destination_file)) |
461 | { |
462 | if (!@copy($this->filename, $this->destination_file)) |
463 | { |
464 | $this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); |
465 | } |
466 | } |
467 | |
468 | break; |
469 | |
470 | case 'local': |
471 | |
472 | if (!@copy($this->filename, $this->destination_file)) |
473 | { |
474 | $this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); |
475 | } |
476 | |
477 | break; |
478 | } |
479 | |
480 | // Remove temporary filename |
481 | if (file_exists($this->filename)) |
482 | { |
483 | @unlink($this->filename); |
484 | } |
485 | |
486 | if (count($this->error)) |
487 | { |
488 | return false; |
489 | } |
490 | |
491 | try |
492 | { |
493 | $this->filesystem->phpbb_chmod($this->destination_file, $chmod); |
494 | } |
495 | catch (\phpbb\filesystem\exception\filesystem_exception $e) |
496 | { |
497 | // Do nothing |
498 | } |
499 | } |
500 | |
501 | // Try to get real filesize from destination folder |
502 | $this->filesize = ($this->get_filesize($this->destination_file)) ?: $this->filesize; |
503 | |
504 | // Get mimetype of supplied file |
505 | $this->mimetype = $this->get_mimetype($this->destination_file); |
506 | |
507 | if ($this->is_image() && !$skip_image_check) |
508 | { |
509 | $this->width = $this->height = 0; |
510 | |
511 | $this->image_info = $this->imagesize->getImageSize($this->destination_file, $this->mimetype); |
512 | |
513 | if ($this->image_info !== false) |
514 | { |
515 | $this->width = $this->image_info['width']; |
516 | $this->height = $this->image_info['height']; |
517 | |
518 | // Check image type |
519 | $types = upload::image_types(); |
520 | |
521 | if (!isset($types[$this->image_info['type']]) || !in_array($this->extension, $types[$this->image_info['type']])) |
522 | { |
523 | if (!isset($types[$this->image_info['type']])) |
524 | { |
525 | $this->error[] = $this->language->lang('IMAGE_FILETYPE_INVALID', $this->image_info['type'], $this->mimetype); |
526 | } |
527 | else |
528 | { |
529 | $this->error[] = $this->language->lang('IMAGE_FILETYPE_MISMATCH', $types[$this->image_info['type']][0], $this->extension); |
530 | } |
531 | } |
532 | |
533 | // Make sure the dimensions match a valid image |
534 | if (empty($this->width) || empty($this->height)) |
535 | { |
536 | $this->error[] = $this->language->lang('ATTACHED_IMAGE_NOT_IMAGE'); |
537 | } |
538 | } |
539 | else |
540 | { |
541 | $this->error[] = $this->language->lang('UNABLE_GET_IMAGE_SIZE'); |
542 | } |
543 | } |
544 | |
545 | $this->file_moved = true; |
546 | $this->additional_checks(); |
547 | unset($this->upload); |
548 | |
549 | return true; |
550 | } |
551 | |
552 | /** |
553 | * Performing additional checks |
554 | * |
555 | * @return bool False if issue was found, true if not |
556 | */ |
557 | public function additional_checks() |
558 | { |
559 | if (!$this->file_moved) |
560 | { |
561 | return false; |
562 | } |
563 | |
564 | // Filesize is too big or it's 0 if it was larger than the maxsize in the upload form |
565 | if ($this->upload->max_filesize && ($this->get('filesize') > $this->upload->max_filesize || $this->filesize == 0)) |
566 | { |
567 | $max_filesize = get_formatted_filesize($this->upload->max_filesize, false); |
568 | |
569 | $this->error[] = $this->language->lang($this->upload->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']); |
570 | |
571 | return false; |
572 | } |
573 | |
574 | if (!$this->upload->valid_dimensions($this)) |
575 | { |
576 | $this->error[] = $this->language->lang($this->upload->error_prefix . 'WRONG_SIZE', |
577 | $this->language->lang('PIXELS', (int) $this->upload->min_width), |
578 | $this->language->lang('PIXELS', (int) $this->upload->min_height), |
579 | $this->language->lang('PIXELS', (int) $this->upload->max_width), |
580 | $this->language->lang('PIXELS', (int) $this->upload->max_height), |
581 | $this->language->lang('PIXELS', (int) $this->width), |
582 | $this->language->lang('PIXELS', (int) $this->height)); |
583 | |
584 | return false; |
585 | } |
586 | |
587 | return true; |
588 | } |
589 | } |