legacy.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /**
  2. * PrivateBin
  3. *
  4. * a zero-knowledge paste bin
  5. *
  6. * @see {@link https://github.com/PrivateBin/PrivateBin}
  7. * @copyright 2012 Sébastien SAUVAGE ({@link http://sebsauvage.net})
  8. * @license {@link https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License}
  9. * @name Legacy
  10. * @namespace
  11. */
  12. /**
  13. * IMPORTANT NOTICE FOR DEVELOPERS:
  14. * The logic in this file is intended to run in legacy browsers. Avoid any use of:
  15. * - jQuery (doesn't work in older browsers)
  16. * - ES5 or newer in general
  17. * - const/let, use the traditional var declarations instead
  18. * - async/await or Promises, use traditional callbacks
  19. * - shorthand function notation "() => output", use the full "function() {return output;}" style
  20. * - IE doesn't support:
  21. * - URL(), use the traditional window.location object
  22. * - endsWith(), use indexof()
  23. * - yes, this logic needs to support IE 6, to at least display the error message
  24. */
  25. 'use strict';
  26. (function() {
  27. /**
  28. * compatibility check
  29. *
  30. * @name Check
  31. * @class
  32. */
  33. var Check = (function () {
  34. var me = {};
  35. /**
  36. * Status of the initial check, true means it passed
  37. *
  38. * @private
  39. * @prop {bool}
  40. */
  41. var status = false;
  42. /**
  43. * Initialization check did run
  44. *
  45. * @private
  46. * @prop {bool}
  47. */
  48. var init = false;
  49. /**
  50. * blacklist of UserAgents (parts) known to belong to a bot
  51. *
  52. * @private
  53. * @enum {Array}
  54. * @readonly
  55. */
  56. var badBotUA = [
  57. 'Bot',
  58. 'bot'
  59. ];
  60. /**
  61. * whitelist of top level domains to consider a secure context,
  62. * regardless of protocol
  63. *
  64. * @private
  65. * @enum {Array}
  66. * @readonly
  67. */
  68. var tld = [
  69. '.onion',
  70. '.i2p'
  71. ];
  72. /**
  73. * whitelist of hostnames to consider a secure context,
  74. * regardless of protocol
  75. *
  76. * @private
  77. * @enum {Array}
  78. * @readonly
  79. */
  80. // whitelists of TLDs & local hostnames
  81. var hostname = [
  82. 'localhost',
  83. '127.0.0.1',
  84. '[::1]'
  85. ];
  86. /**
  87. * check if the context is secure
  88. *
  89. * @private
  90. * @name Check.isSecureContext
  91. * @function
  92. * @return {bool}
  93. */
  94. function isSecureContext()
  95. {
  96. // use .isSecureContext if available
  97. if (window.isSecureContext === true || window.isSecureContext === false) {
  98. return window.isSecureContext;
  99. }
  100. // HTTP is obviously insecure
  101. if (window.location.protocol !== 'http:') {
  102. return true;
  103. }
  104. // filter out actually secure connections over HTTP
  105. for (var i = 0; i < tld.length; i++) {
  106. if (
  107. window.location.hostname.indexOf(
  108. tld[i],
  109. window.location.hostname.length - tld[i].length
  110. ) !== -1
  111. ) {
  112. return true;
  113. }
  114. }
  115. // whitelist localhost for development
  116. for (var j = 0; j < hostname.length; j++) {
  117. if (window.location.hostname === hostname[j]) {
  118. return true;
  119. }
  120. }
  121. // totally INSECURE http protocol!
  122. return false;
  123. }
  124. /**
  125. * checks whether this is a bot we dislike
  126. *
  127. * @private
  128. * @name Check.isBadBot
  129. * @function
  130. * @return {bool}
  131. */
  132. function isBadBot() {
  133. // check whether a bot user agent part can be found in the current
  134. // user agent
  135. for (var i = 0; i < badBotUA.length; i++) {
  136. if (navigator.userAgent.indexOf(badBotUA[i]) !== -1) {
  137. return true;
  138. }
  139. }
  140. return false;
  141. }
  142. /**
  143. * checks whether this is an unsupported browser, via feature detection
  144. *
  145. * @private
  146. * @name Check.isOldBrowser
  147. * @function
  148. * @return {bool}
  149. */
  150. function isOldBrowser() {
  151. // webcrypto support
  152. if (!(
  153. 'crypto' in window &&
  154. 'getRandomValues' in window.crypto &&
  155. 'subtle' in window.crypto &&
  156. 'encrypt' in window.crypto.subtle &&
  157. 'decrypt' in window.crypto.subtle &&
  158. 'Uint8Array' in window &&
  159. 'Uint32Array' in window
  160. )) {
  161. return true;
  162. }
  163. // async & ES6 support
  164. try {
  165. async () => {};
  166. } catch (e) {
  167. if (e instanceof SyntaxError) {
  168. return true;
  169. } else {
  170. throw e; // throws CSP error
  171. }
  172. }
  173. return false;
  174. }
  175. /**
  176. * shows an error message
  177. *
  178. * @private
  179. * @name Check.showError
  180. * @param {string} message
  181. * @function
  182. */
  183. function showError(message)
  184. {
  185. var element = document.getElementById('errormessage');
  186. if (message.indexOf('<a') === -1) {
  187. element.appendChild(
  188. document.createTextNode(message)
  189. );
  190. } else {
  191. element.innerHTML = message;
  192. }
  193. removeHiddenFromId('errormessage');
  194. }
  195. /**
  196. * removes "hidden" CSS class from element with given ID
  197. *
  198. * @private
  199. * @name Check.removeHiddenFromId
  200. * @param {string} id
  201. * @function
  202. */
  203. function removeHiddenFromId(id)
  204. {
  205. var element = document.getElementById(id);
  206. if (element) {
  207. element.className = element.className.replace(/\bhidden\b/g, '');
  208. }
  209. }
  210. /**
  211. * returns if the check has concluded
  212. *
  213. * @name Check.getInit
  214. * @function
  215. * @return {bool}
  216. */
  217. me.getInit = function()
  218. {
  219. return init;
  220. };
  221. /**
  222. * returns the current status of the check
  223. *
  224. * @name Check.getStatus
  225. * @function
  226. * @return {bool}
  227. */
  228. me.getStatus = function()
  229. {
  230. return status;
  231. };
  232. /**
  233. * init on application start, returns an all-clear signal
  234. *
  235. * @name Check.init
  236. * @function
  237. */
  238. me.init = function()
  239. {
  240. // prevent bots from viewing a paste and potentially deleting data
  241. // when burn-after-reading is set
  242. if (isBadBot()) {
  243. showError('I love you too, bot…');
  244. init = true;
  245. return;
  246. }
  247. if (isOldBrowser()) {
  248. // some browsers (Chrome based ones) would have webcrypto support if using HTTPS
  249. if (!isSecureContext()) {
  250. removeHiddenFromId('insecurecontextnotice');
  251. }
  252. removeHiddenFromId('oldnotice');
  253. init = true;
  254. return;
  255. }
  256. if (!isSecureContext()) {
  257. removeHiddenFromId('httpnotice');
  258. }
  259. init = true;
  260. // only if everything passed, we set the status to true
  261. status = true;
  262. };
  263. return me;
  264. })();
  265. // main application start, called when DOM is fully loaded
  266. if (document.readyState === 'complete' || (!document.attachEvent && document.readyState === 'interactive')) {
  267. Check.init();
  268. } else {
  269. if (document.addEventListener) {
  270. // first choice is DOMContentLoaded event
  271. document.addEventListener('DOMContentLoaded', Check.init, false);
  272. // backup is window load event
  273. window.addEventListener('load', Check.init, false);
  274. } else {
  275. // must be IE
  276. document.attachEvent('onreadystatechange', Check.init);
  277. window.attachEvent('onload', Check.init);
  278. }
  279. }
  280. this.Legacy = {
  281. Check: Check
  282. };
  283. }).call(this);