#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
Code injection vulnerabilities.
"""

__license__ = """
GoLismero 2.0 - The web knife - Copyright (C) 2011-2014

Golismero project site: http://golismero-project.com
Golismero project mail: contact@golismero-project.com


This program 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; either version 2
of the License, or (at your option) any later version.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
"""


__all__ = ["Injection"]

from .. import Vulnerability, WebVulnerability
from ... import identity
from ...resource import Resource
from ...resource.url import URL


#------------------------------------------------------------------------------
class Injection(Vulnerability):
    """
    Injection Flaw.

    An unspecified injection vulnerability was found.
    """

    DEFAULTS = Vulnerability.DEFAULTS.copy()
    DEFAULTS["level"] = "high"
    DEFAULTS["cwe"]   = "CWE-77"
    DEFAULTS["references"] = (
        "https://www.owasp.org/index.php/Top_10_2007-Injection_Flaws",
    )


    #--------------------------------------------------------------------------
    def __init__(self, target, vulnerable_params, injection_point,
                 injection_type, **kwargs):
        """
        :param target: Resource where the vulnerability was found.
        :type target: Resource

        :param vulnerable_params:
            Map of parameter names and their required payload.
        :type vulnerable_params: dict(str -> str)

        :param injection_point: Injection point.
            The exact meaning of this value is up to the subclass.
        :type injection_point: int

        :param injection_type: Injection type.
            The exact meaning of this value is up to the subclass.
        :type injection_type: str
        """

        # Validate the arguments.
        if not type(vulnerable_params) is dict:
            raise TypeError(
                "Expected dict, got %r instead" % type(vulnerable_params))
        if not type(injection_point) is int:
            raise TypeError(
                "Expected int, got %r instead" % type(injection_point))
        if not type(injection_type) is str:
            raise TypeError(
                "Expected str, got %r instead" % type(injection_type))

        # Save the properties.
        self.__vulnerable_params = tuple(sorted(vulnerable_params.items()))
        self.__injection_point   = injection_point
        self.__injection_type    = injection_type

        # Parent constructor.
        super(Injection, self).__init__(target, **kwargs)

    __init__.__doc__ += Vulnerability.__init__.__doc__[
            Vulnerability.__init__.__doc__.find("\n        :keyword"):]


    #--------------------------------------------------------------------------
    @identity
    def vulnerable_params(self):
        """
        :return: Map of parameter names and their required payload.
        :rtype: tuple((tuple(str, str), ...)
        """
        return self.__vulnerable_params


    #--------------------------------------------------------------------------
    @property
    def vulnerable_params_as_dict(self):
        """
        :return: Map of parameter names and their required payload.
        :rtype: dict(str -> str)
        """
        return dict(self.__vulnerable_params)


    #--------------------------------------------------------------------------
    @identity
    def injection_point(self):
        """
        :return: Injection point.
            The exact meaning changes for different injection classes, see
            the documentation for this class.
        :rtype: int
        """
        return self.__injection_point


    #--------------------------------------------------------------------------
    @identity
    def injection_type(self):
        """
        :return: Injection type.
            The exact meaning changes for different injection classes, see
            the documentation for this class.
        :rtype: str
        """
        return self.__injection_type


#------------------------------------------------------------------------------
class HTTPInjection(Injection):
    """
    Abstract class for injection vulnerabilities in an HTTP context.
    """

    TARGET_CLASS = URL

    data_subtype = "vulnerability/abstract"

    INJECTION_POINT_URL    = 1
    INJECTION_POINT_HEADER = 2
    INJECTION_POINT_BODY   = 3
    INJECTION_POINT_COOKIE = 4

    RESOLVER = {

        # HTTP method
        'get'          : INJECTION_POINT_URL,
        'post'         : INJECTION_POINT_URL,
        'put'          : INJECTION_POINT_URL,
        'delete'       : INJECTION_POINT_URL,
        'head'         : INJECTION_POINT_URL,
        'options'      : INJECTION_POINT_URL,
        'trace'        : INJECTION_POINT_URL,

        # HTTP field
        'user-agent'    : INJECTION_POINT_HEADER,
        'accept'        : INJECTION_POINT_HEADER,
        'host'          : INJECTION_POINT_HEADER,
        'date'          : INJECTION_POINT_HEADER,
        'via'           : INJECTION_POINT_HEADER,

        # Cookie
        'cookie'        : INJECTION_POINT_COOKIE,

        # Body
        'body'          : INJECTION_POINT_BODY,
    }


    #--------------------------------------------------------------------------
    def __init__(self, target, vulnerable_params, injection_point,
                 injection_type, **kwargs):
        """
        :param target: URL where the vulnerability was found.
        :type target: Url

        :param vulnerable_params:
            Map of parameter names and their required payload.
        :type vulnerable_params: dict(str -> str)

        :param injection_point: Injection point.
            Must be one of the INJECTION_POINT_* constants.
        :type injection_point: int

        :param injection_type: Injection type.
            The exact meaning changes for different injection classes.
        :type injection_type: str
        """

        # Sanitize the "url" argument.
        url = WebVulnerability._sanitize_url(self, target)

        # Save the raw URL.
        self.__url = url.url

        # Parse the payload.
        if vulnerable_params:
            self.__payload = "\r\n".join( vulnerable_params.itervalues() )
        else:
            self.__payload = None

        # Parent constructor.
        super(HTTPInjection, self).__init__(
            target, vulnerable_params, injection_point, injection_type, **kwargs)


    #--------------------------------------------------------------------------
    @property
    def url(self):
        """
        :return: Raw URL where the vulnerability was found.
        :rtype: str
        """
        return self.__url


    #--------------------------------------------------------------------------
    @property
    def payload(self):
        return self.__payload


    #--------------------------------------------------------------------------
    @classmethod
    def str2injection_point(cls, text):
        """
        From a text, get the value of the injection point. For example:

        >>> HTTPInjection.str2injection("GET")
        2
        >>> HTTPInjection.str2injection("cookie")
        5

        :param text: Injection point, HTTP method name or HTTP header name.
        :type text: str

        :return: One of the INJECTION_POINT_* constants, or None if unknown.
        :type: int | None
        """
        return cls.RESOLVER.get(text.lower().strip())
