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() {