legacy.js 9.2 KB

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