legacy.js 9.3 KB

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