#!/usr/bin/env python

"""
$Id$

Copyright (c) 2006-2010 sqlmap developers (http://sqlmap.sourceforge.net/)
See the file 'doc/COPYING' for copying permission
"""

import codecs
import os
import re
import time

from extra.clientform.clientform import ParseResponse
from lib.core.common import dataToSessionFile
from lib.core.common import paramToDict
from lib.core.common import readInput
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.data import paths
from lib.core.dump import dumper
from lib.core.exception import sqlmapFilePathException
from lib.core.exception import sqlmapGenericException
from lib.core.exception import sqlmapSyntaxException
from lib.core.session import resumeConfKb
from lib.core.xmldump import dumper as xmldumper
from lib.request.connect import Connect as Request

def __setRequestParams():
    """
    Check and set the parameters and perform checks on 'data' option for
    HTTP method POST.
    """

    if conf.direct:
        conf.parameters[None] = "direct connection"
        return

    __testableParameters = False

    # Perform checks on GET parameters
    if conf.parameters.has_key("GET") and conf.parameters["GET"]:
        parameters = conf.parameters["GET"]
        __paramDict = paramToDict("GET", parameters)

        if __paramDict:
            conf.paramDict["GET"] = __paramDict
            __testableParameters = True

    # Perform checks on POST parameters
    if conf.method == "POST" and not conf.data:
        errMsg = "HTTP POST method depends on HTTP data value to be posted"
        raise sqlmapSyntaxException, errMsg

    if conf.data:
        conf.data = conf.data.replace("\n", " ")
        conf.parameters["POST"] = conf.data

        # Check if POST data is in xml syntax
        if re.match("[\n]*<(\?xml |soap\:|ns).*>", conf.data):
            conf.paramDict["POSTxml"] = True
            __paramDict = paramToDict("POSTxml", conf.data)
        else:
            __paramDict = paramToDict("POST", conf.data)

        if __paramDict:
            conf.paramDict["POST"] = __paramDict
            __testableParameters = True

        conf.method = "POST"

    if "*" in conf.url:
        conf.parameters["URI"] = conf.url
        conf.paramDict["URI"] = {}
        parts = conf.url.split("*")
        for i in range(len(parts)-1):
            result = str()
            for j in range(len(parts)):
                result += parts[j]
                if i == j:
                    result += "*"
            conf.paramDict["URI"]["#%d*" % (i+1)] = result
        conf.url = conf.url.replace("*", str())
        __testableParameters = True

    # Perform checks on Cookie parameters
    if conf.cookie:
        conf.parameters["Cookie"] = conf.cookie
        __paramDict = paramToDict("Cookie", conf.cookie)

        if __paramDict:
            conf.paramDict["Cookie"] = __paramDict
            __testableParameters = True

    # Perform checks on User-Agent header value
    if conf.httpHeaders:
        for httpHeader, headerValue in conf.httpHeaders:
            if httpHeader == "User-Agent":
                # No need for url encoding/decoding the user agent
                conf.parameters["User-Agent"] = headerValue

                condition  = not conf.testParameter
                condition |= "User-Agent" in conf.testParameter
                condition |= "user-agent" in conf.testParameter
                condition |= "useragent" in conf.testParameter
                condition |= "ua" in conf.testParameter

                if condition:
                    conf.paramDict["User-Agent"] = { "User-Agent": headerValue }
                    __testableParameters = True

    if not conf.parameters:
        errMsg  = "you did not provide any GET, POST and Cookie "
        errMsg += "parameter, neither an User-Agent header"
        raise sqlmapGenericException, errMsg

    elif not __testableParameters:
        errMsg  = "all testable parameters you provided are not present "
        errMsg += "within the GET, POST and Cookie parameters"
        raise sqlmapGenericException, errMsg

def findPageForms():
    infoMsg = "searching for forms"
    logger.info(infoMsg)

    response, _ = Request.queryPage(response=True)
    forms = ParseResponse(response, backwards_compat=False)

    count = 1
    for form in forms:
        request = form.click()

        url = request.get_full_url()
        method = request.get_method()
        data = request.get_data() if request.has_data() else None

        message = "(#%d) Do you want to test form '%s' (%s, %s%s) [Y/n] " % (count, form.name, method, url, ", %s" % repr(data) if data else "")
        test = readInput(message, default="Y")

        if not test or test[0] in ("y", "Y"):
            if method == "POST":
                message = " Edit POST data [default: %s]: " % (data if data else "")
                test = readInput(message, default=data)

            elif method == "GET":
                if url.find("?") > -1:
                    firstPart = url[:url.find("?")]
                    secondPart = url[url.find("?")+1:]
                    message = " Edit GET data [default: %s]: " % secondPart
                    test = readInput(message, default=secondPart)
                    url = "%s?%s" % (firstPart, test)

            kb.targetUrls.add((url, method, data, conf.cookie))

        count +=1

