'''
timeAnalysis.py

Copyright 2006 Andres Riancho

This file is part of w3af, w3af.sourceforge.net .

w3af is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 2 of the License.

w3af is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with w3af; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

'''

import time
import core.controllers.outputManager as om

# Define two internal variables
# AVERAGE_CALCULATION is used to define how many request/response values
# are used to calculate the average time it takes to get a response.
AVERAGE_CALCULATION = 3

# TIME_DEVIATION_MULTIPLIER , big name for a simple thing :)
# If average time is 1 second, TIME_DEVIATION_MULTIPLIER is 3 and the time of
# the current request being analized is 4, then we report a vulnerability 
# ( current > TIME_DEVIATION_MULTIPLIER * avg )
TIME_DEVIATION_MULTIPLIER = 25

class timeAnalysis:
	'''
	This class analyzes the response time of a GET/POST .
	It is usefull for finding possible DoS's to Web Apps, buffer overflows, etc.
	
	@author: Andres Riancho ( andres.riancho@gmail.com )
	'''
	
	def __init__(self):
		self._numberOfRequests = 0
		self._outgoingRequests = {}
		self._incomingResponses = {}
		self._average = 0
		
	def preRequest(self, uri, method, dc={} ):
		'''
		It is called before the actual request is done. This method registers all 
		outgoing requests with their corresponding time.
		
		@param uri: This is the url to register.
		@param method: GET/POST
		@param dc: This is the data container that xUrllib is going to send.
		@return: No value is returned.
		'''
		if self._numberOfRequests < AVERAGE_CALCULATION:
			# The first 3 requests are used to calculate the average time that the
			# response takes to return to w3af
			self._calculateAvg(uri, method, dc )
		else:
			self._registerRequest(uri, method, dc)
		
	def postRequest(self, uri, method, dc={} ):
		'''
		It is called before the actual request is done. This method registers all 
		outgoing requests with their corresponding time.
		
		@param uri: This is the url to register.
		@param method: GET/POST
		@param dc: This is the data container that xUrllib is going to send.
		@return: No value is returned.
		'''
		if self._numberOfRequests < AVERAGE_CALCULATION :
			# The first 3 requests are used to calculate the average time that the
			# response takes to return to w3af
			self._calculateAvg(uri, method, dc )
		else:
			# I'll compare the average with the current value
			receiveTime = time.time()
			sentTime = self._getRequestTime(uri, method, dc)
			self._removeRequest( uri, method, dc)
			
			if ( receiveTime - sentTime ) > ( self._average * TIME_DEVIATION_MULTIPLIER ):
				# The time deviation was to big, report it.
				### TODO: Check WHY this AINT working and enable output again
				#om.out.vulnerability('timeAnalysis detected a big time deviation when ' +
				#'requesting the URI: ' + uri + ' with method: ' + method + 
				#' and the following data: ' + str(dc) + ' . The average time for a response is: ' +
				#str(self._average) + ' , this response took: ' + str(receiveTime - sentTime))
				pass
				
	def _calculateAvg(self, uri, method, dc):
		'''
		This method calculates the AVG time.
		
		@param uri: This is the url to register.
		@param method: GET/POST
		@param dc: This is the data container that xUrllib is going to send.
		@return: No value is returned.
		'''
		if (uri, method, str(dc)) not in self._outgoingRequests.keys():
			# This tuple hasnt been registered as an outgoing request.
			# It should be registered now.
			self._registerRequest( uri, method, dc )
		elif (uri, method, str(dc)) not in self._incomingResponses.keys():
			# This tuple has been registered before and now it has arrived for analysis
			self._numberOfRequests += 1
			self._registerResponse(uri, method, dc)
		else:
			# Something went really wrong, the tuple aint registered anywhere
			om.out.error('timeAnalysis plugin detected an internal error.')
		
		if self._numberOfRequests == AVERAGE_CALCULATION:
			# Now we are going to calculate the average time
			reqTime = 0
			resTime = 0
			for req in self._outgoingRequests.keys():
				reqTime += self._outgoingRequests[ req ]
				del self._outgoingRequests[ req ]
				
			for res in self._incomingResponses.keys():
				resTime += self._incomingResponses[ res ]
				del self._incomingResponses[ res ]
				
			self._average = (resTime - reqTime) / AVERAGE_CALCULATION
		
	def _registerRequest(self, uri, method, dc ):
		sentTime = time.time()	# Return the current clock
		self._outgoingRequests[ (uri, method, str(dc)) ] = sentTime
	
	def _registerResponse(self, uri, method, dc ):
		receiveTime = time.time()
		self._incomingResponses[ (uri, method, str(dc)) ] = receiveTime
		
	def _getRequestTime(self , uri, method, dc ):
		return self._outgoingRequests[ (uri, method, str(dc) ) ]

	def _removeRequest( self, uri, method, dc ):
		del self._outgoingRequests[ (uri, method, str(dc)) ]
