/lib/core/common.py
Python | 3293 lines | 3232 code | 28 blank | 33 comment | 44 complexity | f51f1d2fcb14a527696acb64962097c9 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
Large files files are truncated, but you can click here to view the full file
- #!/usr/bin/env python
- """
- Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/)
- See the file 'doc/COPYING' for copying permission
- """
- import codecs
- import contextlib
- import cookielib
- import copy
- import ctypes
- import httplib
- import inspect
- import logging
- import ntpath
- import os
- import posixpath
- import random
- import re
- import socket
- import string
- import struct
- import sys
- import time
- import urllib
- import urlparse
- import unicodedata
- from ConfigParser import DEFAULTSECT
- from ConfigParser import RawConfigParser
- from StringIO import StringIO
- from difflib import SequenceMatcher
- from math import sqrt
- from optparse import OptionValueError
- from subprocess import PIPE
- from subprocess import Popen as execute
- from xml.dom import minidom
- from xml.sax import parse
- from extra.safe2bin.safe2bin import safecharencode
- from lib.core.bigarray import BigArray
- 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.convert import base64pickle
- from lib.core.convert import base64unpickle
- from lib.core.convert import hexdecode
- from lib.core.convert import htmlunescape
- from lib.core.convert import stdoutencode
- from lib.core.convert import unicodeencode
- from lib.core.convert import utf8encode
- from lib.core.decorators import cachedmethod
- from lib.core.dicts import DBMS_DICT
- from lib.core.dicts import DEPRECATED_HINTS
- from lib.core.dicts import SQL_STATEMENTS
- from lib.core.enums import ADJUST_TIME_DELAY
- from lib.core.enums import CHARSET_TYPE
- from lib.core.enums import DBMS
- from lib.core.enums import EXPECTED
- from lib.core.enums import HEURISTIC_TEST
- from lib.core.enums import HTTPHEADER
- from lib.core.enums import HTTPMETHOD
- from lib.core.enums import OS
- from lib.core.enums import PLACE
- from lib.core.enums import PAYLOAD
- from lib.core.enums import REFLECTIVE_COUNTER
- from lib.core.enums import SORT_ORDER
- from lib.core.exception import SqlmapDataException
- from lib.core.exception import SqlmapFilePathException
- from lib.core.exception import SqlmapGenericException
- from lib.core.exception import SqlmapNoneDataException
- from lib.core.exception import SqlmapMissingDependence
- from lib.core.exception import SqlmapSilentQuitException
- from lib.core.exception import SqlmapSyntaxException
- from lib.core.exception import SqlmapUserQuitException
- from lib.core.log import LOGGER_HANDLER
- from lib.core.optiondict import optDict
- from lib.core.settings import BOLD_PATTERNS
- from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR
- from lib.core.settings import DBMS_DIRECTORY_DICT
- from lib.core.settings import DEFAULT_COOKIE_DELIMITER
- from lib.core.settings import DEFAULT_GET_POST_DELIMITER
- from lib.core.settings import DEFAULT_MSSQL_SCHEMA
- from lib.core.settings import DEPRECATED_OPTIONS
- from lib.core.settings import DESCRIPTION
- from lib.core.settings import DUMMY_SQL_INJECTION_CHARS
- from lib.core.settings import DUMMY_USER_INJECTION
- from lib.core.settings import DYNAMICITY_MARK_LENGTH
- from lib.core.settings import ERROR_PARSING_REGEXES
- from lib.core.settings import FORM_SEARCH_REGEX
- from lib.core.settings import GENERIC_DOC_ROOT_DIRECTORY_NAMES
- from lib.core.settings import HASHDB_MILESTONE_VALUE
- from lib.core.settings import HOST_ALIASES
- from lib.core.settings import INFERENCE_UNKNOWN_CHAR
- from lib.core.settings import ISSUES_PAGE
- from lib.core.settings import IS_WIN
- from lib.core.settings import LARGE_OUTPUT_THRESHOLD
- from lib.core.settings import MIN_TIME_RESPONSES
- from lib.core.settings import ML
- from lib.core.settings import NULL
- from lib.core.settings import PARAMETER_AMP_MARKER
- from lib.core.settings import PARAMETER_SEMICOLON_MARKER
- from lib.core.settings import PARTIAL_VALUE_MARKER
- from lib.core.settings import PAYLOAD_DELIMITER
- from lib.core.settings import PLATFORM
- from lib.core.settings import PRINTABLE_CHAR_REGEX
- from lib.core.settings import PYVERSION
- from lib.core.settings import REFERER_ALIASES
- from lib.core.settings import REFLECTED_BORDER_REGEX
- from lib.core.settings import REFLECTED_MAX_REGEX_PARTS
- from lib.core.settings import REFLECTED_REPLACEMENT_REGEX
- from lib.core.settings import REFLECTED_VALUE_MARKER
- from lib.core.settings import REFLECTIVE_MISS_THRESHOLD
- from lib.core.settings import REVISION
- from lib.core.settings import SENSITIVE_DATA_REGEX
- from lib.core.settings import SITE
- from lib.core.settings import SUPPORTED_DBMS
- from lib.core.settings import TEXT_TAG_REGEX
- from lib.core.settings import TIME_STDEV_COEFF
- from lib.core.settings import UNICODE_ENCODING
- from lib.core.settings import UNKNOWN_DBMS_VERSION
- from lib.core.settings import URI_QUESTION_MARKER
- from lib.core.settings import URLENCODE_CHAR_LIMIT
- from lib.core.settings import URLENCODE_FAILSAFE_CHARS
- from lib.core.settings import USER_AGENT_ALIASES
- from lib.core.settings import VERSION
- from lib.core.settings import VERSION_STRING
- from lib.core.threads import getCurrentThreadData
- from thirdparty.clientform.clientform import ParseResponse
- from thirdparty.clientform.clientform import ParseError
- from thirdparty.magic import magic
- from thirdparty.odict.odict import OrderedDict
- from thirdparty.termcolor.termcolor import colored
- class UnicodeRawConfigParser(RawConfigParser):
- """
- RawConfigParser with unicode writing support
- """
- def write(self, fp):
- """
- Write an .ini-format representation of the configuration state.
- """
- if self._defaults:
- fp.write("[%s]\n" % DEFAULTSECT)
- for (key, value) in self._defaults.items():
- fp.write("%s = %s\n" % (key, getUnicode(value, UNICODE_ENCODING).replace('\n', '\n\t')))
- fp.write("\n")
- for section in self._sections:
- fp.write("[%s]\n" % section)
- for (key, value) in self._sections[section].items():
- if key != "__name__":
- if value is None:
- fp.write("%s\n" % (key))
- else:
- fp.write("%s = %s\n" % (key, getUnicode(value, UNICODE_ENCODING).replace('\n', '\n\t')))
- fp.write("\n")
- class Format(object):
- @staticmethod
- def humanize(values, chain=" or "):
- return chain.join(values)
- # Get methods
- @staticmethod
- def getDbms(versions=None):
- """
- Format the back-end DBMS fingerprint value and return its
- values formatted as a human readable string.
- @return: detected back-end DBMS based upon fingerprint techniques.
- @rtype: C{str}
- """
- if versions is None and Backend.getVersionList():
- versions = Backend.getVersionList()
- return Backend.getDbms() if versions is None else "%s %s" % (Backend.getDbms(), " and ".join(v for v in versions))
- @staticmethod
- def getErrorParsedDBMSes():
- """
- Parses the knowledge base htmlFp list and return its values
- formatted as a human readable string.
- @return: list of possible back-end DBMS based upon error messages
- parsing.
- @rtype: C{str}
- """
- htmlParsed = None
- if len(kb.htmlFp) == 0 or kb.heuristicTest != HEURISTIC_TEST.POSITIVE:
- pass
- elif len(kb.htmlFp) == 1:
- htmlParsed = kb.htmlFp[0]
- elif len(kb.htmlFp) > 1:
- htmlParsed = " or ".join(kb.htmlFp)
- return htmlParsed
- @staticmethod
- def getOs(target, info):
- """
- Formats the back-end operating system fingerprint value
- and return its values formatted as a human readable string.
- Example of info (kb.headersFp) dictionary:
- {
- 'distrib': set(['Ubuntu']),
- 'type': set(['Linux']),
- 'technology': set(['PHP 5.2.6', 'Apache 2.2.9']),
- 'release': set(['8.10'])
- }
- Example of info (kb.bannerFp) dictionary:
- {
- 'sp': set(['Service Pack 4']),
- 'dbmsVersion': '8.00.194',
- 'dbmsServicePack': '0',
- 'distrib': set(['2000']),
- 'dbmsRelease': '2000',
- 'type': set(['Windows'])
- }
- @return: detected back-end operating system based upon fingerprint
- techniques.
- @rtype: C{str}
- """
- infoStr = ""
- if info and "type" in info:
- infoStr += "%s operating system: %s" % (target, Format.humanize(info["type"]))
- if "distrib" in info:
- infoStr += " %s" % Format.humanize(info["distrib"])
- if "release" in info:
- infoStr += " %s" % Format.humanize(info["release"])
- if "sp" in info:
- infoStr += " %s" % Format.humanize(info["sp"])
- if "codename" in info:
- infoStr += " (%s)" % Format.humanize(info["codename"])
- if "technology" in info:
- infoStr += "\nweb application technology: %s" % Format.humanize(info["technology"], ", ")
- return infoStr.lstrip()
- class Backend:
- # Set methods
- @staticmethod
- def setDbms(dbms):
- dbms = aliasToDbmsEnum(dbms)
- if dbms is None:
- return None
- # Little precaution, in theory this condition should always be false
- elif kb.dbms is not None and kb.dbms != dbms:
- msg = "sqlmap previously fingerprinted back-end DBMS "
- msg += "%s. However now it has been fingerprinted " % kb.dbms
- msg += "to be %s. " % dbms
- msg += "Please, specify which DBMS is "
- msg += "correct [%s (default)/%s] " % (kb.dbms, dbms)
- while True:
- _ = readInput(msg, default=kb.dbms)
- if aliasToDbmsEnum(_) == kb.dbms:
- break
- elif aliasToDbmsEnum(_) == dbms:
- kb.dbms = aliasToDbmsEnum(_)
- break
- else:
- warnMsg = "invalid value"
- logger.warn(warnMsg)
- elif kb.dbms is None:
- kb.dbms = aliasToDbmsEnum(dbms)
- return kb.dbms
- @staticmethod
- def setVersion(version):
- if isinstance(version, basestring):
- kb.dbmsVersion = [version]
- return kb.dbmsVersion
- @staticmethod
- def setVersionList(versionsList):
- if isinstance(versionsList, list):
- kb.dbmsVersion = versionsList
- elif isinstance(versionsList, basestring):
- Backend.setVersion(versionsList)
- else:
- logger.error("invalid format of versionsList")
- @staticmethod
- def forceDbms(dbms, sticky=False):
- if not kb.stickyDBMS:
- kb.forcedDbms = aliasToDbmsEnum(dbms)
- kb.stickyDBMS = sticky
- @staticmethod
- def flushForcedDbms(force=False):
- if not kb.stickyDBMS or force:
- kb.forcedDbms = None
- kb.stickyDBMS = False
- @staticmethod
- def setOs(os):
- if os is None:
- return None
- # Little precaution, in theory this condition should always be false
- elif kb.os is not None and isinstance(os, basestring) and kb.os.lower() != os.lower():
- msg = "sqlmap previously fingerprinted back-end DBMS "
- msg += "operating system %s. However now it has " % kb.os
- msg += "been fingerprinted to be %s. " % os
- msg += "Please, specify which OS is "
- msg += "correct [%s (default)/%s] " % (kb.os, os)
- while True:
- _ = readInput(msg, default=kb.os)
- if _ == kb.os:
- break
- elif _ == os:
- kb.os = _.capitalize()
- break
- else:
- warnMsg = "invalid value"
- logger.warn(warnMsg)
- elif kb.os is None and isinstance(os, basestring):
- kb.os = os.capitalize()
- return kb.os
- @staticmethod
- def setOsVersion(version):
- if version is None:
- return None
- elif kb.osVersion is None and isinstance(version, basestring):
- kb.osVersion = version
- @staticmethod
- def setOsServicePack(sp):
- if sp is None:
- return None
- elif kb.osSP is None and isinstance(sp, int):
- kb.osSP = sp
- @staticmethod
- def setArch():
- msg = "what is the back-end database management system architecture?"
- msg += "\n[1] 32-bit (default)"
- msg += "\n[2] 64-bit"
- while True:
- _ = readInput(msg, default='1')
- if isinstance(_, basestring) and _.isdigit() and int(_) in (1, 2):
- kb.arch = 32 if int(_) == 1 else 64
- break
- else:
- warnMsg = "invalid value. Valid values are 1 and 2"
- logger.warn(warnMsg)
- return kb.arch
- # Get methods
- @staticmethod
- def getForcedDbms():
- return aliasToDbmsEnum(kb.get("forcedDbms"))
- @staticmethod
- def getDbms():
- return aliasToDbmsEnum(kb.get("dbms"))
- @staticmethod
- def getErrorParsedDBMSes():
- """
- Returns array with parsed DBMS names till now
- This functions is called to:
- 1. Sort the tests, getSortedInjectionTests() - detection phase.
- 2. Ask user whether or not skip specific DBMS tests in detection phase,
- lib/controller/checks.py - detection phase.
- 3. Sort the fingerprint of the DBMS, lib/controller/handler.py -
- fingerprint phase.
- """
- return kb.htmlFp if kb.get("heuristicTest") == HEURISTIC_TEST.POSITIVE else []
- @staticmethod
- def getIdentifiedDbms():
- dbms = None
- if not kb:
- pass
- elif Backend.getForcedDbms() is not None:
- dbms = Backend.getForcedDbms()
- elif Backend.getDbms() is not None:
- dbms = kb.dbms
- elif conf.get("dbms"):
- dbms = conf.dbms
- elif Backend.getErrorParsedDBMSes():
- dbms = unArrayizeValue(Backend.getErrorParsedDBMSes())
- elif kb.get("injection") and kb.injection.dbms:
- dbms = unArrayizeValue(kb.injection.dbms)
- return aliasToDbmsEnum(dbms)
- @staticmethod
- def getVersion():
- if len(kb.dbmsVersion) > 0:
- return kb.dbmsVersion[0]
- else:
- return None
- @staticmethod
- def getVersionList():
- if len(kb.dbmsVersion) > 0:
- return kb.dbmsVersion
- else:
- return None
- @staticmethod
- def getOs():
- return kb.os
- @staticmethod
- def getOsVersion():
- return kb.osVersion
- @staticmethod
- def getOsServicePack():
- return kb.osSP
- @staticmethod
- def getArch():
- if kb.arch is None:
- Backend.setArch()
- return kb.arch
- # Comparison methods
- @staticmethod
- def isDbms(dbms):
- if Backend.getDbms() is not None:
- return Backend.getDbms() == aliasToDbmsEnum(dbms)
- else:
- return Backend.getIdentifiedDbms() == aliasToDbmsEnum(dbms)
- @staticmethod
- def isDbmsWithin(aliases):
- return Backend.getDbms() is not None and Backend.getDbms().lower() in aliases
- @staticmethod
- def isVersion(version):
- return Backend.getVersion() is not None and Backend.getVersion() == version
- @staticmethod
- def isVersionWithin(versionList):
- if Backend.getVersionList() is None:
- return False
- for _ in Backend.getVersionList():
- if _ != UNKNOWN_DBMS_VERSION and _ in versionList:
- return True
- return False
- @staticmethod
- def isVersionGreaterOrEqualThan(version):
- return Backend.getVersion() is not None and str(Backend.getVersion()) >= str(version)
- @staticmethod
- def isOs(os):
- return Backend.getOs() is not None and Backend.getOs().lower() == os.lower()
- def paramToDict(place, parameters=None):
- """
- Split the parameters into names and values, check if these parameters
- are within the testable parameters and return in a dictionary.
- """
- testableParameters = OrderedDict()
- if place in conf.parameters and not parameters:
- parameters = conf.parameters[place]
- parameters = parameters.replace(", ", ",")
- parameters = re.sub(r"&(\w{1,4});", r"%s\g<1>%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), parameters)
- splitParams = parameters.split(conf.pDel or (DEFAULT_COOKIE_DELIMITER if place == PLACE.COOKIE else DEFAULT_GET_POST_DELIMITER))
- for element in splitParams:
- element = re.sub(r"%s(.+?)%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), r"&\g<1>;", element)
- elem = element.split("=")
- if len(elem) >= 2:
- parameter = elem[0].replace(" ", "")
- condition = not conf.testParameter
- condition |= parameter in conf.testParameter
- if condition:
- testableParameters[parameter] = "=".join(elem[1:])
- if not conf.multipleTargets:
- _ = urldecode(testableParameters[parameter], convall=True)
- if _.strip(DUMMY_SQL_INJECTION_CHARS) != _\
- or re.search(r'\A9{3,}', _) or re.search(DUMMY_USER_INJECTION, _):
- warnMsg = "it appears that you have provided tainted parameter values "
- warnMsg += "('%s') with most probably leftover " % element
- warnMsg += "chars/statements from manual SQL injection test(s). "
- warnMsg += "Please, always use only valid parameter values "
- warnMsg += "so sqlmap could be able to properly run "
- logger.warn(warnMsg)
- message = "Are you sure you want to continue? [y/N] "
- test = readInput(message, default="N")
- if test[0] not in ("y", "Y"):
- raise SqlmapSilentQuitException
- if conf.testParameter and not testableParameters:
- paramStr = ", ".join(test for test in conf.testParameter)
- if len(conf.testParameter) > 1:
- warnMsg = "provided parameters '%s' " % paramStr
- warnMsg += "are not inside the %s" % place
- logger.warn(warnMsg)
- else:
- parameter = conf.testParameter[0]
- if not intersect(USER_AGENT_ALIASES + REFERER_ALIASES + HOST_ALIASES, parameter, True):
- warnMsg = "provided parameter '%s' " % paramStr
- warnMsg += "is not inside the %s" % place
- logger.warn(warnMsg)
- elif len(conf.testParameter) != len(testableParameters.keys()):
- for parameter in conf.testParameter:
- if parameter not in testableParameters:
- warnMsg = "provided parameter '%s' " % parameter
- warnMsg += "is not inside the %s" % place
- logger.warn(warnMsg)
- return testableParameters
- def getDocRoot():
- docRoot = None
- pagePath = directoryPath(conf.path)
- defaultDocRoot = ("C:/xampp/htdocs/", "C:/Inetpub/wwwroot/") if Backend.isOs(OS.WINDOWS) else ("/var/www/",)
- if kb.absFilePaths:
- for absFilePath in kb.absFilePaths:
- if docRoot:
- break
- if directoryPath(absFilePath) == '/':
- continue
- absFilePath = normalizePath(absFilePath)
- windowsDriveLetter = None
- if isWindowsDriveLetterPath(absFilePath):
- windowsDriveLetter, absFilePath = absFilePath[:2], absFilePath[2:]
- absFilePath = ntToPosixSlashes(posixToNtSlashes(absFilePath))
- if any("/%s/" % _ in absFilePath for _ in GENERIC_DOC_ROOT_DIRECTORY_NAMES):
- for _ in GENERIC_DOC_ROOT_DIRECTORY_NAMES:
- _ = "/%s/" % _
- if _ in absFilePath:
- docRoot = "%s%s" % (absFilePath.split(_)[0], _)
- break
- elif pagePath in absFilePath:
- docRoot = absFilePath.split(pagePath)[0]
- if windowsDriveLetter:
- docRoot = "%s/%s" % (windowsDriveLetter, ntToPosixSlashes(docRoot))
- docRoot = normalizePath(docRoot)
- if docRoot:
- infoMsg = "retrieved the web server document root: '%s'" % docRoot
- logger.info(infoMsg)
- else:
- warnMsg = "unable to retrieve the web server document root"
- logger.warn(warnMsg)
- message = "please provide the web server document root "
- message += "[%s]: " % ",".join(root for root in defaultDocRoot)
- inputDocRoot = readInput(message, default=defaultDocRoot)
- if inputDocRoot:
- if isinstance(inputDocRoot, basestring):
- docRoot = inputDocRoot.split(',')
- else:
- docRoot = inputDocRoot
- else:
- docRoot = defaultDocRoot
- return docRoot
- def getDirs():
- directories = set("/")
- if kb.absFilePaths:
- infoMsg = "retrieved web server full paths: "
- infoMsg += "'%s'" % ", ".join(ntToPosixSlashes(path) for path in kb.absFilePaths)
- logger.info(infoMsg)
- for absFilePath in kb.absFilePaths:
- if absFilePath:
- directory = directoryPath(absFilePath)
- directory = ntToPosixSlashes(directory)
- directories.add(directory)
- else:
- warnMsg = "unable to retrieve any web server path"
- logger.warn(warnMsg)
- webDir = extractRegexResult(r"//[^/]+?/(?P<result>.*)/.", conf.url)
- if webDir:
- directories.add(webDir)
- message = "please provide any additional web server full path to try "
- message += "to upload the agent [Enter for None]: "
- inputDirs = readInput(message)
- if inputDirs:
- inputDirs = inputDirs.replace(", ", ",")
- inputDirs = inputDirs.split(",")
- for inputDir in inputDirs:
- if inputDir:
- directories.add(inputDir)
- return list(directories)
- def filePathToString(filePath):
- strRepl = filePath.replace("/", "_").replace("\\", "_")
- strRepl = strRepl.replace(" ", "_").replace(":", "_")
- return strRepl
- def singleTimeDebugMessage(message):
- singleTimeLogMessage(message, logging.DEBUG)
- def singleTimeWarnMessage(message):
- singleTimeLogMessage(message, logging.WARN)
- def singleTimeLogMessage(message, level=logging.INFO, flag=None):
- if flag is None:
- flag = hash(message)
- if flag not in kb.singleLogFlags:
- kb.singleLogFlags.add(flag)
- logger.log(level, message)
- def boldifyMessage(message):
- retVal = message
- if any(_ in message for _ in BOLD_PATTERNS):
- retVal = setColor(message, True)
- return retVal
- def setColor(message, bold=False):
- retVal = message
- level = extractRegexResult(r"\[(?P<result>[A-Z ]+)\]", message) or kb.get("stickyLevel")
- if message and getattr(LOGGER_HANDLER, "is_tty", False): # colorizing handler
- if bold:
- retVal = colored(message, color=None, on_color=None, attrs=("bold",))
- elif level:
- _ = LOGGER_HANDLER.level_map.get(logging.getLevelName(level))
- if _:
- background, foreground, bold = _
- retVal = colored(message, color=foreground, on_color="on_%s" % background if background else None, attrs=("bold",) if bold else None)
- kb.stickyLevel = level if message and message[-1] != "\n" else None
- return retVal
- def dataToStdout(data, forceOutput=False, bold=False):
- """
- Writes text to the stdout (console) stream
- """
- message = ""
- if not kb.get("threadException"):
- if forceOutput or not getCurrentThreadData().disableStdOut:
- if kb.get("multiThreadMode"):
- logging._acquireLock()
- message = stdoutencode(data)
- sys.stdout.write(setColor(message, bold))
- try:
- sys.stdout.flush()
- except IOError:
- pass
- if kb.get("multiThreadMode"):
- logging._releaseLock()
- kb.prependFlag = len(data) == 1 and data not in ('\n', '\r') or len(data) > 2 and data[0] == '\r' and data[-1] != '\n'
- def dataToTrafficFile(data):
- if not conf.trafficFile:
- return
- conf.trafficFP.write(data)
- conf.trafficFP.flush()
- def dataToDumpFile(dumpFile, data):
- dumpFile.write(data)
- dumpFile.flush()
- def dataToOutFile(filename, data):
- if not data:
- return "No data retrieved"
- retVal = "%s%s%s" % (conf.filePath, os.sep, filePathToString(filename))
- with codecs.open(retVal, "wb") as f:
- f.write(data)
- return retVal
- def strToHex(value):
- """
- Converts string value to it's hexadecimal representation
- """
- return (value if not isinstance(value, unicode) else value.encode(UNICODE_ENCODING)).encode("hex").upper()
- def readInput(message, default=None, checkBatch=True):
- """
- Reads input from terminal
- """
- retVal = None
- kb.stickyLevel = None
- if "\n" in message:
- message += "%s> " % ("\n" if message.count("\n") > 1 else "")
- elif message[-1] == ']':
- message += " "
- if conf.answers:
- for item in conf.answers.split(','):
- question = item.split('=')[0].strip()
- answer = item.split('=')[1] if len(item.split('=')) > 1 else None
- if answer and question.lower() in message.lower():
- retVal = getUnicode(answer, UNICODE_ENCODING)
- infoMsg = "%s%s" % (getUnicode(message), retVal)
- logger.info(infoMsg)
- debugMsg = "used the given answer"
- logger.debug(debugMsg)
- break
- if retVal is None:
- if checkBatch and conf.batch:
- if isListLike(default):
- options = ",".join(getUnicode(opt, UNICODE_ENCODING) for opt in default)
- elif default:
- options = getUnicode(default, UNICODE_ENCODING)
- else:
- options = unicode()
- infoMsg = "%s%s" % (getUnicode(message), options)
- logger.info(infoMsg)
- debugMsg = "used the default behaviour, running in batch mode"
- logger.debug(debugMsg)
- retVal = default
- else:
- logging._acquireLock()
- dataToStdout("\r%s" % message, forceOutput=True, bold=True)
- kb.prependFlag = False
- try:
- retVal = raw_input() or default
- retVal = getUnicode(retVal, system=True) if retVal else retVal
- except:
- time.sleep(0.05) # Reference: http://www.gossamer-threads.com/lists/python/python/781893
- kb.prependFlag = True
- raise SqlmapUserQuitException
- finally:
- logging._releaseLock()
- return retVal
- def randomRange(start=0, stop=1000):
- """
- Returns random integer value in given range
- """
- return int(random.randint(start, stop))
- def randomInt(length=4):
- """
- Returns random integer value with provided number of digits
- """
- return int("".join(random.choice(string.digits if i!=0 else string.digits.replace('0', '')) for i in xrange(0, length)))
- def randomStr(length=4, lowercase=False, alphabet=None):
- """
- Returns random string value with provided number of characters
- """
- if alphabet:
- retVal = "".join(random.choice(alphabet) for _ in xrange(0, length))
- elif lowercase:
- retVal = "".join(random.choice(string.lowercase) for _ in xrange(0, length))
- else:
- retVal = "".join(random.choice(string.letters) for _ in xrange(0, length))
- return retVal
- def sanitizeStr(value):
- """
- Sanitizes string value in respect to newline and line-feed characters
- """
- return getUnicode(value).replace("\n", " ").replace("\r", "")
- def checkFile(filename):
- """
- Checks for file existence
- """
- if not os.path.isfile(filename):
- raise SqlmapFilePathException, "unable to read file '%s'" % filename
- def banner():
- """
- This function prints sqlmap banner with its version
- """
- _ = """\n %s - %s\n %s\n\n""" % (VERSION_STRING, DESCRIPTION, SITE)
- dataToStdout(_, forceOutput=True)
- def parsePasswordHash(password):
- blank = " " * 8
- if not password or password == " ":
- password = NULL
- if Backend.isDbms(DBMS.MSSQL) and password != NULL and isHexEncodedString(password):
- hexPassword = password
- password = "%s\n" % hexPassword
- password += "%sheader: %s\n" % (blank, hexPassword[:6])
- password += "%ssalt: %s\n" % (blank, hexPassword[6:14])
- password += "%smixedcase: %s\n" % (blank, hexPassword[14:54])
- if not Backend.isVersionWithin(("2005", "2008")):
- password += "%suppercase: %s" % (blank, hexPassword[54:])
- return password
- def cleanQuery(query):
- retVal = query
- for sqlStatements in SQL_STATEMENTS.values():
- for sqlStatement in sqlStatements:
- sqlStatementEsc = sqlStatement.replace("(", "\\(")
- queryMatch = re.search("(%s)" % sqlStatementEsc, query, re.I)
- if queryMatch and "sys_exec" not in query:
- retVal = retVal.replace(queryMatch.group(1), sqlStatement.upper())
- return retVal
- def setPaths():
- """
- Sets absolute paths for project directories and files
- """
- # sqlmap paths
- paths.SQLMAP_EXTRAS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "extra")
- paths.SQLMAP_PROCS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "procs")
- paths.SQLMAP_SHELL_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "shell")
- paths.SQLMAP_TAMPER_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "tamper")
- paths.SQLMAP_TXT_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "txt")
- paths.SQLMAP_UDF_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "udf")
- paths.SQLMAP_XML_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "xml")
- paths.SQLMAP_XML_BANNER_PATH = os.path.join(paths.SQLMAP_XML_PATH, "banner")
- paths.SQLMAP_OUTPUT_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "output")
- paths.SQLMAP_DUMP_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "dump")
- paths.SQLMAP_FILES_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "files")
- paths.SQLMAP_SEXEC_PATH = os.path.join(paths.SQLMAP_EXTRAS_PATH, "shellcodeexec")
- # sqlmap files
- paths.SQLMAP_HISTORY = os.path.join(paths.SQLMAP_ROOT_PATH, ".sqlmap_history")
- paths.SQLMAP_CONFIG = os.path.join(paths.SQLMAP_ROOT_PATH, "sqlmap-%s.conf" % randomStr())
- paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.txt")
- paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt")
- paths.COMMON_OUTPUTS = os.path.join(paths.SQLMAP_TXT_PATH, 'common-outputs.txt')
- paths.SQL_KEYWORDS = os.path.join(paths.SQLMAP_TXT_PATH, "keywords.txt")
- paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt")
- paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt")
- paths.WORDLIST = os.path.join(paths.SQLMAP_TXT_PATH, "wordlist.zip")
- paths.PHPIDS_RULES_XML = os.path.join(paths.SQLMAP_XML_PATH, "phpids_rules.xml")
- paths.ERRORS_XML = os.path.join(paths.SQLMAP_XML_PATH, "errors.xml")
- paths.PAYLOADS_XML = os.path.join(paths.SQLMAP_XML_PATH, "payloads.xml")
- paths.INJECTIONS_XML = os.path.join(paths.SQLMAP_XML_PATH, "injections.xml")
- paths.LIVE_TESTS_XML = os.path.join(paths.SQLMAP_XML_PATH, "livetests.xml")
- paths.QUERIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "queries.xml")
- paths.GENERIC_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "generic.xml")
- paths.MSSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mssql.xml")
- paths.MYSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mysql.xml")
- paths.ORACLE_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "oracle.xml")
- paths.PGSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "postgresql.xml")
- def weAreFrozen():
- """
- Returns whether we are frozen via py2exe.
- This will affect how we find out where we are located.
- Reference: http://www.py2exe.org/index.cgi/WhereAmI
- """
- return hasattr(sys, "frozen")
- def parseTargetDirect():
- """
- Parse target dbms and set some attributes into the configuration singleton.
- """
- if not conf.direct:
- return
- details = None
- remote = False
- for dbms in SUPPORTED_DBMS:
- details = re.search("^(?P<dbms>%s)://(?P<credentials>(?P<user>.+?)\:(?P<pass>.*)\@)?(?P<remote>(?P<hostname>.+?)\:(?P<port>[\d]+)\/)?(?P<db>[\w\d\ \:\.\_\-\/\\\\]+?)$" % dbms, conf.direct, re.I)
- if details:
- conf.dbms = details.group('dbms')
- if details.group('credentials'):
- conf.dbmsUser = details.group('user')
- conf.dbmsPass = details.group('pass')
- else:
- conf.dbmsUser = unicode()
- conf.dbmsPass = unicode()
- if not conf.dbmsPass:
- conf.dbmsPass = None
- if details.group('remote'):
- remote = True
- conf.hostname = details.group('hostname')
- conf.port = int(details.group('port'))
- else:
- conf.hostname = "localhost"
- conf.port = 0
- conf.dbmsDb = details.group('db')
- conf.parameters[None] = "direct connection"
- break
- if not details:
- errMsg = "invalid target details, valid syntax is for instance "
- errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' "
- errMsg += "or 'access://DATABASE_FILEPATH'"
- raise SqlmapSyntaxException, errMsg
- for dbmsName, data in DBMS_DICT.items():
- if conf.dbms in data[0]:
- try:
- if dbmsName in (DBMS.ACCESS, DBMS.SQLITE, DBMS.FIREBIRD):
- if remote:
- warnMsg = "direct connection over the network for "
- warnMsg += "%s DBMS is not supported" % dbmsName
- logger.warn(warnMsg)
- conf.hostname = "localhost"
- conf.port = 0
- elif not remote:
- errMsg = "missing remote connection details"
- raise SqlmapSyntaxException, errMsg
- if dbmsName in (DBMS.MSSQL, DBMS.SYBASE):
- import _mssql
- import pymssql
- if not hasattr(pymssql, "__version__") or pymssql.__version__ < "1.0.2":
- errMsg = "'%s' third-party library must be " % data[1]
- errMsg += "version >= 1.0.2 to work properly. "
- errMsg += "Download from '%s'" % data[2]
- raise SqlmapMissingDependence, errMsg
- elif dbmsName == DBMS.MYSQL:
- import pymysql
- elif dbmsName == DBMS.PGSQL:
- import psycopg2
- elif dbmsName == DBMS.ORACLE:
- import cx_Oracle
- elif dbmsName == DBMS.SQLITE:
- import sqlite3
- elif dbmsName == DBMS.ACCESS:
- import pyodbc
- elif dbmsName == DBMS.FIREBIRD:
- import kinterbasdb
- except ImportError:
- errMsg = "sqlmap requires '%s' third-party library " % data[1]
- errMsg += "in order to directly connect to the database "
- errMsg += "%s. Download from '%s'" % (dbmsName, data[2])
- raise SqlmapMissingDependence, errMsg
- def parseTargetUrl():
- """
- Parse target url and set some attributes into the configuration singleton.
- """
- if not conf.url:
- return
- originalUrl = conf.url
- if re.search("\[.+\]", conf.url) and not socket.has_ipv6:
- errMsg = "IPv6 addressing is not supported "
- errMsg += "on this platform"
- raise SqlmapGenericException, errMsg
- if not re.search("^http[s]*://", conf.url, re.I):
- if ":443/" in conf.url:
- conf.url = "https://" + conf.url
- else:
- conf.url = "http://" + conf.url
- if CUSTOM_INJECTION_MARK_CHAR in conf.url:
- conf.url = conf.url.replace('?', URI_QUESTION_MARKER)
- urlSplit = urlparse.urlsplit(conf.url)
- hostnamePort = urlSplit[1].split(":") if not re.search("\[.+\]", urlSplit[1]) else filter(None, (re.search("\[.+\]", urlSplit[1]).group(0), re.search("\](:(?P<port>\d+))?", urlSplit[1]).group("port")))
- conf.scheme = urlSplit[0].strip().lower() if not conf.forceSSL else "https"
- conf.path = urlSplit[2].strip()
- conf.hostname = hostnamePort[0].strip()
- conf.ipv6 = conf.hostname != conf.hostname.strip("[]")
- conf.hostname = conf.hostname.strip("[]")
- try:
- _ = conf.hostname.encode("idna")
- except UnicodeError:
- _ = None
- if any((_ is None, re.search(r'\s', conf.hostname), '..' in conf.hostname, conf.hostname.startswith('.'))):
- errMsg = "invalid target url"
- raise SqlmapSyntaxException, errMsg
- if len(hostnamePort) == 2:
- try:
- conf.port = int(hostnamePort[1])
- except:
- errMsg = "invalid target url"
- raise SqlmapSyntaxException, errMsg
- elif conf.scheme == "https":
- conf.port = 443
- else:
- conf.port = 80
- if urlSplit[3]:
- conf.parameters[PLACE.GET] = urldecode(urlSplit[3]) if urlSplit[3] and urlencode(DEFAULT_GET_POST_DELIMITER, None) not in urlSplit[3] else urlSplit[3]
- conf.url = getUnicode("%s://%s:%d%s" % (conf.scheme, ("[%s]" % conf.hostname) if conf.ipv6 else conf.hostname, conf.port, conf.path))
- conf.url = conf.url.replace(URI_QUESTION_MARKER, '?')
- if not conf.referer and intersect(REFERER_ALIASES, conf.testParameter, True):
- debugMsg = "setting the HTTP Referer header to the target url"
- logger.debug(debugMsg)
- conf.httpHeaders = filter(lambda (key, value): key != HTTPHEADER.REFERER, conf.httpHeaders)
- conf.httpHeaders.append((HTTPHEADER.REFERER, conf.url))
- if not conf.host and intersect(HOST_ALIASES, conf.testParameter, True):
- debugMsg = "setting the HTTP Host header to the target url"
- logger.debug(debugMsg)
- conf.httpHeaders = filter(lambda (key, value): key != HTTPHEADER.HOST, conf.httpHeaders)
- conf.httpHeaders.append((HTTPHEADER.HOST, getHostHeader(conf.url)))
- if originalUrl != conf.url:
- kb.originalUrls[conf.url] = originalUrl
- def expandAsteriskForColumns(expression):
- """
- If the user provided an asterisk rather than the column(s)
- name, sqlmap will retrieve the columns itself and reprocess
- the SQL query string (expression)
- """
- asterisk = re.search("^SELECT\s+\*\s+FROM\s+([\w\.\_]+)\s*", expression, re.I)
- if asterisk:
- infoMsg = "you did not provide the fields in your query. "
- infoMsg += "sqlmap will retrieve the column names itself"
- logger.info(infoMsg)
- dbTbl = asterisk.group(1)
- if dbTbl and ".." in dbTbl:
- dbTbl = dbTbl.replace('..', '.dbo.')
- if dbTbl and "." in dbTbl:
- conf.db, conf.tbl = dbTbl.split(".", 1)
- else:
- conf.tbl = dbTbl
- columnsDict = conf.dbmsHandler.getColumns(onlyColNames=True)
- if columnsDict and conf.db in columnsDict and conf.tbl in columnsDict[conf.db]:
- columns = columnsDict[conf.db][conf.tbl].keys()
- columns.sort()
- columnsStr = ", ".join(column for column in columns)
- expression = expression.replace("*", columnsStr, 1)
- infoMsg = "the query with column names is: "
- infoMsg += "%s" % expression
- logger.info(infoMsg)
- return expression
- def getLimitRange(count, dump=False, plusOne=False):
- """
- Returns range of values used in limit/offset constructs
- """
- retVal = None
- count = int(count)
- limitStart, limitStop = 1, count
- if dump:
- if isinstance(conf.limitStop, int) and conf.limitStop > 0 and conf.limitStop < limitStop:
- limitStop = conf.limitStop
- if isinstance(conf.limitStart, int) and conf.limitStart > 0 and conf.limitStart <= limitStop:
- limitStart = conf.limitStart
- retVal = xrange(limitStart, limitStop + 1) if plusOne else xrange(limitStart - 1, limitStop)
- return retVal
- def parseUnionPage(page):
- """
- Returns resulting items from UNION query inside provided page content
- """
- if page is None:
- return None
- if page.startswith(kb.chars.start) and page.endswith(kb.chars.stop):
- if len(page) > LARGE_OUTPUT_THRESHOLD:
- warnMsg = "large output detected. This might take a while"
- logger.warn(warnMsg)
- data = BigArray()
- keys = set()
- for match in re.finditer("%s(.*?)%s" % (kb.chars.start, kb.chars.stop), page, re.DOTALL | re.IGNORECASE):
- entry = match.group(1)
- if kb.chars.start in entry:
- entry = entry.split(kb.chars.start)[-1]
- if kb.unionDuplicates:
- key = entry.lower()
- if key not in keys:
- keys.add(key)
- else:
- continue
- entry = entry.split(kb.chars.delimiter)
- if conf.hexConvert:
- entry = applyFunctionRecursively(entry, decodeHexValue)
- if kb.safeCharEncode:
- entry = applyFunctionRecursively(entry, safecharencode)
- data.append(entry[0] if len(entry) == 1 else entry)
- else:
- data = page
- if len(data) == 1 and isinstance(data[0], basestring):
- data = data[0]
- return data
- def parseFilePaths(page):
- """
- Detects (possible) absolute system paths inside the provided page content
- """
- if page:
- for regex in (r" in <b>(?P<result>.*?)</b> on line", r"(?:>|\s)(?P<result>[A-Za-z]:[\\/][\w.\\/]*)", r"(?:>|\s)(?P<result>/\w[/\w.]+)"):
- for match in re.finditer(regex, page):
- absFilePath = match.group("result").strip()
- page = page.replace(absFilePath, "")
- if isWindowsDriveLetterPath(absFilePath):
- absFilePath = posixToNtSlashes(absFilePath)
- if absFilePath not in kb.absFilePaths:
- kb.absFilePaths.add(absFilePath)
- def getLocalIP():
- retVal = None
- try:
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.connect((conf.hostname, conf.port))
- retVal, _ = s.getsockname()
- s.close()
- except:
- debugMsg = "there was an error in opening socket "
- debugMsg += "connection toward '%s'" % conf.hostname
- logger.debug(debugMsg)
- return retVal
- def getRemoteIP():
- return socket.gethostbyname(conf.hostname)
- def getFileType(filePath):
- try:
- _ = magic.from_file(filePath)
- except:
- return "unknown"
- return "text" if "ASCII" in _ or "text" in _ else "binary"
- def getCharset(charsetType=None):
- asciiTbl = []
- if charsetType is None:
- asciiTbl.extend(xrange(0, 128))
- # 0 or 1
- elif charsetType == CHARSET_TYPE.BINARY:
- asciiTbl.extend([0, 1])
- asciiTbl.extend(xrange(47, 50))
- # Digits
- elif charsetType == CHARSET_TYPE.DIGITS:
- asciiTbl.extend([0, 1])
- asciiTbl.extend(xrange(47, 58))
- # Hexadecimal
- elif charsetType == CHARSET_TYPE.HEXADECIMAL:
- asciiTbl.extend([0, 1])
- asciiTbl.extend(xrange(47, 58))
- asciiTbl.extend(xrange(64, 71))
- asciiTbl.extend([87, 88]) # X
- asciiTbl.extend(xrange(96, 103))
- asciiTbl.extend([119, 120]) # x
- # Characters
- elif charsetType == CHARSET_TYPE.ALPHA:
- asciiTbl.extend([0, 1])
- asciiTbl.extend(xrange(64, 91))
- asciiTbl.extend(xrange(96, 123))
- # Characters and digits
- elif charsetType == CHARSET_TYPE.ALPHANUM:
- asciiTbl.extend([0, 1])
- asciiTbl.extend(xrange(47, 58))
- asciiTbl.extend(xrange(64, 91))
- asciiTbl.extend(xrange(96, 123))
- return asciiTbl
- def searchEnvPath(filename):
- retVal = None
- path = os.environ.get("PATH", "")
- paths = path.split(";") if IS_WIN else path.split(":")
- for _ in paths:
- _ = _.replace(";", "")
- retVal = os.path.exists(os.path.normpath(os.path.join(_, filename)))
- if retVal:
- break
- return retVal
- def directoryPath(filepath):
- """
- Returns directory path for a given filepath
- """
- retVal = filepath
- if filepath:
- retVal = ntpath.dirname(filepath) if isWindowsDriveLetterPath(filepath) else posixpath.dirname(filepath)
- return retVal
- def normalizePath(filepath):
- """
- Returns normalized string representation of a given filepath
- """
- retVal = filepath
- if filepath:
- retVal = ntpath.normpath(filepath) if isWindowsDriveLetterPath(filepath) else posixpath.normpath(filepath)
- return retVal
- def safeStringFormat(format_, params):
- """
- Avoids problems with inappropriate string format strings
- """
- retVal = format_.replace("%d", "%s")
- if isinstance(params, basestring):
- retVal = retVal.replace("%s", params)
- else:
- count, index = 0, 0
- while index != -1:
- index = retVal.find("%s")
- if index != -1:
- if count < len(params):
- retVal = retVal[:index] + getUnicode(params[count]) + retVal[index + 2:]
- else:
- raise SqlmapNoneDataException, "wrong number of parameters during string formatting"
- count += 1
- return retVal
- def getFilteredPageContent(page, onlyText=True):
- """
- Returns filtered page content without script, style and/or comments
- or all HTML tags
- """
- retVal = page
- # only if the page's charset has been successfully identified
- if isinstance(page, unicode):
- retVal = re.sub(r"(?si)<script.+?</script>|<!--.+?-->|<style.+?</style>%s" % (r"|<[^>]+>|\t|\n|\r" if onlyText else ""), " ", page)
- while retVal.find(" ") != -1:
- retVal = retVal.replace(" ", " ")
- retVal = htmlunescape(retVal)
- return retVal
- def getPageWordSet(page):
- """
- Returns word set used in page content
- """
- retVal = set()
- # only if the page's charset has been successfully identified
- if isinstance(page, unicode):
- _ = getFilteredPageContent(page)
- retVal = set(re.findall(r"\w+", _))
- return retVal
- def showStaticWords(firstPage, secondPage):
- infoMsg = "finding static words in longest matching part of dynamic page content"
- logger.info(infoMsg)
- firstPage = getFilteredPageContent(firstPage)
- secondPage = getFilteredPageContent(secondPage)
- infoMsg = "static words: "
- if firstPage and secondPage:
- match = SequenceMatcher(None, firstPage, secondPage).find_longest_match(0, len(firstPage), 0, len(secondPage))
- commonText = firstPage[match[0]:match[0] + match[2]]
- commonWords = getPageWordSet(commonText)
- else:
- commonWords = None
- if commonWords:
- commonWords = list(commonWords)
- commonWords.sort(lambda a, b: cmp(a.lower(), b.lower()))
- for word in commonWords:
- if len(word) > 2:
- infoMsg += "'%s', " % word
- infoMsg = infoMsg.rstrip(", ")
- else:
- infoMsg += "None"
- logger.info(infoMsg)
- def isWindowsPath(filepath):
- """
- Returns True if given filepath is in Windows format
- """
- return re.search("\A[\w]\:\\\\", filepath) is not None
- def isWindowsDriveLetterPath(filepath):
- """
- Returns True if given filepath starts with a Windows drive letter
- """
- return re.search("\A[\w]\:", filepath) is not None
- def posixToNtSlashes(filepath):
- """
- Replaces all occurances of Posix slashes (/) in provided
- filepath with NT ones (/)
- >>> posixToNtSlashes('C:/Windows')
- 'C:\\\\Windows'
- """
- return filepath.replace('/', '\\')
- def ntToPosixSlashes(filepath):
- """
- Replaces all occurances of NT slashes (\) in provided
- filepath with Posix ones (/)
- >>> ntToPosixSlashes('C:\\Windows')
- 'C:/Windows'
- """
- return filepath.replace('\\', '/')
- def isBase64EncodedString(subject):
- """
- Checks if the provided string is Base64 encoded
- >>> isBase64EncodedString('dGVzdA==')
- True
- >>> isBase64EncodedString('123456')
- False
- """
- return re.match(r"\A(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z", subject) is not None
- def isHexEncodedString(subject):
- """
- Checks if the provided string is hex encoded
- >>> isHexEncodedString('DEADBEEF')
- True
- >>> isHexEncodedString('test')
- False
- """
- return re.match(r"\A[0-9a-fA-Fx]+\Z", subject) is not None
- def getConsoleWidth(default=80):
- """
- Returns console width
- """
- width = None
- if os.getenv("COLUMNS", "").isdigit():
- width = int(os.getenv("COLUMNS"))
- else:
- output=execute('stty size', shell=True, stdout=PIPE, stderr=PIPE).stdout.read()
- items = output.split()
- if len(items) == 2 and items[1].isdigit():
- width = int(items[1])
- if width is None:
- try:
- import curses
- stdscr = curses.initscr()
- _, width = stdscr.getmaxyx()
- curses.endwin()
- except:
- pass
- return width or default
- def clearConsoleLine(forceOutput=False):
- """
- Clears current console line
- """
- if getattr(LOGGER_HANDLER, "is_tty", False):
- dataToStdout("\r%s\r" % (" " * (getConsoleWidth() - 1)), forceOutput)
- kb.prependFlag = False
- kb.stickyLevel = None
- def parseXmlFile(xmlFile, handler):
- """
- Parses XML file by a give…
Large files files are truncated, but you can click here to view the full file