def __setOutputResume():
    """
    Check and set the output text file and the resume functionality.
    """

    if not conf.sessionFile:
        conf.sessionFile = "%s%ssession" % (conf.outputPath, os.sep)

    logger.info("using '%s' as session file" % conf.sessionFile)

    if os.path.exists(conf.sessionFile):
        if not conf.flushSession:
            readSessionFP = codecs.open(conf.sessionFile, "r", conf.dataEncoding, 'replace')
            __url_cache = set()
            __expression_cache = {}

            for line in readSessionFP.readlines(): # xreadlines doesn't return unicode strings when codec.open() is used
                if line.count("][") == 4:
                    line = line.split("][")

                    if len(line) != 5:
                        continue

                    url, _, _, expression, value = line
    
                    if not value:
                        continue
    
                    if url[0] == "[":
                        url = url[1:]
    
                    value = value.rstrip('\r\n') # Strips both chars independently

                    if url not in ( conf.url, conf.hostname ):
                        continue

                    if url not in __url_cache:
                        kb.resumedQueries[url] = {}
                        kb.resumedQueries[url][expression] = value
                        __url_cache.add(url)
                        __expression_cache[url] = set(expression)
    
                    resumeConfKb(expression, url, value)
    
                    if expression not in __expression_cache[url]:
                        kb.resumedQueries[url][expression] = value
                        __expression_cache[url].add(value)
                    elif len(value) >= len(kb.resumedQueries[url][expression]):
                        kb.resumedQueries[url][expression] = value

            readSessionFP.close()
        else:
            try:
                os.remove(conf.sessionFile)
                logger.info("flushing session file")
            except OSError, msg:
                errMsg = "unable to flush the session file (%s)" % msg
                raise sqlmapFilePathException, errMsg

    try:
        conf.sessionFP = codecs.open(conf.sessionFile, "a", conf.dataEncoding)
        dataToSessionFile("\n[%s]\n" % time.strftime("%X %x"))
    except IOError:
        errMsg = "unable to write on the session file specified"
        raise sqlmapFilePathException, errMsg

def __createFilesDir():
    """
    Create the file directory.
    """

    if not conf.rFile:
        return

    conf.filePath = paths.SQLMAP_FILES_PATH % conf.hostname

    if not os.path.isdir(conf.filePath):
        os.makedirs(conf.filePath, 0755)

def __createDumpDir():
    """
    Create the dump directory.
    """

    if not conf.dumpTable and not conf.dumpAll and not conf.search:
        return

    conf.dumpPath = paths.SQLMAP_DUMP_PATH % conf.hostname

    if not os.path.isdir(conf.dumpPath):
        os.makedirs(conf.dumpPath, 0755)

def __configureDumper():
    if conf.xmlFile:
        conf.dumper = xmldumper
    else:
        conf.dumper = dumper

    conf.dumper.setOutputFile()

def __createTargetDirs():
    """
    Create the output directory.
    """

    conf.outputPath = "%s%s%s" % (paths.SQLMAP_OUTPUT_PATH, os.sep, conf.hostname)

    if not os.path.isdir(paths.SQLMAP_OUTPUT_PATH):
        os.makedirs(paths.SQLMAP_OUTPUT_PATH, 0755)

    if not os.path.isdir(conf.outputPath):
        os.makedirs(conf.outputPath, 0755)

    __createDumpDir()
    __createFilesDir()
    __configureDumper()

def initTargetEnv():
    """
    Initialize target environment.
    """

    if conf.multipleTargets:
        if conf.cj:
            conf.cj.clear()

        conf.paramDict     = {}
        conf.parameters    = {}
        conf.sessionFile   = None

        kb.authHeader      = None
        kb.dbms            = None
        kb.dbmsDetected    = False
        kb.dbmsVersion     = [ "Unknown" ]
        kb.dynamicMarkings = []
        kb.errorTest       = None
        kb.htmlFp          = []
        kb.lastErrorPage   = None
        kb.injParameter    = None
        kb.injPlace        = None
        kb.injType         = None
        kb.nullConnection  = None
        kb.pageStable      = None
        kb.parenthesis     = None
        kb.proxyAuthHeader = None
        kb.stackedTest     = None
        kb.timeTest        = None
        kb.unionComment    = ""
        kb.unionCount      = None
        kb.unionPosition   = None

def setupTargetEnv():
    __createTargetDirs()
    __setRequestParams()
    __setOutputResume()