blob: dd7012211cd8baa64515e540b950a9e1123ec5f3 [file] [log] [blame]
Derek Allardd2df9bc2007-04-15 17:41:17 +00001<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
2/**
3 * CodeIgniter
4 *
5 * An open source application development framework for PHP 4.3.2 or newer
6 *
7 * @package CodeIgniter
Derek Allard3d879d52008-01-18 19:41:32 +00008 * @author ExpressionEngine Dev Team
Derek Allardd2df9bc2007-04-15 17:41:17 +00009 * @copyright Copyright (c) 2006, EllisLab, Inc.
Derek Jones7a9193a2008-01-21 18:39:20 +000010 * @license http://codeigniter.com/user_guide/license.html
11 * @link http://codeigniter.com
Derek Allardd2df9bc2007-04-15 17:41:17 +000012 * @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
Derek Allard3d879d52008-01-18 19:41:32 +000024 * @author ExpressionEngine Dev Team
Derek Jones7a9193a2008-01-21 18:39:20 +000025 * @link http://codeigniter.com/user_guide/libraries/file_uploading.html
Derek Allardd2df9bc2007-04-15 17:41:17 +000026 */
27class CI_Upload {
28
29 var $max_size = 0;
30 var $max_width = 0;
31 var $max_height = 0;
32 var $allowed_types = "";
33 var $file_temp = "";
34 var $file_name = "";
35 var $orig_name = "";
36 var $file_type = "";
37 var $file_size = "";
38 var $file_ext = "";
39 var $upload_path = "";
40 var $overwrite = FALSE;
41 var $encrypt_name = FALSE;
42 var $is_image = FALSE;
43 var $image_width = '';
44 var $image_height = '';
45 var $image_type = '';
46 var $image_size_str = '';
47 var $error_msg = array();
48 var $mimes = array();
49 var $remove_spaces = TRUE;
50 var $xss_clean = FALSE;
51 var $temp_prefix = "temp_file_";
52
53 /**
54 * Constructor
55 *
56 * @access public
57 */
58 function CI_Upload($props = array())
59 {
60 if (count($props) > 0)
61 {
62 $this->initialize($props);
63 }
64
65 log_message('debug', "Upload Class Initialized");
66 }
67
68 // --------------------------------------------------------------------
69
70 /**
71 * Initialize preferences
72 *
73 * @access public
74 * @param array
75 * @return void
76 */
77 function initialize($config = array())
78 {
79 $defaults = array(
80 'max_size' => 0,
81 'max_width' => 0,
82 'max_height' => 0,
83 'allowed_types' => "",
84 'file_temp' => "",
85 'file_name' => "",
86 'orig_name' => "",
87 'file_type' => "",
88 'file_size' => "",
89 'file_ext' => "",
90 'upload_path' => "",
91 'overwrite' => FALSE,
92 'encrypt_name' => FALSE,
93 'is_image' => FALSE,
94 'image_width' => '',
95 'image_height' => '',
96 'image_type' => '',
97 'image_size_str' => '',
98 'error_msg' => array(),
99 'mimes' => array(),
100 'remove_spaces' => TRUE,
101 'xss_clean' => FALSE,
102 'temp_prefix' => "temp_file_"
103 );
104
105
106 foreach ($defaults as $key => $val)
107 {
108 if (isset($config[$key]))
109 {
110 $method = 'set_'.$key;
111 if (method_exists($this, $method))
112 {
113 $this->$method($config[$key]);
114 }
115 else
116 {
117 $this->$key = $config[$key];
118 }
119 }
120 else
121 {
122 $this->$key = $val;
123 }
124 }
125 }
126
127 // --------------------------------------------------------------------
128
129 /**
130 * Perform the file upload
131 *
132 * @access public
133 * @return bool
134 */
135 function do_upload($field = 'userfile')
136 {
137 // Is $_FILES[$field] set? If not, no reason to continue.
138 if ( ! isset($_FILES[$field]))
139 {
140 $this->set_error('upload_userfile_not_set');
141 return FALSE;
142 }
143
144 // Is the upload path valid?
145 if ( ! $this->validate_upload_path())
146 {
147 return FALSE;
148 }
149
150 // Was the file able to be uploaded? If not, determine the reason why.
151 if ( ! is_uploaded_file($_FILES[$field]['tmp_name']))
152 {
153 $error = ( ! isset($_FILES[$field]['error'])) ? 4 : $_FILES[$field]['error'];
154
155 switch($error)
156 {
Derek Jonesc4c0ac42008-01-18 20:45:28 +0000157 case 1: // UPLOAD_ERR_INI_SIZE
158 $this->set_error('upload_file_exceeds_limit');
Derek Allardd2df9bc2007-04-15 17:41:17 +0000159 break;
Derek Jonesc4c0ac42008-01-18 20:45:28 +0000160 case 2: // UPLOAD_ERR_FORM_SIZE
161 $this->set_error('upload_file_exceeds_form_limit');
Derek Allardd2df9bc2007-04-15 17:41:17 +0000162 break;
Derek Jonesc4c0ac42008-01-18 20:45:28 +0000163 case 3: // UPLOAD_ERR_PARTIAL
164 $this->set_error('upload_file_partial');
165 break;
166 case 4: // UPLOAD_ERR_NO_FILE
167 $this->set_error('upload_no_file_selected');
168 break;
169 case 6: // UPLOAD_ERR_NO_TMP_DIR
170 $this->set_error('upload_no_temp_directory');
171 break;
172 case 7: // UPLOAD_ERR_CANT_WRITE
173 $this->set_error('upload_unable_to_write_file');
174 break;
175 case 8: // UPLOAD_ERR_EXTENSION
176 $this->set_error('upload_stopped_by_extension');
Derek Allardd2df9bc2007-04-15 17:41:17 +0000177 break;
178 default : $this->set_error('upload_no_file_selected');
179 break;
180 }
181
182 return FALSE;
183 }
184
185 // Set the uploaded data as class variables
186 $this->file_temp = $_FILES[$field]['tmp_name'];
187 $this->file_name = $_FILES[$field]['name'];
188 $this->file_size = $_FILES[$field]['size'];
189 $this->file_type = preg_replace("/^(.+?);.*$/", "\\1", $_FILES[$field]['type']);
190 $this->file_type = strtolower($this->file_type);
191 $this->file_ext = $this->get_extension($_FILES[$field]['name']);
192
193 // Convert the file size to kilobytes
194 if ($this->file_size > 0)
195 {
196 $this->file_size = round($this->file_size/1024, 2);
197 }
198
199 // Is the file type allowed to be uploaded?
200 if ( ! $this->is_allowed_filetype())
201 {
202 $this->set_error('upload_invalid_filetype');
203 return FALSE;
204 }
205
206 // Is the file size within the allowed maximum?
207 if ( ! $this->is_allowed_filesize())
208 {
209 $this->set_error('upload_invalid_filesize');
210 return FALSE;
211 }
212
213 // Are the image dimensions within the allowed size?
214 // Note: This can fail if the server has an open_basdir restriction.
215 if ( ! $this->is_allowed_dimensions())
216 {
217 $this->set_error('upload_invalid_dimensions');
218 return FALSE;
219 }
220
221 // Sanitize the file name for security
222 $this->file_name = $this->clean_file_name($this->file_name);
223
224 // Remove white spaces in the name
225 if ($this->remove_spaces == TRUE)
226 {
227 $this->file_name = preg_replace("/\s+/", "_", $this->file_name);
228 }
229
230 /*
231 * Validate the file name
232 * This function appends an number onto the end of
233 * the file if one with the same name already exists.
234 * If it returns false there was a problem.
235 */
236 $this->orig_name = $this->file_name;
237
238 if ($this->overwrite == FALSE)
239 {
240 $this->file_name = $this->set_filename($this->upload_path, $this->file_name);
241
242 if ($this->file_name === FALSE)
243 {
244 return FALSE;
245 }
246 }
247
248 /*
249 * Move the file to the final destination
250 * To deal with different server configurations
251 * we'll attempt to use copy() first. If that fails
252 * we'll use move_uploaded_file(). One of the two should
253 * reliably work in most environments
254 */
255 if ( ! @copy($this->file_temp, $this->upload_path.$this->file_name))
256 {
257 if ( ! @move_uploaded_file($this->file_temp, $this->upload_path.$this->file_name))
258 {
259 $this->set_error('upload_destination_error');
260 return FALSE;
261 }
262 }
263
264 /*
265 * Run the file through the XSS hacking filter
266 * This helps prevent malicious code from being
267 * embedded within a file. Scripts can easily
268 * be disguised as images or other file types.
269 */
270 if ($this->xss_clean == TRUE)
271 {
272 $this->do_xss_clean();
273 }
274
275 /*
276 * Set the finalized image dimensions
277 * This sets the image width/height (assuming the
278 * file was an image). We use this information
279 * in the "data" function.
280 */
281 $this->set_image_properties($this->upload_path.$this->file_name);
282
283 return TRUE;
284 }
285
286 // --------------------------------------------------------------------
287
288 /**
289 * Finalized Data Array
290 *
291 * Returns an associative array containing all of the information
292 * related to the upload, allowing the developer easy access in one array.
293 *
294 * @access public
295 * @return array
296 */
297 function data()
298 {
299 return array (
300 'file_name' => $this->file_name,
301 'file_type' => $this->file_type,
302 'file_path' => $this->upload_path,
303 'full_path' => $this->upload_path.$this->file_name,
304 'raw_name' => str_replace($this->file_ext, '', $this->file_name),
305 'orig_name' => $this->orig_name,
306 'file_ext' => $this->file_ext,
307 'file_size' => $this->file_size,
308 'is_image' => $this->is_image(),
309 'image_width' => $this->image_width,
310 'image_height' => $this->image_height,
311 'image_type' => $this->image_type,
312 'image_size_str' => $this->image_size_str,
313 );
314 }
315
316 // --------------------------------------------------------------------
317
318 /**
319 * Set Upload Path
320 *
321 * @access public
322 * @param string
323 * @return void
324 */
325 function set_upload_path($path)
326 {
327 $this->upload_path = $path;
328 }
329
330 // --------------------------------------------------------------------
331
332 /**
333 * Set the file name
334 *
335 * This function takes a filename/path as input and looks for the
336 * existence of a file with the same name. If found, it will append a
337 * number to the end of the filename to avoid overwriting a pre-existing file.
338 *
339 * @access public
340 * @param string
341 * @param string
342 * @return string
343 */
344 function set_filename($path, $filename)
345 {
346 if ($this->encrypt_name == TRUE)
347 {
348 mt_srand();
349 $filename = md5(uniqid(mt_rand())).$this->file_ext;
350 }
351
352 if ( ! file_exists($path.$filename))
353 {
354 return $filename;
355 }
356
357 $filename = str_replace($this->file_ext, '', $filename);
358
359 $new_filename = '';
360 for ($i = 1; $i < 100; $i++)
361 {
362 if ( ! file_exists($path.$filename.$i.$this->file_ext))
363 {
364 $new_filename = $filename.$i.$this->file_ext;
365 break;
366 }
367 }
368
369 if ($new_filename == '')
370 {
371 $this->set_error('upload_bad_filename');
372 return FALSE;
373 }
374 else
375 {
376 return $new_filename;
377 }
378 }
379
380 // --------------------------------------------------------------------
381
382 /**
383 * Set Maximum File Size
384 *
385 * @access public
386 * @param integer
387 * @return void
388 */
389 function set_max_filesize($n)
390 {
391 $this->max_size = ( ! eregi("^[[:digit:]]+$", $n)) ? 0 : $n;
392 }
393
394 // --------------------------------------------------------------------
395
396 /**
397 * Set Maximum Image Width
398 *
399 * @access public
400 * @param integer
401 * @return void
402 */
403 function set_max_width($n)
404 {
405 $this->max_width = ( ! eregi("^[[:digit:]]+$", $n)) ? 0 : $n;
406 }
407
408 // --------------------------------------------------------------------
409
410 /**
411 * Set Maximum Image Height
412 *
413 * @access public
414 * @param integer
415 * @return void
416 */
417 function set_max_height($n)
418 {
419 $this->max_height = ( ! eregi("^[[:digit:]]+$", $n)) ? 0 : $n;
420 }
421
422 // --------------------------------------------------------------------
423
424 /**
425 * Set Allowed File Types
426 *
427 * @access public
428 * @param string
429 * @return void
430 */
431 function set_allowed_types($types)
432 {
433 $this->allowed_types = explode('|', $types);
434 }
435
436 // --------------------------------------------------------------------
437
438 /**
439 * Set Image Properties
440 *
441 * Uses GD to determine the width/height/type of image
442 *
443 * @access public
444 * @param string
445 * @return void
446 */
447 function set_image_properties($path = '')
448 {
449 if ( ! $this->is_image())
450 {
451 return;
452 }
453
454 if (function_exists('getimagesize'))
455 {
456 if (FALSE !== ($D = @getimagesize($path)))
457 {
458 $types = array(1 => 'gif', 2 => 'jpeg', 3 => 'png');
459
460 $this->image_width = $D['0'];
461 $this->image_height = $D['1'];
462 $this->image_type = ( ! isset($types[$D['2']])) ? 'unknown' : $types[$D['2']];
463 $this->image_size_str = $D['3']; // string containing height and width
464 }
465 }
466 }
467
468 // --------------------------------------------------------------------
469
470 /**
471 * Set XSS Clean
472 *
473 * Enables the XSS flag so that the file that was uploaded
474 * will be run through the XSS filter.
475 *
476 * @access public
477 * @param bool
478 * @return void
479 */
480 function set_xss_clean($flag = FALSE)
481 {
482 $this->xss_clean = ($flag == TRUE) ? TRUE : FALSE;
483 }
484
485 // --------------------------------------------------------------------
486
487 /**
488 * Validate the image
489 *
490 * @access public
491 * @return bool
492 */
493 function is_image()
494 {
paulburdick2f35c4b2007-06-24 20:29:09 +0000495 // IE will sometimes return odd mime-types during upload, so here we just standardize all
496 // jpegs or pngs to the same file type.
497
498 $png_mimes = array('image/x-png');
499 $jpeg_mimes = array('image/jpg', 'image/jpe', 'image/jpeg', 'image/pjpeg');
500
501 if (in_array($this->file_type, $png_mimes))
502 {
503 $this->file_type = 'image/png';
504 }
505
506 if (in_array($this->file_type, $jpeg_mimes))
507 {
508 $this->file_type = 'image/jpeg';
509 }
510
Derek Allardd2df9bc2007-04-15 17:41:17 +0000511 $img_mimes = array(
512 'image/gif',
Derek Allardd2df9bc2007-04-15 17:41:17 +0000513 'image/jpeg',
Derek Allardd2df9bc2007-04-15 17:41:17 +0000514 'image/png',
Derek Allardd2df9bc2007-04-15 17:41:17 +0000515 );
516
Derek Allardd2df9bc2007-04-15 17:41:17 +0000517 return (in_array($this->file_type, $img_mimes, TRUE)) ? TRUE : FALSE;
518 }
519
520 // --------------------------------------------------------------------
521
522 /**
523 * Verify that the filetype is allowed
524 *
525 * @access public
526 * @return bool
527 */
528 function is_allowed_filetype()
529 {
Derek Allardfd5c01a2008-01-06 20:04:12 +0000530 if (count($this->allowed_types) == 0 || ! is_array($this->allowed_types))
Derek Allardd2df9bc2007-04-15 17:41:17 +0000531 {
532 $this->set_error('upload_no_file_types');
533 return FALSE;
534 }
535
536 foreach ($this->allowed_types as $val)
537 {
538 $mime = $this->mimes_types(strtolower($val));
539
540 if (is_array($mime))
541 {
542 if (in_array($this->file_type, $mime, TRUE))
543 {
544 return TRUE;
545 }
546 }
547 else
548 {
549 if ($mime == $this->file_type)
550 {
551 return TRUE;
552 }
553 }
554 }
555
556 return FALSE;
557 }
558
559 // --------------------------------------------------------------------
560
561 /**
562 * Verify that the file is within the allowed size
563 *
564 * @access public
565 * @return bool
566 */
567 function is_allowed_filesize()
568 {
569 if ($this->max_size != 0 AND $this->file_size > $this->max_size)
570 {
571 return FALSE;
572 }
573 else
574 {
575 return TRUE;
576 }
577 }
578
579 // --------------------------------------------------------------------
580
581 /**
582 * Verify that the image is within the allowed width/height
583 *
584 * @access public
585 * @return bool
586 */
587 function is_allowed_dimensions()
588 {
589 if ( ! $this->is_image())
590 {
591 return TRUE;
592 }
593
594 if (function_exists('getimagesize'))
595 {
596 $D = @getimagesize($this->file_temp);
597
598 if ($this->max_width > 0 AND $D['0'] > $this->max_width)
599 {
600 return FALSE;
601 }
602
603 if ($this->max_height > 0 AND $D['1'] > $this->max_height)
604 {
605 return FALSE;
606 }
607
608 return TRUE;
609 }
610
611 return TRUE;
612 }
613
614 // --------------------------------------------------------------------
615
616 /**
617 * Validate Upload Path
618 *
619 * Verifies that it is a valid upload path with proper permissions.
620 *
621 *
622 * @access public
623 * @return bool
624 */
625 function validate_upload_path()
626 {
627 if ($this->upload_path == '')
628 {
629 $this->set_error('upload_no_filepath');
630 return FALSE;
631 }
632
633 if (function_exists('realpath') AND @realpath($this->upload_path) !== FALSE)
634 {
635 $this->upload_path = str_replace("\\", "/", realpath($this->upload_path));
636 }
637
638 if ( ! @is_dir($this->upload_path))
639 {
640 $this->set_error('upload_no_filepath');
641 return FALSE;
642 }
643
Derek Jonesa25530f2008-01-28 17:11:02 +0000644 if ( ! is_really_writable($this->upload_path))
Derek Allardd2df9bc2007-04-15 17:41:17 +0000645 {
646 $this->set_error('upload_not_writable');
647 return FALSE;
648 }
649
650 $this->upload_path = preg_replace("/(.+?)\/*$/", "\\1/", $this->upload_path);
651 return TRUE;
652 }
653
654 // --------------------------------------------------------------------
655
656 /**
657 * Extract the file extension
658 *
659 * @access public
660 * @param string
661 * @return string
662 */
663 function get_extension($filename)
664 {
665 $x = explode('.', $filename);
666 return '.'.end($x);
667 }
668
669 // --------------------------------------------------------------------
670
671 /**
672 * Clean the file name for security
673 *
674 * @access public
675 * @param string
676 * @return string
677 */
678 function clean_file_name($filename)
679 {
680 $bad = array(
681 "<!--",
682 "-->",
683 "'",
684 "<",
685 ">",
686 '"',
687 '&',
688 '$',
689 '=',
690 ';',
691 '?',
692 '/',
693 "%20",
694 "%22",
695 "%3c", // <
696 "%253c", // <
697 "%3e", // >
698 "%0e", // >
699 "%28", // (
700 "%29", // )
701 "%2528", // (
702 "%26", // &
703 "%24", // $
704 "%3f", // ?
705 "%3b", // ;
706 "%3d" // =
707 );
708
709 foreach ($bad as $val)
710 {
711 $filename = str_replace($val, '', $filename);
712 }
713
Derek Allarda6657432008-01-26 15:26:33 +0000714 return stripslashes($filename);
Derek Allardd2df9bc2007-04-15 17:41:17 +0000715 }
716
717 // --------------------------------------------------------------------
718
719 /**
720 * Runs the file through the XSS clean function
721 *
722 * This prevents people from embedding malicious code in their files.
723 * I'm not sure that it won't negatively affect certain files in unexpected ways,
724 * but so far I haven't found that it causes trouble.
725 *
726 * @access public
727 * @return void
728 */
729 function do_xss_clean()
730 {
731 $file = $this->upload_path.$this->file_name;
732
733 if (filesize($file) == 0)
734 {
735 return FALSE;
736 }
Rick Ellis63966df2007-06-11 04:44:11 +0000737
paulburdick3c5e3732007-06-24 20:27:42 +0000738 if (($data = @file_get_contents($file)) === FALSE)
Rick Ellis64bbd042007-06-11 04:35:52 +0000739 {
740 return FALSE;
741 }
742
Rick Ellise6b4a802007-06-11 04:20:44 +0000743 if ( ! $fp = @fopen($file, 'r+b'))
Derek Allardd2df9bc2007-04-15 17:41:17 +0000744 {
745 return FALSE;
746 }
Derek Allardd2df9bc2007-04-15 17:41:17 +0000747
Derek Allardd2df9bc2007-04-15 17:41:17 +0000748 $CI =& get_instance();
749 $data = $CI->input->xss_clean($data);
Rick Ellis64bbd042007-06-11 04:35:52 +0000750
751 flock($fp, LOCK_EX);
Derek Allardd2df9bc2007-04-15 17:41:17 +0000752 fwrite($fp, $data);
753 flock($fp, LOCK_UN);
754 fclose($fp);
755 }
756
757 // --------------------------------------------------------------------
758
759 /**
760 * Set an error message
761 *
762 * @access public
763 * @param string
764 * @return void
765 */
766 function set_error($msg)
767 {
768 $CI =& get_instance();
769 $CI->lang->load('upload');
770
771 if (is_array($msg))
772 {
773 foreach ($msg as $val)
774 {
775 $msg = ($CI->lang->line($val) == FALSE) ? $val : $CI->lang->line($val);
776 $this->error_msg[] = $msg;
777 log_message('error', $msg);
778 }
779 }
780 else
781 {
782 $msg = ($CI->lang->line($msg) == FALSE) ? $msg : $CI->lang->line($msg);
783 $this->error_msg[] = $msg;
784 log_message('error', $msg);
785 }
786 }
787
788 // --------------------------------------------------------------------
789
790 /**
791 * Display the error message
792 *
793 * @access public
794 * @param string
795 * @param string
796 * @return string
797 */
798 function display_errors($open = '<p>', $close = '</p>')
799 {
800 $str = '';
801 foreach ($this->error_msg as $val)
802 {
803 $str .= $open.$val.$close;
804 }
805
806 return $str;
807 }
808
809 // --------------------------------------------------------------------
810
811 /**
812 * List of Mime Types
813 *
814 * This is a list of mime types. We use it to validate
815 * the "allowed types" set by the developer
816 *
817 * @access public
818 * @param string
819 * @return string
820 */
821 function mimes_types($mime)
822 {
823 if (count($this->mimes) == 0)
824 {
825 if (@include(APPPATH.'config/mimes'.EXT))
826 {
827 $this->mimes = $mimes;
828 unset($mimes);
829 }
830 }
831
832 return ( ! isset($this->mimes[$mime])) ? FALSE : $this->mimes[$mime];
833 }
834
835}
836// END Upload Class
adminb0dd10f2006-08-25 17:25:49 +0000837?>