Răsfoiți Sursa

Merge branch 'better-feature-detection'

El RIDO 6 ani în urmă
părinte
comite
b496ae42fd
31 a modificat fișierele cu 858 adăugiri și 476 ștergeri
  1. 9 9
      .eslintrc
  2. 11 2
      cfg/conf.sample.php
  3. 3 4
      i18n/bg.json
  4. 3 4
      i18n/cs.json
  5. 3 4
      i18n/de.json
  6. 3 4
      i18n/es.json
  7. 3 4
      i18n/fr.json
  8. 3 4
      i18n/hu.json
  9. 3 4
      i18n/it.json
  10. 3 4
      i18n/nl.json
  11. 3 4
      i18n/no.json
  12. 3 4
      i18n/oc.json
  13. 3 4
      i18n/pl.json
  14. 3 4
      i18n/pt.json
  15. 3 4
      i18n/ru.json
  16. 3 4
      i18n/sl.json
  17. 3 4
      i18n/zh.json
  18. 1 0
      js/common.js
  19. 309 0
      js/legacy.js
  20. 150 274
      js/privatebin.js
  21. 207 18
      js/test/Alert.js
  22. 83 0
      js/test/Check.js
  23. 7 1
      js/test/CryptTool.js
  24. 6 6
      js/test/Helper.js
  25. 0 88
      js/test/InitialCheck.js
  26. 1 0
      lib/Controller.php
  27. 15 2
      lib/Request.php
  28. 0 1
      robots.txt
  29. 6 8
      tpl/bootstrap.php
  30. 7 7
      tpl/page.php
  31. 1 0
      tst/ViewTest.php

+ 9 - 9
.eslintrc

@@ -1,3 +1,4 @@
+---
 parserOptions:
   ecmaVersion: 2017
 
@@ -11,17 +12,16 @@ env:
   es6: true
   jquery: true
   node: true
+  mocha: true
 
 globals:
-  DOMPurify: false
-  after: true
-  before: true
-  cleanup: true
-  describe: false
-  it: false
-  jsc: false
-  jsdom: true
-  kjua: true
+  DOMPurify: readonly
+  cleanup: writable
+  describe: readonly
+  jsc: readonly
+  jsdom: writable
+  kjua: writable
+  WebCrypto: writable
 
 # http://eslint.org/docs/rules/
 rules:

+ 11 - 2
cfg/conf.sample.php

@@ -68,8 +68,17 @@ languageselection = false
 ; custom scripts from third-party domains to your templates, e.g. tracking
 ; scripts or run your site behind certain DDoS-protection services.
 ; Check the documentation at https://content-security-policy.com/
-; Note: If you use a bootstrap theme, you can remove the allow-popups from the sandbox restrictions.
-; By default this disallows to load images from third-party servers, e.g. when they are embedded in pastes. If you wish to allow that, you can adjust the policy here. See https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-not-it-load-embedded-images for details.
+; Notes:
+; - If you use a bootstrap theme, you can remove the allow-popups from the
+;   sandbox restrictions.
+; - By default this disallows to load images from third-party servers, e.g. when
+;   they are embedded in pastes. If you wish to allow that, you can adjust the
+;   policy here. See https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-not-it-load-embedded-images
+;   for details.
+; - The 'unsafe-eval' is used in two cases; to check if the browser supports
+;   async functions and display an error if not and for Chrome to enable
+;   webassembly support (used for zlib compression). You can remove it if Chrome
+;   doesn't need to be supported and old browsers don't need to be warned.
 ; cspheader = "default-src 'none'; manifest-src 'self'; connect-src * blob:; script-src 'self' 'unsafe-eval'; style-src 'self'; font-src 'self'; img-src 'self' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals"
 
 ; stay compatible with PrivateBin Alpha 0.19, less secure

+ 3 - 4
i18n/bg.json

@@ -35,8 +35,6 @@
         "Услугата %s се нуждае от JavaScript, за да работи.<br />Съжаляваме за неудобството.",
     "%s requires a modern browser to work.":
         "%s се нуждае от съвременен браузър за да работи.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "Все още използваш Internet Explorer? Направи си услуга и го смени с модерен браузър:",
     "New":
         "Създаване",
     "Send":
@@ -155,11 +153,12 @@
     "Could not get paste data: %s":
         "Взимането на информацията беше неуспешно: %s",
     "QR code": "QR код",
-    "I love you too, bot…": "И аз те обичам, бот…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "Този сайт използва несигурна HTTP връзка. Моля използвайте само за проби.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "<a href=\"%s\">Вижте тази страница</a> за повече информация.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Браузъра ви може да се нуждае от HTTPS връзка за да използва WebCrypto API. Пробвай <a href=\"%s\">да минеш на HTTPS</a>."
+        "Браузъра ви може да се нуждае от HTTPS връзка за да използва WebCrypto API. Пробвай <a href=\"%s\">да минеш на HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/cs.json

@@ -35,8 +35,6 @@
         "JavaScript is required for %s to work.<br />Sorry for the inconvenience.",
     "%s requires a modern browser to work.":
         "%%s requires a modern browser to work.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:",
     "New":
         "Nový",
     "Send":
@@ -155,11 +153,12 @@
     "Could not get paste data: %s":
         "Could not get paste data: %s",
     "QR code": "QR code",
-    "I love you too, bot…": "I love you too, bot…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "This website is using an insecure HTTP connection! Please use it only for testing.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "For more information <a href=\"%s\">see this FAQ entry</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>."
+        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/de.json

@@ -35,8 +35,6 @@
         "JavaScript ist eine Voraussetzung, um %s zu nutzen.<br />Bitte entschuldige die Unannehmlichkeiten.",
     "%s requires a modern browser to work.":
         "%s setzt einen modernen Browser voraus, um funktionieren zu können.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "Du benutzt immer noch den Internet Explorer? Tu Dir einen Gefallen und wechsle zu einem moderneren Browser:",
     "New":
         "Neu",
     "Send":
@@ -155,11 +153,12 @@
     "Could not get paste data: %s":
         "Text konnte nicht geladen werden: %s",
     "QR code": "QR code",
-    "I love you too, bot…": "Ich mag Dich auch, bot…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "Diese Webseite verwendet eine unsichere HTTP Verbindung! Bitte benutze sie nur zum Testen.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "<a href=\"%s\">Besuche diesen FAQ Eintrag</a> für weitere Informationen dazu.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Dein Browser benötigt möglicherweise eine HTTPS Verbindung um das WebCrypto API nutzen zu können. Versuche <a href=\"%s\">auf HTTPS zu wechseln</a>."
+        "Dein Browser benötigt möglicherweise eine HTTPS Verbindung um das WebCrypto API nutzen zu können. Versuche <a href=\"%s\">auf HTTPS zu wechseln</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Dein Browser unterstützt WebAssembly nicht, welches für zlib Komprimierung benötigt wird. Du kannst unkomprimierte Dokumente erzeugen, aber keine komprimierten lesen."
 }

+ 3 - 4
i18n/es.json

@@ -35,8 +35,6 @@
         "JavaScript es necesario para que %s funcione.<br />Sentimos los inconvenientes ocasionados.",
     "%s requires a modern browser to work.":
         "%s requiere un navegador moderno para funcionar.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "¿Sigues usando Internet Explorer? Hazte un favor, cambia a un navegador moderno:",
     "New":
         "Nuevo",
     "Send":
@@ -155,11 +153,12 @@
     "Could not get paste data: %s":
         "No se pudieron obtener los datos: %s",
     "QR code": "Código QR",
