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
|
||||
{
|
||||
/**
|
||||
* singleton instance
|
||||
* Singleton instance
|
||||
*
|
||||
* @access protected
|
||||
* @static
|
||||
|
@ -28,8 +28,14 @@ abstract class AbstractData
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -40,7 +46,7 @@ abstract class AbstractData
|
|||
}
|
||||
|
||||
/**
|
||||
* enforce singleton, disable cloning
|
||||
* Enforce singleton, disable cloning
|
||||
*
|
||||
* 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
|
||||
* @static
|
||||
|
@ -130,6 +136,27 @@ abstract class AbstractData
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
|
||||
namespace PrivateBin\Data;
|
||||
|
||||
use PrivateBin\Persistence\DataStore;
|
||||
use Exception;
|
||||
use PrivateBin\Json;
|
||||
|
||||
/**
|
||||
* Filesystem
|
||||
|
@ -21,6 +22,22 @@ use PrivateBin\Persistence\DataStore;
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
@ -40,7 +57,7 @@ class Filesystem extends AbstractData
|
|||
is_array($options) &&
|
||||
array_key_exists('dir', $options)
|
||||
) {
|
||||
DataStore::setPath($options['dir']);
|
||||
self::$_path = $options['dir'];
|
||||
}
|
||||
return self::$_instance;
|
||||
}
|
||||
|
@ -63,7 +80,7 @@ class Filesystem extends AbstractData
|
|||
if (!is_dir($storagedir)) {
|
||||
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 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';
|
||||
// convert to PHP protected files if needed
|
||||
if (is_readable($basePath)) {
|
||||
DataStore::prependRename($basePath, $pastePath);
|
||||
self::_prependRename($basePath, $pastePath);
|
||||
|
||||
// convert comments, too
|
||||
$discdir = self::_dataid2discussionpath($pasteid);
|
||||
|
@ -136,7 +153,7 @@ class Filesystem extends AbstractData
|
|||
while (false !== ($filename = $dir->read())) {
|
||||
if (substr($filename, -4) !== '.php' && strlen($filename) >= 16) {
|
||||
$commentFilename = $discdir . $filename . '.php';
|
||||
DataStore::prependRename($discdir . $filename, $commentFilename);
|
||||
self::_prependRename($discdir . $filename, $commentFilename);
|
||||
}
|
||||
}
|
||||
$dir->close();
|
||||
|
@ -165,7 +182,7 @@ class Filesystem extends AbstractData
|
|||
if (!is_dir($storagedir)) {
|
||||
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.
|
||||
// - parentid is the comment this comment replies to (It can be pasteid)
|
||||
if (is_file($discdir . $filename)) {
|
||||
$comment = DataStore::get($discdir . $filename);
|
||||
$comment = self::_get($discdir . $filename);
|
||||
$items = explode('.', $filename);
|
||||
// Add some meta information not contained in file.
|
||||
$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
|
||||
*
|
||||
|
@ -233,9 +277,8 @@ class Filesystem extends AbstractData
|
|||
protected function _getExpiredPastes($batchsize)
|
||||
{
|
||||
$pastes = array();
|
||||
$mainpath = DataStore::getPath();
|
||||
$firstLevel = array_filter(
|
||||
scandir($mainpath),
|
||||
scandir(self::$_path),
|
||||
'self::_isFirstLevelDir'
|
||||
);
|
||||
if (count($firstLevel) > 0) {
|
||||
|
@ -243,7 +286,7 @@ class Filesystem extends AbstractData
|
|||
for ($i = 0, $max = $batchsize * 10; $i < $max; ++$i) {
|
||||
$firstKey = array_rand($firstLevel);
|
||||
$secondLevel = array_filter(
|
||||
scandir($mainpath . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]),
|
||||
scandir(self::$_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]),
|
||||
'self::_isSecondLevelDir'
|
||||
);
|
||||
|
||||
|
@ -254,7 +297,7 @@ class Filesystem extends AbstractData
|
|||
}
|
||||
|
||||
$secondKey = array_rand($secondLevel);
|
||||
$path = $mainpath . DIRECTORY_SEPARATOR .
|
||||
$path = self::$_path . DIRECTORY_SEPARATOR .
|
||||
$firstLevel[$firstKey] . DIRECTORY_SEPARATOR .
|
||||
$secondLevel[$secondKey];
|
||||
if (!is_dir($path)) {
|
||||
|
@ -314,10 +357,9 @@ class Filesystem extends AbstractData
|
|||
*/
|
||||
private static function _dataid2path($dataid)
|
||||
{
|
||||
return DataStore::getPath(
|
||||
return self::$_path . 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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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