PrivateBin/lib/Data/Database.php

939 lines
30 KiB
PHP
Raw Normal View History

<?php
/**
2016-07-11 17:58:15 +08:00
* PrivateBin
*
* a zero-knowledge paste bin
*
2016-07-11 17:58:15 +08:00
* @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
2023-09-12 01:36:45 +08:00
* @version 1.6.0
*/
2016-12-13 01:43:23 +08:00
2016-12-13 01:49:08 +08:00
namespace PrivateBin\Data;
2016-07-21 23:09:48 +08:00
use Exception;
use PDO;
use PDOException;
2018-07-29 21:17:35 +08:00
use PrivateBin\Controller;
use PrivateBin\Json;
2016-07-21 23:09:48 +08:00
/**
* Database
*
* Model for database access, implemented as a singleton.
*/
class Database extends AbstractData
{
/**
* instance of database connection
*
* @access private
* @var PDO
*/
private $_db;
/**
* table prefix
*
* @access private
* @var string
*/
private $_prefix = '';
/**
* database type
*
* @access private
* @var string
*/
private $_type = '';
/**
* instantiates a new Database data backend
*
* @access public
* @param array $options
* @throws Exception
* @return
*/
public function __construct(array $options)
{
// set table prefix if given
if (array_key_exists('tbl', $options)) {
$this->_prefix = $options['tbl'];
}
// initialize the db connection with new options
if (
array_key_exists('dsn', $options) &&
array_key_exists('usr', $options) &&
array_key_exists('pwd', $options) &&
array_key_exists('opt', $options)
) {
// set default options
$options['opt'][PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
$options['opt'][PDO::ATTR_EMULATE_PREPARES] = false;
$options['opt'][PDO::ATTR_PERSISTENT] = true;
$db_tables_exist = true;
// setup type and dabase connection
$this->_type = strtolower(
substr($options['dsn'], 0, strpos($options['dsn'], ':'))
);
// MySQL uses backticks to quote identifiers by default,
// tell it to expect ANSI SQL double quotes
if ($this->_type === 'mysql' && defined('PDO::MYSQL_ATTR_INIT_COMMAND')) {
$options['opt'][PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION sql_mode='ANSI_QUOTES'";
}
$tableQuery = $this->_getTableQuery($this->_type);
$this->_db = new PDO(
$options['dsn'],
$options['usr'],
$options['pwd'],
$options['opt']
);
// check if the database contains the required tables
$tables = $this->_db->query($tableQuery)->fetchAll(PDO::FETCH_COLUMN, 0);
// create paste table if necessary
if (!in_array($this->_sanitizeIdentifier('paste'), $tables)) {
$this->_createPasteTable();
$db_tables_exist = false;
}
// create comment table if necessary
if (!in_array($this->_sanitizeIdentifier('comment'), $tables)) {
$this->_createCommentTable();
$db_tables_exist = false;
}
// create config table if necessary
$db_version = Controller::VERSION;
if (!in_array($this->_sanitizeIdentifier('config'), $tables)) {
$this->_createConfigTable();
// if we only needed to create the config table, the DB is older then 0.22
if ($db_tables_exist) {
$db_version = '0.21';
}
} else {
$db_version = $this->_getConfig('VERSION');
}
// update database structure if necessary
if (version_compare($db_version, Controller::VERSION, '<')) {
$this->_upgradeDatabase($db_version);
}
} else {
throw new Exception(
'Missing configuration for key dsn, usr, pwd or opt in the section model_options, please check your configuration file', 6
);
}
}
/**
* Create a paste.
*
* @access public
* @param string $pasteid
* @param array $paste
* @return bool
*/
public function create($pasteid, array $paste)
{
$expire_date = 0;
$opendiscussion = $burnafterreading = false;
$attachment = $attachmentname = null;
$meta = $paste['meta'];
$isVersion1 = array_key_exists('data', $paste);
list($createdKey) = $this->_getVersionedKeys($isVersion1 ? 1 : 2);
$created = (int) $meta[$createdKey];
unset($meta[$createdKey], $paste['meta']);
if (array_key_exists('expire_date', $meta)) {
$expire_date = (int) $meta['expire_date'];
unset($meta['expire_date']);
}
if (array_key_exists('opendiscussion', $meta)) {
$opendiscussion = $meta['opendiscussion'];
2015-09-22 04:32:52 +08:00
unset($meta['opendiscussion']);
}
if (array_key_exists('burnafterreading', $meta)) {
$burnafterreading = $meta['burnafterreading'];
2015-09-22 04:32:52 +08:00
unset($meta['burnafterreading']);
}
if ($isVersion1) {
if (array_key_exists('attachment', $meta)) {
$attachment = $meta['attachment'];
unset($meta['attachment']);
}
if (array_key_exists('attachmentname', $meta)) {
$attachmentname = $meta['attachmentname'];
unset($meta['attachmentname']);
}
} else {
$opendiscussion = $paste['adata'][2];
$burnafterreading = $paste['adata'][3];
}
2021-06-13 16:44:26 +08:00
try {
return $this->_exec(
'INSERT INTO "' . $this->_sanitizeIdentifier('paste') .
'" VALUES(?,?,?,?,?,?,?,?,?)',
2021-06-13 16:44:26 +08:00
array(
$pasteid,
$isVersion1 ? $paste['data'] : Json::encode($paste),
2021-06-13 16:44:26 +08:00
$created,
$expire_date,
(int) $opendiscussion,
(int) $burnafterreading,
Json::encode($meta),
$attachment,
$attachmentname,
)
);
} catch (Exception $e) {
return false;
}
}
/**
* Read a paste.
*
* @access public
* @param string $pasteid
* @return array|false
*/
public function read($pasteid)
{
2021-06-13 18:40:06 +08:00
try {
2022-11-05 04:19:47 +08:00
$row = $this->_select(
'SELECT * FROM "' . $this->_sanitizeIdentifier('paste') .
'" WHERE "dataid" = ?', array($pasteid), true
2021-06-13 18:40:06 +08:00
);
} catch (Exception $e) {
2022-11-05 04:19:47 +08:00
$row = false;
2021-06-13 18:40:06 +08:00
}
2022-11-05 04:19:47 +08:00
if ($row === false) {
return false;
}
// create array
2022-11-05 04:19:47 +08:00
$data = Json::decode($row['data']);
$isVersion2 = array_key_exists('v', $data) && $data['v'] >= 2;
if ($isVersion2) {
2022-11-05 04:19:47 +08:00
$paste = $data;
list($createdKey) = $this->_getVersionedKeys(2);
} else {
2022-11-05 04:19:47 +08:00
$paste = array('data' => $row['data']);
list($createdKey) = $this->_getVersionedKeys(1);
}
try {
2022-11-05 04:19:47 +08:00
$row['meta'] = Json::decode($row['meta']);
} catch (Exception $e) {
2022-11-05 04:19:47 +08:00
$row['meta'] = array();
}
2022-11-05 04:19:47 +08:00
$row = self::upgradePreV1Format($row);
$paste['meta'] = $row['meta'];
$paste['meta'][$createdKey] = (int) $row['postdate'];
$expire_date = (int) $row['expiredate'];
if ($expire_date > 0) {
2022-11-05 04:19:47 +08:00
$paste['meta']['expire_date'] = $expire_date;
}
if ($isVersion2) {
2022-11-05 04:19:47 +08:00
return $paste;
}
// support v1 attachments
2022-11-05 04:19:47 +08:00
if (array_key_exists('attachment', $row) && !empty($row['attachment'])) {
$paste['attachment'] = $row['attachment'];
if (array_key_exists('attachmentname', $row) && !empty($row['attachmentname'])) {
$paste['attachmentname'] = $row['attachmentname'];
}
}
2022-11-05 04:19:47 +08:00
if ($row['opendiscussion']) {
$paste['meta']['opendiscussion'] = true;
}
2022-11-05 04:19:47 +08:00
if ($row['burnafterreading']) {
$paste['meta']['burnafterreading'] = true;
}
2022-11-05 04:19:47 +08:00
return $paste;
}
/**
* Delete a paste and its discussion.
*
* @access public
* @param string $pasteid
*/
public function delete($pasteid)
{
$this->_exec(
'DELETE FROM "' . $this->_sanitizeIdentifier('paste') .
'" WHERE "dataid" = ?', array($pasteid)
);
$this->_exec(
'DELETE FROM "' . $this->_sanitizeIdentifier('comment') .
'" WHERE "pasteid" = ?', array($pasteid)
);
}
/**
* Test if a paste exists.
*
* @access public
* @param string $pasteid
2016-08-09 19:07:11 +08:00
* @return bool
*/
public function exists($pasteid)
{
2022-11-05 04:19:47 +08:00
try {
$row = $this->_select(
2022-11-06 14:40:39 +08:00
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') .
2022-11-05 04:19:47 +08:00
'" WHERE "dataid" = ?', array($pasteid), true
);
} catch (Exception $e) {
2022-11-06 14:40:39 +08:00
return false;
}
2022-11-05 04:19:47 +08:00
return (bool) $row;
}
/**
* Create a comment in a paste.
*
* @access public
* @param string $pasteid
* @param string $parentid
* @param string $commentid
* @param array $comment
* @return bool
*/
public function createComment($pasteid, $parentid, $commentid, array $comment)
{
if (array_key_exists('data', $comment)) {
$version = 1;
$data = $comment['data'];
} else {
$version = 2;
$data = Json::encode($comment);
}
list($createdKey, $iconKey) = $this->_getVersionedKeys($version);
2019-05-11 03:45:34 +08:00
$meta = $comment['meta'];
unset($comment['meta']);
foreach (array('nickname', $iconKey) as $key) {
if (!array_key_exists($key, $meta)) {
$meta[$key] = null;
}
}
2021-06-13 16:44:26 +08:00
try {
return $this->_exec(
'INSERT INTO "' . $this->_sanitizeIdentifier('comment') .
'" VALUES(?,?,?,?,?,?,?)',
2021-06-13 16:44:26 +08:00
array(
$commentid,
$pasteid,
$parentid,
$data,
$meta['nickname'],
$meta[$iconKey],
$meta[$createdKey],
)
);
} catch (Exception $e) {
return false;
}
}
/**
* Read all comments of paste.
*
* @access public
* @param string $pasteid
* @return array
*/
public function readComments($pasteid)
{
$rows = $this->_select(
'SELECT * FROM "' . $this->_sanitizeIdentifier('comment') .
'" WHERE "pasteid" = ?', array($pasteid)
);
// create comment list
$comments = array();
2022-01-24 04:45:22 +08:00
if (is_array($rows) && count($rows)) {
foreach ($rows as $row) {
$i = $this->getOpenSlot($comments, (int) $row['postdate']);
$data = Json::decode($row['data']);
if (array_key_exists('v', $data) && $data['v'] >= 2) {
$version = 2;
$comments[$i] = $data;
} else {
$version = 1;
$comments[$i] = array('data' => $row['data']);
}
list($createdKey, $iconKey) = $this->_getVersionedKeys($version);
$comments[$i]['id'] = $row['dataid'];
$comments[$i]['parentid'] = $row['parentid'];
$comments[$i]['meta'] = array($createdKey => (int) $row['postdate']);
foreach (array('nickname' => 'nickname', 'vizhash' => $iconKey) as $rowKey => $commentKey) {
if (array_key_exists($rowKey, $row) && !empty($row[$rowKey])) {
$comments[$i]['meta'][$commentKey] = $row[$rowKey];
}
}
}
ksort($comments);
}
return $comments;
}
/**
* Test if a comment exists.
*
* @access public
* @param string $pasteid
* @param string $parentid
* @param string $commentid
2016-08-09 19:07:11 +08:00
* @return bool
*/
public function existsComment($pasteid, $parentid, $commentid)
{
2021-06-13 16:44:26 +08:00
try {
return (bool) $this->_select(
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('comment') .
'" WHERE "pasteid" = ? AND "parentid" = ? AND "dataid" = ?',
2021-06-13 16:44:26 +08:00
array($pasteid, $parentid, $commentid), true
);
} catch (Exception $e) {
return false;
}
}
/**
* Save a value.
*
* @access public
* @param string $value
* @param string $namespace
* @param string $key
* @return bool
*/
public function setValue($value, $namespace, $key = '')
{
if ($namespace === 'traffic_limiter') {
$this->_last_cache[$key] = $value;
try {
$value = Json::encode($this->_last_cache);
} catch (Exception $e) {
return false;
}
}
return $this->_exec(
'UPDATE "' . $this->_sanitizeIdentifier('config') .
'" SET "value" = ? WHERE "id" = ?',
array($value, strtoupper($namespace))
);
}
/**
* Load a value.
*
* @access public
* @param string $namespace
* @param string $key
* @return string
*/
public function getValue($namespace, $key = '')
{
$configKey = strtoupper($namespace);
2021-06-10 01:16:22 +08:00
$value = $this->_getConfig($configKey);
if ($value === '') {
// initialize the row, so that setValue can rely on UPDATE queries
$this->_exec(
'INSERT INTO "' . $this->_sanitizeIdentifier('config') .
'" VALUES(?,?)',
array($configKey, '')
);
// migrate filesystem based salt into database
$file = 'data' . DIRECTORY_SEPARATOR . 'salt.php';
if ($namespace === 'salt' && is_readable($file)) {
2022-11-01 23:38:06 +08:00
$fs = new Filesystem(array('dir' => 'data'));
$value = $fs->getValue('salt');
$this->setValue($value, 'salt');
@unlink($file);
return $value;
}
}
if ($value && $namespace === 'traffic_limiter') {
try {
$this->_last_cache = Json::decode($value);
} catch (Exception $e) {
$this->_last_cache = array();
}
if (array_key_exists($key, $this->_last_cache)) {
return $this->_last_cache[$key];
}
}
return (string) $value;
}
/**
* Returns up to batch size number of paste ids that have expired
*
* @access private
* @param int $batchsize
* @return array
*/
2019-05-11 03:52:14 +08:00
protected function _getExpiredPastes($batchsize)
{
$statement = $this->_db->prepare(
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') .
'" WHERE "expiredate" < ? AND "expiredate" != ? ' .
($this->_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?')
);
$statement->execute(array(time(), 0, $batchsize));
return $statement->fetchAll(PDO::FETCH_COLUMN, 0);
}
/**
* @inheritDoc
*/
public function getAllPastes()
{
2022-11-05 16:37:24 +08:00
return $this->_db->query(
2022-11-05 03:19:41 +08:00
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '"'
)->fetchAll(PDO::FETCH_COLUMN, 0);
}
/**
* execute a statement
*
* @access private
* @param string $sql
* @param array $params
* @throws PDOException
* @return bool
*/
private function _exec($sql, array $params)
{
$statement = $this->_db->prepare($sql);
foreach ($params as $key => &$parameter) {
2022-01-26 12:28:29 +08:00
$position = $key + 1;
if (is_int($parameter)) {
2022-01-26 12:28:29 +08:00
$statement->bindParam($position, $parameter, PDO::PARAM_INT);
} elseif (is_string($parameter) && strlen($parameter) >= 4000) {
2022-01-26 12:28:29 +08:00
$statement->bindParam($position, $parameter, PDO::PARAM_STR, strlen($parameter));
} else {
2022-01-26 12:28:29 +08:00
$statement->bindParam($position, $parameter);
}
}
$result = $statement->execute();
$statement->closeCursor();
return $result;
}
/**
* run a select statement
*
* @access private
* @param string $sql
* @param array $params
* @param bool $firstOnly if only the first row should be returned
* @throws PDOException
* @return array|false
*/
private function _select($sql, array $params, $firstOnly = false)
{
$statement = $this->_db->prepare($sql);
$statement->execute($params);
2022-01-24 04:24:28 +08:00
if ($firstOnly) {
$result = $statement->fetch(PDO::FETCH_ASSOC);
} elseif ($this->_type === 'oci') {
2022-01-24 04:24:28 +08:00
// workaround for https://bugs.php.net/bug.php?id=46728
$result = array();
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
$result[] = array_map('PrivateBin\Data\Database::_sanitizeClob', $row);
2022-01-24 04:24:28 +08:00
}
} else {
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
}
$statement->closeCursor();
if ($this->_type === 'oci' && is_array($result)) {
// returned CLOB values are streams, convert these into strings
$result = $firstOnly ?
array_map('PrivateBin\Data\Database::_sanitizeClob', $result) :
2022-01-25 12:59:22 +08:00
$result;
}
return $result;
}
/**
* get version dependent key names
*
* @access private
* @param int $version
* @return array
*/
private function _getVersionedKeys($version)
{
if ($version === 1) {
return array('postdate', 'vizhash');
}
return array('created', 'icon');
}
/**
* get table list query, depending on the database type
*
* @access private
* @param string $type
* @throws Exception
* @return string
*/
private function _getTableQuery($type)
{
switch ($type) {
case 'ibm':
$sql = 'SELECT "tabname" FROM "SYSCAT"."TABLES"';
break;
case 'informix':
$sql = 'SELECT "tabname" FROM "systables"';
break;
case 'mssql':
2022-01-26 12:26:47 +08:00
// U: tables created by the user
$sql = 'SELECT "name" FROM "sysobjects" '
. 'WHERE "type" = \'U\' ORDER BY "name"';
break;
case 'mysql':
$sql = 'SHOW TABLES';
break;
case 'oci':
$sql = 'SELECT table_name FROM all_tables';
break;
case 'pgsql':
$sql = 'SELECT c."relname" AS "table_name" '
. 'FROM "pg_class" c, "pg_user" u '
2022-01-31 04:42:24 +08:00
. 'WHERE c."relowner" = u."usesysid" AND c."relkind" = \'r\' '
. 'AND NOT EXISTS (SELECT 1 FROM "pg_views" WHERE "viewname" = c."relname") '
. "AND c.\"relname\" !~ '^(pg_|sql_)' "
. 'UNION '
. 'SELECT c."relname" AS "table_name" '
. 'FROM "pg_class" c '
. "WHERE c.\"relkind\" = 'r' "
. 'AND NOT EXISTS (SELECT 1 FROM "pg_views" WHERE "viewname" = c."relname") '
. 'AND NOT EXISTS (SELECT 1 FROM "pg_user" WHERE "usesysid" = c."relowner") '
. "AND c.\"relname\" !~ '^pg_'";
break;
case 'sqlite':
$sql = 'SELECT "name" FROM "sqlite_master" WHERE "type"=\'table\' '
. 'UNION ALL SELECT "name" FROM "sqlite_temp_master" '
. 'WHERE "type"=\'table\' ORDER BY "name"';
break;
default:
throw new Exception(
"PDO type $type is currently not supported.", 5
);
}
return $sql;
}
/**
* get a value by key from the config table
*
* @access private
* @param string $key
* @return string
*/
private function _getConfig($key)
{
2021-06-13 18:40:06 +08:00
try {
$row = $this->_select(
'SELECT "value" FROM "' . $this->_sanitizeIdentifier('config') .
'" WHERE "id" = ?', array($key), true
2021-06-13 18:40:06 +08:00
);
} catch (PDOException $e) {
return '';
}
return $row ? $row['value'] : '';
2022-01-19 00:21:25 +08:00
}
/**
* get the primary key clauses, depending on the database driver
*
* @access private
2019-05-05 14:53:40 +08:00
* @param string $key
* @return array
*/
private function _getPrimaryKeyClauses($key = 'dataid')
{
$main_key = $after_key = '';
switch ($this->_type) {
case 'mysql':
case 'oci':
$after_key = ", PRIMARY KEY (\"$key\")";
break;
default:
$main_key = ' PRIMARY KEY';
break;
}
return array($main_key, $after_key);
}
2019-05-05 14:53:40 +08:00
/**
* get the data type, depending on the database driver
*
* PostgreSQL and OCI uses a different API for BLOBs then SQL, hence we use TEXT and CLOB
*
2019-05-05 14:53:40 +08:00
* @access private
* @return string
*/
private function _getDataType()
2019-05-05 14:53:40 +08:00
{
switch ($this->_type) {
case 'oci':
return 'CLOB';
case 'pgsql':
return 'TEXT';
default:
return 'BLOB';
}
2019-05-05 14:53:40 +08:00
}
/**
* get the attachment type, depending on the database driver
*
2022-01-19 00:21:25 +08:00
* PostgreSQL and OCI use different APIs for BLOBs then SQL, hence we use TEXT and CLOB
*
2019-05-05 14:53:40 +08:00
* @access private
* @return string
*/
private function _getAttachmentType()
2019-05-05 14:53:40 +08:00
{
switch ($this->_type) {
case 'oci':
return 'CLOB';
case 'pgsql':
return 'TEXT';
default:
return 'MEDIUMBLOB';
}
2022-01-19 00:21:25 +08:00
}
/**
* get the meta type, depending on the database driver
*
* OCI doesn't accept TEXT so it has to be VARCHAR2(4000)
2022-01-19 00:21:25 +08:00
*
* @access private
* @return string
*/
private function _getMetaType()
2022-01-19 00:21:25 +08:00
{
switch ($this->_type) {
case 'oci':
return 'VARCHAR2(4000)';
default:
return 'TEXT';
}
2019-05-05 14:53:40 +08:00
}
/**
* create the paste table
*
* @access private
*/
private function _createPasteTable()
{
list($main_key, $after_key) = $this->_getPrimaryKeyClauses();
$dataType = $this->_getDataType();
$attachmentType = $this->_getAttachmentType();
$metaType = $this->_getMetaType();
$this->_db->exec(
'CREATE TABLE "' . $this->_sanitizeIdentifier('paste') . '" ( ' .
"\"dataid\" CHAR(16) NOT NULL$main_key, " .
2022-01-24 04:31:40 +08:00
"\"data\" $attachmentType, " .
'"postdate" INT, ' .
'"expiredate" INT, ' .
'"opendiscussion" INT, ' .
'"burnafterreading" INT, ' .
"\"meta\" $metaType, " .
"\"attachment\" $attachmentType, " .
"\"attachmentname\" $dataType$after_key )"
);
}
/**
* create the paste table
*
* @access private
*/
private function _createCommentTable()
{
list($main_key, $after_key) = $this->_getPrimaryKeyClauses();
$dataType = $this->_getDataType();
$this->_db->exec(
'CREATE TABLE "' . $this->_sanitizeIdentifier('comment') . '" ( ' .
"\"dataid\" CHAR(16) NOT NULL$main_key, " .
'"pasteid" CHAR(16), ' .
'"parentid" CHAR(16), ' .
"\"data\" $dataType, " .
"\"nickname\" $dataType, " .
"\"vizhash\" $dataType, " .
"\"postdate\" INT$after_key )"
);
if ($this->_type === 'oci') {
$this->_db->exec(
'declare
already_exists exception;
columns_indexed exception;
pragma exception_init( already_exists, -955 );
pragma exception_init(columns_indexed, -1408);
begin
execute immediate \'create index "comment_parent" on "' . $this->_sanitizeIdentifier('comment') . '" ("pasteid")\';
exception
when already_exists or columns_indexed then
NULL;
end;'
);
} else {
// CREATE INDEX IF NOT EXISTS not supported as of Oracle MySQL <= 8.0
$this->_db->exec(
'CREATE INDEX "' .
$this->_sanitizeIdentifier('comment_parent') . '" ON "' .
$this->_sanitizeIdentifier('comment') . '" ("pasteid")'
);
}
}
/**
* create the paste table
*
* @access private
*/
private function _createConfigTable()
{
list($main_key, $after_key) = $this->_getPrimaryKeyClauses('id');
$charType = $this->_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)';
$textType = $this->_getMetaType();
$this->_db->exec(
'CREATE TABLE "' . $this->_sanitizeIdentifier('config') .
"\" ( \"id\" $charType NOT NULL$main_key, \"value\" $textType$after_key )"
);
$this->_exec(
'INSERT INTO "' . $this->_sanitizeIdentifier('config') .
'" VALUES(?,?)',
2018-07-29 21:17:35 +08:00
array('VERSION', Controller::VERSION)
);
}
2016-07-11 20:15:20 +08:00
/**
* sanitizes CLOB values used with OCI
*
* From: https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field
2016-07-11 20:15:20 +08:00
*
* @access public
* @static
* @param int|string|resource $value
* @return int|string
2016-07-11 20:15:20 +08:00
*/
public static function _sanitizeClob($value)
2016-07-11 20:15:20 +08:00
{
if (is_resource($value)) {
$value = stream_get_contents($value);
}
return $value;
2022-01-18 09:06:26 +08:00
}
/**
* sanitizes identifiers
2022-01-18 09:06:26 +08:00
*
* @access private
* @param string $identifier
2022-01-18 09:06:26 +08:00
* @return string
*/
private function _sanitizeIdentifier($identifier)
2022-01-18 09:06:26 +08:00
{
return preg_replace('/[^A-Za-z0-9_]+/', '', $this->_prefix . $identifier);
2016-07-11 20:15:20 +08:00
}
/**
* upgrade the database schema from an old version
*
* @access private
* @param string $oldversion
*/
private function _upgradeDatabase($oldversion)
{
$dataType = $this->_getDataType();
$attachmentType = $this->_getAttachmentType();
switch ($oldversion) {
case '0.21':
// create the meta column if necessary (pre 0.21 change)
try {
$this->_db->exec(
'SELECT "meta" FROM "' . $this->_sanitizeIdentifier('paste') . '" ' .
($this->_type === 'oci' ? 'FETCH NEXT 1 ROWS ONLY' : 'LIMIT 1')
);
} catch (PDOException $e) {
$this->_db->exec('ALTER TABLE "' . $this->_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT');
}
// SQLite only allows one ALTER statement at a time...
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
"\" ADD COLUMN \"attachment\" $attachmentType"
);
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') . "\" ADD COLUMN \"attachmentname\" $dataType"
);
// SQLite doesn't support MODIFY, but it allows TEXT of similar
// size as BLOB, so there is no need to change it there
if ($this->_type !== 'sqlite') {
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
"\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType"
);
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('comment') .
"\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType, " .
"MODIFY COLUMN \"nickname\" $dataType, MODIFY COLUMN \"vizhash\" $dataType"
);
} else {
$this->_db->exec(
'CREATE UNIQUE INDEX IF NOT EXISTS "' .
$this->_sanitizeIdentifier('paste_dataid') . '" ON "' .
$this->_sanitizeIdentifier('paste') . '" ("dataid")'
);
$this->_db->exec(
'CREATE UNIQUE INDEX IF NOT EXISTS "' .
$this->_sanitizeIdentifier('comment_dataid') . '" ON "' .
$this->_sanitizeIdentifier('comment') . '" ("dataid")'
);
}
// CREATE INDEX IF NOT EXISTS not supported as of Oracle MySQL <= 8.0
$this->_db->exec(
'CREATE INDEX "' .
$this->_sanitizeIdentifier('comment_parent') . '" ON "' .
$this->_sanitizeIdentifier('comment') . '" ("pasteid")'
);
// no break, continue with updates for 0.22 and later
case '1.3':
// SQLite doesn't support MODIFY, but it allows TEXT of similar
// size as BLOB and PostgreSQL uses TEXT, so there is no need
// to change it there
if ($this->_type !== 'sqlite' && $this->_type !== 'pgsql') {
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
"\" MODIFY COLUMN \"data\" $attachmentType"
);
}
2020-01-09 02:31:06 +08:00
// no break, continue with updates for all newer versions
default:
$this->_exec(
'UPDATE "' . $this->_sanitizeIdentifier('config') .
'" SET "value" = ? WHERE "id" = ?',
2018-07-29 21:17:35 +08:00
array(Controller::VERSION, 'VERSION')
2016-08-25 15:53:31 +08:00
);
}
}
}