-    "I love you too, bot…": "I love you too, bot…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "This website is using an insecure HTTP connection! Please use it only for testing.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "For more information <a href=\"%s\">see this FAQ entry</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>."
+        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/fr.json

@@ -35,8 +35,6 @@
         "JavaScript est requis pour faire fonctionner %s. <br />Désolé pour cet inconvénient.",
     "%s requires a modern browser to work.":
         "%s nécessite un navigateur moderne pour fonctionner.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "Encore sur Internet Explorer ? Faites-vous une faveur, passez à un navigateur moderne :",
     "New":
         "Nouveau",
     "Send":
@@ -164,11 +162,12 @@
     "Could not get paste data: %s":
         "Impossible d'obtenir les données du paste: %s",
     "QR code": "QR code",
-    "I love you too, bot…": "Je t’aime aussi, bot…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "Ce site web utilise une connexion HTTP non sécurisée ! Veuillez l’utiliser uniquement pour des tests.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "Pour plus d'informations <a href=\"%s\">consultez cette rubrique de la FAQ</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Votre navigateur peut nécessiter une connexion HTTPS pour prendre en charge l’API WebCrypto. Essayez <a href=\"%s\">de passer en HTTPS</a>."
+        "Votre navigateur peut nécessiter une connexion HTTPS pour prendre en charge l’API WebCrypto. Essayez <a href=\"%s\">de passer en HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/hu.json

@@ -35,8 +35,6 @@
         "JavaScript szükséges a %s működéséhez. Elnézést a fennakadásért.",
     "%s requires a modern browser to work.":
         "A %s működéséhez a jelenleginél újabb böngészőre van szükség.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "Még mindig Internet Explorert használsz? Ideje váltani:",
     "New":
         "Új",
     "Send":
@@ -155,11 +153,12 @@
     "Could not get paste data: %s":
         "Could not get paste data: %s",
     "QR code": "QR code",
-    "I love you too, bot…": "I love you too, bot…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "This website is using an insecure HTTP connection! Please use it only for testing.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "For more information <a href=\"%s\">see this FAQ entry</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>."
+        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/it.json

@@ -35,8 +35,6 @@
         "%s funziona solo con JavaScript attivo.<br />Ci dispiace per l'inconveniente.",
     "%s requires a modern browser to work.":
         "%s richiede un browser moderno e aggiornato per funzionare.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "Usi ancora Internet Explorer? Ti consigliamo di passare ad un browser più sicuro:",
     "New":
         "Nuovo",
     "Send":
@@ -155,11 +153,12 @@
     "Could not get paste data: %s":
         "Could not get paste data: %s",
     "QR code": "QR code",
-    "I love you too, bot…": "I love you too, bot…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "This website is using an insecure HTTP connection! Please use it only for testing.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "For more information <a href=\"%s\">see this FAQ entry</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>."
+        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/nl.json

@@ -35,8 +35,6 @@
         "JavaScript vereist om %s te laten werken.<br />Sorry voor het ongemak.",
     "%s requires a modern browser to work.":
         "%s vereist een moderne browser om te kunnen werken ",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "Gebruik je nog steeds Internet explorer? Doe jezelf een plezier en maak gebruik van een moderne browser:",
     "New":
         "Nieuw",
     "Send":
@@ -155,11 +153,12 @@
     "Could not get paste data: %s":
         "Could not get paste data: %s",
     "QR code": "QR code",
-    "I love you too, bot…": "I love you too, bot…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "This website is using an insecure HTTP connection! Please use it only for testing.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "For more information <a href=\"%s\">see this FAQ entry</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>."
+        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/no.json

@@ -35,8 +35,6 @@
         "Javascript kreves for at %s skal fungere<br />Beklager.",
     "%s requires a modern browser to work.":
         "%s krever en moderne nettleser for å fungere.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "Fortsatt bruker av Internet Explorer? Gjør deg selv en tjeneste og bytt til en moderne nettleser:",
     "New":
         "Ny",
     "Send":
@@ -155,11 +153,12 @@
     "Could not get paste data: %s":
         "Kunne ikke hente utklippsdata: %s",
     "QR code": "QR kode",
-    "I love you too, bot…": "Jeg elsker deg også, bot…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "Denne websiden bruker usikker HTTP tilkobling! Bruk den kun for testing.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "For mer informasjon <a href=\"%s\">se ofte stilte spørsmål</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Din nettleser har behov for HTTPS tilkobling for å støtte WebCrypto biblioteket. Prøv å <a href=\"%s\">bytt til HTTPS</a>."
+        "Din nettleser har behov for HTTPS tilkobling for å støtte WebCrypto biblioteket. Prøv å <a href=\"%s\">bytt til HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/oc.json

@@ -35,8 +35,6 @@
         "JavaScript es requesit per far foncionar %s. <br />O planhèm per l’inconvenient.",
     "%s requires a modern browser to work.":
         "%s necessita un navigator modèrn per foncionar.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "Encora sus Internet Explorer ? Fasètz-vos una favor, passatz a un navigator modèrn :",
     "New":
         "Nòu",
     "Send":
@@ -164,11 +162,12 @@
     "Could not get paste data: %s":
         "Recuperacion impossibla de las donadas copiadas : %s",
     "QR code": "Còdi QR",
-    "I love you too, bot…": "T’aimi tanben, robòt…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "Aqueste site utiliza una connexion HTTP pas segura ! Mercés de l’utilizar pas que per d’ensages.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "Per mai d’informacions <a href=\"%s\">vejatz aqueste article de FAQ</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Se pòt que vòstre navigator faga besonh d’una connexion HTTPS per èsser compatible amb l’API WebCrypto. Ensajatz de <a href=\"%s\">passar al HTTPS</a>."
+        "Se pòt que vòstre navigator faga besonh d’una connexion HTTPS per èsser compatible amb l’API WebCrypto. Ensajatz de <a href=\"%s\">passar al HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/pl.json

@@ -35,8 +35,6 @@
         "Do działania %sa jest wymagany JavaScript. Przepraszamy za tę niedogodność.",
     "%s requires a modern browser to work.":
         "%s wymaga do działania nowoczesnej przeglądarki.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "Cały czas używasz Internet Explorera? Zrób sobie przysługę, przesiądź się na nowoczesną przeglądarkę:",
     "New":
         "Nowa",
     "Send":
@@ -155,11 +153,12 @@
     "Could not get paste data: %s":
         "Nie można było pobrać danych wklejki: %s",
     "QR code": "Kod QR",
-    "I love you too, bot…": "I love you too, bot…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "This website is using an insecure HTTP connection! Please use it only for testing.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "For more information <a href=\"%s\">see this FAQ entry</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>."
+        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/pt.json

@@ -35,8 +35,6 @@
         "JavaScript é necessário para que %s funcione.<br />Pedimos desculpas pela inconveniência.",
     "%s requires a modern browser to work.":
         "%s requer um navegador moderno para funcionar.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "Ainda usando Internet Explorer? Faça-se um favor, mude para um navegador moderno:",
     "New":
         "Novo",
     "Send":
@@ -155,11 +153,12 @@
     "Could not get paste data: %s":
         "Could not get paste data: %s",
     "QR code": "QR code",
-    "I love you too, bot…": "I love you too, bot…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "This website is using an insecure HTTP connection! Please use it only for testing.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "For more information <a href=\"%s\">see this FAQ entry</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>."
+        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/ru.json

