Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.02% |
99 / 101 |
|
93.33% |
14 / 15 |
CRAP | |
0.00% |
0 / 1 |
upload | |
98.02% |
99 / 101 |
|
93.33% |
14 / 15 |
53 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
reset_vars | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
set_allowed_extensions | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
set_allowed_dimensions | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
set_max_filesize | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
set_disallowed_content | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
set_error_prefix | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
handle_upload | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
assign_internal_error | |
100.00% |
29 / 29 |
|
100.00% |
1 / 1 |
13 | |||
common_checks | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
7 | |||
valid_extension | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
valid_dimensions | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
13 | |||
is_valid | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
valid_content | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
image_types | |
100.00% |
21 / 21 |
|
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 | |
14 | namespace phpbb\files; |
15 | |
16 | use phpbb\language\language; |
17 | use 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 | */ |
23 | class 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 | } |