scaffolding interface for AbstractData key/value storage, folding Persistance\DataStore into Data\Filesystem

This commit is contained in:
El RIDO 2021-06-07 06:53:15 +02:00
parent de8f40ac1a
commit 1a7d0799c0
No known key found for this signature in database
GPG key ID: 0F5C940A6BD81F92
3 changed files with 185 additions and 116 deletions

View file

@ -20,7 +20,7 @@ namespace PrivateBin\Data;
abstract class AbstractData abstract class AbstractData
{ {
/** /**
* singleton instance * Singleton instance
* *
* @access protected * @access protected
* @static * @static
@ -28,8 +28,14 @@ abstract class AbstractData
*/ */
protected static $_instance = null; protected static $_instance = null;
protected static $_namespaces = array(
'purge_limiter',
'salt',
'traffic_limiter',
);
/** /**
* enforce singleton, disable constructor * Enforce singleton, disable constructor
* *
* Instantiate using {@link getInstance()}, privatebin is a singleton object. * Instantiate using {@link getInstance()}, privatebin is a singleton object.
* *
@ -40,7 +46,7 @@ abstract class AbstractData
} }
/** /**
* enforce singleton, disable cloning * Enforce singleton, disable cloning
* *
* Instantiate using {@link getInstance()}, privatebin is a singleton object. * Instantiate using {@link getInstance()}, privatebin is a singleton object.
* *
@ -51,7 +57,7 @@ abstract class AbstractData
} }
/** /**
* get instance of singleton * Get instance of singleton
* *
* @access public * @access public
* @static * @static
@ -130,6 +136,27 @@ abstract class AbstractData
*/ */
abstract public function existsComment($pasteid, $parentid, $commentid); abstract public function existsComment($pasteid, $parentid, $commentid);
/**
* Save a value.
*
* @access public
* @param string $value
* @param string $namespace
* @param string $key
* @return bool
*/
abstract public function setValue($value, $namespace, $key = '');
/**
* Load a value.
*
* @access public
* @param string $namespace
* @param string $key
* @return string
*/
abstract public function getValue($namespace, $key = '');
/** /**
* Returns up to batch size number of paste ids that have expired * Returns up to batch size number of paste ids that have expired
* *

View file

@ -12,7 +12,8 @@
namespace PrivateBin\Data; namespace PrivateBin\Data;
use PrivateBin\Persistence\DataStore; use Exception;
use PrivateBin\Json;
/** /**
* Filesystem * Filesystem
@ -21,6 +22,22 @@ use PrivateBin\Persistence\DataStore;
*/ */
class Filesystem extends AbstractData class Filesystem extends AbstractData
{ {
/**
* first line in file, to protect its contents
*
* @const string
*/
const PROTECTION_LINE = '<?php http_response_code(403); /*';
/**
* path in which to persist something
*
* @access private
* @static
* @var string
*/
private static $_path = 'data';
/** /**
* get instance of singleton * get instance of singleton
* *
@ -40,7 +57,7 @@ class Filesystem extends AbstractData
is_array($options) && is_array($options) &&
array_key_exists('dir', $options) array_key_exists('dir', $options)
) { ) {
DataStore::setPath($options['dir']); self::$_path = $options['dir'];
} }
return self::$_instance; return self::$_instance;
} }
@ -63,7 +80,7 @@ class Filesystem extends AbstractData
if (!is_dir($storagedir)) { if (!is_dir($storagedir)) {
mkdir($storagedir, 0700, true); mkdir($storagedir, 0700, true);
} }
return DataStore::store($file, $paste); return self::_store($file, $paste);
} }
/** /**
@ -79,7 +96,7 @@ class Filesystem extends AbstractData
return false; return false;
} }
return self::upgradePreV1Format( return self::upgradePreV1Format(
DataStore::get(self::_dataid2path($pasteid) . $pasteid . '.php') self::_get(self::_dataid2path($pasteid) . $pasteid . '.php')
); );
} }
@ -127,7 +144,7 @@ class Filesystem extends AbstractData
$pastePath = $basePath . '.php'; $pastePath = $basePath . '.php';
// convert to PHP protected files if needed // convert to PHP protected files if needed
if (is_readable($basePath)) { if (is_readable($basePath)) {
DataStore::prependRename($basePath, $pastePath); self::_prependRename($basePath, $pastePath);
// convert comments, too // convert comments, too
$discdir = self::_dataid2discussionpath($pasteid); $discdir = self::_dataid2discussionpath($pasteid);
@ -136,7 +153,7 @@ class Filesystem extends AbstractData
while (false !== ($filename = $dir->read())) { while (false !== ($filename = $dir->read())) {
if (substr($filename, -4) !== '.php' && strlen($filename) >= 16) { if (substr($filename, -4) !== '.php' && strlen($filename) >= 16) {
$commentFilename = $discdir . $filename . '.php'; $commentFilename = $discdir . $filename . '.php';
DataStore::prependRename($discdir . $filename, $commentFilename); self::_prependRename($discdir . $filename, $commentFilename);
} }
} }
$dir->close(); $dir->close();
@ -165,7 +182,7 @@ class Filesystem extends AbstractData
if (!is_dir($storagedir)) { if (!is_dir($storagedir)) {
mkdir($storagedir, 0700, true); mkdir($storagedir, 0700, true);
} }
return DataStore::store($file, $comment); return self::_store($file, $comment);
} }
/** /**
@ -187,7 +204,7 @@ class Filesystem extends AbstractData
// - commentid is the comment identifier itself. // - commentid is the comment identifier itself.
// - parentid is the comment this comment replies to (It can be pasteid) // - parentid is the comment this comment replies to (It can be pasteid)
if (is_file($discdir . $filename)) { if (is_file($discdir . $filename)) {
$comment = DataStore::get($discdir . $filename); $comment = self::_get($discdir . $filename);
$items = explode('.', $filename); $items = explode('.', $filename);
// Add some meta information not contained in file. // Add some meta information not contained in file.
$comment['id'] = $items[1]; $comment['id'] = $items[1];
@ -223,6 +240,33 @@ class Filesystem extends AbstractData
); );
} }
/**
* Save a value.
*
* @access public
* @param string $value
* @param string $namespace
* @param string $key
* @return bool
*/
public function setValue($value, $namespace, $key = '')
{
}
/**
* Load a value.
*
* @access public
* @param string $namespace
* @param string $key
* @return string
*/
public function getValue($namespace, $key = '')
{
}
/** /**
* Returns up to batch size number of paste ids that have expired * Returns up to batch size number of paste ids that have expired
* *
@ -233,9 +277,8 @@ class Filesystem extends AbstractData
protected function _getExpiredPastes($batchsize) protected function _getExpiredPastes($batchsize)
{ {
$pastes = array(); $pastes = array();
$mainpath = DataStore::getPath();
$firstLevel = array_filter( $firstLevel = array_filter(
scandir($mainpath), scandir(self::$_path),
'self::_isFirstLevelDir' 'self::_isFirstLevelDir'
); );
if (count($firstLevel) > 0) { if (count($firstLevel) > 0) {
@ -243,7 +286,7 @@ class Filesystem extends AbstractData
for ($i = 0, $max = $batchsize * 10; $i < $max; ++$i) { for ($i = 0, $max = $batchsize * 10; $i < $max; ++$i) {
$firstKey = array_rand($firstLevel); $firstKey = array_rand($firstLevel);
$secondLevel = array_filter( $secondLevel = array_filter(
scandir($mainpath . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]), scandir(self::$_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]),
'self::_isSecondLevelDir' 'self::_isSecondLevelDir'
); );
@ -254,7 +297,7 @@ class Filesystem extends AbstractData
} }
$secondKey = array_rand($secondLevel); $secondKey = array_rand($secondLevel);
$path = $mainpath . DIRECTORY_SEPARATOR . $path = self::$_path . DIRECTORY_SEPARATOR .
$firstLevel[$firstKey] . DIRECTORY_SEPARATOR . $firstLevel[$firstKey] . DIRECTORY_SEPARATOR .
$secondLevel[$secondKey]; $secondLevel[$secondKey];
if (!is_dir($path)) { if (!is_dir($path)) {
@ -314,10 +357,9 @@ class Filesystem extends AbstractData
*/ */
private static function _dataid2path($dataid) private static function _dataid2path($dataid)
{ {
return DataStore::getPath( return self::$_path . DIRECTORY_SEPARATOR .
substr($dataid, 0, 2) . DIRECTORY_SEPARATOR . substr($dataid, 0, 2) . DIRECTORY_SEPARATOR .
substr($dataid, 2, 2) . DIRECTORY_SEPARATOR substr($dataid, 2, 2) . DIRECTORY_SEPARATOR;
);
} }
/** /**
@ -347,7 +389,7 @@ class Filesystem extends AbstractData
private static function _isFirstLevelDir($element) private static function _isFirstLevelDir($element)
{ {
return self::_isSecondLevelDir($element) && return self::_isSecondLevelDir($element) &&
is_dir(DataStore::getPath($element)); is_dir(self::$_path . DIRECTORY_SEPARATOR . $element);
} }
/** /**
@ -362,4 +404,100 @@ class Filesystem extends AbstractData
{ {
return (bool) preg_match('/^[a-f0-9]{2}$/', $element); return (bool) preg_match('/^[a-f0-9]{2}$/', $element);
} }
/**
* store the data
*
* @access public
* @static
* @param string $filename
* @param array $data
* @return bool
*/
private static function _store($filename, $data)
{
if (strpos($filename, self::$_path) === 0) {
$filename = substr($filename, strlen(self::$_path));
}
// Create storage directory if it does not exist.
if (!is_dir(self::$_path)) {
if (!@mkdir(self::$_path, 0700)) {
throw new Exception('unable to create directory ' . self::$_path, 10);
}
}
$file = self::$_path . DIRECTORY_SEPARATOR . '.htaccess';
if (!is_file($file)) {
$writtenBytes = 0;
if ($fileCreated = @touch($file)) {
$writtenBytes = @file_put_contents(
$file,
'Require all denied' . PHP_EOL,
LOCK_EX
);
}
if ($fileCreated === false || $writtenBytes === false || $writtenBytes < 19) {
return false;
}
}
try {
$data = self::PROTECTION_LINE . PHP_EOL . Json::encode($data);
} catch (Exception $e) {
return false;
}
$file = self::$_path . DIRECTORY_SEPARATOR . $filename;
$fileCreated = true;
$writtenBytes = 0;
if (!is_file($file)) {
$fileCreated = @touch($file);
}
if ($fileCreated) {
$writtenBytes = @file_put_contents($file, $data, LOCK_EX);
}
if ($fileCreated === false || $writtenBytes === false || $writtenBytes < strlen($data)) {
return false;
}
@chmod($file, 0640); // protect file access
return true;
}
/**
* get the data
*
* @access public
* @static
* @param string $filename
* @return array|false $data
*/
private static function _get($filename)
{
return Json::decode(
substr(
file_get_contents($filename),
strlen(self::PROTECTION_LINE . PHP_EOL)
)
);
}
/**
* rename a file, prepending the protection line at the beginning
*
* @access public
* @static
* @param string $srcFile
* @param string $destFile
* @return void
*/
private static function _prependRename($srcFile, $destFile)
{
// don't overwrite already converted file
if (!is_readable($destFile)) {
$handle = fopen($srcFile, 'r', false, stream_context_create());
file_put_contents($destFile, self::PROTECTION_LINE . PHP_EOL);
file_put_contents($destFile, $handle, FILE_APPEND);
fclose($handle);
}
unlink($srcFile);
}
} }

View file

@ -1,96 +0,0 @@
<?php
/**
* 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
* @version 1.3.5
*/
namespace PrivateBin\Persistence;
use Exception;
use PrivateBin\Json;
/**
* DataStore
*
* Handles data storage for Data\Filesystem.
*/
class DataStore extends AbstractPersistence
{
/**
* first line in file, to protect its contents
*
* @const string
*/
const PROTECTION_LINE = '<?php http_response_code(403); /*';
/**
* store the data
*
* @access public
* @static
* @param string $filename
* @param array $data
* @return bool
*/
public static function store($filename, $data)
{
$path = self::getPath();
if (strpos($filename, $path) === 0) {
$filename = substr($filename, strlen($path));
}
try {
self::_store(
$filename,
self::PROTECTION_LINE . PHP_EOL . Json::encode($data)
);
return true;
} catch (Exception $e) {
return false;
}
}
/**
* get the data
*
* @access public
* @static
* @param string $filename
* @return array|false $data
*/
public static function get($filename)
{
return Json::decode(
substr(
file_get_contents($filename),
strlen(self::PROTECTION_LINE . PHP_EOL)
)
);
}
/**
* rename a file, prepending the protection line at the beginning
*
* @access public
* @static
* @param string $srcFile
* @param string $destFile
* @return void
*/
public static function prependRename($srcFile, $destFile)
{
// don't overwrite already converted file
if (!is_readable($destFile)) {
$handle = fopen($srcFile, 'r', false, stream_context_create());
file_put_contents($destFile, self::PROTECTION_LINE . PHP_EOL);
file_put_contents($destFile, $handle, FILE_APPEND);
fclose($handle);
}
unlink($srcFile);
}
}