remove v1 backend support and version checks in the frontend

This commit is contained in:
El RIDO
2025-07-05 17:21:12 +02:00
parent 6d5323e351
commit f7cf389f36
17 changed files with 186 additions and 526 deletions

View File

@@ -116,7 +116,7 @@ class Configuration
'js/kjua-0.9.0.js' => 'sha512-CVn7af+vTMBd9RjoS4QM5fpLFEOtBCoB0zPtaqIDC7sF4F8qgUSRFQQpIyEDGsr6yrjbuOLzdf20tkHHmpaqwQ==',
'js/legacy.js' => 'sha512-UxW/TOZKon83n6dk/09GsYKIyeO5LeBHokxyIq+r7KFS5KMBeIB/EM7NrkVYIezwZBaovnyNtY2d9tKFicRlXg==',
'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==',
'js/privatebin.js' => 'sha512-wQci0zYb+eGsbQaLWunZ7sMoT7+3w5SUfvBJ3JactOOO2WytQGt/q6wRdWZsCkZjCE6hxkEBOxlfSqLHluh3KQ==',
'js/privatebin.js' => 'sha512-9PIA4e9hN7XUWzN4w7K0BKtEK3/N7ZztNchTrV5M0joeMCPdbU1dJWxRr0XmsFmb+V/Zsr8n+YopxiCc5HMPIQ==',
'js/purify-3.2.6.js' => 'sha512-zqwL4OoBLFx89QPewkz4Lz5CSA2ktU+f31fuECkF0iK3Id5qd3Zpq5dMby8KwHjIEpsUgOqwF58cnmcaNem0EA==',
'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==',
'js/zlib-1.3.1-1.js' => 'sha512-5bU9IIP4PgBrOKLZvGWJD4kgfQrkTz8Z3Iqeu058mbQzW3mCumOU6M3UVbVZU9rrVoVwaW4cZK8U8h5xjF88eQ==',

View File

@@ -239,19 +239,15 @@ class Controller
/**
* Store new paste or comment
*
* POST contains one or both:
* data = json encoded FormatV2 encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
* attachment = json encoded FormatV2 encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
*
* All optional data will go to meta information:
* expire (optional) = expiration delay (never,5min,10min,1hour,1day,1week,1month,1year,burn) (default:never)
* formatter (optional) = format to display the paste as (plaintext,syntaxhighlighting,markdown) (default:syntaxhighlighting)
* burnafterreading (optional) = if this paste may only viewed once ? (0/1) (default:0)
* opendiscusssion (optional) = is the discussion allowed on this paste ? (0/1) (default:0)
* attachmentname = json encoded FormatV2 encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
* nickname (optional) = in discussion, encoded FormatV2 encrypted text nickname of author of comment (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
* parentid (optional) = in discussion, which comment this comment replies to.
* pasteid (optional) = in discussion, which paste this comment belongs to.
* POST contains:
* JSON encoded object with mandatory keys:
* v = 2 (version)
* adata (array)
* ct (base64 encoded, encrypted text)
* meta (optional):
* expire = expiration delay (never,5min,10min,1hour,1day,1week,1month,1year,burn) (default:1week)
* parentid (optional) = in discussions, which comment this comment replies to.
* pasteid (optional) = in discussions, which paste this comment belongs to.
*
* @access private
* @return string

View File

@@ -190,25 +190,4 @@ abstract class AbstractData
}
return $postdate;
}
/**
* Upgrade pre-version 1 pastes with attachment to version 1 format.
*
* @access protected
* @static
* @param array $paste
* @return array
*/
protected static function upgradePreV1Format(array &$paste)
{
if (array_key_exists('attachment', $paste['meta'])) {
$paste['attachment'] = $paste['meta']['attachment'];
unset($paste['meta']['attachment']);
if (array_key_exists('attachmentname', $paste['meta'])) {
$paste['attachmentname'] = $paste['meta']['attachmentname'];
unset($paste['meta']['attachmentname']);
}
}
return $paste;
}
}

View File

