Force JSON request for getting paste data

This commit is contained in:
rugk 2017-04-11 16:34:13 +02:00
parent ab2e789aee
commit 183ebe518b
No known key found for this signature in database
GPG key ID: 05D40A636AFAB34D
8 changed files with 159 additions and 80 deletions

View file

@ -11,7 +11,6 @@ insert_final_newline = true
[*.css] [*.css]
indent_style = tab indent_style = tab
indent_size = 4
[*.js] [*.js]
indent_style = space indent_style = space
@ -23,7 +22,6 @@ indent_size = 4
[*.jsonld] [*.jsonld]
indent_style = tab indent_style = tab
indent_size = 4
[*.php] [*.php]
indent_style = space indent_style = space
@ -31,7 +29,6 @@ indent_size = 4
[*.{htm,html}] [*.{htm,html}]
indent_style = tab indent_style = tab
indent_size = 4
[*.{md,markdown}] [*.{md,markdown}]
indent_style = space indent_style = space

View file

@ -64,7 +64,7 @@ process (see also
> #### PATH Example > #### PATH Example
> Your PrivateBin installation lives in a subfolder called "paste" inside of > Your PrivateBin installation lives in a subfolder called "paste" inside of
> your document root. The URL looks like this: > your document root. The URL looks like this:
> http://example.com/paste/ > https://example.com/paste/
> >
> The full path of PrivateBin on your webserver is: > The full path of PrivateBin on your webserver is:
> /home/example.com/htdocs/paste > /home/example.com/htdocs/paste

View file

@ -42,11 +42,22 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
var Helper = (function () { var Helper = (function () {
var me = {}; var me = {};
/**
* list of UserAgents (parts) known to belong to a bot
*
* @private
* @enum {Object}
* @readonly
*/
var BadBotUA = [
'Bot',
'bot'
];
/** /**
* character to HTML entity lookup table * character to HTML entity lookup table
* *
* @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60} * @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60}
* @name Helper.entityMap
* @private * @private
* @enum {Object} * @enum {Object}
* @readonly * @readonly
@ -160,7 +171,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
* URLs to handle: * URLs to handle:
* <pre> * <pre>
* magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7 * magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7
* http://example.com:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM= * https://example.com:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* http://user:example.com@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM= * http://user:example.com@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* </pre> * </pre>
* *
@ -251,7 +262,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
/** /**
* get the current location (without search or hash part of the URL), * get the current location (without search or hash part of the URL),
* eg. http://example.com/path/?aaaa#bbbb --> http://example.com/path/ * eg. https://example.com/path/?aaaa#bbbb --> https://example.com/path/
* *
* @name Helper.baseUri * @name Helper.baseUri
* @function * @function
@ -295,6 +306,32 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
baseUri = null; baseUri = null;
} }
/**
* checks whether this is a bot we dislike
*
* @name Helper.isBadBot
* @function
* @return {bool}
*/
me.isBadBot = function() {
/*
if ($.inArray(navigator.userAgent, BadBotUA) >= 0) {
return true;
}
*/
// check whether a bot user agent part can be found in the current
// user agent
var arrayLength = BadBotUA.length;
for (var i = 0; i < arrayLength; i++) {
if (navigator.userAgent.indexOf(BadBotUA) >= 0) {
return true;
}
}
return false;
}
return me; return me;
})(); })();
@ -689,7 +726,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
var Model = (function () { var Model = (function () {
var me = {}; var me = {};
var $cipherData, var pasteData = null,
$templates; $templates;
var id = null, symmetricKey = null; var id = null, symmetricKey = null;
@ -721,32 +758,53 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
} }
/** /**
* check if cipher data was supplied * returns the paste data (inlduing the cipher data)
* *
* @name Model.getCipherData * @name Model.getPasteData
* @function
* @return boolean
*/
me.hasCipherData = function()
{
return (me.getCipherData().length > 0);
}
/**
* returns the cipher data
*
* @name Model.getCipherData
* @function * @function
* @param {function} callback (optional) Called when data is available
* @param {function} useCache (optional) Whether to use the cache or
* force a data reload. Default: true
* @return string * @return string
*/ */
me.getCipherData = function() me.getPasteData = function(callback, useCache)
{ {
return $cipherData.text(); // use cache if possible/allowed
if (useCache !== false && pasteData !== null) {
//execute callback
if (typeof callback === 'function') {
return callback(pasteData);
}
// alternatively just using inline
return pasteData;
}
// reload data
Uploader.prepare();
Uploader.setUrl(Helper.baseUri() + '?' + me.getPasteId());
Uploader.setFailure(function (status, data) {
// revert loading status…
Alert.hideLoading();
TopNav.showViewButtons();
// show error message
Alert.showError(Uploader.parseUploadError(status, data, 'getting paste data'));
})
Uploader.setSuccess(function (status, data) {
pasteData = data;
if (typeof callback === 'function') {
return callback(data);
}
})
Uploader.run();
} }
/** /**
* get the pastes unique identifier from the URL, * get the pastes unique identifier from the URL,
* eg. http://example.com/path/?c05354954c49a487#dfdsdgdgdfgdf returns c05354954c49a487 * eg. https://example.com/path/?c05354954c49a487#dfdsdgdgdfgdf returns c05354954c49a487
* *
* @name Model.getPasteId * @name Model.getPasteId
* @function * @function
@ -819,7 +877,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/ */
me.reset = function() me.reset = function()
{ {
$cipherData = $templates = id = symmetricKey = null; pasteData = $templates = id = symmetricKey = null;
} }
/** /**
@ -832,7 +890,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/ */
me.init = function() me.init = function()
{ {
$cipherData = $('#cipherdata');
$templates = $('#templates'); $templates = $('#templates');
} }
@ -1333,8 +1390,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
if (pasteMetaData.burnafterreading) { if (pasteMetaData.burnafterreading) {
// display paste "for your eyes only" if it is deleted // display paste "for your eyes only" if it is deleted
// actually remove paste, before we claim it is deleted // the paste has been deleted when the JSOn with the ciohertext
Controller.removePaste(Model.getPasteId(), 'burnafterreading'); // has been downloaded
Alert.showRemaining("FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again."); Alert.showRemaining("FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.");
$remainingTime.addClass('foryoureyesonly'); $remainingTime.addClass('foryoureyesonly');
@ -1462,7 +1519,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
} }
/** /**
* getthe cached password * get the cached password
* *
* If you do not get a password with this function * If you do not get a password with this function
* (returns an empty string), use requestPassword. * (returns an empty string), use requestPassword.
@ -3572,7 +3629,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
var me = {}; var me = {};
/** /**
* decrypt data or prompts for password in cvase of failure * decrypt data or prompts for password in case of failure
* *
* @name PasteDecrypter.decryptOrPromptPassword * @name PasteDecrypter.decryptOrPromptPassword
* @private * @private
@ -3590,18 +3647,23 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
// if it fails, request password // if it fails, request password
if (plaindata.length === 0 && password.length === 0) { if (plaindata.length === 0 && password.length === 0) {
// try to get cached password first // show prompt
password = Prompt.getPassword(); Prompt.requestPassword();
// if password is there, re-try // if password is there instantly (legacy method), re-try encryption
if (password.length === 0) { if (Prompt.getPassword().length !== 0) {
password = Prompt.requestPassword(); // recursive
// note: an infinite loop is prevented as the previous if
// clause checks whether a password is already set and ignores
// errors when a password has been passed
return decryptOrPromptPassword(key, password, cipherdata);
} }
// recursive
// note: an infinite loop is prevented as the previous if // if password could not be received yet, the new modal is used,
// clause checks whether a password is already set and ignores // which uses asyncronous event-driven methods to get the password.
// errors when a password has been passed // Thus, we cannot do anything yet, we need to wait for the user
return decryptOrPromptPassword.apply(key, password, cipherdata); // input.
return false;
} }
// if all tries failed, we can only return an error // if all tries failed, we can only return an error
@ -3615,7 +3677,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
/** /**
* decrypt the actual paste text * decrypt the actual paste text
* *
* @name PasteDecrypter.decryptOrPromptPassword * @name PasteDecrypter.decryptPaste
* @private * @private
* @function * @function
* @param {object} paste - paste data in object form * @param {object} paste - paste data in object form
@ -3627,7 +3689,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/ */
function decryptPaste(paste, key, password, ignoreError) function decryptPaste(paste, key, password, ignoreError)
{ {
var plaintext var plaintext;
if (ignoreError === true) { if (ignoreError === true) {
plaintext = CryptTool.decipher(key, password, paste.data); plaintext = CryptTool.decipher(key, password, paste.data);
} else { } else {
@ -3738,7 +3800,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
Alert.showLoading('Decrypting paste…', 0, 'cloud-download'); // @TODO icon maybe rotation-lock, but needs full Glyphicons Alert.showLoading('Decrypting paste…', 0, 'cloud-download'); // @TODO icon maybe rotation-lock, but needs full Glyphicons
if (typeof paste === 'undefined') { if (typeof paste === 'undefined') {
paste = $.parseJSON(Model.getCipherData()); // get cipher data and wait until it is available
Model.getPasteData(me.run);
return;
} }
var key = Model.getPasteKey(), var key = Model.getPasteKey(),
@ -3761,10 +3825,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
// ignore empty paste, as this is allowed when pasting attachments // ignore empty paste, as this is allowed when pasting attachments
decryptPaste(paste, key, password, true); decryptPaste(paste, key, password, true);
} else { } else {
decryptPaste(paste, key, password); if (decryptPaste(paste, key, password) === false) {
return false;
}
} }
// shows the remaining time (until) deletion // shows the remaining time (until) deletion
PasteStatus.showRemainingTime(paste.meta); PasteStatus.showRemainingTime(paste.meta);
@ -3844,6 +3909,18 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
Alert.hideLoading(); Alert.hideLoading();
} }
/**
* shows how we much we love bots that execute JS ;)
*
* @name Controller.showBadBotMessage
* @function
*/
me.showBadBotMessage = function()
{
TopNav.hideAllButtons();
Alert.showError('I love you too, bot…');
}
/** /**
* shows the loaded paste * shows the loaded paste
* *
@ -3853,7 +3930,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
me.showPaste = function() me.showPaste = function()
{ {
try { try {
Model.getPasteId();
Model.getPasteKey(); Model.getPasteKey();
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -3882,26 +3958,17 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
// save window position to restore it later // save window position to restore it later
var orgPosition = $(window).scrollTop(); var orgPosition = $(window).scrollTop();
Uploader.prepare(); Model.getPasteData(function (data) {
Uploader.setUrl(Helper.baseUri() + '?' + Model.getPasteId());
Uploader.setFailure(function (status, data) {
// revert loading status…
Alert.hideLoading();
TopNav.showViewButtons();
// show error message
Alert.showError(Uploader.parseUploadError(status, data, 'refresh display'));
})
Uploader.setSuccess(function (status, data) {
PasteDecrypter.run(data);
// restore position // restore position
window.scrollTo(0, orgPosition); window.scrollTo(0, orgPosition);
PasteDecrypter.run(data);
// NOTE: could create problems as callback may be called
// asyncronously if PasteDecrypter e.g. needs to wait for a
// password being entered
callback(); callback();
}) }, false); // this false is important as it circumvents the cache
Uploader.run();
} }
/** /**
@ -3959,6 +4026,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
* @function * @function
* @param {string} pasteId * @param {string} pasteId
* @param {string} deleteToken * @param {string} deleteToken
* @deprecated not used anymore, de we still need it?
*/ */
me.removePaste = function(pasteId, deleteToken) { me.removePaste = function(pasteId, deleteToken) {
// unfortunately many web servers don't support DELETE (and PUT) out of the box // unfortunately many web servers don't support DELETE (and PUT) out of the box
@ -3968,7 +4036,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
Uploader.setUnencryptedData('deletetoken', deleteToken); Uploader.setUnencryptedData('deletetoken', deleteToken);
Uploader.setFailure(function () { Uploader.setFailure(function () {
Controller.showError(I18n._('Could not delete the paste, it was not stored in burn after reading mode.')); Alert.showError(I18n._('Could not delete the paste, it was not stored in burn after reading mode.'));
}) })
Uploader.run(); Uploader.run();
} }
@ -4000,13 +4068,23 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
UiHelper.init(); UiHelper.init();
Uploader.init(); Uploader.init();
// display an existing paste // check whether existing paste needs to be shown
if (Model.hasCipherData()) { try {
return me.showPaste(); Model.getPasteId();
} catch (e) {
// otherwise create a new paste
return me.newPaste();
} }
// otherwise create a new paste // prevent bots from viewing a paste and potentially deleting data
me.newPaste(); // when burn-after-reading is set
// see https://github.com/elrido/ZeroBin/issues/11
if (Helper.isBadBot()) {
return me.showBadBotMessage();
}
//display an existing paste
return me.showPaste();
} }
return me; return me;

View file

@ -158,7 +158,7 @@ class Paste extends AbstractModel
* *
* The token is the hmac of the pastes ID signed with the server salt. * The token is the hmac of the pastes ID signed with the server salt.
* The paste can be deleted by calling: * The paste can be deleted by calling:
* http://example.com/privatebin/?pasteid=<pasteid>&deletetoken=<deletetoken> * https://example.com/privatebin/?pasteid=<pasteid>&deletetoken=<deletetoken>
* *
* @access public * @access public
* @return string * @return string

View file

@ -147,7 +147,10 @@ class PrivateBin
); );
break; break;
case 'read': case 'read':
$this->_read($this->_request->getParam('pasteid')); // reading paste is disallowed in HTML display
if ($this->_request->isJsonApiCall()) {
$this->_read($this->_request->getParam('pasteid'));
}
break; break;
case 'jsonld': case 'jsonld':
$this->_jsonld($this->_request->getParam('jsonld')); $this->_jsonld($this->_request->getParam('jsonld'));
@ -328,10 +331,10 @@ class PrivateBin
// deleted if it has already expired // deleted if it has already expired
$burnafterreading = $paste->isBurnafterreading(); $burnafterreading = $paste->isBurnafterreading();
if ( if (
($burnafterreading && $deletetoken == 'burnafterreading') || ($burnafterreading && $deletetoken == 'burnafterreading') || // either we burn-after it has been read //@TODO: not needed anymore now?
Filter::slowEquals($deletetoken, $paste->getDeleteToken()) Filter::slowEquals($deletetoken, $paste->getDeleteToken()) // or we manually delete it with this secret token
) { ) {
// Paste exists and deletion token is valid: Delete the paste. // Paste exists and deletion token (if required) is valid: Delete the paste.
$paste->delete(); $paste->delete();
$this->_status = 'Paste was properly deleted.'; $this->_status = 'Paste was properly deleted.';
} else { } else {
@ -373,6 +376,11 @@ class PrivateBin
unset($data->meta->salt); unset($data->meta->salt);
} }
$this->_data = json_encode($data); $this->_data = json_encode($data);
// If the paste was meant to be read only once, delete it.
if ($paste->isBurnafterreading()) {
$paste->delete();
}
} else { } else {
$this->_error = self::GENERIC_ERROR; $this->_error = self::GENERIC_ERROR;
} }

