Ver Fonte

finishing changes in models, removing last md5 test cases, tightening up allowed POST data

El RIDO há 7 anos atrás
pai
commit
76dc01b959
7 ficheiros alterados com 211 adições e 314 exclusões
  1. 12 1
      lib/FormatV2.php
  2. 36 13
      lib/Model/AbstractModel.php
  3. 19 47
      lib/Model/Comment.php
  4. 60 130
      lib/Model/Paste.php
  5. 16 8
      tst/Bootstrap.php
  6. 1 1
      tst/Data/FilesystemTest.php
  7. 67 114
      tst/ModelTest.php

+ 12 - 1
lib/FormatV2.php

@@ -32,10 +32,12 @@ class FormatV2
      */
     public static function isValid($message, $isComment = false)
     {
-        $required_keys = array('adata', 'meta', 'v', 'ct');
+        $required_keys = array('adata', 'v', 'ct');
         if ($isComment) {
             $required_keys[] = 'pasteid';
             $required_keys[] = 'parentid';
+        } else {
+            $required_keys[] = 'meta';
         }
 
         // Make sure no additionnal keys were added.
@@ -109,6 +111,15 @@ class FormatV2
             return false;
         }
 
+        // require only the key 'expire' in the metadata of pastes
+        if (!$isComment && (
+            count($message['meta']) === 0 ||
+            !array_key_exists('expire', $message['meta']) ||
+            count($message['meta']) > 1
+        )) {
+            return false;
+        }
+
         return true;
     }
 }

+ 36 - 13
lib/Model/AbstractModel.php

@@ -16,7 +16,6 @@ use Exception;
 use PrivateBin\Configuration;
 use PrivateBin\Data\AbstractData;
 use PrivateBin\FormatV2;
-use stdClass;
 
 /**
  * AbstractModel
@@ -37,9 +36,9 @@ abstract class AbstractModel
      * Instance data.
      *
      * @access protected
-     * @var stdClass
+     * @var array
      */
-    protected $_data;
+    protected $_data = array('meta' => array());
 
     /**
      * Configuration.
@@ -68,8 +67,6 @@ abstract class AbstractModel
     {
         $this->_conf       = $configuration;
         $this->_store      = $storage;
-        $this->_data       = new stdClass;
-        $this->_data->meta = new stdClass;
     }
 
     /**
@@ -90,7 +87,7 @@ abstract class AbstractModel
      * @param string $id
      * @throws Exception
      */
