legacy.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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. * @type {string[]}
  54. * @readonly
  55. */
  56. var badBotUA = [
  57. // Generic bot identifiers
  58. 'bot/',
  59. 'Bot/',
  60. '-bot',
  61. '-Bot',
  62. 'crawler',
  63. 'Crawler',
  64. 'spider',
  65. 'Spider',
  66. 'scraper',
  67. 'Scraper',
  68. // Search Engines
  69. 'Mediapartners-Google',
  70. 'BingPreview',
  71. 'Yahoo! Slurp',
  72. // SEO & Analytics
  73. 'Screaming Frog',
  74. // Social Media
  75. 'facebookexternalhit',
  76. // AI & LLM
  77. 'ChatGPT-User',
  78. 'anthropic-ai',
  79. // Security Scanners
  80. 'CensysInspect',
  81. 'Shodan',
  82. // Other Common Crawlers
  83. '80legs',
  84. 'ia_archiver',
  85. 'Teoma',
  86. ];
  87. /**
  88. * whitelist of top level domains to consider a secure context,
  89. * regardless of protocol
  90. *
  91. * @private
  92. * @enum {Array}
  93. * @readonly
  94. */
  95. var tld = [
  96. '.onion',
  97. '.i2p'
  98. ];
  99. /**
  100. * whitelist of hostnames to consider a secure context,
  101. * regardless of protocol
  102. *
  103. * @private
  104. * @enum {Array}
  105. * @readonly
  106. */
  107. // whitelists of TLDs & local hostnames
  108. var hostname = [
  109. 'localhost',
  110. '127.0.0.1',
  111. '[::1]'
  112. ];
  113. /**
  114. * check if the context is secure
  115. *
  116. * @private
  117. * @name Check.isSecureContext
  118. * @function
  119. * @return {bool}
  120. */
  121. function isSecureContext()
  122. {
  123. // use .isSecureContext if available
  124. if (window.isSecureContext === true || window.isSecureContext === false) {
  125. return window.isSecureContext;
  126. }
  127. // HTTPS is considered secure
  128. if (window.location.protocol === 'https:') {
  129. return true;
  130. }
  131. // filter out actually secure connections over HTTP
  132. for (var i = 0; i < tld.length; i++) {
  133. if (
  134. window.location.hostname.indexOf(
  135. tld[i],
  136. window.location.hostname.length - tld[i].length
  137. ) !== -1
  138. ) {
  139. return true;
  140. }
  141. }
  142. // whitelist localhost for development
  143. for (var j = 0; j < hostname.length; j++) {
  144. if (window.location.hostname === hostname[j]) {
  145. return true;
  146. }
  147. }
  148. // totally INSECURE http protocol!
  149. return false;
  150. }
  151. /**
  152. * checks whether this is a bot we dislike
  153. *
  154. * @private
  155. * @name Check.isBadBot
  156. * @function
  157. * @return {bool}
  158. */
  159. function isBadBot() {
  160. // check whether a bot user agent part can be found in the current
  161. // user agent
  162. for (var i = 0; i < badBotUA.length; i++) {
  163. if (navigator.userAgent.indexOf(badBotUA[i]) !== -1) {
  164. return true;
  165. }
  166. }
  167. return false;
  168. }
  169. /**
  170. * checks whether this is an unsupported browser, via feature detection
  171. *
  172. * @private
  173. * @name Check.isOldBrowser
  174. * @function
  175. * @return {bool}
  176. */
  177. function isOldBrowser() {
  178. // webcrypto support
  179. if (!(
  180. 'crypto' in window &&
  181. 'getRandomValues' in window.crypto &&
  182. 'subtle' in window.crypto &&
  183. 'encrypt' in window.crypto.subtle &&
  184. 'decrypt' in window.crypto.subtle &&
  185. 'Uint8Array' in window &&
  186. 'Uint32Array' in window
  187. )) {
  188. return true;
  189. }
  190. return false;
  191. }
  192. /**
  193. * shows an error message
  194. *
  195. * @private
  196. * @name Check.showError
  197. * @param {string} message
  198. * @function
  199. */
  200. function showError(message)
  201. {
  202. var element = document.getElementById('errormessage');
  203. if (message.indexOf('<a') === -1) {
  204. element.appendChild(
  205. document.createTextNode(message)
  206. );
  207. } else {
  208. element.innerHTML = message;
  209. }
  210. removeHiddenFromId('errormessage');
  211. }
  212. /**
  213. * removes "hidden" CSS class from element with given ID
  214. *
  215. * @private
  216. * @name Check.removeHiddenFromId
  217. * @param {string} id
  218. * @function
  219. */
  220. function removeHiddenFromId(id)
  221. {
  222. var element = document.getElementById(id);
  223. if (element) {
  224. element.className = element.className.replace(/\bhidden\b/g, '');
  225. }
  226. }
  227. /**
  228. * returns if the check has concluded
  229. *
  230. * @name Check.getInit
  231. * @function
  232. * @return {bool}
  233. */
  234. me.getInit = function()
  235. {
  236. return init;
  237. };
  238. /**
  239. * returns the current status of the check
  240. *
  241. * @name Check.getStatus
  242. * @function
  243. * @return {bool}
  244. */
  245. me.getStatus = function()
  246. {
  247. return status;
  248. };
  249. /**
  250. * init on application start, returns an all-clear signal
  251. *
  252. * @name Check.init
  253. * @function
  254. */
  255. me.init = function()
  256. {
  257. // prevent early init
  258. if (typeof document === 'undefined' || typeof navigator === 'undefined' || typeof window === 'undefined') {
  259. return;
  260. }
  261. // prevent bots from viewing a document and potentially deleting data
  262. // when burn-after-reading is set
  263. if (isBadBot()) {
  264. showError('I love you too, bot…');
  265. init = true;
  266. return;
  267. }
  268. if (isOldBrowser()) {
  269. // some browsers (Chrome based ones) would have webcrypto support if using HTTPS
  270. if (!isSecureContext()) {
  271. removeHiddenFromId('insecurecontextnotice');
  272. }
  273. removeHiddenFromId('oldnotice');
  274. init = true;
  275. return;
  276. }
  277. if (!isSecureContext()) {
  278. removeHiddenFromId('httpnotice');
  279. }
  280. init = true;
  281. // only if everything passed, we set the status to true
  282. status = true;
  283. };
  284. return me;
  285. })();
  286. // main application start, called when DOM is fully loaded
  287. if (document.readyState === 'complete' || (!document.attachEvent && document.readyState === 'interactive')) {
  288. Check.init();
  289. } else {
  290. if (document.addEventListener) {
  291. // first choice is DOMContentLoaded event
  292. document.addEventListener('DOMContentLoaded', Check.init, false);
  293. // backup is window load event
  294. window.addEventListener('load', Check.init, false);
  295. } else {
  296. // must be IE
  297. document.attachEvent('onreadystatechange', Check.init);
  298. window.attachEvent('onload', Check.init);
  299. }
  300. }
  301. this.Legacy = {
  302. Check: Check
  303. };
  304. }).call(this);