name0000-00-00
c
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('' ); $.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=