-    public function setId($id)
+    public function setId(string $id)
     {
         if (!self::isValidId($id)) {
             throw new Exception('Invalid paste ID.', 60);
@@ -102,15 +99,17 @@ abstract class AbstractModel
      * Set data and recalculate ID.
      *
      * @access public
-     * @param string $data
+     * @param  array $data
      * @throws Exception
      */
-    public function setData($data)
+    public function setData(array $data)
     {
-        if (!FormatV2::isValid($data)) {
+        if (!FormatV2::isValid($data, $this instanceof Comment)) {
             throw new Exception('Invalid data.', 61);
         }
-        $this->_data->data = $data;
+        $data = $this->_sanitize($data);
+        $this->_validate($data);
+        $this->_data = $data;
 
         // calculate a 64 bit checksum to avoid collisions
         $this->setId(hash('fnv1a64', $data['ct']));
@@ -120,9 +119,12 @@ abstract class AbstractModel
      * Get instance data.
      *
      * @access public
-     * @return stdClass
+     * @return array
      */
-    abstract public function get();
+    public function get()
+    {
+        return $this->_data;
+    }
 
     /**
      * Store the instance's data.
@@ -156,8 +158,29 @@ abstract class AbstractModel
      * @param  string $id
      * @return bool
      */
-    public static function isValidId($id)
+    public static function isValidId(string $id)
     {
         return (bool) preg_match('#\A[a-f\d]{16}\z#', (string) $id);
     }
+
+    /**
+     * Sanitizes data to conform with current configuration.
+     *
+     * @access protected
+     * @param  array $data
+     * @return array
+     */
+    abstract protected function _sanitize(array $data);
+
+    /**
+     * Validate data.
+     *
+     * @access protected
+     * @param  array $data
+     * @throws Exception
+     */
+    protected function _validate(array $data)
+    {
+        return;
+    }
 }

+ 19 - 47
lib/Model/Comment.php

@@ -15,7 +15,6 @@ namespace PrivateBin\Model;
 use Exception;
 use Identicon\Identicon;
 use PrivateBin\Persistence\TrafficLimiter;
-use PrivateBin\FormatV2;
 use PrivateBin\Vizhash16x16;
 
 /**
@@ -33,29 +32,6 @@ class Comment extends AbstractModel
      */
     private $_paste;
 
-    /**
-     * Get comment data.
-     *
-     * @access public
-     * @throws Exception
-     * @return \stdClass
-     */
-    public function get()
-    {
-        // @todo add support to read specific comment
-        $comments = $this->_store->readComments($this->getPaste()->getId());
-        foreach ($comments as $comment) {
-            if (
-                $comment->parentid == $this->getParentId() &&
-                $comment->id == $this->getId()
-            ) {
-                $this->_data = $comment;
-                break;
-            }
-        }
-        return $this->_data;
-    }
-
     /**
      * Store the comment's data.
      *
@@ -80,7 +56,7 @@ class Comment extends AbstractModel
             throw new Exception('You are unlucky. Try again.', 69);
         }
 
-        $this->_data->meta->postdate = time();
+        $this->_data['meta']['created'] = time();
 
         // store comment
         if (
@@ -88,7 +64,7 @@ class Comment extends AbstractModel
                 $pasteid,
                 $this->getParentId(),
                 $this->getId(),
-                json_decode(json_encode($this->_data), true)
+                $this->_data
             ) === false
         ) {
             throw new Exception('Error saving comment. Sorry.', 70);
@@ -130,8 +106,8 @@ class Comment extends AbstractModel
      */
     public function setPaste(Paste $paste)
     {
-        $this->_paste               = $paste;
-        $this->_data->meta->pasteid = $paste->getId();
+        $this->_paste           = $paste;
+        $this->_data['pasteid'] = $paste->getId();
     }
 
     /**
@@ -157,7 +133,7 @@ class Comment extends AbstractModel
         if (!self::isValidId($id)) {
             throw new Exception('Invalid paste ID.', 65);
         }
-        $this->_data->meta->parentid = $id;
+        $this->_data['parentid'] = $id;
     }
 
     /**
@@ -168,29 +144,22 @@ class Comment extends AbstractModel
      */
     public function getParentId()
     {
-        if (!property_exists($this->_data->meta, 'parentid')) {
-            $this->_data->meta->parentid = '';
+        if (!array_key_exists('parentid', $this->_data)) {
+            $this->_data['parentid'] = '';
         }
-        return $this->_data->meta->parentid;
+        return $this->_data['parentid'];
     }
 
     /**
-     * Set nickname.
+     * Sanitizes data to conform with current configuration.
      *
-     * @access public
-     * @param string $nickname
-     * @throws Exception
+     * @access protected
+     * @param  array $data
+     * @return array
      */
-    public function setNickname($nickname)
+    protected function _sanitize(array $data)
     {
-        if (!FormatV2::isValid($nickname)) {
-            throw new Exception('Invalid data.', 66);
-        }
-        $this->_data->meta->nickname = $nickname;
-
-        // If a nickname is provided, we generate an icon based on a SHA512 HMAC
-        // of the users IP. (We assume that if the user did not enter a nickname,
-        // the user wants to be anonymous and we will not generate an icon.)
+        // we generate an icon based on a SHA512 HMAC of the users IP, if configured
         $icon = $this->_conf->getKey('icon');
         if ($icon != 'none') {
             $pngdata = '';
@@ -205,9 +174,12 @@ class Comment extends AbstractModel
                 );
             }
             if ($pngdata != '') {
-                $this->_data->meta->vizhash = $pngdata;
+                if (!array_key_exists('meta', $data)) {
+                    $data['meta'] = array();
+                }
+                $data['meta']['icon'] = $pngdata;
             }
         }
-        // Once the icon is generated, we do not keep the IP address hash.
+        return $data;
     }
 }

+ 60 - 130
lib/Model/Paste.php

@@ -15,7 +15,6 @@ namespace PrivateBin\Model;
 use Exception;
 use PrivateBin\Controller;
 use PrivateBin\Persistence\ServerSalt;
-use PrivateBin\FormatV2;
 
 /**
  * Paste
@@ -28,8 +27,8 @@ class Paste extends AbstractModel
      * Get paste data.
      *
      * @access public
-     * @throws \Exception
-     * @return \stdClass
+     * @throws Exception
+     * @return array
      */
     public function get()
     {
@@ -39,44 +38,42 @@ class Paste extends AbstractModel
         }
 
         // check if paste has expired and delete it if neccessary.
-        if (property_exists($data->meta, 'expire_date')) {
-            if ($data->meta->expire_date < time()) {
+        if (array_key_exists('expire_date', $data['meta'])) {
+            if ($data['meta']['expire_date'] < time()) {
                 $this->delete();
                 throw new Exception(Controller::GENERIC_ERROR, 63);
             }
             // We kindly provide the remaining time before expiration (in seconds)
-            $data->meta->remaining_time = $data->meta->expire_date - time();
+            $data['meta']['time_to_live'] = $data['meta']['expire_date'] - time();
         }
 
         // check if non-expired burn after reading paste needs to be deleted
-        if (property_exists($data->meta, 'burnafterreading') && $data->meta->burnafterreading) {
+        if (
+            (array_key_exists('adata', $data) && $data['adata'][3] === 1) ||
+            (array_key_exists('burnafterreading', $data['meta']) && $data['meta']['burnafterreading'])
+        ) {
             $this->delete();
         }
 
-        // set formatter for for the view.
-        if (!property_exists($data->meta, 'formatter')) {
+        // set formatter for the view in version 1 pastes.
+        if (array_key_exists('data', $data) && !array_key_exists('formatter', $data['meta'])) {
             // support < 0.21 syntax highlighting
-            if (property_exists($data->meta, 'syntaxcoloring') && $data->meta->syntaxcoloring === true) {
-                $data->meta->formatter = 'syntaxhighlighting';
+            if (array_key_exists('syntaxcoloring', $data['meta']) && $data['meta']['syntaxcoloring'] === true) {
+                $data['meta']['formatter'] = 'syntaxhighlighting';
             } else {
-                $data->meta->formatter = $this->_conf->getKey('defaultformatter');
+                $data['meta']['formatter'] = $this->_conf->getKey('defaultformatter');
             }
         }
 
         // support old paste format with server wide salt
-        if (!property_exists($data->meta, 'salt')) {
-            $data->meta->salt = ServerSalt::get();
-        }
-        $data->comments       = array_values($this->getComments());
-        $data->comment_count  = count($data->comments);
-        $data->comment_offset = 0;
-        $data->{'@context'}   = 'js/paste.jsonld';
-        $this->_data          = $data;
-
-        // If the paste was meant to be read only once, delete it.
-        if ($this->isBurnafterreading()) {
-            $this->delete();
+        if (!array_key_exists('salt', $data['meta'])) {
+            $data['meta']['salt'] = ServerSalt::get();
         }
+        $data['comments']       = array_values($this->getComments());
+        $data['comment_count']  = count($data['comments']);
+        $data['comment_offset'] = 0;
+        $data['@context']       = 'js/paste.jsonld';
+        $this->_data            = $data;
 
         return $this->_data;
     }
@@ -94,8 +91,8 @@ class Paste extends AbstractModel
             throw new Exception('You are unlucky. Try again.', 75);
         }
 
-        $this->_data->meta->postdate = time();
-        $this->_data->meta->salt     = serversalt::generate();
+        $this->_data['meta']['created'] = time();
+        $this->_data['meta']['salt']    = serversalt::generate();
 
         // store paste
         if (
@@ -139,7 +136,7 @@ class Paste extends AbstractModel
      * @throws Exception
      * @return Comment
      */
-    public function getComment($parentId, $commentId = null)
+    public function getComment(string $parentId, string $commentId = '')
     {
         if (!$this->exists()) {
             throw new Exception('Invalid data.', 62);
@@ -147,7 +144,7 @@ class Paste extends AbstractModel
         $comment = new Comment($this->_conf, $this->_store);
         $comment->setPaste($this);
         $comment->setParentId($parentId);
-        if ($commentId !== null) {
+        if ($commentId !== '') {
             $comment->setId($commentId);
         }
         return $comment;
@@ -187,43 +184,34 @@ class Paste extends AbstractModel
     }
 
     /**
-     * Set paste's attachment.
-     *
-     * @access public
-     * @param string $attachment
-     * @throws Exception
-     */
-    public function setAttachment($attachment)
-    {
-        if (!$this->_conf->getKey('fileupload') || !FormatV2::isValid($attachment)) {
-            throw new Exception('Invalid attachment.', 71);
-        }
-        $this->_data->meta->attachment = $attachment;
-    }
-
-    /**
-     * Set paste's attachment name.
+     * Check if paste has discussions enabled.
      *
      * @access public
-     * @param string $attachmentname
      * @throws Exception
+     * @return bool
      */
-    public function setAttachmentName($attachmentname)
+    public function isOpendiscussion()
     {
-        if (!$this->_conf->getKey('fileupload') || !FormatV2::isValid($attachmentname)) {
-            throw new Exception('Invalid attachment.', 72);
+        if (!array_key_exists('adata', $this->_data) && !array_key_exists('data', $this->_data)) {
+            $this->get();
         }
-        $this->_data->meta->attachmentname = $attachmentname;
+        return (
+            (array_key_exists('adata', $this->_data) && $this->_data['adata'][2] === 1) ||
+            (array_key_exists('opendiscussion', $this->_data['meta']) && $this->_data['meta']['opendiscussion'])
+        );
     }
 
     /**
-     * Set paste expiration.
+     * Sanitizes data to conform with current configuration.
      *
-     * @access public
-     * @param string $expiration
+     * @access protected
+     * @param  array $data
+     * @return array
      */
-    public function setExpiration($expiration)
+    protected function _sanitize(array $data)
     {
+        $expiration = $data['meta']['expire'];
+        unset($data['meta']['expire']);
         $expire_options = $this->_conf->getSection('expire_options');
         if (array_key_exists($expiration, $expire_options)) {
             $expire = $expire_options[$expiration];
@@ -232,97 +220,39 @@ class Paste extends AbstractModel
             $expire = $this->_conf->getKey($this->_conf->getKey('default', 'expire'), 'expire_options');
         }
         if ($expire > 0) {
-            $this->_data->meta->expire_date = time() + $expire;
+            $data['meta']['expire_date'] = time() + $expire;
         }
+        return $data;
     }
 
     /**
-     * Set paste's burn-after-reading type.
+     * Validate data.
      *
-     * @access public
-     * @param string $burnafterreading
+     * @access protected
+     * @param  array $data
      * @throws Exception
      */
-    public function setBurnafterreading($burnafterreading = '1')
+    protected function _validate(array $data)
     {
-        if ($burnafterreading === '0') {
-            $this->_data->meta->burnafterreading = false;
-        } else {
-            if ($burnafterreading !== '1') {
-                throw new Exception('Invalid data.', 73);
-            }
-            $this->_data->meta->burnafterreading = true;
-            $this->_data->meta->opendiscussion   = false;
+        // reject invalid or disabled formatters
+        if (!array_key_exists($data['adata'][1], $this->_conf->getSection('formatter_options'))) {
+            throw new Exception('Invalid data.', 75);
         }
-    }
 
-    /**
-     * Set paste's discussion state.
-     *
-     * @access public
-     * @param string $opendiscussion
-     * @throws Exception
-     */
-    public function setOpendiscussion($opendiscussion = '1')
-    {
+        // discussion requested, but disabled in config or burn after reading requested as well, or invalid integer
         if (
-            !$this->_conf->getKey('discussion') ||
-            $this->isBurnafterreading() ||
-            $opendiscussion === '0'
+            ($data['adata'][2] === 1 && ( // open discussion flag
+                !$this->_conf->getKey('discussion') ||
+                $data['adata'][3] === 1  // burn after reading flag
+            )) ||
+            ($data['adata'][2] !== 0 && $data['adata'][2] !== 1)
         ) {
-            $this->_data->meta->opendiscussion = false;
-        } else {
-            if ($opendiscussion !== '1') {
-                throw new Exception('Invalid data.', 74);
-            }
-            $this->_data->meta->opendiscussion = true;
+            throw new Exception('Invalid data.', 74);
         }
-    }
 
-    /**
-     * Set paste's format.
-     *
-     * @access public
-     * @param string $format
-     * @throws Exception
-     */
-    public function setFormatter($format)
-    {
-        if (!array_key_exists($format, $this->_conf->getSection('formatter_options'))) {
-            $format = $this->_conf->getKey('defaultformatter');
-        }
-        $this->_data->meta->formatter = $format;
-    }
-
-    /**
-     * Check if paste is of burn-after-reading type.
-     *
-     * @access public
-     * @throws Exception
-     * @return bool
-     */
-    public function isBurnafterreading()
-    {
-        if (!property_exists($this->_data, 'data')) {
-            $this->get();
-        }
-        return property_exists($this->_data->meta, 'burnafterreading') &&
-               $this->_data->meta->burnafterreading === true;
-    }
-
-    /**
-     * Check if paste has discussions enabled.
-     *
-     * @access public
-     * @throws Exception
-     * @return bool
-     */
-    public function isOpendiscussion()
-    {
-        if (!property_exists($this->_data, 'data')) {
-            $this->get();
+        // reject invalid burn after reading
+        if ($data['adata'][3] !== 0 && $data['adata'][3] !== 1) {
+            throw new Exception('Invalid data.', 73);
         }
-        return property_exists($this->_data->meta, 'opendiscussion') &&
-               $this->_data->meta->opendiscussion === true;
     }
 }

+ 16 - 8
tst/Bootstrap.php

@@ -167,6 +167,19 @@ class Helper
         return json_encode($example);
     }
 
+    /**
+     * get example paste, as received via POST by user
+     *
+     * @param  int $version
+     * @return array
+     */
+    public static function getPastePost()
+    {
+        $example = self::getPaste();
+        $example['meta'] = array('expire' => $example['meta']['expire']);
+        return $example;
+    }
+
     /**
      * get example paste ID
      *
@@ -203,15 +216,10 @@ class Helper
      * @param  int $version
      * @return array
      */
-    public static function getCommentPost(int $version = 2)
+    public static function getCommentPost()
     {
-        $example = self::getComment($version);
-        if ($version === 1) {
-            $example['nickname'] = $example['meta']['nickname'];
-            unset($example['meta']['nickname']);
-        } else {
-            unset($example['meta']);
-        }
+        $example = self::getComment();
+        unset($example['meta']);
         return $example;
     }
 

+ 1 - 1
tst/Data/FilesystemTest.php

@@ -88,7 +88,7 @@ class FilesystemTest extends PHPUnit_Framework_TestCase
         $keys    = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z');
         $ids     = array();
         foreach ($keys as $key) {
-            $ids[$key] = substr(md5($key), 0, 16);
+            $ids[$key] = hash('fnv1a64', $key);
             $this->assertFalse($this->_model->exists($ids[$key]), "paste $key does not yet exist");
             if (in_array($key, array('x', 'y', 'z'))) {
                 $this->assertTrue($this->_model->create($ids[$key], $paste), "store $key paste");

+ 67 - 114
tst/ModelTest.php

@@ -54,42 +54,46 @@ class ModelTest extends PHPUnit_Framework_TestCase
     public function testBasicWorkflow()
     {
         // storing pastes
-        $pasteData = Helper::getPaste();
+        $pasteData = Helper::getPastePost();
+        unset($pasteData['meta']['created'], $pasteData['meta']['salt']);
         $this->_model->getPaste(Helper::getPasteId())->delete();
         $paste = $this->_model->getPaste(Helper::getPasteId());
         $this->assertFalse($paste->exists(), 'paste does not yet exist');
 
         $paste = $this->_model->getPaste();
         $paste->setData($pasteData);
-        $paste->setOpendiscussion();
-        $paste->setFormatter($pasteData['adata'][1]);
         $paste->store();
 
         $paste = $this->_model->getPaste(Helper::getPasteId());
         $this->assertTrue($paste->exists(), 'paste exists after storing it');
         $paste = $paste->get();
-        $this->assertEquals($pasteData, $paste->data);
-        foreach (array('opendiscussion', 'formatter') as $key) {
-            $this->assertEquals($pasteData['meta'][$key], $paste->meta->$key);
-        }
+        unset(
+            $pasteData['meta'],
+            $paste['meta'],
+            $paste['comments'],
+            $paste['comment_count'],
+            $paste['comment_offset'],
+            $paste['@context']
+        );
+        $this->assertEquals($pasteData, $paste);
 
         // storing comments
-        $commentData = Helper::getComment();
+        $commentData = Helper::getCommentPost();
         $paste       = $this->_model->getPaste(Helper::getPasteId());
         $comment     = $paste->getComment(Helper::getPasteId(), Helper::getCommentId());
         $this->assertFalse($comment->exists(), 'comment does not yet exist');
 
         $comment = $paste->getComment(Helper::getPasteId());
         $comment->setData($commentData);
-        $comment->setNickname($commentData['meta']['nickname']);
-        $comment->getParentId();
+        //$comment->getParentId();
         $comment->store();
 
-        $comment = $paste->getComment(Helper::getPasteId(), Helper::getCommentId());
-        $this->assertTrue($comment->exists(), 'comment exists after storing it');
-        $comment = $comment->get();
-        $this->assertEquals($commentData, $comment->data);
-        $this->assertEquals($commentData['meta']['nickname'], $comment->meta->nickname);
+        $comments = $this->_model->getPaste(Helper::getPasteId())->get()['comments'];
+        $this->assertTrue(count($comments) === 1, 'comment exists after storing it');
+        $commentData['id'] = Helper::getPasteId();
+        $commentData['meta']['created'] = current($comments)['meta']['created'];
+        $commentData['meta']['icon'] = current($comments)['meta']['icon'];
+        $this->assertEquals($commentData, current($comments));
 
         // deleting pastes
         $this->_model->getPaste(Helper::getPasteId())->delete();
@@ -104,19 +108,15 @@ class ModelTest extends PHPUnit_Framework_TestCase
      */
     public function testPasteDuplicate()
     {
-        $pasteData = Helper::getPaste();
+        $pasteData = Helper::getPastePost();
 
         $this->_model->getPaste(Helper::getPasteId())->delete();
         $paste = $this->_model->getPaste();
         $paste->setData($pasteData);
-        $paste->setOpendiscussion();
-        $paste->setFormatter($pasteData['adata'][1]);
         $paste->store();
 
         $paste = $this->_model->getPaste();
         $paste->setData($pasteData);
-        $paste->setOpendiscussion();
-        $paste->setFormatter($pasteData['adata'][1]);
         $paste->store();
     }
 
@@ -126,61 +126,42 @@ class ModelTest extends PHPUnit_Framework_TestCase
      */
     public function testCommentDuplicate()
     {
-        $pasteData   = Helper::getPaste();
-        $commentData = Helper::getComment();
+        $pasteData   = Helper::getPastePost();
+        $commentData = Helper::getCommentPost();
         $this->_model->getPaste(Helper::getPasteId())->delete();
 
         $paste = $this->_model->getPaste();
         $paste->setData($pasteData);
-        $paste->setOpendiscussion();
-        $paste->setFormatter($pasteData['adata'][1]);
         $paste->store();
 
         $comment = $paste->getComment(Helper::getPasteId());
         $comment->setData($commentData);
-        $comment->setNickname($commentData['meta']['nickname']);
         $comment->store();
 
         $comment = $paste->getComment(Helper::getPasteId());
         $comment->setData($commentData);
-        $comment->setNickname($commentData['meta']['nickname']);
         $comment->store();
     }
 
     public function testImplicitDefaults()
     {
-        $pasteData   = Helper::getPaste();
-        $commentData = Helper::getComment();
+        $pasteData   = Helper::getPastePost();
+        $commentData = Helper::getCommentPost();
         $this->_model->getPaste(Helper::getPasteId())->delete();
 
         $paste = $this->_model->getPaste();
         $paste->setData($pasteData);
-        $paste->setBurnafterreading();
-        $paste->setOpendiscussion();
-        // not setting a formatter, should use default one
-        $paste->store();
-
-        $paste = $this->_model->getPaste(Helper::getPasteId())->get(); // ID was set based on data
-        $this->assertEquals(true, property_exists($paste->meta, 'burnafterreading') && $paste->meta->burnafterreading, 'burn after reading takes precendence');
-        $this->assertEquals(false, property_exists($paste->meta, 'opendiscussion') && $paste->meta->opendiscussion, 'opendiscussion is disabled');
-        $this->assertEquals($this->_conf->getKey('defaultformatter'), $paste->meta->formatter, 'default formatter is set');
-
-        $this->_model->getPaste(Helper::getPasteId())->delete();
-        $paste = $this->_model->getPaste();
-        $paste->setData($pasteData);
-        $paste->setBurnafterreading('0');
-        $paste->setOpendiscussion();
         $paste->store();
 
         $comment = $paste->getComment(Helper::getPasteId());
         $comment->setData($commentData);
-        $comment->setNickname($commentData['meta']['nickname']);
+        $comment->get();
         $comment->store();
 
         $identicon = new Identicon();
         $pngdata   = $identicon->getImageDataUri(TrafficLimiter::getHash(), 16);
-        $comment   = $paste->getComment(Helper::getPasteId(), Helper::getCommentId())->get();
-        $this->assertEquals($pngdata, $comment->meta->vizhash, 'nickname triggers vizhash to be set');
+        $comment   = current($this->_model->getPaste(Helper::getPasteId())->get()['comments']);
+        $this->assertEquals($pngdata, $comment['meta']['icon'], 'icon gets set');
     }
 
     public function testPasteIdValidation()
@@ -201,6 +182,15 @@ class ModelTest extends PHPUnit_Framework_TestCase
         $paste->get();
     }
 
+    /**
+     * @expectedException Exception
+     * @expectedExceptionCode 60
+     */
+    public function testInvalidPasteId()
+    {
+        $this->_model->getPaste('');
+    }
+
     /**
      * @expectedException Exception
      * @expectedExceptionCode 61
@@ -227,7 +217,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
      */
     public function testInvalidCommentDeletedPaste()
     {
-        $pasteData = Helper::getPaste();
+        $pasteData = Helper::getPastePost();
         $paste     = $this->_model->getPaste(Helper::getPasteId());
         $paste->setData($pasteData);
         $paste->store();
@@ -243,7 +233,8 @@ class ModelTest extends PHPUnit_Framework_TestCase
      */
     public function testInvalidCommentData()
     {
-        $pasteData = Helper::getPaste();
+        $pasteData = Helper::getPastePost();
+        $pasteData['adata'][2] = 0;
         $paste     = $this->_model->getPaste(Helper::getPasteId());
         $paste->setData($pasteData);
         $paste->store();
@@ -252,20 +243,30 @@ class ModelTest extends PHPUnit_Framework_TestCase
         $comment->store();
     }
 
+    /**
+     * @expectedException Exception
+     * @expectedExceptionCode 65
+     */
+    public function testInvalidCommentParent()
+    {
+        $paste     = $this->_model->getPaste(Helper::getPasteId());
+        $comment = $paste->getComment('');
+        $comment->store();
+    }
+
     public function testExpiration()
     {
-        $pasteData = Helper::getPaste();
+        $pasteData = Helper::getPastePost();
         $this->_model->getPaste(Helper::getPasteId())->delete();
         $paste = $this->_model->getPaste(Helper::getPasteId());
         $this->assertFalse($paste->exists(), 'paste does not yet exist');
 
         $paste = $this->_model->getPaste();
         $paste->setData($pasteData);
-        $paste->setExpiration('5min'); // = 300 seconds
         $paste->store();
 
         $paste = $paste->get();
-        $this->assertEquals(300, $paste->meta->remaining_time, 'remaining time is set correctly');
+        $this->assertEquals((float) 300, (float) $paste['meta']['time_to_live'], 'remaining time is set correctly', 1.0);
     }
 
     /**
@@ -274,7 +275,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
      */
     public function testCommentDeletion()
     {
-        $pasteData = Helper::getPaste();
+        $pasteData = Helper::getPastePost();
         $this->_model->getPaste(Helper::getPasteId())->delete();
 
         $paste = $this->_model->getPaste();
@@ -288,12 +289,12 @@ class ModelTest extends PHPUnit_Framework_TestCase
         $conf  = new Configuration;
         $store = Database::getInstance($conf->getSection('model_options'));
         $store->delete(Helper::getPasteId());
-        $expired = Helper::getPaste(array('expire_date' => 1344803344));
-        $paste   = Helper::getPaste(array('expire_date' => time() + 3600));
+        $expired = Helper::getPaste(2, array('expire_date' => 1344803344));
+        $paste   = Helper::getPaste(2, array('expire_date' => time() + 3600));
         $keys    = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'x', 'y', 'z');
         $ids     = array();
         foreach ($keys as $key) {
-            $ids[$key] = substr(md5($key), 0, 16);
+            $ids[$key] = hash('fnv1a64', $key);
             $store->delete($ids[$key]);
             $this->assertFalse($store->exists($ids[$key]), "paste $key does not yet exist");
             if (in_array($key, array('x', 'y', 'z'))) {
@@ -330,79 +331,34 @@ class ModelTest extends PHPUnit_Framework_TestCase
         Helper::createIniFile(CONF, $options);
         $model = new Model(new Configuration);
 
-        $pasteData = Helper::getPaste();
+        $pasteData = Helper::getPastePost();
         $this->_model->getPaste(Helper::getPasteId())->delete();
         $paste = $model->getPaste(Helper::getPasteId());
         $this->assertFalse($paste->exists(), 'paste does not yet exist');
 
         $paste = $model->getPaste();
         $paste->setData($pasteData);
-        $paste->setOpendiscussion();
-        $paste->setFormatter($pasteData['adata'][1]);
         $paste->store();
 
         $paste = $model->getPaste(Helper::getPasteId());
         $this->assertTrue($paste->exists(), 'paste exists after storing it');
-        $paste = $paste->get();
-        $this->assertEquals($pasteData, $paste->data);
-        foreach (array('opendiscussion', 'formatter') as $key) {
-            $this->assertEquals($pasteData['meta'][$key], $paste->meta->$key);
-        }
 
         // storing comments
-        $commentData = Helper::getComment();
+        $commentData = Helper::getCommentPost();
+        unset($commentData['meta']['icon']);
         $paste       = $model->getPaste(Helper::getPasteId());
-        $comment     = $paste->getComment(Helper::getPasteId(), Helper::getCommentId());
+        $comment     = $paste->getComment(Helper::getPasteId(), Helper::getPasteId());
         $this->assertFalse($comment->exists(), 'comment does not yet exist');
 
         $comment = $paste->getComment(Helper::getPasteId());
         $comment->setData($commentData);
-        $comment->setNickname($commentData['meta']['nickname']);
         $comment->store();
 
-        $comment = $paste->getComment(Helper::getPasteId(), Helper::getCommentId());
+        $comment = $paste->getComment(Helper::getPasteId(), Helper::getPasteId());
         $this->assertTrue($comment->exists(), 'comment exists after storing it');
-        $comment = $comment->get();
-        $this->assertEquals($commentData, $comment->data);
-        $this->assertEquals($commentData['meta']['nickname'], $comment->meta->nickname);
-        $this->assertFalse(property_exists($comment->meta, 'vizhash'), 'vizhash was not generated');
-    }
 
-    public function testCommentIdenticon()
-    {
-        $options                 = parse_ini_file(CONF, true);
-        $options['main']['icon'] = 'identicon';
-        $options['model']        = array(
-            'class' => 'Database',
-        );
-        $options['model_options'] = array(
-            'dsn' => 'sqlite::memory:',
-            'usr' => null,
-            'pwd' => null,
-            'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
-        );
-        Helper::createIniFile(CONF, $options);
-        $model = new Model(new Configuration);
-
-        $pasteData   = Helper::getPaste();
-        $commentData = Helper::getComment();
-        $model->getPaste(Helper::getPasteId())->delete();
-
-        $paste = $model->getPaste();
-        $paste->setData($pasteData);
-        $paste->setOpendiscussion();
-        $paste->setFormatter($pasteData['adata'][1]);
-        $paste->store();
-
-        $comment = $paste->getComment(Helper::getPasteId());
-        $comment->setData($commentData);
-        $comment->setNickname($commentData['meta']['nickname']);
-        $comment->store();
-
-        $identicon = new Identicon();
-        $pngdata   = $identicon->getImageDataUri(TrafficLimiter::getHash(), 16);
-        $comment   = $paste->getComment(Helper::getPasteId(), Helper::getCommentId())->get();
-        $this->assertEquals($pngdata, $comment->meta->vizhash, 'nickname triggers vizhash to be set');
+        $comment = current($this->_model->getPaste(Helper::getPasteId())->get()['comments']);
+        $this->assertFalse(array_key_exists('icon', $comment['meta']), 'icon was not generated');
     }
 
     public function testCommentVizhash()
@@ -421,24 +377,21 @@ class ModelTest extends PHPUnit_Framework_TestCase
         Helper::createIniFile(CONF, $options);
         $model = new Model(new Configuration);
 
-        $pasteData   = Helper::getPaste();
-        $commentData = Helper::getComment();
+        $pasteData   = Helper::getPastePost();
+        $commentData = Helper::getCommentPost();
         $model->getPaste(Helper::getPasteId())->delete();
 
         $paste = $model->getPaste();
         $paste->setData($pasteData);
-        $paste->setOpendiscussion();
-        $paste->setFormatter($pasteData['adata'][1]);
         $paste->store();
 
         $comment = $paste->getComment(Helper::getPasteId());
         $comment->setData($commentData);
-        $comment->setNickname($commentData['meta']['nickname']);
         $comment->store();
 
         $vz      = new Vizhash16x16();
         $pngdata = 'data:image/png;base64,' . base64_encode($vz->generate(TrafficLimiter::getHash()));
-        $comment = $paste->getComment(Helper::getPasteId(), Helper::getCommentId())->get();
-        $this->assertEquals($pngdata, $comment->meta->vizhash, 'nickname triggers vizhash to be set');
+        $comment   = current($this->_model->getPaste(Helper::getPasteId())->get()['comments']);
+        $this->assertEquals($pngdata, $comment['meta']['icon'], 'nickname triggers vizhash to be set');
     }
 }