diff options
Diffstat (limited to 'program/lib/Roundcube/rcube_db.php')
-rw-r--r-- | program/lib/Roundcube/rcube_db.php | 211 |
1 files changed, 145 insertions, 66 deletions
diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index 086a38ab4..852070073 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -47,6 +47,7 @@ class rcube_db 'identifier_end' => '"', ); + const DEBUG_LINE_LENGTH = 4096; /** * Factory, returns driver-specific instance of the class @@ -70,7 +71,7 @@ class rcube_db $driver = isset($driver_map[$driver]) ? $driver_map[$driver] : $driver; $class = "rcube_db_$driver"; - if (!class_exists($class)) { + if (!$driver || !class_exists($class)) { rcube::raise_error(array('code' => 600, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => "Configuration error. Unsupported database driver: $driver"), @@ -99,27 +100,15 @@ class rcube_db $this->db_dsnw_array = self::parse_dsn($db_dsnw); $this->db_dsnr_array = self::parse_dsn($db_dsnr); - - // Initialize driver class - $this->init(); - } - - /** - * Initialization of the object with driver specific code - */ - protected function init() - { - // To be used by driver classes } /** * Connect to specific database * - * @param array $dsn DSN for DB connections - * - * @return PDO database handle + * @param array $dsn DSN for DB connections + * @param string $mode Connection mode (r|w) */ - protected function dsn_connect($dsn) + protected function dsn_connect($dsn, $mode) { $this->db_error = false; $this->db_error_msg = null; @@ -128,7 +117,7 @@ class rcube_db $dsn_string = $this->dsn_string($dsn); $dsn_options = $this->dsn_options($dsn); - if ($db_pconn) { + if ($this->db_pconn) { $dsn_options[PDO::ATTR_PERSISTENT] = true; } @@ -157,9 +146,10 @@ class rcube_db return null; } + $this->dbh = $dbh; + $this->db_mode = $mode; + $this->db_connected = true; $this->conn_configure($dsn, $dbh); - - return $dbh; } /** @@ -182,16 +172,6 @@ class rcube_db } /** - * Driver-specific database character set setting - * - * @param string $charset Character set name - */ - protected function set_charset($charset) - { - $this->query("SET NAMES 'utf8'"); - } - - /** * Connect to appropriate database depending on the operation * * @param string $mode Connection mode (r|w) @@ -218,23 +198,14 @@ class rcube_db $dsn = ($mode == 'r') ? $this->db_dsnr_array : $this->db_dsnw_array; - $this->dbh = $this->dsn_connect($dsn); - $this->db_connected = is_object($this->dbh); + $this->dsn_connect($dsn, $mode); // use write-master when read-only fails - if (!$this->db_connected && $mode == 'r') { - $mode = 'w'; - $this->dbh = $this->dsn_connect($this->db_dsnw_array); - $this->db_connected = is_object($this->dbh); + if (!$this->db_connected && $mode == 'r' && $this->is_replicated()) { + $this->dsn_connect($this->db_dsnw_array, 'w'); } - if ($this->db_connected) { - $this->db_mode = $mode; - $this->set_charset('utf8'); - } - else { - $this->conn_failure = true; - } + $this->conn_failure = !$this->db_connected; } /** @@ -255,6 +226,11 @@ class rcube_db protected function debug($query) { if ($this->options['debug_mode']) { + if (($len = strlen($query)) > self::DEBUG_LINE_LENGTH) { + $diff = $len - self::DEBUG_LINE_LENGTH; + $query = substr($query, 0, self::DEBUG_LINE_LENGTH) + . "... [truncated $diff bytes]"; + } rcube::write_log('sql', '[' . (++$this->db_index) . '] ' . $query . ';'); } } @@ -362,8 +338,10 @@ class rcube_db */ protected function _query($query, $offset, $numrows, $params) { + $query = trim($query); + // Read or write ? - $mode = preg_match('/^(select|show)/i', ltrim($query)) ? 'r' : 'w'; + $mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w'; $this->db_connect($mode); @@ -405,21 +383,25 @@ class rcube_db $this->db_error_msg = null; // send query - $query = $this->dbh->query($query); + $result = $this->dbh->query($query); - if ($query === false) { + if ($result === false) { $error = $this->dbh->errorInfo(); - $this->db_error = true; - $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]); - rcube::raise_error(array('code' => 500, 'type' => 'db', - 'line' => __LINE__, 'file' => __FILE__, - 'message' => $this->db_error_msg), true, false); + if (empty($this->options['ignore_key_errors']) || $error[0] != '23000') { + $this->db_error = true; + $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]); + + rcube::raise_error(array('code' => 500, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => $this->db_error_msg . " (SQL Query: $query)" + ), true, false); + } } - $this->last_result = $query; + $this->last_result = $result; - return $query; + return $result; } /** @@ -439,6 +421,32 @@ class rcube_db } /** + * Get number of rows for a SQL query + * If no query handle is specified, the last query will be taken as reference + * + * @param mixed $result Optional query handle + * @return mixed Number of rows or false on failure + * @deprecated This method shows very poor performance and should be avoided. + */ + public function num_rows($result = null) + { + if ($result || ($result === null && ($result = $this->last_result))) { + // repeat query with SELECT COUNT(*) ... + if (preg_match('/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/ims', $result->queryString, $m)) { + $query = $this->dbh->query('SELECT COUNT(*) FROM ' . $m[1], PDO::FETCH_NUM); + return $query ? intval($query->fetchColumn(0)) : false; + } + else { + $num = count($result->fetchAll()); + $result->execute(); // re-execute query because there's no seek(0) + return $num; + } + } + + return false; + } + + /** * Get last inserted record ID * * @param string $table Table name (to find the incremented sequence) @@ -571,7 +579,7 @@ class rcube_db * Formats input so it can be safely used in a query * * @param mixed $input Value to quote - * @param string $type Type of data + * @param string $type Type of data (integer, bool, ident) * * @return string Quoted/converted string for use in query */ @@ -586,6 +594,10 @@ class rcube_db return 'NULL'; } + if ($type == 'ident') { + return $this->quote_identifier($input); + } + // create DB handle if not available if (!$this->dbh) { $this->db_connect('r'); @@ -604,6 +616,22 @@ class rcube_db } /** + * Escapes a string so it can be safely used in a query + * + * @param string $str A string to escape + * + * @return string Escaped string for use in a query + */ + public function escape($str) + { + if (is_null($str)) { + return 'NULL'; + } + + return substr($this->quote($str), 1, -1); + } + + /** * Quotes a string so it can be safely used as a table or column name * * @param string $str Value to quote @@ -618,6 +646,20 @@ class rcube_db } /** + * Escapes a string so it can be safely used in a query + * + * @param string $str A string to escape + * + * @return string Escaped string for use in a query + * @deprecated Replaced by rcube_db::escape + * @see rcube_db::escape + */ + public function escapeSimple($str) + { + return $this->escape($str); + } + + /** * Quotes a string so it can be safely used as a table or column name * * @param string $str Value to quote @@ -635,24 +677,32 @@ class rcube_db $name[] = $start . $elem . $end; } - return implode($name, '.'); + return implode($name, '.'); } /** * Return SQL function for current time and date * + * @param int $interval Optional interval (in seconds) to add/subtract + * * @return string SQL function to use in query */ - public function now() + public function now($interval = 0) { - return "now()"; + if ($interval) { + $add = ' ' . ($interval > 0 ? '+' : '-') . ' INTERVAL '; + $add .= $interval > 0 ? intval($interval) : intval($interval) * -1; + $add .= ' SECOND'; + } + + return "now()" . $add; } /** * Return list of elements for use with SQL's IN clause * * @param array $arr Input array - * @param string $type Type of data + * @param string $type Type of data (integer, bool, ident) * * @return string Comma-separated list of quoted values for use in query */ @@ -728,12 +778,19 @@ class rcube_db /** * Encodes non-UTF-8 characters in string/array/object (recursive) * - * @param mixed $input Data to fix + * @param mixed $input Data to fix + * @param bool $serialized Enable serialization * * @return mixed Properly UTF-8 encoded data */ - public static function encode($input) + public static function encode($input, $serialized = false) { + // use Base64 encoding to workaround issues with invalid + // or null characters in serialized string (#1489142) + if ($serialized) { + return base64_encode(serialize($input)); + } + if (is_object($input)) { foreach (get_object_vars($input) as $idx => $value) { $input->$idx = self::encode($value); @@ -744,6 +801,7 @@ class rcube_db foreach ($input as $idx => $value) { $input[$idx] = self::encode($value); } + return $input; } @@ -753,12 +811,24 @@ class rcube_db /** * Decodes encoded UTF-8 string/object/array (recursive) * - * @param mixed $input Input data + * @param mixed $input Input data + * @param bool $serialized Enable serialization * * @return mixed Decoded data */ - public static function decode($input) + public static function decode($input, $serialized = false) { + // use Base64 encoding to workaround issues with invalid + // or null characters in serialized string (#1489142) + if ($serialized) { + // Keep backward compatybility where base64 wasn't used + if (strpos(substr($input, 0, 16), ':') !== false) { + return self::decode(@unserialize($input)); + } + + return @unserialize(base64_decode($input)); + } + if (is_object($input)) { foreach (get_object_vars($input) as $idx => $value) { $input->$idx = self::decode($value); @@ -786,17 +856,26 @@ class rcube_db { $rcube = rcube::get_instance(); - // return table name if configured - $config_key = 'db_table_'.$table; - - if ($name = $rcube->config->get($config_key)) { - return $name; + // add prefix to the table name if configured + if ($prefix = $rcube->config->get('db_prefix')) { + return $prefix . $table; } return $table; } /** + * Set class option value + * + * @param string $name Option name + * @param mixed $value Option value + */ + public function set_option($name, $value) + { + $this->options[$name] = $value; + } + + /** * MDB2 DSN string parser * * @param string $sequence Secuence name |