diff --git a/ChangeLog b/ChangeLog index 716051139..d6bf615a1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,7 +5,7 @@ phpMyAdmin - ChangeLog $Id$ $HeadURL$ -- bug #1615530 [data] added more specific error message if field upload fails +- bug #1615530 [upload] added more specific error message if field upload fails - [gui] avoid displaying a wide selector in server selection + [core] added PMA_fatalError() and made use of it . [i18n] use generic $strOptions @@ -17,6 +17,8 @@ $HeadURL$ + [gui] dropped css/ folder and moved into root of PMA + [l10n] new: sinhala + [export] YAML export (see yaml.org), thanks to Bryce Thornton ++ [upload] moved file upload functionality into own class ++ [upload] make use of $cfg['TempDir'] for file uploads 2.10.1.0 (not released yet) ===================== diff --git a/libraries/PMA_File.class.php b/libraries/PMA_File.class.php new file mode 100644 index 000000000..6c212da80 --- /dev/null +++ b/libraries/PMA_File.class.php @@ -0,0 +1,507 @@ +__construct($name); + } + + /** + * constructor + * + * @access public + * @uses PMA_File::setName() + * @param string $name file name + */ + function __construct($name = false) + { + if ($name) { + $this->setName($name); + } + } + + /** + * destructor + * + * @see PMA_File::cleanUp() + * @uses PMA_File::cleanUp() + */ + function __destruct() + { + $this->cleanUp(); + } + + /** + * deletes temp file from upload + */ + function cleanUp() + { + if ($this->isTemp()) { + return $this->delete(); + } + + return true; + } + + /** + * deletes the file + * + * @uses PMA_File::getName() + * @uses unlink() + * @return boolean success + */ + function delete() + { + return unlink($this->getName()); + } + + /** + * checks or sets the temp flag for this file + * file objects with temp flags are deleted with object destruction + * + * @uses PMA_File::$_is_temp to set and read it + * @param boolean sets the temp flag + * @return boolean PMA_File::$_is_temp + */ + function isTemp($is_temp = null) + { + if (null !== $is_temp) { + $this->_is_temp = (bool) $is_temp; + } + + return $this->_is_temp; + } + + /** + * accessor + * + * @uses PMA_File::$_name + * @param string $name file name + * @access public + */ + function setName($name) + { + $this->_name = trim($name); + } + + /** + * @access public + * @uses PMA_File::getName() + * @uses PMA_File::isUploaded() + * @uses PMA_File::checkUploadedFile() + * @uses PMA_File::isReadable() + * @uses PMA_File::$_content + * @uses function_exists() + * @uses file_get_contents() + * @uses filesize() + * @uses fread() + * @uses fopen() + * @uses bin2hex() + * @return string binary file content + */ + function getContent() + { + if (null !== $this->_content) { + return $this->_content; + } + + if ($this->isUploaded() && ! $this->checkUploadedFile()) { + return false; + } + + if (! $this->isReadable()) { + return false; + } + + // check if file is not empty + if (function_exists('file_get_contents')) { + $this->_content = file_get_contents($this->getName()); + } elseif ($size = filesize($this->getName())) { + $this->_content = fread(fopen($this->getName(), 'rb'), $size); + } + + if (! empty($this->_content)) { + $this->_content = '0x' . bin2hex($this->_content); + } + + return $this->_content; + } + + /** + * @uses PMA_File::getName() + * @uses is_uploaded_file() + */ + function isUploaded() + { + return is_uploaded_file($this->getName()); + } + + /** + * accessor + * + * @uses PMA_File::$name as return value + * @return string PMA_File::$_name + */ + function getName() + { + return $this->_name; + } + + /** + * @todo replace error message with localized string + * @uses PMA_File::isUploaded() + * @uses PMA_File::setName() + * @uses PMA_File::$_error_message + * @param string name of file uploaded + * @return boolean success + */ + function setUploadedFile($name) + { + $this->setName($name); + + if (! $this->isUploaded()) { + $this->setName(null); + $this->_error_message = 'not an uploaded file'; + return false; + } + + return true; + } + + /** + * @uses PMA_File::fetchUploadedFromTblChangeRequestMultiple() + * @uses PMA_File::setUploadedFile() + * @uses PMA_File::$_error_message + * @uses $GLOBALS['strUploadErrorIniSize'] + * @uses $GLOBALS['strUploadErrorFormSize'] + * @uses $GLOBALS['strUploadErrorPartial'] + * @uses $GLOBALS['strUploadErrorNoTempDir'] + * @uses $GLOBALS['strUploadErrorCantWrite'] + * @uses $GLOBALS['strUploadErrorExtension'] + * @uses $GLOBALS['strUploadErrorUnknown'] + * @uses $_FILES + * @param string $key a numeric key used to identify the different rows + * @param string $primary_key + * @return boolean success + */ + function setUploadedFromTblChangeRequest($key, $primary = null) + { + if (! isset($_FILES['fields_upload_' . $key])) { + return false; + } + + $file = $_FILES['fields_upload_' . $key]; + if (null !== $primary) { + $file = PMA_File::fetchUploadedFromTblChangeRequestMultiple($file, $primary); + } + + // check for file upload errors + switch ($file['error']) { + // cybot_tm: we do not use the PHP constants here cause not all constants + // are defined in all versions of PHP - but the correct constants names + // are given as comment + case 0: //UPLOAD_ERR_OK: + return $this->setUploadedFile($file['tmp_name']); + break; + case 4: //UPLOAD_ERR_NO_FILE: + break; + case 1: //UPLOAD_ERR_INI_SIZE: + $this->_error_message = $GLOBALS['strUploadErrorIniSize']; + break; + case 2: //UPLOAD_ERR_FORM_SIZE: + $this->_error_message = $GLOBALS['strUploadErrorFormSize']; + break; + case 3: //UPLOAD_ERR_PARTIAL: + $this->_error_message = $GLOBALS['strUploadErrorPartial']; + break; + case 6: //UPLOAD_ERR_NO_TMP_DIR: + $this->_error_message = $GLOBALS['strUploadErrorNoTempDir']; + break; + case 7: //UPLOAD_ERR_CANT_WRITE: + $this->_error_message = $GLOBALS['strUploadErrorCantWrite']; + break; + case 8: //UPLOAD_ERR_EXTENSION: + $this->_error_message = $GLOBALS['strUploadErrorExtension']; + break; + default: + $this->_error_message = $GLOBALS['strUploadErrorUnknown']; + } // end switch + + return false; + } + + /** + * strips some dimension from the multi-dimensional array from $_FILES + * + * + * $file['name']['multi_edit'][$primary] = [value] + * $file['type']['multi_edit'][$primary] = [value] + * $file['size']['multi_edit'][$primary] = [value] + * $file['tmp_name']['multi_edit'][$primary] = [value] + * $file['error']['multi_edit'][$primary] = [value] + * + * // becomes: + * + * $file['name'] = [value] + * $file['type'] = [value] + * $file['size'] = [value] + * $file['tmp_name'] = [value] + * $file['error'] = [value] + * + * + * @todo re-check if requirements changes to PHP >= 4.2.0 + * @param array $file the array + * @param string $primary + * @return array + */ + function fetchUploadedFromTblChangeRequestMultiple($file, $primary) + { + $new_file = array( + 'name' => $file['name']['multi_edit'][$primary], + 'type' => $file['type']['multi_edit'][$primary], + 'size' => $file['size']['multi_edit'][$primary], + 'tmp_name' => $file['tmp_name']['multi_edit'][$primary], + //'error' => $file['error']['multi_edit'][$primary], + ); + + // ['error'] exists since PHP 4.2.0 + if (isset($file['error'])) { + $new_file['error'] = $file['error']['multi_edit'][$primary]; + } + + return $new_file; + } + + /** + * sets the name if the file to the one selected in the tbl_change form + * + * @uses $_REQUEST + * @uses PMA_File::setLocalSelectedFile() + * @uses is_string() + * @param string $key a numeric key used to identify the different rows + * @param string $primary_key + * @return boolean success + */ + function setSelectedFromTblChangeRequest($key, $primary = null) + { + if (null !== $primary) { + if (! empty($_REQUEST['fields_uploadlocal_' . $key]['multi_edit'][$primary]) + && is_string($_REQUEST['fields_uploadlocal_' . $key]['multi_edit'][$primary])) { + // ... whether with multiple rows ... + return $this->setLocalSelectedFile($_REQUEST['fields_uploadlocal_' . $key]['multi_edit'][$primary]); + } else { + return false; + } + } elseif (! empty($_REQUEST['fields_uploadlocal_' . $key]) + && is_string($_REQUEST['fields_uploadlocal_' . $key])) { + return $this->setLocalSelectedFile($_REQUEST['fields_uploadlocal_' . $key]); + } + + return false; + } + + /** + * @uses PMA_File->$_error_message as return value + * @return string error message + */ + function getError() + { + return $this->_error_message; + } + + /** + * @uses PMA_File->$_error_message to check it + * @return boolean whether an error occured or not + */ + function isError() + { + return ! empty($this->_error_message); + } + + /** + * chacks the supergloabls provided if the tbl_change form is submitted + * and uses the submitted/selected file + * + * @uses PMA_File::setUploadedFromTblChangeRequest() + * @uses PMA_File::setSelectedFromTblChangeRequest() + * @param string $key a numeric key used to identify the different rows + * @param string $primary_key + * @return boolean success + */ + function checkTblChangeForm($key, $primary_key) + { + if ($this->setUploadedFromTblChangeRequest($key, $primary_key)) { + // well done ... + return true; + } elseif ($this->setUploadedFromTblChangeRequest($key)) { + // well done ... + return true; + } elseif ($this->setSelectedFromTblChangeRequest($key, $primary_key)) { + // well done ... + return true; + } elseif ($this->setSelectedFromTblChangeRequest($key)) { + // well done ... + return true; + } + // all failed, whether just no file uploaded/selected or an error + + return false; + } + + /** + * + * @uses PMA_File::setName() + * @uses preg_replace() + * @uses PMA_userDir() + * @uses $GLOBALS['cfg']['UploadDir'] + * @param string $name + */ + function setLocalSelectedFile($name) + { + $this->setName(PMA_userDir($GLOBALS['cfg']['UploadDir']) . preg_replace('@\.\.*@', '.', $name)); + } + + /** + * @uses PMA_File::getName() + * @uses is_readable() + * @uses ob_start() + * @uses ob_end_clean() + * @return boolean whether the file is readable or not + */ + function isReadable() + { + // surprees warnings form beeing displayed, but not from beeing logged + // any file access outside of open_basedir will issue a warning + ob_start(); + $is_readable = is_readable($this->getName()); + ob_end_clean(); + return $is_readable; + } + + /** + * If we are on a server with open_basedir, we must move the file + * before opening it. The FAQ 1.11 explains how to create the "./tmp" + * directory - if needed + * + * @todo replace error message with localized string + * @todo move check of $cfg['TempDir'] into PMA_Config? + * @uses $cfg['TempDir'] + * @uses $GLOBALS['strFieldInsertFromFileTempDirNotExists'] + * @uses PMA_File::isReadable() + * @uses PMA_File::getName() + * @uses PMA_File::setName() + * @uses PMA_File::isTemp() + * @uses PMA_File::$_error_message + * @uses is_dir() + * @uses mkdir() + * @uses chmod() + * @uses is_writable() + * @uses basename() + * @uses move_uploaded_file() + * @uses ob_start() + * @uses ob_end_clean() + * @return boolean whether uploaded fiel is fine or not + */ + function checkUploadedFile() + { + if ($this->isReadable()) { + return true; + } + + /** + * it is not important if open_basedir is set - we just cannot read the file + * so we try to move it + if ('' != ini_get('open_basedir')) { + */ + + // check tmp dir config + if (empty($GLOBALS['cfg']['TempDir'])) { + $GLOBALS['cfg']['TempDir'] = 'tmp/'; + } + + // surprees warnings form beeing displayed, but not from beeing logged + ob_start(); + // check tmp dir + if (! is_dir($GLOBALS['cfg']['TempDir'])) { + // try to create the tmp directory + if (@mkdir($GLOBALS['cfg']['TempDir'], 0777)) { + chmod($GLOBALS['cfg']['TempDir'], 0777); + } else { + // create tmp dir failed + $this->_error_message = $GLOBALS['strFieldInsertFromFileTempDirNotExists']; + ob_end_clean(); + return false; + } + } + ob_end_clean(); + + if (! is_writable($GLOBALS['cfg']['TempDir'])) { + // cannot create directory or access, point user to FAQ 1.11 + $this->_error_message = $GLOBALS['strFieldInsertFromFileTempDirNotExists']; + return false; + } + + $new_file_to_upload = $GLOBALS['cfg']['TempDir'] . '/' . basename($this->getName()); + + // surprees warnings form beeing displayed, but not from beeing logged + // any file access outside of open_basedir will issue a warning + ob_start(); + $move_uploaded_file_result = move_uploaded_file($this->getName(), $new_file_to_upload); + ob_end_clean(); + if (! $move_uploaded_file_result) { + $this->_error_message = 'error while moving uploaded file'; + return false; + } + + $this->setName($new_file_to_upload); + $this->isTemp(true); + + if (! $this->isReadable()) { + $this->_error_message = 'cannot read (moved) upload file'; + return false; + } + + return true; + } +} +?> \ No newline at end of file diff --git a/libraries/tbl_replace_fields.inc.php b/libraries/tbl_replace_fields.inc.php index d8b882c28..448a0ff42 100644 --- a/libraries/tbl_replace_fields.inc.php +++ b/libraries/tbl_replace_fields.inc.php @@ -20,35 +20,15 @@ * @version $Id$ * vim: expandtab sw=4 ts=4 sts=4: * - * @uses $GLOBALS['cfg']['UploadDir'] - * @uses $_FILES * @uses $_REQUEST * @uses defined() * @uses define() - * @uses is_uploaded_file() - * @uses ini_get() - * @uses is_dir() - * @uses mkdir() - * @uses chmod() - * @uses is_writable() - * @uses is_readable() - * @uses move_uploaded_file() - * @uses basename() - * @uses preg_replace() * @uses bin2hex() - * @uses fread() - * @uses fopen() - * @uses filesize() - * @uses unlink() * @uses strlen() * @uses md5() * @uses implode() - * @uses PMA_IS_WINDOWS * @uses PMA_NO_VARIABLES_IMPORT - * @uses PMA_checkParameters() * @uses PMA_sqlAddslashes() - * @uses PMA_userDir() - * @todo there are also file uploads in the import dialog - possible we can merge this */ /** @@ -61,129 +41,21 @@ if (! defined('PMA_NO_VARIABLES_IMPORT')) { * Gets some core libraries */ require_once './libraries/common.lib.php'; +require_once './libraries/PMA_File.class.php'; -$valid_file_was_uploaded = false; +$file_to_insert = new PMA_File(); +$file_to_insert->checkTblChangeForm($key, $primary_key); -// Check if a multi-edit row was found -$me_fields_upload = - (isset($_FILES['fields_upload_' . $key]['tmp_name']['multi_edit'][$primary_key]) - ? $_FILES['fields_upload_' . $key]['tmp_name']['multi_edit'][$primary_key] - : (isset($_FILES['fields_upload_' . $key]['tmp_name']) - ? $_FILES['fields_upload_' . $key]['tmp_name'] - : 'none')); +$val = $file_to_insert->getContent(); -$me_fields_uploadlocal = - (isset($_REQUEST['fields_uploadlocal_' . $key]['multi_edit']) - ? $_REQUEST['fields_uploadlocal_' . $key]['multi_edit'][$primary_key] - : (isset($_REQUEST['fields_uploadlocal_' . $key]) - ? $_REQUEST['fields_uploadlocal_' . $key] - : null)); +if ($file_to_insert->isError()) { + $message .= $file_to_insert->getError(); +} +$file_to_insert->cleanUp(); -if ($me_fields_upload != 'none') { - // garvin: This fields content is a blob-file upload. - - $file_to_insert = false; - $unlink = false; - - if (is_uploaded_file($me_fields_upload)) { - // whether we insert form uploaded file ... - - $file_to_insert = $me_fields_upload; - - // If we are on a server with open_basedir, we must move the file - // before opening it. The FAQ 1.11 explains how to create the "./tmp" - // directory - if needed - if ('' != ini_get('open_basedir')) { - $tmp_subdir = (PMA_IS_WINDOWS ? 'tmp' : 'tmp'); - - if (! is_dir($tmp_subdir)) { - // try to create the tmp directory if not exists - if (@mkdir($tmp_subdir, 0777)) { - chmod($tmp_subdir, 0777); - } - } - - if (! is_writable($tmp_subdir)) { - // cannot create directory or access, point user to FAQ 1.11 - $message .= $GLOBALS['strFieldInsertFromFileTempDirNotExists'] . '
'; - // if we cannot move the file don't change blob fields - $file_to_insert = false; - } else { - $new_file_to_upload = $tmp_subdir . basename($file_to_insert); - - move_uploaded_file($file_to_insert, $new_file_to_upload); - - $file_to_insert = $new_file_to_upload; - $unlink = true; - unset($new_file_to_upload); - } - unset($tmp_subdir); - } - } elseif (! empty($me_fields_uploadlocal)) { - // ... or selected file from $cfg['UploadDir'] - - $file_to_insert = PMA_userDir($GLOBALS['cfg']['UploadDir']) . preg_replace('@\.\.*@', '.', $me_fields_uploadlocal); - - if (! is_readable($file_to_insert)) { - $file_to_insert = false; - } - } - // garvin: else: Post-field contains no data. Blob-fields are preserved, see below. ($protected$) - - if ($file_to_insert) { - $val = ''; - // check if file is not empty - if (function_exists('file_get_contents')) { - $val = file_get_contents($file_to_insert); - } elseif ($file_to_insert_size = filesize($file_to_insert)) { - $val = fread(fopen($file_to_insert, 'rb'), $file_to_insert_size); - } - - if (! empty($val)) { - $val = '0x' . bin2hex($val); - $seen_binary = true; - $valid_file_was_uploaded = true; - } - - if ($unlink == true) { - unlink($file_to_insert); - } - } - - unset($file_to_insert, $file_to_insert_size, $unlink); -} elseif (isset($_FILES['fields_upload_' . $key]['error']['multi_edit'][$primary_key])) { - // check for file upload errors - switch ($_FILES['fields_upload_' . $key]['error']['multi_edit'][$primary_key]) { - // cybot_tm: we do not use the PHP constants here cause not all constants - // are defined in all versions of PHP - but the correct constants names - // are given as comment - case 0: //UPLOAD_ERR_OK: - case 4: //UPLOAD_ERR_NO_FILE: - break; - case 1: //UPLOAD_ERR_INI_SIZE: - $message .= $GLOBALS['strUploadErrorIniSize'] . '
'; - break; - case 2: //UPLOAD_ERR_FORM_SIZE: - $message .= $GLOBALS['strUploadErrorFormSize'] . '
'; - break; - case 3: //UPLOAD_ERR_PARTIAL: - $message .= $GLOBALS['strUploadErrorPartial'] . '
'; - break; - case 6: //UPLOAD_ERR_NO_TMP_DIR: - $message .= $GLOBALS['strUploadErrorNoTempDir'] . '
'; - break; - case 7: //UPLOAD_ERR_CANT_WRITE: - $message .= $GLOBALS['strUploadErrorCantWrite'] . '
'; - break; - case 8: //UPLOAD_ERR_EXTENSION: - $message .= $GLOBALS['strUploadErrorExtension'] . '
'; - break; - default: - $message .= $GLOBALS['strUploadErrorUnknown'] . '
'; - } // end switch -} // end else - -if (false === $valid_file_was_uploaded) { +if (false !== $val) { + $seen_binary = true; +} else { // f i e l d v a l u e i n t h e f o r m @@ -250,5 +122,5 @@ if (false === $valid_file_was_uploaded) { $val = "''"; } } // end else (field value in the form) -unset($valid_file_was_uploaded, $me_fields_upload, $me_fields_uploadlocal, $type, $f); +unset($type, $f); ?>