@@ -143,48 +143,20 @@ class Database extends AbstractData
public function create($pasteid, array &$paste)
{
$expire_date = 0;
$opendiscussion = $burnafterreading = false;
$attachment = $attachmentname = null;
$meta = $paste['meta'];
$isVersion1 = array_key_exists('data', $paste);
if (array_key_exists('expire_date', $meta)) {
$expire_date = (int) $meta['expire_date'];
unset($meta['expire_date']);
}
if (array_key_exists('opendiscussion', $meta)) {
$opendiscussion = $meta['opendiscussion'];
unset($meta['opendiscussion']);
}
if (array_key_exists('burnafterreading', $meta)) {
$burnafterreading = $meta['burnafterreading'];
unset($meta['burnafterreading']);
}
if ($isVersion1) {
if (array_key_exists('attachment', $meta)) {
$attachment = $meta['attachment'];
unset($meta['attachment']);
}
if (array_key_exists('attachmentname', $meta)) {
$attachmentname = $meta['attachmentname'];
unset($meta['attachmentname']);
}
} else {
$opendiscussion = $paste['adata'][2];
$burnafterreading = $paste['adata'][3];
}
try {
return $this->_exec(
'INSERT INTO "' . $this->_sanitizeIdentifier('paste') .
'" VALUES(?,?,?,?,?,?,?,?)',
'" VALUES(?,?,?,?)',
array(
$pasteid,
$isVersion1 ? $paste['data'] : Json::encode($paste),
Json::encode($paste),
$expire_date,
(int) $opendiscussion,
(int) $burnafterreading,
Json::encode($meta),
$attachment,
$attachmentname,
)
);
} catch (Exception $e) {
@@ -213,38 +185,17 @@ class Database extends AbstractData
return false;
}
// create array
$data = Json::decode($row['data']);
$isVersion2 = array_key_exists('v', $data) && $data['v'] >= 2;
$paste = $isVersion2 ? $data : array('data' => $row['data']);
$paste = Json::decode($row['data']);
try {
$row['meta'] = Json::decode($row['meta']);
$paste['meta'] = Json::decode($row['meta']);
} catch (Exception $e) {
$row['meta'] = array();
$paste['meta'] = array();
}
$row = self::upgradePreV1Format($row);
$paste['meta'] = $row['meta'];
$expire_date = (int) $row['expiredate'];
$expire_date = (int) $row['expiredate'];
if ($expire_date > 0) {
$paste['meta']['expire_date'] = $expire_date;
}
if ($isVersion2) {
return $paste;
}
// support v1 attachments
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 ($row['opendiscussion']) {
$paste['meta']['opendiscussion'] = true;
}
if ($row['burnafterreading']) {
$paste['meta']['burnafterreading'] = true;
}
return $paste;
}
@@ -299,21 +250,14 @@ class Database extends AbstractData
*/
public function createComment($pasteid, $parentid, $commentid, array &$comment)
{
if (array_key_exists('data', $comment)) {
$version = 1;
$data = $comment['data'];
} else {
try {
$version = 2;
$data = Json::encode($comment);
} catch (Exception $e) {
return false;
}
try {
$data = Json::encode($comment);
} catch (Exception $e) {
return false;
}
list($createdKey, $iconKey) = $this->_getVersionedKeys($version);
$meta = $comment['meta'];
$meta = $comment['meta'];
unset($comment['meta']);
foreach (array('nickname', $iconKey) as $key) {
foreach (array('nickname', 'icon') as $key) {
if (!array_key_exists($key, $meta)) {
$meta[$key] = null;
}
@@ -328,8 +272,8 @@ class Database extends AbstractData
$parentid,
$data,
$meta['nickname'],
$meta[$iconKey],
$meta[$createdKey],
$meta['icon'],
$meta['created'],
)
);
} catch (Exception $e) {
@@ -355,20 +299,12 @@ class Database extends AbstractData
$comments = array();
if (is_array($rows) && count($rows)) {
foreach ($rows as $row) {
$i = $this->getOpenSlot($comments, (int) $row['postdate']);
$data = Json::decode($row['data']);
if (array_key_exists('v', $data) && $data['v'] >= 2) {
$version = 2;
$comments[$i] = $data;
} else {
$version = 1;
$comments[$i] = array('data' => $row['data']);
}
list($createdKey, $iconKey) = $this->_getVersionedKeys($version);
$i = $this->getOpenSlot($comments, (int) $row['postdate']);
$comments[$i] = Json::decode($row['data']);
$comments[$i]['id'] = $row['dataid'];
$comments[$i]['parentid'] = $row['parentid'];
$comments[$i]['meta'] = array($createdKey => (int) $row['postdate']);
foreach (array('nickname' => 'nickname', 'vizhash' => $iconKey) as $rowKey => $commentKey) {
$comments[$i]['meta'] = array('created' => (int) $row['postdate']);
foreach (array('nickname' => 'nickname', 'vizhash' => 'icon') as $rowKey => $commentKey) {
if (array_key_exists($rowKey, $row) && !empty($row[$rowKey])) {
$comments[$i]['meta'][$commentKey] = $row[$rowKey];
}
@@ -561,21 +497,6 @@ class Database extends AbstractData
return $result;
}
/**
* get version dependent key names
*
* @access private
* @param int $version
* @return array
*/
private function _getVersionedKeys($version)
{
if ($version === 1) {
return array('postdate', 'vizhash');
}
return array('created', 'icon');
}
/**
* get table list query, depending on the database type
*
@@ -737,16 +658,12 @@ class Database extends AbstractData
"\"dataid\" CHAR(16) NOT NULL$main_key, " .
"\"data\" $attachmentType, " .
'"expiredate" INT, ' .
'"opendiscussion" INT, ' .
'"burnafterreading" INT, ' .
"\"meta\" $metaType, " .
"\"attachment\" $attachmentType, " .
"\"attachmentname\" $dataType$after_key )"
"\"meta\" $metaType$after_key )"
);
}
/**
* create the paste table
* create the comment table
*
* @access private
*/
@@ -789,7 +706,7 @@ class Database extends AbstractData
}
/**
* create the paste table
* create the config table
*
* @access private
*/
@@ -839,6 +756,26 @@ class Database extends AbstractData
return preg_replace('/[^A-Za-z0-9_]+/', '', $this->_prefix . $identifier);
}
/**
* check if the current database type supports dropping columns
*
* @access private
* @return bool
*/
private function _supportsDropColumn()
{
$supportsDropColumn = true;
if ($this->_type === 'sqlite') {
try {
$row = $this->_select('SELECT sqlite_version() AS "v"', array(), true);
$supportsDropColumn = version_compare($row['v'], '3.35.0', '>=');
} catch (PDOException $e) {
$supportsDropColumn = false;
}
}
return $supportsDropColumn;
}
/**
* upgrade the database schema from an old version
*
@@ -910,22 +847,33 @@ class Database extends AbstractData
}
}
if (version_compare($oldversion, '1.7.1', '<=')) {
$supportsDropColumn = true;
if ($this->_type === 'sqlite') {
try {
$row = $this->_select('SELECT sqlite_version() AS "v"', array(), true);
$supportsDropColumn = version_compare($row['v'], '3.35.0', '>=');
} catch (PDOException $e) {
$supportsDropColumn = false;
}
}
if ($supportsDropColumn) {
if ($this->_supportsDropColumn()) {
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
'" DROP COLUMN "postdate"'
);
}
}
if (version_compare($oldversion, '1.7.8', '<=')) {
if ($this->_supportsDropColumn()) {
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
'" DROP COLUMN "opendiscussion"'
);
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
'" DROP COLUMN "burnafterreading"'
);
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
'" DROP COLUMN "attachment"'
);
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
'" DROP COLUMN "attachmentname"'
);
}
}
$this->_exec(
'UPDATE "' . $this->_sanitizeIdentifier('config') .
'" SET "value" = ? WHERE "id" = ?',

View File

@@ -113,7 +113,7 @@ class Filesystem extends AbstractData
) {
return false;
}
return self::upgradePreV1Format($paste);
return $paste;
}
/**

View File

@@ -98,8 +98,7 @@ class GoogleCloudStorage extends AbstractData
/**
* 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.
* as the GCS object's metadata except for the field salt.
*
* @param $key string to store the payload under
* @param $payload array to store
@@ -108,7 +107,7 @@ class GoogleCloudStorage extends AbstractData
private function _upload($key, &$payload)
{
$metadata = array_key_exists('meta', $payload) ? $payload['meta'] : array();
unset($metadata['attachment'], $metadata['attachmentname'], $metadata['salt']);
unset($metadata['salt']);
foreach ($metadata as $k => $v) {
$metadata[$k] = strval($v);
}

View File

@@ -158,8 +158,7 @@ class S3Storage extends AbstractData
/**
* 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.
* as the S3 object's metadata except for the field salt.
*
* @param $key string to store the payload under
* @param $payload array to store
@@ -168,7 +167,7 @@ class S3Storage extends AbstractData
private function _upload($key, &$payload)
{
$metadata = array_key_exists('meta', $payload) ? $payload['meta'] : array();
unset($metadata['attachment'], $metadata['attachmentname'], $metadata['salt']);
unset($metadata['salt']);
foreach ($metadata as $k => $v) {
$metadata[$k] = strval($v);
}

View File

@@ -22,6 +22,27 @@ use PrivateBin\Persistence\ServerSalt;
*/
class Paste extends AbstractModel
{
/**
* authenticated data index of paste formatter (plaintext/syntaxhighlighting/markdown)
*
* @const int
*/
const ADATA_FORMATTER = 1;
/**
* authenticated data index of open-discussion flag (0/1)
*
* @const int
*/
const ADATA_OPEN_DISCUSSION = 2;
/**
* authenticated data index of burn-after-reading flag (0/1)
*
* @const int
*/
const ADATA_BURN_AFTER_READING = 3;
/**
* Get paste data.
*
@@ -38,12 +59,13 @@ class Paste extends AbstractModel
// check if paste has expired and delete it if necessary.
if (array_key_exists('expire_date', $data['meta'])) {
if ($data['meta']['expire_date'] < time()) {
$now = time();
if ($data['meta']['expire_date'] < $now) {
$this->delete();
throw new Exception(Controller::GENERIC_ERROR, 63);
}
// We kindly provide the remaining time before expiration (in seconds)
$data['meta']['time_to_live'] = $data['meta']['expire_date'] - time();
$data['meta']['time_to_live'] = $data['meta']['expire_date'] - $now;
unset($data['meta']['expire_date']);
}
foreach (array('created', 'postdate') as $key) {
@@ -54,8 +76,8 @@ class Paste extends AbstractModel
// check if non-expired burn after reading paste needs to be deleted
if (
(array_key_exists('adata', $data) && $data['adata'][3] === 1) ||
(array_key_exists('burnafterreading', $data['meta']) && $data['meta']['burnafterreading'])
array_key_exists('adata', $data) &&
$data['adata'][self::ADATA_BURN_AFTER_READING] === 1
) {
$this->delete();
}
@@ -205,9 +227,8 @@ 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'][2] === 1) ||
(array_key_exists('opendiscussion', $this->_data['meta']) && $this->_data['meta']['opendiscussion']);
return array_key_exists('adata', $this->_data) &&
$this->_data['adata'][self::ADATA_OPEN_DISCUSSION] === 1;
}
/**
@@ -242,23 +263,26 @@ class Paste extends AbstractModel
protected function _validate(array &$data)
{
// reject invalid or disabled formatters
if (!array_key_exists($data['adata'][1], $this->_conf->getSection('formatter_options'))) {
if (!array_key_exists($data['adata'][self::ADATA_FORMATTER], $this->_conf->getSection('formatter_options'))) {
throw new Exception('Invalid data.', 75);
}
// discussion requested, but disabled in config or burn after reading requested as well, or invalid integer
if (
($data['adata'][2] === 1 && ( // open discussion flag
($data['adata'][self::ADATA_OPEN_DISCUSSION] === 1 && (
!$this->_conf->getKey('discussion') ||
$data['adata'][3] === 1 // burn after reading flag
$data['adata'][self::ADATA_BURN_AFTER_READING] === 1
)) ||
($data['adata'][2] !== 0 && $data['adata'][2] !== 1)
($data['adata'][self::ADATA_OPEN_DISCUSSION] !== 0 && $data['adata'][self::ADATA_OPEN_DISCUSSION] !== 1)
) {
throw new Exception('Invalid data.', 74);
}
// reject invalid burn after reading
if ($data['adata'][3] !== 0 && $data['adata'][3] !== 1) {
if (
$data['adata'][self::ADATA_BURN_AFTER_READING] !== 0 &&
$data['adata'][self::ADATA_BURN_AFTER_READING] !== 1
) {
throw new Exception('Invalid data.', 73);
}
}