From b25022e403f61c0d28883950dda5902c5cec566c Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 1 Sep 2015 22:33:07 +0200 Subject: [PATCH] refactored JSON API, its now possible to retrieve pastes as JSON, which is now used when posting comments, eliminating the need to store the password in sessionStorage --- js/zerobin.js | 48 +++++++------- lib/zerobin.php | 163 ++++++++++++++++++++++++++++++++++-------------- tst/zerobin.php | 16 +++++ 3 files changed, 157 insertions(+), 70 deletions(-) diff --git a/js/zerobin.js b/js/zerobin.js index 8e9c4a3..66ae257 100644 --- a/js/zerobin.js +++ b/js/zerobin.js @@ -213,11 +213,6 @@ function setElementText(element, text) { * @param array comments : Array of messages to display (items = array with keys ('data','meta') */ function displayMessages(key, comments) { - // restore password if set in previous visit, then clear the session - if (window.sessionStorage && sessionStorage.getItem(pageKey())) { - $('#passwordinput').val(sessionStorage.getItem(pageKey())); - sessionStorage.clear(); - } try { // Try to decrypt the paste. var cleartext = zeroDecipher(key, comments[0].data); if (cleartext == null) throw "password prompt canceled"; @@ -327,7 +322,7 @@ function send_comment(parentid) { showStatus('Sending comment...', spin=true); var cipherdata = zeroCipher(pageKey(), $('#replymessage').val()); var ciphernickname = ''; - var nick=$('#nickname').val(); + var nick = $('#nickname').val(); if (nick != '' && nick != 'Optional nickname...') { ciphernickname = zeroCipher(pageKey(), nick); } @@ -337,29 +332,38 @@ function send_comment(parentid) { nickname: ciphernickname }; - $.post(scriptLocation(), data_to_send, 'json') - .error(function() { - showError('Comment could not be sent (server error or not responding).'); - }) - .success(function(data) { + $.post(scriptLocation(), data_to_send, function(data) { if (data.status == 0) { showStatus('Comment posted.'); - // store password temporarily between page loads - if ($('#passwordinput').val().length > 0 && window.sessionStorage) { - sessionStorage.setItem(pageKey(), $('#passwordinput').val()); - } - location.reload(); + $.get(scriptLocation() + "?" + pasteID() + "&json", function(data) { + if (data.status == 0) { + displayMessages(pageKey(), data.messages); + } + else if (data.status == 1) { + showError('Could not refresh display: ' + data.message); + } + else + { + showError('Could not refresh display: unknown status'); + } + }, 'json') + .fail(function() { + showError('Could not refresh display (server error or not responding).'); + }); } - else if (data.status==1) { - showError('Could not post comment: '+data.message); + else if (data.status == 1) { + showError('Could not post comment: ' + data.message); } - else { - showError('Could not post comment.'); + else + { + showError('Could not post comment: unknown status'); } + }, 'json') + .fail(function() { + showError('Comment could not be sent (server error or not responding).'); }); } - /** * Send a new paste to server */ @@ -407,7 +411,7 @@ function send_data() { if (typeof prettyPrint == 'function') prettyPrint(); } else if (data.status==1) { - showError('Could not create paste: '+data.message); + showError('Could not create paste: ' + data.message); } else { showError('Could not create paste.'); diff --git a/lib/zerobin.php b/lib/zerobin.php index 42a76af..515194b 100644 --- a/lib/zerobin.php +++ b/lib/zerobin.php @@ -65,6 +65,14 @@ class zerobin */ private $_status = ''; + /** + * JSON message + * + * @access private + * @var string + */ + private $_json = ''; + /** * data storage model * @@ -84,7 +92,9 @@ class zerobin public function __construct() { if (version_compare(PHP_VERSION, '5.2.6') < 0) + { throw new Exception('ZeroBin requires php 5.2.6 or above to work. Sorry.', 1); + } // in case stupid admin has left magic_quotes enabled in php.ini if (get_magic_quotes_gpc()) @@ -100,17 +110,12 @@ class zerobin // create new paste or comment if (!empty($_POST['data'])) { - echo $this->_create($_POST['data']); - return; + $this->_create($_POST['data']); } // delete an existing paste elseif (!empty($_GET['deletetoken']) && !empty($_GET['pasteid'])) { - $result = $this->_delete($_GET['pasteid'], $_GET['deletetoken']); - if (strlen($result)) { - echo $result; - return; - } + $this->_delete($_GET['pasteid'], $_GET['deletetoken']); } // display an existing paste elseif (!empty($_SERVER['QUERY_STRING'])) @@ -118,8 +123,16 @@ class zerobin $this->_read($_SERVER['QUERY_STRING']); } - // display ZeroBin frontend - $this->_view(); + // output JSON or HTML + if (strlen($this->_json)) + { + header('Content-type: application/json'); + echo $this->_json; + } + else + { + $this->_view(); + } } /** @@ -186,31 +199,34 @@ class zerobin */ private function _create($data) { - header('Content-type: application/json'); $error = false; // Make sure last paste from the IP address was more than X seconds ago. trafficlimiter::setLimit($this->_conf['traffic']['limit']); trafficlimiter::setPath($this->_conf['traffic']['dir']); - if ( - !trafficlimiter::canPass($_SERVER['REMOTE_ADDR']) - ) return $this->_return_message( - 1, - 'Please wait ' . - $this->_conf['traffic']['limit'] . - ' seconds between each post.' - ); + if (!trafficlimiter::canPass($_SERVER['REMOTE_ADDR'])) + { + $this->_return_message( + 1, + 'Please wait ' . + $this->_conf['traffic']['limit'] . + ' seconds between each post.' + ); + return; + } // Make sure content is not too big. $sizelimit = (int) $this->_getMainConfig('sizelimit', 2097152); - if ( - strlen($data) > $sizelimit - ) return $this->_return_message( - 1, - 'Paste is limited to ' . - filter::size_humanreadable($sizelimit) . - ' of encrypted data.' - ); + if (strlen($data) > $sizelimit) + { + $this->_return_message( + 1, + 'Paste is limited to ' . + filter::size_humanreadable($sizelimit) . + ' of encrypted data.' + ); + return; + } // Make sure format is correct. if (!sjcl::isValid($data)) return $this->_return_message(1, 'Invalid data.'); @@ -280,7 +296,11 @@ class zerobin } } - if ($error) return $this->_return_message(1, 'Invalid data.'); + if ($error) + { + $this->_return_message(1, 'Invalid data.'); + return; + } // Add post date to meta. $meta['postdate'] = time(); @@ -305,7 +325,11 @@ class zerobin if ( !filter::is_valid_paste_id($pasteid) || !filter::is_valid_paste_id($parentid) - ) return $this->_return_message(1, 'Invalid data.'); + ) + { + $this->_return_message(1, 'Invalid data.'); + return; + } // Comments do not expire (it's the paste that expires) unset($storage['expire_date']); @@ -314,26 +338,43 @@ class zerobin // Make sure paste exists. if ( !$this->_model()->exists($pasteid) - ) return $this->_return_message(1, 'Invalid data.'); + ) + { + $this->_return_message(1, 'Invalid data.'); + return; + } // Make sure the discussion is opened in this paste. $paste = $this->_model()->read($pasteid); if ( !$paste->meta->opendiscussion - ) return $this->_return_message(1, 'Invalid data.'); + ) + { + $this->_return_message(1, 'Invalid data.'); + return; + } // Check for improbable collision. if ( $this->_model()->existsComment($pasteid, $parentid, $dataid) - ) return $this->_return_message(1, 'You are unlucky. Try again.'); + ) + { + $this->_return_message(1, 'You are unlucky. Try again.'); + return; + } // New comment if ( $this->_model()->createComment($pasteid, $parentid, $dataid, $storage) === false - ) return $this->_return_message(1, 'Error saving comment. Sorry.'); + ) + { + $this->_return_message(1, 'Error saving comment. Sorry.'); + return; + } // 0 = no error - return $this->_return_message(0, $dataid); + $this->_return_message(0, $dataid); + return; } // The user posts a standard paste. else @@ -341,12 +382,19 @@ class zerobin // Check for improbable collision. if ( $this->_model()->exists($dataid) - ) return $this->_return_message(1, 'You are unlucky. Try again.'); + ) + { + $this->_return_message(1, 'You are unlucky. Try again.'); + return; + } // New paste if ( $this->_model()->create($dataid, $storage) === false - ) return $this->_return_message(1, 'Error saving paste. Sorry.'); + ) { + $this->_return_message(1, 'Error saving paste. Sorry.'); + return; + } // Generate the "delete" token. // The token is the hmac of the pasteid signed with the server salt. @@ -354,10 +402,9 @@ class zerobin $deletetoken = hash_hmac('sha1', $dataid, serversalt::get()); // 0 = no error - return $this->_return_message(0, $dataid, array('deletetoken' => $deletetoken)); + $this->_return_message(0, $dataid, array('deletetoken' => $deletetoken)); + return; } - - return $this->_return_message(1, 'Server error.'); } /** @@ -366,7 +413,7 @@ class zerobin * @access private * @param string $dataid * @param string $deletetoken - * @return string + * @return void */ private function _delete($dataid, $deletetoken) { @@ -374,14 +421,14 @@ class zerobin if (!filter::is_valid_paste_id($dataid)) { $this->_error = 'Invalid paste ID.'; - return ''; + return; } // Check that paste exists. if (!$this->_model()->exists($dataid)) { $this->_error = self::GENERIC_ERROR; - return ''; + return; } // Get the paste itself. @@ -396,10 +443,10 @@ class zerobin // Delete the paste $this->_model()->delete($dataid); $this->_error = self::GENERIC_ERROR; + return; } if ($deletetoken == 'burnafterreading') { - header('Content-type: application/json'); if ( isset($paste->meta->burnafterreading) && $paste->meta->burnafterreading @@ -407,9 +454,13 @@ class zerobin { // Delete the paste $this->_model()->delete($dataid); - return $this->_return_message(0, 'Paste was properly deleted.'); + $this->_return_message(0, 'Paste was properly deleted.'); } - return $this->_return_message(1, 'Paste is not of burn-after-reading type.'); + else + { + $this->_return_message(1, 'Paste is not of burn-after-reading type.'); + } + return; } // Make sure token is valid. @@ -417,13 +468,12 @@ class zerobin if (!filter::slow_equals($deletetoken, hash_hmac('sha1', $dataid, serversalt::get()))) { $this->_error = 'Wrong deletion token. Paste was not deleted.'; - return ''; + return; } // Paste exists and deletion token is valid: Delete the paste. $this->_model()->delete($dataid); $this->_status = 'Paste was properly deleted.'; - return ''; } /** @@ -435,6 +485,12 @@ class zerobin */ private function _read($dataid) { + $isJson = false; + if (($pos = strpos($dataid, '&json')) !== false) { + $isJson = true; + $dataid = substr($dataid, 0, $pos); + } + // Is this a valid paste identifier? if (!filter::is_valid_paste_id($dataid)) { @@ -487,6 +543,17 @@ class zerobin { $this->_error = self::GENERIC_ERROR; } + if ($isJson) + { + if (strlen($this->_error)) + { + $this->_return_message(1, $this->_error); + } + else + { + $this->_return_message(0, $dataid, array('messages' => $messages)); + } + } } /** @@ -555,7 +622,7 @@ class zerobin * @param bool $status * @param string $message * @param array $other - * @return string + * @return void */ private function _return_message($status, $message, $other = array()) { @@ -569,6 +636,6 @@ class zerobin $result['id'] = $message; } $result += $other; - return json_encode($result); + $this->_json = json_encode($result); } } diff --git a/tst/zerobin.php b/tst/zerobin.php index 23da556..9e38119 100644 --- a/tst/zerobin.php +++ b/tst/zerobin.php @@ -437,6 +437,22 @@ class zerobinTest extends PHPUnit_Framework_TestCase ); } + /** + * @runInSeparateProcess + */ + public function testReadJson() + { + $this->reset(); + $this->_model->create(self::$pasteid, self::$paste); + $_SERVER['QUERY_STRING'] = self::$pasteid . '&json'; + ob_start(); + new zerobin; + $content = ob_get_contents(); + $response = json_decode($content, true); + $this->assertEquals(0, $response['status'], 'outputs success status'); + $this->assertEquals(array(self::$paste), $response['messages'], 'outputs data correctly'); + } + /** * @runInSeparateProcess */