Browse Source

more general solution addressing #554, kudos @rugk for the suggestions

El RIDO 6 năm trước cách đây
mục cha
commit
2caddf985f

+ 0 - 31
js/common.js

@@ -36,21 +36,6 @@ var a2zString = ['a','b','c','d','e','f','g','h','i','j','k','l','m',
     supportedLanguages = ['de', 'es', 'fr', 'it', 'no', 'pl', 'pt', 'oc', 'ru', 'sl', 'zh'],
     mimeTypes = ['image/png', 'application/octet-stream'],
     formats = ['plaintext', 'markdown', 'syntaxhighlighting'],
-    /**
-     * character to HTML entity lookup table
-     *
-     * @see    {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60}
-     */
-    entityMap = {
-        '&': '&',
-        '<': '&lt;',
-        '>': '&gt;',
-        '"': '&quot;',
-        "'": '&#39;',
-        '/': '&#x2F;',
-        '`': '&#x60;',
-        '=': '&#x3D;'
-    },
     logFile = fs.createWriteStream('test.log'),
     mimeFile = fs.createReadStream('/etc/mime.types'),
     mimeLine = '';
@@ -97,22 +82,6 @@ function parseMime(line) {
 
 // common testing helper functions
 
-/**
- * convert all applicable characters to HTML entities
- *
- * @see    {@link https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content}
- * @name   htmlEntities
- * @function
- * @param  {string} str
- * @return {string} escaped HTML
- */
-exports.htmlEntities = function(str) {
-    return String(str).replace(
-        /[&<>"'`=\/]/g, function(s) {
-            return entityMap[s];
-        });
-};
-
 // provides random lowercase characters from a to z
 exports.jscA2zString = function() {
     return jsc.elements(a2zString);

+ 72 - 25
js/privatebin.js

@@ -267,6 +267,32 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
             return false;
         }
 
+        /**
+         * encode all applicable characters to HTML entities
+         *
+         * @see    {@link https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html}
+         *
+         * @name   Helper.htmlEntities
+         * @function
+         * @param  string str
+         * @return string escaped HTML
+         */
+        me.htmlEntities = function(str) {
+            // using textarea, since other tags may allow and execute scripts, even when detached from DOM
+            let holder = document.createElement('textarea');
+            holder.textContent = str;
+            // as per OWASP recommendation, also encoding quotes and slash
+            return holder.innerHTML.replace(
+                /["'\/]/g,
+                function(s) {
+                    return {
+                        '"': '&quot;',
+                        "'": '&#x27;',
+                        '/': '&#x2F;'
+                    }[s];
+                });
+        };
+
         return me;
     })();
 
@@ -419,17 +445,31 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
                 args[0] = translations[messageId];
             }
 
+            // messageID may contain links, but should be from a trusted source (code or translation JSON files)
+            let containsNoLinks = args[0].indexOf('<a') === -1;
+            for (let i = 0; i < args.length; ++i) {
+                // parameters (i > 0) may never contain HTML as they may come from untrusted parties
+                if (i > 0 || containsNoLinks) {
+                    args[i] = Helper.htmlEntities(args[i]);
+                }
+            }
+
             // format string
             var 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();
-                if (content.length > 1) {
-                    content[content.length - 1].nodeValue = ' ' + output;
-                } else {
+                if (containsNoLinks) {
+                    // avoid HTML entity encoding if translation contains links
                     $element.text(output);
+                } else {
+                    // only allow tags/attributes we actually use in our translations
+                    $element.html(
+                        DOMPurify.sanitize(output, {
+                            ALLOWED_TAGS: ['a', 'br', 'i', 'span'],
+                            ALLOWED_ATTR: ['href', 'id']
+                        })
+                    );
                 }
             }
 
@@ -1052,28 +1092,35 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
                     icon = null; // icons not supported in this case
                 }
             }
