diff --git a/lib/Controller.php b/lib/Controller.php index 4a1aa0d..fb919ca 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -195,22 +195,17 @@ class Controller */ private function _create() { - try { - // 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; - } - } 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; } diff --git a/lib/Data/Database.php b/lib/Data/Database.php index ae4da19..a15b862 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -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; + } } /** @@ -348,19 +352,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,11 +424,15 @@ 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; + } } /** diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index 6bbea60..2066769 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -34,12 +34,9 @@ class GoogleCloudStorage extends AbstractData if (is_array($options) && array_key_exists('prefix', $options)) { $prefix = $options['prefix']; } - if (is_array($options) && array_key_exists('client', $options)) { - $client = $options['client']; - } if (!(self::$_instance instanceof self)) { - self::$_instance = new self($bucket, $prefix, $client); + self::$_instance = new self($bucket, $prefix); } return self::$_instance; } @@ -48,16 +45,12 @@ class GoogleCloudStorage extends AbstractData protected $_bucket = null; protected $_prefix = 'pastes'; - public function __construct($bucket, $prefix, $client = null) + public function __construct($bucket, $prefix) { parent::__construct(); - if ($client == null) { - $this->_client = new StorageClient(array('suppressKeyFileNotice' => true)); - } else { - // use given client for test purposes - $this->_client = $client; - } - + $this->_client = class_exists('StorageClientStub', false) ? + new \StorageClientStub(array()) : + new StorageClient(array('suppressKeyFileNotice' => true)); $this->_bucket = $this->_client->bucket($bucket); if ($prefix != null) { $this->_prefix = $prefix; diff --git a/lib/FormatV2.php b/lib/FormatV2.php index a06aa5d..d2055f3 100644 --- a/lib/FormatV2.php +++ b/lib/FormatV2.php @@ -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: diff --git a/lib/Persistence/PurgeLimiter.php b/lib/Persistence/PurgeLimiter.php index 6b60817..89d5d60 100644 --- a/lib/Persistence/PurgeLimiter.php +++ b/lib/Persistence/PurgeLimiter.php @@ -59,7 +59,6 @@ class PurgeLimiter extends AbstractPersistence * * @access public * @static - * @throws \Exception * @return bool */ public static function canPurge() diff --git a/lib/Persistence/ServerSalt.php b/lib/Persistence/ServerSalt.php index 93f5486..14f7bd1 100644 --- a/lib/Persistence/ServerSalt.php +++ b/lib/Persistence/ServerSalt.php @@ -12,7 +12,6 @@ namespace PrivateBin\Persistence; -use Exception; use PrivateBin\Data\AbstractData; /** @@ -54,8 +53,7 @@ class ServerSalt extends AbstractPersistence */ public static function generate() { - $randomSalt = bin2hex(random_bytes(256)); - return $randomSalt; + return bin2hex(random_bytes(256)); } /** @@ -63,7 +61,6 @@ class ServerSalt extends AbstractPersistence * * @access public * @static - * @throws Exception * @return string */ public static function get() diff --git a/lib/Request.php b/lib/Request.php index 5776cab..df517bb 100644 --- a/lib/Request.php +++ b/lib/Request.php @@ -108,6 +108,8 @@ class Request case 'DELETE': case 'PUT': case 'POST': + // 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) ); @@ -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 { + } else if ($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; diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index b539351..bdf8a38 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -1,5 +1,14 @@ _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(GoogleCloudStorage::DATETIME_FORMAT); + } + + 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 +744,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; } diff --git a/tst/ControllerTest.php b/tst/ControllerTest.php index 0aa7d79..5c95127 100644 --- a/tst/ControllerTest.php +++ b/tst/ControllerTest.php @@ -48,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(); @@ -470,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 */ @@ -518,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'); } /** diff --git a/tst/ControllerWithGcsTest.php b/tst/ControllerWithGcsTest.php new file mode 100644 index 0000000..3983341 --- /dev/null +++ b/tst/ControllerWithGcsTest.php @@ -0,0 +1,59 @@ +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); + } +} diff --git a/tst/Data/DatabaseTest.php b/tst/Data/DatabaseTest.php index f9cd265..1cfc0be 100644 --- a/tst/Data/DatabaseTest.php +++ b/tst/Data/DatabaseTest.php @@ -302,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); diff --git a/tst/Data/FilesystemTest.php b/tst/Data/FilesystemTest.php index 37e03f3..684c294 100644 --- a/tst/Data/FilesystemTest.php +++ b/tst/Data/FilesystemTest.php @@ -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() diff --git a/tst/Data/GoogleCloudStorageTest.php b/tst/Data/GoogleCloudStorageTest.php index 7945e2b..557984e 100644 --- a/tst/Data/GoogleCloudStorageTest.php +++ b/tst/Data/GoogleCloudStorageTest.php @@ -1,12 +1,6 @@ _model = GoogleCloudStorage::getInstance(array( 'bucket' => self::$_bucket->name(), 'prefix' => 'pastes', - 'client' => self::$_client, )); + )); } public function tearDown() @@ -201,580 +195,3 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase $this->assertFalse($this->_model->getValue('traffic_limiter', $client)); } } - -/** - * 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(GoogleCloudStorage::DATETIME_FORMAT); - } - - 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'); - } -} diff --git a/tst/ModelTest.php b/tst/ModelTest.php index 478d4c1..fb82442 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -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()); + $paste->getDeleteToken(); + $this->assertEquals('plaintext', $paste->get()['meta']['formatter'], 'paste got created with default formatter'); + } + public function testCommentDefaults() { $comment = new Comment( @@ -133,6 +185,96 @@ 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(); + + $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 +337,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