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:
parent
ded24b43ab
commit
b25022e403
|
@ -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
|
||||||
|
{
|
||||||
|
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) {
|
else if (data.status == 1) {
|
||||||
showError('Could not post comment: '+data.message);
|
showError('Could not post comment: ' + data.message);
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
showError('Could not post comment.');
|
{
|
||||||
|
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.');
|
||||||
|
|
163
lib/zerobin.php
163
lib/zerobin.php
|
@ -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,8 +123,16 @@ class zerobin
|
||||||
$this->_read($_SERVER['QUERY_STRING']);
|
$this->_read($_SERVER['QUERY_STRING']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// display ZeroBin frontend
|
// output JSON or HTML
|
||||||
$this->_view();
|
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)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue