Merge branch 'persistence-into-data'

pull/817/head
El RIDO 2021-06-20 08:17:41 +02:00
commit 5f2daa5cd6
No known key found for this signature in database
GPG Key ID: 0F5C940A6BD81F92
33 changed files with 1618 additions and 1073 deletions

View File

@ -10,6 +10,8 @@
* CHANGED: Language selection cookie only transmitted over HTTPS (#472)
* CHANGED: Upgrading libraries to: random_compat 2.0.20
* CHANGED: Removed automatic `.ini` configuration file migration (#808)
* CHANGED: Removed configurable `dir` for `traffic` & `purge` limiters (#419)
* CHANGED: Server salt, traffic and purge limiter now stored in the storage backend (#419)
* **1.3.5 (2021-04-05)**
* ADDED: Translation for Hebrew, Lithuanian, Indonesian and Catalan
* ADDED: Make the project info configurable (#681)

View File

@ -143,9 +143,6 @@ limit = 10
; set the HTTP header containing the visitors IP address, i.e. X_FORWARDED_FOR
; header = "X_FORWARDED_FOR"
; directory to store the traffic limits in
dir = PATH "data"
[purge]
; minimum time limit between two purgings of expired pastes, it is only
; triggered when pastes are created
@ -157,9 +154,6 @@ limit = 300
; site
batchsize = 10
; directory to store the purge limit in
dir = PATH "data"
[model]
; name of data model class to load and directory for storage
; the default model "Filesystem" stores everything in the filesystem

View File

@ -80,13 +80,11 @@ class Configuration
'traffic' => array(
'limit' => 10,
'header' => null,
'dir' => 'data',
'exemptedIp' => null,
),
'purge' => array(
'limit' => 300,
'batchsize' => 10,
'dir' => 'data',
),
'model' => array(
'class' => 'Filesystem',

View File

@ -162,7 +162,6 @@ class Controller
$this->_model = new Model($this->_conf);
$this->_request = new Request;
$this->_urlBase = $this->_request->getRequestUri();
ServerSalt::setPath($this->_conf->getKey('dir', 'traffic'));
// set default language
$lang = $this->_conf->getKey('languagedefault');
@ -196,20 +195,17 @@ class Controller
*/
private function _create()
{
try {
// Ensure last paste from visitors IP address was more than configured amount of seconds ago.
TrafficLimiter::setConfiguration($this->_conf);
if (!TrafficLimiter::canPass()) {
$this->_return_message(
1, I18n::_(
'Please wait %d seconds between each post.',
$this->_conf->getKey('limit', 'traffic')
)
);
return;
}
} catch (Exception $e) {
$this->_return_message(1, I18n::_($e->getMessage()));
// Ensure last paste from visitors IP address was more than configured amount of seconds ago.
ServerSalt::setStore($this->_model->getStore());
TrafficLimiter::setConfiguration($this->_conf);
TrafficLimiter::setStore($this->_model->getStore());
if (!TrafficLimiter::canPass()) {
$this->_return_message(
1, I18n::_(
'Please wait %d seconds between each post.',
$this->_conf->getKey('limit', 'traffic')
)
);
return;
}

View File

@ -15,12 +15,12 @@ namespace PrivateBin\Data;
/**
* AbstractData
*
* Abstract model for PrivateBin data access, implemented as a singleton.
* Abstract model for data access, implemented as a singleton.
*/
abstract class AbstractData
{
/**
* singleton instance
* Singleton instance
*
* @access protected
* @static
@ -29,9 +29,18 @@ abstract class AbstractData
protected static $_instance = null;
/**
* enforce singleton, disable constructor
* cache for the traffic limiter
*
* Instantiate using {@link getInstance()}, privatebin is a singleton object.
* @access private
* @static
* @var array
*/
protected static $_last_cache = array();
/**
* Enforce singleton, disable constructor
*
* Instantiate using {@link getInstance()}, this object implements the singleton pattern.
*
* @access protected
*/
@ -40,9 +49,9 @@ abstract class AbstractData
}
/**
* enforce singleton, disable cloning
* Enforce singleton, disable cloning
*
* Instantiate using {@link getInstance()}, privatebin is a singleton object.
* Instantiate using {@link getInstance()}, this object implements the singleton pattern.
*
* @access private
*/
@ -51,7 +60,7 @@ abstract class AbstractData
}
/**
* get instance of singleton
* Get instance of singleton
*
* @access public
* @static
@ -130,6 +139,46 @@ abstract class AbstractData
*/
abstract public function existsComment($pasteid, $parentid, $commentid);
/**
* Purge outdated entries.
*
* @access public
* @param string $namespace
* @param int $time
* @return void
*/
public function purgeValues($namespace, $time)
{
if ($namespace === 'traffic_limiter') {
foreach (self::$_last_cache as $key => $last_submission) {
if ($last_submission <= $time) {
unset(self::$_last_cache[$key]);
}
}
}
}
/**
* 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
*

View File

@ -198,21 +198,25 @@ class Database extends AbstractData
$opendiscussion = $paste['adata'][2];
$burnafterreading = $paste['adata'][3];
}
return self::_exec(
'INSERT INTO ' . self::_sanitizeIdentifier('paste') .
' VALUES(?,?,?,?,?,?,?,?,?)',
array(
$pasteid,
$isVersion1 ? $paste['data'] : Json::encode($paste),
$created,
$expire_date,
(int) $opendiscussion,
(int) $burnafterreading,
Json::encode($meta),
$attachment,
$attachmentname,
)
);
try {
return self::_exec(
'INSERT INTO ' . self::_sanitizeIdentifier('paste') .
' VALUES(?,?,?,?,?,?,?,?,?)',
array(
$pasteid,
$isVersion1 ? $paste['data'] : Json::encode($paste),
$created,
$expire_date,
(int) $opendiscussion,
(int) $burnafterreading,
Json::encode($meta),
$attachment,
$attachmentname,
)
);
} catch (Exception $e) {
return false;
}
}
/**
@ -229,11 +233,14 @@ class Database extends AbstractData
}
self::$_cache[$pasteid] = false;
$paste = self::_select(
'SELECT * FROM ' . self::_sanitizeIdentifier('paste') .
' WHERE dataid = ?', array($pasteid), true
);
try {
$paste = self::_select(
'SELECT * FROM ' . self::_sanitizeIdentifier('paste') .
' WHERE dataid = ?', array($pasteid), true
);
} catch (Exception $e) {
$paste = false;
}
if ($paste === false) {
return false;
}
@ -348,19 +355,23 @@ class Database extends AbstractData
$meta[$key] = null;
}
}
return self::_exec(
'INSERT INTO ' . self::_sanitizeIdentifier('comment') .
' VALUES(?,?,?,?,?,?,?)',
array(
$commentid,
$pasteid,
$parentid,
$data,
$meta['nickname'],
$meta[$iconKey],
$meta[$createdKey],
)
);
try {
return self::_exec(
'INSERT INTO ' . self::_sanitizeIdentifier('comment') .
' VALUES(?,?,?,?,?,?,?)',
array(
$commentid,
$pasteid,
$parentid,
$data,
$meta['nickname'],
$meta[$iconKey],
$meta[$createdKey],
)
);
} catch (Exception $e) {
return false;
}
}
/**
@ -416,13 +427,85 @@ class Database extends AbstractData
*/
public function existsComment($pasteid, $parentid, $commentid)
{
return (bool) self::_select(
'SELECT dataid FROM ' . self::_sanitizeIdentifier('comment') .
' WHERE pasteid = ? AND parentid = ? AND dataid = ?',
array($pasteid, $parentid, $commentid), true
try {
return (bool) self::_select(
'SELECT dataid FROM ' . self::_sanitizeIdentifier('comment') .
' WHERE pasteid = ? AND parentid = ? AND dataid = ?',
array($pasteid, $parentid, $commentid), true
);
} catch (Exception $e) {
return false;
}
}
/**
* Save a value.
*
* @access public
* @param string $value
* @param string $namespace
* @param string $key
* @return bool
*/
public function setValue($value, $namespace, $key = '')
{
if ($namespace === 'traffic_limiter') {
self::$_last_cache[$key] = $value;
try {
$value = Json::encode(self::$_last_cache);
} catch (Exception $e) {
return false;
}
}
return self::_exec(
'UPDATE ' . self::_sanitizeIdentifier('config') .
' SET value = ? WHERE id = ?',
array($value, strtoupper($namespace))
);
}
/**
* Load a value.
*
* @access public
* @param string $namespace
* @param string $key
* @return string
*/
public function getValue($namespace, $key = '')
{
$configKey = strtoupper($namespace);
$value = $this->_getConfig($configKey);
if ($value === '') {
// initialize the row, so that setValue can rely on UPDATE queries
self::_exec(
'INSERT INTO ' . self::_sanitizeIdentifier('config') .
' VALUES(?,?)',
array($configKey, '')
);
// migrate filesystem based salt into database
$file = 'data' . DIRECTORY_SEPARATOR . 'salt.php';
if ($namespace === 'salt' && is_readable($file)) {
$value = Filesystem::getInstance(array('dir' => 'data'))->getValue('salt');
$this->setValue($value, 'salt');
@unlink($file);
return $value;
}
}
if ($value && $namespace === 'traffic_limiter') {
try {
self::$_last_cache = Json::decode($value);
} catch (Exception $e) {
self::$_last_cache = array();
}
if (array_key_exists($key, self::$_last_cache)) {
return self::$_last_cache[$key];
}
}
return (string) $value;
}
/**
* Returns up to batch size number of paste ids that have expired
*
@ -563,16 +646,19 @@ class Database extends AbstractData
* @access private
* @static
* @param string $key
* @throws PDOException
* @return string
*/
private static function _getConfig($key)
{
$row = self::_select(
'SELECT value FROM ' . self::_sanitizeIdentifier('config') .
' WHERE id = ?', array($key), true
);
return $row['value'];
try {
$row = self::_select(
'SELECT value FROM ' . self::_sanitizeIdentifier('config') .
' WHERE id = ?', array($key), true
);
} catch (PDOException $e) {
return '';
}
return $row ? $row['value'] : '';
}
/**

View File

@ -12,7 +12,8 @@
namespace PrivateBin\Data;
use PrivateBin\Persistence\DataStore;
use Exception;
use PrivateBin\Json;
/**
* Filesystem
@ -21,6 +22,29 @@ use PrivateBin\Persistence\DataStore;
*/
class Filesystem extends AbstractData
{
/**
* first line in paste or comment files, to protect their contents from browsing exposed data directories
*
* @const string
*/
const PROTECTION_LINE = '<?php http_response_code(403); /*';
/**
* line in generated .htaccess files, to protect exposed directories from being browsable on apache web servers
*
* @const string
*/
const HTACCESS_LINE = 'Require all denied';
/**
* path in which to persist something
*
* @access private
* @static
* @var string
*/
private static $_path = 'data';
/**
* get instance of singleton
*
@ -40,7 +64,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 +87,7 @@ class Filesystem extends AbstractData
if (!is_dir($storagedir)) {
mkdir($storagedir, 0700, true);
}
return DataStore::store($file, $paste);
return self::_store($file, $paste);
}
/**
@ -75,12 +99,13 @@ class Filesystem extends AbstractData
*/
public function read($pasteid)
{
if (!$this->exists($pasteid)) {
if (
!$this->exists($pasteid) ||
!$paste = self::_get(self::_dataid2path($pasteid) . $pasteid . '.php')
) {
return false;
}
return self::upgradePreV1Format(
DataStore::get(self::_dataid2path($pasteid) . $pasteid . '.php')
);
return self::upgradePreV1Format($paste);
}
/**
@ -127,7 +152,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 +161,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 +190,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 +212,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 +248,97 @@ 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 = '')
{
switch ($namespace) {
case 'purge_limiter':
return self::_storeString(
self::$_path . DIRECTORY_SEPARATOR . 'purge_limiter.php',
'<?php' . PHP_EOL . '$GLOBALS[\'purge_limiter\'] = ' . $value . ';'
);
case 'salt':
return self::_storeString(
self::$_path . DIRECTORY_SEPARATOR . 'salt.php',
'<?php # |' . $value . '|'
);
case 'traffic_limiter':
self::$_last_cache[$key] = $value;
return self::_storeString(
self::$_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php',
'<?php' . PHP_EOL . '$GLOBALS[\'traffic_limiter\'] = ' . var_export(self::$_last_cache, true) . ';'
);
}
return false;
}
/**
* Load a value.
*
* @access public
* @param string $namespace
* @param string $key
* @return string
*/
public function getValue($namespace, $key = '')
{
switch ($namespace) {
case 'purge_limiter':
$file = self::$_path . DIRECTORY_SEPARATOR . 'purge_limiter.php';
if (is_readable($file)) {
require $file;
return $GLOBALS['purge_limiter'];
}
break;
case 'salt':
$file = self::$_path . DIRECTORY_SEPARATOR . 'salt.php';
if (is_readable($file)) {
$items = explode('|', file_get_contents($file));
if (is_array($items) && count($items) == 3) {
return $items[1];
}
}
break;
case 'traffic_limiter':
$file = self::$_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php';
if (is_readable($file)) {
require $file;
self::$_last_cache = $GLOBALS['traffic_limiter'];
if (array_key_exists($key, self::$_last_cache)) {
return self::$_last_cache[$key];
}
}
break;
}
return '';
}
/**
* 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)
)
);
}
/**
* Returns up to batch size number of paste ids that have expired
*
@ -233,9 +349,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 +358,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 +369,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 +429,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 +461,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 +476,97 @@ 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, array $data)
{
try {
return self::_storeString(
$filename,
self::PROTECTION_LINE . PHP_EOL . Json::encode($data)
);
} catch (Exception $e) {
return false;
}
}
/**
* store a string
*
* @access public
* @static
* @param string $filename
* @param string $data
* @return bool
*/
private static function _storeString($filename, $data)
{
// Create storage directory if it does not exist.
if (!is_dir(self::$_path)) {
if (!@mkdir(self::$_path, 0700)) {
return false;
}
}
$file = self::$_path . DIRECTORY_SEPARATOR . '.htaccess';
if (!is_file($file)) {
$writtenBytes = 0;
if ($fileCreated = @touch($file)) {
$writtenBytes = @file_put_contents(
$file,
self::HTACCESS_LINE . PHP_EOL,
LOCK_EX
);
}
if (
$fileCreated === false ||
$writtenBytes === false ||
$writtenBytes < strlen(self::HTACCESS_LINE . PHP_EOL)
) {
return false;
}
}
$fileCreated = true;
$writtenBytes = 0;
if (!is_file($filename)) {
$fileCreated = @touch($filename);
}
if ($fileCreated) {
$writtenBytes = @file_put_contents($filename, $data, LOCK_EX);
}
if ($fileCreated === false || $writtenBytes === false || $writtenBytes < strlen($data)) {
return false;
}
@chmod($filename, 0640); // protect file from access by other users on the host
return true;
}
/**
* 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

@ -4,11 +4,39 @@ namespace PrivateBin\Data;
use Exception;
use Google\Cloud\Core\Exception\NotFoundException;
use Google\Cloud\Storage\Bucket;
use Google\Cloud\Storage\StorageClient;
use PrivateBin\Json;
class GoogleCloudStorage extends AbstractData
{
/**
* GCS client
*
* @access private
* @static
* @var StorageClient
*/
private static $_client = null;
/**
* GCS bucket
*
* @access private
* @static
* @var Bucket
*/
private static $_bucket = null;
/**
* object prefix
*
* @access private
* @static
* @var string
*/
private static $_prefix = 'pastes';
/**
* returns a Google Cloud Storage data backend.
*
@ -19,10 +47,12 @@ class GoogleCloudStorage extends AbstractData
*/
public static function getInstance(array $options)
{
$client = null;
$bucket = null;
$prefix = 'pastes';
// if needed initialize the singleton
if (!(self::$_instance instanceof self)) {
self::$_instance = new self;
}
$bucket = null;
if (getenv('PRIVATEBIN_GCS_BUCKET')) {
$bucket = getenv('PRIVATEBIN_GCS_BUCKET');
}
@ -30,53 +60,36 @@ class GoogleCloudStorage extends AbstractData
$bucket = $options['bucket'];
}
if (is_array($options) && array_key_exists('prefix', $options)) {
$prefix = $options['prefix'];
}
if (is_array($options) && array_key_exists('client', $options)) {
$client = $options['client'];
self::$_prefix = $options['prefix'];
}
if (!(self::$_instance instanceof self)) {
self::$_instance = new self($bucket, $prefix, $client);
if (empty(self::$_client)) {
self::$_client = class_exists('StorageClientStub', false) ?
new \StorageClientStub(array()) :
new StorageClient(array('suppressKeyFileNotice' => true));
}
self::$_bucket = self::$_client->bucket($bucket);
return self::$_instance;
}
protected $_client = null;
protected $_bucket = null;
protected $_prefix = 'pastes';
public function __construct($bucket, $prefix, $client = null)
{
parent::__construct();
if ($client == null) {
$this->_client = new StorageClient(array('suppressKeyFileNotice' => true));
} else {
// use given client for test purposes
$this->_client = $client;
}
$this->_bucket = $this->_client->bucket($bucket);
if ($prefix != null) {
$this->_prefix = $prefix;
}
}
/**
* returns the google storage object key for $pasteid in $this->_bucket.
* returns the google storage object key for $pasteid in self::$_bucket.
*
* @access private
* @param $pasteid string to get the key for
* @return string
*/
private function _getKey($pasteid)
{
if ($this->_prefix != '') {
return $this->_prefix . '/' . $pasteid;
if (self::$_prefix != '') {
return self::$_prefix . '/' . $pasteid;
}
return $pasteid;
}
/**
* Uploads the payload in the $this->_bucket under the specified key.
* Uploads the payload in the self::$_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.
@ -85,7 +98,7 @@ class GoogleCloudStorage extends AbstractData
* @param $payload array to store
* @return bool true if successful, otherwise false.
*/
private function upload($key, $payload)
private function _upload($key, $payload)
{
$metadata = array_key_exists('meta', $payload) ? $payload['meta'] : array();
unset($metadata['attachment'], $metadata['attachmentname'], $metadata['salt']);
@ -93,7 +106,7 @@ class GoogleCloudStorage extends AbstractData
$metadata[$k] = strval($v);
}
try {
$this->_bucket->upload(Json::encode($payload), array(
self::$_bucket->upload(Json::encode($payload), array(
'name' => $key,
'chunkSize' => 262144,
'predefinedAcl' => 'private',
@ -103,7 +116,7 @@ class GoogleCloudStorage extends AbstractData
),
));
} catch (Exception $e) {
error_log('failed to upload ' . $key . ' to ' . $this->_bucket->name() . ', ' .
error_log('failed to upload ' . $key . ' to ' . self::$_bucket->name() . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
@ -119,7 +132,7 @@ class GoogleCloudStorage extends AbstractData
return false;
}
return $this->upload($this->_getKey($pasteid), $paste);
return $this->_upload($this->_getKey($pasteid), $paste);
}
/**
@ -128,13 +141,13 @@ class GoogleCloudStorage extends AbstractData
public function read($pasteid)
{
try {
$o = $this->_bucket->object($this->_getKey($pasteid));
$o = self::$_bucket->object($this->_getKey($pasteid));
$data = $o->downloadAsString();
return Json::decode($data);
} catch (NotFoundException $e) {
return false;
} catch (Exception $e) {
error_log('failed to read ' . $pasteid . ' from ' . $this->_bucket->name() . ', ' .
error_log('failed to read ' . $pasteid . ' from ' . self::$_bucket->name() . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
@ -148,9 +161,9 @@ class GoogleCloudStorage extends AbstractData
$name = $this->_getKey($pasteid);
try {
foreach ($this->_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) {
foreach (self::$_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) {
try {
$this->_bucket->object($comment->name())->delete();
self::$_bucket->object($comment->name())->delete();
} catch (NotFoundException $e) {
// ignore if already deleted.
}
@ -160,7 +173,7 @@ class GoogleCloudStorage extends AbstractData
}
try {
$this->_bucket->object($name)->delete();
self::$_bucket->object($name)->delete();
} catch (NotFoundException $e) {
// ignore if already deleted
}
@ -171,7 +184,7 @@ class GoogleCloudStorage extends AbstractData
*/
public function exists($pasteid)
{
$o = $this->_bucket->object($this->_getKey($pasteid));
$o = self::$_bucket->object($this->_getKey($pasteid));
return $o->exists();
}
@ -184,7 +197,7 @@ class GoogleCloudStorage extends AbstractData
return false;
}
$key = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid;
return $this->upload($key, $comment);
return $this->_upload($key, $comment);
}
/**
@ -195,8 +208,8 @@ class GoogleCloudStorage extends AbstractData
$comments = array();
$prefix = $this->_getKey($pasteid) . '/discussion/';
try {
foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $key) {
$comment = JSON::decode($this->_bucket->object($key->name())->downloadAsString());
foreach (self::$_bucket->objects(array('prefix' => $prefix)) as $key) {
$comment = JSON::decode(self::$_bucket->object($key->name())->downloadAsString());
$comment['id'] = basename($key->name());
$slot = $this->getOpenSlot($comments, (int) $comment['meta']['created']);
$comments[$slot] = $comment;
@ -213,10 +226,92 @@ class GoogleCloudStorage extends AbstractData
public function existsComment($pasteid, $parentid, $commentid)
{
$name = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid;
$o = $this->_bucket->object($name);
$o = self::$_bucket->object($name);
return $o->exists();
}
/**
* @inheritDoc
*/
public function purgeValues($namespace, $time)
{
$path = 'config/' . $namespace;
try {
foreach (self::$_bucket->objects(array('prefix' => $path)) as $object) {
$name = $object->name();
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.
}
}
}
}
} catch (NotFoundException $e) {
// no objects in the bucket yet
}
}
/**
* For GoogleCloudStorage, the value will also be stored in the metadata for the
* namespaces traffic_limiter and purge_limiter.
* @inheritDoc
*/
public function setValue($value, $namespace, $key = '')
{
if ($key === '') {
$key = 'config/' . $namespace;
} else {
$key = 'config/' . $namespace . '/' . $key;
}
$metadata = array('namespace' => $namespace);
if ($namespace != 'salt') {
$metadata['value'] = strval($value);
}
try {
self::$_bucket->upload($value, array(
'name' => $key,
'chunkSize' => 262144,
'predefinedAcl' => 'private',
'metadata' => array(
'content-type' => 'application/json',
'metadata' => $metadata,
),
));
} catch (Exception $e) {
error_log('failed to set key ' . $key . ' to ' . self::$_bucket->name() . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
return true;
}
/**
* @inheritDoc
*/
public function getValue($namespace, $key = '')
{
if ($key === '') {
$key = 'config/' . $namespace;
} else {
$key = 'config/' . $namespace . '/' . $key;
}
try {
$o = self::$_bucket->object($key);
return $o->downloadAsString();
} catch (NotFoundException $e) {
return '';
}
}
/**
* @inheritDoc
*/
@ -225,12 +320,12 @@ class GoogleCloudStorage extends AbstractData
$expired = array();
$now = time();
$prefix = $this->_prefix;
$prefix = self::$_prefix;
if ($prefix != '') {
$prefix = $prefix . '/';
$prefix .= '/';
}
try {
foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) {
foreach (self::$_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']);

View File

@ -52,6 +52,11 @@ class FormatV2
}
}
// Make sure adata is an array.
if (!is_array($message['adata'])) {
return false;
}
$cipherParams = $isComment ? $message['adata'] : $message['adata'][0];
// Make sure some fields are base64 data:

View File

@ -44,13 +44,13 @@ class Json
* @static
* @param string $input
* @throws Exception
* @return array
* @return mixed
*/
public static function decode($input)
{
$array = json_decode($input, true);
$output = json_decode($input, true);
self::_detectError();
return $array;
return $output;
}
/**

View File

@ -54,7 +54,7 @@ class Model
*/
public function getPaste($pasteId = null)
{
$paste = new Paste($this->_conf, $this->_getStore());
$paste = new Paste($this->_conf, $this->getStore());
if ($pasteId !== null) {
$paste->setId($pasteId);
}
@ -67,8 +67,9 @@ class Model
public function purge()
{
PurgeLimiter::setConfiguration($this->_conf);
PurgeLimiter::setStore($this->getStore());
if (PurgeLimiter::canPurge()) {
$this->_getStore()->purge($this->_conf->getKey('batchsize', 'purge'));
$this->getStore()->purge($this->_conf->getKey('batchsize', 'purge'));
}
}
@ -77,7 +78,7 @@ class Model
*
* @return Data\AbstractData
*/
private function _getStore()
public function getStore()
{
if ($this->_store === null) {
$this->_store = forward_static_call(

View File

@ -93,7 +93,7 @@ class Paste extends AbstractModel
}
$this->_data['meta']['created'] = time();
$this->_data['meta']['salt'] = serversalt::generate();
$this->_data['meta']['salt'] = ServerSalt::generate();
// store paste
if (

View File

@ -12,7 +12,7 @@
namespace PrivateBin\Persistence;
use Exception;
use PrivateBin\Data\AbstractData;
/**
* AbstractPersistence
@ -22,114 +22,23 @@ use Exception;
abstract class AbstractPersistence
{
/**
* path in which to persist something
* data storage to use to persist something
*
* @access private
* @static
* @var string
* @var AbstractData
*/
private static $_path = 'data';
protected static $_store;
/**
* set the path
*
* @access public
* @static
* @param string $path
* @param AbstractData $store
*/
public static function setPath($path)
public static function setStore(AbstractData $store)
{
self::$_path = $path;
}
/**
* get the path
*
* @access public
* @static
* @param string $filename
* @return string
*/
public static function getPath($filename = null)
{
if (strlen($filename)) {
return self::$_path . DIRECTORY_SEPARATOR . $filename;
} else {
return self::$_path;
}
}
/**
* checks if the file exists
*
* @access protected
* @static
* @param string $filename
* @return bool
*/
protected static function _exists($filename)
{
self::_initialize();
return is_file(self::$_path . DIRECTORY_SEPARATOR . $filename);
}
/**
* prepares path for storage
*
* @access protected
* @static
* @throws Exception
*/
protected static function _initialize()
{
// 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) {
throw new Exception('unable to write to file ' . $file, 11);
}
}
}
/**
* store the data
*
* @access protected
* @static
* @param string $filename
* @param string $data
* @throws Exception
* @return string
*/
protected static function _store($filename, $data)
{
self::_initialize();
$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)) {
throw new Exception('unable to write to file ' . $file, 13);
}
@chmod($file, 0640); // protect file access
return $file;
self::$_store = $store;
}
}

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);
}
}

View File

@ -52,7 +52,6 @@ class PurgeLimiter extends AbstractPersistence
public static function setConfiguration(Configuration $conf)
{
self::setLimit($conf->getKey('limit', 'purge'));
self::setPath($conf->getKey('dir', 'purge'));
}
/**
@ -60,7 +59,6 @@ class PurgeLimiter extends AbstractPersistence
*
* @access public
* @static
* @throws \Exception
* @return bool
*/
public static function canPurge()
@ -71,17 +69,14 @@ class PurgeLimiter extends AbstractPersistence
}
$now = time();
$file = 'purge_limiter.php';
if (self::_exists($file)) {
require self::getPath($file);
$pl = $GLOBALS['purge_limiter'];
if ($pl + self::$_limit >= $now) {
return false;
}
$pl = (int) self::$_store->getValue('purge_limiter');
if ($pl + self::$_limit >= $now) {
return false;
}
$content = '<?php' . PHP_EOL . '$GLOBALS[\'purge_limiter\'] = ' . $now . ';';
self::_store($file, $content);
return true;
$hasStored = self::$_store->setValue((string) $now, 'purge_limiter');
if (!$hasStored) {
error_log('failed to store the purge limiter, skipping purge cycle to avoid getting stuck in a purge loop');
}
return $hasStored;
}
}

View File

@ -12,7 +12,7 @@
namespace PrivateBin\Persistence;
use Exception;
use PrivateBin\Data\AbstractData;
/**
* ServerSalt
@ -26,15 +26,6 @@ use Exception;
*/
class ServerSalt extends AbstractPersistence
{
/**
* file where salt is saved to
*
* @access private
* @static
* @var string
*/
private static $_file = 'salt.php';
/**
* generated salt
*
@ -53,8 +44,7 @@ class ServerSalt extends AbstractPersistence
*/
public static function generate()
{
$randomSalt = bin2hex(random_bytes(256));
return $randomSalt;
return bin2hex(random_bytes(256));
}
/**
@ -62,7 +52,6 @@ class ServerSalt extends AbstractPersistence
*
* @access public
* @static
* @throws Exception
* @return string
*/
public static function get()
@ -71,20 +60,14 @@ class ServerSalt extends AbstractPersistence
return self::$_salt;
}
if (self::_exists(self::$_file)) {
if (is_readable(self::getPath(self::$_file))) {
$items = explode('|', file_get_contents(self::getPath(self::$_file)));
}
if (!isset($items) || !is_array($items) || count($items) != 3) {
throw new Exception('unable to read file ' . self::getPath(self::$_file), 20);
}
self::$_salt = $items[1];
$salt = self::$_store->getValue('salt');
if ($salt) {
self::$_salt = $salt;
} else {
self::$_salt = self::generate();
self::_store(
self::$_file,
'<?php # |' . self::$_salt . '|'
);
if (!self::$_store->setValue(self::$_salt, 'salt')) {
error_log('failed to store the server salt, delete tokens, traffic limiter and user icons won\'t work');
}
}
return self::$_salt;
}
@ -94,11 +77,11 @@ class ServerSalt extends AbstractPersistence
*
* @access public
* @static
* @param string $path
* @param AbstractData $store
*/
public static function setPath($path)
public static function setStore(AbstractData $store)
{
self::$_salt = '';
parent::setPath($path);
parent::setStore($store);
}
}

View File

@ -13,7 +13,6 @@
namespace PrivateBin\Persistence;
use Exception;
use IPLib\Factory;
use PrivateBin\Configuration;
@ -85,7 +84,6 @@ class TrafficLimiter extends AbstractPersistence
public static function setConfiguration(Configuration $conf)
{
self::setLimit($conf->getKey('limit', 'traffic'));
self::setPath($conf->getKey('dir', 'traffic'));
self::setExemptedIp($conf->getKey('exemptedIp', 'traffic'));
if (($option = $conf->getKey('header', 'traffic')) !== null) {
@ -134,13 +132,7 @@ class TrafficLimiter extends AbstractPersistence
return false;
}
// Ip-lib throws an exception when something goes wrong, if so we want to catch it and set contained to false
try {
return $address->matches($range);
} catch (Exception $e) {
// If something is wrong with matching the ip, we assume it doesn't match
return false;
}
return $address->matches($range);
}
/**
@ -150,7 +142,6 @@ class TrafficLimiter extends AbstractPersistence
*
* @access public
* @static
* @throws Exception
* @return bool
*/
public static function canPass()
@ -170,35 +161,20 @@ class TrafficLimiter extends AbstractPersistence
}
}
$file = 'traffic_limiter.php';
if (self::_exists($file)) {
require self::getPath($file);
$tl = $GLOBALS['traffic_limiter'];
} else {
$tl = array();
}
// purge file of expired hashes to keep it small
$now = time();
foreach ($tl as $key => $time) {
if ($time + self::$_limit < $now) {
unset($tl[$key]);
}
}
// this hash is used as an array key, hence a shorter algo is used
$hash = self::getHash('sha256');
if (array_key_exists($hash, $tl) && ($tl[$hash] + self::$_limit >= $now)) {
$now = time();
$tl = (int) self::$_store->getValue('traffic_limiter', $hash);
self::$_store->purgeValues('traffic_limiter', $now - self::$_limit);
if ($tl > 0 && ($tl + self::$_limit >= $now)) {
$result = false;
} else {
$tl[$hash] = time();
$result = true;
$tl = time();
$result = true;
}
if (!self::$_store->setValue((string) $tl, 'traffic_limiter', $hash)) {
error_log('failed to store the traffic limiter, it probably contains outdated information');
}
self::_store(
$file,
'<?php' . PHP_EOL .
'$GLOBALS[\'traffic_limiter\'] = ' . var_export($tl, true) . ';'
);
return $result;
}
}

