1"use strict"; 2/* DokuWiki BotMon Captcha JavaScript */ 3/* 23.10.2025 - 0.1.2 - pre-release */ 4/* Author: Sascha Leib <ad@hominem.info> */ 5 6const $BMCaptcha = { 7 8 init: function() { 9 /* mark the page to contain the captcha styles */ 10 document.getElementsByTagName('body')[0].classList.add('botmon_captcha'); 11 12 $BMCaptcha.install() 13 }, 14 15 install: function() { 16 // find the parent element: 17 let bm_parent = document.getElementsByTagName('body')[0]; 18 19 // create the dialog: 20 const dlg = document.createElement('dialog'); 21 dlg.setAttribute('closedby', 'none'); 22 dlg.setAttribute('open', 'open'); 23 dlg.classList.add('checking'); 24 dlg.id = 'botmon_captcha_box'; 25 dlg.innerHTML = '<h2>Captcha box</h2><p>Making sure you are a human:</p>'; 26 27 // Checkbox: 28 const lbl = document.createElement('label'); 29 lbl.innerHTML = '<span class="confirm">Click to confirm.</span><span class="busy"></span><span class="checking">Checking …</span><span class="loading">Loading …</span><span class="erricon">�</span><span class="error">An error occured.</span>'; 30 const cb = document.createElement('input'); 31 cb.setAttribute('type', 'checkbox'); 32 cb.setAttribute('disabled', 'disabled'); 33 cb.addEventListener('click', $BMCaptcha._cbCallback); 34 lbl.prepend(cb); 35 36 dlg.appendChild(lbl); 37 38 bm_parent.appendChild(dlg); 39 40 // call the delayed callback in a couple of seconds: 41 setTimeout($BMCaptcha._delayedCallback, 1500); 42 }, 43 44 /* creates a digest hash for the cookie function */ 45 digest: { 46 47 /* simple SHA hash function - adapted from https://geraintluff.github.io/sha256/ */ 48 hash: function(ascii) { 49 50 // shortcut: 51 const sha256 = $BMCaptcha.digest.hash; 52 53 // helper function 54 const rightRotate = function(v, a) { 55 return (v>>>a) | (v<<(32 - a)); 56 }; 57 58 var mathPow = Math.pow; 59 var maxWord = mathPow(2, 32); 60 var lengthProperty = 'length' 61 var i, j; 62 var result = '' 63 64 var words = []; 65 var asciiBitLength = ascii[lengthProperty]*8; 66 67 //* caching results is optional - remove/add slash from front of this line to toggle 68 // Initial hash value: first 32 bits of the fractional parts of the square roots of the first 8 primes 69 // (we actually calculate the first 64, but extra values are just ignored) 70 var hash = sha256.h = sha256.h || []; 71 // Round constants: first 32 bits of the fractional parts of the cube roots of the first 64 primes 72 var k = sha256.k = sha256.k || []; 73 var primeCounter = k[lengthProperty]; 74 /*/ 75 var hash = [], k = []; 76 var primeCounter = 0; 77 //*/ 78 79 var isComposite = {}; 80 for (var candidate = 2; primeCounter < 64; candidate++) { 81 if (!isComposite[candidate]) { 82 for (i = 0; i < 313; i += candidate) { 83 isComposite[i] = candidate; 84 } 85 hash[primeCounter] = (mathPow(candidate, .5)*maxWord)|0; 86 k[primeCounter++] = (mathPow(candidate, 1/3)*maxWord)|0; 87 } 88 } 89 90 ascii += '\x80' // Append Ƈ' bit (plus zero padding) 91 while (ascii[lengthProperty]%64 - 56) ascii += '\x00' // More zero padding 92 for (i = 0; i < ascii[lengthProperty]; i++) { 93 j = ascii.charCodeAt(i); 94 if (j>>8) return; // ASCII check: only accept characters in range 0-255 95 words[i>>2] |= j << ((3 - i)%4)*8; 96 } 97 words[words[lengthProperty]] = ((asciiBitLength/maxWord)|0); 98 words[words[lengthProperty]] = (asciiBitLength) 99 100 // process each chunk 101 for (j = 0; j < words[lengthProperty];) { 102 var w = words.slice(j, j += 16); // The message is expanded into 64 words as part of the iteration 103 var oldHash = hash; 104 // This is now the undefinedworking hash", often labelled as variables a...g 105 // (we have to truncate as well, otherwise extra entries at the end accumulate 106 hash = hash.slice(0, 8); 107 108 for (i = 0; i < 64; i++) { 109 var i2 = i + j; 110 // Expand the message into 64 words 111 // Used below if 112 var w15 = w[i - 15], w2 = w[i - 2]; 113 114 // Iterate 115 var a = hash[0], e = hash[4]; 116 var temp1 = hash[7] 117 + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) // S1 118 + ((e&hash[5])^((~e)&hash[6])) // ch 119 + k[i] 120 // Expand the message schedule if needed 121 + (w[i] = (i < 16) ? w[i] : ( 122 w[i - 16] 123 + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15>>>3)) // s0 124 + w[i - 7] 125 + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2>>>10)) // s1 126 )|0 127 ); 128 // This is only used once, so *could* be moved below, but it only saves 4 bytes and makes things unreadble 129 var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) // S0 130 + ((a&hash[1])^(a&hash[2])^(hash[1]&hash[2])); // maj 131 132 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() 133 hash[4] = (hash[4] + temp1)|0; 134 } 135 136 for (i = 0; i < 8; i++) { 137 hash[i] = (hash[i] + oldHash[i])|0; 138 } 139 } 140 141 for (i = 0; i < 8; i++) { 142 for (j = 3; j + 1; j--) { 143 var b = (hash[i]>>(j*8))&255; 144 result += ((b < 16) ? 0 : '') + b.toString(16); 145 } 146 } 147 return result; 148 } 149 }, 150 151 _cbCallback: function(e) { 152 if (e.target.checked) { 153 //document.getElementById('botmon_captcha_box').close(); 154 155 try { 156 var $status = 'loading'; 157 158 // generate the hash: 159 const dat = [ // the data to encode 160 document._botmon.seed || '', 161 location.hostname, 162 document._botmon.ip || '0.0.0.0', 163 (new Date()).toISOString().substring(0, 10) 164 ]; 165 const hash = $BMCaptcha.digest.hash(dat.join('|')); 166 167 // set the cookie: 168 document.cookie = "DWConfirm=" + hash + ';path=/;'; 169 170 } catch (err) { 171 console.error(err); 172 $status = 'error'; 173 } 174 175 // change the interface: 176 const dlg = document.getElementById('botmon_captcha_box'); 177 if (dlg) { 178 dlg.classList.remove('ready'); 179 dlg.classList.add( $status ); 180 } 181 182 // reload the page: 183 if ($status !== 'error')window.location.reload(true); 184 } 185 }, 186 187 _delayedCallback: function() { 188 const dlg = document.getElementById('botmon_captcha_box'); 189 if (dlg) { 190 dlg.classList.remove('checking'); 191 dlg.classList.add('ready'); 192 193 const input = dlg.getElementsByTagName('input')[0]; 194 if (input) { 195 input.removeAttribute('disabled'); 196 input.focus(); 197 } 198 } 199 }, 200 201} 202// initialise the captcha module: 203$BMCaptcha.init();