Converted referrals_report.php to standard security model, take 2.
[openemr.git] / phpmyadmin / libraries / File.class.php
blob1929f9603ee1f893017e83408d03224e32d908c8
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * file upload functions
6 * @package PhpMyAdmin
7 */
8 if (! defined('PHPMYADMIN')) {
9 exit;
12 /**
13 * File wrapper class
15 * @todo when uploading a file into a blob field, should we also consider using
16 * chunks like in import? UPDATE `table` SET `field` = `field` + [chunk]
18 * @package PhpMyAdmin
20 class PMA_File
22 /**
23 * @var string the temporary file name
24 * @access protected
26 var $_name = null;
28 /**
29 * @var string the content
30 * @access protected
32 var $_content = null;
34 /**
35 * @var string the error message
36 * @access protected
38 var $_error_message = '';
40 /**
41 * @var bool whether the file is temporary or not
42 * @access protected
44 var $_is_temp = false;
46 /**
47 * @var string type of compression
48 * @access protected
50 var $_compression = null;
52 /**
53 * @var integer
55 var $_offset = 0;
57 /**
58 * @var integer size of chunk to read with every step
60 var $_chunk_size = 32768;
62 /**
63 * @var resource file handle
65 var $_handle = null;
67 /**
68 * @var boolean whether to decompress content before returning
70 var $_decompress = false;
72 /**
73 * @var string charset of file
75 var $_charset = null;
77 /**
78 * constructor
80 * @param boolean|string $name file name or false
82 * @access public
84 public function __construct($name = false)
86 if ($name && is_string($name)) {
87 $this->setName($name);
91 /**
92 * destructor
94 * @see PMA_File::cleanUp()
95 * @access public
97 public function __destruct()
99 $this->cleanUp();
103 * deletes file if it is temporary, usually from a moved upload file
105 * @access public
106 * @return boolean success
108 public function cleanUp()
110 if ($this->isTemp()) {
111 return $this->delete();
114 return true;
118 * deletes the file
120 * @access public
121 * @return boolean success
123 public function delete()
125 return unlink($this->getName());
129 * checks or sets the temp flag for this file
130 * file objects with temp flags are deleted with object destruction
132 * @param boolean $is_temp sets the temp flag
134 * @return boolean PMA_File::$_is_temp
135 * @access public
137 public function isTemp($is_temp = null)
139 if (null !== $is_temp) {
140 $this->_is_temp = (bool) $is_temp;
143 return $this->_is_temp;
147 * accessor
149 * @param string $name file name
151 * @return void
152 * @access public
154 public function setName($name)
156 $this->_name = trim($name);
160 * Gets file content
162 * @return string|false the binary file content as a string,
163 * or false if no content
165 * @access public
167 public function getContent()
169 if (null === $this->_content) {
170 if ($this->isUploaded() && ! $this->checkUploadedFile()) {
171 return false;
174 if (! $this->isReadable()) {
175 return false;
178 if (function_exists('file_get_contents')) {
179 $this->_content = file_get_contents($this->getName());
180 } elseif ($size = filesize($this->getName())) {
181 $this->_content = fread(fopen($this->getName(), 'rb'), $size);
185 return '0x' . bin2hex($this->_content);
189 * Whether file is uploaded.
191 * @access public
193 * @return bool
195 public function isUploaded()
197 return is_uploaded_file($this->getName());
201 * accessor
203 * @access public
204 * @return string PMA_File::$_name
206 public function getName()
208 return $this->_name;
212 * Initializes object from uploaded file.
214 * @param string $name name of file uploaded
216 * @return boolean success
217 * @access public
219 public function setUploadedFile($name)
221 $this->setName($name);
223 if (! $this->isUploaded()) {
224 $this->setName(null);
225 $this->_error_message = __('File was not an uploaded file.');
226 return false;
229 return true;
233 * Loads uploaded file from table change request.
235 * @param string $key the md5 hash of the column name
236 * @param string $rownumber number of row to process
238 * @return boolean success
239 * @access public
241 public function setUploadedFromTblChangeRequest($key, $rownumber)
243 if (! isset($_FILES['fields_upload'])
244 || empty($_FILES['fields_upload']['name']['multi_edit'][$rownumber][$key])
246 return false;
248 $file = PMA_File::fetchUploadedFromTblChangeRequestMultiple(
249 $_FILES['fields_upload'],
250 $rownumber,
251 $key
254 // check for file upload errors
255 switch ($file['error']) {
256 // we do not use the PHP constants here cause not all constants
257 // are defined in all versions of PHP - but the correct constants names
258 // are given as comment
259 case 0: //UPLOAD_ERR_OK:
260 return $this->setUploadedFile($file['tmp_name']);
261 case 4: //UPLOAD_ERR_NO_FILE:
262 break;
263 case 1: //UPLOAD_ERR_INI_SIZE:
264 $this->_error_message = __(
265 'The uploaded file exceeds the upload_max_filesize directive in '
266 . 'php.ini.'
268 break;
269 case 2: //UPLOAD_ERR_FORM_SIZE:
270 $this->_error_message = __(
271 'The uploaded file exceeds the MAX_FILE_SIZE directive that was '
272 . 'specified in the HTML form.'
274 break;
275 case 3: //UPLOAD_ERR_PARTIAL:
276 $this->_error_message = __(
277 'The uploaded file was only partially uploaded.'
279 break;
280 case 6: //UPLOAD_ERR_NO_TMP_DIR:
281 $this->_error_message = __('Missing a temporary folder.');
282 break;
283 case 7: //UPLOAD_ERR_CANT_WRITE:
284 $this->_error_message = __('Failed to write file to disk.');
285 break;
286 case 8: //UPLOAD_ERR_EXTENSION:
287 $this->_error_message = __('File upload stopped by extension.');
288 break;
289 default:
290 $this->_error_message = __('Unknown error in file upload.');
291 } // end switch
293 return false;
297 * strips some dimension from the multi-dimensional array from $_FILES
299 * <code>
300 * $file['name']['multi_edit'][$rownumber][$key] = [value]
301 * $file['type']['multi_edit'][$rownumber][$key] = [value]
302 * $file['size']['multi_edit'][$rownumber][$key] = [value]
303 * $file['tmp_name']['multi_edit'][$rownumber][$key] = [value]
304 * $file['error']['multi_edit'][$rownumber][$key] = [value]
306 * // becomes:
308 * $file['name'] = [value]
309 * $file['type'] = [value]
310 * $file['size'] = [value]
311 * $file['tmp_name'] = [value]
312 * $file['error'] = [value]
313 * </code>
315 * @param array $file the array
316 * @param string $rownumber number of row to process
317 * @param string $key key to process
319 * @return array
320 * @access public
321 * @static
323 public function fetchUploadedFromTblChangeRequestMultiple(
324 $file, $rownumber, $key
326 $new_file = array(
327 'name' => $file['name']['multi_edit'][$rownumber][$key],
328 'type' => $file['type']['multi_edit'][$rownumber][$key],
329 'size' => $file['size']['multi_edit'][$rownumber][$key],
330 'tmp_name' => $file['tmp_name']['multi_edit'][$rownumber][$key],
331 'error' => $file['error']['multi_edit'][$rownumber][$key],
334 return $new_file;
338 * sets the name if the file to the one selected in the tbl_change form
340 * @param string $key the md5 hash of the column name
341 * @param string $rownumber number of row to process
343 * @return boolean success
344 * @access public
346 public function setSelectedFromTblChangeRequest($key, $rownumber = null)
348 if (! empty($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])
349 && is_string($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])
351 // ... whether with multiple rows ...
352 return $this->setLocalSelectedFile(
353 $_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key]
355 } else {
356 return false;
361 * Returns possible error message.
363 * @access public
364 * @return string error message
366 public function getError()
368 return $this->_error_message;
372 * Checks whether there was any error.
374 * @access public
375 * @return boolean whether an error occurred or not
377 public function isError()
379 return ! empty($this->_error_message);
383 * checks the superglobals provided if the tbl_change form is submitted
384 * and uses the submitted/selected file
386 * @param string $key the md5 hash of the column name
387 * @param string $rownumber number of row to process
389 * @return boolean success
390 * @access public
392 public function checkTblChangeForm($key, $rownumber)
394 if ($this->setUploadedFromTblChangeRequest($key, $rownumber)) {
395 // well done ...
396 $this->_error_message = '';
397 return true;
398 } elseif ($this->setSelectedFromTblChangeRequest($key, $rownumber)) {
399 // well done ...
400 $this->_error_message = '';
401 return true;
403 // all failed, whether just no file uploaded/selected or an error
405 return false;
409 * Sets named file to be read from UploadDir.
411 * @param string $name file name
413 * @return boolean success
414 * @access public
416 public function setLocalSelectedFile($name)
418 if (empty($GLOBALS['cfg']['UploadDir'])) {
419 return false;
422 $this->setName(
423 PMA_Util::userDir($GLOBALS['cfg']['UploadDir']) . PMA_securePath($name)
425 if (! $this->isReadable()) {
426 $this->_error_message = __('File could not be read!');
427 $this->setName(null);
428 return false;
431 return true;
435 * Checks whether file can be read.
437 * @access public
438 * @return boolean whether the file is readable or not
440 public function isReadable()
442 // suppress warnings from being displayed, but not from being logged
443 // any file access outside of open_basedir will issue a warning
444 ob_start();
445 $is_readable = is_readable($this->getName());
446 ob_end_clean();
447 return $is_readable;
451 * If we are on a server with open_basedir, we must move the file
452 * before opening it. The FAQ 1.11 explains how to create the "./tmp"
453 * directory - if needed
455 * @todo move check of $cfg['TempDir'] into PMA_Config?
456 * @access public
457 * @return boolean whether uploaded file is fine or not
459 public function checkUploadedFile()
461 if ($this->isReadable()) {
462 return true;
465 if (empty($GLOBALS['cfg']['TempDir'])
466 || ! is_writable($GLOBALS['cfg']['TempDir'])
468 // cannot create directory or access, point user to FAQ 1.11
469 $this->_error_message = __(
470 'Error moving the uploaded file, see [doc@faq1-11]FAQ 1.11[/doc].'
472 return false;
475 $new_file_to_upload = tempnam(
476 realpath($GLOBALS['cfg']['TempDir']),
477 basename($this->getName())
480 // suppress warnings from being displayed, but not from being logged
481 // any file access outside of open_basedir will issue a warning
482 ob_start();
483 $move_uploaded_file_result = move_uploaded_file(
484 $this->getName(),
485 $new_file_to_upload
487 ob_end_clean();
488 if (! $move_uploaded_file_result) {
489 $this->_error_message = __('Error while moving uploaded file.');
490 return false;
493 $this->setName($new_file_to_upload);
494 $this->isTemp(true);
496 if (! $this->isReadable()) {
497 $this->_error_message = __('Cannot read uploaded file.');
498 return false;
501 return true;
505 * Detects what compression the file uses
507 * @todo move file read part into readChunk() or getChunk()
508 * @todo add support for compression plugins
509 * @access protected
510 * @return string|false false on error, otherwise string MIME type of
511 * compression, none for none
513 protected function detectCompression()
515 // suppress warnings from being displayed, but not from being logged
516 // f.e. any file access outside of open_basedir will issue a warning
517 ob_start();
518 $file = fopen($this->getName(), 'rb');
519 ob_end_clean();
521 if (! $file) {
522 $this->_error_message = __('File could not be read!');
523 return false;
527 * @todo
528 * get registered plugins for file compression
530 foreach (PMA_getPlugins($type = 'compression') as $plugin) {
531 if ($plugin['classname']::canHandle($this->getName())) {
532 $this->setCompressionPlugin($plugin);
533 break;
538 $this->_compression = PMA_Util::getCompressionMimeType($file);
539 return $this->_compression;
543 * Sets whether the content should be decompressed before returned
545 * @param boolean $decompress whether to decompress
547 * @return void
549 public function setDecompressContent($decompress)
551 $this->_decompress = (bool) $decompress;
555 * Returns the file handle
557 * @return resource file handle
559 public function getHandle()
561 if (null === $this->_handle) {
562 $this->open();
564 return $this->_handle;
568 * Sets the file handle
570 * @param object $handle file handle
572 * @return void
574 public function setHandle($handle)
576 $this->_handle = $handle;
581 * Sets error message for unsupported compression.
583 * @return void
585 public function errorUnsupported()
587 $this->_error_message = sprintf(
589 'You attempted to load file with unsupported compression (%s). '
590 . 'Either support for it is not implemented or disabled by your '
591 . 'configuration.'
593 $this->getCompression()
598 * Attempts to open the file.
600 * @return bool
602 public function open()
604 if (! $this->_decompress) {
605 $this->_handle = @fopen($this->getName(), 'r');
608 switch ($this->getCompression()) {
609 case false:
610 return false;
611 case 'application/bzip2':
612 if ($GLOBALS['cfg']['BZipDump'] && @function_exists('bzopen')) {
613 $this->_handle = @bzopen($this->getName(), 'r');
614 } else {
615 $this->errorUnsupported();
616 return false;
618 break;
619 case 'application/gzip':
620 if ($GLOBALS['cfg']['GZipDump'] && @function_exists('gzopen')) {
621 $this->_handle = @gzopen($this->getName(), 'r');
622 } else {
623 $this->errorUnsupported();
624 return false;
626 break;
627 case 'application/zip':
628 if ($GLOBALS['cfg']['ZipDump'] && @function_exists('zip_open')) {
629 include_once './libraries/zip_extension.lib.php';
630 $result = PMA_getZipContents($this->getName());
631 if (! empty($result['error'])) {
632 $this->_error_message = PMA_Message::rawError($result['error']);
633 return false;
635 unset($result);
636 } else {
637 $this->errorUnsupported();
638 return false;
640 break;
641 case 'none':
642 $this->_handle = @fopen($this->getName(), 'r');
643 break;
644 default:
645 $this->errorUnsupported();
646 return false;
649 return true;
653 * Returns the character set of the file
655 * @return string character set of the file
657 public function getCharset()
659 return $this->_charset;
663 * Sets the character set of the file
665 * @param string $charset character set of the file
667 * @return void
669 public function setCharset($charset)
671 $this->_charset = $charset;
675 * Returns compression used by file.
677 * @return string MIME type of compression, none for none
678 * @access public
680 public function getCompression()
682 if (null === $this->_compression) {
683 return $this->detectCompression();
686 return $this->_compression;
690 * Returns the offset
692 * @return integer the offset
694 public function getOffset()
696 return $this->_offset;
700 * Returns the chunk size
702 * @return integer the chunk size
704 public function getChunkSize()
706 return $this->_chunk_size;
710 * Sets the chunk size
712 * @param integer $chunk_size the chunk size
714 * @return void
716 public function setChunkSize($chunk_size)
718 $this->_chunk_size = (int) $chunk_size;
722 * Returns the length of the content in the file
724 * @return integer the length of the file content
726 public function getContentLength()
728 return /*overload*/mb_strlen($this->_content);
732 * Returns whether the end of the file has been reached
734 * @return boolean whether the end of the file has been reached
736 public function eof()
738 if ($this->getHandle()) {
739 return feof($this->getHandle());
740 } else {
741 return ($this->getOffset() >= $this->getContentLength());