From 3e6f1733f95c423fd51bb9113b31de1e01050f60 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 17 Nov 2025 17:28:13 +0100 Subject: [PATCH 01/14] refactored exceptions in controller - added missing exception doc blocks - introduced exception type that translates message during construction - catch explicit exception types where possible --- lib/Configuration.php | 11 +++++--- lib/Controller.php | 25 ++++++++++--------- lib/Model/AbstractModel.php | 26 ++++++++++++++----- lib/Model/Comment.php | 17 ++++++------- lib/Model/Paste.php | 28 ++++++++++----------- lib/Persistence/TrafficLimiter.php | 9 +++---- lib/TranslatedException.php | 36 +++++++++++++++++++++++++++ vendor/composer/autoload_classmap.php | 1 + vendor/composer/autoload_static.php | 1 + vendor/composer/installed.php | 4 +-- 10 files changed, 105 insertions(+), 53 deletions(-) create mode 100644 lib/TranslatedException.php diff --git a/lib/Configuration.php b/lib/Configuration.php index 2cccc342..c4651f8b 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -12,6 +12,7 @@ namespace PrivateBin; use Exception; +use PrivateBin\TranslatedException; /** * Configuration @@ -131,7 +132,7 @@ class Configuration /** * parse configuration file and ensure default configuration values are present * - * @throws Exception + * @throws TranslatedException */ public function __construct() { @@ -148,7 +149,9 @@ class Configuration $config = parse_ini_file($configFile, true); foreach (array('main', 'model', 'model_options') as $section) { if (!array_key_exists($section, $config)) { - throw new Exception(I18n::_('PrivateBin requires configuration section [%s] to be present in configuration file.', $section), 2); + $name = array_key_exists('main', $config) && array_key_exists('name', $config['main']) ? + $config['main']['name'] : self::getDefaults()['main']['name']; + throw new TranslatedException(array('%s requires configuration section [%s] to be present in configuration file.', I18n::_($name), $section), 2); } } break; @@ -304,13 +307,13 @@ class Configuration * get a section from the configuration, must exist * * @param string $section - * @throws Exception + * @throws TranslatedException * @return mixed */ public function getSection($section) { if (!array_key_exists($section, $this->_configuration)) { - throw new Exception(I18n::_('%s requires configuration section [%s] to be present in configuration file.', I18n::_($this->getKey('name')), $section), 3); + throw new TranslatedException(array('%s requires configuration section [%s] to be present in configuration file.', I18n::_($this->getKey('name')), $section), 3); } return $this->_configuration[$section]; } diff --git a/lib/Controller.php b/lib/Controller.php index bb161907..2e056db6 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -17,6 +17,7 @@ use PrivateBin\Persistence\TrafficLimiter; use PrivateBin\Proxy\AbstractProxy; use PrivateBin\Proxy\ShlinkProxy; use PrivateBin\Proxy\YourlsProxy; +use PrivateBin\TranslatedException; /** * Controller @@ -195,6 +196,7 @@ class Controller * Set default language * * @access private + * @throws Exception */ private function _setDefaultLanguage() { @@ -211,6 +213,7 @@ class Controller * Set default template * * @access private + * @throws Exception */ private function _setDefaultTemplate() { @@ -260,6 +263,7 @@ class Controller * pasteid (optional) = in discussions, which paste this comment belongs to. * * @access private + * @throws Exception * @return string */ private function _create() @@ -270,8 +274,7 @@ class Controller TrafficLimiter::setStore($this->_model->getStore()); try { TrafficLimiter::canPass(); - } catch (Exception $e) { - // traffic limiter exceptions come translated + } catch (TranslatedException $e) { $this->_json_error($e->getMessage()); return; } @@ -305,9 +308,8 @@ class Controller $comment = $paste->getComment($data['parentid']); $comment->setData($data); $comment->store(); - } catch (Exception $e) { - // comment exceptions need translation - $this->_json_error(I18n::_($e->getMessage())); + } catch (TranslatedException $e) { + $this->_json_error($e->getMessage()); return; } $this->_json_result($comment->getId()); @@ -319,7 +321,7 @@ class Controller else { try { $this->_model->purge(); - } catch (Exception $e) { + } catch (Exception $e) { // JSON error!!! error_log('Error purging documents: ' . $e->getMessage() . PHP_EOL . 'Use the administration scripts statistics to find ' . 'damaged paste IDs and either delete them or restore them ' . @@ -329,9 +331,8 @@ class Controller try { $paste->setData($data); $paste->store(); - } catch (Exception $e) { - // paste exceptions need translation - $this->_json_error(I18n::_($e->getMessage())); + } catch (TranslatedException $e) { + $this->_json_error($e->getMessage()); return; } $this->_json_result($paste->getId(), array('deletetoken' => $paste->getDeleteToken())); @@ -399,9 +400,8 @@ class Controller } else { $this->_json_error(I18n::_(self::GENERIC_ERROR)); } - } catch (Exception $e) { - // paste exceptions need translation - $this->_json_error(I18n::_($e->getMessage())); + } catch (TranslatedException $e) { + $this->_json_error($e->getMessage()); } } @@ -409,6 +409,7 @@ class Controller * Display frontend. * * @access private + * @throws Exception */ private function _view() { diff --git a/lib/Model/AbstractModel.php b/lib/Model/AbstractModel.php index d7b5102f..5ff4ab1b 100644 --- a/lib/Model/AbstractModel.php +++ b/lib/Model/AbstractModel.php @@ -11,9 +11,9 @@ namespace PrivateBin\Model; -use Exception; use PrivateBin\Configuration; use PrivateBin\Data\AbstractData; +use PrivateBin\TranslatedException; /** * AbstractModel @@ -22,6 +22,20 @@ use PrivateBin\Data\AbstractData; */ abstract class AbstractModel { + /** + * show the same error message if the data is invalid + * + * @const string + */ + const INVALID_DATA_ERROR = 'Invalid data.'; + + /** + * show the same error message if the document ID already exists + * + * @const string + */ + const COLLISION_ERROR = 'You are unlucky. Try again.'; + /** * Instance ID. * @@ -83,12 +97,12 @@ abstract class AbstractModel * * @access public * @param string $id - * @throws Exception + * @throws TranslatedException */ public function setId($id) { if (!self::isValidId($id)) { - throw new Exception('Invalid document ID.', 60); + throw new TranslatedException('Invalid document ID.', 60); } $this->_id = $id; } @@ -98,7 +112,7 @@ abstract class AbstractModel * * @access public * @param array $data - * @throws Exception + * @throws TranslatedException */ public function setData(array &$data) { @@ -125,7 +139,7 @@ abstract class AbstractModel * Store the instance's data. * * @access public - * @throws Exception + * @throws TranslatedException */ abstract public function store(); @@ -163,7 +177,7 @@ abstract class AbstractModel * * @access protected * @param array $data - * @throws Exception + * @throws TranslatedException */ protected function _validate(array &$data) { diff --git a/lib/Model/Comment.php b/lib/Model/Comment.php index 3c0b2b20..3a9d6336 100644 --- a/lib/Model/Comment.php +++ b/lib/Model/Comment.php @@ -11,10 +11,10 @@ namespace PrivateBin\Model; -use Exception; use Identicon\Identicon; use Jdenticon\Identicon as Jdenticon; use PrivateBin\Persistence\TrafficLimiter; +use PrivateBin\TranslatedException; use PrivateBin\Vizhash16x16; /** @@ -36,24 +36,24 @@ class Comment extends AbstractModel * Store the comment's data. * * @access public - * @throws Exception + * @throws TranslatedException */ public function store() { // Make sure paste exists. $pasteid = $this->getPaste()->getId(); if (!$this->getPaste()->exists()) { - throw new Exception('Invalid data.', 67); + throw new TranslatedException(self::INVALID_DATA_ERROR, 67); } // Make sure the discussion is opened in this paste and allowed in the configuration. if (!$this->getPaste()->isOpendiscussion() || !$this->_conf->getKey('discussion')) { - throw new Exception('Invalid data.', 68); + throw new TranslatedException(self::INVALID_DATA_ERROR, 68); } // Check for improbable collision. if ($this->exists()) { - throw new Exception('You are unlucky. Try again.', 69); + throw new TranslatedException(self::COLLISION_ERROR, 69); } $this->_data['meta']['created'] = time(); @@ -67,7 +67,7 @@ class Comment extends AbstractModel $this->_data ) === false ) { - throw new Exception('Error saving comment. Sorry.', 70); + throw new TranslatedException('Error saving comment. Sorry.', 70); } } @@ -91,7 +91,6 @@ class Comment extends AbstractModel * * @access public * @param Paste $paste - * @throws Exception */ public function setPaste(Paste &$paste) { @@ -115,12 +114,12 @@ class Comment extends AbstractModel * * @access public * @param string $id - * @throws Exception + * @throws TranslatedException */ public function setParentId($id) { if (!self::isValidId($id)) { - throw new Exception('Invalid document ID.', 65); + throw new TranslatedException('Invalid document ID.', 65); } $this->_data['parentid'] = $id; } diff --git a/lib/Model/Paste.php b/lib/Model/Paste.php index a42ab35e..0d52b2a3 100644 --- a/lib/Model/Paste.php +++ b/lib/Model/Paste.php @@ -11,9 +11,9 @@ namespace PrivateBin\Model; -use Exception; use PrivateBin\Controller; use PrivateBin\Persistence\ServerSalt; +use PrivateBin\TranslatedException; /** * Paste @@ -47,14 +47,14 @@ class Paste extends AbstractModel * Get paste data. * * @access public - * @throws Exception + * @throws TranslatedException * @return array */ public function get() { $data = $this->_store->read($this->getId()); if ($data === false) { - throw new Exception(Controller::GENERIC_ERROR, 64); + throw new TranslatedException(Controller::GENERIC_ERROR, 64); } // check if paste has expired and delete it if necessary. @@ -62,7 +62,7 @@ class Paste extends AbstractModel $now = time(); if ($data['meta']['expire_date'] < $now) { $this->delete(); - throw new Exception(Controller::GENERIC_ERROR, 63); + throw new TranslatedException(Controller::GENERIC_ERROR, 63); } // We kindly provide the remaining time before expiration (in seconds) $data['meta']['time_to_live'] = $data['meta']['expire_date'] - $now; @@ -93,13 +93,13 @@ class Paste extends AbstractModel * Store the paste's data. * * @access public - * @throws Exception + * @throws TranslatedException */ public function store() { // Check for improbable collision. if ($this->exists()) { - throw new Exception('You are unlucky. Try again.', 75); + throw new TranslatedException(self::COLLISION_ERROR, 75); } $this->_data['meta']['salt'] = ServerSalt::generate(); @@ -111,7 +111,7 @@ class Paste extends AbstractModel $this->_data ) === false ) { - throw new Exception('Error saving document. Sorry.', 76); + throw new TranslatedException('Error saving document. Sorry.', 76); } } @@ -119,7 +119,6 @@ class Paste extends AbstractModel * Delete the paste. * * @access public - * @throws Exception */ public function delete() { @@ -143,13 +142,13 @@ class Paste extends AbstractModel * @access public * @param string $parentId * @param string $commentId - * @throws Exception + * @throws TranslatedException * @return Comment */ public function getComment($parentId, $commentId = '') { if (!$this->exists()) { - throw new Exception('Invalid data.', 62); + throw new TranslatedException(self::INVALID_DATA_ERROR, 62); } $comment = new Comment($this->_conf, $this->_store); $comment->setPaste($this); @@ -201,7 +200,6 @@ class Paste extends AbstractModel * Check if paste has discussions enabled. * * @access public - * @throws Exception * @return bool */ public function isOpendiscussion() @@ -240,13 +238,13 @@ class Paste extends AbstractModel * * @access protected * @param array $data - * @throws Exception + * @throws TranslatedException */ protected function _validate(array &$data) { // reject invalid or disabled formatters if (!array_key_exists($data['adata'][self::ADATA_FORMATTER], $this->_conf->getSection('formatter_options'))) { - throw new Exception('Invalid data.', 75); + throw new TranslatedException(self::INVALID_DATA_ERROR, 75); } // discussion requested, but disabled in config or burn after reading requested as well, or invalid integer @@ -257,7 +255,7 @@ class Paste extends AbstractModel )) || ($data['adata'][self::ADATA_OPEN_DISCUSSION] !== 0 && $data['adata'][self::ADATA_OPEN_DISCUSSION] !== 1) ) { - throw new Exception('Invalid data.', 74); + throw new TranslatedException(self::INVALID_DATA_ERROR, 74); } // reject invalid burn after reading @@ -265,7 +263,7 @@ class Paste extends AbstractModel $data['adata'][self::ADATA_BURN_AFTER_READING] !== 0 && $data['adata'][self::ADATA_BURN_AFTER_READING] !== 1 ) { - throw new Exception('Invalid data.', 73); + throw new TranslatedException(self::INVALID_DATA_ERROR, 73); } } } diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 60977f5d..c94bfdb2 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -12,11 +12,10 @@ namespace PrivateBin\Persistence; -use Exception; use IPLib\Factory; use IPLib\ParseStringFlag; use PrivateBin\Configuration; -use PrivateBin\I18n; +use PrivateBin\TranslatedException; /** * TrafficLimiter @@ -167,7 +166,7 @@ class TrafficLimiter extends AbstractPersistence * * @access public * @static - * @throws Exception + * @throws TranslatedException * @return true */ public static function canPass() @@ -181,7 +180,7 @@ class TrafficLimiter extends AbstractPersistence return true; } } - throw new Exception(I18n::_('Your IP is not authorized to create documents.')); + throw new TranslatedException('Your IP is not authorized to create documents.'); } // disable limits if set to less then 1 @@ -210,7 +209,7 @@ class TrafficLimiter extends AbstractPersistence } return true; } - throw new Exception(I18n::_( + throw new TranslatedException(array( 'Please wait %d seconds between each post.', self::$_limit )); diff --git a/lib/TranslatedException.php b/lib/TranslatedException.php new file mode 100644 index 00000000..bbadec43 --- /dev/null +++ b/lib/TranslatedException.php @@ -0,0 +1,36 @@ + $baseDir . '/lib/Proxy/YourlsProxy.php', 'PrivateBin\\Request' => $baseDir . '/lib/Request.php', 'PrivateBin\\TemplateSwitcher' => $baseDir . '/lib/TemplateSwitcher.php', + 'PrivateBin\\TranslatedException' => $baseDir . '/lib/TranslatedException.php', 'PrivateBin\\View' => $baseDir . '/lib/View.php', 'PrivateBin\\Vizhash16x16' => $baseDir . '/lib/Vizhash16x16.php', 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index e9194d01..aa7dd9e8 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -139,6 +139,7 @@ class ComposerStaticInitDontChange 'PrivateBin\\Proxy\\YourlsProxy' => __DIR__ . '/../..' . '/lib/Proxy/YourlsProxy.php', 'PrivateBin\\Request' => __DIR__ . '/../..' . '/lib/Request.php', 'PrivateBin\\TemplateSwitcher' => __DIR__ . '/../..' . '/lib/TemplateSwitcher.php', + 'PrivateBin\\TranslatedException' => __DIR__ . '/../..' . '/lib/TranslatedException.php', 'PrivateBin\\View' => __DIR__ . '/../..' . '/lib/View.php', 'PrivateBin\\Vizhash16x16' => __DIR__ . '/../..' . '/lib/Vizhash16x16.php', 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 56dccd55..c9befe93 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'privatebin/privatebin', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '06496a1b0e975b79c5a7abc0bd54b492ca264640', + 'reference' => 'a051c4bd6b71fd56f0d7fc235e815dafc8eb54ea', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -31,7 +31,7 @@ 'privatebin/privatebin' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '06496a1b0e975b79c5a7abc0bd54b492ca264640', + 'reference' => 'a051c4bd6b71fd56f0d7fc235e815dafc8eb54ea', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), From 3a23117ebfcfaf6fddfda82f73789df89e2e6adf Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 19 Nov 2025 09:36:08 +0100 Subject: [PATCH 02/14] Refactored translation of exception messages --- CHANGELOG.md | 4 +- composer.lock | 24 ++++----- lib/Configuration.php | 2 +- lib/Controller.php | 28 +++++------ lib/Data/Database.php | 55 ++++++++++++++------- lib/Data/Filesystem.php | 20 ++++---- lib/Data/GoogleCloudStorage.php | 8 ++- lib/Data/S3Storage.php | 11 +++-- lib/Exception/JsonException.php | 37 ++++++++++++++ lib/{ => Exception}/TranslatedException.php | 2 +- lib/I18n.php | 2 + lib/Json.php | 21 +++----- lib/Model/AbstractModel.php | 2 +- lib/Model/Comment.php | 2 +- lib/Model/Paste.php | 2 +- lib/Persistence/TrafficLimiter.php | 2 +- lib/Proxy/AbstractProxy.php | 4 +- lib/Proxy/ShlinkProxy.php | 18 ++++--- lib/Request.php | 4 +- tpl/bootstrap.php | 19 ++++--- tpl/bootstrap5.php | 19 ++++--- vendor/composer/autoload_classmap.php | 3 +- vendor/composer/autoload_static.php | 3 +- vendor/composer/installed.php | 4 +- 24 files changed, 186 insertions(+), 110 deletions(-) create mode 100644 lib/Exception/JsonException.php rename lib/{ => Exception}/TranslatedException.php (96%) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7977c0d..8002155b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ # PrivateBin version history ## 2.0.4 (not yet released) -* CHANGED: Deduplicate JSON error message translations. +* CHANGED: Deduplicate JSON error message translations +* CHANGED: Refactored translation of exception messages +* FIXED: Some exceptions not getting translated ## 1.7.9 (2025-11-13) * CHANGED: Upgrading libraries to: base-x 5.0.1, bootstrap 5.3.8, DOMpurify 3.2.7, ip-lib 1.21.0 & kjua 0.10.0 diff --git a/composer.lock b/composer.lock index dac377f9..a7a66ba0 100644 --- a/composer.lock +++ b/composer.lock @@ -397,16 +397,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + "reference": "3a454ca033b9e06b63282ce19562e892747449bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", + "reference": "3a454ca033b9e06b63282ce19562e892747449bb", "shasum": "" }, "require": { @@ -449,9 +449,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" }, - "time": "2025-08-13T20:13:15+00:00" + "time": "2025-10-21T19:32:17+00:00" }, { "name": "phar-io/manifest", @@ -2014,16 +2014,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "d74205c497bfbca49f34d4bc4c19c17e22db4ebb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/d74205c497bfbca49f34d4bc4c19c17e22db4ebb", + "reference": "d74205c497bfbca49f34d4bc4c19c17e22db4ebb", "shasum": "" }, "require": { @@ -2052,7 +2052,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.0" }, "funding": [ { @@ -2060,7 +2060,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-13T13:44:09+00:00" } ], "aliases": [], diff --git a/lib/Configuration.php b/lib/Configuration.php index c4651f8b..7836c3ec 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -12,7 +12,7 @@ namespace PrivateBin; use Exception; -use PrivateBin\TranslatedException; +use PrivateBin\Exception\TranslatedException; /** * Configuration diff --git a/lib/Controller.php b/lib/Controller.php index 2e056db6..056647de 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -12,12 +12,13 @@ namespace PrivateBin; use Exception; +use PrivateBin\Exception\JsonException; +use PrivateBin\Exception\TranslatedException; use PrivateBin\Persistence\ServerSalt; use PrivateBin\Persistence\TrafficLimiter; use PrivateBin\Proxy\AbstractProxy; use PrivateBin\Proxy\ShlinkProxy; use PrivateBin\Proxy\YourlsProxy; -use PrivateBin\TranslatedException; /** * Controller @@ -308,11 +309,10 @@ class Controller $comment = $paste->getComment($data['parentid']); $comment->setData($data); $comment->store(); - } catch (TranslatedException $e) { + $this->_json_result($comment->getId()); + } catch (Exception $e) { $this->_json_error($e->getMessage()); - return; } - $this->_json_result($comment->getId()); } else { $this->_json_error(I18n::_('Invalid data.')); } @@ -321,21 +321,13 @@ class Controller else { try { $this->_model->purge(); - } catch (Exception $e) { // JSON error!!! - error_log('Error purging documents: ' . $e->getMessage() . PHP_EOL . - 'Use the administration scripts statistics to find ' . - 'damaged paste IDs and either delete them or restore them ' . - 'from backup.'); - } - $paste = $this->_model->getPaste(); - try { + $paste = $this->_model->getPaste(); $paste->setData($data); $paste->store(); - } catch (TranslatedException $e) { + $this->_json_result($paste->getId(), array('deletetoken' => $paste->getDeleteToken())); + } catch (Exception $e) { $this->_json_error($e->getMessage()); - return; } - $this->_json_result($paste->getId(), array('deletetoken' => $paste->getDeleteToken())); } } @@ -365,7 +357,7 @@ class Controller } else { $this->_error = self::GENERIC_ERROR; } - } catch (Exception $e) { + } catch (TranslatedException $e) { $this->_error = $e->getMessage(); } if ($this->_request->isJsonApiCall()) { @@ -470,7 +462,7 @@ class Controller } $page->assign('BASEPATH', I18n::_($this->_conf->getKey('basepath'))); $page->assign('STATUS', I18n::_($this->_status)); - $page->assign('ISDELETED', I18n::_(json_encode($this->_is_deleted))); + $page->assign('ISDELETED', $this->_is_deleted); $page->assign('VERSION', self::VERSION); $page->assign('DISCUSSION', $this->_conf->getKey('discussion')); $page->assign('OPENDISCUSSION', $this->_conf->getKey('opendiscussion')); @@ -546,6 +538,7 @@ class Controller * * @access private * @param string $error + * @throws JsonException */ private function _json_error($error) { @@ -562,6 +555,7 @@ class Controller * @access private * @param string $dataid * @param array $other + * @throws JsonException */ private function _json_result($dataid, $other = array()) { diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 0fd42dcc..108278a5 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -15,6 +15,7 @@ use Exception; use PDO; use PDOException; use PrivateBin\Controller; +use PrivateBin\Exception\JsonException; use PrivateBin\Json; /** @@ -179,18 +180,24 @@ class Database extends AbstractData 'SELECT * FROM "' . $this->_sanitizeIdentifier('paste') . '" WHERE "dataid" = ?', array($pasteid), true ); - } catch (Exception $e) { + } catch (PDOException $e) { $row = false; } if ($row === false) { return false; } // create array - $paste = Json::decode($row['data']); + try { + $paste = Json::decode($row['data']); + } catch (JsonException $e) { + error_log('Error while reading a paste from the database: ' . $e->getMessage()); + $paste = array(); + } try { $paste['meta'] = Json::decode($row['meta']); - } catch (Exception $e) { + } catch (JsonException $e) { + error_log('Error while reading a paste from the database: ' . $e->getMessage()); $paste['meta'] = array(); } $expire_date = (int) $row['expiredate']; @@ -233,7 +240,7 @@ class Database extends AbstractData 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '" WHERE "dataid" = ?', array($pasteid), true ); - } catch (Exception $e) { + } catch (PDOException $e) { return false; } return (bool) $row; @@ -253,7 +260,7 @@ class Database extends AbstractData { try { $data = Json::encode($comment); - } catch (Exception $e) { + } catch (JsonException $e) { error_log('Error while attempting to insert a comment into the database: ' . $e->getMessage()); return false; } @@ -274,7 +281,7 @@ class Database extends AbstractData $meta['created'], ) ); - } catch (Exception $e) { + } catch (PDOException $e) { error_log('Error while attempting to insert a comment into the database: ' . $e->getMessage()); return false; } @@ -298,8 +305,14 @@ class Database extends AbstractData $comments = array(); if (count($rows)) { foreach ($rows as $row) { + try { + $data = Json::decode($row['data']); + } catch (JsonException $e) { + error_log('Error while reading a comment from the database: ' . $e->getMessage()); + $data = array(); + } $i = $this->getOpenSlot($comments, (int) $row['postdate']); - $comments[$i] = Json::decode($row['data']); + $comments[$i] = $data; $comments[$i]['id'] = $row['dataid']; $comments[$i]['parentid'] = $row['parentid']; $comments[$i]['meta'] = array('created' => (int) $row['postdate']); @@ -329,7 +342,7 @@ class Database extends AbstractData '" WHERE "pasteid" = ? AND "parentid" = ? AND "dataid" = ?', array($pasteid, $parentid, $commentid), true ); - } catch (Exception $e) { + } catch (PDOException $e) { return false; } } @@ -349,7 +362,8 @@ class Database extends AbstractData $this->_last_cache[$key] = $value; try { $value = Json::encode($this->_last_cache); - } catch (Exception $e) { + } catch (JsonException $e) { + error_log('Error encoding JSON for table "config", row "traffic_limiter": ' . $e->getMessage()); return false; } } @@ -393,7 +407,8 @@ class Database extends AbstractData if ($value && $namespace === 'traffic_limiter') { try { $this->_last_cache = Json::decode($value); - } catch (Exception $e) { + } catch (JsonException $e) { + error_log('Error decoding JSON from table "config", row "traffic_limiter": ' . $e->getMessage()); $this->_last_cache = array(); } if (array_key_exists($key, $this->_last_cache)) { @@ -412,13 +427,18 @@ class Database extends AbstractData */ protected function _getExpiredPastes($batchsize) { - $statement = $this->_db->prepare( - 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . - '" WHERE "expiredate" < ? AND "expiredate" != ? ' . - ($this->_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?') - ); - $statement->execute(array(time(), 0, $batchsize)); - return $statement->fetchAll(PDO::FETCH_COLUMN, 0); + try { + $statement = $this->_db->prepare( + 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . + '" WHERE "expiredate" < ? AND "expiredate" != ? ' . + ($this->_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?') + ); + $statement->execute(array(time(), 0, $batchsize)); + return $statement->fetchAll(PDO::FETCH_COLUMN, 0); + } catch (PDOException $e) { + error_log('Error while attempting to find expired pastes in the database: ' . $e->getMessage()); + return array(); + } } /** @@ -552,6 +572,7 @@ class Database extends AbstractData '" WHERE "id" = ?', array($key), true ); } catch (PDOException $e) { + error_log('Error while attempting to fetch configuration key "' . $key . '" in the database: ' . $e->getMessage()); return ''; } return $row ? $row['value'] : ''; diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index e4377a9f..751980ab 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -11,8 +11,8 @@ namespace PrivateBin\Data; -use Exception; use GlobIterator; +use PrivateBin\Exception\JsonException; use PrivateBin\Json; /** @@ -104,13 +104,10 @@ class Filesystem extends AbstractData */ public function read($pasteid) { - if ( - !$this->exists($pasteid) || - !$paste = $this->_get($this->_dataid2path($pasteid) . $pasteid . '.php') - ) { - return false; + if ($this->exists($pasteid)) { + return $this->_get($this->_dataid2path($pasteid) . $pasteid . '.php'); } - return $paste; + return false; } /** @@ -346,7 +343,12 @@ class Filesystem extends AbstractData file_get_contents($filename), strlen(self::PROTECTION_LINE . PHP_EOL) ); - return Json::decode($data); + try { + return Json::decode($data); + } catch (JsonException $e) { + error_log('Error decoding JSON from "' . $filename . '": ' . $e->getMessage()); + return false; + } } /** @@ -450,7 +452,7 @@ class Filesystem extends AbstractData $filename, self::PROTECTION_LINE . PHP_EOL . Json::encode($data) ); - } catch (Exception $e) { + } catch (JsonException $e) { error_log('Error while trying to store data to the filesystem at path "' . $filename . '": ' . $e->getMessage()); return false; } diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index 9f64abe5..2ebfb7fe 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -15,6 +15,7 @@ use Exception; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Storage\Bucket; use Google\Cloud\Storage\StorageClient; +use PrivateBin\Exception\JsonException; use PrivateBin\Json; class GoogleCloudStorage extends AbstractData @@ -219,7 +220,12 @@ class GoogleCloudStorage extends AbstractData try { foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $key) { $data = $this->_bucket->object($key->name())->downloadAsString(); - $comment = Json::decode($data); + try { + $comment = Json::decode($data); + } catch (JsonException $e) { + error_log('failed to read comment from ' . $key->name() . ', ' . $e->getMessage()); + $comment = array(); + } $comment['id'] = basename($key->name()); $slot = $this->getOpenSlot($comments, (int) $comment['meta']['created']); $comments[$slot] = $comment; diff --git a/lib/Data/S3Storage.php b/lib/Data/S3Storage.php index 526a9ec2..575f90f4 100644 --- a/lib/Data/S3Storage.php +++ b/lib/Data/S3Storage.php @@ -37,6 +37,7 @@ namespace PrivateBin\Data; use Aws\S3\Exception\S3Exception; use Aws\S3\S3Client; +use PrivateBin\Exception\JsonException; use PrivateBin\Json; class S3Storage extends AbstractData @@ -177,12 +178,14 @@ class S3Storage extends AbstractData 'ContentType' => 'application/json', 'Metadata' => $metadata, )); + return true; } catch (S3Exception $e) { error_log('failed to upload ' . $key . ' to ' . $this->_bucket . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); - return false; + } catch (JsonException $e) { + error_log('failed to JSON encode ' . $key . ', ' . $e->getMessage()); } - return true; + return false; } /** @@ -212,8 +215,10 @@ class S3Storage extends AbstractData } catch (S3Exception $e) { error_log('failed to read ' . $pasteid . ' from ' . $this->_bucket . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); - return false; + } catch (JsonException $e) { + error_log('failed to JSON decode ' . $key . ', ' . $e->getMessage()); } + return false; } /** diff --git a/lib/Exception/JsonException.php b/lib/Exception/JsonException.php new file mode 100644 index 00000000..dc844a2d --- /dev/null +++ b/lib/Exception/JsonException.php @@ -0,0 +1,37 @@ +_error = 'Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.'; $this->logErrorWithClassName('Error calling proxy: ' . $e->getMessage()); return; diff --git a/lib/Proxy/ShlinkProxy.php b/lib/Proxy/ShlinkProxy.php index ee4507cf..639a957c 100644 --- a/lib/Proxy/ShlinkProxy.php +++ b/lib/Proxy/ShlinkProxy.php @@ -12,6 +12,7 @@ namespace PrivateBin\Proxy; use PrivateBin\Configuration; +use PrivateBin\Exception\JsonException; use PrivateBin\Json; /** @@ -48,12 +49,17 @@ class ShlinkProxy extends AbstractProxy 'longUrl' => $link, ); - return array( - 'method' => 'POST', - 'header' => "Content-Type: application/json\r\n" . - 'X-Api-Key: ' . $shlink_api_key . "\r\n", - 'content' => Json::encode($body), - ); + try { + return array( + 'method' => 'POST', + 'header' => "Content-Type: application/json\r\n" . + 'X-Api-Key: ' . $shlink_api_key . "\r\n", + 'content' => Json::encode($body), + ); + } catch (JsonException $e) { + error_log('[' . get_class($this) . '] Error encoding body: ' . $e->getMessage()); + return array(); + } } /** diff --git a/lib/Request.php b/lib/Request.php index 24a083be..fb6fb37c 100644 --- a/lib/Request.php +++ b/lib/Request.php @@ -11,7 +11,7 @@ namespace PrivateBin; -use Exception; +use PrivateBin\Exception\JsonException; use PrivateBin\Model\Paste; /** @@ -113,7 +113,7 @@ class Request try { $data = file_get_contents(self::$_inputStream); $this->_params = Json::decode($data); - } catch (Exception $e) { + } catch (JsonException $e) { // ignore error, $this->_params will remain empty } break; diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index e842065d..b219f713 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -513,16 +513,19 @@ if ($FILEUPLOAD) : -