@@ -35,8 +35,6 @@
         "Для работы %s требуется включенный JavaScript.<br />Приносим извинения за неудобства.",
     "%s requires a modern browser to work.":
         "Для работы %s требуется более современный браузер.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "До сих пор используете Internet Explorer? Пожалейте себя, перейдите на более современный браузер:",
     "New":
         "Новая запись",
     "Send":
@@ -165,11 +163,12 @@
     "Could not get paste data: %s":
         "Не удалось получить данные записи: %s",
     "QR code": "QR код",
-    "I love you too, bot…": "Я тоже люблю тебя, бот…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "Данный сайт использует незащищенное HTTP подключение! Пожалуйста используйте его только для тестирования.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "Для продробностей <a href=\"%s\">прочтите информацию в FAQ</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Ваш браузер требует использования HTTPS подключения для поддержки WebCrypto API. Попробуйте <a href=\"%s\">переключиться на HTTPS</a>."
+        "Ваш браузер требует использования HTTPS подключения для поддержки WebCrypto API. Попробуйте <a href=\"%s\">переключиться на HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/sl.json

@@ -35,8 +35,6 @@
         "Da %s deluje, moraš vklopiti JavaScript.<br />Oprosti za povročene nevšečnosti.",
     "%s requires a modern browser to work.":
         "%s za svoje delovanje potrebuje moderen brskalnik.",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "Še vedno uporabljaš Internet Explorer? Naredi si uslugo, preklopi na moderen brskalnik:",
     "New":
         "Nov prilepek",
     "Send":
@@ -164,11 +162,12 @@
     "Could not get paste data: %s":
         "Could not get paste data: %s",
     "QR code": "QR code",
-    "I love you too, bot…": "I love you too, bot…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "This website is using an insecure HTTP connection! Please use it only for testing.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "For more information <a href=\"%s\">see this FAQ entry</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>."
+        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 3 - 4
i18n/zh.json

@@ -35,8 +35,6 @@
         "%s需要JavaScript来进行加解密。<br />给你带来的不便敬请谅解。",
     "%s requires a modern browser to work.":
         "%s需要在现代浏览器上工作。",
-    "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
-        "还在使用Internet Explorer?对自己好点,换一个现代浏览器:",
     "New":
         "新建",
     "Send":
@@ -155,11 +153,12 @@
     "Could not get paste data: %s":
         "无法获取粘贴数据:%s",
     "QR code": "二维码",
-    "I love you too, bot…": "I love you too, bot…",
     "This website is using an insecure HTTP connection! Please use it only for testing.":
         "This website is using an insecure HTTP connection! Please use it only for testing.",
     "For more information <a href=\"%s\">see this FAQ entry</a>.":
         "For more information <a href=\"%s\">see this FAQ entry</a>.",
     "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.":
-        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>."
+        "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
+    "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.":
+        "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones."
 }

+ 1 - 0
js/common.js

@@ -19,6 +19,7 @@ global.prettyPrintOne = window.PR.prettyPrintOne;
 global.showdown = require('./showdown-1.9.1');
 global.DOMPurify = require('./purify-2.0.1');
 global.baseX = require('./base-x-3.0.5.1').baseX;
+global.Legacy = require('./legacy').Legacy;
 require('./bootstrap-3.3.7');
 require('./privatebin');
 

+ 309 - 0
js/legacy.js

