diff --git a/.editorconfig b/.editorconfig index 2c9ddc3c..86252fa0 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/INSTALL.md b/INSTALL.md index b627bc9e..a4bb08cf 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -64,7 +64,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/js/privatebin.js b/js/privatebin.js index 8cf76831..2d7ead1f 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -42,11 +42,22 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { var Helper = (function () { 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 * * @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60} - * @name Helper.entityMap * @private * @enum {Object} * @readonly @@ -160,7 +171,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=
*
*
@@ -251,7 +262,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
@@ -295,6 +306,32 @@ 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() {
+ /*
+ 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;
})();
@@ -689,7 +726,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
var Model = (function () {
var me = {};
- var $cipherData,
+ var pasteData = null,
$templates;
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
- * @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
@@ -819,7 +877,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
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()
{
- $cipherData = $('#cipherdata');
$templates = $('#templates');
}
@@ -1333,8 +1390,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 ciohertext
+ // has been downloaded
Alert.showRemaining("FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.");
$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
* (returns an empty string), use requestPassword.
@@ -3572,7 +3629,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
@@ -3590,18 +3647,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();
+ // show prompt
+ Prompt.requestPassword();
- // if password is there, re-try
- if (password.length === 0) {
- password = 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
@@ -3615,7 +3677,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
@@ -3627,7 +3689,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
function decryptPaste(paste, key, password, ignoreError)
{
- var plaintext
+ var plaintext;
if (ignoreError === true) {
plaintext = CryptTool.decipher(key, password, paste.data);
} 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
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(),
@@ -3761,10 +3825,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);
@@ -3844,6 +3909,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
*
@@ -3853,7 +3930,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
me.showPaste = function()
{
try {
- Model.getPasteId();
Model.getPasteKey();
} catch (err) {
console.error(err);
@@ -3882,26 +3958,17 @@ 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());
-
- 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);
-
+ Model.getPasteData(function (data) {
// restore position
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();
- })
- Uploader.run();
+ }, false); // this false is important as it circumvents the cache
}
/**
@@ -3959,6 +4026,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
@@ -3968,7 +4036,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
Uploader.setUnencryptedData('deletetoken', deleteToken);
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();
}
@@ -4000,13 +4068,23 @@ 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();
+ // 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;
diff --git a/lib/Model/Paste.php b/lib/Model/Paste.php
index 8f171fe1..938d1840 100644
--- a/lib/Model/Paste.php
+++ b/lib/Model/Paste.php
@@ -158,7 +158,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=