* magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7
@@ -167,12 +297,12 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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)
{
@@ -200,10 +330,10 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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);
@@ -231,11 +361,55 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
return baseUri;
}
- // window.location.origin is a newer alternative, but requires FF 21 / Chrome 31 / Safari 7 / IE 11
- baseUri = window.location.protocol + '//' + window.location.host + window.location.pathname;
+ baseUri = window.location.origin + window.location.pathname;
return baseUri;
};
+
+ /**
+ * 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
+ for (let i = 0; i < BadBotUA.length; ++i) {
+ if (navigator.userAgent.indexOf(BadBotUA) >= 0) {
+ 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
*
@@ -247,26 +421,6 @@ 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;
})();
@@ -276,8 +430,8 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* @name I18n
* @class
*/
- var I18n = (function () {
- var me = {};
+ const I18n = (function () {
+ const me = {};
/**
* const for string of loaded language
@@ -287,7 +441,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* @prop {string}
* @readonly
*/
- var languageLoadedEvent = 'languageLoaded';
+ const languageLoadedEvent = 'languageLoaded';
/**
* supported languages, minus the built in 'en'
@@ -297,7 +451,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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 +460,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* @private
* @prop {string|null}
*/
- var language = null;
+ let language = null;
/**
* translation cache
@@ -315,7 +469,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* @private
* @enum {Object}
*/
- var translations = {};
+ let translations = {};
/**
* translate a string, alias for I18n.translate
@@ -352,7 +506,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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 +518,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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 +535,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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 +560,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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 +574,12 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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 +626,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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,124 +683,444 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* @name CryptTool
* @class
*/
- var CryptTool = (function () {
- var me = {};
+ const CryptTool = (function () {
+ const me = {};
/**
- * compress a message (deflate compression), returns base64 encoded data
+ * base58 encoder & decoder
*
- * @name CryptTool.compress
+ * @private
+ */
+ let base58 = new baseX('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
+
+ /**
+ * convert UTF-8 string stored in a DOMString to a standard UTF-16 DOMString
+ *
+ * Iterates over the bytes of the message, converting them all hexadecimal
+ * percent encoded representations, then URI decodes them all
+ *
+ * @name CryptTool.utf8To16
* @function
* @private
- * @param {string} message
- * @return {string} base64 data
+ * @param {string} message UTF-8 string
+ * @return {string} UTF-16 string
*/
- function compress(message)
+ function utf8To16(message)
{
- return Base64.toBase64( RawDeflate.deflate( Base64.utob(message) ) );
+ return decodeURIComponent(
+ message.split('').map(
+ function(character)
+ {
+ return '%' + ('00' + character.charCodeAt(0).toString(16)).slice(-2);
+ }
+ ).join('')
+ );
}
/**
- * decompress a message compressed with cryptToolcompress()
+ * convert DOMString (UTF-16) to a UTF-8 string stored in a DOMString
*
- * @name CryptTool.decompress
+ * URI encodes the message, then finds the percent encoded characters
+ * and transforms these hexadecimal representation back into bytes
+ *
+ * @name CryptTool.utf16To8
* @function
* @private
- * @param {string} data - base64 data
+ * @param {string} message UTF-16 string
+ * @return {string} UTF-8 string
+ */
+ function utf16To8(message)
+ {
+ return encodeURIComponent(message).replace(
+ /%([0-9A-F]{2})/g,
+ function (match, hexCharacter)
+ {
+ return String.fromCharCode('0x' + hexCharacter);
+ }
+ );
+ }
+
+ /**
+ * convert ArrayBuffer into a UTF-8 string
+ *
+ * Iterates over the bytes of the array, catenating them into a string
+ *
+ * @name CryptTool.arraybufferToString
+ * @function
+ * @private
+ * @param {ArrayBuffer} messageArray
* @return {string} message
*/
- function decompress(data)
+ function arraybufferToString(messageArray)
{
- return Base64.btou( RawDeflate.inflate( Base64.fromBase64(data) ) );
+ const array = new Uint8Array(messageArray);
+ let message = '',
+ i = 0;
+ while(i < array.length) {
+ message += String.fromCharCode(array[i++]);
+ }
+ return message;
+ }
+
+ /**
+ * convert UTF-8 string into a Uint8Array
+ *
+ * Iterates over the bytes of the message, writing them to the array
+ *
+ * @name CryptTool.stringToArraybuffer
+ * @function
+ * @private
+ * @param {string} message UTF-8 string
+ * @return {Uint8Array} array
+ */
+ function stringToArraybuffer(message)
+ {
+ const messageArray = new Uint8Array(message.length);
+ for (let i = 0; i < message.length; ++i) {
+ messageArray[i] = message.charCodeAt(i);
+ }
+ return messageArray;
+ }
+
+ /**
+ * compress a string (deflate compression), returns buffer
+ *
+ * @name CryptTool.compress
+ * @async
+ * @function
+ * @private
+ * @param {string} message
+ * @param {string} mode
+ * @return {ArrayBuffer} data
+ */
+ async function compress(message, mode)
+ {
+ message = stringToArraybuffer(
+ utf16To8(message)
+ );
+ if (mode === 'zlib') {
+ return z.deflate(message).buffer;
+ }
+ return message;
+ }
+
+ /**
+ * decompress potentially base64 encoded, deflate compressed buffer, returns string
+ *
+ * @name CryptTool.decompress
+ * @async
+ * @function
+ * @private
+ * @param {ArrayBuffer} data
+ * @param {string} mode
+ * @return {string} message
+ */
+ async function decompress(data, mode)
+ {
+ if (mode === 'zlib' || mode === 'none') {
+ if (mode === 'zlib') {
+ data = z.inflate(
+ new Uint8Array(data)
+ ).buffer;
+ }
+ return utf8To16(
+ arraybufferToString(data)
+ );
+ }
+ // detect presence of Base64.js, indicating legacy ZeroBin paste
+ if (typeof Base64 === 'undefined') {
+ return utf8To16(
+ RawDeflate.inflate(
+ utf8To16(
+ atob(
+ arraybufferToString(data)
+ )
+ )
+ )
+ );
+ } else {
+ return Base64.btou(
+ RawDeflate.inflate(
+ Base64.fromBase64(
+ arraybufferToString(data)
+ )
+ )
+ );
+ }
+ }
+
+ /**
+ * returns specified number of random bytes
+ *
+ * @name CryptTool.getRandomBytes
+ * @function
+ * @private
+ * @param {int} length number of random bytes to fetch
+ * @throws {string}
+ * @return {string} random bytes
+ */
+ function getRandomBytes(length)
+ {
+ if (
+ typeof window !== 'undefined' &&
+ typeof Uint8Array !== 'undefined' &&
+ String.fromCodePoint &&
+ (
+ typeof window.crypto !== 'undefined' ||
+ typeof window.msCrypto !== 'undefined'
+ )
+ ) {
+ // modern browser environment
+ let bytes = '';
+ const byteArray = new Uint8Array(length),
+ crypto = window.crypto || window.msCrypto;
+ crypto.getRandomValues(byteArray);
+ for (let i = 0; i < length; ++i) {
+ bytes += String.fromCharCode(byteArray[i]);
+ }
+ return bytes;
+ } else {
+ // legacy browser or unsupported environment
+ throw 'No supported crypto API detected, you may read pastes and comments, but can\'t create pastes or add new comments.';
+ }
+ }
+
+ /**
+ * derive cryptographic key from key string and password
+ *
+ * @name CryptTool.deriveKey
+ * @async
+ * @function
+ * @private
+ * @param {string} key
+ * @param {string} password
+ * @param {array} spec cryptographic specification
+ * @return {CryptoKey} derived key
+ */
+ async function deriveKey(key, password, spec)
+ {
+ 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'},
+ stringToArraybuffer(
+ utf16To8(password)
+ )
+ );
+ password = Array.prototype.map.call(
+ new Uint8Array(passwordBuffer),
+ x => ('00' + x.toString(16)).slice(-2)
+ ).join('');
+ }
+ let passwordArray = stringToArraybuffer(password),
+ newKeyArray = new Uint8Array(keyArray.length + passwordArray.length);
+ newKeyArray.set(keyArray, 0);
+ newKeyArray.set(passwordArray, keyArray.length);
+ keyArray = newKeyArray;
+ }
+
+ // import raw key
+ const importedKey = await window.crypto.subtle.importKey(
+ 'raw', // only 'raw' is allowed
+ keyArray,
+ {name: 'PBKDF2'}, // we use PBKDF2 for key derivation
+ false, // the key may not be exported
+ ['deriveKey'] // we may only use it for key derivation
+ );
+
+ // derive a stronger key for use with AES
+ return window.crypto.subtle.deriveKey(
+ {
+ name: 'PBKDF2', // we use PBKDF2 for key derivation
+ 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"
+ },
+ importedKey,
+ {
+ 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")
+ length: spec[3] // can be 128, 192 or 256
+ },
+ false, // the key may not be exported
+ ['encrypt', 'decrypt'] // we may only use it for en- and decryption
+ );
+ }
+
+ /**
+ * gets crypto settings from specification and authenticated data
+ *
+ * @name CryptTool.cryptoSettings
+ * @function
+ * @private
+ * @param {string} adata authenticated data
+ * @param {array} spec cryptographic specification
+ * @return {object} crypto settings
+ */
+ function cryptoSettings(adata, spec)
+ {
+ 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: 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)
+ };
}
/**
* compress, then encrypt message with given key and password
*
* @name CryptTool.cipher
+ * @async
* @function
* @param {string} key
* @param {string} password
* @param {string} message
- * @return {string} data - JSON with encrypted data
+ * @param {array} adata
+ * @return {array} encrypted message in base64 encoding & adata containing encryption spec
*/
- me.cipher = function(key, password, message)
+ me.cipher = async function(key, password, message, adata)
{
- // Galois Counter Mode, keysize 256 bit, authentication tag 128 bit
- var options = {
- mode: 'gcm',
- ks: 256,
- ts: 128
- };
-
- if ((password || '').trim().length === 0) {
- return sjcl.encrypt(key, compress(message), options);
+ // AES in Galois Counter Mode, keysize 256 bit,
+ // authentication tag 128 bit, 10000 iterations in key derivation
+ const spec = [
+ getRandomBytes(16), // initialization vector
+ getRandomBytes(8), // salt
+ 100000, // iterations
+ 256, // key size
+ 128, // tag size
+ 'aes', // algorithm
+ 'gcm', // algorithm mode
+ 'zlib' // compression
+ ], encodedSpec = [];
+ for (let i = 0; i < spec.length; ++i) {
+ encodedSpec[i] = i < 2 ? btoa(spec[i]) : spec[i];
}
- return sjcl.encrypt(key + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(password)), compress(message), options);
+ if (adata.length === 0) {
+ // comment
+ adata = encodedSpec;
+ } else if (adata[0] === null) {
+ // paste
+ adata[0] = encodedSpec;
+ }
+
+ // finally, encrypt message
+ return [
+ btoa(
+ arraybufferToString(
+ await window.crypto.subtle.encrypt(
+ cryptoSettings(JSON.stringify(adata), spec),
+ await deriveKey(key, password, spec),
+ await compress(message, spec[7])
+ )
+ )
+ ),
+ adata
+ ];
};
/**
* decrypt message with key, then decompress
*
* @name CryptTool.decipher
+ * @async
* @function
* @param {string} key
* @param {string} password
- * @param {string} data - JSON with encrypted data
+ * @param {string|object} data encrypted message
* @return {string} decrypted message, empty if decryption failed
*/
- me.decipher = function(key, password, data)
+ me.decipher = async function(key, password, data)
{
- if (data !== undefined) {
- try {
- return decompress(sjcl.decrypt(key, data));
- } catch(err) {
- try {
- return decompress(sjcl.decrypt(key + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(password)), data));
- } catch(e) {
- return '';
- }
- }
+ let adataString, encodedSpec, cipherMessage;
+ if (data instanceof Array) {
+ // version 2
+ adataString = JSON.stringify(data[1]);
+ encodedSpec = (data[1][0] instanceof Array ? data[1][0] : data[1]);
+ cipherMessage = data[0];
+ } else if (typeof data === 'string') {
+ // version 1
+ let object = JSON.parse(data);
+ adataString = atob(object.adata);
+ encodedSpec = [
+ object.iv,
+ object.salt,
+ object.iter,
+ object.ks,
+ object.ts,
+ object.cipher,
+ object.mode,
+ 'rawdeflate'
+ ];
+ cipherMessage = object.ct;
+ } else {
+ throw 'unsupported message format';
+ }
+ let spec = encodedSpec, plainText = '';
+ spec[0] = atob(spec[0]);
+ spec[1] = atob(spec[1]);
+ try {
+ return await decompress(
+ await window.crypto.subtle.decrypt(
+ cryptoSettings(adataString, spec),
+ await deriveKey(key, password, spec),
+ stringToArraybuffer(
+ atob(cipherMessage)
+ )
+ ),
+ encodedSpec[7]
+ );
+ } catch(err) {
+ return '';
}
- };
-
- /**
- * checks whether the crypt tool has collected enough entropy
- *
- * @name CryptTool.isEntropyReady
- * @function
- * @return {bool}
- */
- me.isEntropyReady = function()
- {
- return sjcl.random.isReady();
- };
-
- /**
- * add a listener function, triggered when enough entropy is available
- *
- * @name CryptTool.addEntropySeedListener
- * @function
- * @param {function} func
- */
- me.addEntropySeedListener = function(func)
- {
- sjcl.random.addEventListener('seeded', func);
};
/**
* returns a random symmetric key
*
+ * generates 256 bit long keys (8 Bits * 32) for AES with 256 bit long blocks
+ *
* @name CryptTool.getSymmetricKey
* @function
- * @return {string} func
+ * @throws {string}
+ * @return {string} raw bytes
*/
me.getSymmetricKey = function()
{
- var bs58 = new baseX('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
- return bs58.encode(sjcl.codec.base64.fromBits(sjcl.random.randomWords(8, 10), 0));
+ return getRandomBytes(32);
};
+ /**
+ * base58 encode a DOMString (UTF-16)
+ *
+ * @name CryptTool.base58encode
+ * @function
+ * @param {string} input
+ * @return {string} output
+ */
+ me.base58encode = function(input)
+ {
+ return base58.encode(
+ stringToArraybuffer(input)
+ );
+ }
+
+ /**
+ * base58 decode a DOMString (UTF-16)
+ *
+ * @name CryptTool.base58decode
+ * @function
+ * @param {string} input
+ * @return {string} output
+ */
+ me.base58decode = function(input)
+ {
+ return arraybufferToString(
+ base58.decode(input)
+ );
+ }
+
return me;
})();
@@ -656,14 +1130,14 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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
*
@@ -712,25 +1186,25 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
}
// reload data
- Uploader.prepare();
- Uploader.setUrl(Helper.baseUri() + '?' + me.getPasteId());
+ ServerInteraction.prepare();
+ ServerInteraction.setUrl(Helper.baseUri() + '?' + me.getPasteId());
- Uploader.setFailure(function (status, data) {
+ ServerInteraction.setFailure(function (status, data) {
// revert loading status…
Alert.hideLoading();
TopNav.showViewButtons();
// show error message
- Alert.showError(Uploader.parseUploadError(status, data, 'get paste data'));
+ Alert.showError(ServerInteraction.parseUploadError(status, data, 'get paste data'));
});
- Uploader.setSuccess(function (status, data) {
- pasteData = data;
+ ServerInteraction.setSuccess(function (status, data) {
+ pasteData = new Paste(data);
if (typeof callback === 'function') {
- return callback(data);
+ return callback(pasteData);
}
});
- Uploader.run();
+ ServerInteraction.run();
};
/**
@@ -771,12 +1245,8 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
// fallback below
console.error('URL interface not properly supported, error:', e);
}
- } else {
- console.warn('URL interface appears not to be supported in this browser.');
}
- // fallback to simple RegEx
- console.warn('fallback to simple RegEx search');
// Attention: This also returns the delete token inside of the ID, if it is specified
id = (window.location.search.match(idRegExFind) || [''])[0];
@@ -788,7 +1258,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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
@@ -810,18 +1280,26 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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);
+ }
+
+ // version 2 uses base58, version 1 uses base64 without decoding
+ try {
+ // base58 encode strips NULL bytes at the beginning of the
+ // string, so we re-add them if necessary
+ symmetricKey = CryptTool.base58decode(newKey).padStart(32, '\u0000');
+ } catch(e) {
+ symmetricKey = newKey;
}
}
@@ -839,7 +1317,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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);
};
@@ -879,8 +1357,8 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* @name UiHelper
* @class
*/
- var UiHelper = (function () {
- var me = {};
+ const UiHelper = (function () {
+ const me = {};
/**
* handle history (pop) state changes
@@ -894,7 +1372,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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
@@ -928,10 +1406,9 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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;
};
@@ -948,12 +1425,12 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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()) {
@@ -1028,25 +1505,23 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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
*
@@ -1073,7 +1548,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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;
@@ -1089,7 +1564,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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]);
@@ -1127,7 +1602,6 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
*/
me.showStatus = function(message, icon)
{
- console.info('status shown: ', message);
handleNotification(1, $statusMessage, message, icon);
};
@@ -1144,7 +1618,6 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
*/
me.showError = function(message, icon)
{
- console.error('error message shown: ', message);
handleNotification(3, $errorMessage, message, icon);
};
@@ -1159,7 +1632,6 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
*/
me.showRemaining = function(message)
{
- console.info('remaining message shown: ', message);
handleNotification(1, $remainingTime, message);
};
@@ -1175,10 +1647,6 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
*/
me.showLoading = function(message, icon)
{
- if (typeof message !== 'undefined' && message !== null) {
- console.info('status changed: ', message);
- }
-
// default message text
if (typeof message === 'undefined') {
message = 'Loading…';
@@ -1278,10 +1746,10 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* @name PasteStatus
* @class
*/
- var PasteStatus = (function () {
- var me = {};
+ const PasteStatus = (function () {
+ const me = {};
- var $pasteSuccess,
+ let $pasteSuccess,
$pasteUrl,
$remainingTime,
$shortenButton;
@@ -1352,25 +1820,25 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
*
* @name PasteStatus.showRemainingTime
* @function
- * @param {object} pasteMetaData
+ * @param {Paste} paste
*/
- me.showRemainingTime = function(pasteMetaData)
+ me.showRemainingTime = function(paste)
{
- if (pasteMetaData.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
// has been downloaded
- Alert.showRemaining("FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.");
+ Alert.showRemaining('FOR YOUR EYES ONLY. Don\'t close this window, this message can\'t be displayed again.');
$remainingTime.addClass('foryoureyesonly');
// discourage cloning (it cannot really be prevented)
TopNav.hideCloneButton();
- } else if (pasteMetaData.expire_date) {
+ } else if (paste.getTimeToLive() > 0) {
// display paste expiration
- var expiration = Helper.secondsToHuman(pasteMetaData.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.'
@@ -1427,14 +1895,13 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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
@@ -1551,15 +2018,14 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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
@@ -1571,13 +2037,13 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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
@@ -1635,7 +2101,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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();
@@ -1767,15 +2233,14 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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
@@ -1795,16 +2260,16 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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,
@@ -1813,7 +2278,9 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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');
@@ -1969,7 +2436,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
me.hide = function()
{
if (!isDisplayed) {
- console.warn('PasteViewer was called to hide the parsed view, but it is already hidden.');
+ return;
}
$plainText.addClass('hidden');
@@ -2025,17 +2492,17 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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
@@ -2054,24 +2521,21 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
// data URI format: data:[][;base64],
// position in data URI string of where data begins
- var base64Start = attachmentData.indexOf(',') + 1;
+ const base64Start = attachmentData.indexOf(',') + 1;
// position in data URI string of where mediaType ends
- var mediaTypeEnd = attachmentData.indexOf(';');
+ const mediaTypeEnd = attachmentData.indexOf(';');
// extract mediaType
- var mediaType = attachmentData.substring(5, mediaTypeEnd);
+ const mediaType = attachmentData.substring(5, mediaTypeEnd);
// extract data and convert to binary
- var decodedData = Base64.atob(attachmentData.substring(base64Start));
+ const decodedData = atob(attachmentData.substring(base64Start));
// Transform into a Blob
- var decodedDataLength = decodedData.length;
- var buf = new Uint8Array(decodedDataLength);
-
- for (var i = 0; i < decodedDataLength; i++) {
+ const buf = new Uint8Array(decodedData.length);
+ for (let i = 0; i < decodedData.length; ++i) {
buf[i] = decodedData.charCodeAt(i);
}
-
- var blob = new window.Blob([ buf ], { type: mediaType });
+ const blob = new window.Blob([ buf ], { type: mediaType });
navigator.msSaveBlob(blob, fileName);
});
} else {
@@ -2188,7 +2652,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
if (!$attachment.length) {
return false;
}
- var link = $attachmentLink.prop('href');
+ const link = $attachmentLink.prop('href');
return (typeof link !== 'undefined' && link !== '');
};
@@ -2260,7 +2724,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
return;
}
- var fileReader = new FileReader();
+ const fileReader = new FileReader();
if (loadedFile === undefined) {
loadedFile = $fileInput[0].files[0];
$dragAndDropFileName.text('');
@@ -2271,7 +2735,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
file = loadedFile;
fileReader.onload = function (event) {
- var dataURL = event.target.result;
+ const dataURL = event.target.result;
attachmentData = dataURL;
if (Editor.isPreview()) {
@@ -2293,7 +2757,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
me.handleAttachmentPreview = function ($targetElement, data) {
if (data) {
// source: https://developer.mozilla.org/en-US/docs/Web/API/FileReader#readAsDataURL()
- var mimeType = data.slice(
+ const mimeType = data.slice(
data.indexOf('data:') + 5,
data.indexOf(';base64,')
);
@@ -2339,7 +2803,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
}
// Fallback for browsers, that don't support the vh unit
- var clientHeight = $(window).height();
+ const clientHeight = $(window).height();
$targetElement.html(
$(document.createElement('embed'))
@@ -2366,18 +2830,18 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
return;
}
- var ignoreDragDrop = function(event) {
+ const ignoreDragDrop = function(event) {
event.stopPropagation();
event.preventDefault();
};
- var drop = function(event) {
- var evt = event.originalEvent;
+ const drop = function(event) {
+ const evt = event.originalEvent;
evt.stopPropagation();
evt.preventDefault();
if ($fileInput) {
- var file = evt.dataTransfer.files[0];
+ const file = evt.dataTransfer.files[0];
//Clear the file input:
$fileInput.wrap('