fonts
2 years ago
img
1 year ago
rtl
1 year ago
akismet-admin.css
1 year ago
akismet-admin.js
1 year ago
akismet-frontend.js
2 years ago
akismet.css
1 year ago
akismet.js
1 year ago
akismet-frontend.js
376 lines
| 1 | /** |
| 2 | * Observe how the user enters content into the comment form in order to determine whether it's a bot or not. |
| 3 | * |
| 4 | * Note that no actual input is being saved here, only counts and timings between events. |
| 5 | */ |
| 6 | |
| 7 | ( function() { |
| 8 | // Passive event listeners are guaranteed to never call e.preventDefault(), |
| 9 | // but they're not supported in all browsers. Use this feature detection |
| 10 | // to determine whether they're available for use. |
| 11 | var supportsPassive = false; |
| 12 | |
| 13 | try { |
| 14 | var opts = Object.defineProperty( {}, 'passive', { |
| 15 | get : function() { |
| 16 | supportsPassive = true; |
| 17 | } |
| 18 | } ); |
| 19 | |
| 20 | window.addEventListener( 'testPassive', null, opts ); |
| 21 | window.removeEventListener( 'testPassive', null, opts ); |
| 22 | } catch ( e ) {} |
| 23 | |
| 24 | function init() { |
| 25 | var input_begin = ''; |
| 26 | |
| 27 | var keydowns = {}; |
| 28 | var lastKeyup = null; |
| 29 | var lastKeydown = null; |
| 30 | var keypresses = []; |
| 31 | |
| 32 | var modifierKeys = []; |
| 33 | var correctionKeys = []; |
| 34 | |
| 35 | var lastMouseup = null; |
| 36 | var lastMousedown = null; |
| 37 | var mouseclicks = []; |
| 38 | |
| 39 | var mousemoveTimer = null; |
| 40 | var lastMousemoveX = null; |
| 41 | var lastMousemoveY = null; |
| 42 | var mousemoveStart = null; |
| 43 | var mousemoves = []; |
| 44 | |
| 45 | var touchmoveCountTimer = null; |
| 46 | var touchmoveCount = 0; |
| 47 | |
| 48 | var lastTouchEnd = null; |
| 49 | var lastTouchStart = null; |
| 50 | var touchEvents = []; |
| 51 | |
| 52 | var scrollCountTimer = null; |
| 53 | var scrollCount = 0; |
| 54 | |
| 55 | var correctionKeyCodes = [ 'Backspace', 'Delete', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown' ]; |
| 56 | var modifierKeyCodes = [ 'Shift', 'CapsLock' ]; |
| 57 | |
| 58 | var forms = document.querySelectorAll( 'form[method=post]' ); |
| 59 | |
| 60 | for ( var i = 0; i < forms.length; i++ ) { |
| 61 | var form = forms[i]; |
| 62 | |
| 63 | var formAction = form.getAttribute( 'action' ); |
| 64 | |
| 65 | // Ignore forms that POST directly to other domains; these could be things like payment forms. |
| 66 | if ( formAction ) { |
| 67 | // Check that the form is posting to an external URL, not a path. |
| 68 | if ( formAction.indexOf( 'http://' ) == 0 || formAction.indexOf( 'https://' ) == 0 ) { |
| 69 | if ( formAction.indexOf( 'http://' + window.location.hostname + '/' ) != 0 && formAction.indexOf( 'https://' + window.location.hostname + '/' ) != 0 ) { |
| 70 | continue; |
| 71 | } |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | form.addEventListener( 'submit', function () { |
| 76 | var ak_bkp = prepare_timestamp_array_for_request( keypresses ); |
| 77 | var ak_bmc = prepare_timestamp_array_for_request( mouseclicks ); |
| 78 | var ak_bte = prepare_timestamp_array_for_request( touchEvents ); |
| 79 | var ak_bmm = prepare_timestamp_array_for_request( mousemoves ); |
| 80 | |
| 81 | var input_fields = { |
| 82 | // When did the user begin entering any input? |
| 83 | 'bib': input_begin, |
| 84 | |
| 85 | // When was the form submitted? |
| 86 | 'bfs': Date.now(), |
| 87 | |
| 88 | // How many keypresses did they make? |
| 89 | 'bkpc': keypresses.length, |
| 90 | |
| 91 | // How quickly did they press a sample of keys, and how long between them? |
| 92 | 'bkp': ak_bkp, |
| 93 | |
| 94 | // How quickly did they click the mouse, and how long between clicks? |
| 95 | 'bmc': ak_bmc, |
| 96 | |
| 97 | // How many mouseclicks did they make? |
| 98 | 'bmcc': mouseclicks.length, |
| 99 | |
| 100 | // When did they press modifier keys (like Shift or Capslock)? |
| 101 | 'bmk': modifierKeys.join( ';' ), |
| 102 | |
| 103 | // When did they correct themselves? e.g., press Backspace, or use the arrow keys to move the cursor back |
| 104 | 'bck': correctionKeys.join( ';' ), |
| 105 | |
| 106 | // How many times did they move the mouse? |
| 107 | 'bmmc': mousemoves.length, |
| 108 | |
| 109 | // How many times did they move around using a touchscreen? |
| 110 | 'btmc': touchmoveCount, |
| 111 | |
| 112 | // How many times did they scroll? |
| 113 | 'bsc': scrollCount, |
| 114 | |
| 115 | // How quickly did they perform touch events, and how long between them? |
| 116 | 'bte': ak_bte, |
| 117 | |
| 118 | // How many touch events were there? |
| 119 | 'btec' : touchEvents.length, |
| 120 | |
| 121 | // How quickly did they move the mouse, and how long between moves? |
| 122 | 'bmm' : ak_bmm |
| 123 | }; |
| 124 | |
| 125 | var akismet_field_prefix = 'ak_'; |
| 126 | |
| 127 | if ( this.getElementsByClassName ) { |
| 128 | // Check to see if we've used an alternate field name prefix. We store this as an attribute of the container around some of the Akismet fields. |
| 129 | var possible_akismet_containers = this.getElementsByClassName( 'akismet-fields-container' ); |
| 130 | |
| 131 | for ( var containerIndex = 0; containerIndex < possible_akismet_containers.length; containerIndex++ ) { |
| 132 | var container = possible_akismet_containers.item( containerIndex ); |
| 133 | |
| 134 | if ( container.getAttribute( 'data-prefix' ) ) { |
| 135 | akismet_field_prefix = container.getAttribute( 'data-prefix' ); |
| 136 | break; |
| 137 | } |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | for ( var field_name in input_fields ) { |
| 142 | var field = document.createElement( 'input' ); |
| 143 | field.setAttribute( 'type', 'hidden' ); |
| 144 | field.setAttribute( 'name', akismet_field_prefix + field_name ); |
| 145 | field.setAttribute( 'value', input_fields[ field_name ] ); |
| 146 | this.appendChild( field ); |
| 147 | } |
| 148 | }, supportsPassive ? { passive: true } : false ); |
| 149 | |
| 150 | form.addEventListener( 'keydown', function ( e ) { |
| 151 | // If you hold a key down, some browsers send multiple keydown events in a row. |
| 152 | // Ignore any keydown events for a key that hasn't come back up yet. |
| 153 | if ( e.key in keydowns ) { |
| 154 | return; |
| 155 | } |
| 156 | |
| 157 | var keydownTime = ( new Date() ).getTime(); |
| 158 | keydowns[ e.key ] = [ keydownTime ]; |
| 159 | |
| 160 | if ( ! input_begin ) { |
| 161 | input_begin = keydownTime; |
| 162 | } |
| 163 | |
| 164 | // In some situations, we don't want to record an interval since the last keypress -- for example, |
| 165 | // on the first keypress, or on a keypress after focus has changed to another element. Normally, |
| 166 | // we want to record the time between the last keyup and this keydown. But if they press a |
| 167 | // key while already pressing a key, we want to record the time between the two keydowns. |
| 168 | |
| 169 | var lastKeyEvent = Math.max( lastKeydown, lastKeyup ); |
| 170 | |
| 171 | if ( lastKeyEvent ) { |
| 172 | keydowns[ e.key ].push( keydownTime - lastKeyEvent ); |
| 173 | } |
| 174 | |
| 175 | lastKeydown = keydownTime; |
| 176 | }, supportsPassive ? { passive: true } : false ); |
| 177 | |
| 178 | form.addEventListener( 'keyup', function ( e ) { |
| 179 | if ( ! ( e.key in keydowns ) ) { |
| 180 | // This key was pressed before this script was loaded, or a mouseclick happened during the keypress, or... |
| 181 | return; |
| 182 | } |
| 183 | |
| 184 | var keyupTime = ( new Date() ).getTime(); |
| 185 | |
| 186 | if ( 'TEXTAREA' === e.target.nodeName || 'INPUT' === e.target.nodeName ) { |
| 187 | if ( -1 !== modifierKeyCodes.indexOf( e.key ) ) { |
| 188 | modifierKeys.push( keypresses.length - 1 ); |
| 189 | } else if ( -1 !== correctionKeyCodes.indexOf( e.key ) ) { |
| 190 | correctionKeys.push( keypresses.length - 1 ); |
| 191 | } else { |
| 192 | // ^ Don't record timings for keys like Shift or backspace, since they |
| 193 | // typically get held down for longer than regular typing. |
| 194 | |
| 195 | var keydownTime = keydowns[ e.key ][0]; |
| 196 | |
| 197 | var keypress = []; |
| 198 | |
| 199 | // Keypress duration. |
| 200 | keypress.push( keyupTime - keydownTime ); |
| 201 | |
| 202 | // Amount of time between this keypress and the previous keypress. |
| 203 | if ( keydowns[ e.key ].length > 1 ) { |
| 204 | keypress.push( keydowns[ e.key ][1] ); |
| 205 | } |
| 206 | |
| 207 | keypresses.push( keypress ); |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | delete keydowns[ e.key ]; |
| 212 | |
| 213 | lastKeyup = keyupTime; |
| 214 | }, supportsPassive ? { passive: true } : false ); |
| 215 | |
| 216 | form.addEventListener( "focusin", function ( e ) { |
| 217 | lastKeydown = null; |
| 218 | lastKeyup = null; |
| 219 | keydowns = {}; |
| 220 | }, supportsPassive ? { passive: true } : false ); |
| 221 | |
| 222 | form.addEventListener( "focusout", function ( e ) { |
| 223 | lastKeydown = null; |
| 224 | lastKeyup = null; |
| 225 | keydowns = {}; |
| 226 | }, supportsPassive ? { passive: true } : false ); |
| 227 | } |
| 228 | |
| 229 | document.addEventListener( 'mousedown', function ( e ) { |
| 230 | lastMousedown = ( new Date() ).getTime(); |
| 231 | }, supportsPassive ? { passive: true } : false ); |
| 232 | |
| 233 | document.addEventListener( 'mouseup', function ( e ) { |
| 234 | if ( ! lastMousedown ) { |
| 235 | // If the mousedown happened before this script was loaded, but the mouseup happened after... |
| 236 | return; |
| 237 | } |
| 238 | |
| 239 | var now = ( new Date() ).getTime(); |
| 240 | |
| 241 | var mouseclick = []; |
| 242 | mouseclick.push( now - lastMousedown ); |
| 243 | |
| 244 | if ( lastMouseup ) { |
| 245 | mouseclick.push( lastMousedown - lastMouseup ); |
| 246 | } |
| 247 | |
| 248 | mouseclicks.push( mouseclick ); |
| 249 | |
| 250 | lastMouseup = now; |
| 251 | |
| 252 | // If the mouse has been clicked, don't record this time as an interval between keypresses. |
| 253 | lastKeydown = null; |
| 254 | lastKeyup = null; |
| 255 | keydowns = {}; |
| 256 | }, supportsPassive ? { passive: true } : false ); |
| 257 | |
| 258 | document.addEventListener( 'mousemove', function ( e ) { |
| 259 | if ( mousemoveTimer ) { |
| 260 | clearTimeout( mousemoveTimer ); |
| 261 | mousemoveTimer = null; |
| 262 | } |
| 263 | else { |
| 264 | mousemoveStart = ( new Date() ).getTime(); |
| 265 | lastMousemoveX = e.offsetX; |
| 266 | lastMousemoveY = e.offsetY; |
| 267 | } |
| 268 | |
| 269 | mousemoveTimer = setTimeout( function ( theEvent, originalMousemoveStart ) { |
| 270 | var now = ( new Date() ).getTime() - 500; // To account for the timer delay. |
| 271 | |
| 272 | var mousemove = []; |
| 273 | mousemove.push( now - originalMousemoveStart ); |
| 274 | mousemove.push( |
| 275 | Math.round( |
| 276 | Math.sqrt( |
| 277 | Math.pow( theEvent.offsetX - lastMousemoveX, 2 ) + |
| 278 | Math.pow( theEvent.offsetY - lastMousemoveY, 2 ) |
| 279 | ) |
| 280 | ) |
| 281 | ); |
| 282 | |
| 283 | if ( mousemove[1] > 0 ) { |
| 284 | // If there was no measurable distance, then it wasn't really a move. |
| 285 | mousemoves.push( mousemove ); |
| 286 | } |
| 287 | |
| 288 | mousemoveStart = null; |
| 289 | mousemoveTimer = null; |
| 290 | }, 500, e, mousemoveStart ); |
| 291 | }, supportsPassive ? { passive: true } : false ); |
| 292 | |
| 293 | document.addEventListener( 'touchmove', function ( e ) { |
| 294 | if ( touchmoveCountTimer ) { |
| 295 | clearTimeout( touchmoveCountTimer ); |
| 296 | } |
| 297 | |
| 298 | touchmoveCountTimer = setTimeout( function () { |
| 299 | touchmoveCount++; |
| 300 | }, 500 ); |
| 301 | }, supportsPassive ? { passive: true } : false ); |
| 302 | |
| 303 | document.addEventListener( 'touchstart', function ( e ) { |
| 304 | lastTouchStart = ( new Date() ).getTime(); |
| 305 | }, supportsPassive ? { passive: true } : false ); |
| 306 | |
| 307 | document.addEventListener( 'touchend', function ( e ) { |
| 308 | if ( ! lastTouchStart ) { |
| 309 | // If the touchstart happened before this script was loaded, but the touchend happened after... |
| 310 | return; |
| 311 | } |
| 312 | |
| 313 | var now = ( new Date() ).getTime(); |
| 314 | |
| 315 | var touchEvent = []; |
| 316 | touchEvent.push( now - lastTouchStart ); |
| 317 | |
| 318 | if ( lastTouchEnd ) { |
| 319 | touchEvent.push( lastTouchStart - lastTouchEnd ); |
| 320 | } |
| 321 | |
| 322 | touchEvents.push( touchEvent ); |
| 323 | |
| 324 | lastTouchEnd = now; |
| 325 | |
| 326 | // Don't record this time as an interval between keypresses. |
| 327 | lastKeydown = null; |
| 328 | lastKeyup = null; |
| 329 | keydowns = {}; |
| 330 | }, supportsPassive ? { passive: true } : false ); |
| 331 | |
| 332 | document.addEventListener( 'scroll', function ( e ) { |
| 333 | if ( scrollCountTimer ) { |
| 334 | clearTimeout( scrollCountTimer ); |
| 335 | } |
| 336 | |
| 337 | scrollCountTimer = setTimeout( function () { |
| 338 | scrollCount++; |
| 339 | }, 500 ); |
| 340 | }, supportsPassive ? { passive: true } : false ); |
| 341 | } |
| 342 | |
| 343 | /** |
| 344 | * For the timestamp data that is collected, don't send more than `limit` data points in the request. |
| 345 | * Choose a random slice and send those. |
| 346 | */ |
| 347 | function prepare_timestamp_array_for_request( a, limit ) { |
| 348 | if ( ! limit ) { |
| 349 | limit = 100; |
| 350 | } |
| 351 | |
| 352 | var rv = ''; |
| 353 | |
| 354 | if ( a.length > 0 ) { |
| 355 | var random_starting_point = Math.max( 0, Math.floor( Math.random() * a.length - limit ) ); |
| 356 | |
| 357 | for ( var i = 0; i < limit && i < a.length; i++ ) { |
| 358 | rv += a[ random_starting_point + i ][0]; |
| 359 | |
| 360 | if ( a[ random_starting_point + i ].length >= 2 ) { |
| 361 | rv += "," + a[ random_starting_point + i ][1]; |
| 362 | } |
| 363 | |
| 364 | rv += ";"; |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | return rv; |
| 369 | } |
| 370 | |
| 371 | if ( document.readyState !== 'loading' ) { |
| 372 | init(); |
| 373 | } else { |
| 374 | document.addEventListener( 'DOMContentLoaded', init ); |
| 375 | } |
| 376 | })(); |