diff --git a/js/common.js b/js/common.js index 96a4bf11..08a4907f 100644 --- a/js/common.js +++ b/js/common.js @@ -89,6 +89,8 @@ function parseMime(line) { } // common testing helper functions +exports.atob = atob; +exports.btoa = btoa; /** * convert all applicable characters to HTML entities diff --git a/js/privatebin.js b/js/privatebin.js index 79b2d40a..f4bb3caf 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -45,8 +45,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @name Helper * @class */ - var Helper = (function () { - var me = {}; + const Helper = (function () { + const me = {}; /** * blacklist of UserAgents (parts) known to belong to a bot @@ -55,7 +55,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @enum {Object} * @readonly */ - var BadBotUA = [ + const BadBotUA = [ 'Bot', 'bot' ]; @@ -67,7 +67,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @private * @enum {string|null} */ - var baseUri = null; + let baseUri = null; /** * converts a duration (in seconds) into human friendly approximation @@ -79,7 +79,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ me.secondsToHuman = function(seconds) { - var v; + let v; if (seconds < 60) { v = Math.floor(seconds); @@ -115,7 +115,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ me.selectText = function(element) { - var range, selection; + let range, selection; // MS if (document.body.createTextRange) { @@ -168,12 +168,12 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ me.sprintf = function() { - var args = Array.prototype.slice.call(arguments); - var format = args[0], + const args = Array.prototype.slice.call(arguments); + let format = args[0], i = 1; return format.replace(/%(s|d)/g, function (m) { // m is the matched format, e.g. %s, %d - var val = args[i]; + let val = args[i]; // A switch statement so that the formatter can be extended. switch (m) { @@ -201,10 +201,10 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @return {string} */ me.getCookie = function(cname) { - var name = cname + '=', - ca = document.cookie.split(';'); - for (var i = 0; i < ca.length; ++i) { - var c = ca[i]; + const name = cname + '=', + ca = document.cookie.split(';'); + for (let i = 0; i < ca.length; ++i) { + let c = ca[i]; while (c.charAt(0) === ' ') { c = c.substring(1); @@ -257,8 +257,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { 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) { + for (let i = 0; i < BadBotUA.length; ++i) { if (navigator.userAgent.indexOf(BadBotUA) >= 0) { return true; } @@ -276,8 +275,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @name I18n * @class */ - var I18n = (function () { - var me = {}; + const I18n = (function () { + const me = {}; /** * const for string of loaded language @@ -287,7 +286,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @prop {string} * @readonly */ - var languageLoadedEvent = 'languageLoaded'; + const languageLoadedEvent = 'languageLoaded'; /** * supported languages, minus the built in 'en' @@ -297,7 +296,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @prop {string[]} * @readonly */ - var supportedLanguages = ['de', 'es', 'fr', 'it', 'hu', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'zh']; + const supportedLanguages = ['de', 'es', 'fr', 'it', 'hu', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'zh']; /** * built in language @@ -306,7 +305,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @private * @prop {string|null} */ - var language = null; + let language = null; /** * translation cache @@ -315,7 +314,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @private * @enum {Object} */ - var translations = {}; + let translations = {}; /** * translate a string, alias for I18n.translate @@ -352,7 +351,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { me.translate = function() { // convert parameters to array - var args = Array.prototype.slice.call(arguments), + let args = Array.prototype.slice.call(arguments), messageId, $element = null; @@ -364,7 +363,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { } // extract messageId from arguments - var usesPlurals = $.isArray(args[0]); + let usesPlurals = $.isArray(args[0]); if (usesPlurals) { // use the first plural form as messageId, otherwise the singular messageId = args[0].length > 1 ? args[0][1] : args[0][0]; @@ -381,7 +380,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { // if language is still loading and we have an elemt assigned if (language === null && $element !== null) { // handle the error by attaching the language loaded event - var orgArguments = arguments; + let orgArguments = arguments; $(document).on(languageLoadedEvent, function () { // log to show that the previous error could be mitigated console.warn('Fix missing translation of \'' + messageId + '\' with now loaded language ' + language); @@ -406,7 +405,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { // lookup plural translation if (usesPlurals && $.isArray(translations[messageId])) { - var n = parseInt(args[1] || 1, 10), + let n = parseInt(args[1] || 1, 10), key = me.getPluralForm(n), maxKey = translations[messageId].length - 1; if (key > maxKey) { @@ -420,12 +419,12 @@ jQuery.PrivateBin = (function($, RawDeflate) { } // format string - var output = Helper.sprintf.apply(this, args); + let output = Helper.sprintf.apply(this, args); // if $element is given, apply text to element if ($element !== null) { // get last text node of element - var content = $element.contents(); + let content = $element.contents(); if (content.length > 1) { content[content.length - 1].nodeValue = ' ' + output; } else { @@ -472,7 +471,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ me.loadTranslations = function() { - var newLanguage = Helper.getCookie('lang'); + let newLanguage = Helper.getCookie('lang'); // auto-select language based on browser settings if (newLanguage.length === 0) { @@ -529,8 +528,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @name CryptTool * @class */ - var CryptTool = (function () { - var me = {}; + const CryptTool = (function () { + const me = {}; /** * convert UTF-8 string stored in a DOMString to a standard UTF-16 DOMString @@ -538,13 +537,13 @@ jQuery.PrivateBin = (function($, RawDeflate) { * Iterates over the bytes of the message, converting them all hexadecimal * percent encoded representations, then URI decodes them all * - * @name CryptTool.btou + * @name CryptTool.utf8To16 * @function * @private * @param {string} message UTF-8 string * @return {string} UTF-16 string */ - function btou(message) + function utf8To16(message) { return decodeURIComponent( message.split('').map( @@ -562,13 +561,13 @@ jQuery.PrivateBin = (function($, RawDeflate) { * URI encodes the message, then finds the percent encoded characters * and transforms these hexadecimal representation back into bytes * - * @name CryptTool.utob + * @name CryptTool.utf16To8 * @function * @private * @param {string} message UTF-16 string * @return {string} UTF-8 string */ - function utob(message) + function utf16To8(message) { return encodeURIComponent(message).replace( /%([0-9A-F]{2})/g, @@ -584,21 +583,19 @@ jQuery.PrivateBin = (function($, RawDeflate) { * * Iterates over the bytes of the array, catenating them into a string * - * @name CryptTool.ArrToStr + * @name CryptTool.arraybufferToString * @function * @private * @param {ArrayBuffer} messageArray * @return {string} message */ - function ArrToStr(messageArray) + function arraybufferToString(messageArray) { - var array = new Uint8Array(messageArray), - len = array.length, - message = '', - i = 0; - while(i < len) { - var c = array[i++]; - message += String.fromCharCode(c); + const array = new Uint8Array(messageArray); + let message = '', + i = 0; + while(i < array.length) { + message += String.fromCharCode(array[i++]); } return message; } @@ -608,19 +605,17 @@ jQuery.PrivateBin = (function($, RawDeflate) { * * Iterates over the bytes of the message, writing them to the array * - * @name CryptTool.StrToArr + * @name CryptTool.stringToArraybuffer * @function * @private * @param {string} message UTF-8 string * @return {Uint8Array} array */ - function StrToArr(message) + function stringToArraybuffer(message) { - var messageUtf8 = message, - messageLen = messageUtf8.length, - messageArray = new Uint8Array(messageLen); - for (var i = 0; i < messageLen; ++i) { - messageArray[i] = messageUtf8.charCodeAt(i); + const messageArray = new Uint8Array(message.length); + for (let i = 0; i < message.length; ++i) { + messageArray[i] = message.charCodeAt(i); } return messageArray; } @@ -638,8 +633,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ async function compress(message, mode) { - message = StrToArr( - utob(message) + message = stringToArraybuffer( + utf16To8(message) ); if (mode === 'zlib') { return z.deflate(message).buffer; @@ -662,19 +657,21 @@ jQuery.PrivateBin = (function($, RawDeflate) { { if (mode === 'zlib' || mode === 'none') { if (mode === 'zlib') { - data = z.inflate(new Uint8Array(data)).buffer; + data = z.inflate( + new Uint8Array(data) + ).buffer; } - return btou( - ArrToStr(data) + return utf8To16( + arraybufferToString(data) ); } // detect presence of Base64.js, indicating legacy ZeroBin paste if (typeof Base64 === 'undefined') { - return btou( + return utf8To16( RawDeflate.inflate( - btou( + utf8To16( atob( - ArrToStr(data) + arraybufferToString(data) ) ) ) @@ -683,7 +680,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { return Base64.btou( RawDeflate.inflate( Base64.fromBase64( - ArrToStr(data) + arraybufferToString(data) ) ) ); @@ -712,11 +709,11 @@ jQuery.PrivateBin = (function($, RawDeflate) { ) ) { // modern browser environment - var bytes = '', - byteArray = new Uint8Array(length), - crypto = window.crypto || window.msCrypto; + let bytes = ''; + const byteArray = new Uint8Array(length), + crypto = window.crypto || window.msCrypto; crypto.getRandomValues(byteArray); - for (var i = 0; i < length; ++i) { + for (let i = 0; i < length; ++i) { bytes += String.fromCharCode(byteArray[i]); } return bytes; @@ -740,19 +737,22 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ async function deriveKey(key, password, spec) { - let keyArray = StrToArr(key); + let keyArray = stringToArraybuffer(key); if (password.length > 0) { // version 1 pastes did append the passwords SHA-256 hash in hex if (spec[7] === 'rawdeflate') { let passwordBuffer = await window.crypto.subtle.digest( {name: 'SHA-256'}, - StrToArr(utob(password)) + stringToArraybuffer( + utf16To8(password) + ) ); password = Array.prototype.map.call( - new Uint8Array(passwordBuffer), x => ('00' + x.toString(16)).slice(-2) + new Uint8Array(passwordBuffer), + x => ('00' + x.toString(16)).slice(-2) ).join(''); } - let passwordArray = StrToArr(password), + let passwordArray = stringToArraybuffer(password), newKeyArray = new Uint8Array(keyArray.length + passwordArray.length); newKeyArray.set(keyArray, 0); newKeyArray.set(passwordArray, keyArray.length); @@ -772,7 +772,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { return window.crypto.subtle.deriveKey( { name: 'PBKDF2', // we use PBKDF2 for key derivation - salt: StrToArr(spec[1]), // salt used in HMAC + salt: stringToArraybuffer(spec[1]), // salt used in HMAC iterations: spec[2], // amount of iterations to apply hash: {name: 'SHA-256'} // can be "SHA-1", "SHA-256", "SHA-384" or "SHA-512" }, @@ -800,8 +800,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { { return { name: 'AES-' + spec[6].toUpperCase(), // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC") - iv: StrToArr(spec[0]), // the initialization vector you used to encrypt - additionalData: StrToArr(adata), // the addtional data you used during encryption (if any) + iv: stringToArraybuffer(spec[0]), // the initialization vector you used to encrypt + additionalData: stringToArraybuffer(adata), // the addtional data you used during encryption (if any) tagLength: spec[4] // the length of the tag you used to encrypt (if any) }; } @@ -846,7 +846,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { // finally, encrypt message return [ btoa( - ArrToStr( + arraybufferToString( await window.crypto.subtle.encrypt( cryptoSettings(JSON.stringify(adata), spec), await deriveKey(key, password, spec), @@ -903,7 +903,9 @@ jQuery.PrivateBin = (function($, RawDeflate) { await window.crypto.subtle.decrypt( cryptoSettings(adataString, spec), await deriveKey(key, password, spec), - StrToArr(atob(cipherMessage)) + stringToArraybuffer( + atob(cipherMessage) + ) ), encodedSpec[7] ); @@ -924,7 +926,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ me.getSymmetricKey = function() { - return btoa(getRandomBytes(32)); + return getRandomBytes(32); }; return me; @@ -936,14 +938,14 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @name Model * @class */ - var Model = (function () { - var me = {}; + const Model = (function () { + const me = {}; - var pasteData = null, + let id = null, + pasteData = null, + symmetricKey = null, $templates; - var id = null, symmetricKey = null; - /** * returns the expiration set in the HTML * @@ -1037,7 +1039,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { } /** - * Returns true, when the URL has a delete token and the current call was used for deleting a paste. + * returns true, when the URL has a delete token and the current call was used for deleting a paste. * * @name Model.hasDeleteToken * @function @@ -1059,19 +1061,20 @@ jQuery.PrivateBin = (function($, RawDeflate) { me.getPasteKey = function() { if (symmetricKey === null) { - symmetricKey = window.location.hash.substring(1); - - if (symmetricKey === '') { + let newKey = window.location.hash.substring(1); + if (newKey === '') { throw 'no encryption key given'; } // Some web 2.0 services and redirectors add data AFTER the anchor // (such as &utm_source=...). We will strip any additional data. - var ampersandPos = symmetricKey.indexOf('&'); + let ampersandPos = newKey.indexOf('&'); if (ampersandPos > -1) { - symmetricKey = symmetricKey.substring(0, ampersandPos); + newKey = newKey.substring(0, ampersandPos); } + + symmetricKey = atob(newKey); } return symmetricKey; @@ -1088,7 +1091,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { me.getTemplate = function(name) { // find template - var $element = $templates.find('#' + name + 'template').clone(true); + let $element = $templates.find('#' + name + 'template').clone(true); // change ID to avoid collisions (one ID should really be unique) return $element.prop('id', name); }; @@ -1128,8 +1131,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @name UiHelper * @class */ - var UiHelper = (function () { - var me = {}; + const UiHelper = (function () { + const me = {}; /** * handle history (pop) state changes @@ -1143,7 +1146,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ function historyChange(event) { - var currentLocation = Helper.baseUri(); + let currentLocation = Helper.baseUri(); if (event.originalEvent.state === null && // no state object passed event.target.location.href === currentLocation && // target location is home page window.location.href === currentLocation // and we are not already on the home page @@ -1177,10 +1180,9 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ me.isVisible = function($element) { - var elementTop = $element.offset().top; - var viewportTop = $(window).scrollTop(); - var viewportBottom = viewportTop + $(window).height(); - + let elementTop = $element.offset().top, + viewportTop = $(window).scrollTop(), + viewportBottom = viewportTop + $(window).height(); return elementTop > viewportTop && elementTop < viewportBottom; }; @@ -1197,12 +1199,12 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ me.scrollTo = function($element, animationDuration, animationEffect, finishedCallback) { - var $body = $('html, body'), + let $body = $('html, body'), margin = 50, - callbackCalled = false; + callbackCalled = false, + dest = 0; - //calculate destination place - var dest = 0; + // calculate destination place // if it would scroll out of the screen at the bottom only scroll it as // far as the screen can go if ($element.offset().top > $(document).height() - $(window).height()) { @@ -1277,25 +1279,23 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @name Alert * @class */ - var Alert = (function () { - var me = {}; + const Alert = (function () { + const me = {}; - var $errorMessage, + let $errorMessage, $loadingIndicator, $statusMessage, - $remainingTime; + $remainingTime, + currentIcon, + customHandler; - var currentIcon; - - var alertType = [ - 'loading', // not in bootstrap, but using a good value here - 'info', // status icon + const alertType = [ + 'loading', // not in bootstrap CSS, but using a plausible value here + 'info', // status icon 'warning', // not used yet - 'danger' // error icon + 'danger' // error icon ]; - var customHandler; - /** * forwards a request to the i18n module and shows the element * @@ -1322,7 +1322,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { // pass to custom handler if defined if (typeof customHandler === 'function') { - var handlerResult = customHandler(alertType[id], $element, args, icon); + let handlerResult = customHandler(alertType[id], $element, args, icon); if (handlerResult === true) { // if it returns true, skip own handler return; @@ -1338,7 +1338,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { if (icon !== null && // icon was passed icon !== currentIcon[id] // and it differs from current icon ) { - var $glyphIcon = $element.find(':first'); + let $glyphIcon = $element.find(':first'); // remove (previous) icon $glyphIcon.removeClass(currentIcon[id]); @@ -1520,10 +1520,10 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @name PasteStatus * @class */ - var PasteStatus = (function () { - var me = {}; + const PasteStatus = (function () { + const me = {}; - var $pasteSuccess, + let $pasteSuccess, $pasteUrl, $remainingTime, $shortenButton; @@ -1612,7 +1612,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { } else if (pasteMetaData.expire_date) { // display paste expiration - var expiration = Helper.secondsToHuman(pasteMetaData.remaining_time), + let expiration = Helper.secondsToHuman(pasteMetaData.remaining_time), expirationLabel = [ 'This document will expire in %d ' + expiration[1] + '.', 'This document will expire in %d ' + expiration[1] + 's.' @@ -1669,14 +1669,13 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @name Prompt * @class */ - var Prompt = (function () { - var me = {}; + const Prompt = (function () { + const me = {}; - var $passwordDecrypt, + let $passwordDecrypt, $passwordForm, - $passwordModal; - - var password = ''; + $passwordModal, + password = ''; /** * submit a password in the modal dialog @@ -1793,15 +1792,14 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @name Editor * @class */ - var Editor = (function () { - var me = {}; + const Editor = (function () { + const me = {}; - var $editorTabs, + let $editorTabs, $messageEdit, $messagePreview, - $message; - - var isPreview = false; + $message, + isPreview = false; /** * support input of tab character @@ -1813,13 +1811,13 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ function supportTabs(event) { - var keyCode = event.keyCode || event.which; + const keyCode = event.keyCode || event.which; // tab was pressed if (keyCode === 9) { // get caret position & selection - var val = this.value, - start = this.selectionStart, - end = this.selectionEnd; + const val = this.value, + start = this.selectionStart, + end = this.selectionEnd; // set textarea value to: text before caret + tab + text after caret this.value = val.substring(0, start) + '\t' + val.substring(end); // put caret at right position again @@ -1877,7 +1875,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { // show preview PasteViewer.setText($message.val()); if (AttachmentViewer.hasAttachmentData()) { - var attachmentData = AttachmentViewer.getAttachmentData() || AttachmentViewer.getAttachmentLink().attr('href'); + let attachmentData = AttachmentViewer.getAttachmentData() || AttachmentViewer.getAttachmentLink().attr('href'); AttachmentViewer.handleAttachmentPreview(AttachmentViewer.getAttachmentPreview(), attachmentData); } PasteViewer.run(); @@ -2009,15 +2007,14 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @name PasteViewer * @class */ - var PasteViewer = (function () { - var me = {}; + const PasteViewer = (function () { + const me = {}; - var $placeholder, + let $placeholder, $prettyMessage, $prettyPrint, - $plainText; - - var text, + $plainText, + text, format = 'plaintext', isDisplayed = false, isChanged = true; // by default true as nothing was parsed yet @@ -2037,16 +2034,16 @@ jQuery.PrivateBin = (function($, RawDeflate) { } // escape HTML entities, link URLs, sanitize - var escapedLinkedText = Helper.urls2links( + const escapedLinkedText = Helper.urls2links( $('
').text(text).html() - ), - sanitizedLinkedText = DOMPurify.sanitize(escapedLinkedText); + ), + sanitizedLinkedText = DOMPurify.sanitize(escapedLinkedText); $plainText.html(sanitizedLinkedText); $prettyPrint.html(sanitizedLinkedText); switch (format) { case 'markdown': - var converter = new showdown.Converter({ + const converter = new showdown.Converter({ strikethrough: true, tables: true, tablesHeaderId: true, @@ -2055,7 +2052,9 @@ jQuery.PrivateBin = (function($, RawDeflate) { }); // let showdown convert the HTML and sanitize HTML *afterwards*! $plainText.html( - DOMPurify.sanitize(converter.makeHtml(text)) + DOMPurify.sanitize( + converter.makeHtml(text) + ) ); // add table classes from bootstrap css $plainText.find('table').addClass('table-condensed table-bordered'); @@ -2267,17 +2266,17 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @name AttachmentViewer * @class */ - var AttachmentViewer = (function () { - var me = {}; + const AttachmentViewer = (function () { + const me = {}; - var $attachmentLink; - var $attachmentPreview; - var $attachment; - var attachmentData; - var file; - var $fileInput; - var $dragAndDropFileName; - var attachmentHasPreview = false; + let $attachmentLink, + $attachmentPreview, + $attachment, + attachmentData, + file, + $fileInput, + $dragAndDropFileName, + attachmentHasPreview = false; /** * sets the attachment but does not yet show it @@ -2296,24 +2295,21 @@ jQuery.PrivateBin = (function($, RawDeflate) { // data URI format: data:[