lib/arachni/browser/javascript/scripts/dom_monitor.js in arachni-1.3.2 vs lib/arachni/browser/javascript/scripts/dom_monitor.js in arachni-1.4

- old
+ new

@@ -1,13 +1,20 @@ /* - * Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com> + * Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com> * * This file is part of the Arachni Framework project and is subject to * redistribution and commercial restrictions. Please see the Arachni Framework * web site for more information on licensing and terms of use. */ +//if( !window.onerror ) { +// window.errors = []; +// window.onerror = function() { +// window.errors.push( arguments ) +// }; +//} + /* * Allows the system to optimize DOM/JS/AJAX analysis by overriding JS prototypes * and tracking things like bound events and timers. */ var _tokenDOMMonitor = _tokenDOMMonitor || { @@ -24,11 +31,50 @@ exclude_tags_from_digest: ['P'], exclude_attributes_from_digest: ['data-arachni-id'], - // Initialize. + event_attributes: { + "click" : true, + "dblclick" : true, + "mousedown" : true, + "mousemove" : true, + "mouseout" : true, + "mouseover" : true, + "mouseup" : true, + "load" : true, + "submit" : true, + "reset" : true, + "select" : true, + "change" : true, + "focus" : true, + "blur" : true, + "keydown" : true, + "keypress" : true, + "keyup" : true, + "input" : true + }, + + allowed_elements_without_events: { + "a": true, + "input": true, + "textarea": true, + "select": true, + "form": true + }, + + allowed_elements_with_inherited_events: { + "a": true, + "input": true, + "textarea": true, + "select": true, + "form": true, + "li": true, + "span": true, + "button": true + }, + initialize: function () { if( _tokenDOMMonitor.initialized ) return; _tokenDOMMonitor.track_setTimeout(); _tokenDOMMonitor.track_setInterval(); @@ -36,47 +82,89 @@ _tokenDOMMonitor.initialized = true }, update_trackers: function () { - _tokenDOMMonitor.track_jQuery_delegated_events(); }, - // Returns information about all DOM elements, their attributes and registered - // events. + // Returns information about all DOM elements that have events, along with + // some elements that elements_with_events: function () { var events_with_elements = []; var elements = document.getElementsByTagName("*"); var length = elements.length; + var global_events = window._arachni_events || []; + global_events = global_events.concat( document._arachni_events || [] ); + global_events = _tokenDOMMonitor.arrayUnique( global_events ); + for( var i = 0; i < length; i++ ) { + var has_events = false; var element = elements[i]; + _tokenDOMMonitor.bequeath_events( element ); + // Skip invisible elements. if( element.offsetWidth <= 0 && element.offsetHeight <= 0 ) continue; - _tokenDOMMonitor.apply_jQuery_delegated_events( element ); - var e = { tag_name: element.tagName.toLowerCase(), events: element._arachni_events || [], attributes: {} }; + if( _tokenDOMMonitor.is_allowed_element_with_inherited_events( e.tag_name ) ) { + e.events = e.events.concat( element._arachni_inherited_events || [] ); + e.events = _tokenDOMMonitor.arrayUnique( e.events.concat( global_events ) ); + } + var attributes = element.attributes; var attr_length = attributes.length; for( var j = 0; j < attr_length; j++ ){ - e.attributes[attributes[j].nodeName] = attributes[j].nodeValue; + var attr_name = attributes[j].nodeName; + + if( _tokenDOMMonitor.is_valid_event( attr_name ) ) { + has_events = true; + } + + e.attributes[attr_name] = attributes[j].nodeValue; } + if( !_tokenDOMMonitor.is_allowed_element_without_event( e.tag_name ) && + !has_events && e.events.length == 0 ) { + continue + } + has_events = false; + events_with_elements.push( e ); } return events_with_elements; }, + is_valid_event: function ( event ) { + return Object.prototype.hasOwnProperty.call( + _tokenDOMMonitor.event_attributes, + event.replace( 'on', '' ) + ); + }, + + is_allowed_element_without_event: function ( tag_name ) { + return Object.prototype.hasOwnProperty.call( + _tokenDOMMonitor.allowed_elements_without_events, + tag_name + ); + }, + + is_allowed_element_with_inherited_events: function ( tag_name ) { + return Object.prototype.hasOwnProperty.call( + _tokenDOMMonitor.allowed_elements_with_inherited_events, + tag_name + ); + }, + // Returns a string digest of the current DOM tree (i.e. node names and their // attributes without text-nodes). digest: function () { var elements = document.getElementsByTagName("*"); var length = elements.length; @@ -126,58 +214,10 @@ _tokenDOMMonitor.timeouts.push( arguments ); original_setTimeout.apply( this, arguments ); }; }, - track_jQuery_delegated_events: function () { - if( _tokenDOMMonitor.tracked_jQuery_delegated_events || !window.jQuery ) return; - _tokenDOMMonitor.tracked_jQuery_delegated_events = true; - - var original = window.jQuery.fn.on; - - // We only care for calls with selectors, as any other will attach the - // events to the DOM element immediately and thus be captured by the - // addEventListener tracker. - window.jQuery.fn.on = function ( types, selector, data, fn, one ) { - - // Types can be a map of types/handlers, in that case just run - // the original as it'll act recursively and pass itself (which is - // this override, really) each type. - if ( typeof types === "object" ) { - return original.apply( this, [].slice.call( arguments ) ); - } - - if ( data == null && fn == null ) { - // ( types, fn ) -- no selector, bail out. - return original.apply( this, [].slice.call( arguments ) ); - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - // ( types, selector, fn ) -- with selector, proceed. - fn = data; - } else { - // ( types, data, fn ) -- no selector, bail out. - return original.apply( this, [].slice.call( arguments ) ); - } - } - - if( selector ) { - this.each( function( i, e ){ - e['_arachni_jquery_delegated_event'] = - e['_arachni_jquery_delegated_event'] || []; - - e['_arachni_jquery_delegated_event'].push({ - selector: selector, - event: types, - handler: fn - }); - }); - } - - return original.apply( this, [].slice.call( arguments ) ); - }; - }, - // Overrides window.addEventListener and Node.prototype.addEventListener // to intercept event binds so that we can keep track of them in order to // optimize DOM analysis. track_addEventListener: function () { // Override window.addEventListener @@ -186,41 +226,72 @@ window.addEventListener = function ( event, listener, useCapture ) { _tokenDOMMonitor.registerEvent( window, event, listener ); original_Window_addEventListener.apply( window, [].slice.call( arguments ) ); }; + // Override document.addEventListener + var original_Document_addEventListener = document.addEventListener; + + document.addEventListener = function ( event, listener, useCapture ) { + _tokenDOMMonitor.registerEvent( document, event, listener ); + original_Document_addEventListener.apply( document, [].slice.call( arguments ) ); + }; + // Override Node.prototype.addEventListener var original_Node_addEventListener = Node.prototype.addEventListener; Node.prototype.addEventListener = function ( event, listener, useCapture ) { _tokenDOMMonitor.registerEvent( this, event, listener ); original_Node_addEventListener.apply( this, [].slice.call( arguments ) ); }; }, - apply_jQuery_delegated_events: function ( element ){ - if( !element['_arachni_jquery_delegated_event'] ) return; + bequeath_events: function( element ) { + var children = element.childNodes; - var event_data = element['_arachni_jquery_delegated_event']; - var jquery_element = jQuery( element ); + for( var i = 0; i < children.length; i++ ) { + var child = children[i]; - for( var i = 0; i < event_data.length; i++ ) { - var data = event_data[i]; + if( !('_arachni_inherited_events' in child) ) child['_arachni_inherited_events'] = []; - jquery_element.find( data.selector ).each( function ( j, child ){ - _tokenDOMMonitor.registerEvent( child, data.event, data.handler ); - }); + if( element['_arachni_events'] ) { + child['_arachni_inherited_events'] = + element['_arachni_events'].concat( child['_arachni_inherited_events'] ); + } + + if( element['_arachni_inherited_events'] ) { + child['_arachni_inherited_events'] = + element['_arachni_inherited_events'].concat( child['_arachni_inherited_events'] ); + } + + child['_arachni_inherited_events'] = + _tokenDOMMonitor.arrayUnique( child['_arachni_inherited_events'] ) } + }, - element['_arachni_jquery_delegated_event'] = undefined; + arrayUnique: function( array ) { + var a = array.concat(); + + for( var i = 0; i < a.length; ++i ) { + for( var j = i + 1; j < a.length; ++j ) { + if( a[i] === a[j] ) + a.splice( j--, 1 ); + } + } + + return a; }, // Registers an event and its handler for the given element. registerEvent: function ( element, event, handler ) { if( !('_arachni_events' in element) ) element['_arachni_events'] = []; // Custom events are usually in the form of "click.delegateEventsview13". - element['_arachni_events'].push( [event.split( '.' )[0], handler] ); + event = event.split( '.' )[0]; + + if( _tokenDOMMonitor.is_valid_event( event ) ) { + element['_arachni_events'].push( [event, handler] ); + } }, // Sets a unique enough custom ID attribute to elements that lack proper IDs. // This gets called externally (by the Browser) once the page is settled. setElementIds: function() {