112993035SSascha Leib"use strict"; 212993035SSascha Leib/* DokuWiki BotMon Captcha JavaScript */ 3cdc02cd4SSascha Leib/* 23.10.2025 - 0.1.2 - pre-release */ 412993035SSascha Leib/* Author: Sascha Leib <ad@hominem.info> */ 512993035SSascha Leib 612993035SSascha Leibconst $BMCaptcha = { 712993035SSascha Leib 812993035SSascha Leib init: function() { 9*cb916979SSascha Leib 10*cb916979SSascha Leib // hide the NoJS warning: 11*cb916979SSascha Leib document.getElementById('BM__NoJSWarning').close(); 12*cb916979SSascha Leib 13*cb916979SSascha Leib // install the captcha: 1412993035SSascha Leib document.getElementsByTagName('body')[0].classList.add('botmon_captcha'); 15ad279a21SSascha Leib $BMCaptcha._cbDly = 1.5; 1612993035SSascha Leib $BMCaptcha.install() 1712993035SSascha Leib }, 1812993035SSascha Leib 1912993035SSascha Leib install: function() { 20620d9253SSascha Leib 21620d9253SSascha Leib // localisation helper function: 22620d9253SSascha Leib let _loc = function(id, alt) { 23620d9253SSascha Leib if ($BMLocales && $BMLocales[id]) return $BMLocales[id]; 24620d9253SSascha Leib return alt; 25620d9253SSascha Leib } 26620d9253SSascha Leib 2712993035SSascha Leib // find the parent element: 2812993035SSascha Leib let bm_parent = document.getElementsByTagName('body')[0]; 2912993035SSascha Leib 3012993035SSascha Leib // create the dialog: 3112993035SSascha Leib const dlg = document.createElement('dialog'); 3212993035SSascha Leib dlg.setAttribute('closedby', 'none'); 3312993035SSascha Leib dlg.setAttribute('open', 'open'); 34cef38106SSascha Leib dlg.setAttribute('role', 'alertdialog'); 35cef38106SSascha Leib dlg.setAttribute('aria-labelledby', 'botmon_captcha_title'); 36cdc02cd4SSascha Leib dlg.classList.add('checking'); 3712993035SSascha Leib dlg.id = 'botmon_captcha_box'; 38cef38106SSascha Leib dlg.innerHTML = '<h2 id="botmon_captcha_title">' + _loc('dlgTitle', 'Title') + '</h2><p>' + _loc('dlgSubtitle', 'Subtitle') + '</p>'; 3912993035SSascha Leib 4012993035SSascha Leib // Checkbox: 4112993035SSascha Leib const lbl = document.createElement('label'); 42cef38106SSascha Leib lbl.setAttribute('aria-live', 'assertive'); 43620d9253SSascha Leib lbl.innerHTML = '<span class="confirm">' + _loc('dlgConfirm', "Confirm.") + '</span>' + 44620d9253SSascha Leib '<span class="busy"></span><span class="checking">' + _loc('dlgChecking', "Checking") + '</span>' + 45620d9253SSascha Leib '<span class="loading">' + _loc('dlgLoading', "Loading") + '</span>' + 46620d9253SSascha Leib '<span class="erricon">�</span><span class="error">' + _loc('dlgError', "Error") + '</span>'; 4712993035SSascha Leib const cb = document.createElement('input'); 4812993035SSascha Leib cb.setAttribute('type', 'checkbox'); 49cdc02cd4SSascha Leib cb.setAttribute('disabled', 'disabled'); 5012993035SSascha Leib cb.addEventListener('click', $BMCaptcha._cbCallback); 51cdc02cd4SSascha Leib lbl.prepend(cb); 5212993035SSascha Leib 5312993035SSascha Leib dlg.appendChild(lbl); 5412993035SSascha Leib 5512993035SSascha Leib bm_parent.appendChild(dlg); 56cdc02cd4SSascha Leib 57cdc02cd4SSascha Leib // call the delayed callback in a couple of seconds: 58ad279a21SSascha Leib $BMCaptcha._st = performance.now(); 59ad279a21SSascha Leib setTimeout($BMCaptcha._delayedCallback, $BMCaptcha._cbDly * 1000); 6012993035SSascha Leib }, 6112993035SSascha Leib 62871c97bfSSascha Leib /* creates a digest hash */ 6312993035SSascha Leib digest: { 6412993035SSascha Leib 6512993035SSascha Leib /* simple SHA hash function - adapted from https://geraintluff.github.io/sha256/ */ 6612993035SSascha Leib hash: function(ascii) { 6712993035SSascha Leib 6812993035SSascha Leib // shortcut: 6912993035SSascha Leib const sha256 = $BMCaptcha.digest.hash; 7012993035SSascha Leib 7112993035SSascha Leib // helper function 7212993035SSascha Leib const rightRotate = function(v, a) { 7312993035SSascha Leib return (v>>>a) | (v<<(32 - a)); 7412993035SSascha Leib }; 7512993035SSascha Leib 7612993035SSascha Leib var mathPow = Math.pow; 7712993035SSascha Leib var maxWord = mathPow(2, 32); 7812993035SSascha Leib var lengthProperty = 'length' 7912993035SSascha Leib var i, j; 8012993035SSascha Leib var result = '' 8112993035SSascha Leib 8212993035SSascha Leib var words = []; 8312993035SSascha Leib var asciiBitLength = ascii[lengthProperty]*8; 8412993035SSascha Leib 8512993035SSascha Leib //* caching results is optional - remove/add slash from front of this line to toggle 8612993035SSascha Leib // Initial hash value: first 32 bits of the fractional parts of the square roots of the first 8 primes 8712993035SSascha Leib // (we actually calculate the first 64, but extra values are just ignored) 8812993035SSascha Leib var hash = sha256.h = sha256.h || []; 8912993035SSascha Leib // Round constants: first 32 bits of the fractional parts of the cube roots of the first 64 primes 9012993035SSascha Leib var k = sha256.k = sha256.k || []; 9112993035SSascha Leib var primeCounter = k[lengthProperty]; 9212993035SSascha Leib /*/ 9312993035SSascha Leib var hash = [], k = []; 9412993035SSascha Leib var primeCounter = 0; 9512993035SSascha Leib //*/ 9612993035SSascha Leib 9712993035SSascha Leib var isComposite = {}; 9812993035SSascha Leib for (var candidate = 2; primeCounter < 64; candidate++) { 9912993035SSascha Leib if (!isComposite[candidate]) { 10012993035SSascha Leib for (i = 0; i < 313; i += candidate) { 10112993035SSascha Leib isComposite[i] = candidate; 10212993035SSascha Leib } 10312993035SSascha Leib hash[primeCounter] = (mathPow(candidate, .5)*maxWord)|0; 10412993035SSascha Leib k[primeCounter++] = (mathPow(candidate, 1/3)*maxWord)|0; 10512993035SSascha Leib } 10612993035SSascha Leib } 10712993035SSascha Leib 10812993035SSascha Leib ascii += '\x80' // Append Ƈ' bit (plus zero padding) 10912993035SSascha Leib while (ascii[lengthProperty]%64 - 56) ascii += '\x00' // More zero padding 11012993035SSascha Leib for (i = 0; i < ascii[lengthProperty]; i++) { 11112993035SSascha Leib j = ascii.charCodeAt(i); 11212993035SSascha Leib if (j>>8) return; // ASCII check: only accept characters in range 0-255 11312993035SSascha Leib words[i>>2] |= j << ((3 - i)%4)*8; 11412993035SSascha Leib } 11512993035SSascha Leib words[words[lengthProperty]] = ((asciiBitLength/maxWord)|0); 11612993035SSascha Leib words[words[lengthProperty]] = (asciiBitLength) 11712993035SSascha Leib 11812993035SSascha Leib // process each chunk 11912993035SSascha Leib for (j = 0; j < words[lengthProperty];) { 12012993035SSascha Leib var w = words.slice(j, j += 16); // The message is expanded into 64 words as part of the iteration 12112993035SSascha Leib var oldHash = hash; 12212993035SSascha Leib // This is now the undefinedworking hash", often labelled as variables a...g 12312993035SSascha Leib // (we have to truncate as well, otherwise extra entries at the end accumulate 12412993035SSascha Leib hash = hash.slice(0, 8); 12512993035SSascha Leib 12612993035SSascha Leib for (i = 0; i < 64; i++) { 12712993035SSascha Leib var i2 = i + j; 12812993035SSascha Leib // Expand the message into 64 words 12912993035SSascha Leib // Used below if 13012993035SSascha Leib var w15 = w[i - 15], w2 = w[i - 2]; 13112993035SSascha Leib 13212993035SSascha Leib // Iterate 13312993035SSascha Leib var a = hash[0], e = hash[4]; 13412993035SSascha Leib var temp1 = hash[7] 13512993035SSascha Leib + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) // S1 13612993035SSascha Leib + ((e&hash[5])^((~e)&hash[6])) // ch 13712993035SSascha Leib + k[i] 13812993035SSascha Leib // Expand the message schedule if needed 13912993035SSascha Leib + (w[i] = (i < 16) ? w[i] : ( 14012993035SSascha Leib w[i - 16] 14112993035SSascha Leib + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15>>>3)) // s0 14212993035SSascha Leib + w[i - 7] 14312993035SSascha Leib + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2>>>10)) // s1 14412993035SSascha Leib )|0 14512993035SSascha Leib ); 14612993035SSascha Leib // This is only used once, so *could* be moved below, but it only saves 4 bytes and makes things unreadble 14712993035SSascha Leib var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) // S0 14812993035SSascha Leib + ((a&hash[1])^(a&hash[2])^(hash[1]&hash[2])); // maj 14912993035SSascha Leib 15012993035SSascha Leib hash = [(temp1 + temp2)|0].concat(hash); // We don't bother trimming off the extra ones, they're harmless as long as we're truncating when we do the slice() 15112993035SSascha Leib hash[4] = (hash[4] + temp1)|0; 15212993035SSascha Leib } 15312993035SSascha Leib 15412993035SSascha Leib for (i = 0; i < 8; i++) { 15512993035SSascha Leib hash[i] = (hash[i] + oldHash[i])|0; 15612993035SSascha Leib } 15712993035SSascha Leib } 15812993035SSascha Leib 15912993035SSascha Leib for (i = 0; i < 8; i++) { 16012993035SSascha Leib for (j = 3; j + 1; j--) { 16112993035SSascha Leib var b = (hash[i]>>(j*8))&255; 16212993035SSascha Leib result += ((b < 16) ? 0 : '') + b.toString(16); 16312993035SSascha Leib } 16412993035SSascha Leib } 16512993035SSascha Leib return result; 16612993035SSascha Leib } 16712993035SSascha Leib }, 16812993035SSascha Leib 16912993035SSascha Leib _cbCallback: function(e) { 17012993035SSascha Leib if (e.target.checked) { 17112993035SSascha Leib //document.getElementById('botmon_captcha_box').close(); 17212993035SSascha Leib 173d49ab213SSascha Leib try { 174d49ab213SSascha Leib var $status = 'loading'; 175d49ab213SSascha Leib 176871c97bfSSascha Leib // generate the hash: 177871c97bfSSascha Leib const dat = [ // the data to encode 178cdc02cd4SSascha Leib document._botmon.seed || '', 179cdc02cd4SSascha Leib location.hostname, 180cdc02cd4SSascha Leib document._botmon.ip || '0.0.0.0', 181d49ab213SSascha Leib (new Date()).toISOString().substring(0, 10) 182871c97bfSSascha Leib ]; 183871c97bfSSascha Leib if (performance.now() - $BMCaptcha._st <= 1500) dat.push(performance.now() - $BMCaptcha._st); 18412993035SSascha Leib 185cdc02cd4SSascha Leib // set the cookie: 1866861f2afSSascha Leib document.cookie = "DWConfirm=" + encodeURIComponent($BMCaptcha.digest.hash(dat.join(';'))) + '; path=/; session;'; 1876861f2afSSascha Leib // + (document.location.protocol === 'https:' ? ' secure;' : ''); 18812993035SSascha Leib 189d49ab213SSascha Leib } catch (err) { 190d49ab213SSascha Leib console.error(err); 191d49ab213SSascha Leib $status = 'error'; 192d49ab213SSascha Leib } 193d49ab213SSascha Leib 194cdc02cd4SSascha Leib // change the interface: 195cdc02cd4SSascha Leib const dlg = document.getElementById('botmon_captcha_box'); 196cdc02cd4SSascha Leib if (dlg) { 197d49ab213SSascha Leib dlg.classList.add( $status ); 1980cfc0c5dSSascha Leib dlg.classList.remove('ready'); 199cdc02cd4SSascha Leib } 200cdc02cd4SSascha Leib 201cdc02cd4SSascha Leib // reload the page: 202d49ab213SSascha Leib if ($status !== 'error')window.location.reload(true); 20312993035SSascha Leib } 204cdc02cd4SSascha Leib }, 205cdc02cd4SSascha Leib 206cdc02cd4SSascha Leib _delayedCallback: function() { 207cdc02cd4SSascha Leib const dlg = document.getElementById('botmon_captcha_box'); 208cdc02cd4SSascha Leib if (dlg) { 209cdc02cd4SSascha Leib dlg.classList.add('ready'); 2100cfc0c5dSSascha Leib dlg.classList.remove('checking'); 211cdc02cd4SSascha Leib 212cdc02cd4SSascha Leib const input = dlg.getElementsByTagName('input')[0]; 213cdc02cd4SSascha Leib if (input) { 214cdc02cd4SSascha Leib input.removeAttribute('disabled'); 215cdc02cd4SSascha Leib input.focus(); 216ad279a21SSascha Leib setTimeout($BMCaptcha._autoCheck, 200, input); 21712993035SSascha Leib } 218cdc02cd4SSascha Leib } 219cdc02cd4SSascha Leib }, 220ad279a21SSascha Leib _cbDly: null, 221ad279a21SSascha Leib _st: null, 22212993035SSascha Leib 223ad279a21SSascha Leib _autoCheck: function(e) { 224ad279a21SSascha Leib 225fb281ca1SSascha Leib const bypass = ($BMConfig['captchaBypass'] || '').split(','); 226fb281ca1SSascha Leib var action = false; 227ad279a21SSascha Leib 228fb281ca1SSascha Leib if (bypass.indexOf('langmatch') >= 0) { // Languages matching 229ad279a21SSascha Leib const cntLangs = navigator.languages.map(lang => lang.split('-')[0]); 230fb281ca1SSascha Leib if (cntLangs.indexOf(document.documentElement.lang || 'en') >= 0) action = true; 231ad279a21SSascha Leib } 232ad279a21SSascha Leib 233fb281ca1SSascha Leib if (action) e.click(); // action! 234ad279a21SSascha Leib } 23512993035SSascha Leib} 23612993035SSascha Leib// initialise the captcha module: 23712993035SSascha Leib$BMCaptcha.init();