diff --git a/.editorconfig b/.editorconfig index 2c9ddc3..86252fa 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,6 @@ insert_final_newline = true [*.css] indent_style = tab -indent_size = 4 [*.js] indent_style = space @@ -23,7 +22,6 @@ indent_size = 4 [*.jsonld] indent_style = tab -indent_size = 4 [*.php] indent_style = space @@ -31,7 +29,6 @@ indent_size = 4 [*.{htm,html}] indent_style = tab -indent_size = 4 [*.{md,markdown}] indent_style = space diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e77d86..ff49712 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * CHANGED: Minimum required PHP version is 5.4 (#186) * CHANGED: Shipped .htaccess files were updated for Apache 2.4 (#192) * CHANGED: Cleanup of bootstrap template variants and moved icons to `img` directory + * CHANGED: Removed option to hide clone button on expiring pastes, since this requires reading the paste for rendering the template, which leaks information on the pastes state * **1.1.1 (2017-10-06)** * CHANGED: Switched to `.php` file extension for configuration file, to avoid leaking configuration data in unprotected installation. * **1.1 (2016-12-26)** diff --git a/INSTALL.md b/INSTALL.md index 6eebfe9..29dc7f1 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -39,7 +39,7 @@ process (see also > #### PATH Example > Your PrivateBin installation lives in a subfolder called "paste" inside of > 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: > /home/example.com/htdocs/paste diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index ed494b7..0f71c87 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -22,10 +22,6 @@ fileupload = false ; preselect the burn-after-reading feature, defaults to false burnafterreadingselected = false -; delete a burn after reading paste immediatly after it is first accessed from -; the server and do not wait for a successful decryption -instantburnafterreading = false - ; which display mode to preselect by default, defaults to "plaintext" ; make sure the value exists in [formatter_options] defaultformatter = "plaintext" @@ -85,10 +81,6 @@ zerobincompatibility = false ; make sure the value exists in [expire_options] default = "1week" -; optionally the "clone" button can be disabled on expiring pastes -; note that this only hides the button, copy & paste is still possible -; clone = false - [expire_options] ; Set each one of these to the number of seconds in the expiration period, ; or 0 if it should never expire diff --git a/js/privatebin.js b/js/privatebin.js index 3c8b02d..9013156 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -45,6 +45,18 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { var Helper = (function () { var me = {}; + /** + * blacklist of UserAgents (parts) known to belong to a bot + * + * @private + * @enum {Object} + * @readonly + */ + var BadBotUA = [ + 'Bot', + 'bot' + ]; + /** * cache for script location * @@ -121,7 +133,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { * URLs to handle: *
          *     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=
          * 
* @@ -204,7 +216,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { /** * 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 * @function @@ -232,6 +244,26 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { baseUri = null; }; + /** + * checks whether this is a bot we dislike + * + * @name Helper.isBadBot + * @function + * @return {bool} + */ + me.isBadBot = function() { + // 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; })(); @@ -358,7 +390,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { // file is loaded } - // for all other langauges than English for which this behaviour + // for all other languages than English for which this behaviour // is expected as it is built-in, log error if (language !== null && language !== 'en') { console.error('Missing translation for: \'' + messageId + '\' in language ' + language); @@ -623,7 +655,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { var Model = (function () { var me = {}; - var $cipherData, + var pasteData = null, $templates; var id = null, symmetricKey = null; @@ -653,32 +685,53 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { }; /** - * check if cipher data was supplied + * returns the paste data (including the cipher data) * - * @name Model.getCipherData - * @function - * @return boolean - */ - me.hasCipherData = function() - { - return me.getCipherData().length > 0; - }; - - /** - * returns the cipher data - * - * @name Model.getCipherData + * @name Model.getPasteData * @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 */ - 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, - * eg. http://example.com/path/?c05354954c49a487#dfdsdgdgdfgdf returns c05354954c49a487 + * eg. https://example.com/path/?c05354954c49a487#dfdsdgdgdfgdf returns c05354954c49a487 * * @name Model.getPasteId * @function @@ -688,6 +741,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { me.getPasteId = function() { if (id === null) { + // Attention: This also returns the delete token inside of the ID, if it is specified id = window.location.search.substring(1); if (id === '') { @@ -696,7 +750,19 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { } return id; - }; + } + + /** + * Returns true, when the URL has a delete token and the current call was used for deleting a paste. + * + * @name Model.hasDeleteToken + * @function + * @return {bool} + */ + me.hasDeleteToken = function() + { + return window.location.search.indexOf('deletetoken') !== -1; + } /** * return the deciphering key stored in anchor part of the URL @@ -751,7 +817,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { */ me.reset = function() { - $cipherData = $templates = id = symmetricKey = null; + pasteData = $templates = id = symmetricKey = null; }; /** @@ -764,7 +830,6 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { */ me.init = function() { - $cipherData = $('#cipherdata'); $templates = $('#templates'); }; @@ -1259,8 +1324,8 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { if (pasteMetaData.burnafterreading) { // display paste "for your eyes only" if it is deleted - // actually remove paste, before we claim it is deleted - Controller.removePaste(Model.getPasteId(), 'burnafterreading'); + // the paste has been deleted when the JSON with the ciphertext + // has been downloaded Alert.showRemaining("FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again."); $remainingTime.addClass('foryoureyesonly'); @@ -1402,6 +1467,21 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { return password; }; + /** + * resets the password to an empty string + * + * @name Prompt.reset + * @function + */ + me.reset = function() + { + // reset internal + password = ''; + + // and also reset UI + $passwordDecrypt.val(''); + } + /** * init status manager * @@ -2149,7 +2229,6 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { loadedFile = $fileInput[0].files[0]; $dragAndDropFileName.text(''); } else { - // TODO: cannot set original $fileWrap here for security reasons… $dragAndDropFileName.text(loadedFile.name); } @@ -2295,6 +2374,10 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { if (items.hasOwnProperty(i)) { var item = items[i]; if (item.kind === 'file') { + //Clear the file input: + $fileInput.wrap('
').closest('form').get(0).reset(); + $fileInput.unwrap(); + readFileData(item.getAsFile()); } } @@ -2685,9 +2768,11 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { $passwordInput, $rawTextButton, $qrCodeLink, - $sendButton; + $sendButton, + $retryButton; - var pasteExpiration = '1week'; + var pasteExpiration = '1week', + retryButtonCallback; /** * set the expiration on bootstrap templates in dropdown @@ -2836,6 +2921,19 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { Controller.newPaste(); } + /** + * retrys some callback registered before + * + * @name TopNav.clickRetryButton + * @private + * @function + * @param {Event} event + */ + function clickRetryButton(event) + { + retryButtonCallback(event); + } + /** * removes the existing attachment * @@ -2915,8 +3013,8 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { return; } - $newButton.addClass('hidden'); $cloneButton.addClass('hidden'); + $newButton.addClass('hidden'); $rawTextButton.addClass('hidden'); $qrCodeLink.addClass('hidden'); @@ -2948,14 +3046,14 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { return; } - $sendButton.removeClass('hidden'); + $attach.removeClass('hidden'); + $burnAfterReadingOption.removeClass('hidden'); $expiration.removeClass('hidden'); $formatter.removeClass('hidden'); - $burnAfterReadingOption.removeClass('hidden'); - $openDiscussionOption.removeClass('hidden'); $newButton.removeClass('hidden'); + $openDiscussionOption.removeClass('hidden'); $password.removeClass('hidden'); - $attach.removeClass('hidden'); + $sendButton.removeClass('hidden'); createButtonsDisplayed = true; }; @@ -2996,6 +3094,28 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { $newButton.removeClass('hidden'); }; + /** + * only shows the "retry" button + * + * @name TopNav.showRetryButton + * @function + */ + me.showRetryButton = function() + { + $retryButton.removeClass('hidden'); + } + + /** + * hides the "retry" button + * + * @name TopNav.hideRetryButton + * @function + */ + me.hideRetryButton = function() + { + $retryButton.addClass('hidden'); + } + /** * only hides the clone button * @@ -3140,6 +3260,18 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { return $customAttachment; }; + /** + * Set a function to call when the retry button is clicked. + * + * @name TopNav.setRetryCallback + * @function + * @param {function} callback + */ + me.setRetryCallback = function(callback) + { + retryButtonCallback = callback; + } + /** * init navigation manager * @@ -3165,6 +3297,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { $password = $('#password'); $passwordInput = $('#passwordinput'); $rawTextButton = $('#rawtextbutton'); + $retryButton = $('#retrybutton'); $sendButton = $('#sendbutton'); $qrCodeLink = $('#qrcodelink'); @@ -3180,6 +3313,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { $sendButton.click(PasteEncrypter.sendPaste); $cloneButton.click(Controller.clonePaste); $rawTextButton.click(rawText); + $retryButton.click(clickRetryButton); $fileRemoveButton.click(removeAttachment); $qrCodeLink.click(displayQrCode); @@ -3815,7 +3949,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { 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 * @private @@ -3833,18 +3967,23 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { // if it fails, request password if (plaindata.length === 0 && password.length === 0) { - // try to get cached password first - password = Prompt.getPassword(); - - // if password is there, re-try - if (password.length === 0) { - password = Prompt.requestPassword(); + // show prompt + Prompt.requestPassword(); + + // if password is there instantly (legacy method), re-try encryption + if (Prompt.getPassword().length !== 0) { + // 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 - // clause checks whether a password is already set and ignores - // errors when a password has been passed - return decryptOrPromptPassword.apply(key, password, cipherdata); + + // if password could not be received yet, the new modal is used, + // which uses asyncronous event-driven methods to get the password. + // Thus, we cannot do anything yet, we need to wait for the user + // input. + return false; } // if all tries failed, we can only return an error @@ -3858,7 +3997,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { /** * decrypt the actual paste text * - * @name PasteDecrypter.decryptOrPromptPassword + * @name PasteDecrypter.decryptPaste * @private * @function * @param {object} paste - paste data in object form @@ -3981,7 +4120,9 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { Alert.showLoading('Decrypting paste…', 'cloud-download'); 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(), @@ -4006,10 +4147,11 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { // ignore empty paste, as this is allowed when pasting attachments decryptPaste(paste, key, password, true); } else { - decryptPaste(paste, key, password); + if (decryptPaste(paste, key, password) === false) { + return false; + } } - // shows the remaining time (until) deletion PasteStatus.showRemainingTime(paste.meta); @@ -4025,7 +4167,15 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { // log and show error console.error(err); - Alert.showError('Could not decrypt data (Wrong key?)'); + Alert.showError('Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.'); + // reset password, so it can be re-entered and sow retry button + Prompt.reset(); + TopNav.setRetryCallback(function () { + TopNav.hideRetryButton(); + + me.run(paste); + }); + TopNav.showRetryButton(); } }; @@ -4090,6 +4240,18 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { 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 * @@ -4099,7 +4261,6 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { me.showPaste = function() { try { - Model.getPasteId(); Model.getPasteKey(); } catch (err) { console.error(err); @@ -4127,29 +4288,36 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { // save window position to restore it later var orgPosition = $(window).scrollTop(); - Uploader.prepare(); - Uploader.setUrl(Helper.baseUri() + '?' + Model.getPasteId()); + Model.getPasteData(function (data) { + Uploader.prepare(); + Uploader.setUrl(Helper.baseUri() + '?' + Model.getPasteId()); - Uploader.setFailure(function (status, data) { - // revert loading status… - Alert.hideLoading(); - TopNav.showViewButtons(); + 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); + // show error message + Alert.showError( + Uploader.parseUploadError(status, data, 'refresh display') + ); + }); + Uploader.setSuccess(function (status, data) { + PasteDecrypter.run(data); - // restore position - window.scrollTo(0, orgPosition); + // restore position + window.scrollTo(0, orgPosition); - callback(); - }); - Uploader.run(); - }; + 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(); + }); + Uploader.run(); + }, false); // this false is important as it circumvents the cache + } /** * clone the current paste @@ -4207,6 +4375,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { * @function * @param {string} pasteId * @param {string} deleteToken + * @deprecated not used anymore, de we still need it? */ me.removePaste = function(pasteId, deleteToken) { // unfortunately many web servers don't support DELETE (and PUT) out of the box @@ -4251,14 +4420,30 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) { UiHelper.init(); Uploader.init(); - // display an existing paste - if (Model.hasCipherData()) { - return me.showPaste(); + // check whether existing paste needs to be shown + try { + Model.getPasteId(); + } catch (e) { + // otherwise create a new paste + return me.newPaste(); } - // otherwise create a new paste - me.newPaste(); - }; + // if delete token is passed (i.e. paste has been deleted by this access) + // there is no more stuf we need to do + if (Model.hasDeleteToken()) { + return; + } + + // prevent bots from viewing a paste and potentially deleting data + // 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; })(window, document); diff --git a/js/test/Model.js b/js/test/Model.js index 3763d93..8605476 100644 --- a/js/test/Model.js +++ b/js/test/Model.js @@ -70,48 +70,6 @@ describe('Model', function () { ); }); - describe('hasCipherData', function () { - before(function () { - $.PrivateBin.Model.reset(); - cleanup(); - }); - - jsc.property( - 'checks if the element with id "cipherdata" contains any data', - 'asciistring', - function (value) { - value = common.htmlEntities(value).trim(); - $('body').html('
' + value + '
'); - $.PrivateBin.Model.init(); - var result = $.PrivateBin.Model.hasCipherData(); - $.PrivateBin.Model.reset(); - return (value.length > 0) === result; - } - ); - }); - - describe('getCipherData', function () { - before(function () { - $.PrivateBin.Model.reset(); - cleanup(); - }); - - jsc.property( - 'returns the contents of the element with id "cipherdata"', - 'asciistring', - function (value) { - value = common.htmlEntities(value).trim(); - $('body').html('
' + value + '
'); - $.PrivateBin.Model.init(); - var result = common.htmlEntities( - $.PrivateBin.Model.getCipherData() - ); - $.PrivateBin.Model.reset(); - return value === result; - } - ); - }); - describe('getPasteId', function () { this.timeout(30000); before(function () { diff --git a/js/test/Prompt.js b/js/test/Prompt.js index 2b55ae7..0e65b25 100644 --- a/js/test/Prompt.js +++ b/js/test/Prompt.js @@ -7,6 +7,7 @@ describe('Prompt', function () { describe('requestPassword & getPassword', function () { this.timeout(30000); before(function () { + $.PrivateBin.Model.reset(); cleanup(); }); @@ -15,7 +16,7 @@ describe('Prompt', function () { 'string', function (password) { password = password.replace(/\r+/g, ''); - var clean = jsdom('', {url: 'ftp://example.com/#0'}); + var clean = jsdom('', {url: 'ftp://example.com/?0'}); $('body').html( '
{}
' + '' ); $.PrivateBin.Model.init(); $.PrivateBin.Prompt.init(); @@ -31,6 +32,7 @@ describe('Prompt', function () { $('#passworddecrypt').val(password); $('#passwordform').submit(); var result = $.PrivateBin.Prompt.getPassword(); + $.PrivateBin.Model.reset(); clean(); return result === password; } diff --git a/lib/Configuration.php b/lib/Configuration.php index f505b90..4810569 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -43,7 +43,6 @@ class Configuration 'password' => true, 'fileupload' => false, 'burnafterreadingselected' => false, - 'instantburnafterreading' => false, 'defaultformatter' => 'plaintext', 'syntaxhighlightingtheme' => null, 'sizelimit' => 2097152, @@ -59,7 +58,6 @@ class Configuration ), 'expire' => array( 'default' => '1week', - 'clone' => true, ), 'expire_options' => array( '5min' => 300, diff --git a/lib/Model/Paste.php b/lib/Model/Paste.php index 1bac7c8..d8749a9 100644 --- a/lib/Model/Paste.php +++ b/lib/Model/Paste.php @@ -49,7 +49,7 @@ class Paste extends AbstractModel } // check if non-expired burn after reading paste needs to be deleted - if (property_exists($data->meta, 'burnafterreading') && $data->meta->burnafterreading && $this->_conf->getKey('instantburnafterreading')) { + if (property_exists($data->meta, 'burnafterreading') && $data->meta->burnafterreading) { $this->delete(); } @@ -72,6 +72,12 @@ class Paste extends AbstractModel $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(); + } + return $this->_data; } @@ -163,7 +169,7 @@ class Paste extends AbstractModel * * The token is the hmac of the pastes ID signed with the server salt. * The paste can be deleted by calling: - * http://example.com/privatebin/?pasteid=&deletetoken= + * https://example.com/privatebin/?pasteid=&deletetoken= * * @access public * @return string diff --git a/lib/PrivateBin.php b/lib/PrivateBin.php index 39fb2db..33802e1 100644 --- a/lib/PrivateBin.php +++ b/lib/PrivateBin.php @@ -52,22 +52,6 @@ class PrivateBin */ private $_conf; - /** - * data - * - * @access private - * @var string - */ - private $_data = ''; - - /** - * does the paste expire - * - * @access private - * @var bool - */ - private $_doesExpire = false; - /** * error message * @@ -327,10 +311,10 @@ class PrivateBin // deleted if it has already expired $burnafterreading = $paste->isBurnafterreading(); if ( - ($burnafterreading && $deletetoken == 'burnafterreading') || - Filter::slowEquals($deletetoken, $paste->getDeleteToken()) + ($burnafterreading && $deletetoken == 'burnafterreading') || // either we burn-after it has been read //@TODO: not needed anymore now? + 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(); $this->_status = 'Paste was properly deleted.'; } else { @@ -356,35 +340,30 @@ class PrivateBin } /** - * Read an existing paste or comment + * Read an existing paste or comment, only allowed via a JSON API call * * @access private * @param string $dataid */ private function _read($dataid) { + if (!$this->_request->isJsonApiCall()) { + return; + } + try { $paste = $this->_model->getPaste($dataid); if ($paste->exists()) { - $data = $paste->get(); - $this->_doesExpire = property_exists($data, 'meta') && property_exists($data->meta, 'expire_date'); + $data = $paste->get(); if (property_exists($data->meta, 'salt')) { unset($data->meta->salt); } - $this->_data = json_encode($data); + $this->_return_message(0, $dataid, (array) $data); } else { - $this->_error = self::GENERIC_ERROR; + $this->_return_message(1, self::GENERIC_ERROR); } } catch (Exception $e) { - $this->_error = $e->getMessage(); - } - - if ($this->_request->isJsonApiCall()) { - if (strlen($this->_error)) { - $this->_return_message(1, $this->_error); - } else { - $this->_return_message(0, $dataid, json_decode($this->_data, true)); - } + $this->_return_message(1, $e->getMessage()); } } @@ -425,7 +404,6 @@ class PrivateBin $page = new View; $page->assign('NAME', $this->_conf->getKey('name')); - $page->assign('CIPHERDATA', $this->_data); $page->assign('ERROR', I18n::_($this->_error)); $page->assign('STATUS', I18n::_($this->_status)); $page->assign('VERSION', self::VERSION); @@ -445,7 +423,6 @@ class PrivateBin $page->assign('LANGUAGES', I18n::getLanguageLabels(I18n::getAvailableLanguages())); $page->assign('EXPIRE', $expire); $page->assign('EXPIREDEFAULT', $this->_conf->getKey('default', 'expire')); - $page->assign('EXPIRECLONE', !$this->_doesExpire || ($this->_doesExpire && $this->_conf->getKey('clone', 'expire'))); $page->assign('URLSHORTENER', $this->_conf->getKey('urlshortener')); $page->assign('QRCODE', $this->_conf->getKey('qrcode')); $page->draw($this->_conf->getKey('template')); diff --git a/tpl/.editorconfig b/tpl/.editorconfig index 30c7ad2..9159bf4 100644 --- a/tpl/.editorconfig +++ b/tpl/.editorconfig @@ -5,5 +5,3 @@ root = false # special format for PHP templates [*.php] indent_style = tab -indent_size = 4 - diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 7abcca6..605737f 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -75,7 +75,7 @@ if ($MARKDOWN): - + @@ -147,6 +147,11 @@ endif;
  • + +
  • +
  • @@ -159,15 +164,9 @@ else: endif; ?> - - @@ -519,19 +518,18 @@ endif; - diff --git a/tpl/page.php b/tpl/page.php index a4a94ce..5533f55 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -54,7 +54,7 @@ if ($QRCODE): - + @@ -96,14 +96,9 @@ endif;
    + - -
    -
    #', - $content, - $template . ': outputs data correctly' - ); $this->assertRegExp( '#]+id="errormessage"[^>]*>.*' . self::$error . '#s', $content,