scaffolding interface for AbstractData key/value storage, folding Persistance\DataStore into Data\Filesystem
This commit is contained in:
parent
de8f40ac1a
commit
1a7d0799c0
3 changed files with 185 additions and 116 deletions
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue