]>
Commit | Line | Data |
---|---|---|
59c06b17 CS |
1 | <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); |
2 | /** | |
3 | * CodeIgniter | |
4 | * | |
5 | * An open source application development framework for PHP 5.1.6 or newer | |
6 | * | |
7 | * @package CodeIgniter | |
8 | * @author ExpressionEngine Dev Team | |
9 | * @copyright Copyright (c) 2008 - 2011, EllisLab, Inc. | |
10 | * @license http://codeigniter.com/user_guide/license.html | |
11 | * @link http://codeigniter.com | |
12 | * @since Version 1.0 | |
13 | * @filesource | |
14 | */ | |
15 | ||
16 | // ------------------------------------------------------------------------ | |
17 | ||
18 | /** | |
19 | * File Uploading Class | |
20 | * | |
21 | * @package CodeIgniter | |
22 | * @subpackage Libraries | |
23 | * @category Uploads | |
24 | * @author ExpressionEngine Dev Team | |
25 | * @link http://codeigniter.com/user_guide/libraries/file_uploading.html | |
26 | */ | |
27 | class CI_Upload { | |
28 | ||
29 | public $max_size = 0; | |
30 | public $max_width = 0; | |
31 | public $max_height = 0; | |
32 | public $max_filename = 0; | |
33 | public $allowed_types = ""; | |
34 | public $file_temp = ""; | |
35 | public $file_name = ""; | |
36 | public $orig_name = ""; | |
37 | public $file_type = ""; | |
38 | public $file_size = ""; | |
39 | public $file_ext = ""; | |
40 | public $upload_path = ""; | |
41 | public $overwrite = FALSE; | |
42 | public $encrypt_name = FALSE; | |
43 | public $is_image = FALSE; | |
44 | public $image_width = ''; | |
45 | public $image_height = ''; | |
46 | public $image_type = ''; | |
47 | public $image_size_str = ''; | |
48 | public $error_msg = array(); | |
49 | public $mimes = array(); | |
50 | public $remove_spaces = TRUE; | |
51 | public $xss_clean = FALSE; | |
52 | public $temp_prefix = "temp_file_"; | |
53 | public $client_name = ''; | |
54 | ||
55 | protected $_file_name_override = ''; | |
56 | ||
57 | /** | |
58 | * Constructor | |
59 | * | |
60 | * @access public | |
61 | */ | |
62 | public function __construct($props = array()) | |
63 | { | |
64 | if (count($props) > 0) | |
65 | { | |
66 | $this->initialize($props); | |
67 | } | |
68 | ||
69 | log_message('debug', "Upload Class Initialized"); | |
70 | } | |
71 | ||
72 | // -------------------------------------------------------------------- | |
73 | ||
74 | /** | |
75 | * Initialize preferences | |
76 | * | |
77 | * @param array | |
78 | * @return void | |
79 | */ | |
80 | public function initialize($config = array()) | |
81 | { | |
82 | $defaults = array( | |
83 | 'max_size' => 0, | |
84 | 'max_width' => 0, | |
85 | 'max_height' => 0, | |
86 | 'max_filename' => 0, | |
87 | 'allowed_types' => "", | |
88 | 'file_temp' => "", | |
89 | 'file_name' => "", | |
90 | 'orig_name' => "", | |
91 | 'file_type' => "", | |
92 | 'file_size' => "", | |
93 | 'file_ext' => "", | |
94 | 'upload_path' => "", | |
95 | 'overwrite' => FALSE, | |
96 | 'encrypt_name' => FALSE, | |
97 | 'is_image' => FALSE, | |
98 | 'image_width' => '', | |
99 | 'image_height' => '', | |
100 | 'image_type' => '', | |
101 | 'image_size_str' => '', | |
102 | 'error_msg' => array(), | |
103 | 'mimes' => array(), | |
104 | 'remove_spaces' => TRUE, | |
105 | 'xss_clean' => FALSE, | |
106 | 'temp_prefix' => "temp_file_", | |
107 | 'client_name' => '' | |
108 | ); | |
109 | ||
110 | ||
111 | foreach ($defaults as $key => $val) | |
112 | { | |
113 | if (isset($config[$key])) | |
114 | { | |
115 | $method = 'set_'.$key; | |
116 | if (method_exists($this, $method)) | |
117 | { | |
118 | $this->$method($config[$key]); | |
119 | } | |
120 | else | |
121 | { | |
122 | $this->$key = $config[$key]; | |
123 | } | |
124 | } | |
125 | else | |
126 | { | |
127 | $this->$key = $val; | |
128 | } | |
129 | } | |
130 | ||
131 | // if a file_name was provided in the config, use it instead of the user input | |
132 | // supplied file name for all uploads until initialized again | |
133 | $this->_file_name_override = $this->file_name; | |
134 | } | |
135 | ||
136 | // -------------------------------------------------------------------- | |
137 | ||
138 | /** | |
139 | * Perform the file upload | |
140 | * | |
141 | * @return bool | |
142 | */ | |
143 | public function do_upload($field = 'userfile') | |
144 | { | |
145 | ||
146 | // Is $_FILES[$field] set? If not, no reason to continue. | |
147 | if ( ! isset($_FILES[$field])) | |
148 | { | |
149 | $this->set_error('upload_no_file_selected'); | |
150 | return FALSE; | |
151 | } | |
152 | ||
153 | // Is the upload path valid? | |
154 | if ( ! $this->validate_upload_path()) | |
155 | { | |
156 | // errors will already be set by validate_upload_path() so just return FALSE | |
157 | return FALSE; | |
158 | } | |
159 | ||
160 | // Was the file able to be uploaded? If not, determine the reason why. | |
161 | if ( ! is_uploaded_file($_FILES[$field]['tmp_name'])) | |
162 | { | |
163 | $error = ( ! isset($_FILES[$field]['error'])) ? 4 : $_FILES[$field]['error']; | |
164 | ||
165 | switch($error) | |
166 | { | |
167 | case 1: // UPLOAD_ERR_INI_SIZE | |
168 | $this->set_error('upload_file_exceeds_limit'); | |
169 | break; | |
170 | case 2: // UPLOAD_ERR_FORM_SIZE | |
171 | $this->set_error('upload_file_exceeds_form_limit'); | |
172 | break; | |
173 | case 3: // UPLOAD_ERR_PARTIAL | |
174 | $this->set_error('upload_file_partial'); | |
175 | break; | |
176 | case 4: // UPLOAD_ERR_NO_FILE | |
177 | $this->set_error('upload_no_file_selected'); | |
178 | break; | |
179 | case 6: // UPLOAD_ERR_NO_TMP_DIR | |
180 | $this->set_error('upload_no_temp_directory'); | |
181 | break; | |
182 | case 7: // UPLOAD_ERR_CANT_WRITE | |
183 | $this->set_error('upload_unable_to_write_file'); | |
184 | break; | |
185 | case 8: // UPLOAD_ERR_EXTENSION | |
186 | $this->set_error('upload_stopped_by_extension'); | |
187 | break; | |
188 | default : $this->set_error('upload_no_file_selected'); | |
189 | break; | |
190 | } | |
191 | ||
192 | return FALSE; | |
193 | } | |
194 | ||
195 | ||
196 | // Set the uploaded data as class variables | |
197 | $this->file_temp = $_FILES[$field]['tmp_name']; | |
198 | $this->file_size = $_FILES[$field]['size']; | |
199 | $this->_file_mime_type($_FILES[$field]); | |
200 | $this->file_type = preg_replace("/^(.+?);.*$/", "\\1", $this->file_type); | |
201 | $this->file_type = strtolower(trim(stripslashes($this->file_type), '"')); | |
202 | $this->file_name = $this->_prep_filename($_FILES[$field]['name']); | |
203 | $this->file_ext = $this->get_extension($this->file_name); | |
204 | $this->client_name = $this->file_name; | |
205 | ||
206 | // Is the file type allowed to be uploaded? | |
207 | if ( ! $this->is_allowed_filetype()) | |
208 | { | |
209 | $this->set_error('upload_invalid_filetype'); | |
210 | return FALSE; | |
211 | } | |
212 | ||
213 | // if we're overriding, let's now make sure the new name and type is allowed | |
214 | if ($this->_file_name_override != '') | |
215 | { | |
216 | $this->file_name = $this->_prep_filename($this->_file_name_override); | |
217 | ||
218 | // If no extension was provided in the file_name config item, use the uploaded one | |
219 | if (strpos($this->_file_name_override, '.') === FALSE) | |
220 | { | |
221 | $this->file_name .= $this->file_ext; | |
222 | } | |
223 | ||
224 | // An extension was provided, lets have it! | |
225 | else | |
226 | { | |
227 | $this->file_ext = $this->get_extension($this->_file_name_override); | |
228 | } | |
229 | ||
230 | if ( ! $this->is_allowed_filetype(TRUE)) | |
231 | { | |
232 | $this->set_error('upload_invalid_filetype'); | |
233 | return FALSE; | |
234 | } | |
235 | } | |
236 | ||
237 | // Convert the file size to kilobytes | |
238 | if ($this->file_size > 0) | |
239 | { | |
240 | $this->file_size = round($this->file_size/1024, 2); | |
241 | } | |
242 | ||
243 | // Is the file size within the allowed maximum? | |
244 | if ( ! $this->is_allowed_filesize()) | |
245 | { | |
246 | $this->set_error('upload_invalid_filesize'); | |
247 | return FALSE; | |
248 | } | |
249 | ||
250 | // Are the image dimensions within the allowed size? | |
251 | // Note: This can fail if the server has an open_basdir restriction. | |
252 | if ( ! $this->is_allowed_dimensions()) | |
253 | { | |
254 | $this->set_error('upload_invalid_dimensions'); | |
255 | return FALSE; | |
256 | } | |
257 | ||
258 | // Sanitize the file name for security | |
259 | $this->file_name = $this->clean_file_name($this->file_name); | |
260 | ||
261 | // Truncate the file name if it's too long | |
262 | if ($this->max_filename > 0) | |
263 | { | |
264 | $this->file_name = $this->limit_filename_length($this->file_name, $this->max_filename); | |
265 | } | |
266 | ||
267 | // Remove white spaces in the name | |
268 | if ($this->remove_spaces == TRUE) | |
269 | { | |
270 | $this->file_name = preg_replace("/\s+/", "_", $this->file_name); | |
271 | } | |
272 | ||
273 | /* | |
274 | * Validate the file name | |
275 | * This function appends an number onto the end of | |
276 | * the file if one with the same name already exists. | |
277 | * If it returns false there was a problem. | |
278 | */ | |
279 | $this->orig_name = $this->file_name; | |
280 | ||
281 | if ($this->overwrite == FALSE) | |
282 | { | |
283 | $this->file_name = $this->set_filename($this->upload_path, $this->file_name); | |
284 | ||
285 | if ($this->file_name === FALSE) | |
286 | { | |
287 | return FALSE; | |
288 | } | |
289 | } | |
290 | ||
291 | /* | |
292 | * Run the file through the XSS hacking filter | |
293 | * This helps prevent malicious code from being | |
294 | * embedded within a file. Scripts can easily | |
295 | * be disguised as images or other file types. | |
296 | */ | |
297 | if ($this->xss_clean) | |
298 | { | |
299 | if ($this->do_xss_clean() === FALSE) | |
300 | { | |
301 | $this->set_error('upload_unable_to_write_file'); | |
302 | return FALSE; | |
303 | } | |
304 | } | |
305 | ||
306 | /* | |
307 | * Move the file to the final destination | |
308 | * To deal with different server configurations | |
309 | * we'll attempt to use copy() first. If that fails | |
310 | * we'll use move_uploaded_file(). One of the two should | |
311 | * reliably work in most environments | |
312 | */ | |
313 | if ( ! @copy($this->file_temp, $this->upload_path.$this->file_name)) | |
314 | { | |
315 | if ( ! @move_uploaded_file($this->file_temp, $this->upload_path.$this->file_name)) | |
316 | { | |
317 | $this->set_error('upload_destination_error'); | |
318 | return FALSE; | |
319 | } | |
320 | } | |
321 | ||
322 | /* | |
323 | * Set the finalized image dimensions | |
324 | * This sets the image width/height (assuming the | |
325 | * file was an image). We use this information | |
326 | * in the "data" function. | |
327 | */ | |
328 | $this->set_image_properties($this->upload_path.$this->file_name); | |
329 | ||
330 | return TRUE; | |
331 | } | |
332 | ||
333 | // -------------------------------------------------------------------- | |
334 | ||
335 | /** | |
336 | * Finalized Data Array | |
337 | * | |
338 | * Returns an associative array containing all of the information | |
339 | * related to the upload, allowing the developer easy access in one array. | |
340 | * | |
341 | * @return array | |
342 | */ | |
343 | public function data() | |
344 | { | |
345 | return array ( | |
346 | 'file_name' => $this->file_name, | |
347 | 'file_type' => $this->file_type, | |
348 | 'file_path' => $this->upload_path, | |
349 | 'full_path' => $this->upload_path.$this->file_name, | |
350 | 'raw_name' => str_replace($this->file_ext, '', $this->file_name), | |
351 | 'orig_name' => $this->orig_name, | |
352 | 'client_name' => $this->client_name, | |
353 | 'file_ext' => $this->file_ext, | |
354 | 'file_size' => $this->file_size, | |
355 | 'is_image' => $this->is_image(), | |
356 | 'image_width' => $this->image_width, | |
357 | 'image_height' => $this->image_height, | |
358 | 'image_type' => $this->image_type, | |
359 | 'image_size_str' => $this->image_size_str, | |
360 | ); | |
361 | } | |
362 | ||
363 | // -------------------------------------------------------------------- | |
364 | ||
365 | /** | |
366 | * Set Upload Path | |
367 | * | |
368 | * @param string | |
369 | * @return void | |
370 | */ | |
371 | public function set_upload_path($path) | |
372 | { | |
373 | // Make sure it has a trailing slash | |
374 | $this->upload_path = rtrim($path, '/').'/'; | |
375 | } | |
376 | ||
377 | // -------------------------------------------------------------------- | |
378 | ||
379 | /** | |
380 | * Set the file name | |
381 | * | |
382 | * This function takes a filename/path as input and looks for the | |
383 | * existence of a file with the same name. If found, it will append a | |
384 | * number to the end of the filename to avoid overwriting a pre-existing file. | |
385 | * | |
386 | * @param string | |
387 | * @param string | |
388 | * @return string | |
389 | */ | |
390 | public function set_filename($path, $filename) | |
391 | { | |
392 | if ($this->encrypt_name == TRUE) | |
393 | { | |
394 | mt_srand(); | |
395 | $filename = md5(uniqid(mt_rand())).$this->file_ext; | |
396 | } | |
397 | ||
398 | if ( ! file_exists($path.$filename)) | |
399 | { | |
400 | return $filename; | |
401 | } | |
402 | ||
403 | $filename = str_replace($this->file_ext, '', $filename); | |
404 | ||
405 | $new_filename = ''; | |
406 | for ($i = 1; $i < 100; $i++) | |
407 | { | |
408 | if ( ! file_exists($path.$filename.$i.$this->file_ext)) | |
409 | { | |
410 | $new_filename = $filename.$i.$this->file_ext; | |
411 | break; | |
412 | } | |
413 | } | |
414 | ||
415 | if ($new_filename == '') | |
416 | { | |
417 | $this->set_error('upload_bad_filename'); | |
418 | return FALSE; | |
419 | } | |
420 | else | |
421 | { | |
422 | return $new_filename; | |
423 | } | |
424 | } | |
425 | ||
426 | // -------------------------------------------------------------------- | |
427 | ||
428 | /** | |
429 | * Set Maximum File Size | |
430 | * | |
431 | * @param integer | |
432 | * @return void | |
433 | */ | |
434 | public function set_max_filesize($n) | |
435 | { | |
436 | $this->max_size = ((int) $n < 0) ? 0: (int) $n; | |
437 | } | |
438 | ||
439 | // -------------------------------------------------------------------- | |
440 | ||
441 | /** | |
442 | * Set Maximum File Name Length | |
443 | * | |
444 | * @param integer | |
445 | * @return void | |
446 | */ | |
447 | public function set_max_filename($n) | |
448 | { | |
449 | $this->max_filename = ((int) $n < 0) ? 0: (int) $n; | |
450 | } | |
451 | ||
452 | // -------------------------------------------------------------------- | |
453 | ||
454 | /** | |
455 | * Set Maximum Image Width | |
456 | * | |
457 | * @param integer | |
458 | * @return void | |
459 | */ | |
460 | public function set_max_width($n) | |
461 | { | |
462 | $this->max_width = ((int) $n < 0) ? 0: (int) $n; | |
463 | } | |
464 | ||
465 | // -------------------------------------------------------------------- | |
466 | ||
467 | /** | |
468 | * Set Maximum Image Height | |
469 | * | |
470 | * @param integer | |
471 | * @return void | |
472 | */ | |
473 | public function set_max_height($n) | |
474 | { | |
475 | $this->max_height = ((int) $n < 0) ? 0: (int) $n; | |
476 | } | |
477 | ||
478 | // -------------------------------------------------------------------- | |
479 | ||
480 | /** | |
481 | * Set Allowed File Types | |
482 | * | |
483 | * @param string | |
484 | * @return void | |
485 | */ | |
486 | public function set_allowed_types($types) | |
487 | { | |
488 | if ( ! is_array($types) && $types == '*') | |
489 | { | |
490 | $this->allowed_types = '*'; | |
491 | return; | |
492 | } | |
493 | $this->allowed_types = explode('|', $types); | |
494 | } | |
495 | ||
496 | // -------------------------------------------------------------------- | |
497 | ||
498 | /** | |
499 | * Set Image Properties | |
500 | * | |
501 | * Uses GD to determine the width/height/type of image | |
502 | * | |
503 | * @param string | |
504 | * @return void | |
505 | */ | |
506 | public function set_image_properties($path = '') | |
507 | { | |
508 | if ( ! $this->is_image()) | |
509 | { | |
510 | return; | |
511 | } | |
512 | ||
513 | if (function_exists('getimagesize')) | |
514 | { | |
515 | if (FALSE !== ($D = @getimagesize($path))) | |
516 | { | |
517 | $types = array(1 => 'gif', 2 => 'jpeg', 3 => 'png'); | |
518 | ||
519 | $this->image_width = $D['0']; | |
520 | $this->image_height = $D['1']; | |
521 | $this->image_type = ( ! isset($types[$D['2']])) ? 'unknown' : $types[$D['2']]; | |
522 | $this->image_size_str = $D['3']; // string containing height and width | |
523 | } | |
524 | } | |
525 | } | |
526 | ||
527 | // -------------------------------------------------------------------- | |
528 | ||
529 | /** | |
530 | * Set XSS Clean | |
531 | * | |
532 | * Enables the XSS flag so that the file that was uploaded | |
533 | * will be run through the XSS filter. | |
534 | * | |
535 | * @param bool | |
536 | * @return void | |
537 | */ | |
538 | public function set_xss_clean($flag = FALSE) | |
539 | { | |
540 | $this->xss_clean = ($flag == TRUE) ? TRUE : FALSE; | |
541 | } | |
542 | ||
543 | // -------------------------------------------------------------------- | |
544 | ||
545 | /** | |
546 | * Validate the image | |
547 | * | |
548 | * @return bool | |
549 | */ | |
550 | public function is_image() | |
551 | { | |
552 | // IE will sometimes return odd mime-types during upload, so here we just standardize all | |
553 | // jpegs or pngs to the same file type. | |
554 | ||
555 | $png_mimes = array('image/x-png'); | |
556 | $jpeg_mimes = array('image/jpg', 'image/jpe', 'image/jpeg', 'image/pjpeg'); | |
557 | ||
558 | if (in_array($this->file_type, $png_mimes)) | |
559 | { | |
560 | $this->file_type = 'image/png'; | |
561 | } | |
562 | ||
563 | if (in_array($this->file_type, $jpeg_mimes)) | |
564 | { | |
565 | $this->file_type = 'image/jpeg'; | |
566 | } | |
567 | ||
568 | $img_mimes = array( | |
569 | 'image/gif', | |
570 | 'image/jpeg', | |
571 | 'image/png', | |
572 | ); | |
573 | ||
574 | return (in_array($this->file_type, $img_mimes, TRUE)) ? TRUE : FALSE; | |
575 | } | |
576 | ||
577 | // -------------------------------------------------------------------- | |
578 | ||
579 | /** | |
580 | * Verify that the filetype is allowed | |
581 | * | |
582 | * @return bool | |
583 | */ | |
584 | public function is_allowed_filetype($ignore_mime = FALSE) | |
585 | { | |
586 | if ($this->allowed_types == '*') | |
587 | { | |
588 | return TRUE; | |
589 | } | |
590 | ||
591 | if (count($this->allowed_types) == 0 OR ! is_array($this->allowed_types)) | |
592 | { | |
593 | $this->set_error('upload_no_file_types'); | |
594 | return FALSE; | |
595 | } | |
596 | ||
597 | $ext = strtolower(ltrim($this->file_ext, '.')); | |
598 | ||
599 | if ( ! in_array($ext, $this->allowed_types)) | |
600 | { | |
601 | return FALSE; | |
602 | } | |
603 | ||
604 | // Images get some additional checks | |
605 | $image_types = array('gif', 'jpg', 'jpeg', 'png', 'jpe'); | |
606 | ||
607 | if (in_array($ext, $image_types)) | |
608 | { | |
609 | if (getimagesize($this->file_temp) === FALSE) | |
610 | { | |
611 | return FALSE; | |
612 | } | |
613 | } | |
614 | ||
615 | if ($ignore_mime === TRUE) | |
616 | { | |
617 | return TRUE; | |
618 | } | |
619 | ||
620 | $mime = $this->mimes_types($ext); | |
621 | ||
622 | if (is_array($mime)) | |
623 | { | |
624 | if (in_array($this->file_type, $mime, TRUE)) | |
625 | { | |
626 | return TRUE; | |
627 | } | |
628 | } | |
629 | elseif ($mime == $this->file_type) | |
630 | { | |
631 | return TRUE; | |
632 | } | |
633 | ||
634 | return FALSE; | |
635 | } | |
636 | ||
637 | // -------------------------------------------------------------------- | |
638 | ||
639 | /** | |
640 | * Verify that the file is within the allowed size | |
641 | * | |
642 | * @return bool | |
643 | */ | |
644 | public function is_allowed_filesize() | |
645 | { | |
646 | if ($this->max_size != 0 AND $this->file_size > $this->max_size) | |
647 | { | |
648 | return FALSE; | |
649 | } | |
650 | else | |
651 | { | |
652 | return TRUE; | |
653 | } | |
654 | } | |
655 | ||
656 | // -------------------------------------------------------------------- | |
657 | ||
658 | /** | |
659 | * Verify that the image is within the allowed width/height | |
660 | * | |
661 | * @return bool | |
662 | */ | |
663 | public function is_allowed_dimensions() | |
664 | { | |
665 | if ( ! $this->is_image()) | |
666 | { | |
667 | return TRUE; | |
668 | } | |
669 | ||
670 | if (function_exists('getimagesize')) | |
671 | { | |
672 | $D = @getimagesize($this->file_temp); | |
673 | ||
674 | if ($this->max_width > 0 AND $D['0'] > $this->max_width) | |
675 | { | |
676 | return FALSE; | |
677 | } | |
678 | ||
679 | if ($this->max_height > 0 AND $D['1'] > $this->max_height) | |
680 | { | |
681 | return FALSE; | |
682 | } | |
683 | ||
684 | return TRUE; | |
685 | } | |
686 | ||
687 | return TRUE; | |
688 | } | |
689 | ||
690 | // -------------------------------------------------------------------- | |
691 | ||
692 | /** | |
693 | * Validate Upload Path | |
694 | * | |
695 | * Verifies that it is a valid upload path with proper permissions. | |
696 | * | |
697 | * | |
698 | * @return bool | |
699 | */ | |
700 | public function validate_upload_path() | |
701 | { | |
702 | if ($this->upload_path == '') | |
703 | { | |
704 | $this->set_error('upload_no_filepath'); | |
705 | return FALSE; | |
706 | } | |
707 | ||
708 | if (function_exists('realpath') AND @realpath($this->upload_path) !== FALSE) | |
709 | { | |
710 | $this->upload_path = str_replace("\\", "/", realpath($this->upload_path)); | |
711 | } | |
712 | ||
713 | if ( ! @is_dir($this->upload_path)) | |
714 | { | |
715 | $this->set_error('upload_no_filepath'); | |
716 | return FALSE; | |
717 | } | |
718 | ||
719 | if ( ! is_really_writable($this->upload_path)) | |
720 | { | |
721 | $this->set_error('upload_not_writable'); | |
722 | return FALSE; | |
723 | } | |
724 | ||
725 | $this->upload_path = preg_replace("/(.+?)\/*$/", "\\1/", $this->upload_path); | |
726 | return TRUE; | |
727 | } | |
728 | ||
729 | // -------------------------------------------------------------------- | |
730 | ||
731 | /** | |
732 | * Extract the file extension | |
733 | * | |
734 | * @param string | |
735 | * @return string | |
736 | */ | |
737 | public function get_extension($filename) | |
738 | { | |
739 | $x = explode('.', $filename); | |
740 | return '.'.end($x); | |
741 | } | |
742 | ||
743 | // -------------------------------------------------------------------- | |
744 | ||
745 | /** | |
746 | * Clean the file name for security | |
747 | * | |
748 | * @param string | |
749 | * @return string | |
750 | */ | |
751 | public function clean_file_name($filename) | |
752 | { | |
753 | $bad = array( | |
754 | "<!--", | |
755 | "-->", | |
756 | "'", | |
757 | "<", | |
758 | ">", | |
759 | '"', | |
760 | '&', | |
761 | '$', | |
762 | '=', | |
763 | ';', | |
764 | '?', | |
765 | '/', | |
766 | "%20", | |
767 | "%22", | |
768 | "%3c", // < | |
769 | "%253c", // < | |
770 | "%3e", // > | |
771 | "%0e", // > | |
772 | "%28", // ( | |
773 | "%29", // ) | |
774 | "%2528", // ( | |
775 | "%26", // & | |
776 | "%24", // $ | |
777 | "%3f", // ? | |
778 | "%3b", // ; | |
779 | "%3d" // = | |
780 | ); | |
781 | ||
782 | $filename = str_replace($bad, '', $filename); | |
783 | ||
784 | return stripslashes($filename); | |
785 | } | |
786 | ||
787 | // -------------------------------------------------------------------- | |
788 | ||
789 | /** | |
790 | * Limit the File Name Length | |
791 | * | |
792 | * @param string | |
793 | * @return string | |
794 | */ | |
795 | public function limit_filename_length($filename, $length) | |
796 | { | |
797 | if (strlen($filename) < $length) | |
798 | { | |
799 | return $filename; | |
800 | } | |
801 | ||
802 | $ext = ''; | |
803 | if (strpos($filename, '.') !== FALSE) | |
804 | { | |
805 | $parts = explode('.', $filename); | |
806 | $ext = '.'.array_pop($parts); | |
807 | $filename = implode('.', $parts); | |
808 | } | |
809 | ||
810 | return substr($filename, 0, ($length - strlen($ext))).$ext; | |
811 | } | |
812 | ||
813 | // -------------------------------------------------------------------- | |
814 | ||
815 | /** | |
816 | * Runs the file through the XSS clean function | |
817 | * | |
818 | * This prevents people from embedding malicious code in their files. | |
819 | * I'm not sure that it won't negatively affect certain files in unexpected ways, | |
820 | * but so far I haven't found that it causes trouble. | |
821 | * | |
822 | * @return void | |
823 | */ | |
824 | public function do_xss_clean() | |
825 | { | |
826 | $file = $this->file_temp; | |
827 | ||
828 | if (filesize($file) == 0) | |
829 | { | |
830 | return FALSE; | |
831 | } | |
832 | ||
833 | if (function_exists('memory_get_usage') && memory_get_usage() && ini_get('memory_limit') != '') | |
834 | { | |
835 | $current = ini_get('memory_limit') * 1024 * 1024; | |
836 | ||
837 | // There was a bug/behavioural change in PHP 5.2, where numbers over one million get output | |
838 | // into scientific notation. number_format() ensures this number is an integer | |
839 | // http://bugs.php.net/bug.php?id=43053 | |
840 | ||
841 | $new_memory = number_format(ceil(filesize($file) + $current), 0, '.', ''); | |
842 | ||
843 | ini_set('memory_limit', $new_memory); // When an integer is used, the value is measured in bytes. - PHP.net | |
844 | } | |
845 | ||
846 | // If the file being uploaded is an image, then we should have no problem with XSS attacks (in theory), but | |
847 | // IE can be fooled into mime-type detecting a malformed image as an html file, thus executing an XSS attack on anyone | |
848 | // using IE who looks at the image. It does this by inspecting the first 255 bytes of an image. To get around this | |
849 | // CI will itself look at the first 255 bytes of an image to determine its relative safety. This can save a lot of | |
850 | // processor power and time if it is actually a clean image, as it will be in nearly all instances _except_ an | |
851 | // attempted XSS attack. | |
852 | ||
853 | if (function_exists('getimagesize') && @getimagesize($file) !== FALSE) | |
854 | { | |
855 | if (($file = @fopen($file, 'rb')) === FALSE) // "b" to force binary | |
856 | { | |
857 | return FALSE; // Couldn't open the file, return FALSE | |
858 | } | |
859 | ||
860 | $opening_bytes = fread($file, 256); | |
861 | fclose($file); | |
862 | ||
863 | // These are known to throw IE into mime-type detection chaos | |
864 | // <a, <body, <head, <html, <img, <plaintext, <pre, <script, <table, <title | |
865 | // title is basically just in SVG, but we filter it anyhow | |
866 | ||
867 | if ( ! preg_match('/<(a|body|head|html|img|plaintext|pre|script|table|title)[\s>]/i', $opening_bytes)) | |
868 | { | |
869 | return TRUE; // its an image, no "triggers" detected in the first 256 bytes, we're good | |
870 | } | |
871 | else | |
872 | { | |
873 | return FALSE; | |
874 | } | |
875 | } | |
876 | ||
877 | if (($data = @file_get_contents($file)) === FALSE) | |
878 | { | |
879 | return FALSE; | |
880 | } | |
881 | ||
882 | $CI =& get_instance(); | |
883 | return $CI->security->xss_clean($data, TRUE); | |
884 | } | |
885 | ||
886 | // -------------------------------------------------------------------- | |
887 | ||
888 | /** | |
889 | * Set an error message | |
890 | * | |
891 | * @param string | |
892 | * @return void | |
893 | */ | |
894 | public function set_error($msg) | |
895 | { | |
896 | $CI =& get_instance(); | |
897 | $CI->lang->load('upload'); | |
898 | ||
899 | if (is_array($msg)) | |
900 | { | |
901 | foreach ($msg as $val) | |
902 | { | |
903 | $msg = ($CI->lang->line($val) == FALSE) ? $val : $CI->lang->line($val); | |
904 | $this->error_msg[] = $msg; | |
905 | log_message('error', $msg); | |
906 | } | |
907 | } | |
908 | else | |
909 | { | |
910 | $msg = ($CI->lang->line($msg) == FALSE) ? $msg : $CI->lang->line($msg); | |
911 | $this->error_msg[] = $msg; | |
912 | log_message('error', $msg); | |
913 | } | |
914 | } | |
915 | ||
916 | // -------------------------------------------------------------------- | |
917 | ||
918 | /** | |
919 | * Display the error message | |
920 | * | |
921 | * @param string | |
922 | * @param string | |
923 | * @return string | |
924 | */ | |
925 | public function display_errors($open = '<p>', $close = '</p>') | |
926 | { | |
927 | $str = ''; | |
928 | foreach ($this->error_msg as $val) | |
929 | { | |
930 | $str .= $open.$val.$close; | |
931 | } | |
932 | ||
933 | return $str; | |
934 | } | |
935 | ||
936 | // -------------------------------------------------------------------- | |
937 | ||
938 | /** | |
939 | * List of Mime Types | |
940 | * | |
941 | * This is a list of mime types. We use it to validate | |
942 | * the "allowed types" set by the developer | |
943 | * | |
944 | * @param string | |
945 | * @return string | |
946 | */ | |
947 | public function mimes_types($mime) | |
948 | { | |
949 | global $mimes; | |
950 | ||
951 | if (count($this->mimes) == 0) | |
952 | { | |
953 | if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')) | |
954 | { | |
955 | include(APPPATH.'config/'.ENVIRONMENT.'/mimes.php'); | |
956 | } | |
957 | elseif (is_file(APPPATH.'config/mimes.php')) | |
958 | { | |
959 | include(APPPATH.'config//mimes.php'); | |
960 | } | |
961 | else | |
962 | { | |
963 | return FALSE; | |
964 | } | |
965 | ||
966 | $this->mimes = $mimes; | |
967 | unset($mimes); | |
968 | } | |
969 | ||
970 | return ( ! isset($this->mimes[$mime])) ? FALSE : $this->mimes[$mime]; | |
971 | } | |
972 | ||
973 | // -------------------------------------------------------------------- | |
974 | ||
975 | /** | |
976 | * Prep Filename | |
977 | * | |
978 | * Prevents possible script execution from Apache's handling of files multiple extensions | |
979 | * http://httpd.apache.org/docs/1.3/mod/mod_mime.html#multipleext | |
980 | * | |
981 | * @param string | |
982 | * @return string | |
983 | */ | |
984 | protected function _prep_filename($filename) | |
985 | { | |
986 | if (strpos($filename, '.') === FALSE OR $this->allowed_types == '*') | |
987 | { | |
988 | return $filename; | |
989 | } | |
990 | ||
991 | $parts = explode('.', $filename); | |
992 | $ext = array_pop($parts); | |
993 | $filename = array_shift($parts); | |
994 | ||
995 | foreach ($parts as $part) | |
996 | { | |
997 | if ( ! in_array(strtolower($part), $this->allowed_types) OR $this->mimes_types(strtolower($part)) === FALSE) | |
998 | { | |
999 | $filename .= '.'.$part.'_'; | |
1000 | } | |
1001 | else | |
1002 | { | |
1003 | $filename .= '.'.$part; | |
1004 | } | |
1005 | } | |
1006 | ||
1007 | $filename .= '.'.$ext; | |
1008 | ||
1009 | return $filename; | |
1010 | } | |
1011 | ||
1012 | // -------------------------------------------------------------------- | |
1013 | ||
1014 | /** | |
1015 | * File MIME type | |
1016 | * | |
1017 | * Detects the (actual) MIME type of the uploaded file, if possible. | |
1018 | * The input array is expected to be $_FILES[$field] | |
1019 | * | |
1020 | * @param array | |
1021 | * @return void | |
1022 | */ | |
1023 | protected function _file_mime_type($file) | |
1024 | { | |
1025 | // We'll need this to validate the MIME info string (e.g. text/plain; charset=us-ascii) | |
1026 | $regexp = '/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s.+)?$/'; | |
1027 | ||
1028 | /* Fileinfo extension - most reliable method | |
1029 | * | |
1030 | * Unfortunately, prior to PHP 5.3 - it's only available as a PECL extension and the | |
1031 | * more convenient FILEINFO_MIME_TYPE flag doesn't exist. | |
1032 | */ | |
1033 | if (function_exists('finfo_file')) | |
1034 | { | |
1035 | $finfo = finfo_open(FILEINFO_MIME); | |
1036 | if (is_resource($finfo)) // It is possible that a FALSE value is returned, if there is no magic MIME database file found on the system | |
1037 | { | |
1038 | $mime = @finfo_file($finfo, $file['tmp_name']); | |
1039 | finfo_close($finfo); | |
1040 | ||
1041 | /* According to the comments section of the PHP manual page, | |
1042 | * it is possible that this function returns an empty string | |
1043 | * for some files (e.g. if they don't exist in the magic MIME database) | |
1044 | */ | |
1045 | if (is_string($mime) && preg_match($regexp, $mime, $matches)) | |
1046 | { | |
1047 | $this->file_type = $matches[1]; | |
1048 | return; | |
1049 | } | |
1050 | } | |
1051 | } | |
1052 | ||
1053 | /* This is an ugly hack, but UNIX-type systems provide a "native" way to detect the file type, | |
1054 | * which is still more secure than depending on the value of $_FILES[$field]['type'], and as it | |
1055 | * was reported in issue #750 (https://github.com/EllisLab/CodeIgniter/issues/750) - it's better | |
1056 | * than mime_content_type() as well, hence the attempts to try calling the command line with | |
1057 | * three different functions. | |
1058 | * | |
1059 | * Notes: | |
1060 | * - the DIRECTORY_SEPARATOR comparison ensures that we're not on a Windows system | |
1061 | * - many system admins would disable the exec(), shell_exec(), popen() and similar functions | |
1062 | * due to security concerns, hence the function_exists() checks | |
1063 | */ | |
1064 | if (DIRECTORY_SEPARATOR !== '\\') | |
1065 | { | |
1066 | $cmd = 'file --brief --mime ' . escapeshellarg($file['tmp_name']) . ' 2>&1'; | |
1067 | ||
1068 | if (function_exists('exec')) | |
1069 | { | |
1070 | /* This might look confusing, as $mime is being populated with all of the output when set in the second parameter. | |
1071 | * However, we only neeed the last line, which is the actual return value of exec(), and as such - it overwrites | |
1072 | * anything that could already be set for $mime previously. This effectively makes the second parameter a dummy | |
1073 | * value, which is only put to allow us to get the return status code. | |
1074 | */ | |
1075 | $mime = @exec($cmd, $mime, $return_status); | |
1076 | if ($return_status === 0 && is_string($mime) && preg_match($regexp, $mime, $matches)) | |
1077 | { | |
1078 | $this->file_type = $matches[1]; | |
1079 | return; | |
1080 | } | |
1081 | } | |
1082 | ||
1083 | if ( (bool) @ini_get('safe_mode') === FALSE && function_exists('shell_exec')) | |
1084 | { | |
1085 | $mime = @shell_exec($cmd); | |
1086 | if (strlen($mime) > 0) | |
1087 | { | |
1088 | $mime = explode("\n", trim($mime)); | |
1089 | if (preg_match($regexp, $mime[(count($mime) - 1)], $matches)) | |
1090 | { | |
1091 | $this->file_type = $matches[1]; | |
1092 | return; | |
1093 | } | |
1094 | } | |
1095 | } | |
1096 | ||
1097 | if (function_exists('popen')) | |
1098 | { | |
1099 | $proc = @popen($cmd, 'r'); | |
1100 | if (is_resource($proc)) | |
1101 | { | |
1102 | $mime = @fread($proc, 512); | |
1103 | @pclose($proc); | |
1104 | if ($mime !== FALSE) | |
1105 | { | |
1106 | $mime = explode("\n", trim($mime)); | |
1107 | if (preg_match($regexp, $mime[(count($mime) - 1)], $matches)) | |
1108 | { | |
1109 | $this->file_type = $matches[1]; | |
1110 | return; | |
1111 | } | |
1112 | } | |
1113 | } | |
1114 | } | |
1115 | } | |
1116 | ||
1117 | // Fall back to the deprecated mime_content_type(), if available (still better than $_FILES[$field]['type']) | |
1118 | if (function_exists('mime_content_type')) | |
1119 | { | |
1120 | $this->file_type = @mime_content_type($file['tmp_name']); | |
1121 | if (strlen($this->file_type) > 0) // It's possible that mime_content_type() returns FALSE or an empty string | |
1122 | { | |
1123 | return; | |
1124 | } | |
1125 | } | |
1126 | ||
1127 | $this->file_type = $file['type']; | |
1128 | } | |
1129 | ||
1130 | // -------------------------------------------------------------------- | |
1131 | ||
1132 | } | |
1133 | // END Upload Class | |
1134 | ||
1135 | /* End of file Upload.php */ | |
1136 | /* Location: ./system/libraries/Upload.php */ |