diff --git a/js/common.js b/js/common.js
index a12595d3..ea975e03 100644
--- a/js/common.js
+++ b/js/common.js
@@ -20,6 +20,7 @@ global.showdown = require('./showdown-1.9.1');
global.DOMPurify = require('./purify-1.0.11');
global.baseX = require('./base-x-3.0.5.1').baseX;
require('./bootstrap-3.3.7');
+require('./legacy');
require('./privatebin');
// internal variables
diff --git a/js/legacy.js b/js/legacy.js
new file mode 100644
index 00000000..82a6bd12
--- /dev/null
+++ b/js/legacy.js
@@ -0,0 +1,256 @@
+/**
+ * PrivateBin
+ *
+ * a zero-knowledge paste bin
+ *
+ * @see {@link https://github.com/PrivateBin/PrivateBin}
+ * @copyright 2012 Sébastien SAUVAGE ({@link http://sebsauvage.net})
+ * @license {@link https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License}
+ * @version 1.3
+ * @name Legacy
+ * @namespace
+ *
+ * IMPORTANT NOTICE FOR DEVELOPERS:
+ * The logic in this file is intended to run in legacy browsers. Avoid any use of:
+ * - ES6 or newer in general
+ * - const/let, use the traditional var declarations instead
+ * - async/await or Promises, use traditional callbacks
+ * - shorthand function notation "() => output", use the full "function() {return output;}" style
+ * - IE doesn't support:
+ * - URL(), use the traditional window.location object
+ * - endsWith(), use indexof()
+ * - yes, this logic needs to support IE 5 or 6, to at least display the error message
+ */
+
+// main application start, called when DOM is fully loaded
+jQuery(document).ready(function() {
+ 'use strict';
+ // run main controller
+ $.Legacy.Check.init();
+});
+
+jQuery.Legacy = (function($) {
+ 'use strict';
+
+ /**
+ * compatibility check
+ *
+ * @name Check
+ * @class
+ */
+ var Check = (function () {
+ var me = {};
+
+ /**
+ * Status of the initial check, true means it passed
+ *
+ * @private
+ * @prop {bool}
+ */
+ var status = false;
+
+ /**
+ * Initialization check did run
+ *
+ * @private
+ * @prop {bool}
+ */
+ var init = false;
+
+ /**
+ * blacklist of UserAgents (parts) known to belong to a bot
+ *
+ * @private
+ * @enum {Array}
+ * @readonly
+ */
+ var badBotUA = [
+ 'Bot',
+ 'bot'
+ ];
+
+ /**
+ * whitelist of top level domains to consider a secure context,
+ * regardless of protocol
+ *
+ * @private
+ * @enum {Array}
+ * @readonly
+ */
+ var tld = [
+ '.onion',
+ '.i2p'
+ ];
+
+ /**
+ * whitelist of hostnames to consider a secure context,
+ * regardless of protocol
+ *
+ * @private
+ * @enum {Array}
+ * @readonly
+ */
+ // whitelists of TLDs & local hostnames
+ var hostname = [
+ 'localhost',
+ '127.0.0.1',
+ '[::1]'
+ ];
+
+ /**
+ * check if the context is secure
+ *
+ * @private
+ * @name Check.isSecureContext
+ * @function
+ * @return {bool}
+ */
+ function isSecureContext()
+ {
+ // use .isSecureContext if available
+ if (window.isSecureContext === true || window.isSecureContext === false) {
+ return window.isSecureContext;
+ }
+
+ // HTTP is obviously insecure
+ if (window.location.protocol !== 'http:') {
+ return true;
+ }
+
+ // filter out actually secure connections over HTTP
+ for (var i = 0; i < tld.length; i++) {
+ if (
+ window.location.hostname.indexOf(
+ tld[i],
+ window.location.hostname.length - tld[i].length
+ ) !== -1
+ ) {
+ return true;
+ }
+ }
+
+ // whitelist localhost for development
+ for (var i = 0; i < hostname.length; i++) {
+ if (window.location.hostname === hostname[i]) {
+ return true;
+ }
+ }
+
+ // totally INSECURE http protocol!
+ return false;
+ }
+
+ /**
+ * checks whether this is a bot we dislike
+ *
+ * @private
+ * @name Check.isBadBot
+ * @function
+ * @return {bool}
+ */
+ function isBadBot() {
+ // check whether a bot user agent part can be found in the current
+ // user agent
+ for (var i = 0; i < badBotUA.length; i++) {
+ if (navigator.userAgent.indexOf(badBotUA[i]) !== -1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * checks whether this is an unsupported browser, via feature detection
+ *
+ * @private
+ * @name Check.isOldBrowser
+ * @function
+ * @return {bool}
+ */
+ function isOldBrowser() {
+ // webcrypto support
+ if (!(
+ 'crypto' in window &&
+ 'getRandomValues' in window.crypto &&
+ 'subtle' in window.crypto &&
+ 'encrypt' in window.crypto.subtle &&
+ 'decrypt' in window.crypto.subtle &&
+ 'Uint8Array' in window &&
+ 'Uint32Array' in window
+ )) {
+ return true;
+ }
+
+ // not checking for async/await, ES6 or Promise support, as most
+ // browsers introduced these earlier then webassembly and webcrypto:
+ // https://github.com/PrivateBin/PrivateBin/pull/431#issuecomment-493129359
+
+ return false;
+ }
+
+ /**
+ * returns if the check has concluded
+ *
+ * @name Check.getInit
+ * @function
+ * @return {bool}
+ */
+ me.getInit = function()
+ {
+ return init;
+ }
+
+ /**
+ * returns the current status of the check
+ *
+ * @name Check.getStatus
+ * @function
+ * @return {bool}
+ */
+ me.getStatus = function()
+ {
+ return status;
+ }
+
+ /**
+ * init on application start, returns an all-clear signal
+ *
+ * @name Check.init
+ * @function
+ */
+ me.init = function()
+ {
+ // prevent bots from viewing a paste and potentially deleting data
+ // when burn-after-reading is set
+ if (isBadBot()) {
+ $.PrivateBin.Alert.showError('I love you too, bot…');
+ init = true;
+ return;
+ }
+
+ if (isOldBrowser()) {
+ // some browsers (Chrome based ones) would have webcrypto support if using HTTPS
+ if (!isSecureContext()) {
+ $.PrivateBin.Alert.showError(['Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.', 'https' + window.location.href.slice(4)]);
+ }
+ $('#oldnotice').removeClass('hidden');
+ init = true;
+ return;
+ }
+
+ if (!isSecureContext()) {
+ $('#httpnotice').removeClass('hidden');
+ }
+ init = true;
+
+ // only if everything passed, we set the status to true
+ status = true;
+ }
+
+ return me;
+ })();
+
+ return {
+ Check: Check
+ };
+})(jQuery);
diff --git a/js/privatebin.js b/js/privatebin.js
index cdfce541..1db85ee0 100644
--- a/js/privatebin.js
+++ b/js/privatebin.js
@@ -14,6 +14,7 @@
*/
jQuery.fn.draghover = function() {
+ 'use strict';
return this.each(function() {
let collection = $(),
self = $(this);
@@ -4709,156 +4710,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
return me;
})();
-
- /**
- * initial (security) check
- *
- * @name InitialCheck
- * @class
- */
- const InitialCheck = (function () {
- const me = {};
-
- /**
- * blacklist of UserAgents (parts) known to belong to a bot
- *
- * @private
- * @enum {Array}
- * @readonly
- */
- const badBotUA = [
- 'Bot',
- 'bot'
- ];
-
- /**
- * check if the connection is insecure
- *
- * @private
- * @name InitialCheck.isInsecureConnection
- * @function
- * @return {bool}
- */
- function isInsecureConnection()
- {
- // use .isSecureContext if available
- if (window.isSecureContext === true || window.isSecureContext === false) {
- return !window.isSecureContext;
- }
-
- const url = new URL(window.location);
-
- // HTTP is obviously insecure
- if (url.protocol !== 'http:') {
- return false;
- }
-
- // filter out actually secure connections over HTTP
- for (const tld of ['.onion', '.i2p']) {
- if (url.hostname.endsWith(tld)) {
- return false;
- }
- }
-
- // whitelist localhost for development
- for (const hostname of ['localhost', '127.0.0.1', '[::1]']) {
- if (url.hostname === hostname) {
- return false;
- }
- }
-
- // totally INSECURE http protocol!
- return true;
- }
-
- /**
- * checks whether this is a bot we dislike
- *
- * @private
- * @name InitialCheck.isBadBot
- * @function
- * @return {bool}
- */
- function isBadBot() {
- // check whether a bot user agent part can be found in the current
- // user agent
- for (const UAfragment of badBotUA) {
- if (navigator.userAgent.indexOf(UAfragment) >= 0) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * checks whether this is an unsupported browser, via feature detection
- *
- * @private
- * @name InitialCheck.isOldBrowser
- * @function
- * @return {bool}
- */
- function isOldBrowser() {
- // webcrypto support
- if (!(
- 'crypto' in window &&
- 'getRandomValues' in window.crypto &&
- 'subtle' in window.crypto &&
- 'encrypt' in window.crypto.subtle &&
- 'decrypt' in window.crypto.subtle &&
- 'Uint8Array' in window &&
- 'Uint32Array' in window
- )) {
- return true;
- }
-
- // not checking for async/await, ES6 or Promise support, as most
- // browsers introduced these earlier then webassembly and webcrypto:
- // https://github.com/PrivateBin/PrivateBin/pull/431#issuecomment-493129359
-
- return false;
- }
-
- /**
- * init on application start, returns an all-clear signal
- *
- * @name InitialCheck.init
- * @function
- * @return {bool}
- */
- me.init = function()
- {
- // prevent bots from viewing a paste and potentially deleting data
- // when burn-after-reading is set
- if (isBadBot()) {
- Alert.showError('I love you too, bot…');
- return false;
- }
-
- if (isOldBrowser()) {
- // some browsers (Chrome based ones) would have webcrypto support if using HTTPS
- if (isInsecureConnection()) {
- Alert.showError(['Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.', 'https' + window.location.href.slice(4)]);
- }
- $('#oldnotice').removeClass('hidden');
- return false;
- }
-
- if (isInsecureConnection()) {
- $('#httpnotice').removeClass('hidden');
- }
-
- z = zlib.catch(function () {
- if ($('body').data('compression') !== 'none') {
- Alert.showWarning('Your browser doesn\'t support WebAssembly, used for zlib compression. You can create uncompressed documents, but can\'t read compressed ones.');
- }
- });
- return true;
- }
-
- return me;
- })();
-
/**
* (controller) main PrivateBin logic
*
@@ -5033,13 +4884,29 @@ jQuery.PrivateBin = (function($, RawDeflate) {
DiscussionViewer.prepareNewDiscussion();
};
+ /**
+ * try initializing zlib or display a warning if it fails,
+ * extracted from main init to allow unit testing
+ *
+ * @name Controller.initZ
+ * @function
+ */
+ me.initZ = function()
+ {
+ z = zlib.catch(function () {
+ if ($('body').data('compression') !== 'none') {
+ Alert.showWarning('Your browser doesn\'t support WebAssembly, used for zlib compression. You can create uncompressed documents, but can\'t read compressed ones.');
+ }
+ });
+ }
+
/**
* application start
*
* @name Controller.init
* @function
*/
- me.init = async function()
+ me.init = function()
{
// first load translations
I18n.loadTranslations();
@@ -5057,10 +4924,18 @@ jQuery.PrivateBin = (function($, RawDeflate) {
Prompt.init();
TopNav.init();
UiHelper.init();
- if (!InitialCheck.init()) {
+
+ // check for legacy browsers before going any further
+ if (!$.Legacy.Check.getInit()) {
+ // Legacy check didn't complete, wait and try again
+ setTimeout(init, 500);
+ return;
+ }
+ if (!$.Legacy.Check.getStatus()) {
// something major is wrong, stop right away
return;
}
+ me.initZ();
// check whether existing paste needs to be shown
try {
@@ -5100,7 +4975,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
ServerInteraction: ServerInteraction,
PasteEncrypter: PasteEncrypter,
PasteDecrypter: PasteDecrypter,
- InitialCheck: InitialCheck,
Controller: Controller
};
})(jQuery, RawDeflate);
diff --git a/js/test/InitialCheck.js b/js/test/Check.js
similarity index 87%
rename from js/test/InitialCheck.js
rename to js/test/Check.js
index 4e181f15..dd49236c 100644
--- a/js/test/InitialCheck.js
+++ b/js/test/Check.js
@@ -2,7 +2,7 @@
var common = require('../common');
/* global WebCrypto */
-describe('InitialCheck', function () {
+describe('Check', function () {
describe('init', function () {
this.timeout(30000);
before(function () {
@@ -23,7 +23,8 @@ describe('InitialCheck', function () {
'