Merge branch 'master' into xss/jsImprove

This commit is contained in:
rugk
2026-02-23 16:48:21 +01:00
committed by GitHub
66 changed files with 1370 additions and 1202 deletions

View File

@@ -12,6 +12,7 @@
namespace PrivateBin;
use Exception;
use PrivateBin\Exception\TranslatedException;
/**
* Configuration
@@ -119,19 +120,19 @@ class Configuration
'js/dark-mode-switch.js' => 'sha512-BhY7dNU14aDN5L+muoUmA66x0CkYUWkQT0nxhKBLP/o2d7jE025+dvWJa4OiYffBGEFgmhrD/Sp+QMkxGMTz2g==',
'js/jquery-3.7.1.js' => 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==',
'js/kjua-0.10.0.js' => 'sha512-BYj4xggowR7QD150VLSTRlzH62YPfhpIM+b/1EUEr7RQpdWAGKulxWnOvjFx1FUlba4m6ihpNYuQab51H6XlYg==',
'js/legacy.js' => 'sha512-rGXYUpIqbFoHAgBXZ0UlJBdNAIMOC9EQ67MG0X46D5uRB8LvwzgKirbSQRGdYfk8I2jsUcm+tvHXYboUnC6DUg==',
'js/legacy.js' => 'sha512-RQEo1hxpNc37i+jz/D9/JiAZhG8GFx3+SNxjYnI7jUgirDIqrCSj6QPAAZeaidditcWzsJ3jxfEj5lVm7ZwTRQ==',
'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==',
'js/privatebin.js' => 'sha512-lR/UzD67Pbg9nHDxDHxXktRKbXTQ96bDucbzbc0ELOLGzJHBqv1Qih+Aw+blBlAxXAdjuJDf9ch+R4m+i5bsQg==',
'js/privatebin.js' => 'sha512-4kyDedBvdmfL+0OQcVMkHIAsf4TMW8/iuKyQfYJYfjxc6lPYwFiBQo7Qvy6sILRnKy6TJoK0KmTFDDi1p83vHA==',
'js/purify-3.3.0.js' => 'sha512-lsHD5zxs4lu/NDzaaibe27Vd2t7Cy9JQ3qDHUvDfb4oZvKoWDNEhwUY+4bT3R68cGgpgCYp8U1x2ifeVxqurdQ==',
'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==',
'js/zlib-1.3.1-1.js' => 'sha512-5bU9IIP4PgBrOKLZvGWJD4kgfQrkTz8Z3Iqeu058mbQzW3mCumOU6M3UVbVZU9rrVoVwaW4cZK8U8h5xjF88eQ==',
'js/zlib-1.3.1-2.js' => 'sha512-4gT+v+BkBqdVBbKOO4qKGOAzuay+v1FmOLksS+bMgQ08Oo4xEb3X48Xq1Kv2b4HtiCQA7xq9dFRzxal7jmQI7w==',
),
);
/**
* parse configuration file and ensure default configuration values are present
*
* @throws Exception
* @throws TranslatedException
*/
public function __construct()
{
@@ -148,7 +149,8 @@ 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 = $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;
@@ -158,7 +160,7 @@ class Configuration
$opts = '_options';
foreach (self::getDefaults() as $section => $values) {
// fill missing sections with default values
if (!array_key_exists($section, $config) || count($config[$section]) == 0) {
if (!array_key_exists($section, $config) || count($config[$section]) === 0) {
$this->_configuration[$section] = $values;
if (array_key_exists('dir', $this->_configuration[$section])) {
$this->_configuration[$section]['dir'] = PATH . $this->_configuration[$section]['dir'];
@@ -167,7 +169,7 @@ class Configuration
}
// provide different defaults for database model
elseif (
$section == 'model_options' &&
$section === 'model_options' &&
$this->_configuration['model']['class'] === 'Database'
) {
$values = array(
@@ -178,7 +180,7 @@ class Configuration
'opt' => array(),
);
} elseif (
$section == 'model_options' &&
$section === 'model_options' &&
$this->_configuration['model']['class'] === 'GoogleCloudStorage'
) {
$values = array(
@@ -187,7 +189,7 @@ class Configuration
'uniformacl' => false,
);
} elseif (
$section == 'model_options' &&
$section === 'model_options' &&
$this->_configuration['model']['class'] === 'S3Storage'
) {
$values = array(
@@ -216,11 +218,11 @@ class Configuration
// check for missing keys and set defaults if necessary
else {
// preserve configured SRI hashes
if ($section == 'sri' && array_key_exists($section, $config)) {
if ($section === 'sri' && array_key_exists($section, $config)) {
$this->_configuration[$section] = $config[$section];
}
foreach ($values as $key => $val) {
if ($key == 'dir') {
if ($key === 'dir') {
$val = PATH . $val;
}
$result = $val;
@@ -304,13 +306,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];
}

View File

@@ -12,6 +12,8 @@
namespace PrivateBin;
use Exception;
use PrivateBin\Exception\JsonException;
use PrivateBin\Exception\TranslatedException;
use PrivateBin\Persistence\ServerSalt;
use PrivateBin\Persistence\TrafficLimiter;
use PrivateBin\Proxy\AbstractProxy;
@@ -195,13 +197,14 @@ class Controller
* Set default language
*
* @access private
* @throws Exception
*/
private function _setDefaultLanguage()
{
$lang = $this->_conf->getKey('languagedefault');
I18n::setLanguageFallback($lang);
// force default language, if language selection is disabled and a default is set
if (!$this->_conf->getKey('languageselection') && strlen($lang) == 2) {
if (!$this->_conf->getKey('languageselection') && strlen($lang) === 2) {
$_COOKIE['lang'] = $lang;
setcookie('lang', $lang, array('SameSite' => 'Lax', 'Secure' => true));
}
@@ -211,6 +214,7 @@ class Controller
* Set default template
*
* @access private
* @throws Exception
*/
private function _setDefaultTemplate()
{
@@ -260,6 +264,7 @@ class Controller
* pasteid (optional) = in discussions, which paste this comment belongs to.
*
* @access private
* @throws Exception
* @return string
*/
private function _create()
@@ -270,8 +275,8 @@ class Controller
TrafficLimiter::setStore($this->_model->getStore());
try {
TrafficLimiter::canPass();
} catch (Exception $e) {
$this->_return_message(1, $e->getMessage());
} catch (TranslatedException $e) {
$this->_json_error($e->getMessage());
return;
}
@@ -281,14 +286,13 @@ class Controller
array_key_exists('parentid', $data) &&
!empty($data['parentid']);
if (!FormatV2::isValid($data, $isComment)) {
$this->_return_message(1, I18n::_('Invalid data.'));
$this->_json_error(I18n::_('Invalid data.'));
return;
}
$sizelimit = $this->_conf->getKey('sizelimit');
// Ensure content is not too big.
if (strlen($data['ct']) > $sizelimit) {
$this->_return_message(
1,
$this->_json_error(
I18n::_(
'Document is limited to %s of encrypted data.',
Filter::formatHumanReadableSize($sizelimit)
@@ -305,34 +309,25 @@ class Controller
$comment = $paste->getComment($data['parentid']);
$comment->setData($data);
$comment->store();
$this->_json_result($comment->getId());
} catch (Exception $e) {
$this->_return_message(1, $e->getMessage());
return;
$this->_json_error($e->getMessage());
}
$this->_return_message(0, $comment->getId());
} else {
$this->_return_message(1, I18n::_('Invalid data.'));
$this->_json_error(I18n::_('Invalid data.'));
}
}
// The user posts a standard paste.
else {
try {
$this->_model->purge();
} catch (Exception $e) {
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();
$this->_json_result($paste->getId(), array('deletetoken' => $paste->getDeleteToken()));
} catch (Exception $e) {
$this->_return_message(1, $e->getMessage());
return;
$this->_json_error($e->getMessage());
}
$this->_return_message(0, $paste->getId(), array('deletetoken' => $paste->getDeleteToken()));
}
}
@@ -362,14 +357,14 @@ class Controller
} else {
$this->_error = self::GENERIC_ERROR;
}
} catch (Exception $e) {
} catch (TranslatedException $e) {
$this->_error = $e->getMessage();
}
if ($this->_request->isJsonApiCall()) {
if (empty($this->_error)) {
$this->_return_message(0, $dataid);
$this->_json_result($dataid);
} else {
$this->_return_message(1, $this->_error);
$this->_json_error(I18n::_($this->_error));
}
}
}
@@ -393,12 +388,12 @@ class Controller
if (array_key_exists('salt', $data['meta'])) {
unset($data['meta']['salt']);
}
$this->_return_message(0, $dataid, (array) $data);
$this->_json_result($dataid, (array) $data);
} else {
$this->_return_message(1, self::GENERIC_ERROR);
$this->_json_error(I18n::_(self::GENERIC_ERROR));
}
} catch (Exception $e) {
$this->_return_message(1, $e->getMessage());
} catch (TranslatedException $e) {
$this->_json_error($e->getMessage());
}
}
@@ -406,6 +401,7 @@ class Controller
* Display frontend.
*
* @access private
* @throws Exception
*/
private function _view()
{
@@ -425,7 +421,7 @@ class Controller
// label all the expiration options
$expire = array();
foreach ($this->_conf->getSection('expire_options') as $time => $seconds) {
$expire[$time] = ($seconds == 0) ? I18n::_(ucfirst($time)) : Filter::formatHumanReadableTime($time);
$expire[$time] = ($seconds === 0) ? I18n::_(ucfirst($time)) : Filter::formatHumanReadableTime($time);
}
// translate all the formatter options
@@ -466,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'));
@@ -537,6 +533,40 @@ class Controller
echo $content;
}
/**
* prepares JSON encoded error message
*
* @access private
* @param string $error
* @throws JsonException
*/
private function _json_error($error)
{
$result = array(
'status' => 1,
'message' => $error,
);
$this->_json = Json::encode($result);
}
/**
* prepares JSON encoded result message
*
* @access private
* @param string $dataid
* @param array $other
* @throws JsonException
*/
private function _json_result($dataid, $other = array())
{
$result = array(
'status' => 0,
'id' => $dataid,
'url' => $this->_urlBase . '?' . $dataid,
) + $other;
$this->_json = Json::encode($result);
}
/**
* Proxies a link using the specified proxy class, and updates the status or error with the response.
*
@@ -551,25 +581,4 @@ class Controller
$this->_status = $proxy->getUrl();
}
}
/**
* prepares JSON encoded status message
*
* @access private
* @param int $status
* @param string $message
* @param array $other
*/
private function _return_message($status, $message, $other = array())
{
$result = array('status' => $status);
if ($status) {
$result['message'] = I18n::_($message);
} else {
$result['id'] = $message;
$result['url'] = $this->_urlBase . '?' . $message;
}
$result += $other;
$this->_json = Json::encode($result);
}
}

View File

@@ -15,6 +15,7 @@ use Exception;
use PDO;
use PDOException;
use PrivateBin\Controller;
use PrivateBin\Exception\JsonException;
use PrivateBin\Json;
/**
@@ -83,8 +84,13 @@ class Database extends AbstractData
);
// MySQL uses backticks to quote identifiers by default,
// tell it to expect ANSI SQL double quotes
if ($this->_type === 'mysql' && defined('PDO::MYSQL_ATTR_INIT_COMMAND')) {
$options['opt'][PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION sql_mode='ANSI_QUOTES'";
if ($this->_type === 'mysql') {
// deprecated as of PHP 8.5
if (version_compare(PHP_VERSION, '8.5') < 0 && defined('PDO::MYSQL_ATTR_INIT_COMMAND')) {
$options['opt'][PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION sql_mode='ANSI_QUOTES'";
} elseif (defined('Pdo\Mysql::ATTR_INIT_COMMAND')) {
$options['opt'][Pdo\Mysql::ATTR_INIT_COMMAND] = "SET SESSION sql_mode='ANSI_QUOTES'";
}
}
$tableQuery = $this->_getTableQuery($this->_type);
$this->_db = new PDO(
@@ -179,18 +185,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 +245,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 +265,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 +286,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 +310,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 +347,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 +367,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;
}
}
@@ -386,14 +405,17 @@ class Database extends AbstractData
$fs = new Filesystem(array('dir' => 'data'));
$value = $fs->getValue('salt');
$this->setValue($value, 'salt');
unlink($file);
if (!unlink($file)) {
error_log('Error deleting migrated salt: ' . $file);
}
return $value;
}
}
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 +434,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 +579,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'] : '';

View File

@@ -11,8 +11,9 @@
namespace PrivateBin\Data;
use Exception;
use DirectoryIterator;
use GlobIterator;
use PrivateBin\Exception\JsonException;
use PrivateBin\Json;
/**
@@ -104,13 +105,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;
}
/**
@@ -124,21 +122,24 @@ class Filesystem extends AbstractData
$pastedir = $this->_dataid2path($pasteid);
if (is_dir($pastedir)) {
// Delete the paste itself.
if (is_file($pastedir . $pasteid . '.php')) {
unlink($pastedir . $pasteid . '.php');
$pastefile = $pastedir . $pasteid . '.php';
if (is_file($pastefile)) {
if (!unlink($pastefile)) {
error_log('Error deleting paste: ' . $pastefile);
}
}
// Delete discussion if it exists.
$discdir = $this->_dataid2discussionpath($pasteid);
if (is_dir($discdir)) {
// Delete all files in discussion directory
$dir = dir($discdir);
while (false !== ($filename = $dir->read())) {
if (is_file($discdir . $filename)) {
unlink($discdir . $filename);
foreach (new DirectoryIterator($discdir) as $file) {
if ($file->isFile()) {
if (!unlink($file->getPathname())) {
error_log('Error deleting comment: ' . $file->getPathname());
}
}
}
$dir->close();
rmdir($discdir);
}
}
@@ -162,14 +163,11 @@ class Filesystem extends AbstractData
// convert comments, too
$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';
$this->_prependRename($discdir . $filename, $commentFilename);
foreach (new DirectoryIterator($discdir) as $file) {
if ($file->getExtension() !== 'php' && strlen($file->getFilename()) >= 16) {
$this->_prependRename($file->getPathname(), $file->getPathname() . '.php');
}
}
$dir->close();
}
}
return is_readable($pastePath);
@@ -210,15 +208,14 @@ class Filesystem extends AbstractData
$comments = array();
$discdir = $this->_dataid2discussionpath($pasteid);
if (is_dir($discdir)) {
$dir = dir($discdir);
while (false !== ($filename = $dir->read())) {
foreach (new DirectoryIterator($discdir) as $file) {
// Filename is in the form pasteid.commentid.parentid.php:
// - pasteid is the paste this reply belongs to.
// - commentid is the comment identifier itself.
// - parentid is the comment this comment replies to (It can be pasteid)
if (is_file($discdir . $filename)) {
$comment = $this->_get($discdir . $filename);
$items = explode('.', $filename);
if ($file->isFile()) {
$comment = $this->_get($file->getPathname());
$items = explode('.', $file->getBasename('.php'));
// Add some meta information not contained in file.
$comment['id'] = $items[1];
$comment['parentid'] = $items[2];
@@ -231,7 +228,6 @@ class Filesystem extends AbstractData
$comments[$key] = $comment;
}
}
$dir->close();
// Sort comments by date, oldest first.
ksort($comments);
@@ -312,7 +308,7 @@ class Filesystem extends AbstractData
$file = $this->_path . DIRECTORY_SEPARATOR . 'salt.php';
if (is_readable($file)) {
$items = explode('|', file_get_contents($file));
if (count($items) == 3) {
if (count($items) === 3) {
return $items[1];
}
}
@@ -346,7 +342,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;
}
}
/**
@@ -368,10 +369,7 @@ class Filesystem extends AbstractData
foreach ($files as $pasteid) {
if ($this->exists($pasteid)) {
$data = $this->read($pasteid);
if (
array_key_exists('expire_date', $data['meta']) &&
$data['meta']['expire_date'] < $time
) {
if (($data['meta']['expire_date'] ?? $time) < $time) {
$pastes[] = $pasteid;
if (++$count >= $batchsize) {
break;
@@ -450,7 +448,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;
}
@@ -523,6 +521,8 @@ class Filesystem extends AbstractData
file_put_contents($destFile, $handle, FILE_APPEND);
fclose($handle);
}
unlink($srcFile);
if (!unlink($srcFile)) {
error_log('Error deleting converted document: ' . $srcFile);
}
}
}

View File

@@ -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
@@ -89,7 +90,7 @@ class GoogleCloudStorage extends AbstractData
*/
private function _getKey($pasteid)
{
if ($this->_prefix != '') {
if (!empty($this->_prefix)) {
return $this->_prefix . '/' . $pasteid;
}
return $pasteid;
@@ -106,7 +107,7 @@ class GoogleCloudStorage extends AbstractData
*/
private function _upload($key, &$payload)
{
$metadata = array_key_exists('meta', $payload) ? $payload['meta'] : array();
$metadata = $payload['meta'] ?? array();
unset($metadata['salt']);
foreach ($metadata as $k => $v) {
$metadata[$k] = strval($v);
@@ -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;
@@ -252,15 +258,12 @@ class GoogleCloudStorage extends AbstractData
if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') {
continue;
}
$info = $object->info();
if (key_exists('metadata', $info) && key_exists('value', $info['metadata'])) {
$value = $info['metadata']['value'];
if (is_numeric($value) && intval($value) < $time) {
try {
$object->delete();
} catch (NotFoundException $e) {
// deleted by another instance.
}
$value = $object->info()['metadata']['value'] ?? '';
if (is_numeric($value) && intval($value) < $time) {
try {
$object->delete();
} catch (NotFoundException $e) {
// deleted by another instance.
}
}
}
@@ -276,14 +279,14 @@ class GoogleCloudStorage extends AbstractData
*/
public function setValue($value, $namespace, $key = '')
{
if ($key === '') {
if (empty($key)) {
$key = 'config/' . $namespace;
} else {
$key = 'config/' . $namespace . '/' . $key;
}
$metadata = array('namespace' => $namespace);
if ($namespace != 'salt') {
if ($namespace !== 'salt') {
$metadata['value'] = strval($value);
}
try {
@@ -334,17 +337,14 @@ class GoogleCloudStorage extends AbstractData
$now = time();
$prefix = $this->_prefix;
if ($prefix != '') {
if (!empty($prefix)) {
$prefix .= '/';
}
try {
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']);
if ($expire_at != 0 && $expire_at < $now) {
array_push($expired, basename($object->name()));
}
$expire_at = $object->info()['metadata']['expire_date'] ?? '';
if (is_numeric($expire_at) && intval($expire_at) < $now) {
array_push($expired, basename($object->name()));
}
if (count($expired) > $batchsize) {
@@ -364,7 +364,7 @@ class GoogleCloudStorage extends AbstractData
{
$pastes = array();
$prefix = $this->_prefix;
if ($prefix != '') {
if (!empty($prefix)) {
$prefix .= '/';
}

View File

@@ -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
@@ -147,7 +148,7 @@ class S3Storage extends AbstractData
*/
private function _getKey($pasteid)
{
if ($this->_prefix != '') {
if (!empty($this->_prefix)) {
return $this->_prefix . '/' . $pasteid;
}
return $pasteid;
@@ -164,7 +165,7 @@ class S3Storage extends AbstractData
*/
private function _upload($key, &$payload)
{
$metadata = array_key_exists('meta', $payload) ? $payload['meta'] : array();
$metadata = $payload['meta'] ?? array();
unset($metadata['salt']);
foreach ($metadata as $k => $v) {
$metadata[$k] = strval($v);
@@ -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 ' . $pasteid . ', ' . $e->getMessage());
}
return false;
}
/**
@@ -312,7 +317,7 @@ class S3Storage extends AbstractData
public function purgeValues($namespace, $time)
{
$path = $this->_prefix;
if ($path != '') {
if (!empty($path)) {
$path .= '/';
}
$path .= 'config/' . $namespace;
@@ -327,17 +332,15 @@ class S3Storage extends AbstractData
'Bucket' => $this->_bucket,
'Key' => $name,
));
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 {
$this->_client->deleteObject(array(
'Bucket' => $this->_bucket,
'Key' => $name,
));
} catch (S3Exception $e) {
// deleted by another instance.
}
$value = $head->get('Metadata')['value'] ?? '';
if (is_numeric($value) && intval($value) < $time) {
try {
$this->_client->deleteObject(array(
'Bucket' => $this->_bucket,
'Key' => $name,
));
} catch (S3Exception $e) {
// deleted by another instance.
}
}
}
@@ -354,7 +357,7 @@ class S3Storage extends AbstractData
public function setValue($value, $namespace, $key = '')
{
$prefix = $this->_prefix;
if ($prefix != '') {
if (!empty($prefix)) {
$prefix .= '/';
}
@@ -365,7 +368,7 @@ class S3Storage extends AbstractData
}
$metadata = array('namespace' => $namespace);
if ($namespace != 'salt') {
if ($namespace !== 'salt') {
$metadata['value'] = strval($value);
}
try {
@@ -390,7 +393,7 @@ class S3Storage extends AbstractData
public function getValue($namespace, $key = '')
{
$prefix = $this->_prefix;
if ($prefix != '') {
if (!empty($prefix)) {
$prefix .= '/';
}
@@ -419,7 +422,7 @@ class S3Storage extends AbstractData
$expired = array();
$now = time();
$prefix = $this->_prefix;
if ($prefix != '') {
if (!empty($prefix)) {
$prefix .= '/';
}
@@ -429,11 +432,9 @@ class S3Storage extends AbstractData
'Bucket' => $this->_bucket,
'Key' => $object['Key'],
));
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']);
}
$expire_at = $head->get('Metadata')['expire_date'] ?? '';
if (is_numeric($expire_at) && intval($expire_at) < $now) {
array_push($expired, $object['Key']);
}
if (count($expired) > $batchsize) {
@@ -453,7 +454,7 @@ class S3Storage extends AbstractData
{
$pastes = array();
$prefix = $this->_prefix;
if ($prefix != '') {
if (!empty($prefix)) {
$prefix .= '/';
}

View File

@@ -0,0 +1,38 @@
<?php declare(strict_types=1);
/**
* PrivateBin
*
* a zero-knowledge paste bin
*
* @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
*/
namespace PrivateBin\Exception;
use Exception;
/**
* JsonException
*
* An Exception representing JSON en- or decoding errors.
*/
class JsonException extends Exception
{
/**
* Exception constructor with mandatory JSON error code.
*
* @access public
* @param int $code
*/
public function __construct(int $code)
{
$message = 'A JSON error occurred';
if (function_exists('json_last_error_msg')) {
$message .= ': ' . json_last_error_msg();
}
$message .= ' (' . $code . ')';
parent::__construct($message, 90);
}
}

View File

@@ -0,0 +1,36 @@
<?php declare(strict_types=1);
/**
* PrivateBin
*
* a zero-knowledge paste bin
*
* @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
*/
namespace PrivateBin\Exception;
use Exception;
use PrivateBin\I18n;
/**
* TranslatedException
*
* An Exception that translates it's message.
*/
class TranslatedException extends Exception
{
/**
* Translating exception constructor with mandatory messageId.
*
* @access public
* @param string|array $messageId message ID or array of message ID and parameters
* @param int $code
*/
public function __construct($messageId, int $code = 0)
{
$message = is_string($messageId) ? I18n::translate($messageId) : forward_static_call_array('PrivateBin\I18n::translate', $messageId);
parent::__construct($message, $code);
}
}

View File

@@ -40,7 +40,7 @@ class FormatV2
}
// Make sure no additionnal keys were added.
if (count(array_keys($message)) != count($required_keys)) {
if (count(array_keys($message)) !== count($required_keys)) {
return false;
}

View File

@@ -127,14 +127,13 @@ class I18n
} else {
$args[0] = self::$_translations[$messageId];
}
// encode any non-integer arguments and the message ID, if it doesn't contain a link or keyboard input
// encode any non-integer arguments, but not the message itself
// The message ID comes from trusted sources (code or translation JSON files),
// while parameters may come from untrusted sources and need HTML entity encoding
// to prevent XSS attacks when the message is inserted into HTML context
$argsCount = count($args);
for ($i = 0; $i < $argsCount; ++$i) {
if ($i === 0) {
if (str_contains($args[0], '<a') || str_contains($args[0], '<kbd>')) {
continue;
}
} elseif (is_int($args[$i])) {
for ($i = 1; $i < $argsCount; ++$i) {
if (is_int($args[$i])) {
continue;
}
$args[$i] = self::encode($args[$i]);
@@ -162,6 +161,7 @@ class I18n
*
* @access public
* @static
* @throws JsonException
*/
public static function loadTranslations()
{
@@ -172,21 +172,20 @@ class I18n
array_key_exists('lang', $_COOKIE) &&
($key = array_search($_COOKIE['lang'], $availableLanguages)) !== false
) {
$match = $availableLanguages[$key];
self::$_language = $availableLanguages[$key];
}
// find a translation file matching the browsers language preferences
else {
$match = self::_getMatchingLanguage(
self::$_language = self::_getMatchingLanguage(
self::getBrowserLanguages(), $availableLanguages
);
}
// load translations
self::$_language = $match;
if ($match == 'en') {
if (self::$_language === 'en') {
self::$_translations = array();
} else {
$data = file_get_contents(self::_getPath($match . '.json'));
$data = file_get_contents(self::_getPath(self::$_language . '.json'));
self::$_translations = Json::decode($data);
}
}
@@ -200,14 +199,14 @@ class I18n
*/
public static function getAvailableLanguages()
{
if (count(self::$_availableLanguages) == 0) {
if (count(self::$_availableLanguages) === 0) {
self::$_availableLanguages[] = 'en'; // en.json is not part of the release archive
$languageIterator = new AppendIterator();
$languageIterator->append(new GlobIterator(self::_getPath('??.json')));
$languageIterator->append(new GlobIterator(self::_getPath('???.json'))); // for jbo
foreach ($languageIterator as $file) {
$language = $file->getBasename('.json');
if ($language != 'en') {
if ($language !== 'en') {
self::$_availableLanguages[] = $language;
}
}
@@ -270,16 +269,17 @@ class I18n
* @access public
* @static
* @param array $languages
* @throws JsonException
* @return array
*/
public static function getLanguageLabels($languages = array())
{
$file = self::_getPath('languages.json');
if (count(self::$_languageLabels) == 0 && is_readable($file)) {
if (count(self::$_languageLabels) === 0 && is_readable($file)) {
$data = file_get_contents($file);
self::$_languageLabels = Json::decode($data);
}
if (count($languages) == 0) {
if (count($languages) === 0) {
return self::$_languageLabels;
}
return array_intersect_key(self::$_languageLabels, array_flip($languages));
@@ -346,6 +346,7 @@ class I18n
case 'sk':
return $n === 1 ? 0 : ($n >= 2 && $n <= 4 ? 1 : 2);
case 'co':
case 'fa':
case 'fr':
case 'oc':
case 'tr':
@@ -366,11 +367,11 @@ class I18n
return $n === 1 ? 0 : (($n === 0 || ($n % 100 > 0 && $n % 100 < 20)) ? 1 : 2);
case 'ru':
case 'uk':
return $n % 10 === 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
return $n % 10 === 1 && $n % 100 !== 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
case 'sl':
return $n % 100 === 1 ? 1 : ($n % 100 === 2 ? 2 : ($n % 100 === 3 || $n % 100 === 4 ? 3 : 0));
default:
// bg, ca, de, el, en, es, et, fi, hu, it, nl, no, pt
// bg, ca, de, el, en, es, et, fi, hu, it, nl, no, pt, sv
return $n !== 1 ? 1 : 0;
}
}

View File

@@ -11,7 +11,7 @@
namespace PrivateBin;
use Exception;
use PrivateBin\Exception\JsonException;
/**
* Json
@@ -26,7 +26,7 @@ class Json
* @access public
* @static
* @param mixed $input
* @throws Exception
* @throws JsonException
* @return string
*/
public static function encode(&$input)
@@ -42,7 +42,7 @@ class Json
* @access public
* @static
* @param string $input
* @throws Exception
* @throws JsonException
* @return mixed
*/
public static function decode(&$input)
@@ -57,21 +57,14 @@ class Json
*
* @access private
* @static
* @throws Exception
* @throws JsonException
* @return void
*/
private static function _detectError()
{
$errorCode = json_last_error();
if ($errorCode === JSON_ERROR_NONE) {
return;
if ($errorCode !== JSON_ERROR_NONE) {
throw new JsonException($errorCode);
}
$message = 'A JSON error occurred';
if (function_exists('json_last_error_msg')) {
$message .= ': ' . json_last_error_msg();
}
$message .= ' (' . $errorCode . ')';
throw new Exception($message, 90);
}
}

View File

@@ -11,9 +11,9 @@
namespace PrivateBin\Model;
use Exception;
use PrivateBin\Configuration;
use PrivateBin\Data\AbstractData;
use PrivateBin\Exception\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)
{

View File

@@ -11,9 +11,9 @@
namespace PrivateBin\Model;
use Exception;
use Identicon\Identicon;
use Jdenticon\Identicon as Jdenticon;
use PrivateBin\Exception\TranslatedException;
use PrivateBin\Persistence\TrafficLimiter;
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;
}
@@ -149,13 +148,13 @@ class Comment extends AbstractModel
{
// we generate an icon based on a SHA512 HMAC of the users IP, if configured
$icon = $this->_conf->getKey('icon');
if ($icon != 'none') {
if ($icon !== 'none') {
$pngdata = '';
$hmac = TrafficLimiter::getHash();
if ($icon == 'identicon') {
if ($icon === 'identicon') {
$identicon = new Identicon();
$pngdata = $identicon->getImageDataUri($hmac, 16);
} elseif ($icon == 'jdenticon') {
} elseif ($icon === 'jdenticon') {
$jdenticon = new Jdenticon(array(
'hash' => $hmac,
'size' => 16,
@@ -165,13 +164,13 @@ class Comment extends AbstractModel
),
));
$pngdata = $jdenticon->getImageDataUri('png');
} elseif ($icon == 'vizhash') {
} elseif ($icon === 'vizhash') {
$vh = new Vizhash16x16();
$pngdata = 'data:image/png;base64,' . base64_encode(
$vh->generate($hmac)
);
}
if ($pngdata != '') {
if (!empty($pngdata)) {
if (!array_key_exists('meta', $data)) {
$data['meta'] = array();
}

View File

@@ -11,8 +11,8 @@
namespace PrivateBin\Model;
use Exception;
use PrivateBin\Controller;
use PrivateBin\Exception\TranslatedException;
use PrivateBin\Persistence\ServerSalt;
/**
@@ -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;
@@ -73,10 +73,7 @@ class Paste extends AbstractModel
}
// check if non-expired burn after reading paste needs to be deleted
if (
array_key_exists('adata', $data) &&
$data['adata'][self::ADATA_BURN_AFTER_READING] === 1
) {
if (($data['adata'][self::ADATA_BURN_AFTER_READING] ?? 0) === 1) {
$this->delete();
}
@@ -93,13 +90,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 +108,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 +116,6 @@ class Paste extends AbstractModel
* Delete the paste.
*
* @access public
* @throws Exception
*/
public function delete()
{
@@ -143,18 +139,18 @@ 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);
$comment->setParentId($parentId);
if ($commentId !== '') {
if (!empty($commentId)) {
$comment->setId($commentId);
}
return $comment;
@@ -201,7 +197,6 @@ class Paste extends AbstractModel
* Check if paste has discussions enabled.
*
* @access public
* @throws Exception
* @return bool
*/
public function isOpendiscussion()
@@ -209,8 +204,7 @@ class Paste extends AbstractModel
if (!array_key_exists('adata', $this->_data) && !array_key_exists('data', $this->_data)) {
$this->get();
}
return array_key_exists('adata', $this->_data) &&
$this->_data['adata'][self::ADATA_OPEN_DISCUSSION] === 1;
return ($this->_data['adata'][self::ADATA_OPEN_DISCUSSION] ?? 0) === 1;
}
/**
@@ -224,12 +218,9 @@ class Paste extends AbstractModel
$expiration = $data['meta']['expire'] ?? 0;
unset($data['meta']['expire']);
$expire_options = $this->_conf->getSection('expire_options');
if (array_key_exists($expiration, $expire_options)) {
$expire = $expire_options[$expiration];
} else {
// using getKey() to ensure a default value is present
$expire = $this->_conf->getKey($this->_conf->getKey('default', 'expire'), 'expire_options');
}
// using getKey() to ensure a default value is present
$expire = $expire_options[$expiration] ??
$this->_conf->getKey($this->_conf->getKey('default', 'expire'), 'expire_options');
if ($expire > 0) {
$data['meta']['expire_date'] = time() + $expire;
}
@@ -240,13 +231,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 +248,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 +256,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);
}
}
}

View File

@@ -12,11 +12,10 @@
namespace PrivateBin\Persistence;
use Exception;
use IPLib\Factory;
use IPLib\ParseStringFlag;
use PrivateBin\Configuration;
use PrivateBin\I18n;
use PrivateBin\Exception\TranslatedException;
/**
* TrafficLimiter
@@ -74,7 +73,7 @@ class TrafficLimiter extends AbstractPersistence
self::setExempted($conf->getKey('exempted', 'traffic'));
self::setLimit($conf->getKey('limit', 'traffic'));
if (($option = $conf->getKey('header', 'traffic')) !== '') {
if (!empty($option = $conf->getKey('header', 'traffic'))) {
$httpHeader = 'HTTP_' . $option;
if (array_key_exists($httpHeader, $_SERVER) && !empty($_SERVER[$httpHeader])) {
self::$_ipKey = $httpHeader;
@@ -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,9 +209,9 @@ class TrafficLimiter extends AbstractPersistence
}
return true;
}
throw new Exception(I18n::_(
throw new TranslatedException(array(
'Please wait %d seconds between each post.',
self::$_limit
self::$_limit,
));
}
}

View File

@@ -11,8 +11,8 @@
namespace PrivateBin\Proxy;
use Exception;
use PrivateBin\Configuration;
use PrivateBin\Exception\JsonException;
use PrivateBin\Json;
/**
@@ -55,7 +55,7 @@ abstract class AbstractProxy
}
if (!str_starts_with($link, $conf->getKey('basepath') . '?') ||
parse_url($link, PHP_URL_HOST) != parse_url($conf->getKey('basepath'), PHP_URL_HOST)
parse_url($link, PHP_URL_HOST) !== parse_url($conf->getKey('basepath'), PHP_URL_HOST)
) {
$this->_error = 'Trying to shorten a URL that isn\'t pointing at our instance.';
return;
@@ -90,7 +90,7 @@ abstract class AbstractProxy
try {
$jsonData = Json::decode($data);
} catch (Exception $e) {
} catch (JsonException $e) {
$this->_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;

View File

@@ -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();
}
}
/**
@@ -65,11 +71,6 @@ class ShlinkProxy extends AbstractProxy
*/
protected function _extractShortUrl(array $data): ?string
{
if (
array_key_exists('shortUrl', $data)
) {
return $data['shortUrl'];
}
return null;
return $data['shortUrl'] ?? null;
}
}

View File

@@ -65,12 +65,8 @@ class YourlsProxy extends AbstractProxy
*/
protected function _extractShortUrl(array $data): ?string
{
if (
array_key_exists('statusCode', $data) &&
$data['statusCode'] == 200 &&
array_key_exists('shorturl', $data)
) {
return $data['shorturl'];
if (($data['statusCode'] ?? 0) === 200) {
return $data['shorturl'] ?? 0;
}
return null;
}

View File

@@ -11,7 +11,7 @@
namespace PrivateBin;
use Exception;
use PrivateBin\Exception\JsonException;
use PrivateBin\Model\Paste;
/**
@@ -104,7 +104,7 @@ class Request
$this->_isJsonApi = $this->_detectJsonRequest();
// parse parameters, depending on request type
switch (array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : 'GET') {
switch ($_SERVER['REQUEST_METHOD'] ?? 'GET') {
case 'DELETE':
case 'PUT':
case 'POST':
@@ -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;
@@ -141,7 +141,7 @@ class Request
if (array_key_exists('pasteid', $this->_params) && !empty($this->_params['pasteid'])) {
if (array_key_exists('deletetoken', $this->_params) && !empty($this->_params['deletetoken'])) {
$this->_operation = 'delete';
} elseif ($this->_operation != 'create') {
} elseif ($this->_operation !== 'create') {
$this->_operation = 'read';
}
} elseif (array_key_exists('jsonld', $this->_params) && !empty($this->_params['jsonld'])) {
@@ -187,7 +187,7 @@ class Request
$data['meta'] = $meta;
}
foreach ($required_keys as $key) {
$data[$key] = $this->getParam($key, $key == 'v' ? 1 : '');
$data[$key] = $this->getParam($key, $key === 'v' ? 1 : '');
}
// forcing a cast to int or float
$data['v'] = $data['v'] + 0;
@@ -204,8 +204,7 @@ class Request
*/
public function getParam($param, $default = '')
{
return array_key_exists($param, $this->_params) ?
$this->_params[$param] : $default;
return $this->_params[$param] ?? $default;
}
/**
@@ -263,23 +262,22 @@ class Request
*/
private function _detectJsonRequest()
{
$hasAcceptHeader = array_key_exists('HTTP_ACCEPT', $_SERVER);
$acceptHeader = $hasAcceptHeader ? $_SERVER['HTTP_ACCEPT'] : '';
$acceptHeader = $_SERVER['HTTP_ACCEPT'] ?? '';
// simple cases
if (
(array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) &&
$_SERVER['HTTP_X_REQUESTED_WITH'] == 'JSONHttpRequest') ||
($hasAcceptHeader &&
($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') === 'JSONHttpRequest' ||
(
str_contains($acceptHeader, self::MIME_JSON) &&
!str_contains($acceptHeader, self::MIME_HTML) &&
!str_contains($acceptHeader, self::MIME_XHTML))
!str_contains($acceptHeader, self::MIME_XHTML)
)
) {
return true;
}
// advanced case: media type negotiation
if ($hasAcceptHeader) {
if (!empty($acceptHeader)) {
$mediaTypes = array();
foreach (explode(',', trim($acceptHeader)) as $mediaTypeRange) {
if (preg_match(

View File

@@ -103,7 +103,7 @@ class Vizhash16x16
// First, create an image with a specific gradient background.
$op = 'v';
if (($this->getInt() % 2) == 0) {
if (($this->getInt() % 2) === 0) {
$op = 'h';
}
$image = $this->degrade($image, $op, array($r0, $g0, $b0), array(0, 0, 0));
@@ -179,7 +179,7 @@ class Vizhash16x16
*/
private function degrade($img, $direction, $color1, $color2)
{
if ($direction == 'h') {
if ($direction === 'h') {
$size = imagesx($img);
$sizeinv = imagesy($img);
} else {
@@ -195,7 +195,7 @@ class Vizhash16x16
$r = $color1[0] + ((int) $diffs[0] * $i);
$g = $color1[1] + ((int) $diffs[1] * $i);
$b = $color1[2] + ((int) $diffs[2] * $i);
if ($direction == 'h') {
if ($direction === 'h') {
imageline($img, $i, 0, $i, $sizeinv, imagecolorallocate($img, $r, $g, $b));
} else {
imageline($img, 0, $i, $sizeinv, $i, imagecolorallocate($img, $r, $g, $b));