View file

@ -5,5 +5,3 @@ root = false
# special format for PHP templates # special format for PHP templates
[*.php] [*.php]
indent_style = tab indent_style = tab
indent_size = 4

View file

@ -69,7 +69,7 @@ if ($MARKDOWN):
<?php <?php
endif; endif;
?> ?>
<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-7WGautcQxef6PeNh1sNcdCFCNRNo2uULN7QCgjqd+fWalRubtu1mtMEz8BLQ8sKgzPRF8E6dqgBQJ5ycwt03gA==" crossorigin="anonymous"></script> <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-TCwmpH2nrRe3mLYIsGG90FGOFAqZal8SUQVNp16iA7K1B80I//RXp8mYxXdyyk/eluKilwCNY5wo9MYC4vdEoQ==" crossorigin="anonymous"></script>
<!--[if lt IE 10]> <!--[if lt IE 10]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
<![endif]--> <![endif]-->
@ -478,7 +478,6 @@ endif;
</footer> </footer>
</main> </main>
<div id="serverdata" class="hidden" aria-hidden="true"> <div id="serverdata" class="hidden" aria-hidden="true">
<div id="cipherdata"><?php echo htmlspecialchars($CIPHERDATA, ENT_NOQUOTES); ?></div>
<?php <?php
if ($DISCUSSION): if ($DISCUSSION):
?> ?>

View file

@ -47,7 +47,7 @@ if ($MARKDOWN):
<?php <?php
endif; endif;
?> ?>
<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-7WGautcQxef6PeNh1sNcdCFCNRNo2uULN7QCgjqd+fWalRubtu1mtMEz8BLQ8sKgzPRF8E6dqgBQJ5ycwt03gA==" crossorigin="anonymous"></script> <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-TCwmpH2nrRe3mLYIsGG90FGOFAqZal8SUQVNp16iA7K1B80I//RXp8mYxXdyyk/eluKilwCNY5wo9MYC4vdEoQ==" crossorigin="anonymous"></script>
<!--[if lt IE 10]> <!--[if lt IE 10]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
<![endif]--> <![endif]-->
@ -227,7 +227,6 @@ endif;
</div> </div>
</section> </section>
<div id="serverdata" class="hidden" aria-hidden="true"> <div id="serverdata" class="hidden" aria-hidden="true">
<div id="cipherdata" class="hidden"><?php echo htmlspecialchars($CIPHERDATA, ENT_NOQUOTES); ?></div>
<?php <?php
if ($DISCUSSION): if ($DISCUSSION):
?> ?>