blob: fa1cf1e73d8406bfc4cf096c17b33029a313baca [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
8 * @author Rick Ellis
9 * @copyright Copyright (c) 2006, EllisLab, Inc.
10 * @license http://www.codeignitor.com/user_guide/license.html
11 * @link http://www.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 Rick Ellis
25 * @link http://www.codeigniter.com/user_guide/libraries/file_uploading.html
26 */
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 {
157 case 1 : $this->set_error('upload_file_exceeds_limit');
158 break;
159 case 3 : $this->set_error('upload_file_partial');
160 break;
161 case 4 : $this->set_error('upload_no_file_selected');
162 break;
163 default : $this->set_error('upload_no_file_selected');
164 break;
165 }
166
167 return FALSE;
168 }
169
170 // Set the uploaded data as class variables
171 $this->file_temp = $_FILES[$field]['tmp_name'];
172 $this->file_name = $_FILES[$field]['name'];
173 $this->file_size = $_FILES[$field]['size'];
174 $this->file_type = preg_replace("/^(.+?);.*$/", "\\1", $_FILES[$field]['type']);
175 $this->file_type = strtolower($this->file_type);
176 $this->file_ext = $this->get_extension($_FILES[$field]['name']);
177
178 // Convert the file size to kilobytes
179 if ($this->file_size > 0)
180 {
181 $this->file_size = round($this->file_size/1024, 2);
182 }
183
184 // Is the file type allowed to be uploaded?
185 if ( ! $this->is_allowed_filetype())
186 {
187 $this->set_error('upload_invalid_filetype');
188 return FALSE;
189 }
190
191 // Is the file size within the allowed maximum?
192 if ( ! $this->is_allowed_filesize())
193 {
194 $this->set_error('upload_invalid_filesize');
195 return FALSE;
196 }
197
198 // Are the image dimensions within the allowed size?
199 // Note: This can fail if the server has an open_basdir restriction.
200 if ( ! $this->is_allowed_dimensions())
201 {
202 $this->set_error('upload_invalid_dimensions');
203 return FALSE;
204 }
205
206 // Sanitize the file name for security
207 $this->file_name = $this->clean_file_name($this->file_name);
208
209 // Remove white spaces in the name
210 if ($this->remove_spaces == TRUE)
211 {
212 $this->file_name = preg_replace("/\s+/", "_", $this->file_name);
213 }
214
215 /*
216 * Validate the file name
217 * This function appends an number onto the end of
218 * the file if one with the same name already exists.
219 * If it returns false there was a problem.
220 */
221 $this->orig_name = $this->file_name;
222
223 if ($this->overwrite == FALSE)
224 {
225 $this->file_name = $this->set_filename($this->upload_path, $this->file_name);
226
227 if ($this->file_name === FALSE)
228 {
229 return FALSE;
230 }
231 }
232
233 /*
234 * Move the file to the final destination
235 * To deal with different server configurations
236 * we'll attempt to use copy() first. If that fails
237 * we'll use move_uploaded_file(). One of the two should
238 * reliably work in most environments
239 */
240 if ( ! @copy($this->file_temp, $this->upload_path.$this->file_name))
241 {
242 if ( ! @move_uploaded_file($this->file_temp, $this->upload_path.$this->file_name))
243 {
244 $this->set_error('upload_destination_error');
245 return FALSE;
246 }
247 }
248
249 /*
250 * Run the file through the XSS hacking filter
251 * This helps prevent malicious code from being
252 * embedded within a file. Scripts can easily
253 * be disguised as images or other file types.
254 */
255 if ($this->xss_clean == TRUE)
256 {
257 $this->do_xss_clean();
258 }
259
260 /*
261 * Set the finalized image dimensions
262 * This sets the image width/height (assuming the
263 * file was an image). We use this information
264 * in the "data" function.
265 */
266 $this->set_image_properties($this->upload_path.$this->file_name);
267
268 return TRUE;
269 }
270
271 // --------------------------------------------------------------------
272
273 /**
274 * Finalized Data Array
275 *
276 * Returns an associative array containing all of the information
277 * related to the upload, allowing the developer easy access in one array.
278 *
279 * @access public
280 * @return array
281 */
282 function data()
283 {
284 return array (
285 'file_name' => $this->file_name,
286 'file_type' => $this->file_type,
287 'file_path' => $this->upload_path,
288 'full_path' => $this->upload_path.$this->file_name,
289 'raw_name' => str_replace($this->file_ext, '', $this->file_name),
290 'orig_name' => $this->orig_name,
291 'file_ext' => $this->file_ext,
292 'file_size' => $this->file_size,
293 'is_image' => $this->is_image(),
294 'image_width' => $this->image_width,
295 'image_height' => $this->image_height,
296 'image_type' => $this->image_type,
297 'image_size_str' => $this->image_size_str,
298 );
299 }
300
301 // --------------------------------------------------------------------
302
303 /**
304 * Set Upload Path
305 *
306 * @access public
307 * @param string
308 * @return void
309 */
310 function set_upload_path($path)
311 {
312 $this->upload_path = $path;
313 }
314
315 // --------------------------------------------------------------------
316
317 /**
318 * Set the file name
319 *
320 * This function takes a filename/path as input and looks for the
321 * existence of a file with the same name. If found, it will append a
322 * number to the end of the filename to avoid overwriting a pre-existing file.
323 *
324 * @access public
325 * @param string
326 * @param string
327 * @return string
328 */
329 function set_filename($path, $filename)
330 {
331 if ($this->encrypt_name == TRUE)
332 {
333 mt_srand();
334 $filename = md5(uniqid(mt_rand())).$this->file_ext;
335 }
336
337 if ( ! file_exists($path.$filename))
338 {
339 return $filename;
340 }
341
342 $filename = str_replace($this->file_ext, '', $filename);
343
344 $new_filename = '';
345 for ($i = 1; $i < 100; $i++)
346 {
347 if ( ! file_exists($path.$filename.$i.$this->file_ext))
348 {
349 $new_filename = $filename.$i.$this->file_ext;
350 break;
351 }
352 }
353
354 if ($new_filename == '')
355 {
356 $this->set_error('upload_bad_filename');
357 return FALSE;
358 }
359 else
360 {
361 return $new_filename;
362 }
363 }
364
365 // --------------------------------------------------------------------
366
367 /**
368 * Set Maximum File Size
369 *
370 * @access public
371 * @param integer
372 * @return void
373 */
374 function set_max_filesize($n)
375 {
376 $this->max_size = ( ! eregi("^[[:digit:]]+$", $n)) ? 0 : $n;
377 }
378
379 // --------------------------------------------------------------------
380
381 /**
382 * Set Maximum Image Width
383 *
384 * @access public
385 * @param integer
386 * @return void
387 */
388 function set_max_width($n)
389 {
390 $this->max_width = ( ! eregi("^[[:digit:]]+$", $n)) ? 0 : $n;
391 }
392
393 // --------------------------------------------------------------------
394
395 /**
396 * Set Maximum Image Height
397 *
398 * @access public
399 * @param integer
400 * @return void
401 */
402 function set_max_height($n)
403 {
404 $this->max_height = ( ! eregi("^[[:digit:]]+$", $n)) ? 0 : $n;
405 }
406
407 // --------------------------------------------------------------------
408
409 /**
410 * Set Allowed File Types
411 *
412 * @access public
413 * @param string
414 * @return void
415 */
416 function set_allowed_types($types)
417 {
418 $this->allowed_types = explode('|', $types);
419 }
420
421 // --------------------------------------------------------------------
422
423 /**
424 * Set Image Properties
425 *
426 * Uses GD to determine the width/height/type of image
427 *
428 * @access public
429 * @param string
430 * @return void
431 */
432 function set_image_properties($path = '')
433 {
434 if ( ! $this->is_image())
435 {
436 return;
437 }
438
439 if (function_exists('getimagesize'))
440 {
441 if (FALSE !== ($D = @getimagesize($path)))
442 {
443 $types = array(1 => 'gif', 2 => 'jpeg', 3 => 'png');
444
445 $this->image_width = $D['0'];
446 $this->image_height = $D['1'];
447 $this->image_type = ( ! isset($types[$D['2']])) ? 'unknown' : $types[$D['2']];
448 $this->image_size_str = $D['3']; // string containing height and width
449 }
450 }
451 }
452
453 // --------------------------------------------------------------------
454
455 /**
456 * Set XSS Clean
457 *
458 * Enables the XSS flag so that the file that was uploaded
459 * will be run through the XSS filter.
460 *
461 * @access public
462 * @param bool
463 * @return void
464 */
465 function set_xss_clean($flag = FALSE)
466 {
467 $this->xss_clean = ($flag == TRUE) ? TRUE : FALSE;
468 }
469
470 // --------------------------------------------------------------------
471
472 /**
473 * Validate the image
474 *
475 * @access public
476 * @return bool
477 */
478 function is_image()
479 {
paulburdick2f35c4b2007-06-24 20:29:09 +0000480 // IE will sometimes return odd mime-types during upload, so here we just standardize all
481 // jpegs or pngs to the same file type.
482
483 $png_mimes = array('image/x-png');
484 $jpeg_mimes = array('image/jpg', 'image/jpe', 'image/jpeg', 'image/pjpeg');
485
486 if (in_array($this->file_type, $png_mimes))
487 {
488 $this->file_type = 'image/png';
489 }
490
491 if (in_array($this->file_type, $jpeg_mimes))
492 {
493 $this->file_type = 'image/jpeg';
494 }
495
Derek Allardd2df9bc2007-04-15 17:41:17 +0000496 $img_mimes = array(
497 'image/gif',
Derek Allardd2df9bc2007-04-15 17:41:17 +0000498 'image/jpeg',
Derek Allardd2df9bc2007-04-15 17:41:17 +0000499 'image/png',
Derek Allardd2df9bc2007-04-15 17:41:17 +0000500 );
501
Derek Allardd2df9bc2007-04-15 17:41:17 +0000502 return (in_array($this->file_type, $img_mimes, TRUE)) ? TRUE : FALSE;
503 }
504
505 // --------------------------------------------------------------------
506
507 /**
508 * Verify that the filetype is allowed
509 *
510 * @access public
511 * @return bool
512 */
513 function is_allowed_filetype()
514 {
515 if (count($this->allowed_types) == 0)
516 {
517 $this->set_error('upload_no_file_types');
518 return FALSE;
519 }
520
521 foreach ($this->allowed_types as $val)
522 {
523 $mime = $this->mimes_types(strtolower($val));
524
525 if (is_array($mime))
526 {
527 if (in_array($this->file_type, $mime, TRUE))
528 {
529 return TRUE;
530 }
531 }
532 else
533 {
534 if ($mime == $this->file_type)
535 {
536 return TRUE;
537 }
538 }
539 }
540
541 return FALSE;
542 }
543
544 // --------------------------------------------------------------------
545
546 /**
547 * Verify that the file is within the allowed size
548 *
549 * @access public
550 * @return bool
551 */
552 function is_allowed_filesize()
553 {
554 if ($this->max_size != 0 AND $this->file_size > $this->max_size)
555 {
556 return FALSE;
557 }
558 else
559 {
560 return TRUE;
561 }
562 }
563
564 // --------------------------------------------------------------------
565
566 /**
567 * Verify that the image is within the allowed width/height
568 *
569 * @access public
570 * @return bool
571 */
572 function is_allowed_dimensions()
573 {
574 if ( ! $this->is_image())
575 {
576 return TRUE;
577 }
578
579 if (function_exists('getimagesize'))
580 {
581 $D = @getimagesize($this->file_temp);
582
583 if ($this->max_width > 0 AND $D['0'] > $this->max_width)
584 {
585 return FALSE;
586 }
587
588 if ($this->max_height > 0 AND $D['1'] > $this->max_height)
589 {
590 return FALSE;
591 }
592
593 return TRUE;
594 }
595
596 return TRUE;
597 }
598
599 // --------------------------------------------------------------------
600
601 /**
602 * Validate Upload Path
603 *
604 * Verifies that it is a valid upload path with proper permissions.
605 *
606 *
607 * @access public
608 * @return bool
609 */
610 function validate_upload_path()
611 {
612 if ($this->upload_path == '')
613 {
614 $this->set_error('upload_no_filepath');
615 return FALSE;
616 }
617
618 if (function_exists('realpath') AND @realpath($this->upload_path) !== FALSE)
619 {
620 $this->upload_path = str_replace("\\", "/", realpath($this->upload_path));
621 }
622
623 if ( ! @is_dir($this->upload_path))
624 {
625 $this->set_error('upload_no_filepath');
626 return FALSE;
627 }
628
629 if ( ! is_writable($this->upload_path))
630 {
631 $this->set_error('upload_not_writable');
632 return FALSE;
633 }
634
635 $this->upload_path = preg_replace("/(.+?)\/*$/", "\\1/", $this->upload_path);
636 return TRUE;
637 }
638
639 // --------------------------------------------------------------------
640
641 /**
642 * Extract the file extension
643 *
644 * @access public
645 * @param string
646 * @return string
647 */
648 function get_extension($filename)
649 {
650 $x = explode('.', $filename);
651 return '.'.end($x);
652 }
653
654 // --------------------------------------------------------------------
655
656 /**
657 * Clean the file name for security
658 *
659 * @access public
660 * @param string
661 * @return string
662 */
663 function clean_file_name($filename)
664 {
665 $bad = array(
666 "<!--",
667 "-->",
668 "'",
669 "<",
670 ">",
671 '"',
672 '&',
673 '$',
674 '=',
675 ';',
676 '?',
677 '/',
678 "%20",
679 "%22",
680 "%3c", // <
681 "%253c", // <
682 "%3e", // >
683 "%0e", // >
684 "%28", // (
685 "%29", // )
686 "%2528", // (
687 "%26", // &
688 "%24", // $
689 "%3f", // ?
690 "%3b", // ;
691 "%3d" // =
692 );
693
694 foreach ($bad as $val)
695 {
696 $filename = str_replace($val, '', $filename);
697 }
698
699 return $filename;
700 }
701
702 // --------------------------------------------------------------------
703
704 /**
705 * Runs the file through the XSS clean function
706 *
707 * This prevents people from embedding malicious code in their files.
708 * I'm not sure that it won't negatively affect certain files in unexpected ways,
709 * but so far I haven't found that it causes trouble.
710 *
711 * @access public
712 * @return void
713 */
714 function do_xss_clean()
715 {
716 $file = $this->upload_path.$this->file_name;
717
718 if (filesize($file) == 0)
719 {
720 return FALSE;
721 }
Rick Ellis63966df2007-06-11 04:44:11 +0000722
paulburdick3c5e3732007-06-24 20:27:42 +0000723 if (($data = @file_get_contents($file)) === FALSE)
Rick Ellis64bbd042007-06-11 04:35:52 +0000724 {
725 return FALSE;
726 }
727
Rick Ellise6b4a802007-06-11 04:20:44 +0000728 if ( ! $fp = @fopen($file, 'r+b'))
Derek Allardd2df9bc2007-04-15 17:41:17 +0000729 {
730 return FALSE;
731 }
Derek Allardd2df9bc2007-04-15 17:41:17 +0000732
Derek Allardd2df9bc2007-04-15 17:41:17 +0000733 $CI =& get_instance();
734 $data = $CI->input->xss_clean($data);
Rick Ellis64bbd042007-06-11 04:35:52 +0000735
736 flock($fp, LOCK_EX);
Derek Allardd2df9bc2007-04-15 17:41:17 +0000737 fwrite($fp, $data);
738 flock($fp, LOCK_UN);
739 fclose($fp);
740 }
741
742 // --------------------------------------------------------------------
743
744 /**
745 * Set an error message
746 *
747 * @access public
748 * @param string
749 * @return void
750 */
751 function set_error($msg)
752 {
753 $CI =& get_instance();
754 $CI->lang->load('upload');
755
756 if (is_array($msg))
757 {
758 foreach ($msg as $val)
759 {
760 $msg = ($CI->lang->line($val) == FALSE) ? $val : $CI->lang->line($val);
761 $this->error_msg[] = $msg;
762 log_message('error', $msg);
763 }
764 }
765 else
766 {
767 $msg = ($CI->lang->line($msg) == FALSE) ? $msg : $CI->lang->line($msg);
768 $this->error_msg[] = $msg;
769 log_message('error', $msg);
770 }
771 }
772
773 // --------------------------------------------------------------------
774
775 /**
776 * Display the error message
777 *
778 * @access public
779 * @param string
780 * @param string
781 * @return string
782 */
783 function display_errors($open = '<p>', $close = '</p>')
784 {
785 $str = '';
786 foreach ($this->error_msg as $val)
787 {
788 $str .= $open.$val.$close;
789 }
790
791 return $str;
792 }
793
794 // --------------------------------------------------------------------
795
796 /**
797 * List of Mime Types
798 *
799 * This is a list of mime types. We use it to validate
800 * the "allowed types" set by the developer
801 *
802 * @access public
803 * @param string
804 * @return string
805 */
806 function mimes_types($mime)
807 {
808 if (count($this->mimes) == 0)
809 {
810 if (@include(APPPATH.'config/mimes'.EXT))
811 {
812 $this->mimes = $mimes;
813 unset($mimes);
814 }
815 }
816
817 return ( ! isset($this->mimes[$mime])) ? FALSE : $this->mimes[$mime];
818 }
819
820}
821// END Upload Class
adminb0dd10f2006-08-25 17:25:49 +0000822?>