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
This commit is contained in:
El RIDO 2015-09-01 22:33:07 +02:00
parent ded24b43ab
commit b25022e403
3 changed files with 157 additions and 70 deletions

View File

@ -213,11 +213,6 @@ function setElementText(element, text) {
* @param array comments : Array of messages to display (items = array with keys ('data','meta') * @param array comments : Array of messages to display (items = array with keys ('data','meta')
*/ */
function displayMessages(key, comments) { 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. try { // Try to decrypt the paste.
var cleartext = zeroDecipher(key, comments[0].data); var cleartext = zeroDecipher(key, comments[0].data);
if (cleartext == null) throw "password prompt canceled"; if (cleartext == null) throw "password prompt canceled";
@ -327,7 +322,7 @@ function send_comment(parentid) {
showStatus('Sending comment...', spin=true); showStatus('Sending comment...', spin=true);
var cipherdata = zeroCipher(pageKey(), $('#replymessage').val()); var cipherdata = zeroCipher(pageKey(), $('#replymessage').val());
var ciphernickname = ''; var ciphernickname = '';
var nick=$('#nickname').val(); var nick = $('#nickname').val();
if (nick != '' && nick != 'Optional nickname...') { if (nick != '' && nick != 'Optional nickname...') {
ciphernickname = zeroCipher(pageKey(), nick); ciphernickname = zeroCipher(pageKey(), nick);
} }
@ -337,29 +332,38 @@ function send_comment(parentid) {
nickname: ciphernickname nickname: ciphernickname
}; };
$.post(scriptLocation(), data_to_send, 'json') $.post(scriptLocation(), data_to_send, function(data) {
.error(function() {
showError('Comment could not be sent (server error or not responding).');
})
.success(function(data) {
if (data.status == 0) { if (data.status == 0) {
showStatus('Comment posted.'); showStatus('Comment posted.');
// store password temporarily between page loads $.get(scriptLocation() + "?" + pasteID() + "&json", function(data) {
if ($('#passwordinput').val().length > 0 && window.sessionStorage) { if (data.status == 0) {
sessionStorage.setItem(pageKey(), $('#passwordinput').val()); displayMessages(pageKey(), data.messages);
} }
location.reload(); else if (data.status == 1) {
showError('Could not refresh display: ' + data.message);
} }
else if (data.status==1) { else
showError('Could not post comment: '+data.message); {
showError('Could not refresh display: unknown status');
} }
else { }, 'json')
showError('Could not post comment.'); .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
{
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 * Send a new paste to server
*/ */
@ -407,7 +411,7 @@ function send_data() {
if (typeof prettyPrint == 'function') prettyPrint(); if (typeof prettyPrint == 'function') prettyPrint();
} }
else if (data.status==1) { else if (data.status==1) {
showError('Could not create paste: '+data.message); showError('Could not create paste: ' + data.message);
} }
else { else {
showError('Could not create paste.'); showError('Could not create paste.');

View File

@ -65,6 +65,14 @@ class zerobin
*/ */
private $_status = ''; private $_status = '';
/**
* JSON message
*
* @access private
* @var string
*/
private $_json = '';
/** /**
* data storage model * data storage model
* *
@ -84,7 +92,9 @@ class zerobin
public function __construct() public function __construct()
{ {
if (version_compare(PHP_VERSION, '5.2.6') < 0) if (version_compare(PHP_VERSION, '5.2.6') < 0)
{
throw new Exception('ZeroBin requires php 5.2.6 or above to work. Sorry.', 1); 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 // in case stupid admin has left magic_quotes enabled in php.ini
if (get_magic_quotes_gpc()) if (get_magic_quotes_gpc())
@ -100,17 +110,12 @@ class zerobin
// create new paste or comment // create new paste or comment
if (!empty($_POST['data'])) if (!empty($_POST['data']))
{ {
echo $this->_create($_POST['data']); $this->_create($_POST['data']);
return;
} }
// delete an existing paste // delete an existing paste
elseif (!empty($_GET['deletetoken']) && !empty($_GET['pasteid'])) elseif (!empty($_GET['deletetoken']) && !empty($_GET['pasteid']))
{ {
$result = $this->_delete($_GET['pasteid'], $_GET['deletetoken']); $this->_delete($_GET['pasteid'], $_GET['deletetoken']);
if (strlen($result)) {
echo $result;
return;
}
} }
// display an existing paste // display an existing paste
elseif (!empty($_SERVER['QUERY_STRING'])) elseif (!empty($_SERVER['QUERY_STRING']))
@ -118,9 +123,17 @@ class zerobin
$this->_read($_SERVER['QUERY_STRING']); $this->_read($_SERVER['QUERY_STRING']);
} }
// display ZeroBin frontend // output JSON or HTML
if (strlen($this->_json))
{
header('Content-type: application/json');
echo $this->_json;
}
else
{
$this->_view(); $this->_view();
} }
}
/** /**
* initialize zerobin * initialize zerobin
@ -186,31 +199,34 @@ class zerobin
*/ */
private function _create($data) private function _create($data)
{ {
header('Content-type: application/json');
$error = false; $error = false;
// Make sure last paste from the IP address was more than X seconds ago. // Make sure last paste from the IP address was more than X seconds ago.
trafficlimiter::setLimit($this->_conf['traffic']['limit']); trafficlimiter::setLimit($this->_conf['traffic']['limit']);
trafficlimiter::setPath($this->_conf['traffic']['dir']); trafficlimiter::setPath($this->_conf['traffic']['dir']);
if ( if (!trafficlimiter::canPass($_SERVER['REMOTE_ADDR']))
!trafficlimiter::canPass($_SERVER['REMOTE_ADDR']) {
) return $this->_return_message( $this->_return_message(
1, 1,
'Please wait ' . 'Please wait ' .
$this->_conf['traffic']['limit'] . $this->_conf['traffic']['limit'] .
' seconds between each post.' ' seconds between each post.'
); );
return;
}
// Make sure content is not too big. // Make sure content is not too big.
$sizelimit = (int) $this->_getMainConfig('sizelimit', 2097152); $sizelimit = (int) $this->_getMainConfig('sizelimit', 2097152);
if ( if (strlen($data) > $sizelimit)
strlen($data) > $sizelimit {
) return $this->_return_message( $this->_return_message(
1, 1,
'Paste is limited to ' . 'Paste is limited to ' .
filter::size_humanreadable($sizelimit) . filter::size_humanreadable($sizelimit) .
' of encrypted data.' ' of encrypted data.'
); );
return;
}
// Make sure format is correct. // Make sure format is correct.
if (!sjcl::isValid($data)) return $this->_return_message(1, 'Invalid data.'); 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. // Add post date to meta.
$meta['postdate'] = time(); $meta['postdate'] = time();
@ -305,7 +325,11 @@ class zerobin
if ( if (
!filter::is_valid_paste_id($pasteid) || !filter::is_valid_paste_id($pasteid) ||
!filter::is_valid_paste_id($parentid) !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) // Comments do not expire (it's the paste that expires)
unset($storage['expire_date']); unset($storage['expire_date']);
@ -314,26 +338,43 @@ class zerobin
// Make sure paste exists. // Make sure paste exists.
if ( if (
!$this->_model()->exists($pasteid) !$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. // Make sure the discussion is opened in this paste.
$paste = $this->_model()->read($pasteid); $paste = $this->_model()->read($pasteid);
if ( if (
!$paste->meta->opendiscussion !$paste->meta->opendiscussion
) return $this->_return_message(1, 'Invalid data.'); )
{
$this->_return_message(1, 'Invalid data.');
return;
}
// Check for improbable collision. // Check for improbable collision.
if ( if (
$this->_model()->existsComment($pasteid, $parentid, $dataid) $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 // New comment
if ( if (
$this->_model()->createComment($pasteid, $parentid, $dataid, $storage) === false $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 // 0 = no error
return $this->_return_message(0, $dataid); $this->_return_message(0, $dataid);
return;
} }
// The user posts a standard paste. // The user posts a standard paste.
else else
@ -341,12 +382,19 @@ class zerobin
// Check for improbable collision. // Check for improbable collision.
if ( if (
$this->_model()->exists($dataid) $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 // New paste
if ( if (
$this->_model()->create($dataid, $storage) === false $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. // Generate the "delete" token.
// The token is the hmac of the pasteid signed with the server salt. // 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()); $deletetoken = hash_hmac('sha1', $dataid, serversalt::get());
// 0 = no error // 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 * @access private
* @param string $dataid * @param string $dataid
* @param string $deletetoken * @param string $deletetoken
* @return string * @return void
*/ */
private function _delete($dataid, $deletetoken) private function _delete($dataid, $deletetoken)
{ {
@ -374,14 +421,14 @@ class zerobin
if (!filter::is_valid_paste_id($dataid)) if (!filter::is_valid_paste_id($dataid))
{ {
$this->_error = 'Invalid paste ID.'; $this->_error = 'Invalid paste ID.';
return ''; return;
} }
// Check that paste exists. // Check that paste exists.
if (!$this->_model()->exists($dataid)) if (!$this->_model()->exists($dataid))
{ {
$this->_error = self::GENERIC_ERROR; $this->_error = self::GENERIC_ERROR;
return ''; return;
} }
// Get the paste itself. // Get the paste itself.
@ -396,10 +443,10 @@ class zerobin
// Delete the paste // Delete the paste
$this->_model()->delete($dataid); $this->_model()->delete($dataid);
$this->_error = self::GENERIC_ERROR; $this->_error = self::GENERIC_ERROR;
return;
} }
if ($deletetoken == 'burnafterreading') { if ($deletetoken == 'burnafterreading') {
header('Content-type: application/json');
if ( if (
isset($paste->meta->burnafterreading) && isset($paste->meta->burnafterreading) &&
$paste->meta->burnafterreading $paste->meta->burnafterreading
@ -407,9 +454,13 @@ class zerobin
{ {
// Delete the paste // Delete the paste
$this->_model()->delete($dataid); $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. // Make sure token is valid.
@ -417,13 +468,12 @@ class zerobin
if (!filter::slow_equals($deletetoken, hash_hmac('sha1', $dataid, serversalt::get()))) if (!filter::slow_equals($deletetoken, hash_hmac('sha1', $dataid, serversalt::get())))
{ {
$this->_error = 'Wrong deletion token. Paste was not deleted.'; $this->_error = 'Wrong deletion token. Paste was not deleted.';
return ''; return;
} }
// Paste exists and deletion token is valid: Delete the paste. // Paste exists and deletion token is valid: Delete the paste.
$this->_model()->delete($dataid); $this->_model()->delete($dataid);
$this->_status = 'Paste was properly deleted.'; $this->_status = 'Paste was properly deleted.';
return '';
} }
/** /**
@ -435,6 +485,12 @@ class zerobin
*/ */
private function _read($dataid) 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? // Is this a valid paste identifier?
if (!filter::is_valid_paste_id($dataid)) if (!filter::is_valid_paste_id($dataid))
{ {
@ -487,6 +543,17 @@ class zerobin
{ {
$this->_error = self::GENERIC_ERROR; $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 bool $status
* @param string $message * @param string $message
* @param array $other * @param array $other
* @return string * @return void
*/ */
private function _return_message($status, $message, $other = array()) private function _return_message($status, $message, $other = array())
{ {
@ -569,6 +636,6 @@ class zerobin
$result['id'] = $message; $result['id'] = $message;
} }
$result += $other; $result += $other;
return json_encode($result); $this->_json = json_encode($result);
} }
} }

View File

@ -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 * @runInSeparateProcess
*/ */