From 9a61e8fd48d193a79bb5203155c8303a719d3ff1 Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Fri, 28 Oct 2022 01:01:02 +0200 Subject: [PATCH 1/7] started script for storage backend migrations todo: GCS added GCS, no GLOBALS, two methods for saving pastes and comments use GLOBALS for verbosity again added getAllPastes() to all storage providers moved to bin, added --delete options, make use of $store->getAllPastes() added --delete-* options to help longopts without -- *sigh* fixed arguments drop singleton behaviour to allow multiple backends of the same type simultaneously remove singleton from Model, collapse loop in migrate.php comments is not indexed tests without data singleton fix exit if scandir() fails extended meta doc --- CREDITS.md | 1 + INSTALL.md | 8 +- bin/migrate.php | 194 ++++++++++++++ lib/Data/AbstractData.php | 62 +---- lib/Data/Database.php | 353 ++++++++++++------------- lib/Data/Filesystem.php | 159 ++++++----- lib/Data/GoogleCloudStorage.php | 111 ++++---- lib/Data/S3Storage.php | 151 ++++++----- lib/Model.php | 6 +- tst/ConfigurationTestGenerator.php | 2 +- tst/ControllerTest.php | 2 +- tst/ControllerWithDbTest.php | 2 +- tst/ControllerWithGcsTest.php | 2 +- tst/Data/DatabaseTest.php | 32 +-- tst/Data/FilesystemTest.php | 2 +- tst/Data/GoogleCloudStorageTest.php | 2 +- tst/JsonApiTest.php | 2 +- tst/ModelTest.php | 8 +- tst/Persistence/PurgeLimiterTest.php | 2 +- tst/Persistence/ServerSaltTest.php | 18 +- tst/Persistence/TrafficLimiterTest.php | 2 +- tst/Vizhash16x16Test.php | 2 +- 22 files changed, 658 insertions(+), 465 deletions(-) create mode 100644 bin/migrate.php diff --git a/CREDITS.md b/CREDITS.md index 97c11251..eb5f152e 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -31,6 +31,7 @@ * Austin Huang - Oracle database support * Felix J. Ogris - S3 Storage backend * Mounir Idrassi & J. Mozdzen - secure YOURLS integration +* Felix J. Ogris - script for data backend migrations, dropped singleton behaviour of data backends ## Translations * Hexalyse - French diff --git a/INSTALL.md b/INSTALL.md index f35d68bf..57777c94 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -38,10 +38,10 @@ install and configure PrivateBin on your server. It's available on ### Changing the Path In the index.php you can define a different `PATH`. This is useful to secure -your installation. You can move the configuration, data files, templates and PHP -libraries (directories cfg, doc, data, lib, tpl, tst and vendor) outside of your -document root. This new location must still be accessible to your webserver and -PHP process (see also +your installation. You can move the utilities, configuration, data files, +templates and PHP libraries (directories bin, cfg, doc, data, lib, tpl, tst and +vendor) outside of your document root. This new location must still be +accessible to your webserver and PHP process (see also [open_basedir setting](https://secure.php.net/manual/en/ini.core.php#ini.open-basedir)). > #### PATH Example diff --git a/bin/migrate.php b/bin/migrate.php new file mode 100644 index 00000000..193430e3 --- /dev/null +++ b/bin/migrate.php @@ -0,0 +1,194 @@ += $argc) { + dieerr("Missing source configuration directory"); +} +if ($delete_after && $delete_during) { + dieerr("--delete-after and --delete-during are mutually exclusive"); +} + +$srcconf = getConfig("source", $argv[$rest]); +$rest++; +$dstconf = getConfig("destination", ($rest < $argc ? $argv[$rest] : "")); + +if (($srcconf->getSection("model") == $dstconf->getSection("model")) && + ($srcconf->getSection("model_options") == $dstconf->getSection("model_options"))) { + dieerr("Source and destination storage configurations are identical"); +} + +$srcmodel = new Model($srcconf); +$srcstore = $srcmodel->getStore(); +$dstmodel = new Model($dstconf); +$dststore = $dstmodel->getStore(); +$ids = $srcstore->getAllPastes(); + +foreach ($ids as $id) { + debug("Reading paste id " . $id); + $paste = $srcstore->read($id); + $comments = $srcstore->readComments($id); + + savePaste($force_overwrite, $dryrun, $id, $paste, $dststore); + foreach ($comments as $comment) { + saveComment($force_overwrite, $dryrun, $id, $comment, $dststore); + } + if ($delete_during) { + deletePaste($dryrun, $id, $srcstore); + } +} + +if ($delete_after) { + foreach ($ids as $id) { + deletePaste($dryrun, $id, $srcstore); + } +} + +debug("Done."); + + +function deletePaste($dryrun, $pasteid, $srcstore) +{ + if (!$dryrun) { + debug("Deleting paste id " . $pasteid); + $srcstore->delete($pasteid); + } else { + debug("Would delete paste id " . $pasteid); + } +} + +function saveComment ($force_overwrite, $dryrun, $pasteid, $comment, $dststore) +{ + $parentid = $comment["parentid"]; + $commentid = $comment["id"]; + + if (!$dststore->existsComment($pasteid, $parentid, $commentid)) { + if (!$dryrun) { + debug("Saving paste id " . $pasteid . ", parent id " . + $parentid . ", comment id " . $commentid); + $dststore->createComment($pasteid, $parentid, $commentid, $comment); + } else { + debug("Would save paste id " . $pasteid . ", parent id " . + $parentid . ", comment id " . $commentid); + } + } else if ($force_overwrite) { + if (!$dryrun) { + debug("Overwriting paste id " . $pasteid . ", parent id " . + $parentid . ", comment id " . $commentid); + $dststore->createComment($pasteid, $parentid, $commentid, $comment); + } else { + debug("Would overwrite paste id " . $pasteid . ", parent id " . + $parentid . ", comment id " . $commentid); + } + } else { + if (!$dryrun) { + dieerr("Not overwriting paste id " . $pasteid . ", parent id " . + $parentid . ", comment id " . $commentid); + } else { + dieerr("Would not overwrite paste id " . $pasteid . ", parent id " . + $parentid . ", comment id " . $commentid); + } + } +} + +function savePaste ($force_overwrite, $dryrun, $pasteid, $paste, $dststore) +{ + if (!$dststore->exists($pasteid)) { + if (!$dryrun) { + debug("Saving paste id " . $pasteid); + $dststore->create($pasteid, $paste); + } else { + debug("Would save paste id " . $pasteid); + } + } else if ($force_overwrite) { + if (!$dryrun) { + debug("Overwriting paste id " . $pasteid); + $dststore->create($pasteid, $paste); + } else { + debug("Would overwrite paste id " . $pasteid); + } + } else { + if (!$dryrun) { + dieerr("Not overwriting paste id " . $pasteid); + } else { + dieerr("Would not overwrite paste id " . $pasteid); + } + } +} + +function getConfig ($target, $confdir) +{ + debug("Trying to load " . $target . " conf.php" . + ($confdir === "" ? "" : " from " . $confdir)); + + putenv("CONFIG_PATH=" . $confdir); + $conf = new Configuration; + putenv("CONFIG_PATH="); + + return $conf; +} + +function dieerr ($text) +{ + fprintf(STDERR, "ERROR: %s" . PHP_EOL, $text); + die(1); +} + +function debug ($text) { + if ($GLOBALS["verbose"]) { + printf("DEBUG: %s" . PHP_EOL, $text); + } +} + +function helpexit () +{ + print("migrate.php - Copy data between PrivateBin backends + +Usage: + php migrate.php [--delete-after] [--delete-during] [-f] [-n] [-v] srcconfdir + [] + php migrate.php [-h] + +Options: + --delete-after delete data from source after all pastes and comments have + successfully been copied to the destination + --delete-during delete data from source after the current paste and its + comments have successfully been copied to the destination + -f forcefully overwrite data which already exists at the + destination + -n dry run, do not copy data + -v be verbose + use storage backend configration from conf.php found in + this directory as source + optionally, use storage backend configration from conf.php + found in this directory as destination; defaults to: + " . PATH . "cfg" . DIRECTORY_SEPARATOR . "conf.php +"); + exit(); +} diff --git a/lib/Data/AbstractData.php b/lib/Data/AbstractData.php index 05353ce0..eddab09b 100644 --- a/lib/Data/AbstractData.php +++ b/lib/Data/AbstractData.php @@ -15,61 +15,17 @@ namespace PrivateBin\Data; /** * AbstractData * - * Abstract model for data access, implemented as a singleton. + * Abstract model for data access */ abstract class AbstractData { - /** - * Singleton instance - * - * @access protected - * @static - * @var AbstractData - */ - protected static $_instance = null; - /** * cache for the traffic limiter * - * @access private - * @static + * @access protected * @var array */ - protected static $_last_cache = array(); - - /** - * Enforce singleton, disable constructor - * - * Instantiate using {@link getInstance()}, this object implements the singleton pattern. - * - * @access protected - */ - protected function __construct() - { - } - - /** - * Enforce singleton, disable cloning - * - * Instantiate using {@link getInstance()}, this object implements the singleton pattern. - * - * @access private - */ - private function __clone() - { - } - - /** - * Get instance of singleton - * - * @access public - * @static - * @param array $options - * @return AbstractData - */ - public static function getInstance(array $options) - { - } + protected $_last_cache = array(); /** * Create a paste. @@ -150,9 +106,9 @@ abstract class AbstractData public function purgeValues($namespace, $time) { if ($namespace === 'traffic_limiter') { - foreach (self::$_last_cache as $key => $last_submission) { + foreach ($this->_last_cache as $key => $last_submission) { if ($last_submission <= $time) { - unset(self::$_last_cache[$key]); + unset($thi->_last_cache[$key]); } } } @@ -207,6 +163,14 @@ abstract class AbstractData } } + /** + * Returns all paste ids + * + * @access public + * @return array + */ + abstract public function getAllPastes(); + /** * Get next free slot for comment from postdate. * diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 3ca7b756..05952a95 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -30,54 +30,45 @@ class Database extends AbstractData * * @var array */ - private static $_cache = array(); + private $_cache = array(); /** * instance of database connection * * @access private - * @static * @var PDO */ - private static $_db; + private $_db; /** * table prefix * * @access private - * @static * @var string */ - private static $_prefix = ''; + private $_prefix = ''; /** * database type * * @access private - * @static * @var string */ - private static $_type = ''; + private $_type = ''; /** - * get instance of singleton + * instantiates a new Database data backend * * @access public - * @static * @param array $options * @throws Exception - * @return Database + * @return */ - public static function getInstance(array $options) + public function __construct(array $options) { - // if needed initialize the singleton - if (!(self::$_instance instanceof self)) { - self::$_instance = new self; - } - // set table prefix if given if (array_key_exists('tbl', $options)) { - self::$_prefix = $options['tbl']; + $this->_prefix = $options['tbl']; } // initialize the db connection with new options @@ -94,16 +85,16 @@ class Database extends AbstractData $db_tables_exist = true; // setup type and dabase connection - self::$_type = strtolower( + $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 (self::$_type === 'mysql' && defined('PDO::MYSQL_ATTR_INIT_COMMAND')) { + if ($this->_type === 'mysql' && defined('PDO::MYSQL_ATTR_INIT_COMMAND')) { $options['opt'][PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION sql_mode='ANSI_QUOTES'"; } - $tableQuery = self::_getTableQuery(self::$_type); - self::$_db = new PDO( + $tableQuery = $this->_getTableQuery($this->_type); + $this->_db = new PDO( $options['dsn'], $options['usr'], $options['pwd'], @@ -111,43 +102,41 @@ class Database extends AbstractData ); // check if the database contains the required tables - $tables = self::$_db->query($tableQuery)->fetchAll(PDO::FETCH_COLUMN, 0); + $tables = $this->_db->query($tableQuery)->fetchAll(PDO::FETCH_COLUMN, 0); // create paste table if necessary - if (!in_array(self::_sanitizeIdentifier('paste'), $tables)) { - self::_createPasteTable(); + if (!in_array($this->_sanitizeIdentifier('paste'), $tables)) { + $this->_createPasteTable(); $db_tables_exist = false; } // create comment table if necessary - if (!in_array(self::_sanitizeIdentifier('comment'), $tables)) { - self::_createCommentTable(); + 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(self::_sanitizeIdentifier('config'), $tables)) { - self::_createConfigTable(); + 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 = self::_getConfig('VERSION'); + $db_version = $this->_getConfig('VERSION'); } // update database structure if necessary if (version_compare($db_version, Controller::VERSION, '<')) { - self::_upgradeDatabase($db_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 ); } - - return self::$_instance; } /** @@ -161,12 +150,12 @@ class Database extends AbstractData public function create($pasteid, array $paste) { if ( - array_key_exists($pasteid, self::$_cache) + array_key_exists($pasteid, $this->_cache) ) { - if (false !== self::$_cache[$pasteid]) { + if (false !== $this->_cache[$pasteid]) { return false; } else { - unset(self::$_cache[$pasteid]); + unset($this->_cache[$pasteid]); } } @@ -175,7 +164,7 @@ class Database extends AbstractData $attachment = $attachmentname = null; $meta = $paste['meta']; $isVersion1 = array_key_exists('data', $paste); - list($createdKey) = self::_getVersionedKeys($isVersion1 ? 1 : 2); + list($createdKey) = $this->_getVersionedKeys($isVersion1 ? 1 : 2); $created = (int) $meta[$createdKey]; unset($meta[$createdKey], $paste['meta']); if (array_key_exists('expire_date', $meta)) { @@ -204,8 +193,8 @@ class Database extends AbstractData $burnafterreading = $paste['adata'][3]; } try { - return self::_exec( - 'INSERT INTO "' . self::_sanitizeIdentifier('paste') . + return $this->_exec( + 'INSERT INTO "' . $this->_sanitizeIdentifier('paste') . '" VALUES(?,?,?,?,?,?,?,?,?)', array( $pasteid, @@ -233,14 +222,14 @@ class Database extends AbstractData */ public function read($pasteid) { - if (array_key_exists($pasteid, self::$_cache)) { - return self::$_cache[$pasteid]; + if (array_key_exists($pasteid, $this->_cache)) { + return $this->_cache[$pasteid]; } - self::$_cache[$pasteid] = false; + $this->_cache[$pasteid] = false; try { - $paste = self::_select( - 'SELECT * FROM "' . self::_sanitizeIdentifier('paste') . + $paste = $this->_select( + 'SELECT * FROM "' . $this->_sanitizeIdentifier('paste') . '" WHERE "dataid" = ?', array($pasteid), true ); } catch (Exception $e) { @@ -253,11 +242,11 @@ class Database extends AbstractData $data = Json::decode($paste['data']); $isVersion2 = array_key_exists('v', $data) && $data['v'] >= 2; if ($isVersion2) { - self::$_cache[$pasteid] = $data; - list($createdKey) = self::_getVersionedKeys(2); + $this->_cache[$pasteid] = $data; + list($createdKey) = $this->_getVersionedKeys(2); } else { - self::$_cache[$pasteid] = array('data' => $paste['data']); - list($createdKey) = self::_getVersionedKeys(1); + $this->_cache[$pasteid] = array('data' => $paste['data']); + list($createdKey) = $this->_getVersionedKeys(1); } try { @@ -266,31 +255,31 @@ class Database extends AbstractData $paste['meta'] = array(); } $paste = self::upgradePreV1Format($paste); - self::$_cache[$pasteid]['meta'] = $paste['meta']; - self::$_cache[$pasteid]['meta'][$createdKey] = (int) $paste['postdate']; + $this->_cache[$pasteid]['meta'] = $paste['meta']; + $this->_cache[$pasteid]['meta'][$createdKey] = (int) $paste['postdate']; $expire_date = (int) $paste['expiredate']; if ($expire_date > 0) { - self::$_cache[$pasteid]['meta']['expire_date'] = $expire_date; + $this->_cache[$pasteid]['meta']['expire_date'] = $expire_date; } if ($isVersion2) { - return self::$_cache[$pasteid]; + return $this->_cache[$pasteid]; } // support v1 attachments if (array_key_exists('attachment', $paste) && !empty($paste['attachment'])) { - self::$_cache[$pasteid]['attachment'] = $paste['attachment']; + $this->_cache[$pasteid]['attachment'] = $paste['attachment']; if (array_key_exists('attachmentname', $paste) && !empty($paste['attachmentname'])) { - self::$_cache[$pasteid]['attachmentname'] = $paste['attachmentname']; + $this->_cache[$pasteid]['attachmentname'] = $paste['attachmentname']; } } if ($paste['opendiscussion']) { - self::$_cache[$pasteid]['meta']['opendiscussion'] = true; + $this->_cache[$pasteid]['meta']['opendiscussion'] = true; } if ($paste['burnafterreading']) { - self::$_cache[$pasteid]['meta']['burnafterreading'] = true; + $this->_cache[$pasteid]['meta']['burnafterreading'] = true; } - return self::$_cache[$pasteid]; + return $this->_cache[$pasteid]; } /** @@ -301,18 +290,18 @@ class Database extends AbstractData */ public function delete($pasteid) { - self::_exec( - 'DELETE FROM "' . self::_sanitizeIdentifier('paste') . + $this->_exec( + 'DELETE FROM "' . $this->_sanitizeIdentifier('paste') . '" WHERE "dataid" = ?', array($pasteid) ); - self::_exec( - 'DELETE FROM "' . self::_sanitizeIdentifier('comment') . + $this->_exec( + 'DELETE FROM "' . $this->_sanitizeIdentifier('comment') . '" WHERE "pasteid" = ?', array($pasteid) ); if ( - array_key_exists($pasteid, self::$_cache) + array_key_exists($pasteid, $this->_cache) ) { - unset(self::$_cache[$pasteid]); + unset($this->_cache[$pasteid]); } } @@ -326,11 +315,11 @@ class Database extends AbstractData public function exists($pasteid) { if ( - !array_key_exists($pasteid, self::$_cache) + !array_key_exists($pasteid, $this->_cache) ) { - self::$_cache[$pasteid] = $this->read($pasteid); + $this->_cache[$pasteid] = $this->read($pasteid); } - return (bool) self::$_cache[$pasteid]; + return (bool) $this->_cache[$pasteid]; } /** @@ -352,7 +341,7 @@ class Database extends AbstractData $version = 2; $data = Json::encode($comment); } - list($createdKey, $iconKey) = self::_getVersionedKeys($version); + list($createdKey, $iconKey) = $this->_getVersionedKeys($version); $meta = $comment['meta']; unset($comment['meta']); foreach (array('nickname', $iconKey) as $key) { @@ -361,8 +350,8 @@ class Database extends AbstractData } } try { - return self::_exec( - 'INSERT INTO "' . self::_sanitizeIdentifier('comment') . + return $this->_exec( + 'INSERT INTO "' . $this->_sanitizeIdentifier('comment') . '" VALUES(?,?,?,?,?,?,?)', array( $commentid, @@ -388,8 +377,8 @@ class Database extends AbstractData */ public function readComments($pasteid) { - $rows = self::_select( - 'SELECT * FROM "' . self::_sanitizeIdentifier('comment') . + $rows = $this->_select( + 'SELECT * FROM "' . $this->_sanitizeIdentifier('comment') . '" WHERE "pasteid" = ?', array($pasteid) ); @@ -406,7 +395,7 @@ class Database extends AbstractData $version = 1; $comments[$i] = array('data' => $row['data']); } - list($createdKey, $iconKey) = self::_getVersionedKeys($version); + list($createdKey, $iconKey) = $this->_getVersionedKeys($version); $comments[$i]['id'] = $row['dataid']; $comments[$i]['parentid'] = $row['parentid']; $comments[$i]['meta'] = array($createdKey => (int) $row['postdate']); @@ -433,8 +422,8 @@ class Database extends AbstractData public function existsComment($pasteid, $parentid, $commentid) { try { - return (bool) self::_select( - 'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('comment') . + return (bool) $this->_select( + 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('comment') . '" WHERE "pasteid" = ? AND "parentid" = ? AND "dataid" = ?', array($pasteid, $parentid, $commentid), true ); @@ -455,15 +444,15 @@ class Database extends AbstractData public function setValue($value, $namespace, $key = '') { if ($namespace === 'traffic_limiter') { - self::$_last_cache[$key] = $value; + $this->_last_cache[$key] = $value; try { - $value = Json::encode(self::$_last_cache); + $value = Json::encode($this->_last_cache); } catch (Exception $e) { return false; } } - return self::_exec( - 'UPDATE "' . self::_sanitizeIdentifier('config') . + return $this->_exec( + 'UPDATE "' . $this->_sanitizeIdentifier('config') . '" SET "value" = ? WHERE "id" = ?', array($value, strtoupper($namespace)) ); @@ -483,8 +472,8 @@ class Database extends AbstractData $value = $this->_getConfig($configKey); if ($value === '') { // initialize the row, so that setValue can rely on UPDATE queries - self::_exec( - 'INSERT INTO "' . self::_sanitizeIdentifier('config') . + $this->_exec( + 'INSERT INTO "' . $this->_sanitizeIdentifier('config') . '" VALUES(?,?)', array($configKey, '') ); @@ -492,7 +481,8 @@ class Database extends AbstractData // migrate filesystem based salt into database $file = 'data' . DIRECTORY_SEPARATOR . 'salt.php'; if ($namespace === 'salt' && is_readable($file)) { - $value = Filesystem::getInstance(array('dir' => 'data'))->getValue('salt'); + $fs = new Filesystem(array('dir' => 'data')); + $value = $fs->getValue('salt'); $this->setValue($value, 'salt'); @unlink($file); return $value; @@ -500,12 +490,12 @@ class Database extends AbstractData } if ($value && $namespace === 'traffic_limiter') { try { - self::$_last_cache = Json::decode($value); + $this->_last_cache = Json::decode($value); } catch (Exception $e) { - self::$_last_cache = array(); + $this->_last_cache = array(); } - if (array_key_exists($key, self::$_last_cache)) { - return self::$_last_cache[$key]; + if (array_key_exists($key, $this->_last_cache)) { + return $this->_last_cache[$key]; } } return (string) $value; @@ -521,10 +511,10 @@ class Database extends AbstractData protected function _getExpiredPastes($batchsize) { $pastes = array(); - $rows = self::_select( - 'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('paste') . + $rows = $this->_select( + 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '" WHERE "expiredate" < ? AND "expiredate" != ? ' . - (self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'), + ($this->_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'), array(time(), 0, $batchsize) ); if (is_array($rows) && count($rows)) { @@ -535,19 +525,36 @@ class Database extends AbstractData return $pastes; } + /** + * @inheritDoc + */ + public function getAllPastes() + { + $pastes = array(); + $rows = $this->_select( + 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '"', + array() + ); + if (is_array($rows) && count($rows)) { + foreach ($rows as $row) { + $pastes[] = $row['dataid']; + } + } + return $pastes; + } + /** * execute a statement * * @access private - * @static * @param string $sql * @param array $params * @throws PDOException * @return bool */ - private static function _exec($sql, array $params) + private function _exec($sql, array $params) { - $statement = self::$_db->prepare($sql); + $statement = $this->_db->prepare($sql); foreach ($params as $key => &$parameter) { $position = $key + 1; if (is_int($parameter)) { @@ -567,20 +574,19 @@ class Database extends AbstractData * run a select statement * * @access private - * @static * @param string $sql * @param array $params * @param bool $firstOnly if only the first row should be returned * @throws PDOException * @return array|false */ - private static function _select($sql, array $params, $firstOnly = false) + private function _select($sql, array $params, $firstOnly = false) { - $statement = self::$_db->prepare($sql); + $statement = $this->_db->prepare($sql); $statement->execute($params); if ($firstOnly) { $result = $statement->fetch(PDO::FETCH_ASSOC); - } elseif (self::$_type === 'oci') { + } elseif ($this->_type === 'oci') { // workaround for https://bugs.php.net/bug.php?id=46728 $result = array(); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { @@ -590,7 +596,7 @@ class Database extends AbstractData $result = $statement->fetchAll(PDO::FETCH_ASSOC); } $statement->closeCursor(); - if (self::$_type === 'oci' && is_array($result)) { + 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) : @@ -603,11 +609,10 @@ class Database extends AbstractData * get version dependent key names * * @access private - * @static * @param int $version * @return array */ - private static function _getVersionedKeys($version) + private function _getVersionedKeys($version) { if ($version === 1) { return array('postdate', 'vizhash'); @@ -619,12 +624,11 @@ class Database extends AbstractData * get table list query, depending on the database type * * @access private - * @static * @param string $type * @throws Exception * @return string */ - private static function _getTableQuery($type) + private function _getTableQuery($type) { switch ($type) { case 'ibm': @@ -675,15 +679,14 @@ class Database extends AbstractData * get a value by key from the config table * * @access private - * @static * @param string $key * @return string */ - private static function _getConfig($key) + private function _getConfig($key) { try { - $row = self::_select( - 'SELECT "value" FROM "' . self::_sanitizeIdentifier('config') . + $row = $this->_select( + 'SELECT "value" FROM "' . $this->_sanitizeIdentifier('config') . '" WHERE "id" = ?', array($key), true ); } catch (PDOException $e) { @@ -696,14 +699,13 @@ class Database extends AbstractData * get the primary key clauses, depending on the database driver * * @access private - * @static * @param string $key * @return array */ - private static function _getPrimaryKeyClauses($key = 'dataid') + private function _getPrimaryKeyClauses($key = 'dataid') { $main_key = $after_key = ''; - switch (self::$_type) { + switch ($this->_type) { case 'mysql': case 'oci': $after_key = ", PRIMARY KEY (\"$key\")"; @@ -721,12 +723,11 @@ class Database extends AbstractData * PostgreSQL and OCI uses a different API for BLOBs then SQL, hence we use TEXT and CLOB * * @access private - * @static * @return string */ - private static function _getDataType() + private function _getDataType() { - switch (self::$_type) { + switch ($this->_type) { case 'oci': return 'CLOB'; case 'pgsql': @@ -742,12 +743,11 @@ class Database extends AbstractData * PostgreSQL and OCI use different APIs for BLOBs then SQL, hence we use TEXT and CLOB * * @access private - * @static * @return string */ - private static function _getAttachmentType() + private function _getAttachmentType() { - switch (self::$_type) { + switch ($this->_type) { case 'oci': return 'CLOB'; case 'pgsql': @@ -763,12 +763,11 @@ class Database extends AbstractData * OCI doesn't accept TEXT so it has to be VARCHAR2(4000) * * @access private - * @static * @return string */ - private static function _getMetaType() + private function _getMetaType() { - switch (self::$_type) { + switch ($this->_type) { case 'oci': return 'VARCHAR2(4000)'; default: @@ -780,16 +779,15 @@ class Database extends AbstractData * create the paste table * * @access private - * @static */ - private static function _createPasteTable() + private function _createPasteTable() { - list($main_key, $after_key) = self::_getPrimaryKeyClauses(); - $dataType = self::_getDataType(); - $attachmentType = self::_getAttachmentType(); - $metaType = self::_getMetaType(); - self::$_db->exec( - 'CREATE TABLE "' . self::_sanitizeIdentifier('paste') . '" ( ' . + 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, " . "\"data\" $attachmentType, " . '"postdate" INT, ' . @@ -806,14 +804,13 @@ class Database extends AbstractData * create the paste table * * @access private - * @static */ - private static function _createCommentTable() + private function _createCommentTable() { - list($main_key, $after_key) = self::_getPrimaryKeyClauses(); - $dataType = self::_getDataType(); - self::$_db->exec( - 'CREATE TABLE "' . self::_sanitizeIdentifier('comment') . '" ( ' . + 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), ' . @@ -822,15 +819,15 @@ class Database extends AbstractData "\"vizhash\" $dataType, " . "\"postdate\" INT$after_key )" ); - if (self::$_type === 'oci') { - self::$_db->exec( + 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 "' . self::_sanitizeIdentifier('comment') . '" ("pasteid")\'; + execute immediate \'create index "comment_parent" on "' . $this->_sanitizeIdentifier('comment') . '" ("pasteid")\'; exception when already_exists or columns_indexed then NULL; @@ -838,10 +835,10 @@ class Database extends AbstractData ); } else { // CREATE INDEX IF NOT EXISTS not supported as of Oracle MySQL <= 8.0 - self::$_db->exec( + $this->_db->exec( 'CREATE INDEX "' . - self::_sanitizeIdentifier('comment_parent') . '" ON "' . - self::_sanitizeIdentifier('comment') . '" ("pasteid")' + $this->_sanitizeIdentifier('comment_parent') . '" ON "' . + $this->_sanitizeIdentifier('comment') . '" ("pasteid")' ); } } @@ -850,19 +847,18 @@ class Database extends AbstractData * create the paste table * * @access private - * @static */ - private static function _createConfigTable() + private function _createConfigTable() { - list($main_key, $after_key) = self::_getPrimaryKeyClauses('id'); - $charType = self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)'; - $textType = self::_getMetaType(); - self::$_db->exec( - 'CREATE TABLE "' . self::_sanitizeIdentifier('config') . + 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 )" ); - self::_exec( - 'INSERT INTO "' . self::_sanitizeIdentifier('config') . + $this->_exec( + 'INSERT INTO "' . $this->_sanitizeIdentifier('config') . '" VALUES(?,?)', array('VERSION', Controller::VERSION) ); @@ -874,11 +870,10 @@ class Database extends AbstractData * 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) + public function _sanitizeClob($value) { if (is_resource($value)) { $value = stream_get_contents($value); @@ -890,90 +885,88 @@ class Database extends AbstractData * sanitizes identifiers * * @access private - * @static * @param string $identifier * @return string */ - private static function _sanitizeIdentifier($identifier) + private function _sanitizeIdentifier($identifier) { - return preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); + return preg_replace('/[^A-Za-z0-9_]+/', '', $this->_prefix . $identifier); } /** * upgrade the database schema from an old version * * @access private - * @static * @param string $oldversion */ - private static function _upgradeDatabase($oldversion) + private function _upgradeDatabase($oldversion) { - $dataType = self::_getDataType(); - $attachmentType = self::_getAttachmentType(); + $dataType = $this->_getDataType(); + $attachmentType = $this->_getAttachmentType(); switch ($oldversion) { case '0.21': // create the meta column if necessary (pre 0.21 change) try { - self::$_db->exec( - 'SELECT "meta" FROM "' . self::_sanitizeIdentifier('paste') . '" ' . - (self::$_type === 'oci' ? 'FETCH NEXT 1 ROWS ONLY' : 'LIMIT 1') + $this->_db->exec( + 'SELECT "meta" FROM "' . $this->_sanitizeIdentifier('paste') . '" ' . + ($this->_type === 'oci' ? 'FETCH NEXT 1 ROWS ONLY' : 'LIMIT 1') ); } catch (PDOException $e) { - self::$_db->exec('ALTER TABLE "' . self::_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT'); + $this->_db->exec('ALTER TABLE "' . $this->_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT'); } // SQLite only allows one ALTER statement at a time... - self::$_db->exec( - 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . + $this->_db->exec( + 'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') . "\" ADD COLUMN \"attachment\" $attachmentType" ); - self::$_db->exec( - 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . "\" ADD COLUMN \"attachmentname\" $dataType" + $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 (self::$_type !== 'sqlite') { - self::$_db->exec( - 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . + if ($this->_type !== 'sqlite') { + $this->_db->exec( + 'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') . "\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType" ); - self::$_db->exec( - 'ALTER TABLE "' . self::_sanitizeIdentifier('comment') . + $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 { - self::$_db->exec( + $this->_db->exec( 'CREATE UNIQUE INDEX IF NOT EXISTS "' . - self::_sanitizeIdentifier('paste_dataid') . '" ON "' . - self::_sanitizeIdentifier('paste') . '" ("dataid")' + $this->_sanitizeIdentifier('paste_dataid') . '" ON "' . + $this->_sanitizeIdentifier('paste') . '" ("dataid")' ); - self::$_db->exec( + $this->_db->exec( 'CREATE UNIQUE INDEX IF NOT EXISTS "' . - self::_sanitizeIdentifier('comment_dataid') . '" ON "' . - self::_sanitizeIdentifier('comment') . '" ("dataid")' + $this->_sanitizeIdentifier('comment_dataid') . '" ON "' . + $this->_sanitizeIdentifier('comment') . '" ("dataid")' ); } // CREATE INDEX IF NOT EXISTS not supported as of Oracle MySQL <= 8.0 - self::$_db->exec( + $this->_db->exec( 'CREATE INDEX "' . - self::_sanitizeIdentifier('comment_parent') . '" ON "' . - self::_sanitizeIdentifier('comment') . '" ("pasteid")' + $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 (self::$_type !== 'sqlite' && self::$_type !== 'pgsql') { - self::$_db->exec( - 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . + if ($this->_type !== 'sqlite' && $this->_type !== 'pgsql') { + $this->_db->exec( + 'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') . "\" MODIFY COLUMN \"data\" $attachmentType" ); } // no break, continue with updates for all newer versions default: - self::_exec( - 'UPDATE "' . self::_sanitizeIdentifier('config') . + $this->_exec( + 'UPDATE "' . $this->_sanitizeIdentifier('config') . '" SET "value" = ? WHERE "id" = ?', array(Controller::VERSION, 'VERSION') ); diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 9674513c..3dd69578 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -40,33 +40,26 @@ class Filesystem extends AbstractData * path in which to persist something * * @access private - * @static * @var string */ - private static $_path = 'data'; + private $_path = 'data'; /** - * get instance of singleton + * instantiates a new Filesystem data backend * * @access public - * @static * @param array $options - * @return Filesystem + * @return */ - public static function getInstance(array $options) + public function __construct(array $options) { - // if needed initialize the singleton - if (!(self::$_instance instanceof self)) { - self::$_instance = new self; - } // if given update the data directory if ( is_array($options) && array_key_exists('dir', $options) ) { - self::$_path = $options['dir']; + $this->_path = $options['dir']; } - return self::$_instance; } /** @@ -79,7 +72,7 @@ class Filesystem extends AbstractData */ public function create($pasteid, array $paste) { - $storagedir = self::_dataid2path($pasteid); + $storagedir = $this->_dataid2path($pasteid); $file = $storagedir . $pasteid . '.php'; if (is_file($file)) { return false; @@ -87,7 +80,7 @@ class Filesystem extends AbstractData if (!is_dir($storagedir)) { mkdir($storagedir, 0700, true); } - return self::_store($file, $paste); + return $this->_store($file, $paste); } /** @@ -101,7 +94,7 @@ class Filesystem extends AbstractData { if ( !$this->exists($pasteid) || - !$paste = self::_get(self::_dataid2path($pasteid) . $pasteid . '.php') + !$paste = $this->_get($this->_dataid2path($pasteid) . $pasteid . '.php') ) { return false; } @@ -116,7 +109,7 @@ class Filesystem extends AbstractData */ public function delete($pasteid) { - $pastedir = self::_dataid2path($pasteid); + $pastedir = $this->_dataid2path($pasteid); if (is_dir($pastedir)) { // Delete the paste itself. if (is_file($pastedir . $pasteid . '.php')) { @@ -124,7 +117,7 @@ class Filesystem extends AbstractData } // Delete discussion if it exists. - $discdir = self::_dataid2discussionpath($pasteid); + $discdir = $this->_dataid2discussionpath($pasteid); if (is_dir($discdir)) { // Delete all files in discussion directory $dir = dir($discdir); @@ -148,20 +141,20 @@ class Filesystem extends AbstractData */ public function exists($pasteid) { - $basePath = self::_dataid2path($pasteid) . $pasteid; + $basePath = $this->_dataid2path($pasteid) . $pasteid; $pastePath = $basePath . '.php'; // convert to PHP protected files if needed if (is_readable($basePath)) { - self::_prependRename($basePath, $pastePath); + $this->_prependRename($basePath, $pastePath); // convert comments, too - $discdir = self::_dataid2discussionpath($pasteid); + $discdir = $this->_dataid2discussionpath($pasteid); if (is_dir($discdir)) { $dir = dir($discdir); while (false !== ($filename = $dir->read())) { if (substr($filename, -4) !== '.php' && strlen($filename) >= 16) { $commentFilename = $discdir . $filename . '.php'; - self::_prependRename($discdir . $filename, $commentFilename); + $this->_prependRename($discdir . $filename, $commentFilename); } } $dir->close(); @@ -182,7 +175,7 @@ class Filesystem extends AbstractData */ public function createComment($pasteid, $parentid, $commentid, array $comment) { - $storagedir = self::_dataid2discussionpath($pasteid); + $storagedir = $this->_dataid2discussionpath($pasteid); $file = $storagedir . $pasteid . '.' . $commentid . '.' . $parentid . '.php'; if (is_file($file)) { return false; @@ -190,7 +183,7 @@ class Filesystem extends AbstractData if (!is_dir($storagedir)) { mkdir($storagedir, 0700, true); } - return self::_store($file, $comment); + return $this->_store($file, $comment); } /** @@ -203,7 +196,7 @@ class Filesystem extends AbstractData public function readComments($pasteid) { $comments = array(); - $discdir = self::_dataid2discussionpath($pasteid); + $discdir = $this->_dataid2discussionpath($pasteid); if (is_dir($discdir)) { $dir = dir($discdir); while (false !== ($filename = $dir->read())) { @@ -212,7 +205,7 @@ class Filesystem extends AbstractData // - commentid is the comment identifier itself. // - parentid is the comment this comment replies to (It can be pasteid) if (is_file($discdir . $filename)) { - $comment = self::_get($discdir . $filename); + $comment = $this->_get($discdir . $filename); $items = explode('.', $filename); // Add some meta information not contained in file. $comment['id'] = $items[1]; @@ -243,7 +236,7 @@ class Filesystem extends AbstractData public function existsComment($pasteid, $parentid, $commentid) { return is_file( - self::_dataid2discussionpath($pasteid) . + $this->_dataid2discussionpath($pasteid) . $pasteid . '.' . $commentid . '.' . $parentid . '.php' ); } @@ -261,20 +254,20 @@ class Filesystem extends AbstractData { switch ($namespace) { case 'purge_limiter': - return self::_storeString( - self::$_path . DIRECTORY_SEPARATOR . 'purge_limiter.php', + return $this->_storeString( + $this->_path . DIRECTORY_SEPARATOR . 'purge_limiter.php', '_storeString( + $this->_path . DIRECTORY_SEPARATOR . 'salt.php', '_last_cache[$key] = $value; + return $this->_storeString( + $this->_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php', + '_last_cache, true) . ';' ); } return false; @@ -292,14 +285,14 @@ class Filesystem extends AbstractData { switch ($namespace) { case 'purge_limiter': - $file = self::$_path . DIRECTORY_SEPARATOR . 'purge_limiter.php'; + $file = $this->_path . DIRECTORY_SEPARATOR . 'purge_limiter.php'; if (is_readable($file)) { require $file; return $GLOBALS['purge_limiter']; } break; case 'salt': - $file = self::$_path . DIRECTORY_SEPARATOR . 'salt.php'; + $file = $this->_path . DIRECTORY_SEPARATOR . 'salt.php'; if (is_readable($file)) { $items = explode('|', file_get_contents($file)); if (is_array($items) && count($items) == 3) { @@ -308,12 +301,12 @@ class Filesystem extends AbstractData } break; case 'traffic_limiter': - $file = self::$_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php'; + $file = $this->_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php'; if (is_readable($file)) { require $file; - self::$_last_cache = $GLOBALS['traffic_limiter']; - if (array_key_exists($key, self::$_last_cache)) { - return self::$_last_cache[$key]; + $this->_last_cache = $GLOBALS['traffic_limiter']; + if (array_key_exists($key, $this->_last_cache)) { + return $this->_last_cache[$key]; } } break; @@ -325,11 +318,10 @@ class Filesystem extends AbstractData * get the data * * @access public - * @static * @param string $filename * @return array|false $data */ - private static function _get($filename) + private function _get($filename) { return Json::decode( substr( @@ -350,7 +342,7 @@ class Filesystem extends AbstractData { $pastes = array(); $firstLevel = array_filter( - scandir(self::$_path), + scandir($this->_path), 'PrivateBin\Data\Filesystem::_isFirstLevelDir' ); if (count($firstLevel) > 0) { @@ -358,7 +350,7 @@ class Filesystem extends AbstractData for ($i = 0, $max = $batchsize * 10; $i < $max; ++$i) { $firstKey = array_rand($firstLevel); $secondLevel = array_filter( - scandir(self::$_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]), + scandir($this->_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]), 'PrivateBin\Data\Filesystem::_isSecondLevelDir' ); @@ -369,7 +361,7 @@ class Filesystem extends AbstractData } $secondKey = array_rand($secondLevel); - $path = self::$_path . DIRECTORY_SEPARATOR . + $path = $this->_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey] . DIRECTORY_SEPARATOR . $secondLevel[$secondKey]; if (!is_dir($path)) { @@ -412,6 +404,46 @@ class Filesystem extends AbstractData return $pastes; } + /** + * @inheritDoc + */ + public function getAllPastes() + { + $pastes = array(); + $subdirs = scandir($this->_path); + if ($subdirs === false) { + dieerr("Unable to list directory " . $this->_path); + } + $subdirs = preg_grep("/^[^.].$/", $subdirs); + + foreach ($subdirs as $subdir) { + $subpath = $this->_path . DIRECTORY_SEPARATOR . $subdir; + + $subsubdirs = scandir($subpath); + if ($subsubdirs === false) { + dieerr("Unable to list directory " . $subpath); + } + $subsubdirs = preg_grep("/^[^.].$/", $subsubdirs); + foreach ($subsubdirs as $subsubdir) { + $subsubpath = $subpath . DIRECTORY_SEPARATOR . $subsubdir; + + $files = scandir($subsubpath); + if ($files === false) { + dieerr("Unable to list directory " . $subsubpath); + } + $files = preg_grep("/\.php$/", $files); + + foreach ($files as $file) { + if (substr($file, 0, 4) === $subdir . $subsubdir) { + $pastes[] = substr($file, 0, strlen($file) - 4); + } + } + } + } + + return $pastes; + } + /** * Convert paste id to storage path. * @@ -423,13 +455,12 @@ class Filesystem extends AbstractData * eg. input 'e3570978f9e4aa90' --> output 'data/e3/57/' * * @access private - * @static * @param string $dataid * @return string */ - private static function _dataid2path($dataid) + private function _dataid2path($dataid) { - return self::$_path . DIRECTORY_SEPARATOR . + return $this->_path . DIRECTORY_SEPARATOR . substr($dataid, 0, 2) . DIRECTORY_SEPARATOR . substr($dataid, 2, 2) . DIRECTORY_SEPARATOR; } @@ -440,13 +471,12 @@ class Filesystem extends AbstractData * eg. input 'e3570978f9e4aa90' --> output 'data/e3/57/e3570978f9e4aa90.discussion/' * * @access private - * @static * @param string $dataid * @return string */ - private static function _dataid2discussionpath($dataid) + private function _dataid2discussionpath($dataid) { - return self::_dataid2path($dataid) . $dataid . + return $this->_dataid2path($dataid) . $dataid . '.discussion' . DIRECTORY_SEPARATOR; } @@ -454,25 +484,23 @@ class Filesystem extends AbstractData * Check that the given element is a valid first level directory. * * @access private - * @static * @param string $element * @return bool */ - private static function _isFirstLevelDir($element) + private function _isFirstLevelDir($element) { - return self::_isSecondLevelDir($element) && - is_dir(self::$_path . DIRECTORY_SEPARATOR . $element); + return $this->_isSecondLevelDir($element) && + is_dir($this->_path . DIRECTORY_SEPARATOR . $element); } /** * Check that the given element is a valid second level directory. * * @access private - * @static * @param string $element * @return bool */ - private static function _isSecondLevelDir($element) + private function _isSecondLevelDir($element) { return (bool) preg_match('/^[a-f0-9]{2}$/', $element); } @@ -481,15 +509,14 @@ class Filesystem extends AbstractData * store the data * * @access public - * @static * @param string $filename * @param array $data * @return bool */ - private static function _store($filename, array $data) + private function _store($filename, array $data) { try { - return self::_storeString( + return $this->_storeString( $filename, self::PROTECTION_LINE . PHP_EOL . Json::encode($data) ); @@ -502,20 +529,19 @@ class Filesystem extends AbstractData * store a string * * @access public - * @static * @param string $filename * @param string $data * @return bool */ - private static function _storeString($filename, $data) + private function _storeString($filename, $data) { // Create storage directory if it does not exist. - if (!is_dir(self::$_path)) { - if (!@mkdir(self::$_path, 0700)) { + if (!is_dir($this->_path)) { + if (!@mkdir($this->_path, 0700)) { return false; } } - $file = self::$_path . DIRECTORY_SEPARATOR . '.htaccess'; + $file = $this->_path . DIRECTORY_SEPARATOR . '.htaccess'; if (!is_file($file)) { $writtenBytes = 0; if ($fileCreated = @touch($file)) { @@ -553,12 +579,11 @@ class Filesystem extends AbstractData * rename a file, prepending the protection line at the beginning * * @access public - * @static * @param string $srcFile * @param string $destFile * @return void */ - private static function _prependRename($srcFile, $destFile) + private function _prependRename($srcFile, $destFile) { // don't overwrite already converted file if (!is_readable($destFile)) { diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index e8763258..e271e2f8 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -14,54 +14,43 @@ class GoogleCloudStorage extends AbstractData * GCS client * * @access private - * @static * @var StorageClient */ - private static $_client = null; + private $_client = null; /** * GCS bucket * * @access private - * @static * @var Bucket */ - private static $_bucket = null; + private $_bucket = null; /** * object prefix * * @access private - * @static * @var string */ - private static $_prefix = 'pastes'; + private $_prefix = 'pastes'; /** * bucket acl type * * @access private - * @static * @var bool */ - private static $_uniformacl = false; + private $_uniformacl = false; /** - * returns a Google Cloud Storage data backend. + * instantiantes a new Google Cloud Storage data backend. * * @access public - * @static * @param array $options - * @return GoogleCloudStorage + * @return */ - public static function getInstance(array $options) + public function __construct(array $options) { - // if needed initialize the singleton - if (!(self::$_instance instanceof self)) { - self::$_instance = new self; - } - - $bucket = null; if (getenv('PRIVATEBIN_GCS_BUCKET')) { $bucket = getenv('PRIVATEBIN_GCS_BUCKET'); } @@ -69,24 +58,20 @@ class GoogleCloudStorage extends AbstractData $bucket = $options['bucket']; } if (is_array($options) && array_key_exists('prefix', $options)) { - self::$_prefix = $options['prefix']; + $this->_prefix = $options['prefix']; } if (is_array($options) && array_key_exists('uniformacl', $options)) { - self::$_uniformacl = $options['uniformacl']; + $this->_uniformacl = $options['uniformacl']; } - if (empty(self::$_client)) { - self::$_client = class_exists('StorageClientStub', false) ? - new \StorageClientStub(array()) : - new StorageClient(array('suppressKeyFileNotice' => true)); - } - self::$_bucket = self::$_client->bucket($bucket); - - return self::$_instance; + $this->_client = class_exists('StorageClientStub', false) ? + new \StorageClientStub(array()) : + new StorageClient(array('suppressKeyFileNotice' => true)); + $this->_bucket = $this->_client->bucket($bucket); } /** - * returns the google storage object key for $pasteid in self::$_bucket. + * returns the google storage object key for $pasteid in $this->_bucket. * * @access private * @param $pasteid string to get the key for @@ -94,14 +79,14 @@ class GoogleCloudStorage extends AbstractData */ private function _getKey($pasteid) { - if (self::$_prefix != '') { - return self::$_prefix . '/' . $pasteid; + if ($this->_prefix != '') { + return $this->_prefix . '/' . $pasteid; } return $pasteid; } /** - * Uploads the payload in the self::$_bucket under the specified key. + * Uploads the payload in the $this->_bucket under the specified key. * The entire payload is stored as a JSON document. The metadata is replicated * as the GCS object's metadata except for the fields attachment, attachmentname * and salt. @@ -126,12 +111,12 @@ class GoogleCloudStorage extends AbstractData 'metadata' => $metadata, ), ); - if (!self::$_uniformacl) { + if (!$this->_uniformacl) { $data['predefinedAcl'] = 'private'; } - self::$_bucket->upload(Json::encode($payload), $data); + $this->_bucket->upload(Json::encode($payload), $data); } catch (Exception $e) { - error_log('failed to upload ' . $key . ' to ' . self::$_bucket->name() . ', ' . + error_log('failed to upload ' . $key . ' to ' . $this->_bucket->name() . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); return false; } @@ -156,13 +141,13 @@ class GoogleCloudStorage extends AbstractData public function read($pasteid) { try { - $o = self::$_bucket->object($this->_getKey($pasteid)); + $o = $this->_bucket->object($this->_getKey($pasteid)); $data = $o->downloadAsString(); return Json::decode($data); } catch (NotFoundException $e) { return false; } catch (Exception $e) { - error_log('failed to read ' . $pasteid . ' from ' . self::$_bucket->name() . ', ' . + error_log('failed to read ' . $pasteid . ' from ' . $this->_bucket->name() . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); return false; } @@ -176,9 +161,9 @@ class GoogleCloudStorage extends AbstractData $name = $this->_getKey($pasteid); try { - foreach (self::$_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) { + foreach ($this->_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) { try { - self::$_bucket->object($comment->name())->delete(); + $this->_bucket->object($comment->name())->delete(); } catch (NotFoundException $e) { // ignore if already deleted. } @@ -188,7 +173,7 @@ class GoogleCloudStorage extends AbstractData } try { - self::$_bucket->object($name)->delete(); + $this->_bucket->object($name)->delete(); } catch (NotFoundException $e) { // ignore if already deleted } @@ -199,7 +184,7 @@ class GoogleCloudStorage extends AbstractData */ public function exists($pasteid) { - $o = self::$_bucket->object($this->_getKey($pasteid)); + $o = $this->_bucket->object($this->_getKey($pasteid)); return $o->exists(); } @@ -223,8 +208,8 @@ class GoogleCloudStorage extends AbstractData $comments = array(); $prefix = $this->_getKey($pasteid) . '/discussion/'; try { - foreach (self::$_bucket->objects(array('prefix' => $prefix)) as $key) { - $comment = JSON::decode(self::$_bucket->object($key->name())->downloadAsString()); + foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $key) { + $comment = JSON::decode($this->_bucket->object($key->name())->downloadAsString()); $comment['id'] = basename($key->name()); $slot = $this->getOpenSlot($comments, (int) $comment['meta']['created']); $comments[$slot] = $comment; @@ -241,7 +226,7 @@ class GoogleCloudStorage extends AbstractData public function existsComment($pasteid, $parentid, $commentid) { $name = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid; - $o = self::$_bucket->object($name); + $o = $this->_bucket->object($name); return $o->exists(); } @@ -252,7 +237,7 @@ class GoogleCloudStorage extends AbstractData { $path = 'config/' . $namespace; try { - foreach (self::$_bucket->objects(array('prefix' => $path)) as $object) { + foreach ($this->_bucket->objects(array('prefix' => $path)) as $object) { $name = $object->name(); if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') { continue; @@ -300,12 +285,12 @@ class GoogleCloudStorage extends AbstractData 'metadata' => $metadata, ), ); - if (!self::$_uniformacl) { + if (!$this->_uniformacl) { $data['predefinedAcl'] = 'private'; } - self::$_bucket->upload($value, $data); + $this->_bucket->upload($value, $data); } catch (Exception $e) { - error_log('failed to set key ' . $key . ' to ' . self::$_bucket->name() . ', ' . + error_log('failed to set key ' . $key . ' to ' . $this->_bucket->name() . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); return false; } @@ -323,7 +308,7 @@ class GoogleCloudStorage extends AbstractData $key = 'config/' . $namespace . '/' . $key; } try { - $o = self::$_bucket->object($key); + $o = $this->_bucket->object($key); return $o->downloadAsString(); } catch (NotFoundException $e) { return ''; @@ -338,12 +323,12 @@ class GoogleCloudStorage extends AbstractData $expired = array(); $now = time(); - $prefix = self::$_prefix; + $prefix = $this->_prefix; if ($prefix != '') { $prefix .= '/'; } try { - foreach (self::$_bucket->objects(array('prefix' => $prefix)) as $object) { + foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) { $metadata = $object->info()['metadata']; if ($metadata != null && array_key_exists('expire_date', $metadata)) { $expire_at = intval($metadata['expire_date']); @@ -361,4 +346,28 @@ class GoogleCloudStorage extends AbstractData } return $expired; } + + /** + * @inheritDoc + */ + public function getAllPastes() + { + $pastes = array(); + $prefix = $this->_prefix; + if ($prefix != '') { + $prefix .= '/'; + } + + try { + foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) { + $candidate = substr($object->name(), strlen($prefix)); + if (strpos($candidate, "/") === false) { + $pastes[] = $candidate; + } + } + } catch (NotFoundException $e) { + // no objects in the bucket yet + } + return $pastes; + } } diff --git a/lib/Data/S3Storage.php b/lib/Data/S3Storage.php index d741e099..c59aaaeb 100644 --- a/lib/Data/S3Storage.php +++ b/lib/Data/S3Storage.php @@ -45,86 +45,71 @@ class S3Storage extends AbstractData * S3 client * * @access private - * @static * @var S3Client */ - private static $_client = null; + private $_client = null; /** * S3 client options * * @access private - * @static * @var array */ - private static $_options = array(); + private $_options = array(); /** * S3 bucket * * @access private - * @static * @var string */ - private static $_bucket = null; + private $_bucket = null; /** * S3 prefix for all PrivateBin data in this bucket * * @access private - * @static * @var string */ - private static $_prefix = ''; + private $_prefix = ''; /** - * returns an S3 data backend. + * instantiates a new S3 data backend. * * @access public - * @static * @param array $options - * @return S3Storage + * @return */ - public static function getInstance(array $options) + public function __construct(array $options) { - // if needed initialize the singleton - if (!(self::$_instance instanceof self)) { - self::$_instance = new self; - } - - self::$_options = array(); - self::$_options['credentials'] = array(); + $this->_options['credentials'] = array(); if (is_array($options) && array_key_exists('region', $options)) { - self::$_options['region'] = $options['region']; + $this->_options['region'] = $options['region']; } if (is_array($options) && array_key_exists('version', $options)) { - self::$_options['version'] = $options['version']; + $this->_options['version'] = $options['version']; } if (is_array($options) && array_key_exists('endpoint', $options)) { - self::$_options['endpoint'] = $options['endpoint']; + $this->_options['endpoint'] = $options['endpoint']; } if (is_array($options) && array_key_exists('accesskey', $options)) { - self::$_options['credentials']['key'] = $options['accesskey']; + $this->_options['credentials']['key'] = $options['accesskey']; } if (is_array($options) && array_key_exists('secretkey', $options)) { - self::$_options['credentials']['secret'] = $options['secretkey']; + $this->_options['credentials']['secret'] = $options['secretkey']; } if (is_array($options) && array_key_exists('use_path_style_endpoint', $options)) { - self::$_options['use_path_style_endpoint'] = filter_var($options['use_path_style_endpoint'], FILTER_VALIDATE_BOOLEAN); + $this->_options['use_path_style_endpoint'] = filter_var($options['use_path_style_endpoint'], FILTER_VALIDATE_BOOLEAN); } if (is_array($options) && array_key_exists('bucket', $options)) { - self::$_bucket = $options['bucket']; + $this->_bucket = $options['bucket']; } if (is_array($options) && array_key_exists('prefix', $options)) { - self::$_prefix = $options['prefix']; + $this->_prefix = $options['prefix']; } - if (empty(self::$_client)) { - self::$_client = new S3Client(self::$_options); - } - - return self::$_instance; + $this->_client = new S3Client($this->_options); } /** @@ -138,12 +123,12 @@ class S3Storage extends AbstractData { $allObjects = array(); $options = array( - 'Bucket' => self::$_bucket, + 'Bucket' => $this->_bucket, 'Prefix' => $prefix, ); do { - $objectsListResponse = self::$_client->listObjects($options); + $objectsListResponse = $this->_client->listObjects($options); $objects = $objectsListResponse['Contents'] ?? array(); foreach ($objects as $object) { $allObjects[] = $object; @@ -155,7 +140,7 @@ class S3Storage extends AbstractData } /** - * returns the S3 storage object key for $pasteid in self::$_bucket. + * returns the S3 storage object key for $pasteid in $this->_bucket. * * @access private * @param $pasteid string to get the key for @@ -163,14 +148,14 @@ class S3Storage extends AbstractData */ private function _getKey($pasteid) { - if (self::$_prefix != '') { - return self::$_prefix . '/' . $pasteid; + if ($this->_prefix != '') { + return $this->_prefix . '/' . $pasteid; } return $pasteid; } /** - * Uploads the payload in the self::$_bucket under the specified key. + * Uploads the payload in the $this->_bucket under the specified key. * The entire payload is stored as a JSON document. The metadata is replicated * as the S3 object's metadata except for the fields attachment, attachmentname * and salt. @@ -187,15 +172,15 @@ class S3Storage extends AbstractData $metadata[$k] = strval($v); } try { - self::$_client->putObject(array( - 'Bucket' => self::$_bucket, + $this->_client->putObject(array( + 'Bucket' => $this->_bucket, 'Key' => $key, 'Body' => Json::encode($payload), 'ContentType' => 'application/json', 'Metadata' => $metadata, )); } catch (S3Exception $e) { - error_log('failed to upload ' . $key . ' to ' . self::$_bucket . ', ' . + error_log('failed to upload ' . $key . ' to ' . $this->_bucket . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); return false; } @@ -220,14 +205,14 @@ class S3Storage extends AbstractData public function read($pasteid) { try { - $object = self::$_client->getObject(array( - 'Bucket' => self::$_bucket, + $object = $this->_client->getObject(array( + 'Bucket' => $this->_bucket, 'Key' => $this->_getKey($pasteid), )); $data = $object['Body']->getContents(); return Json::decode($data); } catch (S3Exception $e) { - error_log('failed to read ' . $pasteid . ' from ' . self::$_bucket . ', ' . + error_log('failed to read ' . $pasteid . ' from ' . $this->_bucket . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); return false; } @@ -244,8 +229,8 @@ class S3Storage extends AbstractData $comments = $this->_listAllObjects($name . '/discussion/'); foreach ($comments as $comment) { try { - self::$_client->deleteObject(array( - 'Bucket' => self::$_bucket, + $this->_client->deleteObject(array( + 'Bucket' => $this->_bucket, 'Key' => $comment['Key'], )); } catch (S3Exception $e) { @@ -257,8 +242,8 @@ class S3Storage extends AbstractData } try { - self::$_client->deleteObject(array( - 'Bucket' => self::$_bucket, + $this->_client->deleteObject(array( + 'Bucket' => $this->_bucket, 'Key' => $name, )); } catch (S3Exception $e) { @@ -271,7 +256,7 @@ class S3Storage extends AbstractData */ public function exists($pasteid) { - return self::$_client->doesObjectExistV2(self::$_bucket, $this->_getKey($pasteid)); + return $this->_client->doesObjectExistV2($this->_bucket, $this->_getKey($pasteid)); } /** @@ -296,8 +281,8 @@ class S3Storage extends AbstractData try { $entries = $this->_listAllObjects($prefix); foreach ($entries as $entry) { - $object = self::$_client->getObject(array( - 'Bucket' => self::$_bucket, + $object = $this->_client->getObject(array( + 'Bucket' => $this->_bucket, 'Key' => $entry['Key'], )); $body = JSON::decode($object['Body']->getContents()); @@ -319,7 +304,7 @@ class S3Storage extends AbstractData public function existsComment($pasteid, $parentid, $commentid) { $name = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid; - return self::$_client->doesObjectExistV2(self::$_bucket, $name); + return $this->_client->doesObjectExistV2($this->_bucket, $name); } /** @@ -327,7 +312,7 @@ class S3Storage extends AbstractData */ public function purgeValues($namespace, $time) { - $path = self::$_prefix; + $path = $this->_prefix; if ($path != '') { $path .= '/'; } @@ -339,16 +324,16 @@ class S3Storage extends AbstractData if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') { continue; } - $head = self::$_client->headObject(array( - 'Bucket' => self::$_bucket, + $head = $this->_client->headObject(array( + 'Bucket' => $this->_bucket, 'Key' => $name, )); - if (array_key_exists('Metadata', $head) && array_key_exists('value', $head['Metadata'])) { - $value = $head['Metadata']['value']; + if ($head->get('Metadata') != null && array_key_exists('value', $head->get('Metadata'))) { + $value = $head->get('Metadata')['value']; if (is_numeric($value) && intval($value) < $time) { try { - self::$_client->deleteObject(array( - 'Bucket' => self::$_bucket, + $this->_client->deleteObject(array( + 'Bucket' => $this->_bucket, 'Key' => $name, )); } catch (S3Exception $e) { @@ -369,7 +354,7 @@ class S3Storage extends AbstractData */ public function setValue($value, $namespace, $key = '') { - $prefix = self::$_prefix; + $prefix = $this->_prefix; if ($prefix != '') { $prefix .= '/'; } @@ -385,15 +370,15 @@ class S3Storage extends AbstractData $metadata['value'] = strval($value); } try { - self::$_client->putObject(array( - 'Bucket' => self::$_bucket, + $this->_client->putObject(array( + 'Bucket' => $this->_bucket, 'Key' => $key, 'Body' => $value, 'ContentType' => 'application/json', 'Metadata' => $metadata, )); } catch (S3Exception $e) { - error_log('failed to set key ' . $key . ' to ' . self::$_bucket . ', ' . + error_log('failed to set key ' . $key . ' to ' . $this->_bucket . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); return false; } @@ -405,7 +390,7 @@ class S3Storage extends AbstractData */ public function getValue($namespace, $key = '') { - $prefix = self::$_prefix; + $prefix = $this->_prefix; if ($prefix != '') { $prefix .= '/'; } @@ -417,8 +402,8 @@ class S3Storage extends AbstractData } try { - $object = self::$_client->getObject(array( - 'Bucket' => self::$_bucket, + $object = $this->_client->getObject(array( + 'Bucket' => $this->_bucket, 'Key' => $key, )); return $object['Body']->getContents(); @@ -434,19 +419,19 @@ class S3Storage extends AbstractData { $expired = array(); $now = time(); - $prefix = self::$_prefix; + $prefix = $this->_prefix; if ($prefix != '') { $prefix .= '/'; } try { foreach ($this->_listAllObjects($prefix) as $object) { - $head = self::$_client->headObject(array( - 'Bucket' => self::$_bucket, + $head = $this->_client->headObject(array( + 'Bucket' => $this->_bucket, 'Key' => $object['Key'], )); - if (array_key_exists('Metadata', $head) && array_key_exists('expire_date', $head['Metadata'])) { - $expire_at = intval($head['Metadata']['expire_date']); + if ($head->get('Metadata') != null && array_key_exists('expire_date', $head->get('Metadata'))) { + $expire_at = intval($head->get('Metadata')['expire_date']); if ($expire_at != 0 && $expire_at < $now) { array_push($expired, $object['Key']); } @@ -461,4 +446,28 @@ class S3Storage extends AbstractData } return $expired; } + + /** + * @inheritDoc + */ + public function getAllPastes() + { + $pastes = array(); + $prefix = $this->_prefix; + if ($prefix != '') { + $prefix .= '/'; + } + + try { + foreach ($this->_listAllObjects($prefix) as $object) { + $candidate = substr($object["Key"], strlen($prefix)); + if (strpos($candidate, "/") === false) { + $pastes[] = $candidate; + } + } + } catch (S3Exception $e) { + // no objects in the bucket yet + } + return $pastes; + } } diff --git a/lib/Model.php b/lib/Model.php index 360cf68c..94f77007 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -81,10 +81,8 @@ class Model public function getStore() { if ($this->_store === null) { - $this->_store = forward_static_call( - 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model') . '::getInstance', - $this->_conf->getSection('model_options') - ); + $class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model'); + $this->_store = new $class($this->_conf->getSection('model_options')); } return $this->_store; } diff --git a/tst/ConfigurationTestGenerator.php b/tst/ConfigurationTestGenerator.php index 945fc479..a0106a3f 100755 --- a/tst/ConfigurationTestGenerator.php +++ b/tst/ConfigurationTestGenerator.php @@ -427,7 +427,7 @@ class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase /* Setup Routine */ Helper::confBackup(); $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data'; - $this->_model = Filesystem::getInstance(array('dir' => $this->_path)); + $this->_model = new Filesystem(array('dir' => $this->_path)); $this->reset(); } diff --git a/tst/ControllerTest.php b/tst/ControllerTest.php index e1637c11..698d5f86 100644 --- a/tst/ControllerTest.php +++ b/tst/ControllerTest.php @@ -16,7 +16,7 @@ class ControllerTest extends PHPUnit_Framework_TestCase { /* Setup Routine */ $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data'; - $this->_data = Filesystem::getInstance(array('dir' => $this->_path)); + $this->_data = new Filesystem(array('dir' => $this->_path)); ServerSalt::setStore($this->_data); TrafficLimiter::setStore($this->_data); $this->reset(); diff --git a/tst/ControllerWithDbTest.php b/tst/ControllerWithDbTest.php index bc8cd7be..d5bbe534 100644 --- a/tst/ControllerWithDbTest.php +++ b/tst/ControllerWithDbTest.php @@ -25,7 +25,7 @@ class ControllerWithDbTest extends ControllerTest mkdir($this->_path); } $this->_options['dsn'] = 'sqlite:' . $this->_path . DIRECTORY_SEPARATOR . 'tst.sq3'; - $this->_data = Database::getInstance($this->_options); + $this->_data = new Database($this->_options); ServerSalt::setStore($this->_data); TrafficLimiter::setStore($this->_data); $this->reset(); diff --git a/tst/ControllerWithGcsTest.php b/tst/ControllerWithGcsTest.php index 39833417..5490db8d 100644 --- a/tst/ControllerWithGcsTest.php +++ b/tst/ControllerWithGcsTest.php @@ -39,7 +39,7 @@ class ControllerWithGcsTest extends ControllerTest 'bucket' => self::$_bucket->name(), 'prefix' => 'pastes', ); - $this->_data = GoogleCloudStorage::getInstance($this->_options); + $this->_data = new GoogleCloudStorage($this->_options); ServerSalt::setStore($this->_data); TrafficLimiter::setStore($this->_data); $this->reset(); diff --git a/tst/Data/DatabaseTest.php b/tst/Data/DatabaseTest.php index 16e6fcb8..3e65933a 100644 --- a/tst/Data/DatabaseTest.php +++ b/tst/Data/DatabaseTest.php @@ -22,7 +22,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase { /* Setup Routine */ $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data'; - $this->_model = Database::getInstance($this->_options); + $this->_model = new Database($this->_options); } public function tearDown() @@ -35,7 +35,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase public function testSaltMigration() { - ServerSalt::setStore(Filesystem::getInstance(array('dir' => 'data'))); + ServerSalt::setStore(new Filesystem(array('dir' => 'data'))); $salt = ServerSalt::get(); $file = 'data' . DIRECTORY_SEPARATOR . 'salt.php'; $this->assertFileExists($file, 'ServerSalt got initialized and stored on disk'); @@ -141,7 +141,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase */ public function testGetIbmInstance() { - Database::getInstance(array( + new Database(array( 'dsn' => 'ibm:', 'usr' => null, 'pwd' => null, 'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION), )); @@ -152,7 +152,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase */ public function testGetInformixInstance() { - Database::getInstance(array( + new Database(array( 'dsn' => 'informix:', 'usr' => null, 'pwd' => null, 'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION), )); @@ -163,7 +163,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase */ public function testGetMssqlInstance() { - Database::getInstance(array( + new Database(array( 'dsn' => 'mssql:', 'usr' => null, 'pwd' => null, 'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION), )); @@ -174,7 +174,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase */ public function testGetMysqlInstance() { - Database::getInstance(array( + new Database(array( 'dsn' => 'mysql:', 'usr' => null, 'pwd' => null, 'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION), )); @@ -185,7 +185,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase */ public function testGetOciInstance() { - Database::getInstance(array( + new Database(array( 'dsn' => 'oci:', 'usr' => null, 'pwd' => null, 'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION), )); @@ -196,7 +196,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase */ public function testGetPgsqlInstance() { - Database::getInstance(array( + new Database(array( 'dsn' => 'pgsql:', 'usr' => null, 'pwd' => null, 'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION), )); @@ -208,7 +208,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase */ public function testGetFooInstance() { - Database::getInstance(array( + new Database(array( 'dsn' => 'foo:', 'usr' => null, 'pwd' => null, 'opt' => null, )); } @@ -221,7 +221,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase { $options = $this->_options; unset($options['dsn']); - Database::getInstance($options); + new Database($options); } /** @@ -232,7 +232,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase { $options = $this->_options; unset($options['usr']); - Database::getInstance($options); + new Database($options); } /** @@ -243,7 +243,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase { $options = $this->_options; unset($options['pwd']); - Database::getInstance($options); + new Database($options); } /** @@ -254,7 +254,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase { $options = $this->_options; unset($options['opt']); - Database::getInstance($options); + new Database($options); } public function testOldAttachments() @@ -266,7 +266,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase } $this->_options['dsn'] = 'sqlite:' . $path; $this->_options['tbl'] = 'bar_'; - $model = Database::getInstance($this->_options); + $model = new Database($this->_options); $original = $paste = Helper::getPasteWithAttachment(1, array('expire_date' => 1344803344)); $meta = $paste['meta']; @@ -311,7 +311,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase } $this->_options['dsn'] = 'sqlite:' . $path; $this->_options['tbl'] = 'baz_'; - $model = Database::getInstance($this->_options); + $model = new Database($this->_options); $paste = Helper::getPaste(1, array('expire_date' => 1344803344)); unset($paste['meta']['formatter'], $paste['meta']['opendiscussion'], $paste['meta']['salt']); $model->delete(Helper::getPasteId()); @@ -378,7 +378,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase 'vizhash BLOB, ' . 'postdate INT );' ); - $this->assertInstanceOf('PrivateBin\\Data\\Database', Database::getInstance($this->_options)); + $this->assertInstanceOf('PrivateBin\\Data\\Database', new Database($this->_options)); // check if version number was upgraded in created configuration table $statement = $db->prepare('SELECT value FROM foo_config WHERE id LIKE ?'); diff --git a/tst/Data/FilesystemTest.php b/tst/Data/FilesystemTest.php index 684c2940..bc9dcd06 100644 --- a/tst/Data/FilesystemTest.php +++ b/tst/Data/FilesystemTest.php @@ -15,7 +15,7 @@ class FilesystemTest extends PHPUnit_Framework_TestCase /* Setup Routine */ $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data'; $this->_invalidPath = $this->_path . DIRECTORY_SEPARATOR . 'bar'; - $this->_model = Filesystem::getInstance(array('dir' => $this->_path)); + $this->_model = new Filesystem(array('dir' => $this->_path)); if (!is_dir($this->_path)) { mkdir($this->_path); } diff --git a/tst/Data/GoogleCloudStorageTest.php b/tst/Data/GoogleCloudStorageTest.php index 3b101c40..5f1389a3 100644 --- a/tst/Data/GoogleCloudStorageTest.php +++ b/tst/Data/GoogleCloudStorageTest.php @@ -26,7 +26,7 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase public function setUp() { ini_set('error_log', stream_get_meta_data(tmpfile())['uri']); - $this->_model = GoogleCloudStorage::getInstance(array( + $this->_model = new GoogleCloudStorage(array( 'bucket' => self::$_bucket->name(), 'prefix' => 'pastes', )); diff --git a/tst/JsonApiTest.php b/tst/JsonApiTest.php index 4ad629ee..3fa0665e 100644 --- a/tst/JsonApiTest.php +++ b/tst/JsonApiTest.php @@ -18,7 +18,7 @@ class JsonApiTest extends PHPUnit_Framework_TestCase if (!is_dir($this->_path)) { mkdir($this->_path); } - $this->_model = Filesystem::getInstance(array('dir' => $this->_path)); + $this->_model = new Filesystem(array('dir' => $this->_path)); ServerSalt::setStore($this->_model); $_POST = array(); diff --git a/tst/ModelTest.php b/tst/ModelTest.php index a88e0296..0d715c2a 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -38,7 +38,7 @@ class ModelTest extends PHPUnit_Framework_TestCase ); Helper::confBackup(); Helper::createIniFile(CONF, $options); - ServerSalt::setStore(Database::getInstance($options['model_options'])); + ServerSalt::setStore(new Database($options['model_options'])); $this->_conf = new Configuration; $this->_model = new Model($this->_conf); $_SERVER['REMOTE_ADDR'] = '::1'; @@ -156,10 +156,10 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testCommentDefaults() { + $class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model'); $comment = new Comment( $this->_conf, - forward_static_call( - 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model') . '::getInstance', + new $class( $this->_conf->getSection('model_options') ) ); @@ -445,7 +445,7 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testPurge() { $conf = new Configuration; - $store = Database::getInstance($conf->getSection('model_options')); + $store = new Database($conf->getSection('model_options')); $store->delete(Helper::getPasteId()); $expired = Helper::getPaste(2, array('expire_date' => 1344803344)); $paste = Helper::getPaste(2, array('expire_date' => time() + 3600)); diff --git a/tst/Persistence/PurgeLimiterTest.php b/tst/Persistence/PurgeLimiterTest.php index adb96ffd..899576fe 100644 --- a/tst/Persistence/PurgeLimiterTest.php +++ b/tst/Persistence/PurgeLimiterTest.php @@ -15,7 +15,7 @@ class PurgeLimiterTest extends PHPUnit_Framework_TestCase mkdir($this->_path); } PurgeLimiter::setStore( - Filesystem::getInstance(array('dir' => $this->_path)) + new Filesystem(array('dir' => $this->_path)) ); } diff --git a/tst/Persistence/ServerSaltTest.php b/tst/Persistence/ServerSaltTest.php index 3db5f7d7..ce4fc681 100644 --- a/tst/Persistence/ServerSaltTest.php +++ b/tst/Persistence/ServerSaltTest.php @@ -21,7 +21,7 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase mkdir($this->_path); } ServerSalt::setStore( - Filesystem::getInstance(array('dir' => $this->_path)) + new Filesystem(array('dir' => $this->_path)) ); $this->_otherPath = $this->_path . DIRECTORY_SEPARATOR . 'foo'; @@ -44,17 +44,17 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase { // generating new salt ServerSalt::setStore( - Filesystem::getInstance(array('dir' => $this->_path)) + new Filesystem(array('dir' => $this->_path)) ); $salt = ServerSalt::get(); // try setting a different path and resetting it ServerSalt::setStore( - Filesystem::getInstance(array('dir' => $this->_otherPath)) + new Filesystem(array('dir' => $this->_otherPath)) ); $this->assertNotEquals($salt, ServerSalt::get()); ServerSalt::setStore( - Filesystem::getInstance(array('dir' => $this->_path)) + new Filesystem(array('dir' => $this->_path)) ); $this->assertEquals($salt, ServerSalt::get()); } @@ -63,7 +63,7 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase { // try setting an invalid path chmod($this->_invalidPath, 0000); - $store = Filesystem::getInstance(array('dir' => $this->_invalidPath)); + $store = new Filesystem(array('dir' => $this->_invalidPath)); ServerSalt::setStore($store); $salt = ServerSalt::get(); ServerSalt::setStore($store); @@ -76,7 +76,7 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase chmod($this->_invalidPath, 0700); file_put_contents($this->_invalidFile, ''); chmod($this->_invalidFile, 0000); - $store = Filesystem::getInstance(array('dir' => $this->_invalidPath)); + $store = new Filesystem(array('dir' => $this->_invalidPath)); ServerSalt::setStore($store); $salt = ServerSalt::get(); ServerSalt::setStore($store); @@ -93,7 +93,7 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase } file_put_contents($this->_invalidPath . DIRECTORY_SEPARATOR . '.htaccess', ''); chmod($this->_invalidPath, 0500); - $store = Filesystem::getInstance(array('dir' => $this->_invalidPath)); + $store = new Filesystem(array('dir' => $this->_invalidPath)); ServerSalt::setStore($store); $salt = ServerSalt::get(); ServerSalt::setStore($store); @@ -105,9 +105,9 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase // try creating an invalid path chmod($this->_invalidPath, 0000); ServerSalt::setStore( - Filesystem::getInstance(array('dir' => $this->_invalidPath . DIRECTORY_SEPARATOR . 'baz')) + new Filesystem(array('dir' => $this->_invalidPath . DIRECTORY_SEPARATOR . 'baz')) ); - $store = Filesystem::getInstance(array('dir' => $this->_invalidPath)); + $store = new Filesystem(array('dir' => $this->_invalidPath)); ServerSalt::setStore($store); $salt = ServerSalt::get(); ServerSalt::setStore($store); diff --git a/tst/Persistence/TrafficLimiterTest.php b/tst/Persistence/TrafficLimiterTest.php index 84e0bae8..011b9eeb 100644 --- a/tst/Persistence/TrafficLimiterTest.php +++ b/tst/Persistence/TrafficLimiterTest.php @@ -12,7 +12,7 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase { /* Setup Routine */ $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'trafficlimit'; - $store = Filesystem::getInstance(array('dir' => $this->_path)); + $store = new Filesystem(array('dir' => $this->_path)); ServerSalt::setStore($store); TrafficLimiter::setStore($store); } diff --git a/tst/Vizhash16x16Test.php b/tst/Vizhash16x16Test.php index abfb8c49..cdc0ad3d 100644 --- a/tst/Vizhash16x16Test.php +++ b/tst/Vizhash16x16Test.php @@ -18,7 +18,7 @@ class Vizhash16x16Test extends PHPUnit_Framework_TestCase mkdir($this->_path); } $this->_file = $this->_path . DIRECTORY_SEPARATOR . 'vizhash.png'; - ServerSalt::setStore(Filesystem::getInstance(array('dir' => $this->_path))); + ServerSalt::setStore(new Filesystem(array('dir' => $this->_path))); } public function tearDown() From bde5802a3aff22c3144277defc5f0a3fed35bab0 Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Tue, 1 Nov 2022 16:38:06 +0100 Subject: [PATCH 2/7] syntax fix, changelog --- CHANGELOG.md | 1 + lib/Data/Database.php | 2 +- lib/Data/Filesystem.php | 14 +++++++------- lib/Data/GoogleCloudStorage.php | 4 ++-- lib/Data/S3Storage.php | 8 ++++---- lib/Model.php | 2 +- tst/ModelTest.php | 2 +- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b897b676..61d258c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # PrivateBin version history * **1.4.1 (not yet released)** + * ADDED: script for data storage backend migrations (#1012) * ADDED: Translations for Turkish, Slovak and Greek * ADDED: S3 Storage backend (#994) * CHANGED: Avoid `SUPER` privilege for setting the `sql_mode` for MariaDB/MySQL (#919) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 05952a95..f4318589 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -481,7 +481,7 @@ class Database extends AbstractData // migrate filesystem based salt into database $file = 'data' . DIRECTORY_SEPARATOR . 'salt.php'; if ($namespace === 'salt' && is_readable($file)) { - $fs = new Filesystem(array('dir' => 'data')); + $fs = new Filesystem(array('dir' => 'data')); $value = $fs->getValue('salt'); $this->setValue($value, 'salt'); @unlink($file); diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 3dd69578..2d082144 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -409,29 +409,29 @@ class Filesystem extends AbstractData */ public function getAllPastes() { - $pastes = array(); + $pastes = array(); $subdirs = scandir($this->_path); if ($subdirs === false) { - dieerr("Unable to list directory " . $this->_path); + dieerr('Unable to list directory ' . $this->_path); } - $subdirs = preg_grep("/^[^.].$/", $subdirs); + $subdirs = preg_grep('/^[^.].$/', $subdirs); foreach ($subdirs as $subdir) { $subpath = $this->_path . DIRECTORY_SEPARATOR . $subdir; $subsubdirs = scandir($subpath); if ($subsubdirs === false) { - dieerr("Unable to list directory " . $subpath); + dieerr('Unable to list directory ' . $subpath); } - $subsubdirs = preg_grep("/^[^.].$/", $subsubdirs); + $subsubdirs = preg_grep('/^[^.].$/', $subsubdirs); foreach ($subsubdirs as $subsubdir) { $subsubpath = $subpath . DIRECTORY_SEPARATOR . $subsubdir; $files = scandir($subsubpath); if ($files === false) { - dieerr("Unable to list directory " . $subsubpath); + dieerr('Unable to list directory ' . $subsubpath); } - $files = preg_grep("/\.php$/", $files); + $files = preg_grep('/\.php$/', $files); foreach ($files as $file) { if (substr($file, 0, 4) === $subdir . $subsubdir) { diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index e271e2f8..dd2aff39 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -353,7 +353,7 @@ class GoogleCloudStorage extends AbstractData public function getAllPastes() { $pastes = array(); - $prefix = $this->_prefix; + $prefix = $this->_prefix; if ($prefix != '') { $prefix .= '/'; } @@ -361,7 +361,7 @@ class GoogleCloudStorage extends AbstractData try { foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) { $candidate = substr($object->name(), strlen($prefix)); - if (strpos($candidate, "/") === false) { + if (strpos($candidate, '/') === false) { $pastes[] = $candidate; } } diff --git a/lib/Data/S3Storage.php b/lib/Data/S3Storage.php index c59aaaeb..f2746507 100644 --- a/lib/Data/S3Storage.php +++ b/lib/Data/S3Storage.php @@ -78,7 +78,7 @@ class S3Storage extends AbstractData * * @access public * @param array $options - * @return + * @return */ public function __construct(array $options) { @@ -453,15 +453,15 @@ class S3Storage extends AbstractData public function getAllPastes() { $pastes = array(); - $prefix = $this->_prefix; + $prefix = $this->_prefix; if ($prefix != '') { $prefix .= '/'; } try { foreach ($this->_listAllObjects($prefix) as $object) { - $candidate = substr($object["Key"], strlen($prefix)); - if (strpos($candidate, "/") === false) { + $candidate = substr($object['Key'], strlen($prefix)); + if (strpos($candidate, '/') === false) { $pastes[] = $candidate; } } diff --git a/lib/Model.php b/lib/Model.php index 94f77007..f7fdc232 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -81,7 +81,7 @@ class Model public function getStore() { if ($this->_store === null) { - $class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model'); + $class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model'); $this->_store = new $class($this->_conf->getSection('model_options')); } return $this->_store; diff --git a/tst/ModelTest.php b/tst/ModelTest.php index 0d715c2a..cf04b4db 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -156,7 +156,7 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testCommentDefaults() { - $class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model'); + $class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model'); $comment = new Comment( $this->_conf, new $class( From 726f54ce9e0476cbf3c82e0a29ca13ab432e0b7e Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Fri, 4 Nov 2022 20:19:41 +0100 Subject: [PATCH 3/7] typos --- lib/Data/AbstractData.php | 2 +- lib/Data/Database.php | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/Data/AbstractData.php b/lib/Data/AbstractData.php index eddab09b..b28fd539 100644 --- a/lib/Data/AbstractData.php +++ b/lib/Data/AbstractData.php @@ -108,7 +108,7 @@ abstract class AbstractData if ($namespace === 'traffic_limiter') { foreach ($this->_last_cache as $key => $last_submission) { if ($last_submission <= $time) { - unset($thi->_last_cache[$key]); + unset($this->_last_cache[$key]); } } } diff --git a/lib/Data/Database.php b/lib/Data/Database.php index f4318589..5502573c 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -530,16 +530,9 @@ class Database extends AbstractData */ public function getAllPastes() { - $pastes = array(); - $rows = $this->_select( - 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '"', - array() - ); - if (is_array($rows) && count($rows)) { - foreach ($rows as $row) { - $pastes[] = $row['dataid']; - } - } + $pastes = $this->_db->_query( + 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '"' + )->fetchAll(PDO::FETCH_COLUMN, 0); return $pastes; } From 3d485ecd7f9dbc508aa1bc072fb7b0a403f0f38c Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Fri, 4 Nov 2022 21:04:18 +0100 Subject: [PATCH 4/7] let GCS backends talk to the same "storage account" during testing --- tst/Bootstrap.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index 5b6012f0..f3b77870 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -34,7 +34,7 @@ class StorageClientStub extends StorageClient { private $_config = null; private $_connection = null; - private $_buckets = array(); + private static $_buckets = array(); public function __construct(array $config = array()) { @@ -44,11 +44,11 @@ class StorageClientStub extends StorageClient public function bucket($name, $userProject = false) { - if (!key_exists($name, $this->_buckets)) { + if (!key_exists($name, self::$_buckets)) { $b = new BucketStub($this->_connection, $name, array(), $this); - $this->_buckets[$name] = $b; + self::$_buckets[$name] = $b; } - return $this->_buckets[$name]; + return self::$_buckets[$name]; } /** @@ -56,8 +56,8 @@ class StorageClientStub extends StorageClient */ public function deleteBucket($name) { - if (key_exists($name, $this->_buckets)) { - unset($this->_buckets[$name]); + if (key_exists($name, self::$_buckets)) { + unset(self::$_buckets[$name]); } else { throw new NotFoundException(); } @@ -110,11 +110,11 @@ class StorageClientStub extends StorageClient public function createBucket($name, array $options = array()) { - if (key_exists($name, $this->_buckets)) { + if (key_exists($name, self::$_buckets)) { throw new BadRequestException('already exists'); } $b = new BucketStub($this->_connection, $name, array(), $this); - $this->_buckets[$name] = $b; + self::$_buckets[$name] = $b; return $b; } } From 604c9318751f71a4922b7afcd98eaf277974170b Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Fri, 4 Nov 2022 21:19:47 +0100 Subject: [PATCH 5/7] remove cache from database backend --- lib/Data/Database.php | 90 ++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 57 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 5502573c..3aaac84d 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -25,13 +25,6 @@ use PrivateBin\Json; */ class Database extends AbstractData { - /** - * cache for select queries - * - * @var array - */ - private $_cache = array(); - /** * instance of database connection * @@ -149,16 +142,6 @@ class Database extends AbstractData */ public function create($pasteid, array $paste) { - if ( - array_key_exists($pasteid, $this->_cache) - ) { - if (false !== $this->_cache[$pasteid]) { - return false; - } else { - unset($this->_cache[$pasteid]); - } - } - $expire_date = 0; $opendiscussion = $burnafterreading = false; $attachment = $attachmentname = null; @@ -222,64 +205,59 @@ class Database extends AbstractData */ public function read($pasteid) { - if (array_key_exists($pasteid, $this->_cache)) { - return $this->_cache[$pasteid]; - } - - $this->_cache[$pasteid] = false; try { - $paste = $this->_select( + $row = $this->_select( 'SELECT * FROM "' . $this->_sanitizeIdentifier('paste') . '" WHERE "dataid" = ?', array($pasteid), true ); } catch (Exception $e) { - $paste = false; + $row = false; } - if ($paste === false) { + if ($row === false) { return false; } // create array - $data = Json::decode($paste['data']); + $data = Json::decode($row['data']); $isVersion2 = array_key_exists('v', $data) && $data['v'] >= 2; if ($isVersion2) { - $this->_cache[$pasteid] = $data; - list($createdKey) = $this->_getVersionedKeys(2); + $paste = $data; + list($createdKey) = $this->_getVersionedKeys(2); } else { - $this->_cache[$pasteid] = array('data' => $paste['data']); - list($createdKey) = $this->_getVersionedKeys(1); + $paste = array('data' => $row['data']); + list($createdKey) = $this->_getVersionedKeys(1); } try { - $paste['meta'] = Json::decode($paste['meta']); + $row['meta'] = Json::decode($row['meta']); } catch (Exception $e) { - $paste['meta'] = array(); + $row['meta'] = array(); } - $paste = self::upgradePreV1Format($paste); - $this->_cache[$pasteid]['meta'] = $paste['meta']; - $this->_cache[$pasteid]['meta'][$createdKey] = (int) $paste['postdate']; - $expire_date = (int) $paste['expiredate']; + $row = self::upgradePreV1Format($row); + $paste['meta'] = $row['meta']; + $paste['meta'][$createdKey] = (int) $row['postdate']; + $expire_date = (int) $row['expiredate']; if ($expire_date > 0) { - $this->_cache[$pasteid]['meta']['expire_date'] = $expire_date; + $paste['meta']['expire_date'] = $expire_date; } if ($isVersion2) { - return $this->_cache[$pasteid]; + return $paste; } // support v1 attachments - if (array_key_exists('attachment', $paste) && !empty($paste['attachment'])) { - $this->_cache[$pasteid]['attachment'] = $paste['attachment']; - if (array_key_exists('attachmentname', $paste) && !empty($paste['attachmentname'])) { - $this->_cache[$pasteid]['attachmentname'] = $paste['attachmentname']; + 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']; } } - if ($paste['opendiscussion']) { - $this->_cache[$pasteid]['meta']['opendiscussion'] = true; + if ($row['opendiscussion']) { + $paste['meta']['opendiscussion'] = true; } - if ($paste['burnafterreading']) { - $this->_cache[$pasteid]['meta']['burnafterreading'] = true; + if ($row['burnafterreading']) { + $paste['meta']['burnafterreading'] = true; } - return $this->_cache[$pasteid]; + return $paste; } /** @@ -298,11 +276,6 @@ class Database extends AbstractData 'DELETE FROM "' . $this->_sanitizeIdentifier('comment') . '" WHERE "pasteid" = ?', array($pasteid) ); - if ( - array_key_exists($pasteid, $this->_cache) - ) { - unset($this->_cache[$pasteid]); - } } /** @@ -314,12 +287,15 @@ class Database extends AbstractData */ public function exists($pasteid) { - if ( - !array_key_exists($pasteid, $this->_cache) - ) { - $this->_cache[$pasteid] = $this->read($pasteid); + try { + $row = $this->_select( + 'SELECT * FROM "' . $this->_sanitizeIdentifier('paste') . + '" WHERE "dataid" = ?', array($pasteid), true + ); + } catch (Exception $e) { + $row = false; } - return (bool) $this->_cache[$pasteid]; + return (bool) $row; } /** From 75d28ef423e77b23acb14e0b5c526bcec94a5b90 Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Fri, 4 Nov 2022 21:25:53 +0100 Subject: [PATCH 6/7] _sanitizeClob touches no instance variables --- lib/Data/Database.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 3aaac84d..3771b5d8 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -839,10 +839,11 @@ class Database extends AbstractData * From: https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field * * @access public + * @static * @param int|string|resource $value * @return int|string */ - public function _sanitizeClob($value) + public static function _sanitizeClob($value) { if (is_resource($value)) { $value = stream_get_contents($value); From 10013ad09209c068c69d4222ff05f4a2940d4024 Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Fri, 4 Nov 2022 21:27:27 +0100 Subject: [PATCH 7/7] syntax bot --- tst/Bootstrap.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index f3b77870..48a91cb8 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -32,9 +32,9 @@ Helper::updateSubresourceIntegrity(); */ class StorageClientStub extends StorageClient { - private $_config = null; - private $_connection = null; - private static $_buckets = array(); + private $_config = null; + private $_connection = null; + private static $_buckets = array(); public function __construct(array $config = array()) {