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