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