Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.02% covered (success)
98.02%
99 / 101
93.33% covered (success)
93.33%
14 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
upload
98.02% covered (success)
98.02%
99 / 101
93.33% covered (success)
93.33%
14 / 15
53
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 reset_vars
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 set_allowed_extensions
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 set_allowed_dimensions
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 set_max_filesize
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 set_disallowed_content
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 set_error_prefix
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 handle_upload
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 assign_internal_error
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
13
 common_checks
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
7
 valid_extension
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 valid_dimensions
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
13
 is_valid
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 valid_content
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 image_types
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
2
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
14namespace phpbb\files;
15
16use phpbb\language\language;
17use phpbb\request\request;
18
19/**
20 * File upload class
21 * Init class (all parameters optional and able to be set/overwritten separately) - scope is global and valid for all uploads
22 */
23class upload
24{
25    /** @var array Allowed file extensions */
26    public $allowed_extensions = array();
27
28    /** @var array Disallowed content */
29    protected $disallowed_content = array('body', 'head', 'html', 'img', 'plaintext', 'a href', 'pre', 'script', 'table', 'title');
30
31    /** @var int Maximum filesize */
32    public $max_filesize = 0;
33
34    /** @var int Minimum width of images */
35    public $min_width = 0;
36
37    /** @var int Minimum height of images */
38    public $min_height = 0;
39
40    /** @var int Maximum width of images */
41    public $max_width = 0;
42
43    /** @var int Maximum height of images */
44    public $max_height = 0;
45
46    /** @var string Prefix for language variables of errors */
47    public $error_prefix = '';
48
49    /** @var \phpbb\files\factory Files factory */
50    protected $factory;
51
52    /** @var \bantu\IniGetWrapper\IniGetWrapper ini_get() wrapper */
53    protected $php_ini;
54
55    /** @var language Language class */
56    protected $language;
57
58    /** @var request Request class */
59    protected $request;
60
61    /**
62     * Init file upload class.
63     *
64     * @param factory $factory Files factory
65     * @param language $language Language class
66     * @param \bantu\IniGetWrapper\IniGetWrapper $php_ini ini_get() wrapper
67     * @param request $request Request class
68     */
69    public function __construct(factory $factory, language $language, \bantu\IniGetWrapper\IniGetWrapper $php_ini, request $request)
70    {
71        $this->factory = $factory;
72        $this->language = $language;
73        $this->php_ini = $php_ini;
74        $this->request = $request;
75    }
76
77    /**
78     * Reset vars
79     */
80    public function reset_vars()
81    {
82        $this->max_filesize = 0;
83        $this->min_width = $this->min_height = $this->max_width = $this->max_height = 0;
84        $this->error_prefix = '';
85        $this->allowed_extensions = array();
86        $this->disallowed_content = array();
87    }
88
89    /**
90     * Set allowed extensions
91     *
92     * @param array|bool $allowed_extensions Allowed file extensions
93     *
94     * @return \phpbb\files\upload This instance of upload
95     */
96    public function set_allowed_extensions($allowed_extensions)
97    {
98        if ($allowed_extensions !== false)
99        {
100            $this->allowed_extensions = $allowed_extensions;
101        }
102
103        return $this;
104    }
105
106    /**
107     * Set allowed dimensions
108     *
109     * @param int $min_width Minimum image width
110     * @param int $min_height Minimum image height
111     * @param int $max_width Maximum image width
112     * @param int $max_height Maximum image height
113     *
114     * @return \phpbb\files\upload This instance of upload
115     */
116    public function set_allowed_dimensions($min_width, $min_height, $max_width, $max_height)
117    {
118        $this->min_width = (int) $min_width;
119        $this->min_height = (int) $min_height;
120        $this->max_width = (int) $max_width;
121        $this->max_height = (int) $max_height;
122
123        return $this;
124    }
125
126    /**
127     * Set maximum allowed file size
128     *
129     * @param int $max_filesize Maximum file size
130     *
131     * @return \phpbb\files\upload This instance of upload
132     */
133    public function set_max_filesize($max_filesize)
134    {
135        if ($max_filesize !== false && (int) $max_filesize)
136        {
137            $this->max_filesize = (int) $max_filesize;
138        }
139
140        return $this;
141    }
142
143    /**
144     * Set disallowed strings
145     *
146     * @param array|bool $disallowed_content Disallowed content
147     *
148     * @return \phpbb\files\upload This instance of upload
149     */
150    public function set_disallowed_content($disallowed_content)
151    {
152        if ($disallowed_content !== false)
153        {
154            $this->disallowed_content = array_diff($disallowed_content, array(''));
155        }
156
157        return $this;
158    }
159
160    /**
161     * Set error prefix
162     *
163     * @param string $error_prefix Prefix for language variables of errors
164     *
165     * @return \phpbb\files\upload This instance of upload
166     */
167    public function set_error_prefix($error_prefix)
168    {
169        $this->error_prefix = $error_prefix;
170
171        return $this;
172    }
173
174    /**
175     * Handle upload based on type
176     *
177     * @param string $type Upload type
178     *
179     * @return \phpbb\files\filespec_storage|bool A filespec instance if upload was
180     *        successful, false if there were issues or the type is not supported
181     */
182    public function handle_upload($type)
183    {
184        $args = func_get_args();
185        array_shift($args);
186        $type_class = $this->factory->get($type)
187            ->set_upload($this);
188
189        return (is_object($type_class)) ? call_user_func_array(array($type_class, 'upload'), $args) : false;
190    }
191
192    /**
193     * Assign internal error
194     *
195     * @param string $errorcode Error code to assign
196     *
197     * @return string|false Error string or false if error code is not supported
198     * @access public
199     */
200    public function assign_internal_error($errorcode)
201    {
202        switch ($errorcode)
203        {
204            case UPLOAD_ERR_INI_SIZE:
205                $max_filesize = $this->php_ini->getString('upload_max_filesize');
206                $unit = 'MB';
207
208                if (!empty($max_filesize))
209                {
210                    $unit = strtolower(substr($max_filesize, -1, 1));
211                    $max_filesize = (int) $max_filesize;
212
213                    $unit = ($unit == 'k') ? 'KB' : (($unit == 'g') ? 'GB' : 'MB');
214                }
215
216                $error = (empty($max_filesize)) ? $this->language->lang($this->error_prefix . 'PHP_SIZE_NA') : $this->language->lang($this->error_prefix . 'PHP_SIZE_OVERRUN', $max_filesize, $this->language->lang($unit));
217            break;
218
219            case UPLOAD_ERR_FORM_SIZE:
220                $max_filesize = get_formatted_filesize($this->max_filesize, false);
221
222                $error = $this->language->lang($this->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']);
223            break;
224
225            case UPLOAD_ERR_PARTIAL:
226                $error = $this->language->lang($this->error_prefix . 'PARTIAL_UPLOAD');
227            break;
228
229            case UPLOAD_ERR_NO_FILE:
230                $error = $this->language->lang($this->error_prefix . 'NOT_UPLOADED');
231            break;
232
233            case UPLOAD_ERR_NO_TMP_DIR:
234            case UPLOAD_ERR_CANT_WRITE:
235                $error = $this->language->lang($this->error_prefix . 'NO_TEMP_DIR');
236            break;
237
238            case UPLOAD_ERR_EXTENSION:
239                $error = $this->language->lang($this->error_prefix . 'PHP_UPLOAD_STOPPED');
240            break;
241
242            default:
243                $error = false;
244            break;
245        }
246
247        return $error;
248    }
249
250    /**
251     * Perform common file checks
252     *
253     * @param filespec_storage|filespec $file Instance of filespec class
254     */
255    public function common_checks($file)
256    {
257        // Filesize is too big or it's 0 if it was larger than the maxsize in the upload form
258        if ($this->max_filesize && ($file->get('filesize') > $this->max_filesize || $file->get('filesize') == 0))
259        {
260            $max_filesize = get_formatted_filesize($this->max_filesize, false);
261
262            $file->error[] = $this->language->lang($this->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']);
263        }
264
265        // check Filename
266        if (preg_match("#[\\/:*?\"<>|]#i", $file->get('realname')))
267        {
268            $file->error[] = $this->language->lang($this->error_prefix . 'INVALID_FILENAME', $file->get('realname'));
269        }
270
271        // Invalid Extension
272        if (!$this->valid_extension($file))
273        {
274            $file->error[] = $this->language->lang($this->error_prefix . 'DISALLOWED_EXTENSION', $file->get('extension'));
275        }
276
277        // MIME Sniffing
278        if (!$this->valid_content($file))
279        {
280            $file->error[] = $this->language->lang($this->error_prefix . 'DISALLOWED_CONTENT');
281        }
282    }
283
284    /**
285     * Check for allowed extension
286     *
287     * @param filespec_storage $file Instance of filespec class
288     *
289     * @return bool True if extension is allowed, false if not
290     */
291    public function valid_extension($file)
292    {
293        return (in_array($file->get('extension'), $this->allowed_extensions)) ? true : false;
294    }
295
296    /**
297     * Check for allowed dimension
298     *
299     * @param filespec_storage|filespec $file Instance of filespec class
300     *
301     * @return bool True if dimensions are valid or no constraints set, false
302     *            if not
303     */
304    public function valid_dimensions($file)
305    {
306        if (!$this->max_width && !$this->max_height && !$this->min_width && !$this->min_height)
307        {
308            return true;
309        }
310
311        if (($file->get('width') > $this->max_width && $this->max_width) ||
312            ($file->get('height') > $this->max_height && $this->max_height) ||
313            ($file->get('width') < $this->min_width && $this->min_width) ||
314            ($file->get('height') < $this->min_height && $this->min_height))
315        {
316            return false;
317        }
318
319        return true;
320    }
321
322    /**
323     * Check if form upload is valid
324     *
325     * @param string $form_name Name of form
326     *
327     * @return bool True if form upload is valid, false if not
328     */
329    public function is_valid($form_name)
330    {
331        $upload = $this->request->file($form_name);
332
333        return (!empty($upload) && $upload['name'] !== 'none');
334    }
335
336
337    /**
338     * Check for bad content (IE mime-sniffing)
339     *
340     * @param filespec_storage $file Instance of filespec class
341     *
342     * @return bool True if content is valid, false if not
343     */
344    public function valid_content($file)
345    {
346        return ($file->check_content($this->disallowed_content));
347    }
348
349    /**
350     * Get image type/extension mapping
351     *
352     * @return array Array containing the image types and their extensions
353     */
354    public static function image_types()
355    {
356        $result = [
357            IMAGETYPE_GIF        => ['gif'],
358            IMAGETYPE_JPEG        => ['jpg', 'jpeg'],
359            IMAGETYPE_PNG        => ['png'],
360            IMAGETYPE_SWF        => ['swf'],
361            IMAGETYPE_PSD        => ['psd'],
362            IMAGETYPE_BMP        => ['bmp'],
363            IMAGETYPE_TIFF_II    => ['tif', 'tiff'],
364            IMAGETYPE_TIFF_MM    => ['tif', 'tiff'],
365            IMAGETYPE_JPC        => ['jpg', 'jpeg'],
366            IMAGETYPE_JP2        => ['jpg', 'jpeg'],
367            IMAGETYPE_JPX        => ['jpg', 'jpeg'],
368            IMAGETYPE_JB2        => ['jpg', 'jpeg'],
369            IMAGETYPE_IFF        => ['iff'],
370            IMAGETYPE_WBMP        => ['wbmp'],
371            IMAGETYPE_XBM        => ['xbm'],
372            IMAGETYPE_WEBP        => ['webp'],
373        ];
374
375        if (defined('IMAGETYPE_SWC'))
376        {
377            $result[IMAGETYPE_SWC] = ['swc'];
378        }
379
380        return $result;
381    }
382}