PageRenderTime 52ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/system/application/libraries/Upload.php

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