2 /* vim: set expandtab sw=4 ts=4 sts=4: */
4 * file upload functions
11 * @todo replace error messages with localized string
12 * @todo when uploading a file into a blob field, should we also consider using
13 * chunks like in import? UPDATE `table` SET `field` = `field` + [chunk]
19 * @var string the temporary file name
25 * @var string the content
31 * @var string the error message
34 var $_error_message = '';
37 * @var bool whether the file is temporary or not
40 var $_is_temp = false;
43 * @var string type of compression
46 var $_compression = null;
54 * @var integer size of chunk to read with every step
56 var $_chunk_size = 32768;
59 * @var resource file handle
64 * @var boolean whether to decompress content before returning
66 var $_decompress = false;
69 * @var string charset of file
74 * @staticvar string most recent BLOB repository reference
76 static $_recent_bs_reference = NULL;
82 * @param string $name file name
84 function __construct($name = false)
87 $this->setName($name);
94 * @see PMA_File::cleanUp()
103 * deletes file if it is temporary, usally from a moved upload file
106 * @return boolean success
110 if ($this->isTemp()) {
111 return $this->delete();
121 * @return boolean success
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
133 * @param boolean sets the temp flag
134 * @return boolean PMA_File::$_is_temp
136 function isTemp($is_temp = null)
138 if (null !== $is_temp) {
139 $this->_is_temp
= (bool) $is_temp;
142 return $this->_is_temp
;
149 * @param string $name file name
151 function setName($name)
153 $this->_name
= trim($name);
158 * @return string binary file content
160 function getContent($as_binary = true, $offset = 0, $length = null)
162 if (null === $this->_content
) {
163 if ($this->isUploaded() && ! $this->checkUploadedFile()) {
167 if (! $this->isReadable()) {
171 if (function_exists('file_get_contents')) {
172 $this->_content
= file_get_contents($this->getName());
173 } elseif ($size = filesize($this->getName())) {
174 $this->_content
= fread(fopen($this->getName(), 'rb'), $size);
178 if (! empty($this->_content
) && $as_binary) {
179 return '0x' . bin2hex($this->_content
);
182 if (null !== $length) {
183 return substr($this->_content
, $offset, $length);
184 } elseif ($offset > 0) {
185 return substr($this->_content
, $offset);
188 return $this->_content
;
194 function isUploaded()
196 return is_uploaded_file($this->getName());
203 * @return string PMA_File::$_name
211 * @todo replace error message with localized string
213 * @param string name of file uploaded
214 * @return boolean success
216 function setUploadedFile($name)
218 $this->setName($name);
220 if (! $this->isUploaded()) {
221 $this->setName(null);
222 $this->_error_message
= 'not an uploaded file';
231 * @param string $key the md5 hash of the column name
232 * @param string $rownumber
233 * @return boolean success
235 function setUploadedFromTblChangeRequest($key, $rownumber)
237 if (! isset($_FILES['fields_upload']) ||
empty($_FILES['fields_upload']['name']['multi_edit'][$rownumber][$key])) {
240 $file = PMA_File
::fetchUploadedFromTblChangeRequestMultiple($_FILES['fields_upload'], $rownumber, $key);
243 $is_bs_upload = false;
245 // check if this field requires a repository upload
246 if (isset($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key])) {
247 $is_bs_upload = ($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key] == "on") ?
true : false;
249 // if request is an upload to the BLOB repository
251 $bs_db = $_REQUEST['db'];
252 $bs_table = $_REQUEST['table'];
253 $tmp_filename = $file['tmp_name'];
254 $tmp_file_type = $file['type'];
256 if (! $tmp_file_type) {
257 $tmp_file_type = NULL;
260 if (! $bs_db ||
! $bs_table) {
261 $this->_error_message
= $GLOBALS['strUploadErrorUnknown'];
264 $blob_url = PMA_BS_UpLoadFile($bs_db, $bs_table, $tmp_file_type, $tmp_filename);
265 PMA_File
::setRecentBLOBReference($blob_url);
266 } // end if ($is_bs_upload)
268 // check for file upload errors
269 switch ($file['error']) {
270 // we do not use the PHP constants here cause not all constants
271 // are defined in all versions of PHP - but the correct constants names
272 // are given as comment
273 case 0: //UPLOAD_ERR_OK:
274 return $this->setUploadedFile($file['tmp_name']);
276 case 4: //UPLOAD_ERR_NO_FILE:
278 case 1: //UPLOAD_ERR_INI_SIZE:
279 $this->_error_message
= __('The uploaded file exceeds the upload_max_filesize directive in php.ini.');
281 case 2: //UPLOAD_ERR_FORM_SIZE:
282 $this->_error_message
= __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.');
284 case 3: //UPLOAD_ERR_PARTIAL:
285 $this->_error_message
= __('The uploaded file was only partially uploaded.');
287 case 6: //UPLOAD_ERR_NO_TMP_DIR:
288 $this->_error_message
= __('Missing a temporary folder.');
290 case 7: //UPLOAD_ERR_CANT_WRITE:
291 $this->_error_message
= __('Failed to write file to disk.');
293 case 8: //UPLOAD_ERR_EXTENSION:
294 $this->_error_message
= __('File upload stopped by extension.');
297 $this->_error_message
= __('Unknown error in file upload.');
304 * strips some dimension from the multi-dimensional array from $_FILES
307 * $file['name']['multi_edit'][$rownumber][$key] = [value]
308 * $file['type']['multi_edit'][$rownumber][$key] = [value]
309 * $file['size']['multi_edit'][$rownumber][$key] = [value]
310 * $file['tmp_name']['multi_edit'][$rownumber][$key] = [value]
311 * $file['error']['multi_edit'][$rownumber][$key] = [value]
315 * $file['name'] = [value]
316 * $file['type'] = [value]
317 * $file['size'] = [value]
318 * $file['tmp_name'] = [value]
319 * $file['error'] = [value]
322 * @todo re-check if requirements changes to PHP >= 4.2.0
325 * @param array $file the array
326 * @param string $rownumber
330 function fetchUploadedFromTblChangeRequestMultiple($file, $rownumber, $key)
333 'name' => $file['name']['multi_edit'][$rownumber][$key],
334 'type' => $file['type']['multi_edit'][$rownumber][$key],
335 'size' => $file['size']['multi_edit'][$rownumber][$key],
336 'tmp_name' => $file['tmp_name']['multi_edit'][$rownumber][$key],
337 'error' => $file['error']['multi_edit'][$rownumber][$key],
344 * sets the name if the file to the one selected in the tbl_change form
347 * @param string $key the md5 hash of the column name
348 * @param string $rownumber
349 * @return boolean success
351 function setSelectedFromTblChangeRequest($key, $rownumber = null)
353 if (! empty($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])
354 && is_string($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])) {
355 // ... whether with multiple rows ...
357 $is_bs_upload = false;
359 // check if this field requires a repository upload
360 if (isset($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key])) {
361 $is_bs_upload = ($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key] == "on") ?
true : false;
364 // is a request to upload file to BLOB repository using uploadDir mechanism
366 $bs_db = $_REQUEST['db'];
367 $bs_table = $_REQUEST['table'];
368 $tmp_filename = $GLOBALS['cfg']['UploadDir'] . '/' . $_REQUEST['fields_uploadlocal_' . $key]['multi_edit'][$rownumber];
370 // check if fileinfo library exists
371 if ($PMA_Config->get('FILEINFO_EXISTS')) {
372 // attempt to init fileinfo
373 $finfo = finfo_open(FILEINFO_MIME
);
377 // pass in filename to fileinfo and close fileinfo handle after
378 $tmp_file_type = finfo_file($finfo, $tmp_filename);
382 // no fileinfo library exists, use file command
383 $tmp_file_type = exec("file -bi " . escapeshellarg($tmp_filename));
386 if (! $tmp_file_type) {
387 $tmp_file_type = NULL;
390 if (! $bs_db ||
!$bs_table) {
391 $this->_error_message
= $GLOBALS['strUploadErrorUnknown'];
394 $blob_url = PMA_BS_UpLoadFile($bs_db, $bs_table, $tmp_file_type, $tmp_filename);
395 PMA_File
::setRecentBLOBReference($blob_url);
396 } // end if ($is_bs_upload)
398 return $this->setLocalSelectedFile($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key]);
406 * @return string error message
410 return $this->_error_message
;
415 * @return boolean whether an error occured or not
419 return ! empty($this->_error_message
);
423 * checks the superglobals provided if the tbl_change form is submitted
424 * and uses the submitted/selected file
427 * @param string $key the md5 hash of the column name
428 * @param string $rownumber
429 * @return boolean success
431 function checkTblChangeForm($key, $rownumber)
433 if ($this->setUploadedFromTblChangeRequest($key, $rownumber)) {
435 $this->_error_message
= '';
437 } elseif ($this->setSelectedFromTblChangeRequest($key, $rownumber)) {
439 $this->_error_message
= '';
442 // all failed, whether just no file uploaded/selected or an error
450 * @param string $name
451 * @return boolean success
453 function setLocalSelectedFile($name)
455 if (empty($GLOBALS['cfg']['UploadDir'])) return false;
457 $this->setName(PMA_userDir($GLOBALS['cfg']['UploadDir']) . PMA_securePath($name));
458 if (! $this->isReadable()) {
459 $this->_error_message
= __('File could not be read');
460 $this->setName(null);
469 * @return boolean whether the file is readable or not
471 function isReadable()
473 // suppress warnings from being displayed, but not from being logged
474 // any file access outside of open_basedir will issue a warning
476 $is_readable = is_readable($this->getName());
482 * If we are on a server with open_basedir, we must move the file
483 * before opening it. The FAQ 1.11 explains how to create the "./tmp"
484 * directory - if needed
486 * @todo replace error message with localized string
487 * @todo move check of $cfg['TempDir'] into PMA_Config?
489 * @return boolean whether uploaded fiel is fine or not
491 function checkUploadedFile()
493 if ($this->isReadable()) {
497 if (empty($GLOBALS['cfg']['TempDir']) ||
! is_writable($GLOBALS['cfg']['TempDir'])) {
498 // cannot create directory or access, point user to FAQ 1.11
499 $this->_error_message
= __('Error moving the uploaded file, see [a@./Documentation.html#faq1_11@Documentation]FAQ 1.11[/a]');
503 $new_file_to_upload = tempnam(realpath($GLOBALS['cfg']['TempDir']), basename($this->getName()));
505 // suppress warnings from being displayed, but not from being logged
506 // any file access outside of open_basedir will issue a warning
508 $move_uploaded_file_result = move_uploaded_file($this->getName(), $new_file_to_upload);
510 if (! $move_uploaded_file_result) {
511 $this->_error_message
= 'error while moving uploaded file';
515 $this->setName($new_file_to_upload);
518 if (! $this->isReadable()) {
519 $this->_error_message
= 'cannot read (moved) upload file';
527 * Detects what compression filse uses
529 * @todo move file read part into readChunk() or getChunk()
530 * @todo add support for compression plugins
532 * @return string MIME type of compression, none for none
534 function _detectCompression()
536 // suppress warnings from being displayed, but not from being logged
537 // f.e. any file access outside of open_basedir will issue a warning
539 $file = fopen($this->getName(), 'rb');
543 $this->_error_message
= __('File could not be read');
549 * get registered plugins for file compression
551 foreach (PMA_getPlugins($type = 'compression') as $plugin) {
552 if (call_user_func_array(array($plugin['classname'], 'canHandle'), array($this->getName()))) {
553 $this->setCompressionPlugin($plugin);
559 $test = fread($file, 4);
560 $len = strlen($test);
563 if ($len >= 2 && $test[0] == chr(31) && $test[1] == chr(139)) {
564 $this->_compression
= 'application/gzip';
565 } elseif ($len >= 3 && substr($test, 0, 3) == 'BZh') {
566 $this->_compression
= 'application/bzip2';
567 } elseif ($len >= 4 && $test == "PK\003\004") {
568 $this->_compression
= 'application/zip';
570 $this->_compression
= 'none';
573 return $this->_compression
;
577 * whether the content should be decompressed before returned
579 function setDecompressContent($decompress)
581 $this->_decompress
= (bool) $decompress;
586 if (null === $this->_handle
) {
589 return $this->_handle
;
592 function setHandle($handle)
594 $this->_handle
= $handle;
602 if (! $this->_decompress
) {
603 $this->_handle
= @fopen
($this->getName(), 'r');
606 switch ($this->getCompression()) {
609 case 'application/bzip2':
610 if ($GLOBALS['cfg']['BZipDump'] && @function_exists
('bzopen')) {
611 $this->_handle
= @bzopen
($this->getName(), 'r');
613 $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());
617 case 'application/gzip':
618 if ($GLOBALS['cfg']['GZipDump'] && @function_exists
('gzopen')) {
619 $this->_handle
= @gzopen
($this->getName(), 'r');
621 $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());
625 case 'application/zip':
626 if ($GLOBALS['cfg']['ZipDump'] && @function_exists
('zip_open')) {
627 include_once './libraries/zip_extension.lib.php';
628 $result = PMA_getZipContents($this->getName());
629 if (! empty($result['error'])) {
630 $this->_error_message
= PMA_Message
::rawError($result['error']);
633 $this->content_uncompressed
= $result['data'];
637 $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());
642 $this->_handle
= @fopen
($this->getName(), 'r');
645 $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());
653 function getCharset()
655 return $this->_charset
;
658 function setCharset($charset)
660 $this->_charset
= $charset;
664 * @return string MIME type of compression, none for none
667 function getCompression()
669 if (null === $this->_compression
) {
670 return $this->_detectCompression();
673 return $this->_compression
;
677 * advances the file pointer in the file handle by $length bytes/chars
679 * @param integer $length numbers of chars/bytes to skip
681 * @todo this function is unused
683 function advanceFilePointer($length)
685 while ($length > 0) {
686 // Disable read progresivity, otherwise we eat all memory!
687 $read_multiply = 1; // required?
688 $this->getNextChunk($length);
689 $length -= $this->getChunkSize();
694 * http://bugs.php.net/bug.php?id=29532
695 * bzip reads a maximum of 8192 bytes on windows systems
696 * @todo this function is unused
698 function getNextChunk($max_size = null)
700 if (null !== $max_size) {
701 $size = min($max_size, $this->getChunkSize());
703 $size = $this->getChunkSize();
706 // $result = $this->handler->getNextChunk($size);
708 switch ($this->getCompression()) {
709 case 'application/bzip2':
711 while (strlen($result) < $size - 8192 && ! feof($this->getHandle())) {
712 $result .= bzread($this->getHandle(), $size);
715 case 'application/gzip':
716 $result = gzread($this->getHandle(), $size);
718 case 'application/zip':
720 * if getNextChunk() is used some day,
721 * replace this code by code similar to the one
724 include_once './libraries/unzip.lib.php';
725 $import_handle = new SimpleUnzip();
726 $import_handle->ReadFile($this->getName());
727 if ($import_handle->Count() == 0) {
728 $this->_error_message = __('No files found inside ZIP archive!');
730 } elseif ($import_handle->GetError(0) != 0) {
731 $this->_error_message = __('Error in ZIP archive:')
732 . ' ' . $import_handle->GetErrorMsg(0);
735 $result = $import_handle->GetData(0);
740 $result = fread($this->getHandle(), $size);
747 echo strlen($result) . ' - ';
748 echo (@$GLOBALS['__len__'] +
= strlen($result)) . ' - ';
749 echo $this->_error_message
;
752 if ($GLOBALS['charset_conversion']) {
753 $result = PMA_convert_string($this->getCharset(), 'utf-8', $result);
756 * Skip possible byte order marks (I do not think we need more
757 * charsets, but feel free to add more, you can use wikipedia for
758 * reference: <http://en.wikipedia.org/wiki/Byte_Order_Mark>)
760 * @todo BOM could be used for charset autodetection
762 if ($this->getOffset() === 0) {
764 if (strncmp($result, "\xEF\xBB\xBF", 3) == 0) {
765 $result = substr($result, 3);
767 } elseif (strncmp($result, "\xFE\xFF", 2) == 0
768 ||
strncmp($result, "\xFF\xFE", 2) == 0) {
769 $result = substr($result, 2);
774 $this->_offset +
= $size;
783 return $this->_offset
;
786 function getChunkSize()
788 return $this->_chunk_size
;
791 function setChunkSize($chunk_size)
793 $this->_chunk_size
= (int) $chunk_size;
796 function getContentLength()
798 return strlen($this->_content
);
803 if ($this->getHandle()) {
804 return feof($this->getHandle());
806 return ($this->getOffset() >= $this->getContentLength());
812 * sets reference to most recent BLOB repository reference
815 * @param string - BLOB repository reference
817 static function setRecentBLOBReference($ref)
819 PMA_File
::$_recent_bs_reference = $ref;
823 * retrieves reference to most recent BLOB repository reference
826 * @return string - most recent BLOB repository reference
828 static function getRecentBLOBReference()
830 $ref = PMA_File
::$_recent_bs_reference;
831 PMA_File
::$_recent_bs_reference = NULL;