diff --git a/js/privatebin.js b/js/privatebin.js index 8f559233..71ec01a6 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -39,18 +39,131 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ let z; + /** + * CryptoData class + * + * bundles helper fuctions used in both paste and comment formats + * + * @name CryptoData + * @class + */ + function CryptoData(data) { + this.v = 1; + // store all keys in the default locations for drop-in replacement + for (let key in data) { + this[key] = data[key]; + } + + /** + * gets the cipher data (cipher text + adata) + * + * @name Paste.getCipherData + * @function + * @return {Array}|{string} + */ + this.getCipherData = function() + { + return this.v === 1 ? this.data : [this.ct, this.adata]; + } + } + /** * Paste class - * + * * bundles helper fuctions around the paste formats * * @name Paste * @class */ function Paste(data) { - // store all keys in the default locations for drop-in replacement - for (let key in data) { - this[key] = raw[key]; + // inherit constructor and methods of CryptoData + CryptoData.call(this, data); + + /** + * gets the used formatter + * + * @name Paste.getFormat + * @function + * @return {string} + */ + this.getFormat = function() + { + return this.v === 1 ? this.meta.formatter : this.adata[1]; + } + + /** + * gets the remaining seconds before the paste expires + * + * returns 0 if there is no expiration + * + * @name Paste.getTimeToLive + * @function + * @return {string} + */ + this.getTimeToLive = function() + { + return (this.v === 1 ? this.meta.remaining_time : this.meta.time_to_live) || 0; + } + + /** + * is burn-after-reading enabled + * + * @name Paste.isBurnAfterReadingEnabled + * @function + * @return {bool} + */ + this.isBurnAfterReadingEnabled = function() + { + return (this.v === 1 ? this.meta.burnafterreading : this.adata[3]); + } + + /** + * are discussions enabled + * + * @name Paste.isDiscussionEnabled + * @function + * @return {bool} + */ + this.isDiscussionEnabled = function() + { + return (this.v === 1 ? this.meta.opendiscussion : this.adata[2]); + } + } + + /** + * Comment class + * + * bundles helper fuctions around the comment formats + * + * @name Comment + * @class + */ + function Comment(data) { + // inherit constructor and methods of CryptoData + CryptoData.call(this, data); + + /** + * gets the UNIX timestamp of the comment creation + * + * @name Paste.getCreated + * @function + * @return {int} + */ + this.getCreated = function() + { + return this.meta[this.v === 1 ? 'postdate' : 'created']; + } + + /** + * gets the icon of the comment submitter + * + * @name Paste.getIcon + * @function + * @return {string} + */ + this.getIcon = function() + { + return this.meta[this.v === 1 ? 'vizhash' : 'icon'] || ''; } } @@ -148,6 +261,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * convert URLs to clickable links. + * * URLs to handle: *
* magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7 @@ -251,16 +365,6 @@ jQuery.PrivateBin = (function($, RawDeflate) { return baseUri; }; - /** - * resets state, used for unit testing - * - * @name Helper.reset - * @function - */ - me.reset = function() - { - baseUri = null; - }; /** * checks whether this is a bot we dislike @@ -277,10 +381,46 @@ jQuery.PrivateBin = (function($, RawDeflate) { return true; } } - return false; } + /** + * wrap an object into a Paste, used for mocking in the unit tests + * + * @name Helper.PasteFactory + * @function + * @param {object} data + * @return {Paste} + */ + me.PasteFactory = function(data) + { + return new Paste(data); + }; + + /** + * wrap an object into a Comment, used for mocking in the unit tests + * + * @name Helper.CommentFactory + * @function + * @param {object} data + * @return {Comment} + */ + me.CommentFactory = function(data) + { + return new Comment(data); + }; + + /** + * resets state, used for unit testing + * + * @name Helper.reset + * @function + */ + me.reset = function() + { + baseUri = null; + }; + return me; })(); @@ -1058,10 +1198,10 @@ jQuery.PrivateBin = (function($, RawDeflate) { Alert.showError(ServerInteraction.parseUploadError(status, data, 'get paste data')); }); ServerInteraction.setSuccess(function (status, data) { - pasteData = data; + pasteData = new Paste(data); if (typeof callback === 'function') { - return callback(data); + return callback(pasteData); } }); ServerInteraction.run(); @@ -1680,11 +1820,11 @@ jQuery.PrivateBin = (function($, RawDeflate) { * * @name PasteStatus.showRemainingTime * @function - * @param {object} paste + * @param {Paste} paste */ me.showRemainingTime = function(paste) { - if ((paste.adata && paste.adata[3]) || paste.meta.burnafterreading) { + if (paste.isBurnAfterReadingEnabled()) { // display paste "for your eyes only" if it is deleted // the paste has been deleted when the JSON with the ciphertext @@ -1696,9 +1836,9 @@ jQuery.PrivateBin = (function($, RawDeflate) { // discourage cloning (it cannot really be prevented) TopNav.hideCloneButton(); - } else if (paste.meta.time_to_live || paste.meta.remaining_time) { + } else if (paste.getTimeToLive() > 0) { // display paste expiration - let expiration = Helper.secondsToHuman(paste.meta.time_to_live || paste.meta.remaining_time), + let expiration = Helper.secondsToHuman(paste.getTimeToLive()), expirationLabel = [ 'This document will expire in %d ' + expiration[1] + '.', 'This document will expire in %d ' + expiration[1] + 's.' @@ -2912,7 +3052,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * * @name DiscussionViewer.addComment * @function - * @param {object} comment + * @param {Comment} comment * @param {string} commentText * @param {string} nickname */ @@ -2944,14 +3084,15 @@ jQuery.PrivateBin = (function($, RawDeflate) { // set date $commentEntry.find('span.commentdate') - .text(' (' + (new Date((comment.meta.created || comment.meta.postdate) * 1000).toLocaleString()) + ')') + .text(' (' + (new Date(comment.getCreated() * 1000).toLocaleString()) + ')') .attr('title', 'CommentID: ' + comment.id); // if an avatar is available, display it - if (comment.meta.icon || comment.meta.vizhash) { + const icon = comment.getIcon(); + if (icon) { $commentEntry.find('span.nickname') .before( - ' ' + ' ' ); $(document).on('languageLoaded', function () { $commentEntry.find('img.vizhash') @@ -3736,11 +3877,9 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ function success(status, result) { - // add useful data to result - result.encryptionKey = symmetricKey; - result.requestData = data; - if (successFunc !== null) { + // add useful data to result + result.encryptionKey = symmetricKey; successFunc(status, result); } } @@ -4239,7 +4378,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @private * @async * @function - * @param {object} paste - paste data in object form + * @param {Paste} paste - paste data in object form * @param {string} key * @param {string} password * @throws {string} @@ -4247,9 +4386,9 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ async function decryptPaste(paste, key, password) { - const pastePlain = await decryptOrPromptPassword( + let pastePlain = await decryptOrPromptPassword( key, password, - paste.hasOwnProperty('ct') ? [paste.ct, paste.adata] : paste.data + paste.getCipherData() ); if (pastePlain === false) { if (password.length === 0) { @@ -4262,15 +4401,14 @@ jQuery.PrivateBin = (function($, RawDeflate) { let format = '', text = ''; - if (paste.hasOwnProperty('ct')) { + if (paste.v > 1) { // version 2 paste const pasteMessage = JSON.parse(pastePlain); if (pasteMessage.hasOwnProperty('attachment') && pasteMessage.hasOwnProperty('attachment_name')) { AttachmentViewer.setAttachment(pasteMessage.attachment, pasteMessage.attachment_name); AttachmentViewer.showAttachment(); } - format = paste.adata[1]; - text = pasteMessage.paste; + pastePlain = pasteMessage.paste; } else { // version 1 paste if (paste.hasOwnProperty('attachment') && paste.hasOwnProperty('attachmentname')) { @@ -4282,11 +4420,9 @@ jQuery.PrivateBin = (function($, RawDeflate) { AttachmentViewer.showAttachment(); }); } - format = paste.meta.formatter; - text = pastePlain; } - PasteViewer.setFormat(format); - PasteViewer.setText(text); + PasteViewer.setFormat(paste.getFormat()); + PasteViewer.setText(pastePlain); PasteViewer.run(); } @@ -4297,7 +4433,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @private * @async * @function - * @param {object} paste - paste data in object form + * @param {Paste} paste - paste data in object form * @param {string} key * @param {string} password * @return {Promise} @@ -4310,38 +4446,39 @@ jQuery.PrivateBin = (function($, RawDeflate) { const commentDecryptionPromises = []; // iterate over comments for (let i = 0; i < paste.comments.length; ++i) { - if (paste.comments[i].hasOwnProperty('v') && paste.comments[i].v === 2) { + const comment = new Comment(paste.comments[i]), + commentPromise = CryptTool.decipher(key, password, comment.getCipherData()); + paste.comments[i] = comment; + if (comment.v > 1) { // version 2 comment commentDecryptionPromises.push( - CryptTool.decipher(key, password, [paste.comments[i].ct, paste.comments[i].adata]) - .then((commentJson) => { - const commentMessage = JSON.parse(commentJson); - return [ - commentMessage.hasOwnProperty('comment') ? commentMessage.comment : '', - commentMessage.hasOwnProperty('nickname') ? commentMessage.nickname : '' - ]; - }) + commentPromise.then(function (commentJson) { + const commentMessage = JSON.parse(commentJson); + return [ + commentMessage.comment || '', + commentMessage.nickname || '' + ]; + }) ); } else { // version 1 comment commentDecryptionPromises.push( Promise.all([ - CryptTool.decipher(key, password, paste.comments[i].data), - paste.comments[i].meta.nickname ? + commentPromise, + paste.comments[i].meta.hasOwnProperty('nickname') ? CryptTool.decipher(key, password, paste.comments[i].meta.nickname) : Promise.resolve('') ]) ); } } - return Promise.all(commentDecryptionPromises).then((plaintexts) => { + return Promise.all(commentDecryptionPromises).then(function (plaintexts) { for (let i = 0; i < paste.comments.length; ++i) { if (plaintexts[i][0].length === 0) { continue; } - const comment = paste.comments[i]; DiscussionViewer.addComment( - comment, + paste.comments[i], plaintexts[i][0], plaintexts[i][1] ); @@ -4376,7 +4513,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * * @name PasteDecrypter.run * @function - * @param {Object} [paste] - (optional) object including comments to display (items = array with keys ('data','meta')) + * @param {Paste} [paste] - (optional) object including comments to display (items = array with keys ('data','meta')) */ me.run = function(paste) { @@ -4402,7 +4539,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { decryptionPromises.push(decryptPaste(paste, key, password)); // if the discussion is opened on this paste, display it - if ((paste.adata && paste.adata[2]) || paste.meta.opendiscussion) { + if (paste.isDiscussionEnabled()) { decryptionPromises.push(decryptComments(paste, key, password)); } @@ -4417,6 +4554,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { .catch((err) => { // wait for the user to type in the password, // then PasteDecrypter.run will be called again + console.error(err); }); }; diff --git a/js/test/DiscussionViewer.js b/js/test/DiscussionViewer.js index ff06c01f..3095d8e4 100644 --- a/js/test/DiscussionViewer.js +++ b/js/test/DiscussionViewer.js @@ -63,7 +63,7 @@ describe('DiscussionViewer', function () { comments.forEach(function (comment) { comment.id = comment.idArray.join(''); comment.parentid = comment.parentidArray.join(''); - $.PrivateBin.DiscussionViewer.addComment(comment, comment.data, comment.meta.nickname); + $.PrivateBin.DiscussionViewer.addComment($.PrivateBin.Helper.CommentFactory(comment), comment.data, comment.meta.nickname); }); results.push( $('#discussion').hasClass('hidden') diff --git a/js/test/PasteStatus.js b/js/test/PasteStatus.js index 823840a7..7479ae1f 100644 --- a/js/test/PasteStatus.js +++ b/js/test/PasteStatus.js @@ -62,10 +62,10 @@ describe('PasteStatus', function () { result; $('body').html(''); $.PrivateBin.PasteStatus.init(); - $.PrivateBin.PasteStatus.showRemainingTime({'meta': { + $.PrivateBin.PasteStatus.showRemainingTime($.PrivateBin.Helper.PasteFactory({'meta': { 'burnafterreading': burnafterreading, 'remaining_time': remainingTime - }}); + }})); if (burnafterreading) { result = $('#remainingtime').hasClass('foryoureyesonly') && !$('#remainingtime').hasClass('hidden'); @@ -100,12 +100,13 @@ describe('PasteStatus', function () { result; $('body').html(''); $.PrivateBin.PasteStatus.init(); - $.PrivateBin.PasteStatus.showRemainingTime({ + $.PrivateBin.PasteStatus.showRemainingTime($.PrivateBin.Helper.PasteFactory({ 'adata': [null, null, null, burnafterreading], + 'v': 2, 'meta': { 'time_to_live': remainingTime } - }); + })); if (burnafterreading) { result = $('#remainingtime').hasClass('foryoureyesonly') && !$('#remainingtime').hasClass('hidden'); diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index c89a172f..1851e1bd 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -72,7 +72,7 @@ if ($MARKDOWN): endif; ?> - + diff --git a/tpl/page.php b/tpl/page.php index 14aed276..4bd1910b 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -50,7 +50,7 @@ if ($MARKDOWN): endif; ?> - +