sqlmap/lib/core/common.py

1407 lines
42 KiB
Python
Raw Normal View History

2008-10-15 19:38:22 +04:00
#!/usr/bin/env python
"""
2008-10-15 19:56:32 +04:00
$Id$
2008-10-15 19:38:22 +04:00
This file is part of the sqlmap project, http://sqlmap.sourceforge.net.
2010-03-03 18:26:27 +03:00
Copyright (c) 2007-2010 Bernardo Damele A. G. <bernardo.damele@gmail.com>
Copyright (c) 2006 Daniele Bellucci <daniele.bellucci@gmail.com>
2008-10-15 19:38:22 +04:00
sqlmap 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.
sqlmap 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 sqlmap; if not, write to the Free Software Foundation, Inc., 51
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
import codecs
import inspect
2008-10-15 19:38:22 +04:00
import os
import random
import re
import socket
2008-10-15 19:38:22 +04:00
import string
import sys
import time
import urlparse
2010-01-05 14:30:33 +03:00
import ntpath
import posixpath
import subprocess
2010-01-28 20:07:34 +03:00
2010-05-28 19:57:43 +04:00
from ConfigParser import DEFAULTSECT
from ConfigParser import RawConfigParser
2010-04-22 20:13:22 +04:00
from StringIO import StringIO
2010-05-21 17:03:57 +04:00
from subprocess import PIPE
from subprocess import Popen as execute
2010-01-28 19:50:34 +03:00
from tempfile import NamedTemporaryFile
from tempfile import mkstemp
from xml.etree import ElementTree as ET
2010-04-22 20:13:22 +04:00
from xml.sax import parse
2010-01-24 02:29:34 +03:00
2010-01-28 19:50:34 +03:00
from extra.cloak.cloak import decloak
from lib.contrib import magic
2008-10-15 19:38:22 +04:00
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.data import queries
2008-10-15 19:38:22 +04:00
from lib.core.data import temp
from lib.core.convert import urlencode
2008-10-15 19:38:22 +04:00
from lib.core.exception import sqlmapFilePathException
from lib.core.exception import sqlmapGenericException
2010-01-15 19:06:59 +03:00
from lib.core.exception import sqlmapNoneDataException
from lib.core.exception import sqlmapMissingDependence
from lib.core.exception import sqlmapSyntaxException
2010-05-27 20:45:09 +04:00
from lib.core.optiondict import optDict
2010-03-03 19:19:17 +03:00
from lib.core.settings import DESCRIPTION
from lib.core.settings import IS_WIN
2010-05-21 16:09:31 +04:00
from lib.core.settings import PLATFORM
2010-02-25 20:37:46 +03:00
from lib.core.settings import SITE
from lib.core.settings import SQL_STATEMENTS
from lib.core.settings import SUPPORTED_DBMS
2008-10-15 19:38:22 +04:00
from lib.core.settings import VERSION_STRING
from lib.core.settings import MSSQL_ALIASES
from lib.core.settings import MYSQL_ALIASES
from lib.core.settings import PGSQL_ALIASES
from lib.core.settings import ORACLE_ALIASES
from lib.core.settings import SQLITE_ALIASES
from lib.core.settings import ACCESS_ALIASES
from lib.core.settings import FIREBIRD_ALIASES
2008-10-15 19:38:22 +04:00
2010-09-15 16:51:02 +04:00
class UnicodeRawConfigParser(RawConfigParser):
2010-09-15 16:52:28 +04:00
"""
RawConfigParser with unicode writing support
"""
2010-09-15 16:51:02 +04:00
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).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).replace('\n', '\n\t')))
fp.write("\n")
class DynamicContentItem:
"""
Represents line in content page with dynamic properties (candidate for removal prior detection phase)
"""
def __init__(self, lineNumber, pageTotal, lineContentBefore, lineContentAfter):
self.lineNumber = lineNumber
self.pageTotal = pageTotal
self.lineContentBefore = lineContentBefore
self.lineContentAfter = lineContentAfter
2008-10-15 19:38:22 +04:00
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.
@param place: where sqlmap has to work, can be GET, POST or Cookie.
@type place: C{str}
@param parameters: parameters string in the format for instance
'p1=v1&p2=v2' (GET and POST) or 'p1=v1;p2=v2' (Cookie).
@type parameters: C{str}
@return: the parameters in a dictionary.
@rtype: C{str}
"""
testableParameters = {}
if conf.parameters.has_key(place) and not parameters:
parameters = conf.parameters[place]
if place is not "POSTxml":
parameters = parameters.replace(", ", ",")
2008-10-15 19:38:22 +04:00
if place == "Cookie":
splitParams = parameters.split(";")
else:
splitParams = parameters.split("&")
for element in splitParams:
elem = element.split("=")
2008-10-15 19:38:22 +04:00
if len(elem) == 2:
parameter = elem[0].replace(" ", "")
2008-10-15 19:38:22 +04:00
condition = not conf.testParameter
condition |= parameter in conf.testParameter
if condition:
testableParameters[parameter] = elem[1]
else:
root = ET.XML(parameters)
iterator = root.getiterator()
2008-10-15 19:38:22 +04:00
for child in iterator:
parameter = child.tag
condition = not conf.testParameter
condition |= parameter.split("}")[1] in conf.testParameter
2008-10-15 19:38:22 +04:00
if condition:
testableParameters[parameter] = child.text
2008-10-15 19:38:22 +04:00
if conf.testParameter and not testableParameters:
paramStr = ", ".join(test for test in conf.testParameter)
if len(conf.testParameter) > 1:
warnMsg = "the testable parameters '%s' " % paramStr
warnMsg += "you provided are not into the %s" % place
else:
parameter = conf.testParameter[0]
warnMsg = "the testable parameter '%s' " % paramStr
warnMsg += "you provided is not into the %s" % place
logger.warn(warnMsg)
elif len(conf.testParameter) != len(testableParameters.keys()):
for parameter in conf.testParameter:
if not testableParameters.has_key(parameter):
warnMsg = "the testable parameter '%s' " % parameter
2008-10-15 19:38:22 +04:00
warnMsg += "you provided is not into the %s" % place
logger.warn(warnMsg)
return testableParameters
def formatDBMSfp(versions=None):
2008-10-15 19:38:22 +04:00
"""
This function 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}
"""
2010-08-10 02:13:56 +04:00
while versions and None in versions:
versions.remove(None)
if not versions and kb.dbmsVersion and kb.dbmsVersion[0] != "Unknown" and kb.dbmsVersion[0] != None:
2008-10-15 19:38:22 +04:00
versions = kb.dbmsVersion
if isinstance(versions, basestring):
2008-10-15 19:38:22 +04:00
return "%s %s" % (kb.dbms, versions)
elif isinstance(versions, (list, set, tuple)):
return "%s %s" % (kb.dbms, " and ".join([version for version in versions]))
2008-12-02 02:27:51 +03:00
elif not versions:
warnMsg = "unable to extensively fingerprint the back-end "
warnMsg += "DBMS version"
logger.warn(warnMsg)
return kb.dbms
2008-10-15 19:38:22 +04:00
def formatFingerprintString(values, chain=" or "):
strJoin = "|".join([v for v in values])
return strJoin.replace("|", chain)
def formatFingerprint(target, info):
"""
This function format 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, formatFingerprintString(info["type"]))
if "distrib" in info:
infoStr += " %s" % formatFingerprintString(info["distrib"])
if "release" in info:
infoStr += " %s" % formatFingerprintString(info["release"])
if "sp" in info:
infoStr += " %s" % formatFingerprintString(info["sp"])
if "codename" in info:
infoStr += " (%s)" % formatFingerprintString(info["codename"])
if "technology" in info:
infoStr += "\nweb application technology: %s" % formatFingerprintString(info["technology"], ", ")
return infoStr
2008-10-15 19:38:22 +04:00
def getHtmlErrorFp():
"""
This function 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 = ""
if not kb.htmlFp:
return None
if len(kb.htmlFp) == 1:
htmlVer = kb.htmlFp[0]
htmlParsed = htmlVer
elif len(kb.htmlFp) > 1:
htmlParsed = " or ".join([htmlFp for htmlFp in kb.htmlFp])
2008-10-15 19:38:22 +04:00
return htmlParsed
def getDocRoot(webApi=None):
2008-10-15 19:38:22 +04:00
docRoot = None
2010-01-05 14:30:33 +03:00
pagePath = directoryPath(conf.path)
2008-10-15 19:38:22 +04:00
if kb.os == "Windows":
if webApi == "php":
defaultDocRoot = "C:/xampp/htdocs/"
else:
defaultDocRoot = "C:/Inetpub/wwwroot/"
2008-10-15 19:38:22 +04:00
else:
defaultDocRoot = "/var/www/"
if kb.absFilePaths:
for absFilePath in kb.absFilePaths:
2010-02-09 17:27:41 +03:00
if directoryPath(absFilePath) == '/':
continue
2010-01-05 14:30:33 +03:00
absFilePath = normalizePath(absFilePath)
absFilePathWin = None
if isWindowsPath(absFilePath):
absFilePathWin = posixToNtSlashes(absFilePath)
absFilePath = ntToPosixSlashes(absFilePath[2:])
elif isWindowsDriveLetterPath(absFilePath): # E.g. C:/xampp/htdocs
absFilePath = absFilePath[2:]
if pagePath in absFilePath:
index = absFilePath.index(pagePath)
docRoot = absFilePath[:index]
2010-02-25 19:38:39 +03:00
if len(docRoot) == 0:
docRoot = None
continue
if absFilePathWin:
docRoot = "C:/%s" % ntToPosixSlashes(docRoot)
docRoot = normalizePath(docRoot)
break
2008-10-15 19:38:22 +04:00
if docRoot:
infoMsg = "retrieved the web server document root: '%s'" % docRoot
logger.info(infoMsg)
2008-10-15 19:38:22 +04:00
else:
warnMsg = "unable to retrieve the web server document root"
2008-10-15 19:38:22 +04:00
logger.warn(warnMsg)
message = "please provide the web server document root "
message += "[%s]: " % defaultDocRoot
inputDocRoot = readInput(message, default=defaultDocRoot)
2008-10-15 19:38:22 +04:00
if inputDocRoot:
docRoot = inputDocRoot
else:
docRoot = defaultDocRoot
2008-10-15 19:38:22 +04:00
return docRoot
2008-10-15 19:38:22 +04:00
def getDirs(webApi=None):
2008-10-15 19:38:22 +04:00
directories = set()
if kb.os == "Windows":
if webApi == "php":
defaultDirs = ["C:/xampp/htdocs/"]
else:
defaultDirs = ["C:/Inetpub/wwwroot/"]
else:
2010-02-25 17:51:39 +03:00
defaultDirs = ["/var/www/"]
if kb.absFilePaths:
infoMsg = "retrieved web server full paths: "
infoMsg += "'%s'" % ", ".join(path for path in kb.absFilePaths)
logger.info(infoMsg)
for absFilePath in kb.absFilePaths:
if absFilePath:
directory = directoryPath(absFilePath)
if isWindowsPath(directory):
2010-04-22 20:35:22 +04:00
directory = ntToPosixSlashes(directory)
2010-02-09 17:27:41 +03:00
if directory == '/':
continue
directories.add(directory)
else:
warnMsg = "unable to retrieve any web server path"
logger.warn(warnMsg)
2008-10-15 19:38:22 +04:00
message = "please provide any additional web server full path to try "
2010-02-25 18:16:41 +03:00
message += "to upload the agent [%s]: " % ",".join(directory for directory in defaultDirs)
inputDirs = readInput(message, default=",".join(directory for directory in defaultDirs))
2008-10-15 19:38:22 +04:00
if inputDirs:
inputDirs = inputDirs.replace(", ", ",")
inputDirs = inputDirs.split(",")
2008-10-15 19:38:22 +04:00
for inputDir in inputDirs:
if inputDir:
directories.add(inputDir)
else:
2010-02-25 17:51:39 +03:00
[directories.add(directory) for directory in defaultDirs]
2008-10-15 19:38:22 +04:00
return directories
2008-10-15 19:38:22 +04:00
def filePathToString(filePath):
strRepl = filePath.replace("/", "_").replace("\\", "_")
strRepl = strRepl.replace(" ", "_").replace(":", "_")
2008-10-15 19:38:22 +04:00
return strRepl
2008-10-15 19:38:22 +04:00
def dataToStdout(data):
try:
sys.stdout.write(data)
sys.stdout.flush()
except UnicodeEncodeError:
2010-05-24 15:12:40 +04:00
print data.encode(conf.dataEncoding)
2008-10-15 19:38:22 +04:00
def dataToSessionFile(data):
if not conf.sessionFile:
return
2008-10-15 19:38:22 +04:00
conf.sessionFP.write(data)
conf.sessionFP.flush()
2008-10-15 19:38:22 +04:00
def dataToDumpFile(dumpFile, data):
dumpFile.write(data)
dumpFile.flush()
def dataToOutFile(data):
if not data:
return "No data retrieved"
rFile = filePathToString(conf.rFile)
rFilePath = "%s%s%s" % (conf.filePath, os.sep, rFile)
2010-05-30 18:53:13 +04:00
rFileFP = codecs.open(rFilePath, "wb")
2010-05-30 18:53:13 +04:00
rFileFP.write(data)
rFileFP.flush()
rFileFP.close()
return rFilePath
def strToHex(inpStr):
2008-10-15 19:38:22 +04:00
"""
@param inpStr: inpStr to be converted into its hexadecimal value.
@type inpStr: C{str}
2008-10-15 19:38:22 +04:00
@return: the hexadecimal converted inpStr.
2008-10-15 19:38:22 +04:00
@rtype: C{str}
"""
hexStr = ""
for character in inpStr:
2008-10-15 19:38:22 +04:00
if character == "\n":
character = " "
hexChar = "%2x" % ord(character)
hexChar = hexChar.replace(" ", "0")
hexChar = hexChar.upper()
hexStr += hexChar
return hexStr
2008-10-15 19:38:22 +04:00
def fileToStr(fileName):
"""
@param fileName: file path to read the content and return as a no
NEWLINE string.
@type fileName: C{file.open}
@return: the file content as a string without TAB and NEWLINE.
@rtype: C{str}
"""
2010-05-30 18:53:13 +04:00
filePointer = codecs.open(fileName, "rb")
2008-10-15 19:38:22 +04:00
fileText = filePointer.read()
return fileText.replace(" ", "").replace("\t", "").replace("\r", "").replace("\n", " ")
2008-10-15 19:38:22 +04:00
def fileToHex(fileName):
"""
@param fileName: file path to read the content and return as an
hexadecimal string.
@type fileName: C{file.open}
@return: the file content as an hexadecimal string.
@rtype: C{str}
"""
fileText = fileToStr(fileName)
hexFile = strToHex(fileText)
return hexFile
def readInput(message, default=None):
"""
@param message: message to display on terminal.
@type message: C{str}
@return: a string read from keyboard as input.
@rtype: C{str}
"""
if "\n" in message:
message += "\n> "
2008-10-15 19:38:22 +04:00
if conf.batch and default:
infoMsg = "%s%s" % (message, getUnicode(default))
2008-10-15 19:38:22 +04:00
logger.info(infoMsg)
debugMsg = "used the default behaviour, running in batch mode"
logger.debug(debugMsg)
data = default
else:
data = raw_input(message.encode(sys.stdout.encoding))
2008-10-15 19:38:22 +04:00
if not data:
data = default
2008-10-15 19:38:22 +04:00
return data
2008-10-15 19:38:22 +04:00
def randomRange(start=0, stop=1000):
"""
@param start: starting number.
@type start: C{int}
@param stop: last number.
@type stop: C{int}
@return: a random number within the range.
@rtype: C{int}
"""
return int(random.randint(start, stop))
def randomInt(length=4):
"""
@param length: length of the random string.
@type length: C{int}
@return: a random string of digits.
@rtype: C{str}
"""
return int("".join([random.choice(string.digits) for _ in xrange(0, length)]))
def randomStr(length=4, lowercase=False):
2008-10-15 19:38:22 +04:00
"""
@param length: length of the random string.
@type length: C{int}
@return: a random string of characters.
@rtype: C{str}
"""
if lowercase:
rndStr = "".join([random.choice(string.lowercase) for _ in xrange(0, length)])
else:
rndStr = "".join([random.choice(string.letters) for _ in xrange(0, length)])
return rndStr
2010-09-13 17:31:01 +04:00
def sanitizeStr(inpStr):
2008-10-15 19:38:22 +04:00
"""
@param inpStr: inpStr to sanitize: cast to str datatype and replace
2008-10-15 19:38:22 +04:00
newlines with one space and strip carriage returns.
@type inpStr: C{str}
2008-10-15 19:38:22 +04:00
@return: sanitized inpStr
2008-10-15 19:38:22 +04:00
@rtype: C{str}
"""
cleanString = getUnicode(inpStr)
2008-10-15 19:38:22 +04:00
cleanString = cleanString.replace("\n", " ").replace("\r", "")
return cleanString
def checkFile(filename):
"""
@param filename: filename to check if it exists.
@type filename: C{str}
"""
if not os.path.exists(filename):
raise sqlmapFilePathException, "unable to read file '%s'" % filename
def replaceNewlineTabs(inpStr, stdout=False):
if stdout:
replacedString = inpStr.replace("\n", " ").replace("\t", " ")
else:
replacedString = inpStr.replace("\n", "__NEWLINE__").replace("\t", "__TAB__")
2008-10-15 19:38:22 +04:00
replacedString = replacedString.replace(temp.delimiter, "__DEL__")
return replacedString
def banner():
"""
This function prints sqlmap banner with its version
"""
print """
2010-03-03 19:19:17 +03:00
%s - %s
%s
2010-03-03 19:19:17 +03:00
""" % (VERSION_STRING, DESCRIPTION, SITE)
2010-09-13 17:31:01 +04:00
2008-10-15 19:38:22 +04:00
def parsePasswordHash(password):
blank = " " * 8
if not password or password == " ":
password = "NULL"
if kb.dbms == "Microsoft SQL Server" and password != "NULL":
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 kb.dbmsVersion[0] not in ( "2005", "2008" ):
password += "%suppercase: %s" % (blank, hexPassword[54:])
return password
2008-10-15 19:38:22 +04:00
def cleanQuery(query):
upperQuery = query
for sqlStatements in SQL_STATEMENTS.values():
for sqlStatement in sqlStatements:
sqlStatementEsc = sqlStatement.replace("(", "\\(")
queryMatch = re.search("(%s)" % sqlStatementEsc, query, re.I)
if queryMatch:
upperQuery = upperQuery.replace(queryMatch.group(1), sqlStatement.upper())
2008-10-15 19:38:22 +04:00
return upperQuery
2010-09-13 17:31:01 +04:00
2008-10-15 19:38:22 +04:00
def setPaths():
# sqlmap paths
paths.SQLMAP_CONTRIB_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "lib", "contrib")
paths.SQLMAP_EXTRAS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "extra")
paths.SQLMAP_SHELL_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "shell")
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")
2008-10-15 19:38:22 +04:00
# 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.FUZZ_VECTORS = os.path.join(paths.SQLMAP_TXT_PATH, "fuzz_vectors.txt")
2010-01-28 03:25:36 +03:00
paths.DETECTION_RULES_XML = os.path.join(paths.SQLMAP_XML_PATH, "detection.xml")
paths.ERRORS_XML = os.path.join(paths.SQLMAP_XML_PATH, "errors.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")
2010-09-13 17:31:01 +04:00
2008-10-15 19:38:22 +04:00
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)
2010-09-13 17:31:01 +04:00
if details:
conf.dbms = details.group('dbms')
2010-03-30 15:21:26 +04:00
if details.group('credentials'):
conf.dbmsUser = details.group('user')
conf.dbmsPass = details.group('pass')
2010-03-30 15:06:30 +04:00
else:
2010-05-28 17:05:02 +04:00
conf.dbmsUser = unicode()
conf.dbmsPass = unicode()
2010-04-29 17:34:03 +04:00
if not conf.dbmsPass:
conf.dbmsPass = None
2010-03-30 15:21:26 +04:00
if details.group('remote'):
remote = True
2010-03-30 15:21:26 +04:00
conf.hostname = details.group('hostname')
conf.port = int(details.group('port'))
2010-03-30 15:21:26 +04:00
else:
2010-03-30 15:06:30 +04:00
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
dbmsDict = { "Microsoft SQL Server": [MSSQL_ALIASES, "python-pymssql", "http://pymssql.sourceforge.net/"],
"MySQL": [MYSQL_ALIASES, "python-mysqldb", "http://mysql-python.sourceforge.net/"],
"PostgreSQL": [PGSQL_ALIASES, "python-psycopg2", "http://initd.org/psycopg/"],
"Oracle": [ORACLE_ALIASES, "python cx_Oracle", "http://cx-oracle.sourceforge.net/"],
"SQLite": [SQLITE_ALIASES, "python-pysqlite2", "http://pysqlite.googlecode.com/"],
"Access": [ACCESS_ALIASES, "python-pyodbc", "http://pyodbc.googlecode.com/"],
"Firebird": [FIREBIRD_ALIASES, "python-kinterbasdb", "http://kinterbasdb.sourceforge.net/"] }
for dbmsName, data in dbmsDict.items():
if conf.dbms in data[0]:
try:
if dbmsName in ('Access', 'SQLite', 'Firebird'):
if remote:
2010-04-13 15:13:01 +04:00
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 == "Microsoft SQL Server":
import _mssql
import pymssql
2010-03-31 19:31:11 +04:00
if not hasattr(pymssql, "__version__") or pymssql.__version__ < "1.0.2":
errMsg = "pymssql library on your system must be "
errMsg += "version 1.0.2 to work, get it from "
errMsg += "http://sourceforge.net/projects/pymssql/files/pymssql/1.0.2/"
raise sqlmapMissingDependence, errMsg
elif dbmsName == "MySQL":
import MySQLdb
elif dbmsName == "PostgreSQL":
import psycopg2
elif dbmsName == "Oracle":
import cx_Oracle
elif dbmsName == "SQLite":
import sqlite3
elif dbmsName == "Access":
import pyodbc
elif dbmsName == "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
2008-10-15 19:38:22 +04:00
def parseTargetUrl():
"""
Parse target url and set some attributes into the configuration singleton.
2008-10-15 19:38:22 +04:00
"""
if not conf.url:
return
if not re.search("^http[s]*://", conf.url):
if ":443/" in conf.url:
conf.url = "https://" + conf.url
else:
conf.url = "http://" + conf.url
__urlSplit = urlparse.urlsplit(conf.url)
__hostnamePort = __urlSplit[1].split(":")
conf.scheme = __urlSplit[0]
conf.path = __urlSplit[2]
conf.hostname = __hostnamePort[0]
if len(__hostnamePort) == 2:
try:
conf.port = int(__hostnamePort[1])
except:
errMsg = "invalid target url"
raise sqlmapSyntaxException, errMsg
2008-10-15 19:38:22 +04:00
elif conf.scheme == "https":
conf.port = 443
else:
conf.port = 80
if __urlSplit[3]:
conf.parameters["GET"] = __urlSplit[3]
2008-10-15 19:38:22 +04:00
conf.url = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, conf.path)
2008-10-15 19:38:22 +04:00
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)
2008-10-15 19:38:22 +04:00
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:
conf.db, conf.tbl = dbTbl.split(".")
else:
conf.tbl = dbTbl
2008-10-15 19:38:22 +04:00
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
2010-01-09 02:50:06 +03:00
def getRange(count, dump=False, plusOne=False):
2008-10-15 19:38:22 +04:00
count = int(count)
indexRange = None
limitStart = 1
limitStop = count
if dump:
if isinstance(conf.limitStop, int) and conf.limitStop > 0 and conf.limitStop < limitStop:
2008-10-15 19:38:22 +04:00
limitStop = conf.limitStop
if isinstance(conf.limitStart, int) and conf.limitStart > 0 and conf.limitStart <= limitStop:
2008-10-15 19:38:22 +04:00
limitStart = conf.limitStart
2010-01-09 02:50:06 +03:00
if plusOne:
2008-10-15 19:38:22 +04:00
indexRange = range(limitStart, limitStop + 1)
else:
indexRange = range(limitStart - 1, limitStop)
return indexRange
def parseUnionPage(output, expression, partial=False, condition=None, sort=True):
data = []
outCond1 = ( output.startswith(temp.start) and output.endswith(temp.stop) )
outCond2 = ( output.startswith("__START__") and output.endswith("__STOP__") )
if outCond1 or outCond2:
if outCond1:
regExpr = '%s(.*?)%s' % (temp.start, temp.stop)
elif outCond2:
regExpr = '__START__(.*?)__STOP__'
output = re.findall(regExpr, output, re.S)
if condition is None:
condition = (
kb.resumedQueries and conf.url in kb.resumedQueries.keys()
and expression in kb.resumedQueries[conf.url].keys()
)
if partial or not condition:
logOutput = "".join(["__START__%s__STOP__" % replaceNewlineTabs(value) for value in output])
dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression, logOutput))
if sort:
output = set(output)
for entry in output:
info = []
if "__DEL__" in entry:
entry = entry.split("__DEL__")
else:
entry = entry.split(temp.delimiter)
if len(entry) == 1:
data.append(entry[0])
else:
for value in entry:
info.append(value)
data.append(info)
else:
data = output
if len(data) == 1 and isinstance(data[0], basestring):
data = data[0]
return data
def getDelayQuery(andCond=False):
query = None
if kb.dbms in ("MySQL", "PostgreSQL"):
if not kb.data.banner:
conf.dbmsHandler.getVersionFromBanner()
banVer = kb.bannerFp["dbmsVersion"]
if (kb.dbms == "MySQL" and banVer >= "5.0.12") or (kb.dbms == "PostgreSQL" and banVer >= "8.2"):
query = queries[kb.dbms].timedelay % conf.timeSec
else:
query = queries[kb.dbms].timedelay2 % conf.timeSec
2010-03-21 03:39:44 +03:00
elif kb.dbms == "Firebird":
query = queries[kb.dbms].timedelay
else:
query = queries[kb.dbms].timedelay % conf.timeSec
if andCond:
if kb.dbms in ( "MySQL", "SQLite" ):
query = query.replace("SELECT ", "")
2010-03-21 03:39:44 +03:00
elif kb.dbms == "Firebird":
query = "(%s)>0" % query
return query
def getLocalIP():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((conf.hostname, conf.port))
ip, _ = s.getsockname()
s.close()
return ip
def getRemoteIP():
return socket.gethostbyname(conf.hostname)
def getFileType(filePath):
try:
magicFileType = magic.from_file(filePath)
except:
return "unknown"
if "ASCII" in magicFileType or "text" in magicFileType:
return "text"
else:
return "binary"
def pollProcess(process):
while True:
dataToStdout(".")
time.sleep(1)
returncode = process.poll()
if returncode is not None:
if returncode == 0:
dataToStdout(" done\n")
elif returncode < 0:
dataToStdout(" process terminated by signal %d\n" % returncode)
elif returncode > 0:
dataToStdout(" quit unexpectedly with return code %d\n" % returncode)
break
def getCharset(charsetType=None):
asciiTbl = []
if charsetType is None:
asciiTbl = range(0, 128)
# 0 or 1
elif charsetType == 1:
asciiTbl.extend([ 0, 1 ])
asciiTbl.extend(range(47, 50))
# Digits
elif charsetType == 2:
asciiTbl.extend([ 0, 1 ])
asciiTbl.extend(range(47, 58))
# Hexadecimal
elif charsetType == 3:
asciiTbl.extend([ 0, 1 ])
asciiTbl.extend(range(47, 58))
asciiTbl.extend(range(64, 71))
asciiTbl.extend(range(96, 103))
# Characters
elif charsetType == 4:
asciiTbl.extend([ 0, 1 ])
asciiTbl.extend(range(64, 91))
asciiTbl.extend(range(96, 123))
# Characters and digits
elif charsetType == 5:
asciiTbl.extend([ 0, 1 ])
asciiTbl.extend(range(47, 58))
asciiTbl.extend(range(64, 91))
asciiTbl.extend(range(96, 123))
return asciiTbl
def searchEnvPath(fileName):
envPaths = os.environ["PATH"]
result = None
if IS_WIN:
envPaths = envPaths.split(";")
else:
envPaths = envPaths.split(":")
for envPath in envPaths:
envPath = envPath.replace(";", "")
result = os.path.exists(os.path.normpath(os.path.join(envPath, fileName)))
if result:
break
return result
2010-01-15 14:45:48 +03:00
def urlEncodeCookieValues(cookieStr):
if cookieStr:
result = ""
for part in cookieStr.split(';'):
index = part.find('=') + 1
if index > 0:
name = part[:index - 1].strip()
value = urlencode(part[index:], convall=True)
result += "; %s=%s" % (name, value)
elif part.strip().lower() != "secure":
result += "%s%s" % ("%3B", urlencode(part, convall=True))
else:
result += "; secure"
if result.startswith('; '):
result = result[2:]
elif result.startswith('%3B'):
result = result[3:]
return result
else:
return None
2010-01-05 14:30:33 +03:00
def directoryPath(path):
retVal = None
2010-04-22 20:35:22 +04:00
if isWindowsDriveLetterPath(path):
2010-01-05 14:30:33 +03:00
retVal = ntpath.dirname(path)
2010-02-21 02:11:05 +03:00
else:
retVal = posixpath.dirname(path)
2010-04-22 20:35:22 +04:00
2010-01-05 14:30:33 +03:00
return retVal
2010-01-15 20:42:46 +03:00
2010-01-05 14:30:33 +03:00
def normalizePath(path):
retVal = None
2010-04-22 20:35:22 +04:00
if isWindowsDriveLetterPath(path):
2010-01-05 14:30:33 +03:00
retVal = ntpath.normpath(path)
2010-02-21 02:11:05 +03:00
else:
retVal = posixpath.normpath(path)
2010-01-05 14:30:33 +03:00
return retVal
2010-01-15 19:06:59 +03:00
def safeStringFormat(formatStr, params):
retVal = formatStr.replace("%d", "%s")
2010-01-15 20:42:46 +03:00
if isinstance(params, basestring):
2010-01-15 20:42:46 +03:00
retVal = retVal.replace("%s", params)
else:
count = 0
index = 0
while index != -1:
index = retVal.find("%s")
2010-01-15 20:42:46 +03:00
if index != -1:
if count < len(params):
retVal = retVal[:index] + getUnicode(params[count]) + retVal[index+2:]
2010-01-15 20:42:46 +03:00
else:
raise sqlmapNoneDataException, "wrong number of parameters during string formatting"
count += 1
2010-01-15 19:06:59 +03:00
return retVal
2010-01-24 02:29:34 +03:00
def sanitizeAsciiString(subject):
2010-05-04 12:43:14 +04:00
if subject:
index = None
for i in xrange(len(subject)):
if ord(subject[i]) >= 128:
index = i
break
if not index:
return subject
else:
2010-05-14 18:03:33 +04:00
return subject[:index] + "".join(subject[i] if ord(subject[i]) < 128 else '?' for i in xrange(index, len(subject)))
else:
return None
2010-01-28 19:50:34 +03:00
2010-09-13 17:31:01 +04:00
def preparePageForLineComparison(page):
retVal = page
if isinstance(page, basestring):
return page.replace("><", ">\n<").replace("<br>", "\n").splitlines()
return retVal
2010-01-28 19:50:34 +03:00
def decloakToNamedTemporaryFile(filepath, name=None):
retVal = NamedTemporaryFile()
2010-03-21 03:39:44 +03:00
2010-02-03 17:49:28 +03:00
def __del__():
try:
if hasattr(retVal, 'old_name'):
retVal.name = old_name
retVal.close()
except OSError:
pass
2010-03-21 03:39:44 +03:00
2010-02-03 17:49:28 +03:00
retVal.__del__ = __del__
2010-01-28 19:50:34 +03:00
retVal.write(decloak(filepath))
retVal.seek(0)
2010-03-21 03:39:44 +03:00
2010-01-28 19:50:34 +03:00
if name:
retVal.old_name = retVal.name
retVal.name = name
2010-03-21 03:39:44 +03:00
2010-01-28 19:50:34 +03:00
return retVal
def decloakToMkstemp(filepath, **kwargs):
name = mkstemp(**kwargs)[1]
retVal = open(name, 'w+b')
retVal.write(decloak(filepath))
retVal.seek(0)
return retVal
def isWindowsPath(filepath):
return re.search("\A[\w]\:\\\\", filepath) is not None
def isWindowsDriveLetterPath(filepath):
return re.search("\A[\w]\:", filepath) is not None
def posixToNtSlashes(filepath):
2010-08-21 01:27:47 +04:00
"""
Replaces all occurances of Posix slashes (/) in provided
filepath with NT ones (/)
>>> posixToNtSlashes('C:/Windows')
'C:\\\\Windows'
"""
return filepath.replace('/', '\\')
def ntToPosixSlashes(filepath):
2010-08-21 01:27:47 +04:00
"""
Replaces all occurances of NT slashes (\) in provided
filepath with Posix ones (/)
>>> ntToPosixSlashes('C:\\Windows')
'C:/Windows'
"""
return filepath.replace('\\', '/')
def isBase64EncodedString(subject):
2010-08-21 01:27:47 +04:00
"""
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):
2010-08-21 01:27:47 +04:00
"""
Checks if the provided string is hex encoded
>>> isHexEncodedString('DEADBEEF')
True
>>> isHexEncodedString('test')
False
"""
return re.match(r"\A[0-9a-fA-F]+\Z", subject) is not None
def getConsoleWidth(default=80):
width = None
if 'COLUMNS' in os.environ and os.environ['COLUMNS'].isdigit():
width = int(os.environ['COLUMNS'])
else:
output=subprocess.Popen('stty size', shell=True, stdout=subprocess.PIPE, stderr=subprocess.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
2010-04-16 23:57:00 +04:00
return width if width else default
def parseXmlFile(xmlFile, handler):
xfile = codecs.open(xmlFile, 'rb', conf.dataEncoding)
2010-04-17 19:43:08 +04:00
content = xfile.read()
2010-04-16 23:57:00 +04:00
stream = StringIO(content)
parse(stream, handler)
stream.close()
2010-04-17 19:43:08 +04:00
xfile.close()
def calculateDeltaSeconds(start, epsilon=0.05):
2010-08-21 01:27:47 +04:00
"""
Returns elapsed time from start till now (including expected
2010-08-21 01:27:47 +04:00
error set by epsilon parameter)
"""
return int(time.time() - start + epsilon)
2010-05-21 13:35:36 +04:00
2010-05-21 16:19:20 +04:00
def initCommonOutputs():
kb.commonOutputs = {}
2010-05-21 16:44:09 +04:00
key = None
2010-05-21 16:19:20 +04:00
fileName = os.path.join(paths.SQLMAP_TXT_PATH, 'common-outputs.txt')
cfile = codecs.open(fileName, 'r', conf.dataEncoding)
2010-05-21 16:44:09 +04:00
2010-05-31 15:11:53 +04:00
for line in cfile.readlines(): # xreadlines doesn't return unicode strings when codec.open() is used
if line.find('#') != -1:
line = line[:line.find('#')]
2010-05-24 19:46:12 +04:00
2010-05-31 15:11:53 +04:00
line = line.strip()
2010-05-27 20:45:09 +04:00
2010-05-21 16:19:20 +04:00
if len(line) > 1:
if line.startswith('[') and line.endswith(']'):
2010-05-21 16:19:20 +04:00
key = line[1:-1]
elif key:
2010-05-21 16:44:09 +04:00
if key not in kb.commonOutputs:
2010-06-28 17:47:20 +04:00
kb.commonOutputs[key] = set()
2010-05-24 19:46:12 +04:00
if line not in kb.commonOutputs[key]:
2010-06-28 17:47:20 +04:00
kb.commonOutputs[key].add(line)
2010-05-24 19:46:12 +04:00
cfile.close()
2010-05-21 16:19:20 +04:00
def goGoodSamaritan(prevValue, originalCharset):
2010-05-26 15:14:22 +04:00
"""
2010-05-27 20:45:09 +04:00
Function for retrieving parameters needed for common prediction (good
samaritan) feature.
prevValue: retrieved query output so far (e.g. 'i').
Returns commonValue if there is a complete single match (in kb.partRun
of txt/common-outputs.txt under kb.partRun) regarding parameter
prevValue. If there is no single value match, but multiple, commonCharset is
2010-05-27 20:45:09 +04:00
returned containing more probable characters (retrieved from matched
values in txt/common-outputs.txt) together with the rest of charset as
otherCharset.
2010-05-26 15:14:22 +04:00
"""
2010-05-27 20:45:09 +04:00
2010-05-24 19:46:12 +04:00
if kb.commonOutputs is None:
2010-05-21 16:19:20 +04:00
initCommonOutputs()
2010-05-21 13:35:36 +04:00
predictionSet = set()
commonValue = None
commonPattern = None
countCommonValue = 0
2010-05-21 16:44:09 +04:00
# If the header (e.g. Databases) we are looking for has common
# outputs defined
if kb.partRun in kb.commonOutputs:
commonPartOutputs = kb.commonOutputs[kb.partRun]
2010-06-30 15:22:25 +04:00
commonPattern = commonFinderOnly(prevValue, commonPartOutputs)
# If the longest common prefix is the same as previous value then
# do not consider it
if commonPattern and commonPattern == prevValue:
commonPattern = None
# For each common output
for item in commonPartOutputs:
2010-05-27 20:45:09 +04:00
# Check if the common output (item) starts with prevValue
# where prevValue is the enumerated character(s) so far
if item.startswith(prevValue):
commonValue = item
countCommonValue += 1
2010-05-27 20:45:09 +04:00
if len(item) > len(prevValue):
char = item[len(prevValue)]
predictionSet.add(char)
# Reset single value if there is more than one possible common
# output
if countCommonValue > 1:
commonValue = None
2010-05-24 19:46:12 +04:00
2010-05-27 20:45:09 +04:00
commonCharset = []
2010-05-21 16:44:09 +04:00
otherCharset = []
2010-05-24 19:46:12 +04:00
2010-05-27 20:45:09 +04:00
# Split the original charset into common chars (commonCharset)
# and other chars (otherCharset)
for ordChar in originalCharset:
2010-05-21 13:35:36 +04:00
if chr(ordChar) not in predictionSet:
2010-05-21 16:44:09 +04:00
otherCharset.append(ordChar)
2010-05-21 13:35:36 +04:00
else:
2010-05-27 20:45:09 +04:00
commonCharset.append(ordChar)
2010-05-24 19:46:12 +04:00
2010-05-27 20:45:09 +04:00
commonCharset.sort()
2010-05-24 19:46:12 +04:00
return commonValue, commonPattern, commonCharset, originalCharset
2010-05-21 13:35:36 +04:00
else:
return None, None, None, originalCharset
2010-05-21 18:42:59 +04:00
def getCompiledRegex(regex, *args):
2010-05-26 15:14:22 +04:00
"""
2010-05-27 20:45:09 +04:00
Returns compiled regular expression and stores it in cache for further
usage
2010-08-21 01:27:47 +04:00
>>> getCompiledRegex('test') # doctest: +ELLIPSIS
<_sre.SRE_Pattern object at...
2010-05-26 15:14:22 +04:00
"""
if (regex, args) in kb.cache.regex:
return kb.cache.regex[(regex, args)]
2010-05-21 18:42:59 +04:00
else:
retVal = re.compile(regex, *args)
kb.cache.regex[(regex, args)] = retVal
2010-05-21 18:42:59 +04:00
return retVal
def getPartRun():
2010-05-26 15:14:22 +04:00
"""
2010-05-27 20:45:09 +04:00
Goes through call stack and finds constructs matching conf.dmbsHandler.*.
Returns it or its alias used in txt/common-outputs.txt
2010-05-26 15:14:22 +04:00
"""
2010-05-27 20:45:09 +04:00
retVal = None
2010-05-27 20:45:09 +04:00
commonPartsDict = optDict["Enumeration"]
stack = [item[4][0] if isinstance(item[4], list) else '' for item in inspect.stack()]
reobj1 = getCompiledRegex('conf\.dbmsHandler\.([^(]+)\(\)')
reobj2 = getCompiledRegex('self\.(get[^(]+)\(\)')
2010-05-27 20:45:09 +04:00
# Goes backwards through the stack to find the conf.dbmsHandler method
# calling this function
for i in xrange(0, len(stack)-1):
for reobj in (reobj2, reobj1):
match = reobj.search(stack[i])
if match:
# This is the calling conf.dbmsHandler or self method
# (e.g. 'getDbms')
retVal = match.groups()[0]
break
2010-05-27 20:45:09 +04:00
if retVal is not None:
break
2010-05-27 20:45:09 +04:00
# Return the INI tag to consider for common outputs (e.g. 'Databases')
2010-05-27 20:45:09 +04:00
return commonPartsDict[retVal][1] if retVal in commonPartsDict else retVal
2010-05-28 13:13:50 +04:00
def getUnicode(value, encoding=None):
2010-08-21 01:01:51 +04:00
"""
Return the unicode representation of the supplied value:
>>> getUnicode(u'test')
u'test'
>>> getUnicode('test')
u'test'
>>> getUnicode(1)
u'1'
"""
if encoding is None:
encoding = conf.dataEncoding if 'dataEncoding' in conf else "utf-8"
2010-06-02 16:31:36 +04:00
if isinstance(value, basestring):
return value if isinstance(value, unicode) else unicode(value, encoding, errors='replace')
2010-06-02 16:31:36 +04:00
else:
2010-09-09 18:08:53 +04:00
return unicode(value) #encoding ignored for non-basestring instances
2010-06-02 16:31:36 +04:00
# http://boredzo.org/blog/archives/2007-01-06/longest-common-prefix-in-python-2
2010-06-30 15:22:25 +04:00
def longestCommonPrefix(*sequences):
if len(sequences) == 1:
return sequences[0]
sequences = [pair[1] for pair in sorted((len(fi), fi) for fi in sequences)]
if not sequences:
return None
for i, comparison_ch in enumerate(sequences[0]):
for fi in sequences[1:]:
ch = fi[i]
if ch != comparison_ch:
return fi[:i]
return sequences[0]
2010-06-30 15:22:25 +04:00
def commonFinderOnly(initial, sequence):
return longestCommonPrefix(*filter(lambda x: x.startswith(initial), sequence))