-
-            // handle icon
-            if (icon !== null && // icon was passed
-                icon !== currentIcon[id] // and it differs from current icon
-            ) {
-                var $glyphIcon = $element.find(':first');
-
-                // remove (previous) icon
-                $glyphIcon.removeClass(currentIcon[id]);
-
-                // any other thing as a string (e.g. 'null') (only) removes the icon
-                if (typeof icon === 'string') {
-                    // set new icon
-                    currentIcon[id] = 'glyphicon-' + icon;
-                    $glyphIcon.addClass(currentIcon[id]);
+            var $translationTarget = $element;
+
+            // handle icon, if template uses one
+            var $glyphIcon = $element.find(':first');
+            if ($glyphIcon.length) {
+                // if there is an icon, we need to provide an inner element
+                // to translate the message into, instead of the parent
+                $translationTarget = $('<span>');
+                $element.html(' ').prepend($glyphIcon).append($translationTarget);
+
+                if (icon !== null && // icon was passed
+                    icon !== currentIcon[id] // and it differs from current icon
+                ) {
+                    // remove (previous) icon
+                    $glyphIcon.removeClass(currentIcon[id]);
+
+                    // any other thing as a string (e.g. 'null') (only) removes the icon
+                    if (typeof icon === 'string') {
+                        // set new icon
+                        currentIcon[id] = 'glyphicon-' + icon;
+                        $glyphIcon.addClass(currentIcon[id]);
+                    }
                 }
             }
 
             // show text
             if (args !== null) {
                 // add jQuery object to it as first parameter
-                args.unshift($element);
+                args.unshift($translationTarget);
                 // pass it to I18n
                 I18n._.apply(this, args);
             }
@@ -1764,9 +1811,9 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
 
             // escape HTML entities, link URLs, sanitize
             var escapedLinkedText = Helper.urls2links(
-                    $('<div />').text(text).html()
-                ),
-                sanitizedLinkedText = DOMPurify.sanitize(escapedLinkedText);
+                    Helper.htmlEntities(text)
+                  ),
+                  sanitizedLinkedText = DOMPurify.sanitize(escapedLinkedText);
             $plainText.html(sanitizedLinkedText);
             $prettyPrint.html(sanitizedLinkedText);
 
@@ -2894,7 +2941,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
             for (var i = 0; i < $head.length; i++) {
                 newDoc.write($head[i].outerHTML);
             }
-            newDoc.write('</head><body><pre>' + DOMPurify.sanitize(paste) + '</pre></body></html>');
+            newDoc.write('</head><body><pre>' + DOMPurify.sanitize(Helper.htmlEntities(paste)) + '</pre></body></html>');
             newDoc.close();
         }
 

+ 124 - 32
js/test/Alert.js