View File

@ -108,7 +108,9 @@ class Request
case 'DELETE':
case 'PUT':
case 'POST':
$this->_params = Json::decode(
// it might be a creation or a deletion, the latter is detected below
$this->_operation = 'create';
$this->_params = Json::decode(
file_get_contents(self::$_inputStream)
);
break;
@ -125,15 +127,10 @@ class Request
}
// prepare operation, depending on current parameters
if (
array_key_exists('ct', $this->_params) &&
!empty($this->_params['ct'])
) {
$this->_operation = 'create';
} elseif (array_key_exists('pasteid', $this->_params) && !empty($this->_params['pasteid'])) {
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';
} else {
} elseif ($this->_operation != 'create') {
$this->_operation = 'read';
}
} elseif (array_key_exists('jsonld', $this->_params) && !empty($this->_params['jsonld'])) {
@ -172,7 +169,7 @@ class Request
$data['meta'] = $meta;
}
foreach ($required_keys as $key) {
$data[$key] = $this->getParam($key);
$data[$key] = $this->getParam($key, $key == 'v' ? 1 : '');
}
// forcing a cast to int or float
$data['v'] = $data['v'] + 0;

View File

@ -1,5 +1,11 @@
<?php
use Google\Cloud\Core\Exception\BadRequestException;
use Google\Cloud\Core\Exception\NotFoundException;
use Google\Cloud\Storage\Bucket;
use Google\Cloud\Storage\Connection\ConnectionInterface;
use Google\Cloud\Storage\StorageClient;
use Google\Cloud\Storage\StorageObject;
use PrivateBin\Persistence\ServerSalt;
error_reporting(E_ALL | E_STRICT);
@ -21,6 +27,586 @@ if (!defined('CONF_SAMPLE')) {
require PATH . 'vendor/autoload.php';
Helper::updateSubresourceIntegrity();
/**
* Class StorageClientStub provides a limited stub for performing the unit test
*/
class StorageClientStub extends StorageClient
{
private $_config = null;
private $_connection = null;
private $_buckets = array();
public function __construct(array $config = array())
{
$this->_config = $config;
$this->_connection = new ConnectionInterfaceStub();
}
public function bucket($name, $userProject = false)
{
if (!key_exists($name, $this->_buckets)) {
$b = new BucketStub($this->_connection, $name, array(), $this);
$this->_buckets[$name] = $b;
}
return $this->_buckets[$name];
}
/**
* @throws \Google\Cloud\Core\Exception\NotFoundException
*/
public function deleteBucket($name)
{
if (key_exists($name, $this->_buckets)) {
unset($this->_buckets[$name]);
} else {
throw new NotFoundException();
}
}
public function buckets(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function registerStreamWrapper($protocol = null)
{
throw new BadMethodCallException('not supported by this stub');
}
public function unregisterStreamWrapper($protocol = null)
{
throw new BadMethodCallException('not supported by this stub');
}
public function signedUrlUploader($uri, $data, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function timestamp(\DateTimeInterface $timestamp, $nanoSeconds = null)
{
throw new BadMethodCallException('not supported by this stub');
}
public function getServiceAccount(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function hmacKeys(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function hmacKey($accessId, $projectId = null, array $metadata = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function createHmacKey($serviceAccountEmail, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function createBucket($name, array $options = array())
{
if (key_exists($name, $this->_buckets)) {
throw new BadRequestException('already exists');
}
$b = new BucketStub($this->_connection, $name, array(), $this);
$this->_buckets[$name] = $b;
return $b;
}
}
/**
* Class BucketStub stubs a GCS bucket.
*/
class BucketStub extends Bucket
{
public $_objects;
private $_name;
private $_info;
private $_connection;
private $_client;
public function __construct(ConnectionInterface $connection, $name, array $info = array(), $client = null)
{
$this->_name = $name;
$this->_info = $info;
$this->_connection = $connection;
$this->_objects = array();
$this->_client = $client;
}
public function acl()
{
throw new BadMethodCallException('not supported by this stub');
}
public function defaultAcl()
{
throw new BadMethodCallException('not supported by this stub');
}
public function exists()
{
return true;
}
public function upload($data, array $options = array())
{
if (!is_string($data) || !key_exists('name', $options)) {
throw new BadMethodCallException('not supported by this stub');
}
$name = $options['name'];
$generation = '1';
$o = new StorageObjectStub($this->_connection, $name, $this, $generation, $options);
$this->_objects[$options['name']] = $o;
$o->setData($data);
}
public function uploadAsync($data, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getResumableUploader($data, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getStreamableUploader($data, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function object($name, array $options = array())
{
if (key_exists($name, $this->_objects)) {
return $this->_objects[$name];
} else {
return new StorageObjectStub($this->_connection, $name, $this, null, $options);
}
}
public function objects(array $options = array())
{
$prefix = key_exists('prefix', $options) ? $options['prefix'] : '';
return new CallbackFilterIterator(
new ArrayIterator($this->_objects),
function ($current, $key, $iterator) use ($prefix) {
return substr($key, 0, strlen($prefix)) == $prefix;
}
);
}
public function createNotification($topic, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function notification($id)
{
throw new BadMethodCallException('not supported by this stub');
}
public function notifications(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function delete(array $options = array())
{
$this->_client->deleteBucket($this->_name);
}
public function update(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function compose(array $sourceObjects, $name, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function info(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function reload(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function name()
{
return $this->_name;
}
public static function lifecycle(array $lifecycle = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function currentLifecycle(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function isWritable($file = null)
{
throw new BadMethodCallException('not supported by this stub');
}
public function iam()
{
throw new BadMethodCallException('not supported by this stub');
}
public function lockRetentionPolicy(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function signedUrl($expires, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function generateSignedPostPolicyV4($objectName, $expires, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
}
/**
* Class StorageObjectStub stubs a GCS storage object.
*/
class StorageObjectStub extends StorageObject
{
private $_name;
private $_data;
private $_info;
private $_bucket;
private $_generation;
private $_exists = false;
private $_connection;
public function __construct(ConnectionInterface $connection, $name, $bucket, $generation = null, array $info = array(), $encryptionKey = null, $encryptionKeySHA256 = null)
{
$this->_name = $name;
$this->_bucket = $bucket;
$this->_generation = $generation;
$this->_info = $info;
$this->_connection = $connection;
$timeCreated = new Datetime();
$this->_info['metadata']['timeCreated'] = $timeCreated->format('Y-m-d\TH:i:s.u\Z');
}
public function acl()
{
throw new BadMethodCallException('not supported by this stub');
}
public function exists(array $options = array())
{
return key_exists($this->_name, $this->_bucket->_objects);
}
/**
* @throws NotFoundException
*/
public function delete(array $options = array())
{
if (key_exists($this->_name, $this->_bucket->_objects)) {
unset($this->_bucket->_objects[$this->_name]);
} else {
throw new NotFoundException('key ' . $this->_name . ' not found.');
}
}
/**
* @throws NotFoundException
*/
public function update(array $metadata, array $options = array())
{
if (!$this->_exists) {
throw new NotFoundException('key ' . $this->_name . ' not found.');
}
$this->_info = $metadata;
}
public function copy($destination, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function rewrite($destination, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function rename($name, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
/**
* @throws NotFoundException
*/
public function downloadAsString(array $options = array())
{
if (!$this->_exists) {
throw new NotFoundException('key ' . $this->_name . ' not found.');
}
return $this->_data;
}
public function downloadToFile($path, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function downloadAsStream(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function downloadAsStreamAsync(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function signedUrl($expires, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function signedUploadUrl($expires, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function beginSignedUploadSession(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function info(array $options = array())
{
return key_exists('metadata',$this->_info) ? $this->_info['metadata'] : array();
}
public function reload(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function name()
{
return $this->_name;
}
public function identity()
{
throw new BadMethodCallException('not supported by this stub');
}
public function gcsUri()
{
return sprintf(
'gs://%s/%s',
$this->_bucket->name(),
$this->_name
);
}
public function setData($data)
{
$this->_data = $data;
$this->_exists = true;
}
}
/**
* Class ConnectionInterfaceStub required for the stubs.
*/
class ConnectionInterfaceStub implements ConnectionInterface
{
public function deleteAcl(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getAcl(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function listAcl(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function insertAcl(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function patchAcl(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function deleteBucket(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getBucket(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function listBuckets(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function insertBucket(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getBucketIamPolicy(array $args)
{
throw new BadMethodCallException('not supported by this stub');
}
public function setBucketIamPolicy(array $args)
{
throw new BadMethodCallException('not supported by this stub');
}
public function testBucketIamPermissions(array $args)
{
throw new BadMethodCallException('not supported by this stub');
}
public function patchBucket(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function deleteObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function copyObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function rewriteObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function composeObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function listObjects(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function patchObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function downloadObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function insertObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getNotification(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function deleteNotification(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function insertNotification(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function listNotifications(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getServiceAccount(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function lockRetentionPolicy(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function createHmacKey(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function deleteHmacKey(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getHmacKey(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function updateHmacKey(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function listHmacKeys(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
}
/**
* Class Helper provides unit tests pastes and comments of various formats
*/
class Helper
{
/**
@ -155,7 +741,11 @@ class Helper
public static function getPastePost($version = 2, array $meta = array())
{
$example = self::getPaste($version, $meta);
$example['meta'] = array('expire' => $example['meta']['expire']);
if ($version == 2) {
$example['meta'] = array('expire' => $example['meta']['expire']);
} else {
unset($example['meta']['postdate']);
}
return $example;
}

View File

@ -17,8 +17,6 @@ class ConfigurationTest extends PHPUnit_Framework_TestCase
$this->_minimalConfig = '[main]' . PHP_EOL . '[model]' . PHP_EOL . '[model_options]';
$this->_options = Configuration::getDefaults();
$this->_options['model_options']['dir'] = PATH . $this->_options['model_options']['dir'];
$this->_options['traffic']['dir'] = PATH . $this->_options['traffic']['dir'];
$this->_options['purge']['dir'] = PATH . $this->_options['purge']['dir'];
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_cfg';
if (!is_dir($this->_path)) {
mkdir($this->_path);

View File

@ -428,8 +428,6 @@ class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase
Helper::confBackup();
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
$this->_model = Filesystem::getInstance(array('dir' => $this->_path));
ServerSalt::setPath($this->_path);
TrafficLimiter::setPath($this->_path);
$this->reset();
}
@ -449,8 +447,6 @@ class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase
if ($this->_model->exists(Helper::getPasteId()))
$this->_model->delete(Helper::getPasteId());
$configuration['model_options']['dir'] = $this->_path;
$configuration['traffic']['dir'] = $this->_path;
$configuration['purge']['dir'] = $this->_path;
Helper::createIniFile(CONF, $configuration);
}

View File

@ -17,6 +17,8 @@ class ControllerTest extends PHPUnit_Framework_TestCase
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
$this->_data = Filesystem::getInstance(array('dir' => $this->_path));
ServerSalt::setStore($this->_data);
TrafficLimiter::setStore($this->_data);
$this->reset();
}
@ -37,11 +39,8 @@ class ControllerTest extends PHPUnit_Framework_TestCase
$this->_data->delete(Helper::getPasteId());
}
$options = parse_ini_file(CONF_SAMPLE, true);
$options['purge']['dir'] = $this->_path;
$options['traffic']['dir'] = $this->_path;
$options['model_options']['dir'] = $this->_path;
Helper::createIniFile(CONF, $options);
ServerSalt::setPath($this->_path);
}
/**
@ -49,6 +48,8 @@ class ControllerTest extends PHPUnit_Framework_TestCase
*/
public function testView()
{
$_SERVER['QUERY_STRING'] = Helper::getPasteId();
$_GET[Helper::getPasteId()] = '';
ob_start();
new Controller;
$content = ob_get_contents();
@ -127,28 +128,6 @@ class ControllerTest extends PHPUnit_Framework_TestCase
);
}
/**
* @runInSeparateProcess
*/
public function testHtaccess()
{
$htaccess = $this->_path . DIRECTORY_SEPARATOR . '.htaccess';
@unlink($htaccess);
$paste = Helper::getPasteJson();
$file = tempnam(sys_get_temp_dir(), 'FOO');
file_put_contents($file, $paste);
Request::setInputStream($file);
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['REMOTE_ADDR'] = '::1';
ob_start();
new Controller;
ob_end_clean();
$this->assertFileExists($htaccess, 'htaccess recreated');
}
/**
* @expectedException Exception
* @expectedExceptionCode 2
@ -493,6 +472,29 @@ class ControllerTest extends PHPUnit_Framework_TestCase
$this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
}
/**
* @runInSeparateProcess
*/
public function testCreateInvalidFormat()
{
$options = parse_ini_file(CONF, true);
$options['traffic']['limit'] = 0;
Helper::createIniFile(CONF, $options);
$file = tempnam(sys_get_temp_dir(), 'FOO');
file_put_contents($file, Helper::getPasteJson(1));
Request::setInputStream($file);
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['REMOTE_ADDR'] = '::1';
ob_start();
new Controller;
$content = ob_get_contents();
ob_end_clean();
$response = json_decode($content, true);
$this->assertEquals(1, $response['status'], 'outputs error status');
$this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
}
/**
* @runInSeparateProcess
*/
@ -541,7 +543,7 @@ class ControllerTest extends PHPUnit_Framework_TestCase
ob_end_clean();
$response = json_decode($content, true);
$this->assertEquals(1, $response['status'], 'outputs error status');
$this->assertFalse($this->_data->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'paste exists after posting data');
$this->assertFalse($this->_data->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment exists after posting data');
}
/**

View File

@ -1,6 +1,8 @@
<?php
use PrivateBin\Data\Database;
use PrivateBin\Persistence\ServerSalt;
use PrivateBin\Persistence\TrafficLimiter;
require_once 'ControllerTest.php';
@ -24,6 +26,8 @@ class ControllerWithDbTest extends ControllerTest
}
$this->_options['dsn'] = 'sqlite:' . $this->_path . DIRECTORY_SEPARATOR . 'tst.sq3';
$this->_data = Database::getInstance($this->_options);
ServerSalt::setStore($this->_data);
TrafficLimiter::setStore($this->_data);
$this->reset();
}

View File

@ -0,0 +1,59 @@
<?php
use Google\Auth\HttpHandler\HttpHandlerFactory;
use GuzzleHttp\Client;
use PrivateBin\Data\GoogleCloudStorage;
use PrivateBin\Persistence\ServerSalt;
use PrivateBin\Persistence\TrafficLimiter;
require_once 'ControllerTest.php';
class ControllerWithGcsTest extends ControllerTest
{
private static $_client;
private static $_bucket;
private $_options = array();
public static function setUpBeforeClass()
{
$httpClient = new Client(array('debug'=>false));
$handler = HttpHandlerFactory::build($httpClient);
$name = 'pb-';
$alphabet = 'abcdefghijklmnopqrstuvwxyz';
for ($i = 0; $i < 29; ++$i) {
$name .= $alphabet[rand(0, strlen($alphabet) - 1)];
}
self::$_client = new StorageClientStub(array());
self::$_bucket = self::$_client->createBucket($name);
}
public function setUp()
{
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
if (!is_dir($this->_path)) {
mkdir($this->_path);
}
$this->_options = array(
'bucket' => self::$_bucket->name(),
'prefix' => 'pastes',
);
$this->_data = GoogleCloudStorage::getInstance($this->_options);
ServerSalt::setStore($this->_data);
TrafficLimiter::setStore($this->_data);
$this->reset();
}
public function reset()
{
parent::reset();
// but then inject a db config
$options = parse_ini_file(CONF, true);
$options['model'] = array(
'class' => 'GoogleCloudStorage',
);
$options['model_options'] = $this->_options;
Helper::createIniFile(CONF, $options);
}
}

View File

@ -2,6 +2,8 @@
use PrivateBin\Controller;
use PrivateBin\Data\Database;
use PrivateBin\Data\Filesystem;
use PrivateBin\Persistence\ServerSalt;
class DatabaseTest extends PHPUnit_Framework_TestCase
{
@ -31,6 +33,19 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
}
}
public function testSaltMigration()
{
ServerSalt::setStore(Filesystem::getInstance(array('dir' => 'data')));
$salt = ServerSalt::get();
$file = 'data' . DIRECTORY_SEPARATOR . 'salt.php';
$this->assertFileExists($file, 'ServerSalt got initialized and stored on disk');
$this->assertNotEquals($salt, '');
ServerSalt::setStore($this->_model);
ServerSalt::get();
$this->assertFileNotExists($file, 'legacy ServerSalt got removed');
$this->assertEquals($salt, ServerSalt::get(), 'ServerSalt got preserved & migrated');
}
public function testDatabaseBasedDataStoreWorks()
{
$this->_model->delete(Helper::getPasteId());
@ -287,6 +302,48 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
Helper::rmDir($this->_path);
}
public function testCorruptMeta()
{
mkdir($this->_path);
$path = $this->_path . DIRECTORY_SEPARATOR . 'meta-test.sq3';
if (is_file($path)) {
unlink($path);
}
$this->_options['dsn'] = 'sqlite:' . $path;
$this->_options['tbl'] = 'baz_';
$model = Database::getInstance($this->_options);
$paste = Helper::getPaste(1, array('expire_date' => 1344803344));
unset($paste['meta']['formatter'], $paste['meta']['opendiscussion'], $paste['meta']['salt']);
$model->delete(Helper::getPasteId());
$db = new PDO(
$this->_options['dsn'],
$this->_options['usr'],
$this->_options['pwd'],
$this->_options['opt']
);
$statement = $db->prepare('INSERT INTO baz_paste VALUES(?,?,?,?,?,?,?,?,?)');
$statement->execute(
array(
Helper::getPasteId(),
$paste['data'],
$paste['meta']['postdate'],
$paste['meta']['expire_date'],
0,
0,
'{',
null,
null,
)
);
$statement->closeCursor();
$this->assertTrue($model->exists(Helper::getPasteId()), 'paste exists after storing it');
$this->assertEquals($paste, $model->read(Helper::getPasteId()));
Helper::rmDir($this->_path);
}
public function testTableUpgrade()
{
mkdir($this->_path);

View File

@ -117,6 +117,7 @@ class FilesystemTest extends PHPUnit_Framework_TestCase
$this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does not yet exist');
$this->assertFalse($this->_model->create(Helper::getPasteId(), $paste), 'unable to store broken paste');
$this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does still not exist');
$this->assertFalse($this->_model->setValue('foo', 'non existing namespace'), 'rejects setting value in non existing namespace');
}
public function testCommentErrorDetection()

View File

@ -1,12 +1,6 @@
<?php
use Google\Auth\HttpHandler\HttpHandlerFactory;
use Google\Cloud\Core\Exception\BadRequestException;
use Google\Cloud\Core\Exception\NotFoundException;
use Google\Cloud\Storage\Bucket;
use Google\Cloud\Storage\Connection\ConnectionInterface;
use Google\Cloud\Storage\StorageClient;
use Google\Cloud\Storage\StorageObject;
use GuzzleHttp\Client;
use PrivateBin\Data\GoogleCloudStorage;
@ -31,14 +25,11 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase
public function setUp()
{
// do not report E_NOTICE as fsouza/fake-gcs-server does not return a `generation` value in the response
// which the Google Cloud Storage PHP library expects.
error_reporting(E_ERROR | E_WARNING | E_PARSE);
ini_set('error_log', stream_get_meta_data(tmpfile())['uri']);
$this->_model = GoogleCloudStorage::getInstance(array(
'bucket' => self::$_bucket->name(),
'prefix' => 'pastes',
'client' => self::$_client, ));
));
}
public function tearDown()
@ -46,7 +37,6 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase
foreach (self::$_bucket->objects() as $object) {
$object->delete();
}
error_reporting(E_ALL);
}
public static function tearDownAfterClass()
@ -138,579 +128,55 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase
$this->assertFalse($this->_model->createComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId(), $comment), 'unable to store broken comment');
$this->assertFalse($this->_model->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment does still not exist');
}
}
/**
* Class StorageClientStub provides a limited stub for performing the unit test
*/
class StorageClientStub extends StorageClient
{
private $_config = null;
private $_connection = null;
private $_buckets = array();
public function __construct(array $config = array())
/**
* @throws Exception
*/
public function testKeyValueStore()
{
$this->_config = $config;
$this->_connection = new ConnectionInterfaceStub();
}
$salt = bin2hex(random_bytes(256));
$this->_model->setValue($salt, 'salt', '');
$storedSalt = $this->_model->getValue('salt', '');
$this->assertEquals($salt, $storedSalt);
$this->_model->purgeValues('salt', time() + 60);
$this->assertEquals('', $this->_model->getValue('salt', 'master'));
public function bucket($name, $userProject = false)
{
if (!key_exists($name, $this->_buckets)) {
$b = new BucketStub($this->_connection, $name, array(), $this);
$this->_buckets[$name] = $b;
}
return $this->_buckets[$name];
$client = hash_hmac('sha512', '127.0.0.1', $salt);
$expire = time();
$this->_model->setValue(strval($expire), 'traffic_limiter', $client);
$storedExpired = $this->_model->getValue('traffic_limiter', $client);
$this->assertEquals(strval($expire), $storedExpired);
$this->_model->purgeValues('traffic_limiter', time() - 60);
$this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client));
$this->_model->purgeValues('traffic_limiter', time() + 60);
$this->assertEquals('', $this->_model->getValue('traffic_limiter', $client));
$purgeAt = $expire + (15 * 60);
$this->_model->setValue(strval($purgeAt), 'purge_limiter', '');
$storedPurgedAt = $this->_model->getValue('purge_limiter', '');
$this->assertEquals(strval($purgeAt), $storedPurgedAt);
$this->_model->purgeValues('purge_limiter', $purgeAt + 60);
$this->assertEquals('', $this->_model->getValue('purge_limiter', ''));
$this->assertEquals('', $this->_model->getValue('purge_limiter', 'at'));
}
/**
* @throws \Google\Cloud\Core\Exception\NotFoundException
* @throws Exception
*/
public function deleteBucket($name)
public function testKeyValuePurgeTrafficLimiter()
{
if (key_exists($name, $this->_buckets)) {
unset($this->_buckets[$name]);
} else {
throw new NotFoundException();
}
}
$salt = bin2hex(random_bytes(256));
$client = hash_hmac('sha512', '127.0.0.1', $salt);
$expire = time();
$this->_model->setValue(strval($expire), 'traffic_limiter', $client);
$storedExpired = $this->_model->getValue('traffic_limiter', $client);
$this->assertEquals(strval($expire), $storedExpired);
public function buckets(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
$this->_model->purgeValues('traffic_limiter', time() - 60);
$this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client));
public function registerStreamWrapper($protocol = null)
{
throw new BadMethodCallException('not supported by this stub');
}
public function unregisterStreamWrapper($protocol = null)
{
throw new BadMethodCallException('not supported by this stub');
}
public function signedUrlUploader($uri, $data, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function timestamp(\DateTimeInterface $timestamp, $nanoSeconds = null)
{
throw new BadMethodCallException('not supported by this stub');
}
public function getServiceAccount(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function hmacKeys(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function hmacKey($accessId, $projectId = null, array $metadata = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function createHmacKey($serviceAccountEmail, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function createBucket($name, array $options = array())
{
if (key_exists($name, $this->_buckets)) {
throw new BadRequestException('already exists');
}
$b = new BucketStub($this->_connection, $name, array(), $this);
$this->_buckets[$name] = $b;
return $b;
}
}
/**
* Class BucketStub stubs a GCS bucket.
*/
class BucketStub extends Bucket
{
public $_objects;
private $_name;
private $_info;
private $_connection;
private $_client;
public function __construct(ConnectionInterface $connection, $name, array $info = array(), $client = null)
{
$this->_name = $name;
$this->_info = $info;
$this->_connection = $connection;
$this->_objects = array();
$this->_client = $client;
}
public function acl()
{
throw new BadMethodCallException('not supported by this stub');
}
public function defaultAcl()
{
throw new BadMethodCallException('not supported by this stub');
}
public function exists()
{
return true;
}
public function upload($data, array $options = array())
{
if (!is_string($data) || !key_exists('name', $options)) {
throw new BadMethodCallException('not supported by this stub');
}
$name = $options['name'];
$generation = '1';
$o = new StorageObjectStub($this->_connection, $name, $this, $generation, $options);
$this->_objects[$options['name']] = $o;
$o->setData($data);
}
public function uploadAsync($data, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getResumableUploader($data, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getStreamableUploader($data, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function object($name, array $options = array())
{
if (key_exists($name, $this->_objects)) {
return $this->_objects[$name];
} else {
return new StorageObjectStub($this->_connection, $name, $this, null, $options);
}
}
public function objects(array $options = array())
{
$prefix = key_exists('prefix', $options) ? $options['prefix'] : '';
return new CallbackFilterIterator(
new ArrayIterator($this->_objects),
function ($current, $key, $iterator) use ($prefix) {
return substr($key, 0, strlen($prefix)) == $prefix;
}
);
}
public function createNotification($topic, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function notification($id)
{
throw new BadMethodCallException('not supported by this stub');
}
public function notifications(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function delete(array $options = array())
{
$this->_client->deleteBucket($this->_name);
}
public function update(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function compose(array $sourceObjects, $name, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function info(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function reload(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function name()
{
return $this->_name;
}
public static function lifecycle(array $lifecycle = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function currentLifecycle(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function isWritable($file = null)
{
throw new BadMethodCallException('not supported by this stub');
}
public function iam()
{
throw new BadMethodCallException('not supported by this stub');
}
public function lockRetentionPolicy(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function signedUrl($expires, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function generateSignedPostPolicyV4($objectName, $expires, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
}
/**
* Class StorageObjectStub stubs a GCS storage object.
*/
class StorageObjectStub extends StorageObject
{
private $_name;
private $_data;
private $_info;
private $_bucket;
private $_generation;
private $_exists = false;
private $_connection;
public function __construct(ConnectionInterface $connection, $name, $bucket, $generation = null, array $info = array(), $encryptionKey = null, $encryptionKeySHA256 = null)
{
$this->_name = $name;
$this->_bucket = $bucket;
$this->_generation = $generation;
$this->_info = $info;
$this->_connection = $connection;
}
public function acl()
{
throw new BadMethodCallException('not supported by this stub');
}
public function exists(array $options = array())
{
return key_exists($this->_name, $this->_bucket->_objects);
}
/**
* @throws NotFoundException
*/
public function delete(array $options = array())
{
if (key_exists($this->_name, $this->_bucket->_objects)) {
unset($this->_bucket->_objects[$this->_name]);
} else {
throw new NotFoundException('key ' . $this->_name . ' not found.');
}
}
/**
* @throws NotFoundException
*/
public function update(array $metadata, array $options = array())
{
if (!$this->_exists) {
throw new NotFoundException('key ' . $this->_name . ' not found.');
}
$this->_info = $metadata;
}
public function copy($destination, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function rewrite($destination, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function rename($name, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
/**
* @throws NotFoundException
*/
public function downloadAsString(array $options = array())
{
if (!$this->_exists) {
throw new NotFoundException('key ' . $this->_name . ' not found.');
}
return $this->_data;
}
public function downloadToFile($path, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function downloadAsStream(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function downloadAsStreamAsync(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function signedUrl($expires, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function signedUploadUrl($expires, array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function beginSignedUploadSession(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function info(array $options = array())
{
return key_exists('metadata',$this->_info) ? $this->_info['metadata'] : array();
}
public function reload(array $options = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function name()
{
return $this->_name;
}
public function identity()
{
throw new BadMethodCallException('not supported by this stub');
}
public function gcsUri()
{
return sprintf(
'gs://%s/%s',
$this->_bucket->name(),
$this->_name
);
}
public function setData($data)
{
$this->_data = $data;
$this->_exists = true;
}
}
/**
* Class ConnectionInterfaceStub required for the stubs.
*/
class ConnectionInterfaceStub implements ConnectionInterface
{
public function deleteAcl(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getAcl(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function listAcl(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function insertAcl(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function patchAcl(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function deleteBucket(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getBucket(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function listBuckets(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function insertBucket(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getBucketIamPolicy(array $args)
{
throw new BadMethodCallException('not supported by this stub');
}
public function setBucketIamPolicy(array $args)
{
throw new BadMethodCallException('not supported by this stub');
}
public function testBucketIamPermissions(array $args)
{
throw new BadMethodCallException('not supported by this stub');
}
public function patchBucket(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function deleteObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function copyObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function rewriteObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function composeObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function listObjects(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function patchObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function downloadObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function insertObject(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getNotification(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function deleteNotification(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function insertNotification(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function listNotifications(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getServiceAccount(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function lockRetentionPolicy(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function createHmacKey(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function deleteHmacKey(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function getHmacKey(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function updateHmacKey(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
}
public function listHmacKeys(array $args = array())
{
throw new BadMethodCallException('not supported by this stub');
$this->_model->purgeValues('traffic_limiter', time() + 60);
$this->assertEquals('', $this->_model->getValue('traffic_limiter', $client));
}
}

View File

@ -16,7 +16,7 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
$this->_model = Filesystem::getInstance(array('dir' => $this->_path));
ServerSalt::setPath($this->_path);
ServerSalt::setStore($this->_model);
$_POST = array();
$_GET = array();
@ -25,8 +25,6 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
$this->_model->delete(Helper::getPasteId());
}
$options = parse_ini_file(CONF_SAMPLE, true);
$options['purge']['dir'] = $this->_path;
$options['traffic']['dir'] = $this->_path;
$options['model_options']['dir'] = $this->_path;
Helper::confBackup();
Helper::createIniFile(CONF, $options);

View File

@ -25,7 +25,6 @@ class ModelTest extends PHPUnit_Framework_TestCase
if (!is_dir($this->_path)) {
mkdir($this->_path);
}
ServerSalt::setPath($this->_path);
$options = parse_ini_file(CONF_SAMPLE, true);
$options['purge']['limit'] = 0;
$options['model'] = array(
@ -39,6 +38,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
);
Helper::confBackup();
Helper::createIniFile(CONF, $options);
ServerSalt::setStore(Database::getInstance($options['model_options']));
$this->_conf = new Configuration;
$this->_model = new Model($this->_conf);
$_SERVER['REMOTE_ADDR'] = '::1';
@ -102,6 +102,58 @@ class ModelTest extends PHPUnit_Framework_TestCase
$this->assertEquals(array(), $paste->getComments(), 'comment was deleted with paste');
}
public function testPasteV1()
{
$pasteData = Helper::getPaste(1);
unset($pasteData['meta']['formatter']);
$path = $this->_path . DIRECTORY_SEPARATOR . 'v1-test.sq3';
if (is_file($path)) {
unlink($path);
}
$options = parse_ini_file(CONF_SAMPLE, true);
$options['purge']['limit'] = 0;
$options['model'] = array(
'class' => 'Database',
);
$options['model_options'] = array(
'dsn' => 'sqlite:' . $path,
'usr' => null,
'pwd' => null,
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
);
Helper::createIniFile(CONF, $options);
$model = new Model(new Configuration);
$model->getPaste('0000000000000000')->exists(); // triggers database table creation
$model->getPaste(Helper::getPasteId())->delete(); // deletes the cache
$db = new PDO(
$options['model_options']['dsn'],
$options['model_options']['usr'],
$options['model_options']['pwd'],
$options['model_options']['opt']
);
$statement = $db->prepare('INSERT INTO paste VALUES(?,?,?,?,?,?,?,?,?)');
$statement->execute(
array(
Helper::getPasteId(),
$pasteData['data'],
$pasteData['meta']['postdate'],
0,
0,
0,
json_encode($pasteData['meta']),
null,
null,
)
);
$statement->closeCursor();
$paste = $model->getPaste(Helper::getPasteId());
$this->assertNotEmpty($paste->getDeleteToken(), 'excercise the condition to load the data from storage');
$this->assertEquals('plaintext', $paste->get()['meta']['formatter'], 'paste got created with default formatter');
}
public function testCommentDefaults()
{
$comment = new Comment(
@ -133,6 +185,97 @@ class ModelTest extends PHPUnit_Framework_TestCase
$paste->store();
}
/**
* @expectedException Exception
* @expectedExceptionCode 76
*/
public function testStoreFail()
{
$path = $this->_path . DIRECTORY_SEPARATOR . 'model-store-test.sq3';
if (is_file($path)) {
unlink($path);
}
$options = parse_ini_file(CONF_SAMPLE, true);
$options['purge']['limit'] = 0;
$options['model'] = array(
'class' => 'Database',
);
$options['model_options'] = array(
'dsn' => 'sqlite:' . $path,
'usr' => null,
'pwd' => null,
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
);
Helper::createIniFile(CONF, $options);
$model = new Model(new Configuration);
$pasteData = Helper::getPastePost();
$model->getPaste(Helper::getPasteId())->delete();
$model->getPaste(Helper::getPasteId())->exists();
$db = new PDO(
$options['model_options']['dsn'],
$options['model_options']['usr'],
$options['model_options']['pwd'],
$options['model_options']['opt']
);
$statement = $db->prepare('DROP TABLE paste');
$statement->execute();
$statement->closeCursor();
$paste = $model->getPaste();
$paste->setData($pasteData);
$paste->store();
}
/**
* @expectedException Exception
* @expectedExceptionCode 70
*/
public function testCommentStoreFail()
{
$path = $this->_path . DIRECTORY_SEPARATOR . 'model-test.sq3';
if (is_file($path)) {
unlink($path);
}
$options = parse_ini_file(CONF_SAMPLE, true);
$options['purge']['limit'] = 0;
$options['model'] = array(
'class' => 'Database',
);
$options['model_options'] = array(
'dsn' => 'sqlite:' . $path,
'usr' => null,
'pwd' => null,
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
);
Helper::createIniFile(CONF, $options);
$model = new Model(new Configuration);
$pasteData = Helper::getPastePost();
$commentData = Helper::getCommentPost();
$model->getPaste(Helper::getPasteId())->delete();
$paste = $model->getPaste();
$paste->setData($pasteData);
$paste->store();
$paste->exists();
$db = new PDO(
$options['model_options']['dsn'],
$options['model_options']['usr'],
$options['model_options']['pwd'],
$options['model_options']['opt']
);
$statement = $db->prepare('DROP TABLE comment');
$statement->execute();
$statement->closeCursor();
$comment = $paste->getComment(Helper::getPasteId());
$comment->setData($commentData);
$comment->store();
}
/**
* @expectedException Exception
* @expectedExceptionCode 69
@ -195,6 +338,18 @@ class ModelTest extends PHPUnit_Framework_TestCase
$paste->get();
}
/**
* @expectedException Exception
* @expectedExceptionCode 75
*/
public function testInvalidPasteFormat()
{
$pasteData = Helper::getPastePost();
$pasteData['adata'][1] = 'format does not exist';
$paste = $this->_model->getPaste();
$paste->setData($pasteData);
}
/**
* @expectedException Exception
* @expectedExceptionCode 60

View File

@ -1,5 +1,6 @@
<?php
use PrivateBin\Data\Filesystem;
use PrivateBin\Persistence\PurgeLimiter;
class PurgeLimiterTest extends PHPUnit_Framework_TestCase
@ -13,7 +14,9 @@ class PurgeLimiterTest extends PHPUnit_Framework_TestCase
if (!is_dir($this->_path)) {
mkdir($this->_path);
}
PurgeLimiter::setPath($this->_path);
PurgeLimiter::setStore(
Filesystem::getInstance(array('dir' => $this->_path))
);
}
public function tearDown()

View File

@ -1,5 +1,6 @@
<?php
use PrivateBin\Data\Filesystem;
use PrivateBin\Persistence\ServerSalt;
class ServerSaltTest extends PHPUnit_Framework_TestCase
@ -19,7 +20,9 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase
if (!is_dir($this->_path)) {
mkdir($this->_path);
}
ServerSalt::setPath($this->_path);
ServerSalt::setStore(
Filesystem::getInstance(array('dir' => $this->_path))
);
$this->_otherPath = $this->_path . DIRECTORY_SEPARATOR . 'foo';
@ -40,46 +43,46 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase
public function testGeneration()
{
// generating new salt
ServerSalt::setPath($this->_path);
ServerSalt::setStore(
Filesystem::getInstance(array('dir' => $this->_path))
);
$salt = ServerSalt::get();
// try setting a different path and resetting it
ServerSalt::setPath($this->_otherPath);
ServerSalt::setStore(
Filesystem::getInstance(array('dir' => $this->_otherPath))
);
$this->assertNotEquals($salt, ServerSalt::get());
ServerSalt::setPath($this->_path);
ServerSalt::setStore(
Filesystem::getInstance(array('dir' => $this->_path))
);
$this->assertEquals($salt, ServerSalt::get());
}
/**
* @expectedException Exception
* @expectedExceptionCode 11
*/
public function testPathShenanigans()
{
// try setting an invalid path
chmod($this->_invalidPath, 0000);
ServerSalt::setPath($this->_invalidPath);
ServerSalt::get();
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
ServerSalt::setStore($store);
$salt = ServerSalt::get();
ServerSalt::setStore($store);
$this->assertNotEquals($salt, ServerSalt::get());
}
/**
* @expectedException Exception
* @expectedExceptionCode 20
*/
public function testFileRead()
{
// try setting an invalid file
chmod($this->_invalidPath, 0700);
file_put_contents($this->_invalidFile, '');
chmod($this->_invalidFile, 0000);
ServerSalt::setPath($this->_invalidPath);
ServerSalt::get();
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
ServerSalt::setStore($store);
$salt = ServerSalt::get();
ServerSalt::setStore($store);
$this->assertNotEquals($salt, ServerSalt::get());
}
/**
* @expectedException Exception
* @expectedExceptionCode 13
*/
public function testFileWrite()
{
// try setting an invalid file
@ -90,19 +93,24 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase
}
file_put_contents($this->_invalidPath . DIRECTORY_SEPARATOR . '.htaccess', '');
chmod($this->_invalidPath, 0500);
ServerSalt::setPath($this->_invalidPath);
ServerSalt::get();
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
ServerSalt::setStore($store);
$salt = ServerSalt::get();
ServerSalt::setStore($store);
$this->assertNotEquals($salt, ServerSalt::get());
}
/**
* @expectedException Exception
* @expectedExceptionCode 10
*/
public function testPermissionShenanigans()
{
// try creating an invalid path
chmod($this->_invalidPath, 0000);
ServerSalt::setPath($this->_invalidPath . DIRECTORY_SEPARATOR . 'baz');
ServerSalt::get();
ServerSalt::setStore(
Filesystem::getInstance(array('dir' => $this->_invalidPath . DIRECTORY_SEPARATOR . 'baz'))
);
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
ServerSalt::setStore($store);
$salt = ServerSalt::get();
ServerSalt::setStore($store);
$this->assertNotEquals($salt, ServerSalt::get());
}
}

View File

@ -1,5 +1,7 @@
<?php
use PrivateBin\Data\Filesystem;
use PrivateBin\Persistence\ServerSalt;
use PrivateBin\Persistence\TrafficLimiter;
class TrafficLimiterTest extends PHPUnit_Framework_TestCase
@ -10,7 +12,9 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase
{
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'trafficlimit';
TrafficLimiter::setPath($this->_path);
$store = Filesystem::getInstance(array('dir' => $this->_path));
ServerSalt::setStore($store);
TrafficLimiter::setStore($store);
}
public function tearDown()
@ -19,11 +23,17 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase
Helper::rmDir($this->_path . DIRECTORY_SEPARATOR);
}
public function testHtaccess()
{
$htaccess = $this->_path . DIRECTORY_SEPARATOR . '.htaccess';
@unlink($htaccess);
$_SERVER['REMOTE_ADDR'] = 'foobar';
TrafficLimiter::canPass();
$this->assertFileExists($htaccess, 'htaccess recreated');
}
public function testTrafficGetsLimited()
{
$this->assertEquals($this->_path, TrafficLimiter::getPath());
$file = 'baz';
$this->assertEquals($this->_path . DIRECTORY_SEPARATOR . $file, TrafficLimiter::getPath($file));
TrafficLimiter::setLimit(4);
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$this->assertTrue(TrafficLimiter::canPass(), 'first request may pass');

View File

@ -1,5 +1,6 @@
<?php
use PrivateBin\Data\Filesystem;
use PrivateBin\Persistence\ServerSalt;
use PrivateBin\Vizhash16x16;
@ -17,7 +18,7 @@ class Vizhash16x16Test extends PHPUnit_Framework_TestCase
mkdir($this->_path);
}
$this->_file = $this->_path . DIRECTORY_SEPARATOR . 'vizhash.png';
ServerSalt::setPath($this->_path);
ServerSalt::setStore(Filesystem::getInstance(array('dir' => $this->_path)));
}
public function tearDown()