PageRenderTime 26ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/wolf/helpers/Upload.php

http://wolfcms.googlecode.com/
PHP | 734 lines | 380 code | 95 blank | 259 comment | 62 complexity | 9e880c503137361b4fa8f90fd50d36bc MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. /*
  3. * Wolf CMS - Content Management Simplified. <http://www.wolfcms.org>
  4. *
  5. * This file is part of Wolf CMS.
  6. *
  7. * Wolf CMS is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * Wolf CMS is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with Wolf CMS. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. * Wolf CMS has made an exception to the GNU General Public License for plugins.
  21. * See exception.txt for details and the full text.
  22. */
  23. /**
  24. * Simple upload library
  25. *
  26. * @package wolf
  27. * @subpackage helpers
  28. *
  29. * @author unknown
  30. * @version 0.1
  31. * @since Wolf version beta 1
  32. * @license http://www.gnu.org/licenses/gpl.html GPL License
  33. * @copyright unknown
  34. */
  35. class Upload {
  36. public $max_size = 0;
  37. public $max_width = 0;
  38. public $max_height = 0;
  39. public $allowed_types = "";
  40. public $file_temp = "";
  41. public $file_name = "";
  42. public $orig_name = "";
  43. public $file_type = "";
  44. public $file_size = "";
  45. public $file_ext = "";
  46. public $upload_path = "";
  47. public $overwrite = false;
  48. public $encrypt_name = false;
  49. public $is_image = false;
  50. public $image_width = '';
  51. public $image_height = '';
  52. public $image_type = '';
  53. public $image_size_str = '';
  54. public $error_msg = array();
  55. public $mimes = array();
  56. public $remove_spaces = true;
  57. public $xss_clean = false;
  58. public $temp_prefix = "temp_file_";
  59. /**
  60. * Constructor
  61. *
  62. * @access public
  63. */
  64. function __construct($props = array()) {
  65. if (count($props) > 0) {
  66. $this->initialize($props);
  67. }
  68. }
  69. // --------------------------------------------------------------------
  70. /**
  71. * Initialize preferences
  72. *
  73. * @access public
  74. * @param array
  75. * @return void
  76. */
  77. function initialize($config = array()) {
  78. $defaults = array(
  79. 'max_size' => 0,
  80. 'max_width' => 0,
  81. 'max_height' => 0,
  82. 'allowed_types' => "",
  83. 'file_temp' => "",
  84. 'file_name' => "",
  85. 'orig_name' => "",
  86. 'file_type' => "",
  87. 'file_size' => "",
  88. 'file_ext' => "",
  89. 'upload_path' => "",
  90. 'overwrite' => false,
  91. 'encrypt_name' => false,
  92. 'is_image' => false,
  93. 'image_width' => '',
  94. 'image_height' => '',
  95. 'image_type' => '',
  96. 'image_size_str' => '',
  97. 'error_msg' => array(),
  98. 'mimes' => array(),
  99. 'remove_spaces' => true,
  100. 'xss_clean' => false,
  101. 'temp_prefix' => "temp_file_"
  102. );
  103. foreach ($defaults as $key => $val) {
  104. if (isset($config[$key])) {
  105. $method = 'set_'.$key;
  106. if (method_exists($this, $method)) {
  107. $this->$method($config[$key]);
  108. } else {
  109. $this->$key = $config[$key];
  110. }
  111. } else {
  112. $this->$key = $val;
  113. }
  114. }
  115. }
  116. // --------------------------------------------------------------------
  117. /**
  118. * Perform the file upload
  119. *
  120. * @access public
  121. * @return bool
  122. */
  123. function doUpload($field = 'userfile') {
  124. // Is $_FILES[$field] set? If not, no reason to continue.
  125. if ( ! isset($_FILES[$field])) {
  126. return false;
  127. }
  128. // Is the upload path valid?
  129. if ( ! $this->validateUploadPath()) {
  130. return false;
  131. }
  132. // Was the file able to be uploaded? If not, determine the reason why.
  133. if ( ! is_uploaded_file($_FILES[$field]['tmp_name'])) {
  134. $error = ( ! isset($_FILES[$field]['error'])) ? 4 : $_FILES[$field]['error'];
  135. switch($error) {
  136. case 1:
  137. $this->setError('upload_file_exceeds_limit');
  138. break;
  139. case 3:
  140. $this->setError('upload_file_partial');
  141. break;
  142. case 4:
  143. $this->setError('upload_no_file_selected');
  144. break;
  145. default:
  146. $this->setError('upload_no_file_selected');
  147. break;
  148. }
  149. return false;
  150. }
  151. // Set the uploaded data as class variables
  152. $this->file_temp = $_FILES[$field]['tmp_name'];
  153. $this->file_name = $_FILES[$field]['name'];
  154. $this->file_size = $_FILES[$field]['size'];
  155. $this->file_type = preg_replace("/^(.+?);.*$/", "\\1", $_FILES[$field]['type']);
  156. $this->file_type = strtolower($this->file_type);
  157. $this->file_ext = $this->getExtension($_FILES[$field]['name']);
  158. // Convert the file size to kilobytes
  159. if ($this->file_size > 0) {
  160. $this->file_size = round($this->file_size/1024, 2);
  161. }
  162. // Is the file type allowed to be uploaded?
  163. if ( ! $this->isAllowedFiletype()) {
  164. $this->setError('upload_invalid_filetype');
  165. return false;
  166. }
  167. // Is the file size within the allowed maximum?
  168. if ( ! $this->isAllowedFilesize()) {
  169. $this->setError('upload_invalid_filesize');
  170. return false;
  171. }
  172. // Are the image dimensions within the allowed size?
  173. // Note: This can fail if the server has an open_basdir restriction.
  174. if ( ! $this->isAllowedDimensions()) {
  175. $this->setError('upload_invalid_dimensions');
  176. return false;
  177. }
  178. // Sanitize the file name for security
  179. $this->file_name = $this->cleanFileName($this->file_name);
  180. // Remove white spaces in the name
  181. if ($this->remove_spaces == true) {
  182. $this->file_name = preg_replace("/\s+/", "_", $this->file_name);
  183. }
  184. /*
  185. * Validate the file name
  186. * This function appends an number onto the end of
  187. * the file if one with the same name already exists.
  188. * If it returns false there was a problem.
  189. */
  190. $this->orig_name = $this->file_name;
  191. if ($this->overwrite == false) {
  192. $this->file_name = $this->setFilename($this->upload_path, $this->file_name);
  193. if ($this->file_name === false) {
  194. return false;
  195. }
  196. }
  197. /*
  198. * Move the file to the final destination
  199. * To deal with different server configurations
  200. * we'll attempt to use copy() first. If that fails
  201. * we'll use move_uploaded_file(). One of the two should
  202. * reliably work in most environments
  203. */
  204. if ( ! @copy($this->file_temp, $this->upload_path . $this->file_name)) {
  205. if ( ! @move_uploaded_file($this->file_temp, $this->upload_path . $this->file_name)) {
  206. $this->setError('upload_destination_error');
  207. return false;
  208. }
  209. }
  210. /*
  211. * Run the file through the XSS hacking filter
  212. * This helps prevent malicious code from being
  213. * embedded within a file. Scripts can easily
  214. * be disguised as images or other file types.
  215. */
  216. if ($this->xss_clean == true) {
  217. $this->doXssClean();
  218. }
  219. /*
  220. * Set the finalized image dimensions
  221. * This sets the image width/height (assuming the
  222. * file was an image). We use this information
  223. * in the "data" function.
  224. */
  225. $this->setImageProperties($this->upload_path . $this->file_name);
  226. return true;
  227. }
  228. // --------------------------------------------------------------------
  229. /**
  230. * Finalized Data Array
  231. *
  232. * Returns an associative array containing all of the information
  233. * related to the upload, allowing the developer easy access in one array.
  234. *
  235. * @access public
  236. * @return array
  237. */
  238. function data() {
  239. return array (
  240. 'file_name' => $this->file_name,
  241. 'file_type' => $this->file_type,
  242. 'file_path' => $this->upload_path,
  243. 'full_path' => $this->upload_path.$this->file_name,
  244. 'raw_name' => str_replace($this->file_ext, '', $this->file_name),
  245. 'orig_name' => $this->orig_name,
  246. 'file_ext' => $this->file_ext,
  247. 'file_size' => $this->file_size,
  248. 'is_image' => $this->isImage(),
  249. 'image_width' => $this->image_width,
  250. 'image_height' => $this->image_height,
  251. 'image_type' => $this->image_type,
  252. 'image_size_str' => $this->image_size_str,
  253. );
  254. }
  255. // --------------------------------------------------------------------
  256. /**
  257. * Set Upload Path
  258. *
  259. * @access public
  260. * @param string
  261. * @return void
  262. */
  263. function setUploadPath($path) {
  264. $this->upload_path = $path;
  265. }
  266. // --------------------------------------------------------------------
  267. /**
  268. * Set the file name
  269. *
  270. * This function takes a filename/path as input and looks for the
  271. * existence of a file with the same name. If found, it will append a
  272. * number to the end of the filename to avoid overwriting a pre-existing file.
  273. *
  274. * @access public
  275. * @param string
  276. * @param string
  277. * @return string
  278. */
  279. function setFilename($path, $filename) {
  280. if ($this->encrypt_name == true) {
  281. mt_srand();
  282. $filename = md5(uniqid(mt_rand())).$this->file_ext;
  283. }
  284. if ( ! file_exists($path.$filename)) {
  285. return $filename;
  286. }
  287. $filename = str_replace($this->file_ext, '', $filename);
  288. $new_filename = '';
  289. for ($i = 1; $i < 100; $i++) {
  290. if ( ! file_exists($path.$filename.$i.$this->file_ext)) {
  291. $new_filename = $filename.$i.$this->file_ext;
  292. break;
  293. }
  294. }
  295. if ($new_filename == '') {
  296. $this->setError('upload_bad_filename');
  297. return false;
  298. } else {
  299. return $new_filename;
  300. }
  301. }
  302. // --------------------------------------------------------------------
  303. /**
  304. * Set Maximum File Size
  305. *
  306. * @access public
  307. * @param integer
  308. * @return void
  309. */
  310. function setMaxFilesize($n) {
  311. $this->max_size = ( ! eregi("^[[:digit:]]+$", $n)) ? 0 : $n;
  312. }
  313. // --------------------------------------------------------------------
  314. /**
  315. * Set Maximum Image Width
  316. *
  317. * @access public
  318. * @param integer
  319. * @return void
  320. */
  321. function setMaxWidth($n) {
  322. $this->max_width = ( ! eregi("^[[:digit:]]+$", $n)) ? 0 : $n;
  323. }
  324. // --------------------------------------------------------------------
  325. /**
  326. * Set Maximum Image Height
  327. *
  328. * @access public
  329. * @param integer
  330. * @return void
  331. */
  332. function setMaxHeight($n) {
  333. $this->max_height = ( ! eregi("^[[:digit:]]+$", $n)) ? 0 : $n;
  334. }
  335. // --------------------------------------------------------------------
  336. /**
  337. * Set Allowed File Types
  338. *
  339. * @access public
  340. * @param string
  341. * @return void
  342. */
  343. function setAllowedTypes($types) {
  344. $this->allowed_types = explode('|', $types);
  345. }
  346. // --------------------------------------------------------------------
  347. /**
  348. * Set Image Properties
  349. *
  350. * Uses GD to determine the width/height/type of image
  351. *
  352. * @access public
  353. * @param string
  354. * @return void
  355. */
  356. function setImageProperties($path = '') {
  357. if ( ! $this->is_image()) {
  358. return;
  359. }
  360. if (function_exists('getimagesize')) {
  361. if (false !== ($D = @getimagesize($path))) {
  362. $types = array(1 => 'gif', 2 => 'jpeg', 3 => 'png');
  363. $this->image_width = $D['0'];
  364. $this->image_height = $D['1'];
  365. $this->image_type = ( ! isset($types[$D['2']])) ? 'unknown' : $types[$D['2']];
  366. $this->image_size_str = $D['3']; // string containing height and width
  367. }
  368. }
  369. }
  370. // --------------------------------------------------------------------
  371. /**
  372. * Set XSS Clean
  373. *
  374. * Enables the XSS flag so that the file that was uploaded
  375. * will be run through the XSS filter.
  376. *
  377. * @access public
  378. * @param bool
  379. * @return void
  380. */
  381. function setXssClean($flag = false) {
  382. $this->xss_clean = ($flag == true) ? true : false;
  383. }
  384. // --------------------------------------------------------------------
  385. /**
  386. * Validate the image
  387. *
  388. * @access public
  389. * @return bool
  390. */
  391. function isImage() {
  392. $img_mimes = array(
  393. 'image/gif',
  394. 'image/jpg',
  395. 'image/jpe',
  396. 'image/jpeg',
  397. 'image/pjpeg',
  398. 'image/png',
  399. 'image/x-png'
  400. );
  401. return (in_array($this->file_type, $img_mimes, true)) ? true : false;
  402. }
  403. // --------------------------------------------------------------------
  404. /**
  405. * Verify that the filetype is allowed
  406. *
  407. * @access public
  408. * @return bool
  409. */
  410. function isAllowedFiletype() {
  411. if (count($this->allowed_types) == 0) {
  412. $this->setError('upload_no_file_types');
  413. return false;
  414. }
  415. foreach ($this->allowed_types as $val) {
  416. $mime = $this->mimesTypes(strtolower($val));
  417. if (is_array($mime)) {
  418. if (in_array($this->file_type, $mime, true)) {
  419. return true;
  420. }
  421. } else {
  422. if ($mime == $this->file_type) {
  423. return true;
  424. }
  425. }
  426. }
  427. return false;
  428. }
  429. // --------------------------------------------------------------------
  430. /**
  431. * Verify that the file is within the allowed size
  432. *
  433. * @access public
  434. * @return bool
  435. */
  436. function isAllowedFilesize() {
  437. if ($this->max_size != 0 AND $this->file_size > $this->max_size) {
  438. return false;
  439. } else {
  440. return true;
  441. }
  442. }
  443. // --------------------------------------------------------------------
  444. /**
  445. * Verify that the image is within the allowed width/height
  446. *
  447. * @access public
  448. * @return bool
  449. */
  450. function isAllowedDimensions() {
  451. if ( ! $this->isImage()) {
  452. return true;
  453. }
  454. if (function_exists('getimagesize')) {
  455. $D = @getimagesize($this->file_temp);
  456. if ($this->max_width > 0 AND $D['0'] > $this->max_width) {
  457. return false;
  458. }
  459. if ($this->max_height > 0 AND $D['1'] > $this->max_height) {
  460. return false;
  461. }
  462. return true;
  463. }
  464. return true;
  465. }
  466. // --------------------------------------------------------------------
  467. /**
  468. * Validate Upload Path
  469. *
  470. * Verifies that it is a valid upload path with proper permissions.
  471. *
  472. *
  473. * @access public
  474. * @return bool
  475. */
  476. function validateUploadPath() {
  477. if ($this->upload_path == '') {
  478. $this->setError('upload_no_filepath');
  479. return false;
  480. }
  481. if (function_exists('realpath') AND @realpath($this->upload_path) !== false) {
  482. $this->upload_path = str_replace("\\", "/", realpath($this->upload_path));
  483. }
  484. if ( ! @is_dir($this->upload_path)) {
  485. $this->setError('upload_no_filepath');
  486. return false;
  487. }
  488. if ( ! is_writable($this->upload_path)) {
  489. $this->setError('upload_not_writable');
  490. return false;
  491. }
  492. $this->upload_path = preg_replace("/(.+?)\/*$/", "\\1/", $this->upload_path);
  493. return true;
  494. }
  495. // --------------------------------------------------------------------
  496. /**
  497. * Extract the file extension
  498. *
  499. * @access public
  500. * @param string
  501. * @return string
  502. */
  503. function getExtension($filename) {
  504. $x = explode('.', $filename);
  505. return '.'.end($x);
  506. }
  507. // --------------------------------------------------------------------
  508. /**
  509. * Clean the file name for security
  510. *
  511. * @access public
  512. * @param string
  513. * @return string
  514. */
  515. function cleanFileName($filename) {
  516. $bad = array(
  517. "<!--",
  518. "-->",
  519. "'",
  520. "<",
  521. ">",
  522. '"',
  523. '&',
  524. '$',
  525. '=',
  526. ';',
  527. '?',
  528. '/',
  529. "%20",
  530. "%22",
  531. "%3c", // <
  532. "%253c", // <
  533. "%3e", // >
  534. "%0e", // >
  535. "%28", // (
  536. "%29", // )
  537. "%2528", // (
  538. "%26", // &
  539. "%24", // $
  540. "%3f", // ?
  541. "%3b", // ;
  542. "%3d" // =
  543. );
  544. foreach ($bad as $val) {
  545. $filename = str_replace($val, '', $filename);
  546. }
  547. return $filename;
  548. }
  549. // --------------------------------------------------------------------
  550. /**
  551. * Runs the file through the XSS clean function
  552. *
  553. * This prevents people from embedding malicious code in their files.
  554. * I'm not sure that it won't negatively affect certain files in unexpected ways,
  555. * but so far I haven't found that it causes trouble.
  556. *
  557. * @access public
  558. * @return void
  559. */
  560. function doXssClean() {
  561. $file = $this->upload_path.$this->file_name;
  562. if (filesize($file) == 0) {
  563. return false;
  564. }
  565. if ( ! $fp = @fopen($file, 'rb')) {
  566. return false;
  567. }
  568. flock($fp, LOCK_EX);
  569. $data = fread($fp, filesize($file));
  570. $CI =& get_instance();
  571. $data = $CI->input->xss_clean($data);
  572. fwrite($fp, $data);
  573. flock($fp, LOCK_UN);
  574. fclose($fp);
  575. }
  576. // --------------------------------------------------------------------
  577. /**
  578. * Set an error message
  579. *
  580. * @access public
  581. * @param string
  582. * @return void
  583. */
  584. function setError($msg) {
  585. $CI =& get_instance();
  586. $CI->lang->load('upload');
  587. if (is_array($msg)) {
  588. foreach ($msg as $val) {
  589. $msg = ($CI->lang->line($val) == false) ? $val : $CI->lang->line($val);
  590. $this->error_msg[] = $msg;
  591. }
  592. } else {
  593. $msg = ($CI->lang->line($msg) == false) ? $msg : $CI->lang->line($msg);
  594. $this->error_msg[] = $msg;
  595. }
  596. }
  597. // --------------------------------------------------------------------
  598. /**
  599. * Display the error message
  600. *
  601. * @access public
  602. * @param string
  603. * @param string
  604. * @return string
  605. */
  606. function displayErrors($open = '<p>', $close = '</p>') {
  607. $str = '';
  608. foreach ($this->error_msg as $val) {
  609. $str .= $open.$val.$close;
  610. }
  611. return $str;
  612. }
  613. // --------------------------------------------------------------------
  614. /**
  615. * List of Mime Types
  616. *
  617. * This is a list of mime types. We use it to validate
  618. * the "allowed types" set by the developer
  619. *
  620. * @access public
  621. * @param string
  622. * @return string
  623. */
  624. function mimesTypes($mime) {
  625. if (count($this->mimes) == 0) {
  626. if (@include(APP_PATH . 'config' . DS . 'mimes.php')) {
  627. $this->mimes = $mimes;
  628. unset($mimes);
  629. }
  630. }
  631. return ( ! isset($this->mimes[$mime])) ? false : $this->mimes[$mime];
  632. }
  633. } // End Upload Class