'''
cors_preflight.py

Copyright 2012 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 core.controllers.outputManager as om
import core.data.kb.knowledgeBase as kb
import core.data.kb.vuln as vuln
import core.data.constants.severity as severity

from core.controllers.plugins.audit_plugin import AuditPlugin
from core.controllers.w3afException import w3afException
from core.controllers.cors.utils import provides_cors_features

from core.data.options.option import option
from core.data.options.option_list import OptionList
from core.data.constants import httpConstants


class cors_preflight(AuditPlugin):
    '''
    Inspect if application include a server side check for incoming HTTP request
    that must be preflighted in case of 'Cross Origin Resource Sharing (CORS)'
    request type.
      
    @author: Dominique RIGHETTO (dominique.righetto@owasp.org)     
    '''

    def __init__(self):
        AuditPlugin.__init__(self)
        
        # Define plugin options configuration variables
        self.origin_header_value = "http://w3af.sourceforge.net"
        self.expected_http_response_code = httpConstants.FORBIDDEN
        self.test_http_method = "POST"
        
    def audit(self, freq):
        '''
        Plugin entry point.

        @param freq: A fuzzableRequest
        '''  
        # Detect if current url provides CORS features
        if not provides_cors_features(freq, self._uri_opener):
            return 
               
        # Get current URL object
        url = freq.getURL()
                
        # Try to send a forged HTTP request in order to test target application
        # behavior using HTTP class provided by W3AF framework : "core.data.url.xUrllib"
        try:         
            #Build request content
            forgedReqBody = '{"Content":"w3af test from cors_preflight plugin"}'
            forgedReq = self.test_http_method + " " + url.getPath() + " HTTP/1.1\r\n"
            forgedReq = forgedReq + "Host: " + url.getDomain() + ":" + str(url.getPort()) + "\r\n"
            forgedReq = forgedReq + "User-Agent: W3AF\r\n"
            forgedReq = forgedReq + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
            forgedReq = forgedReq + "Accept-Language: en-us,en;q=0.5\r\n"
            forgedReq = forgedReq + "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
            forgedReq = forgedReq + "Origin: " + self.origin_header_value.strip() + "\r\n"  
            forgedReq = forgedReq + "Content-Type: application/json\r\n"  
            forgedReq = forgedReq + "Content-Length: " + str(len(forgedReqBody)) + "\r\n"  
            forgedReq = forgedReq + "X-w3af-Plugin: cors_preflight\r\n" 
            #Sent request and analyze response
            #--Sent request
            response = self._uri_opener.sendRawRequest(forgedReq, forgedReqBody, False)
            response_code = response.getCode()                    
            #--Analyze response
            if response_code != self.expected_http_response_code:
                v = vuln.vuln()
                v.setSeverity(severity.MEDIUM)
                v.setPluginName(self.getName())
                v.setName('Inspect Request Preflight')
                v.setURL(url)
                v.setId(response.id)
                msg = 'Application seems to accept the ' + self.test_http_method
                msg += ' request type even if an OPTIONS request type has not be'
                msg += ' previously sent to preflight the current request.'
                v.setDesc(msg)
                kb.kb.append(self , 'cors_preflight' , v)                                    
        except Exception, e:
            #Manage error       
            om.out.error('Error in audit.cors_preflight: "' + repr(e) + '".')       
        
    def get_options(self):
        '''
        @return: A list of option objects for this plugin.
        '''
        ol = OptionList()
        
        d = "Origin HTTP header value in order to create a CORS request type"
        h = "Define value used to specify the 'Origin' HTTP header in order "\
            "to create a CORS request type"
        o = option('origin_header_value', self.origin_header_value, d, "url", help=h)        
        ol.add(o)
            
        d = "Expected HTTP response code from application if it detect a request"\
            " that must be preflighted but have not respected the preflight" \
            " process defined by W3C specification."
        h = "Define the HTTP response code that the application return if it" \
            "detect that a request that must be preflighted but have not" \
            " respected the preflight process defined by W3C specification"
        o = option('expected_http_response_code', self.expected_http_response_code, d, "integer", help=h)        
        ol.add(o)
        
        d = "HTTP method to use for the request that will be forged to test the"\
            " application behavior"
        h = "Define the HTTP method to use for the request that will be forged"\
            " to test the application behavior (value must be 'POST' or"\
            " 'DELETE' or 'PUT')"
        o = option('test_http_method', self.test_http_method, d, "string", help=h)        
        ol.add(o)
                   
        return ol

    def set_options(self, options_list):
        '''
        This method sets all the options that are configured using the user
        interface generated by the framework using the result of getOptions().

        @parameter OptionList: A dictionary with the options for the plugin.
        @return: No value is returned.
        '''
        self.origin_header_value = options_list['origin_header_value'].getValue()
        self.expected_http_response_code = options_list['expected_http_response_code'].getValue()   
        self.test_http_method = options_list['test_http_method'].getValue()
        self.test_http_method = self.test_http_method.strip().upper()
        
        # Check options setted
        if self.expected_http_response_code < 100 or self.expected_http_response_code > 505:
            msg = 'Please enter a valid HTTP response code (see valid code list'\
                  ' on "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html").'
            raise w3afException(msg)
        
        if len(self.origin_header_value.strip()) == 0 :
            msg = 'Please enter a valid value for the "Origin" HTTP header.'
            raise w3afException(msg)  
        
        if self.test_http_method.upper().strip() != 'POST' and \
        self.test_http_method.upper().strip() != 'DELETE' and \
        self.test_http_method.upper().strip() != 'PUT':
            msg = 'Please enter a valid value from "POST", "DELETE", "PUT" list.'
            raise w3afException(msg)
        
    def get_long_desc(self):
        '''
        @return: A DETAILED description of the plugin functions and features.
        '''
        return '''
        Inspect if application include a server side check for incoming HTTP
        request that must be preflighted in case of 'Cross Origin Resource
        Sharing (CORS)' request type.
        
        Configurable parameters are:
            - origin_header_value
            - expected_http_response_code      
            - test_http_method
      
        Note: This plugin is useful to test "Cross Origin Resource Sharing
              (CORS)" application behaviors.
        
        Preflighted : http://developer.mozilla.org/en-US/docs/HTTP_access_control#Preflighted_requests
          requests    http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0
                                       
        CORS        : http://developer.mozilla.org/en-US/docs/HTTP_access_control
                      http://www.w3.org/TR/cors
        '''
