2 /* vim: set expandtab sw=4 ts=4 sts=4: */
4 * file upload functions
11 * @todo when uploading a file into a blob field, should we also consider using
12 * chunks like in import? UPDATE `table` SET `field` = `field` + [chunk]
18 * @var string the temporary file name
24 * @var string the content
30 * @var string the error message
33 var $_error_message = '';
36 * @var bool whether the file is temporary or not
39 var $_is_temp = false;
42 * @var string type of compression
45 var $_compression = null;
53 * @var integer size of chunk to read with every step
55 var $_chunk_size = 32768;
58 * @var resource file handle
63 * @var boolean whether to decompress content before returning
65 var $_decompress = false;
68 * @var string charset of file
73 * @staticvar string most recent BLOB repository reference
75 static $_recent_bs_reference = null;
81 * @param string $name file name
83 function __construct($name = false)
86 $this->setName($name);
93 * @see PMA_File::cleanUp()
102 * deletes file if it is temporary, usally from a moved upload file
105 * @return boolean success
109 if ($this->isTemp()) {
110 return $this->delete();
120 * @return boolean success
124 return unlink($this->getName());
128 * checks or sets the temp flag for this file
129 * file objects with temp flags are deleted with object destruction
132 * @param boolean sets the temp flag
133 * @return boolean PMA_File::$_is_temp
135 function isTemp($is_temp = null)
137 if (null !== $is_temp) {
138 $this->_is_temp
= (bool) $is_temp;
141 return $this->_is_temp
;
148 * @param string $name file name
150 function setName($name)
152 $this->_name
= trim($name);
157 * @return string binary file content
159 function getContent($as_binary = true, $offset = 0, $length = null)
161 if (null === $this->_content
) {
162 if ($this->isUploaded() && ! $this->checkUploadedFile()) {
166 if (! $this->isReadable()) {
170 if (function_exists('file_get_contents')) {
171 $this->_content
= file_get_contents($this->getName());
172 } elseif ($size = filesize($this->getName())) {
173 $this->_content
= fread(fopen($this->getName(), 'rb'), $size);
177 if (! empty($this->_content
) && $as_binary) {
178 return '0x' . bin2hex($this->_content
);
181 if (null !== $length) {
182 return substr($this->_content
, $offset, $length);
183 } elseif ($offset > 0) {
184 return substr($this->_content
, $offset);
187 return $this->_content
;
193 function isUploaded()
195 return is_uploaded_file($this->getName());
202 * @return string PMA_File::$_name
211 * @param string name of file uploaded
212 * @return boolean success
214 function setUploadedFile($name)
216 $this->setName($name);
218 if (! $this->isUploaded()) {
219 $this->setName(null);
220 $this->_error_message
= __('File was not an uploaded file.');
229 * @param string $key the md5 hash of the column name
230 * @param string $rownumber
231 * @return boolean success
233 function setUploadedFromTblChangeRequest($key, $rownumber)
235 if (! isset($_FILES['fields_upload']) ||
empty($_FILES['fields_upload']['name']['multi_edit'][$rownumber][$key])) {
238 $file = PMA_File
::fetchUploadedFromTblChangeRequestMultiple($_FILES['fields_upload'], $rownumber, $key);
241 $is_bs_upload = false;
243 // check if this field requires a repository upload
244 if (isset($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key])) {
245 $is_bs_upload = ($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key] == "on") ?
true : false;
247 // if request is an upload to the BLOB repository
249 $bs_db = $_REQUEST['db'];
250 $bs_table = $_REQUEST['table'];
251 $tmp_filename = $file['tmp_name'];
252 $tmp_file_type = $file['type'];
254 if (! $tmp_file_type) {
255 $tmp_file_type = null;
258 if (! $bs_db ||
! $bs_table) {
259 $this->_error_message
= __('Unknown error while uploading.');
262 $blob_url = PMA_BS_UpLoadFile($bs_db, $bs_table, $tmp_file_type, $tmp_filename);
263 PMA_File
::setRecentBLOBReference($blob_url);
264 } // end if ($is_bs_upload)
266 // check for file upload errors
267 switch ($file['error']) {
268 // we do not use the PHP constants here cause not all constants
269 // are defined in all versions of PHP - but the correct constants names
270 // are given as comment
271 case 0: //UPLOAD_ERR_OK:
272 return $this->setUploadedFile($file['tmp_name']);
274 case 4: //UPLOAD_ERR_NO_FILE:
276 case 1: //UPLOAD_ERR_INI_SIZE:
277 $this->_error_message
= __('The uploaded file exceeds the upload_max_filesize directive in php.ini.');
279 case 2: //UPLOAD_ERR_FORM_SIZE:
280 $this->_error_message
= __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.');
282 case 3: //UPLOAD_ERR_PARTIAL:
283 $this->_error_message
= __('The uploaded file was only partially uploaded.');
285 case 6: //UPLOAD_ERR_NO_TMP_DIR:
286 $this->_error_message
= __('Missing a temporary folder.');
288 case 7: //UPLOAD_ERR_CANT_WRITE:
289 $this->_error_message
= __('Failed to write file to disk.');
291 case 8: //UPLOAD_ERR_EXTENSION:
292 $this->_error_message
= __('File upload stopped by extension.');
295 $this->_error_message
= __('Unknown error in file upload.');
302 * strips some dimension from the multi-dimensional array from $_FILES
305 * $file['name']['multi_edit'][$rownumber][$key] = [value]
306 * $file['type']['multi_edit'][$rownumber][$key] = [value]
307 * $file['size']['multi_edit'][$rownumber][$key] = [value]
308 * $file['tmp_name']['multi_edit'][$rownumber][$key] = [value]
309 * $file['error']['multi_edit'][$rownumber][$key] = [value]
313 * $file['name'] = [value]
314 * $file['type'] = [value]
315 * $file['size'] = [value]
316 * $file['tmp_name'] = [value]
317 * $file['error'] = [value]
322 * @param array $file the array
323 * @param string $rownumber
327 function fetchUploadedFromTblChangeRequestMultiple($file, $rownumber, $key)
330 'name' => $file['name']['multi_edit'][$rownumber][$key],
331 'type' => $file['type']['multi_edit'][$rownumber][$key],
332 'size' => $file['size']['multi_edit'][$rownumber][$key],
333 'tmp_name' => $file['tmp_name']['multi_edit'][$rownumber][$key],
334 'error' => $file['error']['multi_edit'][$rownumber][$key],
341 * sets the name if the file to the one selected in the tbl_change form
344 * @param string $key the md5 hash of the column name
345 * @param string $rownumber
346 * @return boolean success
348 function setSelectedFromTblChangeRequest($key, $rownumber = null)
350 if (! empty($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])
351 && is_string($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])) {
352 // ... whether with multiple rows ...
354 $is_bs_upload = false;
356 // check if this field requires a repository upload
357 if (isset($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key])) {
358 $is_bs_upload = ($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key] == "on") ?
true : false;
361 // is a request to upload file to BLOB repository using uploadDir mechanism
363 $bs_db = $_REQUEST['db'];
364 $bs_table = $_REQUEST['table'];
365 $tmp_filename = $GLOBALS['cfg']['UploadDir'] . '/' . $_REQUEST['fields_uploadlocal_' . $key]['multi_edit'][$rownumber];
367 // check if fileinfo library exists
368 if ($PMA_Config->get('FILEINFO_EXISTS')) {
369 // attempt to init fileinfo
370 $finfo = finfo_open(FILEINFO_MIME
);
374 // pass in filename to fileinfo and close fileinfo handle after
375 $tmp_file_type = finfo_file($finfo, $tmp_filename);
379 // no fileinfo library exists, use file command
380 $tmp_file_type = exec("file -bi " . escapeshellarg($tmp_filename));
383 if (! $tmp_file_type) {
384 $tmp_file_type = null;
387 if (! $bs_db ||
!$bs_table) {
388 $this->_error_message
= __('Unknown error while uploading.');
391 $blob_url = PMA_BS_UpLoadFile($bs_db, $bs_table, $tmp_file_type, $tmp_filename);
392 PMA_File
::setRecentBLOBReference($blob_url);
393 } // end if ($is_bs_upload)
395 return $this->setLocalSelectedFile($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key]);
403 * @return string error message
407 return $this->_error_message
;
412 * @return boolean whether an error occured or not
416 return ! empty($this->_error_message
);
420 * checks the superglobals provided if the tbl_change form is submitted
421 * and uses the submitted/selected file
424 * @param string $key the md5 hash of the column name
425 * @param string $rownumber
426 * @return boolean success
428 function checkTblChangeForm($key, $rownumber)
430 if ($this->setUploadedFromTblChangeRequest($key, $rownumber)) {
432 $this->_error_message
= '';
434 } elseif ($this->setSelectedFromTblChangeRequest($key, $rownumber)) {
436 $this->_error_message
= '';
439 // all failed, whether just no file uploaded/selected or an error
447 * @param string $name
448 * @return boolean success
450 function setLocalSelectedFile($name)
452 if (empty($GLOBALS['cfg']['UploadDir'])) return false;
454 $this->setName(PMA_userDir($GLOBALS['cfg']['UploadDir']) . PMA_securePath($name));
455 if (! $this->isReadable()) {
456 $this->_error_message
= __('File could not be read');
457 $this->setName(null);
466 * @return boolean whether the file is readable or not
468 function isReadable()
470 // suppress warnings from being displayed, but not from being logged
471 // any file access outside of open_basedir will issue a warning
473 $is_readable = is_readable($this->getName());
479 * If we are on a server with open_basedir, we must move the file
480 * before opening it. The FAQ 1.11 explains how to create the "./tmp"
481 * directory - if needed
483 * @todo move check of $cfg['TempDir'] into PMA_Config?
485 * @return boolean whether uploaded fiel is fine or not
487 function checkUploadedFile()
489 if ($this->isReadable()) {
493 if (empty($GLOBALS['cfg']['TempDir']) ||
! is_writable($GLOBALS['cfg']['TempDir'])) {
494 // cannot create directory or access, point user to FAQ 1.11
495 $this->_error_message
= __('Error moving the uploaded file, see [a@./Documentation.html#faq1_11@Documentation]FAQ 1.11[/a]');
499 $new_file_to_upload = tempnam(realpath($GLOBALS['cfg']['TempDir']), basename($this->getName()));
501 // suppress warnings from being displayed, but not from being logged
502 // any file access outside of open_basedir will issue a warning
504 $move_uploaded_file_result = move_uploaded_file($this->getName(), $new_file_to_upload);
506 if (! $move_uploaded_file_result) {
507 $this->_error_message
= __('Error while moving uploaded file.');
511 $this->setName($new_file_to_upload);
514 if (! $this->isReadable()) {
515 $this->_error_message
= __('Cannot read (moved) upload file.');
523 * Detects what compression filse uses
525 * @todo move file read part into readChunk() or getChunk()
526 * @todo add support for compression plugins
528 * @return string MIME type of compression, none for none
530 function _detectCompression()
532 // suppress warnings from being displayed, but not from being logged
533 // f.e. any file access outside of open_basedir will issue a warning
535 $file = fopen($this->getName(), 'rb');
539 $this->_error_message
= __('File could not be read');
545 * get registered plugins for file compression
547 foreach (PMA_getPlugins($type = 'compression') as $plugin) {
548 if (call_user_func_array(array($plugin['classname'], 'canHandle'), array($this->getName()))) {
549 $this->setCompressionPlugin($plugin);
555 $test = fread($file, 4);
556 $len = strlen($test);
559 if ($len >= 2 && $test[0] == chr(31) && $test[1] == chr(139)) {
560 $this->_compression
= 'application/gzip';
561 } elseif ($len >= 3 && substr($test, 0, 3) == 'BZh') {
562 $this->_compression
= 'application/bzip2';
563 } elseif ($len >= 4 && $test == "PK\003\004") {
564 $this->_compression
= 'application/zip';
566 $this->_compression
= 'none';
569 return $this->_compression
;
573 * whether the content should be decompressed before returned
575 function setDecompressContent($decompress)
577 $this->_decompress
= (bool) $decompress;
582 if (null === $this->_handle
) {
585 return $this->_handle
;
588 function setHandle($handle)
590 $this->_handle
= $handle;
598 if (! $this->_decompress
) {
599 $this->_handle
= @fopen
($this->getName(), 'r');
602 switch ($this->getCompression()) {
605 case 'application/bzip2':
606 if ($GLOBALS['cfg']['BZipDump'] && @function_exists
('bzopen')) {
607 $this->_handle
= @bzopen
($this->getName(), 'r');
609 $this->_error_message
= sprintf(__('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.'), $this->getCompression());
613 case 'application/gzip':
614 if ($GLOBALS['cfg']['GZipDump'] && @function_exists
('gzopen')) {
615 $this->_handle
= @gzopen
($this->getName(), 'r');
617 $this->_error_message
= sprintf(__('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.'), $this->getCompression());
621 case 'application/zip':
622 if ($GLOBALS['cfg']['ZipDump'] && @function_exists
('zip_open')) {
623 include_once './libraries/zip_extension.lib.php';
624 $result = PMA_getZipContents($this->getName());
625 if (! empty($result['error'])) {
626 $this->_error_message
= PMA_Message
::rawError($result['error']);
629 $this->content_uncompressed
= $result['data'];
633 $this->_error_message
= sprintf(__('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.'), $this->getCompression());
638 $this->_handle
= @fopen
($this->getName(), 'r');
641 $this->_error_message
= sprintf(__('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.'), $this->getCompression());
649 function getCharset()
651 return $this->_charset
;
654 function setCharset($charset)
656 $this->_charset
= $charset;
660 * @return string MIME type of compression, none for none
663 function getCompression()
665 if (null === $this->_compression
) {
666 return $this->_detectCompression();
669 return $this->_compression
;
673 * advances the file pointer in the file handle by $length bytes/chars
675 * @param integer $length numbers of chars/bytes to skip
677 * @todo this function is unused
679 function advanceFilePointer($length)
681 while ($length > 0) {
682 // Disable read progresivity, otherwise we eat all memory!
683 $read_multiply = 1; // required?
684 $this->getNextChunk($length);
685 $length -= $this->getChunkSize();
690 * http://bugs.php.net/bug.php?id=29532
691 * bzip reads a maximum of 8192 bytes on windows systems
692 * @todo this function is unused
694 function getNextChunk($max_size = null)
696 if (null !== $max_size) {
697 $size = min($max_size, $this->getChunkSize());
699 $size = $this->getChunkSize();
702 // $result = $this->handler->getNextChunk($size);
704 switch ($this->getCompression()) {
705 case 'application/bzip2':
707 while (strlen($result) < $size - 8192 && ! feof($this->getHandle())) {
708 $result .= bzread($this->getHandle(), $size);
711 case 'application/gzip':
712 $result = gzread($this->getHandle(), $size);
714 case 'application/zip':
716 * if getNextChunk() is used some day,
717 * replace this code by code similar to the one
720 include_once './libraries/unzip.lib.php';
721 $import_handle = new SimpleUnzip();
722 $import_handle->ReadFile($this->getName());
723 if ($import_handle->Count() == 0) {
724 $this->_error_message = __('No files found inside ZIP archive!');
726 } elseif ($import_handle->GetError(0) != 0) {
727 $this->_error_message = __('Error in ZIP archive:')
728 . ' ' . $import_handle->GetErrorMsg(0);
731 $result = $import_handle->GetData(0);
736 $result = fread($this->getHandle(), $size);
743 echo strlen($result) . ' - ';
744 echo (@$GLOBALS['__len__'] +
= strlen($result)) . ' - ';
745 echo $this->_error_message
;
748 if ($GLOBALS['charset_conversion']) {
749 $result = PMA_convert_string($this->getCharset(), 'utf-8', $result);
752 * Skip possible byte order marks (I do not think we need more
753 * charsets, but feel free to add more, you can use wikipedia for
754 * reference: <http://en.wikipedia.org/wiki/Byte_Order_Mark>)
756 * @todo BOM could be used for charset autodetection
758 if ($this->getOffset() === 0) {
760 if (strncmp($result, "\xEF\xBB\xBF", 3) == 0) {
761 $result = substr($result, 3);
763 } elseif (strncmp($result, "\xFE\xFF", 2) == 0
764 ||
strncmp($result, "\xFF\xFE", 2) == 0) {
765 $result = substr($result, 2);
770 $this->_offset +
= $size;
779 return $this->_offset
;
782 function getChunkSize()
784 return $this->_chunk_size
;
787 function setChunkSize($chunk_size)
789 $this->_chunk_size
= (int) $chunk_size;
792 function getContentLength()
794 return strlen($this->_content
);
799 if ($this->getHandle()) {
800 return feof($this->getHandle());
802 return ($this->getOffset() >= $this->getContentLength());
808 * sets reference to most recent BLOB repository reference
811 * @param string - BLOB repository reference
813 static function setRecentBLOBReference($ref)
815 PMA_File
::$_recent_bs_reference = $ref;
819 * retrieves reference to most recent BLOB repository reference
822 * @return string - most recent BLOB repository reference
824 static function getRecentBLOBReference()
826 $ref = PMA_File
::$_recent_bs_reference;
827 PMA_File
::$_recent_bs_reference = null;