@@ -0,0 +1,309 @@
+/**
+ * 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:
+ * - jQuery (doesn't work in older browsers)
+ * - ES5 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 6, to at least display the error message
+ */
+
+'use strict';
+(function() {
+    /**
+     * 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 j = 0; j < hostname.length; j++) {
+                if (window.location.hostname === hostname[j]) {
+                    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;
+            }
+
+            // async & ES6 support
+            try {
+                eval('async () => {}');
+            } catch (e) {
+                if (e instanceof SyntaxError) {
+                    return true;
+                } else {
+                    throw e; // throws CSP error
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * shows an error message
+         *
+         * @private
+         * @name   Check.showError
+         * @param  {string} message
+         * @function
+         */
+        function showError(message)
+        {
+            var element = document.getElementById('errormessage');
+            if (message.indexOf('<a') === -1) {
+                element.appendChild(
+                    document.createTextNode(message)
+                );
+            } else {
+                element.innerHTML = message;
+            }
+            removeHiddenFromId('errormessage');
+        }
+
+        /**
+         * removes "hidden" CSS class from element with given ID
+         *
+         * @private
+         * @name   Check.removeHiddenFromId
+         * @param  {string} id
+         * @function
+         */
+        function removeHiddenFromId(id)
+        {
+            var element = document.getElementById(id);
+            if (element) {
+                element.className = element.className.replace(/\bhidden\b/g, '');
+            }
+        }
+
+        /**
+         * 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()) {
+                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()) {
+                    removeHiddenFromId('insecurecontextnotice');
+                }
+                removeHiddenFromId('oldnotice');
+                init = true;
+                return;
+            }
+
+            if (!isSecureContext()) {
+                removeHiddenFromId('httpnotice');
+            }
+            init = true;
+
+            // only if everything passed, we set the status to true
+            status = true;
+        };
+
+        return me;
+    })();
+
+    // main application start, called when DOM is fully loaded
+    if (document.readyState === 'complete' || (!document.attachEvent && document.readyState === 'interactive')) {
+        Check.init();
+    } else {
+        if (document.addEventListener) {
+            // first choice is DOMContentLoaded event
+            document.addEventListener('DOMContentLoaded', Check.init, false);
+            // backup is window load event
+            window.addEventListener('load', Check.init, false);
+        } else {
+            // must be IE
+            document.attachEvent('onreadystatechange', Check.init);
+            window.attachEvent('onload', Check.init);
+        }
+    }
+
+    this.Legacy = {
+        Check: Check
+    };
+}).call(this);

+ 150 - 274
js/privatebin.js

@@ -9,20 +9,12 @@
  * @version   1.3
  * @name      PrivateBin
  * @namespace
+ *
+ * global Base64, DOMPurify, FileReader, RawDeflate, history, navigator, prettyPrint, prettyPrintOne, showdown, kjua
  */
 
-/** global: Base64 */
-/** global: DOMPurify */
-/** global: FileReader */
-/** global: RawDeflate */
-/** global: history */
-/** global: navigator */
-/** global: prettyPrint */
-/** global: prettyPrintOne */
-/** global: showdown */
-/** global: kjua */
-
 jQuery.fn.draghover = function() {
+    'use strict';
     return this.each(function() {
         let collection = $(),
             self = $(this);
@@ -566,12 +558,11 @@ jQuery.PrivateBin = (function($, RawDeflate) {
 
             // if $element is given, apply text to element
             if ($element !== null) {
-                // get last text node of element
-                let content = $element.contents();
-                if (content.length > 1) {
-                    content[content.length - 1].nodeValue = ' ' + output;
-                } else {
+                // avoid HTML entity encoding if translation contains link
+                if (output.indexOf('<a') === -1) {
                     $element.text(output);
+                } else {
+                    $element.html(output);
                 }
             }
 
@@ -781,15 +772,20 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          * @private
          * @param  {string} message
          * @param  {string} mode
+         * @param  {object} zlib
+         * @throws {string}
          * @return {ArrayBuffer} data
          */
-        async function compress(message, mode)
+        async function compress(message, mode, zlib)
         {
             message = stringToArraybuffer(
                 utf16To8(message)
             );
             if (mode === 'zlib') {
-                return z.deflate(message).buffer;
+                if (typeof zlib === 'undefined') {
+                    throw 'Error compressing paste, due to missing WebAssembly support.'
+                }
+                return zlib.deflate(message).buffer;
             }
             return message;
         }
@@ -803,13 +799,18 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          * @private
          * @param  {ArrayBuffer} data
          * @param  {string} mode
+         * @param  {object} zlib
+         * @throws {string}
          * @return {string} message
          */
-        async function decompress(data, mode)
+        async function decompress(data, mode, zlib)
         {
             if (mode === 'zlib' || mode === 'none') {
                 if (mode === 'zlib') {
-                    data = z.inflate(
+                    if (typeof zlib === 'undefined') {
+                        throw 'Error decompressing paste, due to missing WebAssembly support.'
+                    }
+                    data = zlib.inflate(
                         new Uint8Array(data)
                     ).buffer;
                 }
@@ -883,7 +884,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                         stringToArraybuffer(
                             utf16To8(password)
                         )
-                    );
+                    ).catch(Alert.showError);
                     password = Array.prototype.map.call(
                         new Uint8Array(passwordBuffer),
                         x => ('00' + x.toString(16)).slice(-2)
@@ -903,7 +904,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                 {name: 'PBKDF2'}, // we use PBKDF2 for key derivation
                 false, // the key may not be exported
                 ['deriveKey'] // we may only use it for key derivation
-            );
+            ).catch(Alert.showError);
 
             // derive a stronger key for use with AES
             return window.crypto.subtle.deriveKey(
@@ -920,7 +921,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                 },
                 false, // the key may not be exported
                 ['encrypt', 'decrypt'] // we may only use it for en- and decryption
-            );
+            ).catch(Alert.showError);
         }
 
         /**
@@ -957,18 +958,24 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          */
         me.cipher = async function(key, password, message, adata)
         {
+            let zlib = (await z);
             // 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
-                $('body').data('compression') || 'zlib' // compression
-            ], encodedSpec = [];
+            const compression = (
+                    typeof zlib === 'undefined' ?
+                    'none' : // client lacks support for WASM
+                    ($('body').data('compression') || 'zlib')
+                ),
+                spec = [
+                    getRandomBytes(16), // initialization vector
+                    getRandomBytes(8),  // salt
+                    100000,             // iterations
+                    256,                // key size
+                    128,                // tag size
+                    'aes',              // algorithm
+                    'gcm',              // algorithm mode
+                    compression         // compression
+                ], encodedSpec = [];
             for (let i = 0; i < spec.length; ++i) {
                 encodedSpec[i] = i < 2 ? btoa(spec[i]) : spec[i];
             }
@@ -987,8 +994,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                         await window.crypto.subtle.encrypt(
                             cryptoSettings(JSON.stringify(adata), spec),
                             await deriveKey(key, password, spec),
-                            await compress(message, spec[7])
-                        )
+                            await compress(message, compression, zlib)
+                        ).catch(Alert.showError)
                     )
                 ),
                 adata
@@ -1008,7 +1015,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          */
         me.decipher = async function(key, password, data)
         {
-            let adataString, spec, cipherMessage;
+            let adataString, spec, cipherMessage, plaintext;
+            let zlib = (await z);
             if (data instanceof Array) {
                 // version 2
                 adataString = JSON.stringify(data[1]);
@@ -1035,20 +1043,29 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             }
             spec[0] = atob(spec[0]);
             spec[1] = atob(spec[1]);
+            if (spec[7] === 'zlib') {
+                if (typeof zlib === 'undefined') {
+                    throw 'Error decompressing paste, due to missing WebAssembly support.'
+                }
+            }
             try {
-                return await decompress(
-                    await window.crypto.subtle.decrypt(
-                        cryptoSettings(adataString, spec),
-                        await deriveKey(key, password, spec),
-                        stringToArraybuffer(
-                            atob(cipherMessage)
-                        )
-                    ),
-                    spec[7]
+                plaintext = await window.crypto.subtle.decrypt(
+                    cryptoSettings(adataString, spec),
+                    await deriveKey(key, password, spec),
+                    stringToArraybuffer(
+                        atob(cipherMessage)
+                    )
                 );
             } catch(err) {
+                console.error(err);
                 return '';
             }
+            try {
+                return await decompress(plaintext, spec[7], zlib);
+            } catch(err) {
+                Alert.showError(err);
+                return err;
+            }
         };
 
         /**
@@ -1194,7 +1211,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         me.getPasteId = function()
         {
             const idRegEx = /^[a-z0-9]{16}$/;
-            const idRegExFind = /[a-z0-9]{16}/;
 
             // return cached value
             if (id !== null) {
@@ -1483,7 +1499,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         const alertType = [
             'loading', // not in bootstrap CSS, but using a plausible value here
             'info',    // status icon
-            'warning', // not used yet
+            'warning', // warning icon
             'danger'   // error icon
         ];
 
@@ -1527,28 +1543,35 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                     icon = null; // icons not supported in this case
                 }
             }
+            let $translationTarget = $element;
 
-            // handle icon
-            if (icon !== null && // icon was passed
-                icon !== currentIcon[id] // and it differs from current icon
-            ) {
-                let $glyphIcon = $element.find(':first');
-
-                // remove (previous) icon
-                $glyphIcon.removeClass(currentIcon[id]);
+            // handle icon, if template uses one
+            const $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);
 
-                // 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]);
+                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);
             }
@@ -1573,6 +1596,25 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             handleNotification(1, $statusMessage, message, icon);
         };
 
+        /**
+         * display a warning message
+         *
+         * This automatically passes the text to I18n for translation.
+         *
+         * @name   Alert.showWarning
+         * @function
+         * @param  {string|array} message     string, use an array for %s/%d options
+         * @param  {string|null}  icon        optional, the icon to show, default:
+         *                                    leave previous icon
+         */
+        me.showWarning = function(message, icon)
+        {
+            $errorMessage.find(':first')
+                         .removeClass(currentIcon[3])
+                         .addClass(currentIcon[2]);
+            handleNotification(2, $errorMessage, message, icon);
+        };
+
         /**
          * display an error message
          *
@@ -1699,7 +1741,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             currentIcon = [
                 'glyphicon-time', // loading icon
                 'glyphicon-info-sign', // status icon
-                '', // reserved for warning, not used yet
+                'glyphicon-warning-sign', // warning icon
                 'glyphicon-alert' // error icon
             ];
         };
@@ -1768,14 +1810,14 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                             return;
                         }
                     }
-                    Alert.showError(
-                        I18n._('Cannot parse response from URL shortener.')
-                    );
+                    Alert.showError('Cannot parse response from URL shortener.');
                 }
             })
             .fail(function(data, textStatus, errorThrown) {
                 console.error(textStatus, errorThrown);
-                // we don't know why it failed, could be CORS of the external server not setup properly, in which case we follow old behavior to open it in new tab
+                // we don't know why it failed, could be CORS of the external
+                // server not setup properly, in which case we follow old
+                // behavior to open it in new tab
                 window.open(
                     `${$shortenButton.data('shortener')}${encodeURIComponent($pasteUrl.attr('href'))}`,
                     '_blank',
@@ -2731,9 +2773,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                 // revert loading status…
                 me.hideAttachment();
                 me.hideAttachmentPreview();
-                Alert.showError(
-                    I18n._('Your browser does not support uploading encrypted files. Please use a newer browser.')
-                );
+                Alert.showWarning('Your browser does not support uploading encrypted files. Please use a newer browser.');
                 return;
             }
 
@@ -2965,11 +3005,11 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         me.init = function()
         {
             $attachment = $('#attachment');
-            if($attachment.length){
+            $dragAndDropFileName = $('#dragAndDropFileName');
+            $dropzone = $('#dropzone');
+            if($attachment.length) {
                 $attachmentLink = $('#attachment a');
                 $attachmentPreview = $('#attachmentPreview');
-                $dragAndDropFileName = $('#dragAndDropFileName');
-                $dropzone = $('#dropzone');
 
                 $fileInput = $('#file');
                 addDragDropHandler();
@@ -4194,8 +4234,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
     const PasteEncrypter = (function () {
         const me = {};
 
-        let requirementsChecked = false;
-
         /**
          * called after successful paste upload
          *
@@ -4428,16 +4466,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                             });
                             cipherMessage['attachment'] = await fileReading;
                         } else {
-                            Alert.showError(
-                                I18n._('Cannot process attachment data.')
-                            );
-                            throw new TypeError('Cannot process attachment data.');
+                            const error = 'Cannot process attachment data.';
+                            Alert.showError(error);
+                            throw new TypeError(error);
                         }
                     } catch (error) {
                         console.error(error);
-                        Alert.showError(
-                            I18n._('Cannot retrieve attachment.')
-                        );
+                        Alert.showError('Cannot retrieve attachment.');
                         throw error;
                     }
                 }
@@ -4494,7 +4529,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
 
             // if all tries failed, we can only return an error
             if (plaindata.length === 0) {
-                throw 'failed to decipher data';
+                return false;
             }
 
             return plaindata;
@@ -4523,13 +4558,14 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                 if (password.length === 0) {
                     throw 'waiting on user to provide a password';
                 } else {
-                    displayDecryptionError('failed to decipher paste text: Incorrect password?');
-                    throw 'waiting on user to provide correct password';
+                    Alert.hideLoading();
+                    // reset password, so it can be re-entered
+                    Prompt.reset();
+                    TopNav.showRetryButton();
+                    throw 'Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.';
                 }
             }
 
-            let format = '',
-                text   = '';
             if (paste.v > 1) {
                 // version 2 paste
                 const pasteMessage = JSON.parse(pastePlain);
@@ -4616,29 +4652,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             });
         }
 
-        /**
-         * displays and logs decryption errors
-         *
-         * @name   PasteDecrypter.displayDecryptionError
-         * @private
-         * @function
-         * @param  {string} message
-         */
-        function displayDecryptionError(message)
-        {
-            Alert.hideLoading();
-
-            // log detailed error, but display generic translation
-            console.error(message);
-            Alert.showError(
-                I18n._('Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.')
-            );
-
-            // reset password, so it can be re-entered
-            Prompt.reset();
-            TopNav.showRetryButton();
-        }
-
         /**
          * show decrypted text in the display area, including discussion (if open)
          *
@@ -4690,170 +4703,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                 .catch((err) => {
                     // wait for the user to type in the password,
                     // then PasteDecrypter.run will be called again
-                    console.error(err);
+                    Alert.showError(err);
                 });
         };
 
         return me;
     })();
 
-
-    /**
-     * initial (security) check
-     *
-     * @name   InitialCheck
-     * @param  {object} window
-     * @param  {object} document
-     * @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 (typeof window.crypto !== 'object') {
-                return true;
-            }
-
-            if (typeof WebAssembly !== 'object' && typeof WebAssembly.instantiate !== 'function') {
-                return true;
-            }
-            try {
-                // [\0, 'a', 's', 'm', (uint_32) 1] - smallest valid wasm module
-                const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
-                if (
-                    !(
-                        module instanceof WebAssembly.Module &&
-                        new WebAssembly.Instance(module) instanceof WebAssembly.Instance
-                    )
-                ) {
-                    return true;
-                }
-            } catch (e) {
-                return true;
-            }
-
-             // not checking for async/await, ES6, Promise or Uint8Array 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 <a href="%s">switching to HTTPS</a>.', 'https' + window.location.href.slice(4)]);
-                }
-                $('#oldnotice').removeClass('hidden');
-                return false;
-            }
-
-            if (isInsecureConnection()) {
-                $('#httpnotice').removeClass('hidden');
-            }
-
-            return true;
-        }
-
-        return me;
-    })();
-
     /**
      * (controller) main PrivateBin logic
      *
@@ -4926,9 +4782,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
 
                 // missing decryption key (or paste ID) in URL?
                 if (window.location.hash.length === 0) {
-                    Alert.showError(
-                        I18n._('Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)')
-                    );
+                    Alert.showError('Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)');
                     return;
                 }
             }
@@ -5030,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();
@@ -5054,11 +4924,18 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             Prompt.init();
             TopNav.init();
             UiHelper.init();
-            z = (await zlib);
-            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 {
@@ -5098,7 +4975,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         ServerInteraction: ServerInteraction,
         PasteEncrypter: PasteEncrypter,
         PasteDecrypter: PasteDecrypter,
-        InitialCheck: InitialCheck,
         Controller: Controller
     };
 })(jQuery, RawDeflate);

+ 207 - 18
js/test/Alert.js

@@ -4,16 +4,55 @@ var common = require('../common');
 describe('Alert', function () {
     describe('showStatus', function () {
         jsc.property(
-            'shows a status message',
+            'shows a status message (basic)',
             jsc.array(common.jscAlnumString()),
             jsc.array(common.jscAlnumString()),
             function (icon, message) {
                 icon = icon.join('');
                 message = message.join('');
-                var expected = '<div id="status" role="alert" ' +
+                const expected = '<div id="status">' + message + '</div>';
+                $('body').html(
+                    '<div id="status"></div>'
+                );
+                $.PrivateBin.Alert.init();
+                $.PrivateBin.Alert.showStatus(message, icon);
+                const result = $('body').html();
+                return expected === result;
+            }
+        );
+
+        jsc.property(
+            'shows a status message (bootstrap)',
+            jsc.array(common.jscAlnumString()),
+            function (message) {
+                message = message.join('');
+                const 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);
+                const 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('');
+                const 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 ' +
@@ -21,7 +60,76 @@ describe('Alert', function () {
                 );
                 $.PrivateBin.Alert.init();
                 $.PrivateBin.Alert.showStatus(message, icon);
-                var result = $('body').html();
+                const result = $('body').html();
+                return expected === result;
+            }
+        );
+    });
+
+    describe('showWarning', function () {
+        before(function () {
+            cleanup();
+        });
+
+        jsc.property(
+            'shows a warning message (basic)',
+            jsc.array(common.jscAlnumString()),
+            jsc.array(common.jscAlnumString()),
+            function (icon, message) {
+                icon = icon.join('');
+                message = message.join('');
+                const expected = '<div id="errormessage">' + message + '</div>';
+                $('body').html(
+                    '<div id="errormessage"></div>'
+                );
+                $.PrivateBin.Alert.init();
+                $.PrivateBin.Alert.showWarning(message, icon);
+                const result = $('body').html();
+                return expected === result;
+            }
+        );
+
+        jsc.property(
+            'shows a warning message (bootstrap)',
+            jsc.array(common.jscAlnumString()),
+            jsc.array(common.jscAlnumString()),
+            function (message) {
+                message = message.join('');
+                const expected = '<div id="errormessage" role="alert" ' +
+                    'class="statusmessage alert alert-danger"><span ' +
+                    'class="glyphicon glyphicon-warning-sign" ' +
+                    '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.showWarning(message);
+                const result = $('body').html();
+                return expected === result;
+            }
+        );
+
+        jsc.property(
+            'shows a warning message (bootstrap, custom icon)',
+            jsc.array(common.jscAlnumString()),
+            jsc.array(common.jscAlnumString()),
+            function (icon, message) {
+                icon = icon.join('');
+                message = message.join('');
+                const expected = '<div id="errormessage" role="alert" ' +
+                    'class="statusmessage alert alert-danger"><span ' +
+                    'class="glyphicon glyphicon-' + icon +
+                    '" 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.showWarning(message, icon);
+                const result = $('body').html();
                 return expected === result;
             }
         );
@@ -33,16 +141,56 @@ describe('Alert', function () {
         });
 
         jsc.property(
-            'shows an error message',
+            'shows an error message (basic)',
+            jsc.array(common.jscAlnumString()),
+            jsc.array(common.jscAlnumString()),
+            function (icon, message) {
+                icon = icon.join('');
+                message = message.join('');
+                const expected = '<div id="errormessage">' + message + '</div>';
+                $('body').html(
+                    '<div id="errormessage"></div>'
+                );
+                $.PrivateBin.Alert.init();
+                $.PrivateBin.Alert.showError(message, icon);
+                const result = $('body').html();
+                return expected === result;
+            }
+        );
+
+        jsc.property(
+            'shows an error message (bootstrap)',
+            jsc.array(common.jscAlnumString()),
+            jsc.array(common.jscAlnumString()),
+            function (icon, message) {
+                message = message.join('');
+                const 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);
+                const 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) {
                 icon = icon.join('');
                 message = message.join('');
-                var expected = '<div id="errormessage" role="alert" ' +
+                const 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 ' +
@@ -50,7 +198,7 @@ describe('Alert', function () {
                 );
                 $.PrivateBin.Alert.init();
                 $.PrivateBin.Alert.showError(message, icon);
-                var result = $('body').html();
+                const result = $('body').html();
                 return expected === result;
             }
         );
@@ -62,17 +210,36 @@ describe('Alert', function () {
         });
 
         jsc.property(
-            'shows remaining time',
+            'shows remaining time (basic)',
+            jsc.array(common.jscAlnumString()),
+            jsc.array(common.jscAlnumString()),
+            'integer',
+            function (message, string, number) {
+                message = message.join('');
+                string = string.join('');
+                const 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]);
+                const result = $('body').html();
+                return expected === result;
+            }
+        );
+
+        jsc.property(
+            'shows remaining time (bootstrap)',
             jsc.array(common.jscAlnumString()),
             jsc.array(common.jscAlnumString()),
             'integer',
             function (message, string, number) {
                 message = message.join('');
                 string = string.join('');
-                var expected = '<div id="remainingtime" role="alert" ' +
+                const 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 ' +
@@ -80,7 +247,7 @@ describe('Alert', function () {
                 );
                 $.PrivateBin.Alert.init();
                 $.PrivateBin.Alert.showRemaining(['%s' + message + '%d', string, number]);
-                var result = $('body').html();
+                const result = $('body').html();
                 return expected === result;
             }
         );
@@ -92,20 +259,42 @@ describe('Alert', function () {
         });
 
         jsc.property(
-            'shows a loading message',
+            'shows a loading message (basic)',
+            jsc.array(common.jscAlnumString()),
+            jsc.array(common.jscAlnumString()),
+            function (message, icon) {
+                message = message.join('');
+                icon = icon.join('');
+                const defaultMessage = 'Loading…';
+                if (message.length === 0) {
+                    message = defaultMessage;
+                }
+                const expected = '<div id="loadingindicator" class="">' + message + '</div>';
+                $('body').html(
+                    '<div id="loadingindicator" class="hidden">' + defaultMessage + '</div>'
+                );
+                $.PrivateBin.Alert.init();
+                $.PrivateBin.Alert.showLoading(message, icon);
+                const result = $('body').html();
+                return expected === result;
+            }
+        );
+
+        jsc.property(
+            'shows a loading message (bootstrap)',
             jsc.array(common.jscAlnumString()),
             jsc.array(common.jscAlnumString()),
             function (message, icon) {
                 message = message.join('');
                 icon = icon.join('');
-                var defaultMessage = 'Loading…';
+                const defaultMessage = 'Loading…';
                 if (message.length === 0) {
                     message = defaultMessage;
                 }
-                var expected = '<ul class="nav navbar-nav"><li ' +
+                const 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 ' +
@@ -114,7 +303,7 @@ describe('Alert', function () {
                 );
                 $.PrivateBin.Alert.init();
                 $.PrivateBin.Alert.showLoading(message, icon);
-                var result = $('body').html();
+                const result = $('body').html();
                 return expected === result;
             }
         );
@@ -182,7 +371,7 @@ describe('Alert', function () {
             jsc.array(common.jscAlnumString()),
             function (trigger, message) {
                 message = message.join('');
-                var handlerCalled = false,
+                let handlerCalled = false,
                     defaultMessage = 'Loading…',
                     functions = [
                         $.PrivateBin.Alert.showStatus,

+ 83 - 0
js/test/Check.js

@@ -0,0 +1,83 @@
+'use strict';
+var common = require('../common');
+/* global Legacy, WebCrypto */
+
+describe('Check', function () {
+    describe('init', function () {
+        this.timeout(30000);
+        before(function () {
+            cleanup();
+        });
+
+        it('returns false and shows error, if a bot UA is detected', function () {
+            jsc.assert(jsc.forall(
+                'string',
+                jsc.elements(['Bot', 'bot']),
+                'string',
+                function (prefix, botBit, suffix) {
+                    const clean = jsdom(
+                        '<html><body><div id="errormessage" class="hidden"></div>' +
+                        '</body></html>', {
+                            'userAgent': prefix + botBit + suffix
+                        }
+                    );
+                    Legacy.Check.init();
+                    const result1 = Legacy.Check.getInit() && !Legacy.Check.getStatus(),
+                          result2 = (document.getElementById('errormessage').className !== 'hidden');
+                    clean();
+                    return result1 && result2;
+                }
+            ),
+            {tests: 10});
+        });
+
+        jsc.property(
+            'shows error, if no webcrypto is detected',
+            'bool',
+            jsc.elements(['localhost', '127.0.0.1', '[::1]', '']),
+            jsc.nearray(common.jscA2zString()),
+            jsc.elements(['.onion', '.i2p', '']),
+            function (secureProtocol, localhost, domain, tld) {
+                const isDomain = localhost === '',
+                      isSecureContext = secureProtocol || !isDomain || tld.length > 0,
+                      clean = jsdom(
+                          '<html><body><div id="errormessage" class="hidden"></div>' +
+                          '<div id="oldnotice" class="hidden"></div>' +
+                          '<div id="insecurecontextnotice" class="hidden"></div></body></html>',
+                          {
+                              'url': (secureProtocol ? 'https' : 'http' ) + '://' +
+                                     (isDomain ? domain.join('') + tld : localhost) + '/'
+                          }
+                      );
+                Legacy.Check.init();
+                const result1 = Legacy.Check.getInit() && !Legacy.Check.getStatus(),
+                      result2 = isSecureContext === (document.getElementById('insecurecontextnotice').className === 'hidden'),
+                      result3 = (document.getElementById('oldnotice').className !== 'hidden');
+                clean();
+                return result1 && result2 && result3;
+            }
+        );
+
+        jsc.property(
+            'shows error, if HTTP only site is detected',
+            'bool',
+            jsc.nearray(common.jscA2zString()),
+            function (secureProtocol, domain) {
+                const clean = jsdom(
+                          '<html><body><div id="httpnotice" class="hidden"></div>' +
+                          '</body></html>',
+                          {
+                              'url': (secureProtocol ? 'https' : 'http' ) + '://' + domain.join('') + '/'
+                          }
+                      );
+                window.crypto = new WebCrypto();
+                Legacy.Check.init();
+                const result1 = Legacy.Check.getInit() && Legacy.Check.getStatus(),
+                      result2 = secureProtocol === (document.getElementById('httpnotice').className === 'hidden');
+                clean();
+                return result1 && result2;
+            }
+        );
+    });
+});
+

+ 7 - 1
js/test/CryptTool.js

@@ -18,13 +18,15 @@ describe('CryptTool', function () {
                     // pause to let async functions conclude
                     await new Promise(resolve => setTimeout(resolve, 300));
                     let clean = jsdom();
+                    // ensure zlib is getting loaded
+                    $.PrivateBin.Controller.initZ();
                     window.crypto = new WebCrypto();
                     message = message.trim();
                     let cipherMessage = await $.PrivateBin.CryptTool.cipher(
                             key, password, message, []
                         ),
                         plaintext = await $.PrivateBin.CryptTool.decipher(
-                                key, password, cipherMessage
+                            key, password, cipherMessage
                         );
                     clean();
                     return message === plaintext;
@@ -179,6 +181,8 @@ describe('CryptTool', function () {
             let message = fs.readFileSync('test/compression-sample.txt', 'utf8'),
                 clean = jsdom();
             window.crypto = new WebCrypto();
+            // ensure zlib is getting loaded
+            $.PrivateBin.Controller.initZ();
             let cipherMessage = await $.PrivateBin.CryptTool.cipher(
                     'foo', 'bar', message, []
                 ),
@@ -222,6 +226,8 @@ isWhile : interp (while expr sBody) (MemElem mem) =
 conseq_or_bottom inv (interp (nth_iterate sBody n) (MemElem mem))
 `;
                     let clean = jsdom();
+                    // ensure zlib is getting loaded
+                    $.PrivateBin.Controller.initZ();
                     window.crypto = new WebCrypto();
                     let cipherMessage = await $.PrivateBin.CryptTool.cipher(
                             key, password, message, []

+ 6 - 6
js/test/Helper.js

@@ -183,9 +183,9 @@ describe('Helper', function () {
             'string',
             'string',
             function (prefix, uint, middle, string, postfix) {
-                prefix  =  prefix.replace(/%(s|d)/g, '%%');
-                middle  =  middle.replace(/%(s|d)/g, '%%');
-                postfix = postfix.replace(/%(s|d)/g, '%%');
+                prefix  =  prefix.replace(/%(s|d)/g, '');
+                middle  =  middle.replace(/%(s|d)/g, '');
+                postfix = postfix.replace(/%(s|d)/g, '');
                 var params = [prefix + '%d' + middle + '%s' + postfix, uint, string],
                     result = prefix + uint + middle + string + postfix;
                 return result === $.PrivateBin.Helper.sprintf.apply(this, params);
@@ -199,9 +199,9 @@ describe('Helper', function () {
             'string',
             'string',
             function (prefix, uint, middle, string, postfix) {
-                prefix  =  prefix.replace(/%(s|d)/g, '%%');
-                middle  =  middle.replace(/%(s|d)/g, '%%');
-                postfix = postfix.replace(/%(s|d)/g, '%%');
+                prefix  =  prefix.replace(/%(s|d)/g, '');
+                middle  =  middle.replace(/%(s|d)/g, '');
+                postfix = postfix.replace(/%(s|d)/g, '');
                 var params = [prefix + '%s' + middle + '%d' + postfix, string, uint],
                     result = prefix + string + middle + uint + postfix;
                 return result === $.PrivateBin.Helper.sprintf.apply(this, params);

+ 0 - 88
js/test/InitialCheck.js

@@ -1,88 +0,0 @@
-'use strict';
-var common = require('../common');
-
-describe('InitialCheck', function () {
-    describe('init', function () {
-        this.timeout(30000);
-        before(function () {
-            cleanup();
-        });
-
-        it('returns false and shows error, if a bot UA is detected', function () {
-            jsc.assert(jsc.forall(
-                'string',
-                jsc.elements(['Bot', 'bot']),
-                'string',
-                function (prefix, botBit, suffix) {
-                    const clean = jsdom('', {
-                        'userAgent': prefix + botBit + suffix
-                    });
-                    $('body').html(
-                        '<html><body><div id="errormessage" class="hidden"></div>' +
-                        '</body></html>'
-                    );
-                    $.PrivateBin.Alert.init();
-                    window.crypto = null;
-                    const result1 = !$.PrivateBin.InitialCheck.init(),
-                          result2 = !$('#errormessage').hasClass('hidden');
-                    clean();
-                    return result1 && result2;
-                }
-            ),
-            {tests: 10});
-        });
-
-        jsc.property(
-            'shows error, if no webcrypto is detected',
-            'bool',
-            jsc.elements(['localhost', '127.0.0.1', '[::1]', '']),
-            jsc.nearray(common.jscA2zString()),
-            jsc.elements(['.onion', '.i2p', '']),
-            function (secureProtocol, localhost, domain, tld) {
-                const isDomain = localhost === '',
-                      isSecureContext = secureProtocol || !isDomain || tld.length > 0,
-                      clean = jsdom('', {
-                          'url': (secureProtocol ? 'https' : 'http' ) + '://' +
-                                 (isDomain ? domain.join('') + tld : localhost) + '/'
-                      });
-                $('body').html(
-                    '<html><body><div id="errormessage" class="hidden"></div>'+
-                    '<div id="oldnotice" class="hidden"></div></body></html>'
-                );
-                $.PrivateBin.Alert.init();
-                const result1 = !$.PrivateBin.InitialCheck.init(),
-                      result2 = isSecureContext === $('#errormessage').hasClass('hidden'),
-                      result3 = !$('#oldnotice').hasClass('hidden');
-                clean();
-                return result1 && result2 && result3;
-            }
-        );
-
-        jsc.property(
-            'shows error, if HTTP only site is detected',
-            'bool',
-            jsc.elements(['localhost', '127.0.0.1', '[::1]', '']),
-            jsc.nearray(common.jscA2zString()),
-            jsc.elements(['.onion', '.i2p', '']),
-            function (secureProtocol, localhost, domain, tld) {
-                const isDomain = localhost === '',
-                      isSecureContext = secureProtocol || !isDomain || tld.length > 0,
-                      clean = jsdom('', {
-                          'url': (secureProtocol ? 'https' : 'http' ) + '://' +
-                               (isDomain ? domain.join('') + tld : localhost) + '/'
-                      });
-                $('body').html(
-                    '<html><body><div id="httpnotice" class="hidden"></div>'+
-                    '</body></html>'
-                );
-                $.PrivateBin.Alert.init();
-                window.crypto = null;
-                const result1 = $.PrivateBin.InitialCheck.init(),
-                      result2 = isSecureContext === $('#httpnotice').hasClass('hidden');
-                clean();
-                return result1 && result2;
-            }
-        );
-    });
-});
-

+ 1 - 0
lib/Controller.php

@@ -388,6 +388,7 @@ class Controller
         $page->assign('URLSHORTENER', $this->_conf->getKey('urlshortener'));
         $page->assign('QRCODE', $this->_conf->getKey('qrcode'));
         $page->assign('HTTPWARNING', $this->_conf->getKey('httpwarning'));
+        $page->assign('HTTPSLINK', 'https://' . $this->_request->getHost() . $this->_request->getRequestUri());
         $page->assign('COMPRESSION', $this->_conf->getKey('compression'));
         $page->draw($this->_conf->getKey('template'));
     }

+ 15 - 2
lib/Request.php

@@ -193,6 +193,19 @@ class Request
             $this->_params[$param] : $default;
     }
 
+    /**
+     * Get host as requested by the client
+     *
+     * @access public
+     * @return string
+     */
+    public function getHost()
+    {
+        return array_key_exists('HTTP_HOST', $_SERVER) ?
+            htmlspecialchars($_SERVER['HTTP_HOST']) :
+            'localhost';
+    }
+
     /**
      * Get request URI
      *
@@ -202,8 +215,8 @@ class Request
     public function getRequestUri()
     {
         return array_key_exists('REQUEST_URI', $_SERVER) ?
-            htmlspecialchars(
-                parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
+        htmlspecialchars(
+            parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
             ) : '/';
     }
 

+ 0 - 1
robots.txt

@@ -5,5 +5,4 @@
 # directory.
 
 User-agent: *
-Allow: /index.php
 Disallow: /

+ 6 - 8
tpl/bootstrap.php

@@ -71,10 +71,8 @@ if ($MARKDOWN):
 endif;
 ?>
 		<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.1.js" integrity="sha512-ddI36MdUoXp/o7yhQtr9/qj4G3oFwCRga4jCGaoUYtORg0PPmFKVKG4Ess3fIknYzxwwKMlrIL9o4NwuPTCc1Q==" crossorigin="anonymous"></script>
-		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-86VTqw2HsaCQ0DAunK2MH68P+8RLbbaK7HZP8nwDtwNoF44usxDCptmD8TC+zwQc7HM46AkrvVFb3ZkIb6VhMQ==" crossorigin="anonymous"></script>
-		<!--[if IE]>
-		<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;}</style>
-		<![endif]-->
+		<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-dReLYyMJO3kV7/etVKlGWSbmJoWavMpbgG8Hg3759MNHv0mYuonh4Azif3y0F0oopKKlz3vtj8XZV7VY+e6dSQ==" crossorigin="anonymous"></script>
+		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-4iMFqnyyoJ/FJ33aHov+QeItaZ0JegMUjx7J5pXeknEwjXzx4oLw9F9ePI3WK/h3sUYTOK+hdv2JINNGMwi2Vg==" crossorigin="anonymous"></script>
 		<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
 		<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
 		<link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" />
@@ -446,10 +444,6 @@ endif;
 				<div id="oldnotice" role="alert" class="hidden alert alert-danger">
 					<span class="glyphicon glyphicon-alert" aria-hidden="true"></span>
 					<?php echo I18n::_('%s requires a modern browser to work.', I18n::_($NAME)), PHP_EOL; ?>
-				</div>
-				<div id="ienotice" role="alert" class="hidden alert alert-danger">
-					<span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span>
-					<?php echo I18n::_('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:'), PHP_EOL; ?>
 					<a href="https://www.mozilla.org/firefox/">Firefox</a>,
 					<a href="https://www.opera.com/">Opera</a>,
 					<a href="https://www.google.com/chrome">Chrome</a>…
@@ -462,6 +456,10 @@ if ($HTTPWARNING):
 					<?php echo I18n::_('This website is using an insecure connection! Please only use it for testing.'), PHP_EOL; ?><br />
 					<span class="small"><?php echo I18n::_('For more information <a href="%s">see this FAQ entry</a>.', 'https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-it-show-me-an-error-about-an-insecure-connection'); ?></span>
 				</div>
+				<div id="insecurecontextnotice" role="alert" class="hidden alert alert-danger">
+					<span class="glyphicon glyphicon-alert" aria-hidden="true"></span>
+					<?php echo I18n::_('Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href="%s">switching to HTTPS</a>.', $HTTPSLINK); ?>
+				</div>
 <?php
 endif;
 ?>

+ 7 - 7
tpl/page.php

@@ -49,10 +49,8 @@ if ($MARKDOWN):
 endif;
 ?>
 		<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.1.js" integrity="sha512-ddI36MdUoXp/o7yhQtr9/qj4G3oFwCRga4jCGaoUYtORg0PPmFKVKG4Ess3fIknYzxwwKMlrIL9o4NwuPTCc1Q==" crossorigin="anonymous"></script>
-		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-86VTqw2HsaCQ0DAunK2MH68P+8RLbbaK7HZP8nwDtwNoF44usxDCptmD8TC+zwQc7HM46AkrvVFb3ZkIb6VhMQ==" crossorigin="anonymous"></script>
-		<!--[if IE]>
-		<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;}</style>
-		<![endif]-->
+		<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-dReLYyMJO3kV7/etVKlGWSbmJoWavMpbgG8Hg3759MNHv0mYuonh4Azif3y0F0oopKKlz3vtj8XZV7VY+e6dSQ==" crossorigin="anonymous"></script>
+		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-4iMFqnyyoJ/FJ33aHov+QeItaZ0JegMUjx7J5pXeknEwjXzx4oLw9F9ePI3WK/h3sUYTOK+hdv2JINNGMwi2Vg==" crossorigin="anonymous"></script>
 		<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
 		<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
 		<link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" />
@@ -77,8 +75,7 @@ endif;
 			<h2 class="title"><?php echo I18n::_('Because ignorance is bliss'); ?></h2><br />
 			<h3 class="title"><?php echo $VERSION; ?></h3>
 			<noscript><div id="noscript" class="nonworking"><?php echo I18n::_('JavaScript is required for %s to work.<br />Sorry for the inconvenience.', I18n::_($NAME)); ?></div></noscript>
-			<div id="oldnotice" class="nonworking"><?php echo I18n::_('%s requires a modern browser to work.', I18n::_($NAME)); ?></div>
-			<div id="ienotice" class="nonworking"><?php echo I18n::_('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:'), PHP_EOL; ?>
+			<div id="oldnotice" class="nonworking hidden"><?php echo I18n::_('%s requires a modern browser to work.', I18n::_($NAME)), PHP_EOL; ?>
 				<a href="https://www.mozilla.org/firefox/">Firefox</a>,
 				<a href="https://www.opera.com/">Opera</a>,
 				<a href="https://www.google.com/chrome">Chrome</a>…
@@ -86,10 +83,13 @@ endif;
 <?php
 if ($HTTPWARNING):
 ?>
-			<div id="httpnotice" class="errorMessage">
+			<div id="httpnotice" class="errorMessage hidden">
 				<?php echo I18n::_('This website is using an insecure connection! Please only use it for testing.'); ?>
 				<span class="small"><?php echo I18n::_('For more information <a href="%s">see this FAQ entry</a>.', 'https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-it-show-me-an-error-about-an-insecure-connection'); ?></span>
 			</div>
+			<div id="insecurecontextnotice" class="errorMessage hidden">
+				<?php echo I18n::_('Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href="%s">switching to HTTPS</a>.', $HTTPSLINK); ?>
+			</div>
 <?php
 endif;
 ?>

+ 1 - 0
tst/ViewTest.php

@@ -56,6 +56,7 @@ class ViewTest extends PHPUnit_Framework_TestCase
         $page->assign('URLSHORTENER', '');
         $page->assign('QRCODE', true);
         $page->assign('HTTPWARNING', true);
+        $page->assign('HTTPSLINK', 'https://example.com/');
         $page->assign('COMPRESSION', 'zlib');
 
         $dir = dir(PATH . 'tpl');