Merge branch 'austinhuang0131-master'

This commit is contained in:
El RIDO 2022-01-31 06:40:13 +01:00
commit 1f872167c1
No known key found for this signature in database
GPG key ID: 0F5C940A6BD81F92
5 changed files with 235 additions and 107 deletions

View file

@ -7,6 +7,7 @@
* ADDED: Opt-out of federated learning of cohorts (FLoC) (#776) * ADDED: Opt-out of federated learning of cohorts (FLoC) (#776)
* ADDED: Configuration option to exempt IPs from the rate-limiter (#787) * ADDED: Configuration option to exempt IPs from the rate-limiter (#787)
* ADDED: Google Cloud Storage backend support (#795) * ADDED: Google Cloud Storage backend support (#795)
* ADDED: Oracle database support (#868)
* CHANGED: Language selection cookie only transmitted over HTTPS (#472) * CHANGED: Language selection cookie only transmitted over HTTPS (#472)
* CHANGED: Upgrading libraries to: random_compat 2.0.20 * CHANGED: Upgrading libraries to: random_compat 2.0.20
* CHANGED: Removed automatic `.ini` configuration file migration (#808) * CHANGED: Removed automatic `.ini` configuration file migration (#808)
@ -65,7 +66,7 @@
* CHANGED: Upgrading libraries to: DOMpurify 2.0.1 * CHANGED: Upgrading libraries to: DOMpurify 2.0.1
* FIXED: Enabling browsers without WASM to create pastes and read uncompressed ones (#454) * FIXED: Enabling browsers without WASM to create pastes and read uncompressed ones (#454)
* FIXED: Cloning related issues (#489, #491, #493, #494) * FIXED: Cloning related issues (#489, #491, #493, #494)
* FIXED: Enable file operation only when editing (#497) * FIXED: Enable file operation only when editing (#497)
* FIXED: Clicking 'New' on a previously submitted paste does not blank address bar (#354) * FIXED: Clicking 'New' on a previously submitted paste does not blank address bar (#354)
* FIXED: Clear address bar when create new paste from existing paste (#479) * FIXED: Clear address bar when create new paste from existing paste (#479)
* FIXED: Discussion section not hiding when new/clone paste is clicked on (#484) * FIXED: Discussion section not hiding when new/clone paste is clicked on (#484)
@ -228,7 +229,7 @@ encryption), i18n (translation, counterpart of i18n.php) and helper (stateless u
* FIXED: 2 minor corrections to avoid notices in php log. * FIXED: 2 minor corrections to avoid notices in php log.
* FIXED: Sources converted to UTF-8. * FIXED: Sources converted to UTF-8.
* **Alpha 0.14 (2012-04-20):** * **Alpha 0.14 (2012-04-20):**
* ADDED: GD presence is checked. * ADDED: GD presence is checked.
* CHANGED: Traffic limiter data files moved to data/ (→easier rights management) * CHANGED: Traffic limiter data files moved to data/ (→easier rights management)
* ADDED: "Burn after reading" implemented. Opening the URL will display the paste and immediately destroy it on server. * ADDED: "Burn after reading" implemented. Opening the URL will display the paste and immediately destroy it on server.
* **Alpha 0.13 (2012-04-18):** * **Alpha 0.13 (2012-04-18):**
@ -236,16 +237,16 @@ encryption), i18n (translation, counterpart of i18n.php) and helper (stateless u
* FIXED: $error not properly initialized in index.php * FIXED: $error not properly initialized in index.php
* **Alpha 0.12 (2012-04-18):** * **Alpha 0.12 (2012-04-18):**
* **DISCUSSIONS !** Now you can enable discussions on your pastes. Of course, posted comments and nickname are also encrypted and the server cannot see them. * **DISCUSSIONS !** Now you can enable discussions on your pastes. Of course, posted comments and nickname are also encrypted and the server cannot see them.
* This feature implies a change in storage format. You will have to delete all previous pastes in your ZeroBin. * This feature implies a change in storage format. You will have to delete all previous pastes in your ZeroBin.
* Added [[php:vizhash_gd|Vizhash]] as avatars, so you can match posters IP addresses without revealing them. (Same image = same IP). Of course the IP address cannot be deduced from the Vizhash. * Added [[php:vizhash_gd|Vizhash]] as avatars, so you can match posters IP addresses without revealing them. (Same image = same IP). Of course the IP address cannot be deduced from the Vizhash.
* Remaining time before expiration is now displayed. * Remaining time before expiration is now displayed.
* Explicit tags were added to CSS and jQuery selectors (eg. div#aaa instead of #aaa) to speed up browser. * Explicit tags were added to CSS and jQuery selectors (eg. div#aaa instead of #aaa) to speed up browser.
* Better cleaning of the URL (to make sure the key is not broken by some stupid redirection service) * Better cleaning of the URL (to make sure the key is not broken by some stupid redirection service)
* **Alpha 0.11 (2012-04-12):** * **Alpha 0.11 (2012-04-12):**
* Automatically ignore parameters (such as &utm_source=...) added //after// the anchor by some stupid Web 2.0 services. * Automatically ignore parameters (such as &utm_source=...) added //after// the anchor by some stupid Web 2.0 services.
* First public release. * First public release.
* **Alpha 0.10 (2012-04-12):** * **Alpha 0.10 (2012-04-12):**
* IE9 does not seem to correctly support ''pre-wrap'' either. Special handling mode activated for all version of IE<10. (Note: **ALL other browsers** correctly support this feature.) * IE9 does not seem to correctly support ''pre-wrap'' either. Special handling mode activated for all version of IE<10. (Note: **ALL other browsers** correctly support this feature.)
* **Alpha 0.9 (2012-04-11):** * **Alpha 0.9 (2012-04-11):**
* Oh bummer... IE 8 is as shitty as IE6/7: Its does not seem to support ''white-space:pre-wrap'' correctly. I had to activate the special handling mode. I still have to test IE 9. * Oh bummer... IE 8 is as shitty as IE6/7: Its does not seem to support ''white-space:pre-wrap'' correctly. I had to activate the special handling mode. I still have to test IE 9.
* **Alpha 0.8 (2012-04-11):** * **Alpha 0.8 (2012-04-11):**

View file

@ -29,6 +29,7 @@ Sébastien Sauvage - original idea and main developer
* Lucas Savva - configurable config file location, NixOS packaging * Lucas Savva - configurable config file location, NixOS packaging
* rodehoed - option to exempt ips from the rate-limiter * rodehoed - option to exempt ips from the rate-limiter
* Mark van Holsteijn - Google Cloud Storage backend * Mark van Holsteijn - Google Cloud Storage backend
* Austin Huang - Oracle database support
## Translations ## Translations
* Hexalyse - French * Hexalyse - French

View file

@ -190,8 +190,14 @@ CREATE TABLE prefix_config (
INSERT INTO prefix_config VALUES('VERSION', '1.3.5'); INSERT INTO prefix_config VALUES('VERSION', '1.3.5');
``` ```
In **PostgreSQL**, the data, attachment, nickname and vizhash columns needs to In **PostgreSQL**, the `data`, `attachment`, `nickname` and `vizhash` columns
be TEXT and not BLOB or MEDIUMBLOB. need to be `TEXT` and not `BLOB` or `MEDIUMBLOB`. The key names in brackets,
after `PRIMARY KEY`, need to be removed.
In **Oracle**, the `data`, `attachment`, `nickname` and `vizhash` columns need
to be `CLOB` and not `BLOB` or `MEDIUMBLOB`, the `id` column in the `config`
table needs to be `VARCHAR2(16)` and the `meta` column in the `paste` table
and the `value` column in the `config` table need to be `VARCHAR2(4000)`.
### Using Google Cloud Storage ### Using Google Cloud Storage
If you want to deploy PrivateBin in a serverless manner in the Google Cloud, you If you want to deploy PrivateBin in a serverless manner in the Google Cloud, you

View file

@ -97,6 +97,11 @@ class Database extends AbstractData
self::$_type = strtolower( self::$_type = strtolower(
substr($options['dsn'], 0, strpos($options['dsn'], ':')) substr($options['dsn'], 0, strpos($options['dsn'], ':'))
); );
// MySQL uses backticks to quote identifiers by default,
// tell it to expect ANSI SQL double quotes
if (self::$_type === 'mysql' && defined('PDO::MYSQL_ATTR_INIT_COMMAND')) {
$options['opt'][PDO::MYSQL_ATTR_INIT_COMMAND] = "SET sql_mode='ANSI_QUOTES'";
}
$tableQuery = self::_getTableQuery(self::$_type); $tableQuery = self::_getTableQuery(self::$_type);
self::$_db = new PDO( self::$_db = new PDO(
$options['dsn'], $options['dsn'],
@ -200,8 +205,8 @@ class Database extends AbstractData
} }
try { try {
return self::_exec( return self::_exec(
'INSERT INTO ' . self::_sanitizeIdentifier('paste') . 'INSERT INTO "' . self::_sanitizeIdentifier('paste') .
' VALUES(?,?,?,?,?,?,?,?,?)', '" VALUES(?,?,?,?,?,?,?,?,?)',
array( array(
$pasteid, $pasteid,
$isVersion1 ? $paste['data'] : Json::encode($paste), $isVersion1 ? $paste['data'] : Json::encode($paste),
@ -235,8 +240,8 @@ class Database extends AbstractData
self::$_cache[$pasteid] = false; self::$_cache[$pasteid] = false;
try { try {
$paste = self::_select( $paste = self::_select(
'SELECT * FROM ' . self::_sanitizeIdentifier('paste') . 'SELECT * FROM "' . self::_sanitizeIdentifier('paste') .
' WHERE dataid = ?', array($pasteid), true '" WHERE "dataid" = ?', array($pasteid), true
); );
} catch (Exception $e) { } catch (Exception $e) {
$paste = false; $paste = false;
@ -297,12 +302,12 @@ class Database extends AbstractData
public function delete($pasteid) public function delete($pasteid)
{ {
self::_exec( self::_exec(
'DELETE FROM ' . self::_sanitizeIdentifier('paste') . 'DELETE FROM "' . self::_sanitizeIdentifier('paste') .
' WHERE dataid = ?', array($pasteid) '" WHERE "dataid" = ?', array($pasteid)
); );
self::_exec( self::_exec(
'DELETE FROM ' . self::_sanitizeIdentifier('comment') . 'DELETE FROM "' . self::_sanitizeIdentifier('comment') .
' WHERE pasteid = ?', array($pasteid) '" WHERE "pasteid" = ?', array($pasteid)
); );
if ( if (
array_key_exists($pasteid, self::$_cache) array_key_exists($pasteid, self::$_cache)
@ -357,8 +362,8 @@ class Database extends AbstractData
} }
try { try {
return self::_exec( return self::_exec(
'INSERT INTO ' . self::_sanitizeIdentifier('comment') . 'INSERT INTO "' . self::_sanitizeIdentifier('comment') .
' VALUES(?,?,?,?,?,?,?)', '" VALUES(?,?,?,?,?,?,?)',
array( array(
$commentid, $commentid,
$pasteid, $pasteid,
@ -384,13 +389,13 @@ class Database extends AbstractData
public function readComments($pasteid) public function readComments($pasteid)
{ {
$rows = self::_select( $rows = self::_select(
'SELECT * FROM ' . self::_sanitizeIdentifier('comment') . 'SELECT * FROM "' . self::_sanitizeIdentifier('comment') .
' WHERE pasteid = ?', array($pasteid) '" WHERE "pasteid" = ?', array($pasteid)
); );
// create comment list // create comment list
$comments = array(); $comments = array();
if (count($rows)) { if (is_array($rows) && count($rows)) {
foreach ($rows as $row) { foreach ($rows as $row) {
$i = $this->getOpenSlot($comments, (int) $row['postdate']); $i = $this->getOpenSlot($comments, (int) $row['postdate']);
$data = Json::decode($row['data']); $data = Json::decode($row['data']);
@ -429,8 +434,8 @@ class Database extends AbstractData
{ {
try { try {
return (bool) self::_select( return (bool) self::_select(
'SELECT dataid FROM ' . self::_sanitizeIdentifier('comment') . 'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('comment') .
' WHERE pasteid = ? AND parentid = ? AND dataid = ?', '" WHERE "pasteid" = ? AND "parentid" = ? AND "dataid" = ?',
array($pasteid, $parentid, $commentid), true array($pasteid, $parentid, $commentid), true
); );
} catch (Exception $e) { } catch (Exception $e) {
@ -458,8 +463,8 @@ class Database extends AbstractData
} }
} }
return self::_exec( return self::_exec(
'UPDATE ' . self::_sanitizeIdentifier('config') . 'UPDATE "' . self::_sanitizeIdentifier('config') .
' SET value = ? WHERE id = ?', '" SET "value" = ? WHERE "id" = ?',
array($value, strtoupper($namespace)) array($value, strtoupper($namespace))
); );
} }
@ -479,8 +484,8 @@ class Database extends AbstractData
if ($value === '') { if ($value === '') {
// initialize the row, so that setValue can rely on UPDATE queries // initialize the row, so that setValue can rely on UPDATE queries
self::_exec( self::_exec(
'INSERT INTO ' . self::_sanitizeIdentifier('config') . 'INSERT INTO "' . self::_sanitizeIdentifier('config') .
' VALUES(?,?)', '" VALUES(?,?)',
array($configKey, '') array($configKey, '')
); );
@ -517,11 +522,12 @@ class Database extends AbstractData
{ {
$pastes = array(); $pastes = array();
$rows = self::_select( $rows = self::_select(
'SELECT dataid FROM ' . self::_sanitizeIdentifier('paste') . 'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('paste') .
' WHERE expiredate < ? AND expiredate != ? LIMIT ?', '" WHERE "expiredate" < ? AND "expiredate" != ? ' .
(self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'),
array(time(), 0, $batchsize) array(time(), 0, $batchsize)
); );
if (count($rows)) { if (is_array($rows) && count($rows)) {
foreach ($rows as $row) { foreach ($rows as $row) {
$pastes[] = $row['dataid']; $pastes[] = $row['dataid'];
} }
@ -542,7 +548,17 @@ class Database extends AbstractData
private static function _exec($sql, array $params) private static function _exec($sql, array $params)
{ {
$statement = self::$_db->prepare($sql); $statement = self::$_db->prepare($sql);
$result = $statement->execute($params); foreach ($params as $key => &$parameter) {
$position = $key + 1;
if (is_int($parameter)) {
$statement->bindParam($position, $parameter, PDO::PARAM_INT);
} elseif (strlen($parameter) >= 4000) {
$statement->bindParam($position, $parameter, PDO::PARAM_STR, strlen($parameter));
} else {
$statement->bindParam($position, $parameter);
}
}
$result = $statement->execute();
$statement->closeCursor(); $statement->closeCursor();
return $result; return $result;
} }
@ -562,10 +578,24 @@ class Database extends AbstractData
{ {
$statement = self::$_db->prepare($sql); $statement = self::$_db->prepare($sql);
$statement->execute($params); $statement->execute($params);
$result = $firstOnly ? if ($firstOnly) {
$statement->fetch(PDO::FETCH_ASSOC) : $result = $statement->fetch(PDO::FETCH_ASSOC);
$statement->fetchAll(PDO::FETCH_ASSOC); } elseif (self::$_type === 'oci') {
// workaround for https://bugs.php.net/bug.php?id=46728
$result = array();
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
$result[] = array_map('self::_sanitizeClob', $row);
}
} else {
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
}
$statement->closeCursor(); $statement->closeCursor();
if (self::$_type === 'oci' && is_array($result)) {
// returned CLOB values are streams, convert these into strings
$result = $firstOnly ?
array_map('self::_sanitizeClob', $result) :
$result;
}
return $result; return $result;
} }
@ -598,14 +628,15 @@ class Database extends AbstractData
{ {
switch ($type) { switch ($type) {
case 'ibm': case 'ibm':
$sql = 'SELECT tabname FROM SYSCAT.TABLES '; $sql = 'SELECT "tabname" FROM "SYSCAT"."TABLES"';
break; break;
case 'informix': case 'informix':
$sql = 'SELECT tabname FROM systables '; $sql = 'SELECT "tabname" FROM "systables"';
break; break;
case 'mssql': case 'mssql':
$sql = 'SELECT name FROM sysobjects ' // U: tables created by the user
. "WHERE type = 'U' ORDER BY name"; $sql = 'SELECT "name" FROM "sysobjects" '
. 'WHERE "type" = \'U\' ORDER BY "name"';
break; break;
case 'mysql': case 'mysql':
$sql = 'SHOW TABLES'; $sql = 'SHOW TABLES';
@ -614,23 +645,23 @@ class Database extends AbstractData
$sql = 'SELECT table_name FROM all_tables'; $sql = 'SELECT table_name FROM all_tables';
break; break;
case 'pgsql': case 'pgsql':
$sql = 'SELECT c.relname AS table_name ' $sql = 'SELECT c."relname" AS "table_name" '
. 'FROM pg_class c, pg_user u ' . 'FROM "pg_class" c, "pg_user" u '
. "WHERE c.relowner = u.usesysid AND c.relkind = 'r' " . 'WHERE c."relowner" = u."usesysid" AND c."relkind" = \'r\' '
. 'AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) ' . 'AND NOT EXISTS (SELECT 1 FROM "pg_views" WHERE "viewname" = c."relname") '
. "AND c.relname !~ '^(pg_|sql_)' " . "AND c.\"relname\" !~ '^(pg_|sql_)' "
. 'UNION ' . 'UNION '
. 'SELECT c.relname AS table_name ' . 'SELECT c."relname" AS "table_name" '
. 'FROM pg_class c ' . 'FROM "pg_class" c '
. "WHERE c.relkind = 'r' " . "WHERE c.\"relkind\" = 'r' "
. 'AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) ' . '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 NOT EXISTS (SELECT 1 FROM "pg_user" WHERE "usesysid" = c."relowner") '
. "AND c.relname !~ '^pg_'"; . "AND c.\"relname\" !~ '^pg_'";
break; break;
case 'sqlite': case 'sqlite':
$sql = "SELECT name FROM sqlite_master WHERE type='table' " $sql = 'SELECT "name" FROM "sqlite_master" WHERE "type"=\'table\' '
. 'UNION ALL SELECT name FROM sqlite_temp_master ' . 'UNION ALL SELECT "name" FROM "sqlite_temp_master" '
. "WHERE type='table' ORDER BY name"; . 'WHERE "type"=\'table\' ORDER BY "name"';
break; break;
default: default:
throw new Exception( throw new Exception(
@ -652,8 +683,8 @@ class Database extends AbstractData
{ {
try { try {
$row = self::_select( $row = self::_select(
'SELECT value FROM ' . self::_sanitizeIdentifier('config') . 'SELECT "value" FROM "' . self::_sanitizeIdentifier('config') .
' WHERE id = ?', array($key), true '" WHERE "id" = ?', array($key), true
); );
} catch (PDOException $e) { } catch (PDOException $e) {
return ''; return '';
@ -672,10 +703,14 @@ class Database extends AbstractData
private static function _getPrimaryKeyClauses($key = 'dataid') private static function _getPrimaryKeyClauses($key = 'dataid')
{ {
$main_key = $after_key = ''; $main_key = $after_key = '';
if (self::$_type === 'mysql') { switch (self::$_type) {
$after_key = ", PRIMARY KEY ($key)"; case 'mysql':
} else { case 'oci':
$main_key = ' PRIMARY KEY'; $after_key = ", PRIMARY KEY (\"$key\")";
break;
default:
$main_key = ' PRIMARY KEY';
break;
} }
return array($main_key, $after_key); return array($main_key, $after_key);
} }
@ -683,7 +718,7 @@ class Database extends AbstractData
/** /**
* get the data type, depending on the database driver * get the data type, depending on the database driver
* *
* PostgreSQL uses a different API for BLOBs then SQL, hence we use TEXT * PostgreSQL and OCI uses a different API for BLOBs then SQL, hence we use TEXT and CLOB
* *
* @access private * @access private
* @static * @static
@ -691,13 +726,20 @@ class Database extends AbstractData
*/ */
private static function _getDataType() private static function _getDataType()
{ {
return self::$_type === 'pgsql' ? 'TEXT' : 'BLOB'; switch (self::$_type) {
case 'oci':
return 'CLOB';
case 'pgsql':
return 'TEXT';
default:
return 'BLOB';
}
} }
/** /**
* get the attachment type, depending on the database driver * get the attachment type, depending on the database driver
* *
* PostgreSQL uses a different API for BLOBs then SQL, hence we use TEXT * PostgreSQL and OCI use different APIs for BLOBs then SQL, hence we use TEXT and CLOB
* *
* @access private * @access private
* @static * @static
@ -705,7 +747,33 @@ class Database extends AbstractData
*/ */
private static function _getAttachmentType() private static function _getAttachmentType()
{ {
return self::$_type === 'pgsql' ? 'TEXT' : 'MEDIUMBLOB'; switch (self::$_type) {
case 'oci':
return 'CLOB';
case 'pgsql':
return 'TEXT';
default:
return 'MEDIUMBLOB';
}
}
/**
* get the meta type, depending on the database driver
*
* OCI doesn't accept TEXT so it has to be VARCHAR2(4000)
*
* @access private
* @static
* @return string
*/
private static function _getMetaType()
{
switch (self::$_type) {
case 'oci':
return 'VARCHAR2(4000)';
default:
return 'TEXT';
}
} }
/** /**
@ -719,17 +787,18 @@ class Database extends AbstractData
list($main_key, $after_key) = self::_getPrimaryKeyClauses(); list($main_key, $after_key) = self::_getPrimaryKeyClauses();
$dataType = self::_getDataType(); $dataType = self::_getDataType();
$attachmentType = self::_getAttachmentType(); $attachmentType = self::_getAttachmentType();
$metaType = self::_getMetaType();
self::$_db->exec( self::$_db->exec(
'CREATE TABLE ' . self::_sanitizeIdentifier('paste') . ' ( ' . 'CREATE TABLE "' . self::_sanitizeIdentifier('paste') . '" ( ' .
"dataid CHAR(16) NOT NULL$main_key, " . "\"dataid\" CHAR(16) NOT NULL$main_key, " .
"data $attachmentType, " . "\"data\" $attachmentType, " .
'postdate INT, ' . '"postdate" INT, ' .
'expiredate INT, ' . '"expiredate" INT, ' .
'opendiscussion INT, ' . '"opendiscussion" INT, ' .
'burnafterreading INT, ' . '"burnafterreading" INT, ' .
'meta TEXT, ' . "\"meta\" $metaType, " .
"attachment $attachmentType, " . "\"attachment\" $attachmentType, " .
"attachmentname $dataType$after_key );" "\"attachmentname\" $dataType$after_key )"
); );
} }
@ -744,19 +813,35 @@ class Database extends AbstractData
list($main_key, $after_key) = self::_getPrimaryKeyClauses(); list($main_key, $after_key) = self::_getPrimaryKeyClauses();
$dataType = self::_getDataType(); $dataType = self::_getDataType();
self::$_db->exec( self::$_db->exec(
'CREATE TABLE ' . self::_sanitizeIdentifier('comment') . ' ( ' . 'CREATE TABLE "' . self::_sanitizeIdentifier('comment') . '" ( ' .
"dataid CHAR(16) NOT NULL$main_key, " . "\"dataid\" CHAR(16) NOT NULL$main_key, " .
'pasteid CHAR(16), ' . '"pasteid" CHAR(16), ' .
'parentid CHAR(16), ' . '"parentid" CHAR(16), ' .
"data $dataType, " . "\"data\" $dataType, " .
"nickname $dataType, " . "\"nickname\" $dataType, " .
"vizhash $dataType, " . "\"vizhash\" $dataType, " .
"postdate INT$after_key );" "\"postdate\" INT$after_key )"
);
self::$_db->exec(
'CREATE INDEX IF NOT EXISTS comment_parent ON ' .
self::_sanitizeIdentifier('comment') . '(pasteid);'
); );
if (self::$_type === 'oci') {
self::$_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 "' . self::_sanitizeIdentifier('comment') . '" ("pasteid")\';
exception
when already_exists or columns_indexed then
NULL;
end;'
);
} else {
self::$_db->exec(
'CREATE INDEX IF NOT EXISTS "comment_parent" ON "' .
self::_sanitizeIdentifier('comment') . '" ("pasteid")'
);
}
} }
/** /**
@ -768,17 +853,37 @@ class Database extends AbstractData
private static function _createConfigTable() private static function _createConfigTable()
{ {
list($main_key, $after_key) = self::_getPrimaryKeyClauses('id'); list($main_key, $after_key) = self::_getPrimaryKeyClauses('id');
$charType = self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)';
$textType = self::_getMetaType();
self::$_db->exec( self::$_db->exec(
'CREATE TABLE ' . self::_sanitizeIdentifier('config') . 'CREATE TABLE "' . self::_sanitizeIdentifier('config') .
" ( id CHAR(16) NOT NULL$main_key, value TEXT$after_key );" "\" ( \"id\" $charType NOT NULL$main_key, \"value\" $textType$after_key )"
); );
self::_exec( self::_exec(
'INSERT INTO ' . self::_sanitizeIdentifier('config') . 'INSERT INTO "' . self::_sanitizeIdentifier('config') .
' VALUES(?,?)', '" VALUES(?,?)',
array('VERSION', Controller::VERSION) array('VERSION', Controller::VERSION)
); );
} }
/**
* sanitizes CLOB values used with OCI
*
* From: https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field
*
* @access public
* @static
* @param int|string|resource $value
* @return int|string
*/
public static function _sanitizeClob($value)
{
if (is_resource($value)) {
$value = stream_get_contents($value);
}
return $value;
}
/** /**
* sanitizes identifiers * sanitizes identifiers
* *
@ -807,43 +912,46 @@ class Database extends AbstractData
case '0.21': case '0.21':
// create the meta column if necessary (pre 0.21 change) // create the meta column if necessary (pre 0.21 change)
try { try {
self::$_db->exec('SELECT meta FROM ' . self::_sanitizeIdentifier('paste') . ' LIMIT 1;'); self::$_db->exec(
'SELECT "meta" FROM "' . self::_sanitizeIdentifier('paste') . '" ' .
(self::$_type === 'oci' ? 'FETCH NEXT 1 ROWS ONLY' : 'LIMIT 1')
);
} catch (PDOException $e) { } catch (PDOException $e) {
self::$_db->exec('ALTER TABLE ' . self::_sanitizeIdentifier('paste') . ' ADD COLUMN meta TEXT;'); self::$_db->exec('ALTER TABLE "' . self::_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT');
} }
// SQLite only allows one ALTER statement at a time... // SQLite only allows one ALTER statement at a time...
self::$_db->exec( self::$_db->exec(
'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') .
" ADD COLUMN attachment $attachmentType;" "\" ADD COLUMN \"attachment\" $attachmentType"
); );
self::$_db->exec( self::$_db->exec(
'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . " ADD COLUMN attachmentname $dataType;" 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . "\" ADD COLUMN \"attachmentname\" $dataType"
); );
// SQLite doesn't support MODIFY, but it allows TEXT of similar // SQLite doesn't support MODIFY, but it allows TEXT of similar
// size as BLOB, so there is no need to change it there // size as BLOB, so there is no need to change it there
if (self::$_type !== 'sqlite') { if (self::$_type !== 'sqlite') {
self::$_db->exec( self::$_db->exec(
'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') .
" ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType;" "\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType"
); );
self::$_db->exec( self::$_db->exec(
'ALTER TABLE ' . self::_sanitizeIdentifier('comment') . 'ALTER TABLE "' . self::_sanitizeIdentifier('comment') .
" ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType, " . "\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType, " .
"MODIFY COLUMN nickname $dataType, MODIFY COLUMN vizhash $dataType;" "MODIFY COLUMN \"nickname\" $dataType, MODIFY COLUMN \"vizhash\" $dataType"
); );
} else { } else {
self::$_db->exec( self::$_db->exec(
'CREATE UNIQUE INDEX IF NOT EXISTS paste_dataid ON ' . 'CREATE UNIQUE INDEX IF NOT EXISTS "paste_dataid" ON "' .
self::_sanitizeIdentifier('paste') . '(dataid);' self::_sanitizeIdentifier('paste') . '" ("dataid")'
); );
self::$_db->exec( self::$_db->exec(
'CREATE UNIQUE INDEX IF NOT EXISTS comment_dataid ON ' . 'CREATE UNIQUE INDEX IF NOT EXISTS "comment_dataid" ON "' .
self::_sanitizeIdentifier('comment') . '(dataid);' self::_sanitizeIdentifier('comment') . '" ("dataid")'
); );
} }
self::$_db->exec( self::$_db->exec(
'CREATE INDEX IF NOT EXISTS comment_parent ON ' . 'CREATE INDEX IF NOT EXISTS "comment_parent" ON "' .
self::_sanitizeIdentifier('comment') . '(pasteid);' self::_sanitizeIdentifier('comment') . '" ("pasteid")'
); );
// no break, continue with updates for 0.22 and later // no break, continue with updates for 0.22 and later
case '1.3': case '1.3':
@ -852,15 +960,15 @@ class Database extends AbstractData
// to change it there // to change it there
if (self::$_type !== 'sqlite' && self::$_type !== 'pgsql') { if (self::$_type !== 'sqlite' && self::$_type !== 'pgsql') {
self::$_db->exec( self::$_db->exec(
'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') .
" MODIFY COLUMN data $attachmentType;" "\" MODIFY COLUMN \"data\" $attachmentType"
); );
} }
// no break, continue with updates for all newer versions // no break, continue with updates for all newer versions
default: default:
self::_exec( self::_exec(
'UPDATE ' . self::_sanitizeIdentifier('config') . 'UPDATE "' . self::_sanitizeIdentifier('config') .
' SET value = ? WHERE id = ?', '" SET "value" = ? WHERE "id" = ?',
array(Controller::VERSION, 'VERSION') array(Controller::VERSION, 'VERSION')
); );
} }

View file

@ -388,4 +388,16 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
$this->assertEquals(Controller::VERSION, $result['value']); $this->assertEquals(Controller::VERSION, $result['value']);
Helper::rmDir($this->_path); Helper::rmDir($this->_path);
} }
public function testOciClob()
{
$int = (int) random_bytes(1);
$string = random_bytes(10);
$clob = fopen('php://memory', 'r+');
fwrite($clob, $string);
rewind($clob);
$this->assertEquals($int, Database::_sanitizeClob($int));
$this->assertEquals($string, Database::_sanitizeClob($string));
$this->assertEquals($string, Database::_sanitizeClob($clob));
}
} }