@@ -3,21 +3,56 @@ var common = require('../common');
 
 describe('Alert', function () {
     describe('showStatus', function () {
-        before(function () {
-            cleanup();
-        });
-
         jsc.property(
             'shows a status message',
             jsc.array(common.jscAlnumString()),
             jsc.array(common.jscAlnumString()),
+            function (icon, message) {
+                icon = icon.join('');
+                message = message.join('');
+                var expected = '<div id="status">' + message + '</div>';
+                $('body').html(
+                        '<div id="status"></div>'
+                    );
+                $.PrivateBin.Alert.init();
+                $.PrivateBin.Alert.showStatus(message, icon);
+                var result = $('body').html();
+                return expected === result;
+            }
+        );
+
+        jsc.property(
+            'shows a status message (bootstrap)',
+            jsc.array(common.jscAlnumString()),
+            function (message) {
+                message = message.join('');
+                var expected = '<div id="status" role="alert" ' +
+                    'class="statusmessage alert alert-info"><span ' +
+                    'class="glyphicon glyphicon-info-sign" ' +
+                    'aria-hidden="true"></span> <span>' + message + '</span></div>';
+                $('body').html(
+                    '<div id="status" role="alert" class="statusmessage ' +
+                    'alert alert-info hidden"><span class="glyphicon ' +
+                    'glyphicon-info-sign" aria-hidden="true"></span> </div>'
+                );
+                $.PrivateBin.Alert.init();
+                $.PrivateBin.Alert.showStatus(message);
+                var result = $('body').html();
+                return expected === result;
+            }
+        );
+
+        jsc.property(
+            'shows a status message (bootstrap, custom icon)',
+            jsc.array(common.jscAlnumString()),
+            jsc.array(common.jscAlnumString()),
             function (icon, message) {
                 icon = icon.join('');
                 message = message.join('');
                 var expected = '<div id="status" role="alert" ' +
                     'class="statusmessage alert alert-info"><span ' +
                     'class="glyphicon glyphicon-' + icon +
-                    '" aria-hidden="true"></span> ' + message + '</div>';
+                    '" aria-hidden="true"></span> <span>' + message + '</span></div>';
                 $('body').html(
                     '<div id="status" role="alert" class="statusmessage ' +
                     'alert alert-info hidden"><span class="glyphicon ' +
@@ -32,12 +67,48 @@ describe('Alert', function () {
     });
 
     describe('showError', function () {
-        before(function () {
-            cleanup();
-        });
+        jsc.property(
+            'shows an error message (basic)',
+            jsc.array(common.jscAlnumString()),
+            jsc.array(common.jscAlnumString()),
+            function (icon, message) {
+                icon = icon.join('');
+                message = message.join('');
+                var expected = '<div id="errormessage">' + message + '</div>';
+                $('body').html(
+                    '<div id="errormessage"></div>'
+                );
+                $.PrivateBin.Alert.init();
+                $.PrivateBin.Alert.showError(message, icon);
+                var result = $('body').html();
+                return expected === result;
+            }
+        );
 
         jsc.property(
-            'shows an error message',
+            'shows an error message (bootstrap)',
+            jsc.array(common.jscAlnumString()),
+            jsc.array(common.jscAlnumString()),
+            function (icon, message) {
+                message = message.join('');
+                var expected = '<div id="errormessage" role="alert" ' +
+                    'class="statusmessage alert alert-danger"><span ' +
+                    'class="glyphicon glyphicon-alert" ' +
+                    'aria-hidden="true"></span> <span>' + message + '</span></div>';
+                $('body').html(
+                    '<div id="errormessage" role="alert" class="statusmessage ' +
+                    'alert alert-danger hidden"><span class="glyphicon ' +
+                    'glyphicon-alert" aria-hidden="true"></span> </div>'
+                );
+                $.PrivateBin.Alert.init();
+                $.PrivateBin.Alert.showError(message);
+                var result = $('body').html();
+                return expected === result;
+            }
+        );
+
+        jsc.property(
+            'shows an error message (bootstrap, custom icon)',
             jsc.array(common.jscAlnumString()),
             jsc.array(common.jscAlnumString()),
             function (icon, message) {
@@ -46,7 +117,7 @@ describe('Alert', function () {
                 var expected = '<div id="errormessage" role="alert" ' +
                     'class="statusmessage alert alert-danger"><span ' +
                     'class="glyphicon glyphicon-' + icon +
-                    '" aria-hidden="true"></span> ' + message + '</div>';
+                    '" aria-hidden="true"></span> <span>' + message + '</span></div>';
                 $('body').html(
                     '<div id="errormessage" role="alert" class="statusmessage ' +
                     'alert alert-danger hidden"><span class="glyphicon ' +
@@ -61,12 +132,27 @@ describe('Alert', function () {
     });
 
     describe('showRemaining', function () {
-        before(function () {
-            cleanup();
-        });
+        jsc.property(
+            'shows remaining time (basic)',
+            jsc.array(common.jscAlnumString()),
+            jsc.array(common.jscAlnumString()),
+            'integer',
+            function (message, string, number) {
+                message = message.join('');
+                string = string.join('');
+                var expected = '<div id="remainingtime" class="">' + string + message + number + '</div>';
+                $('body').html(
+                    '<div id="remainingtime" class="hidden"></div>'
+                );
+                $.PrivateBin.Alert.init();
+                $.PrivateBin.Alert.showRemaining(['%s' + message + '%d', string, number]);
+                var result = $('body').html();
+                return expected === result;
+            }
+        );
 
         jsc.property(
-            'shows remaining time',
+            'shows remaining time (bootstrap)',
             jsc.array(common.jscAlnumString()),
             jsc.array(common.jscAlnumString()),
             'integer',
@@ -76,7 +162,7 @@ describe('Alert', function () {
                 var expected = '<div id="remainingtime" role="alert" ' +
                     'class="alert alert-info"><span ' +
                     'class="glyphicon glyphicon-fire" aria-hidden="true">' +
-                    '</span> ' + string + message + number + '</div>';
+                    '</span> <span>' + string + message + number + '</span></div>';
                 $('body').html(
                     '<div id="remainingtime" role="alert" class="hidden ' +
                     'alert alert-info"><span class="glyphicon ' +
@@ -91,12 +177,30 @@ describe('Alert', function () {
     });
 
     describe('showLoading', function () {
-        before(function () {
-            cleanup();
-        });
+        jsc.property(
+            'shows a loading message (basic)',
+            jsc.array(common.jscAlnumString()),
+            jsc.array(common.jscAlnumString()),
+            function (message, icon) {
+                message = message.join('');
+                icon = icon.join('');
+                var defaultMessage = 'Loading…';
+                if (message.length === 0) {
+                    message = defaultMessage;
+                }
+                var expected = '<div id="loadingindicator" class="">' + message + '</div>';
+                $('body').html(
+                    '<div id="loadingindicator" class="hidden">' + defaultMessage + '</div>'
+                );
+                $.PrivateBin.Alert.init();
+                $.PrivateBin.Alert.showLoading(message, icon);
+                var result = $('body').html();
+                return expected === result;
+            }
+        );
 
         jsc.property(
-            'shows a loading message',
+            'shows a loading message (bootstrap)',
             jsc.array(common.jscAlnumString()),
             jsc.array(common.jscAlnumString()),
             function (message, icon) {
@@ -109,7 +213,7 @@ describe('Alert', function () {
                 var expected = '<ul class="nav navbar-nav"><li ' +
                     'id="loadingindicator" class="navbar-text"><span ' +
                     'class="glyphicon glyphicon-' + icon +
-                    '" aria-hidden="true"></span> ' + message + '</li></ul>';
+                    '" aria-hidden="true"></span> <span>' + message + '</span></li></ul>';
                 $('body').html(
                     '<ul class="nav navbar-nav"><li id="loadingindicator" ' +
                     'class="navbar-text hidden"><span class="glyphicon ' +
@@ -125,10 +229,6 @@ describe('Alert', function () {
     });
 
     describe('hideLoading', function () {
-        before(function () {
-            cleanup();
-        });
-
         it(
             'hides the loading message',
             function() {
@@ -150,10 +250,6 @@ describe('Alert', function () {
     });
 
     describe('hideMessages', function () {
-        before(function () {
-            cleanup();
-        });
-
         it(
             'hides all messages',
             function() {
@@ -176,10 +272,6 @@ describe('Alert', function () {
     });
 
     describe('setCustomHandler', function () {
-        before(function () {
-            cleanup();
-        });
-
         jsc.property(
             'calls a given handler function',
             'nat 3',

+ 10 - 6
js/test/AttachmentViewer.js

@@ -4,9 +4,6 @@ var common = require('../common');
 describe('AttachmentViewer', function () {
     describe('setAttachment, showAttachment, removeAttachment, hideAttachment, hideAttachmentPreview, hasAttachment, getAttachment & moveAttachmentTo', function () {
         this.timeout(30000);
-        before(function () {
-            cleanup();
-        });
 
         jsc.property(
             'displays & hides data as requested',
@@ -24,7 +21,8 @@ describe('AttachmentViewer', function () {
                         mimeType.substring(0, 6) === 'video/' ||
                         mimeType.match(/\/pdf/i)
                     ),
-                    results = [];
+                    results = [],
+                    result = '';
                 prefix = prefix.replace(/%(s|d)/g, '%%');
                 postfix = postfix.replace(/%(s|d)/g, '%%');
                 $('body').html(
@@ -72,13 +70,19 @@ describe('AttachmentViewer', function () {
                     !$('#attachment').hasClass('hidden') &&
                     (previewSupported ? !$('#attachmentPreview').hasClass('hidden') : $('#attachmentPreview').hasClass('hidden'))
                 );
-                var element = $('<div></div>');
+                let element = $('<div>');
                 $.PrivateBin.AttachmentViewer.moveAttachmentTo(element, prefix + '%s' + postfix);
+                // messageIDs with links get a relaxed treatment
+                if (prefix.indexOf('<a') === -1 && postfix.indexOf('<a') === -1) {
+                    result = $.PrivateBin.Helper.htmlEntities(prefix + filename + postfix);
+                } else {
+                    result = $('<div>').html(prefix + $.PrivateBin.Helper.htmlEntities(filename) + postfix).html();
+                }
                 if (filename.length) {
                     results.push(
                         element.children()[0].href === data &&
                         element.children()[0].getAttribute('download') === filename &&
-                        element.children()[0].text === prefix + filename + postfix
+                        element.children()[0].text === result
                     );
                 } else {
                     results.push(element.children()[0].href === data);

+ 0 - 3
js/test/DiscussionViewer.js

@@ -4,9 +4,6 @@ var common = require('../common');
 describe('DiscussionViewer', function () {
     describe('handleNotification, prepareNewDiscussion, addComment, finishDiscussion, getReplyMessage, getReplyNickname, getReplyCommentId & highlightComment', function () {
         this.timeout(30000);
-        before(function () {
-            cleanup();
-        });
 
         jsc.property(
             'displays & hides comments as requested',

+ 0 - 3
js/test/Editor.js

@@ -4,9 +4,6 @@ require('../common');
 describe('Editor', function () {
     describe('show, hide, getText, setText & isPreview', function () {
         this.timeout(30000);
-        before(function () {
-            cleanup();
-        });
 
         jsc.property(
             'returns text fed into the textarea, handles editor tabs',

+ 20 - 24
js/test/Helper.js

@@ -3,10 +3,6 @@ var common = require('../common');
 
 describe('Helper', function () {
     describe('secondsToHuman', function () {
-        after(function () {
-            cleanup();
-        });
-
         jsc.property('returns an array with a number and a word', 'integer', function (number) {
             var result = $.PrivateBin.Helper.secondsToHuman(number);
             return Array.isArray(result) &&
@@ -57,11 +53,11 @@ describe('Helper', function () {
             'nearray string',
             function (ids, contents) {
                 var html = '',
-                    result = true;
+                    result = true,
+                    clean = jsdom(html);
                 ids.forEach(function(item, i) {
-                    html += '<div id="' + item.join('') + '">' + common.htmlEntities(contents[i] || contents[0]) + '</div>';
+                    html += '<div id="' + item.join('') + '">' + $.PrivateBin.Helper.htmlEntities(contents[i] || contents[0]) + '</div>';
                 });
-                var clean = jsdom(html);
                 // TODO: As per https://github.com/tmpvar/jsdom/issues/321 there is no getSelection in jsdom, yet.
                 // Once there is one, uncomment the block below to actually check the result.
                 /*
@@ -77,8 +73,8 @@ describe('Helper', function () {
     });
 
     describe('urls2links', function () {
-        after(function () {
-            cleanup();
+        before(function () {
+            cleanup = jsdom();
         });
 
         jsc.property(
@@ -97,11 +93,11 @@ describe('Helper', function () {
             jsc.array(common.jscHashString()),
             'string',
             function (prefix, schema, address, query, fragment, postfix) {
-                var query = query.join(''),
+                var query    = query.join(''),
                     fragment = fragment.join(''),
-                    url = schema + '://' + address.join('') + '/?' + query + '#' + fragment,
-                    prefix = common.htmlEntities(prefix),
-                    postfix = ' ' + common.htmlEntities(postfix);
+                    url      = schema + '://' + address.join('') + '/?' + query + '#' + fragment,
+                    prefix   = $.PrivateBin.Helper.htmlEntities(prefix),
+                    postfix  = ' ' + $.PrivateBin.Helper.htmlEntities(postfix);
 
                 // special cases: When the query string and fragment imply the beginning of an HTML entity, eg. &#0 or &#x
                 if (
@@ -122,19 +118,15 @@ describe('Helper', function () {
             jsc.array(common.jscQueryString()),
             'string',
             function (prefix, query, postfix) {
-                var url = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,''),
-                    prefix = common.htmlEntities(prefix),
-                    postfix = common.htmlEntities(postfix);
+                var url     = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,''),
+                    prefix  = $.PrivateBin.Helper.htmlEntities(prefix),
+                    postfix = $.PrivateBin.Helper.htmlEntities(postfix);
                 return prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a> ' + postfix === $.PrivateBin.Helper.urls2links(prefix + url + ' ' + postfix);
             }
         );
     });
 
     describe('sprintf', function () {
-        after(function () {
-            cleanup();
-        });
-
         jsc.property(
             'replaces %s in strings with first given parameter',
             'string',
@@ -211,6 +203,10 @@ describe('Helper', function () {
 
     describe('getCookie', function () {
         this.timeout(30000);
+        before(function () {
+            cleanup();
+        });
+
         jsc.property(
             'returns the requested cookie',
             'nearray asciinestring',
@@ -261,16 +257,16 @@ describe('Helper', function () {
     });
 
     describe('htmlEntities', function () {
-        after(function () {
-            cleanup();
+        before(function () {
+            cleanup = jsdom();
         });
 
         jsc.property(
             'removes all HTML entities from any given string',
             'string',
             function (string) {
-                var result = common.htmlEntities(string);
-                return !(/[<>"'`=\/]/.test(result)) && !(string.indexOf('&') > -1 && !(/&amp;/.test(result)));
+                var result = $.PrivateBin.Helper.htmlEntities(string);
+                return !(/[<>]/.test(result)) && !(string.indexOf('&') > -1 && !(/&amp;/.test(result)));
             }
         );
     });

+ 2 - 1
js/test/I18n.js

@@ -32,6 +32,7 @@ describe('I18n', function () {
                 var fakeAlias = $.PrivateBin.I18n._(fake);
                 $.PrivateBin.I18n.reset();
 
+                messageId = $.PrivateBin.Helper.htmlEntities(messageId);
                 return messageId === result && messageId === alias &&
                     messageId === pluralResult && messageId === pluralAlias &&
                     messageId === fakeResult && messageId === fakeAlias;
@@ -46,7 +47,7 @@ describe('I18n', function () {
                 prefix    =    prefix.replace(/%(s|d)/g, '%%');
                 params[0] = params[0].replace(/%(s|d)/g, '%%');
                 postfix   =   postfix.replace(/%(s|d)/g, '%%');
-                var translation = prefix + params[0] + postfix;
+                var translation = $.PrivateBin.Helper.htmlEntities(prefix + params[0] + postfix);
                 params.unshift(prefix + '%s' + postfix);
                 var result = $.PrivateBin.I18n.translate.apply(this, params);
                 $.PrivateBin.I18n.reset();

+ 13 - 13
js/test/Model.js

@@ -5,18 +5,18 @@ describe('Model', function () {
     describe('getExpirationDefault', function () {
         before(function () {
             $.PrivateBin.Model.reset();
-            cleanup();
+            cleanup = jsdom();
         });
 
         jsc.property(
             'returns the contents of the element with id "pasteExpiration"',
-            'array asciinestring',
+            'nearray asciinestring',
             'string',
             'small nat',
             function (keys, value, key) {
-                keys = keys.map(common.htmlEntities);
-                value = common.htmlEntities(value);
-                var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'),
+                keys = keys.map($.PrivateBin.Helper.htmlEntities);
+                value = $.PrivateBin.Helper.htmlEntities(value);
+                var content = keys.length > key ? keys[key] : keys[0],
                     contents = '<select id="pasteExpiration" name="pasteExpiration">';
                 keys.forEach(function(item) {
                     contents += '<option value="' + item + '"';
@@ -27,7 +27,7 @@ describe('Model', function () {
                 });
                 contents += '</select>';
                 $('body').html(contents);
-                var result = common.htmlEntities(
+                var result = $.PrivateBin.Helper.htmlEntities(
                     $.PrivateBin.Model.getExpirationDefault()
                 );
                 $.PrivateBin.Model.reset();
@@ -39,18 +39,20 @@ describe('Model', function () {
     describe('getFormatDefault', function () {
         before(function () {
             $.PrivateBin.Model.reset();
+        });
+        after(function () {
             cleanup();
         });
 
         jsc.property(
             'returns the contents of the element with id "pasteFormatter"',
-            'array asciinestring',
+            'nearray asciinestring',
             'string',
             'small nat',
             function (keys, value, key) {
-                keys = keys.map(common.htmlEntities);
-                value = common.htmlEntities(value);
-                var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'),
+                keys = keys.map($.PrivateBin.Helper.htmlEntities);
+                value = $.PrivateBin.Helper.htmlEntities(value);
+                var content = keys.length > key ? keys[key] : keys[0],
                     contents = '<select id="pasteFormatter" name="pasteFormatter">';
                 keys.forEach(function(item) {
                     contents += '<option value="' + item + '"';
@@ -61,7 +63,7 @@ describe('Model', function () {
                 });
                 contents += '</select>';
                 $('body').html(contents);
-                var result = common.htmlEntities(
+                var result = $.PrivateBin.Helper.htmlEntities(
                     $.PrivateBin.Model.getFormatDefault()
                 );
                 $.PrivateBin.Model.reset();
@@ -74,7 +76,6 @@ describe('Model', function () {
         this.timeout(30000);
         before(function () {
             $.PrivateBin.Model.reset();
-            cleanup();
         });
 
         jsc.property(
@@ -185,7 +186,6 @@ describe('Model', function () {
     describe('getTemplate', function () {
         before(function () {
             $.PrivateBin.Model.reset();
-            cleanup();
         });
 
         jsc.property(

+ 0 - 7
js/test/PasteStatus.js

@@ -39,9 +39,6 @@ describe('PasteStatus', function () {
 
     describe('showRemainingTime', function () {
         this.timeout(30000);
-        before(function () {
-            cleanup();
-        });
 
         jsc.property(
             'shows burn after reading message or remaining time',
@@ -84,10 +81,6 @@ describe('PasteStatus', function () {
     });
 
     describe('hideMessages', function () {
-        before(function () {
-            cleanup();
-        });
-
         it(
             'hides all messages',
             function() {

+ 0 - 3
js/test/PasteViewer.js

@@ -4,9 +4,6 @@ var common = require('../common');
 describe('PasteViewer', function () {
     describe('run, hide, getText, setText, getFormat, setFormat & isPrettyPrinted', function () {
         this.timeout(30000);
-        before(function () {
-            cleanup();
-        });
 
         jsc.property(
             'displays text according to format',

+ 1 - 4
js/test/Prompt.js

@@ -6,10 +6,6 @@ describe('Prompt', function () {
     //       in nodejs -> replace the prompt in the "page" template with a modal
     describe('requestPassword & getPassword', function () {
         this.timeout(30000);
-        before(function () {
-            $.PrivateBin.Model.reset();
-            cleanup();
-        });
 
         jsc.property(
             'returns the password fed into the dialog',
@@ -26,6 +22,7 @@ describe('Prompt', function () {
                     'password"></div><button type="submit">Decrypt</button>' +
                     '</form></div></div></div></div>'
                 );
+                $.PrivateBin.Model.reset();
                 $.PrivateBin.Model.init();
                 $.PrivateBin.Prompt.init();
                 $.PrivateBin.Prompt.requestPassword();

+ 1 - 1
tpl/bootstrap.php

@@ -75,7 +75,7 @@ if ($MARKDOWN):
 endif;
 ?>
 		<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.7.js" integrity="sha512-XjNEK1xwh7SJ/7FouwV4VZcGW9cMySL3SwNpXgrURLBcXXQYtZdqhGoNdEwx9vwLvFjUGDQVNgpOrTsXlSTiQg==" crossorigin="anonymous"></script>
-		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-5xT6WHyurvC0LFESbkycBMAjhsi0KL/Xhx2oU+d0bqiJPkWK6ZAZgZ7I02oQiXaLtFSLf7UqpBo5XEePlfhH7Q==" crossorigin="anonymous"></script>
+		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-XauVNDkk7v8lwDf2WmHWzloKuejJKJpfEvjBBf4bJV45VRP2SX6tg7ngNhmywIay8vNgoFhVBZZoe3y9tEio8Q==" crossorigin="anonymous"></script>
 		<!--[if lt IE 10]>
 		<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
 		<![endif]-->

+ 1 - 1
tpl/page.php

@@ -53,7 +53,7 @@ if ($MARKDOWN):
 endif;
 ?>
 		<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.7.js" integrity="sha512-XjNEK1xwh7SJ/7FouwV4VZcGW9cMySL3SwNpXgrURLBcXXQYtZdqhGoNdEwx9vwLvFjUGDQVNgpOrTsXlSTiQg==" crossorigin="anonymous"></script>
-		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-5xT6WHyurvC0LFESbkycBMAjhsi0KL/Xhx2oU+d0bqiJPkWK6ZAZgZ7I02oQiXaLtFSLf7UqpBo5XEePlfhH7Q==" crossorigin="anonymous"></script>
+		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-XauVNDkk7v8lwDf2WmHWzloKuejJKJpfEvjBBf4bJV45VRP2SX6tg7ngNhmywIay8vNgoFhVBZZoe3y9tEio8Q==" crossorigin="anonymous"></script>
 		<!--[if lt IE 10]>
 		<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
 		<![endif]-->