mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2025-08-02 11:20:10 +03:00
Merge remote-tracking branch 'sqlmapproject/master'
This commit is contained in:
commit
169c95921a
|
@ -1034,7 +1034,7 @@ def checkStability():
|
||||||
delay = max(0, min(1, delay))
|
delay = max(0, min(1, delay))
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
||||||
secondPage, _ = Request.queryPage(content=True, raise404=False)
|
secondPage, _ = Request.queryPage(content=True, noteResponseTime=False, raise404=False)
|
||||||
|
|
||||||
if kb.redirectChoice:
|
if kb.redirectChoice:
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -2700,7 +2700,7 @@ def parseSqliteTableSchema(value):
|
||||||
table = {}
|
table = {}
|
||||||
columns = {}
|
columns = {}
|
||||||
|
|
||||||
for match in re.finditer(r"(\w+)[\"'`]?\s+(INT|INTEGER|TINYINT|SMALLINT|MEDIUMINT|BIGINT|UNSIGNED BIG INT|INT2|INT8|INTEGER|CHARACTER|VARCHAR|VARYING CHARACTER|NCHAR|NATIVE CHARACTER|NVARCHAR|TEXT|CLOB|TEXT|BLOB|NONE|REAL|DOUBLE|DOUBLE PRECISION|FLOAT|REAL|NUMERIC|DECIMAL|BOOLEAN|DATE|DATETIME|NUMERIC)\b", value, re.I):
|
for match in re.finditer(r"(\w+)[\"'`]?\s+(INT|INTEGER|TINYINT|SMALLINT|MEDIUMINT|BIGINT|UNSIGNED BIG INT|INT2|INT8|INTEGER|CHARACTER|VARCHAR|VARYING CHARACTER|NCHAR|NATIVE CHARACTER|NVARCHAR|TEXT|CLOB|LONGTEXT|BLOB|NONE|REAL|DOUBLE|DOUBLE PRECISION|FLOAT|REAL|NUMERIC|DECIMAL|BOOLEAN|DATE|DATETIME|NUMERIC)\b", value, re.I):
|
||||||
columns[match.group(1)] = match.group(2)
|
columns[match.group(1)] = match.group(2)
|
||||||
|
|
||||||
table[conf.tbl] = columns
|
table[conf.tbl] = columns
|
||||||
|
@ -3770,8 +3770,12 @@ def decodeHexValue(value, raw=False):
|
||||||
|
|
||||||
def _(value):
|
def _(value):
|
||||||
retVal = value
|
retVal = value
|
||||||
if value and isinstance(value, basestring) and len(value) % 2 == 0:
|
if value and isinstance(value, basestring):
|
||||||
retVal = hexdecode(retVal)
|
if len(value) % 2 != 0:
|
||||||
|
retVal = "%s?" % hexdecode(value[:-1])
|
||||||
|
singleTimeWarnMessage("there was a problem decoding value '%s' from expected hexadecimal form" % value)
|
||||||
|
else:
|
||||||
|
retVal = hexdecode(value)
|
||||||
|
|
||||||
if not kb.binaryField and not raw:
|
if not kb.binaryField and not raw:
|
||||||
if Backend.isDbms(DBMS.MSSQL) and value.startswith("0x"):
|
if Backend.isDbms(DBMS.MSSQL) and value.startswith("0x"):
|
||||||
|
|
|
@ -8,10 +8,13 @@ See the file 'doc/COPYING' for copying permission
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import pickle
|
import pickle
|
||||||
|
import StringIO
|
||||||
import sys
|
import sys
|
||||||
|
import types
|
||||||
|
|
||||||
from lib.core.settings import IS_WIN
|
from lib.core.settings import IS_WIN
|
||||||
from lib.core.settings import UNICODE_ENCODING
|
from lib.core.settings import UNICODE_ENCODING
|
||||||
|
from lib.core.settings import PICKLE_REDUCE_WHITELIST
|
||||||
|
|
||||||
def base64decode(value):
|
def base64decode(value):
|
||||||
"""
|
"""
|
||||||
|
@ -67,10 +70,23 @@ def base64unpickle(value):
|
||||||
|
|
||||||
retVal = None
|
retVal = None
|
||||||
|
|
||||||
|
def _(self):
|
||||||
|
if len(self.stack) > 1:
|
||||||
|
func = self.stack[-2]
|
||||||
|
if func not in PICKLE_REDUCE_WHITELIST:
|
||||||
|
raise Exception, "abusing reduce() is bad, Mkay!"
|
||||||
|
self.load_reduce()
|
||||||
|
|
||||||
|
def loads(str):
|
||||||
|
file = StringIO.StringIO(str)
|
||||||
|
unpickler = pickle.Unpickler(file)
|
||||||
|
unpickler.dispatch[pickle.REDUCE] = _
|
||||||
|
return unpickler.load()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
retVal = pickle.loads(base64decode(value))
|
retVal = loads(base64decode(value))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
retVal = pickle.loads(base64decode(bytes(value)))
|
retVal = loads(base64decode(bytes(value)))
|
||||||
|
|
||||||
return retVal
|
return retVal
|
||||||
|
|
||||||
|
|
|
@ -578,7 +578,8 @@ class Dump(object):
|
||||||
if not os.path.isdir(dumpDbPath):
|
if not os.path.isdir(dumpDbPath):
|
||||||
os.makedirs(dumpDbPath, 0755)
|
os.makedirs(dumpDbPath, 0755)
|
||||||
|
|
||||||
filepath = os.path.join(dumpDbPath, "%s-%d.bin" % (unsafeSQLIdentificatorNaming(column), randomInt(8)))
|
_ = re.sub(r"[^\w]", "_", normalizeUnicode(unsafeSQLIdentificatorNaming(column)))
|
||||||
|
filepath = os.path.join(dumpDbPath, "%s-%d.bin" % (_, randomInt(8)))
|
||||||
warnMsg = "writing binary ('%s') content to file '%s' " % (mimetype, filepath)
|
warnMsg = "writing binary ('%s') content to file '%s' " % (mimetype, filepath)
|
||||||
logger.warn(warnMsg)
|
logger.warn(warnMsg)
|
||||||
|
|
||||||
|
|
|
@ -1024,6 +1024,9 @@ def _setSocketPreConnect():
|
||||||
Makes a pre-connect version of socket.connect
|
Makes a pre-connect version of socket.connect
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if conf.disablePrecon:
|
||||||
|
return
|
||||||
|
|
||||||
def _():
|
def _():
|
||||||
while kb.threadContinue:
|
while kb.threadContinue:
|
||||||
try:
|
try:
|
||||||
|
@ -1373,7 +1376,7 @@ def _setHTTPExtraHeaders():
|
||||||
errMsg = "invalid header value: %s. Valid header format is 'name:value'" % repr(headerValue).lstrip('u')
|
errMsg = "invalid header value: %s. Valid header format is 'name:value'" % repr(headerValue).lstrip('u')
|
||||||
raise SqlmapSyntaxException(errMsg)
|
raise SqlmapSyntaxException(errMsg)
|
||||||
|
|
||||||
elif not conf.httpHeaders or len(conf.httpHeaders) == 1:
|
elif not conf.requestFile and len(conf.httpHeaders or []) < 2:
|
||||||
conf.httpHeaders.append((HTTP_HEADER.ACCEPT_LANGUAGE, "en-us,en;q=0.5"))
|
conf.httpHeaders.append((HTTP_HEADER.ACCEPT_LANGUAGE, "en-us,en;q=0.5"))
|
||||||
if not conf.charset:
|
if not conf.charset:
|
||||||
conf.httpHeaders.append((HTTP_HEADER.ACCEPT_CHARSET, "ISO-8859-15,utf-8;q=0.7,*;q=0.7"))
|
conf.httpHeaders.append((HTTP_HEADER.ACCEPT_CHARSET, "ISO-8859-15,utf-8;q=0.7,*;q=0.7"))
|
||||||
|
@ -1573,7 +1576,7 @@ def _cleanupOptions():
|
||||||
conf.progressWidth = width - 46
|
conf.progressWidth = width - 46
|
||||||
|
|
||||||
for key, value in conf.items():
|
for key, value in conf.items():
|
||||||
if value and any(key.endswith(_) for _ in ("Path", "File")):
|
if value and any(key.endswith(_) for _ in ("Path", "File", "Dir")):
|
||||||
conf[key] = safeExpandUser(value)
|
conf[key] = safeExpandUser(value)
|
||||||
|
|
||||||
if conf.testParameter:
|
if conf.testParameter:
|
||||||
|
@ -1894,7 +1897,7 @@ def _setKnowledgeBaseAttributes(flushAll=True):
|
||||||
kb.safeReq = AttribDict()
|
kb.safeReq = AttribDict()
|
||||||
kb.singleLogFlags = set()
|
kb.singleLogFlags = set()
|
||||||
kb.reduceTests = None
|
kb.reduceTests = None
|
||||||
kb.tlsSNI = None
|
kb.tlsSNI = {}
|
||||||
kb.stickyDBMS = False
|
kb.stickyDBMS = False
|
||||||
kb.stickyLevel = None
|
kb.stickyLevel = None
|
||||||
kb.storeCrawlingChoice = None
|
kb.storeCrawlingChoice = None
|
||||||
|
|
|
@ -227,6 +227,7 @@ optDict = {
|
||||||
},
|
},
|
||||||
"Hidden": {
|
"Hidden": {
|
||||||
"dummy": "boolean",
|
"dummy": "boolean",
|
||||||
|
"disablePrecon": "boolean",
|
||||||
"binaryFields": "string",
|
"binaryFields": "string",
|
||||||
"profile": "boolean",
|
"profile": "boolean",
|
||||||
"cpuThrottle": "integer",
|
"cpuThrottle": "integer",
|
||||||
|
|
|
@ -87,5 +87,4 @@ def profile(profileOutputFile=None, dotOutputFile=None, imageOutputFile=None):
|
||||||
win.connect('destroy', gtk.main_quit)
|
win.connect('destroy', gtk.main_quit)
|
||||||
win.set_filter("dot")
|
win.set_filter("dot")
|
||||||
win.open_file(dotOutputFile)
|
win.open_file(dotOutputFile)
|
||||||
gobject.timeout_add(1000, win.update, dotOutputFile)
|
|
||||||
gtk.main()
|
gtk.main()
|
||||||
|
|
|
@ -11,7 +11,9 @@ import subprocess
|
||||||
import string
|
import string
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import types
|
||||||
|
|
||||||
|
from lib.core.datatype import AttribDict
|
||||||
from lib.core.enums import DBMS
|
from lib.core.enums import DBMS
|
||||||
from lib.core.enums import DBMS_DIRECTORY_NAME
|
from lib.core.enums import DBMS_DIRECTORY_NAME
|
||||||
from lib.core.enums import OS
|
from lib.core.enums import OS
|
||||||
|
@ -427,6 +429,8 @@ HTML_TITLE_REGEX = "<title>(?P<result>[^<]+)</title>"
|
||||||
# Table used for Base64 conversion in WordPress hash cracking routine
|
# Table used for Base64 conversion in WordPress hash cracking routine
|
||||||
ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
||||||
|
PICKLE_REDUCE_WHITELIST = (types.BooleanType, types.DictType, types.FloatType, types.IntType, types.ListType, types.LongType, types.NoneType, types.StringType, types.TupleType, types.UnicodeType, types.XRangeType, type(AttribDict()), type(set()))
|
||||||
|
|
||||||
# Chars used to quickly distinguish if the user provided tainted parameter values
|
# Chars used to quickly distinguish if the user provided tainted parameter values
|
||||||
DUMMY_SQL_INJECTION_CHARS = ";()'"
|
DUMMY_SQL_INJECTION_CHARS = ";()'"
|
||||||
|
|
||||||
|
@ -503,7 +507,7 @@ DEFAULT_COOKIE_DELIMITER = ';'
|
||||||
FORCE_COOKIE_EXPIRATION_TIME = "9999999999"
|
FORCE_COOKIE_EXPIRATION_TIME = "9999999999"
|
||||||
|
|
||||||
# Github OAuth token used for creating an automatic Issue for unhandled exceptions
|
# Github OAuth token used for creating an automatic Issue for unhandled exceptions
|
||||||
GITHUB_REPORT_OAUTH_TOKEN = "YzQzM2M2YzgzMDExN2I5ZDMyYjAzNTIzODIwZDA2MDFmMmVjODI1Ng=="
|
GITHUB_REPORT_OAUTH_TOKEN = "YzNkYTgyMTdjYzdjNjZjMjFjMWE5ODI5OGQyNzk2ODM1M2M0MzUyOA=="
|
||||||
|
|
||||||
# Skip unforced HashDB flush requests below the threshold number of cached items
|
# Skip unforced HashDB flush requests below the threshold number of cached items
|
||||||
HASHDB_FLUSH_THRESHOLD = 32
|
HASHDB_FLUSH_THRESHOLD = 32
|
||||||
|
@ -589,6 +593,12 @@ EVENTVALIDATION_REGEX = r'(?i)(?P<name>__EVENTVALIDATION[^"]*)[^>]+value="(?P<re
|
||||||
# Number of rows to generate inside the full union test for limited output (mustn't be too large to prevent payload length problems)
|
# Number of rows to generate inside the full union test for limited output (mustn't be too large to prevent payload length problems)
|
||||||
LIMITED_ROWS_TEST_NUMBER = 15
|
LIMITED_ROWS_TEST_NUMBER = 15
|
||||||
|
|
||||||
|
# Default REST-JSON API server listen address
|
||||||
|
RESTAPI_DEFAULT_ADDRESS = "127.0.0.1"
|
||||||
|
|
||||||
|
# Default REST-JSON API server listen port
|
||||||
|
RESTAPI_DEFAULT_PORT = 8775
|
||||||
|
|
||||||
# Format used for representing invalid unicode characters
|
# Format used for representing invalid unicode characters
|
||||||
INVALID_UNICODE_CHAR_FORMAT = r"\x%02x"
|
INVALID_UNICODE_CHAR_FORMAT = r"\x%02x"
|
||||||
|
|
||||||
|
|
|
@ -77,10 +77,6 @@ class Wordlist(object):
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
self.adjust()
|
self.adjust()
|
||||||
retVal = self.iter.next().rstrip()
|
retVal = self.iter.next().rstrip()
|
||||||
try:
|
|
||||||
retVal = retVal.decode(UNICODE_ENCODING)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
continue
|
|
||||||
if not self.proc_count or self.counter % self.proc_count == self.proc_id:
|
if not self.proc_count or self.counter % self.proc_count == self.proc_id:
|
||||||
break
|
break
|
||||||
return retVal
|
return retVal
|
||||||
|
|
|
@ -754,6 +754,9 @@ def cmdLineParser(argv=None):
|
||||||
parser.add_option("--pickled-options", dest="pickledOptions",
|
parser.add_option("--pickled-options", dest="pickledOptions",
|
||||||
help=SUPPRESS_HELP)
|
help=SUPPRESS_HELP)
|
||||||
|
|
||||||
|
parser.add_option("--disable-precon", dest="disablePrecon", action="store_true",
|
||||||
|
help=SUPPRESS_HELP)
|
||||||
|
|
||||||
parser.add_option("--profile", dest="profile", action="store_true",
|
parser.add_option("--profile", dest="profile", action="store_true",
|
||||||
help=SUPPRESS_HELP)
|
help=SUPPRESS_HELP)
|
||||||
|
|
||||||
|
@ -780,9 +783,6 @@ def cmdLineParser(argv=None):
|
||||||
|
|
||||||
parser.add_option("--run-case", dest="runCase", help=SUPPRESS_HELP)
|
parser.add_option("--run-case", dest="runCase", help=SUPPRESS_HELP)
|
||||||
|
|
||||||
parser.add_option("--nnc5ed", dest="nnc5ed", action="store_true",
|
|
||||||
help=SUPPRESS_HELP) # temporary hidden switch :)
|
|
||||||
|
|
||||||
parser.add_option_group(target)
|
parser.add_option_group(target)
|
||||||
parser.add_option_group(request)
|
parser.add_option_group(request)
|
||||||
parser.add_option_group(optimization)
|
parser.add_option_group(optimization)
|
||||||
|
|
|
@ -150,7 +150,7 @@ def checkCharEncoding(encoding, warn=True):
|
||||||
return encoding
|
return encoding
|
||||||
|
|
||||||
# Reference: http://www.destructor.de/charsets/index.htm
|
# Reference: http://www.destructor.de/charsets/index.htm
|
||||||
translate = {"windows-874": "iso-8859-11", "en_us": "utf8", "macintosh": "iso-8859-1", "euc_tw": "big5_tw", "th": "tis-620", "unicode": "utf8", "utc8": "utf8", "ebcdic": "ebcdic-cp-be", "iso-8859": "iso8859-1", "ansi": "ascii", "gbk2312": "gbk", "windows-31j": "cp932"}
|
translate = {"windows-874": "iso-8859-11", "utf-8859-1": "utf8", "en_us": "utf8", "macintosh": "iso-8859-1", "euc_tw": "big5_tw", "th": "tis-620", "unicode": "utf8", "utc8": "utf8", "ebcdic": "ebcdic-cp-be", "iso-8859": "iso8859-1", "ansi": "ascii", "gbk2312": "gbk", "windows-31j": "cp932"}
|
||||||
|
|
||||||
for delimiter in (';', ',', '('):
|
for delimiter in (';', ',', '('):
|
||||||
if delimiter in encoding:
|
if delimiter in encoding:
|
||||||
|
|
|
@ -343,6 +343,9 @@ class Connect(object):
|
||||||
# Prepare HTTP headers
|
# Prepare HTTP headers
|
||||||
headers = forgeHeaders({HTTP_HEADER.COOKIE: cookie, HTTP_HEADER.USER_AGENT: ua, HTTP_HEADER.REFERER: referer, HTTP_HEADER.HOST: host})
|
headers = forgeHeaders({HTTP_HEADER.COOKIE: cookie, HTTP_HEADER.USER_AGENT: ua, HTTP_HEADER.REFERER: referer, HTTP_HEADER.HOST: host})
|
||||||
|
|
||||||
|
if HTTP_HEADER.COOKIE in headers:
|
||||||
|
cookie = headers[HTTP_HEADER.COOKIE]
|
||||||
|
|
||||||
if kb.authHeader:
|
if kb.authHeader:
|
||||||
headers[HTTP_HEADER.AUTHORIZATION] = kb.authHeader
|
headers[HTTP_HEADER.AUTHORIZATION] = kb.authHeader
|
||||||
|
|
||||||
|
@ -370,6 +373,12 @@ class Connect(object):
|
||||||
if boundary:
|
if boundary:
|
||||||
headers[HTTP_HEADER.CONTENT_TYPE] = "%s; boundary=%s" % (headers[HTTP_HEADER.CONTENT_TYPE], boundary)
|
headers[HTTP_HEADER.CONTENT_TYPE] = "%s; boundary=%s" % (headers[HTTP_HEADER.CONTENT_TYPE], boundary)
|
||||||
|
|
||||||
|
# Reset header values to original in case of provided request file
|
||||||
|
if target and conf.requestFile:
|
||||||
|
headers = OrderedDict(conf.httpHeaders)
|
||||||
|
if cookie:
|
||||||
|
headers[HTTP_HEADER.COOKIE] = cookie
|
||||||
|
|
||||||
if auxHeaders:
|
if auxHeaders:
|
||||||
for key, value in auxHeaders.items():
|
for key, value in auxHeaders.items():
|
||||||
for _ in headers.keys():
|
for _ in headers.keys():
|
||||||
|
|
|
@ -43,11 +43,14 @@ class HTTPSConnection(httplib.HTTPSConnection):
|
||||||
|
|
||||||
success = False
|
success = False
|
||||||
|
|
||||||
if not kb.tlsSNI:
|
# Reference(s): https://docs.python.org/2/library/ssl.html#ssl.SSLContext
|
||||||
for protocol in _protocols:
|
# https://www.mnot.net/blog/2014/12/27/python_2_and_tls_sni
|
||||||
|
if kb.tlsSNI.get(self.host) != False and hasattr(ssl, "SSLContext"):
|
||||||
|
for protocol in filter(lambda _: _ >= ssl.PROTOCOL_TLSv1, _protocols):
|
||||||
try:
|
try:
|
||||||
sock = create_sock()
|
sock = create_sock()
|
||||||
_ = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=protocol)
|
context = ssl.SSLContext(protocol)
|
||||||
|
_ = context.wrap_socket(sock, do_handshake_on_connect=True, server_hostname=self.host)
|
||||||
if _:
|
if _:
|
||||||
success = True
|
success = True
|
||||||
self.sock = _
|
self.sock = _
|
||||||
|
@ -60,16 +63,16 @@ class HTTPSConnection(httplib.HTTPSConnection):
|
||||||
self._tunnel_host = None
|
self._tunnel_host = None
|
||||||
logger.debug("SSL connection error occurred ('%s')" % getSafeExString(ex))
|
logger.debug("SSL connection error occurred ('%s')" % getSafeExString(ex))
|
||||||
|
|
||||||
# Reference(s): https://docs.python.org/2/library/ssl.html#ssl.SSLContext
|
if kb.tlsSNI.get(self.host) is None:
|
||||||
# https://www.mnot.net/blog/2014/12/27/python_2_and_tls_sni
|
kb.tlsSNI[self.host] = success
|
||||||
if not success and hasattr(ssl, "SSLContext"):
|
|
||||||
for protocol in filter(lambda _: _ >= ssl.PROTOCOL_TLSv1, _protocols):
|
if not success:
|
||||||
|
for protocol in _protocols:
|
||||||
try:
|
try:
|
||||||
sock = create_sock()
|
sock = create_sock()
|
||||||
context = ssl.SSLContext(protocol)
|
_ = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=protocol)
|
||||||
_ = context.wrap_socket(sock, do_handshake_on_connect=False, server_hostname=self.host)
|
|
||||||
if _:
|
if _:
|
||||||
kb.tlsSNI = success = True
|
success = True
|
||||||
self.sock = _
|
self.sock = _
|
||||||
_protocols.remove(protocol)
|
_protocols.remove(protocol)
|
||||||
_protocols.insert(0, protocol)
|
_protocols.insert(0, protocol)
|
||||||
|
|
|
@ -9,10 +9,9 @@ import os
|
||||||
import posixpath
|
import posixpath
|
||||||
import re
|
import re
|
||||||
import StringIO
|
import StringIO
|
||||||
|
import tempfile
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
from tempfile import mkstemp
|
|
||||||
|
|
||||||
from extra.cloak.cloak import decloak
|
from extra.cloak.cloak import decloak
|
||||||
from lib.core.agent import agent
|
from lib.core.agent import agent
|
||||||
from lib.core.common import arrayizeValue
|
from lib.core.common import arrayizeValue
|
||||||
|
@ -257,10 +256,10 @@ class Web:
|
||||||
stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webApi)
|
stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webApi)
|
||||||
self.webStagerFilePath = posixpath.join(ntToPosixSlashes(directory), stagerName)
|
self.webStagerFilePath = posixpath.join(ntToPosixSlashes(directory), stagerName)
|
||||||
|
|
||||||
handle, filename = mkstemp()
|
handle, filename = tempfile.mkstemp()
|
||||||
os.fdopen(handle).close() # close low level handle (causing problems later)
|
os.close(handle)
|
||||||
|
|
||||||
with open(filename, "w+") as f:
|
with open(filename, "w+b") as f:
|
||||||
_ = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
|
_ = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
|
||||||
_ = _.replace("WRITABLE_DIR", utf8encode(directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory))
|
_ = _.replace("WRITABLE_DIR", utf8encode(directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory))
|
||||||
f.write(_)
|
f.write(_)
|
||||||
|
|
|
@ -56,7 +56,7 @@ from lib.utils.progress import ProgressBar
|
||||||
from thirdparty.odict.odict import OrderedDict
|
from thirdparty.odict.odict import OrderedDict
|
||||||
|
|
||||||
def _oneShotUnionUse(expression, unpack=True, limited=False):
|
def _oneShotUnionUse(expression, unpack=True, limited=False):
|
||||||
retVal = hashDBRetrieve("%s%s" % (conf.hexConvert, expression), checkConf=True) # as union data is stored raw unconverted
|
retVal = hashDBRetrieve("%s%s" % (conf.hexConvert or False, expression), checkConf=True) # as union data is stored raw unconverted
|
||||||
|
|
||||||
threadData = getCurrentThreadData()
|
threadData = getCurrentThreadData()
|
||||||
threadData.resumed = retVal is not None
|
threadData.resumed = retVal is not None
|
||||||
|
@ -102,7 +102,7 @@ def _oneShotUnionUse(expression, unpack=True, limited=False):
|
||||||
if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError():
|
if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError():
|
||||||
retVal = htmlunescape(retVal).replace("<br>", "\n")
|
retVal = htmlunescape(retVal).replace("<br>", "\n")
|
||||||
|
|
||||||
hashDBWrite("%s%s" % (conf.hexConvert, expression), retVal)
|
hashDBWrite("%s%s" % (conf.hexConvert or False, expression), retVal)
|
||||||
else:
|
else:
|
||||||
trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start))
|
trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start))
|
||||||
|
|
||||||
|
@ -111,6 +111,9 @@ def _oneShotUnionUse(expression, unpack=True, limited=False):
|
||||||
warnMsg += "(probably due to its length and/or content): "
|
warnMsg += "(probably due to its length and/or content): "
|
||||||
warnMsg += safecharencode(trimmed)
|
warnMsg += safecharencode(trimmed)
|
||||||
logger.warn(warnMsg)
|
logger.warn(warnMsg)
|
||||||
|
else:
|
||||||
|
vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector
|
||||||
|
kb.unionDuplicates = vector[7]
|
||||||
|
|
||||||
return retVal
|
return retVal
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
|
import socket
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -35,6 +36,8 @@ from lib.core.exception import SqlmapConnectionException
|
||||||
from lib.core.log import LOGGER_HANDLER
|
from lib.core.log import LOGGER_HANDLER
|
||||||
from lib.core.optiondict import optDict
|
from lib.core.optiondict import optDict
|
||||||
from lib.core.settings import IS_WIN
|
from lib.core.settings import IS_WIN
|
||||||
|
from lib.core.settings import RESTAPI_DEFAULT_ADDRESS
|
||||||
|
from lib.core.settings import RESTAPI_DEFAULT_PORT
|
||||||
from lib.core.subprocessng import Popen
|
from lib.core.subprocessng import Popen
|
||||||
from lib.parse.cmdline import cmdLineParser
|
from lib.parse.cmdline import cmdLineParser
|
||||||
from thirdparty.bottle.bottle import error as return_error
|
from thirdparty.bottle.bottle import error as return_error
|
||||||
|
@ -45,9 +48,6 @@ from thirdparty.bottle.bottle import request
|
||||||
from thirdparty.bottle.bottle import response
|
from thirdparty.bottle.bottle import response
|
||||||
from thirdparty.bottle.bottle import run
|
from thirdparty.bottle.bottle import run
|
||||||
|
|
||||||
RESTAPI_SERVER_HOST = "127.0.0.1"
|
|
||||||
RESTAPI_SERVER_PORT = 8775
|
|
||||||
|
|
||||||
|
|
||||||
# global settings
|
# global settings
|
||||||
class DataStore(object):
|
class DataStore(object):
|
||||||
|
@ -637,7 +637,7 @@ def download(taskid, target, filename):
|
||||||
return jsonize({"success": False, "message": "File does not exist"})
|
return jsonize({"success": False, "message": "File does not exist"})
|
||||||
|
|
||||||
|
|
||||||
def server(host="0.0.0.0", port=RESTAPI_SERVER_PORT):
|
def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT):
|
||||||
"""
|
"""
|
||||||
REST-JSON API server
|
REST-JSON API server
|
||||||
"""
|
"""
|
||||||
|
@ -654,7 +654,13 @@ def server(host="0.0.0.0", port=RESTAPI_SERVER_PORT):
|
||||||
DataStore.current_db.init()
|
DataStore.current_db.init()
|
||||||
|
|
||||||
# Run RESTful API
|
# Run RESTful API
|
||||||
run(host=host, port=port, quiet=True, debug=False)
|
try:
|
||||||
|
run(host=host, port=port, quiet=True, debug=False)
|
||||||
|
except socket.error, ex:
|
||||||
|
if "already in use" in getSafeExString(ex):
|
||||||
|
logger.error("Address already in use ('%s:%s')" % (host, port))
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def _client(url, options=None):
|
def _client(url, options=None):
|
||||||
|
@ -673,7 +679,7 @@ def _client(url, options=None):
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def client(host=RESTAPI_SERVER_HOST, port=RESTAPI_SERVER_PORT):
|
def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT):
|
||||||
"""
|
"""
|
||||||
REST-JSON API client
|
REST-JSON API client
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -209,6 +209,9 @@ def oracle_old_passwd(password, username, uppercase=True): # prior to version '
|
||||||
if isinstance(username, unicode):
|
if isinstance(username, unicode):
|
||||||
username = unicode.encode(username, UNICODE_ENCODING) # pyDes has issues with unicode strings
|
username = unicode.encode(username, UNICODE_ENCODING) # pyDes has issues with unicode strings
|
||||||
|
|
||||||
|
if isinstance(password, unicode):
|
||||||
|
password = unicode.encode(password, UNICODE_ENCODING)
|
||||||
|
|
||||||
unistr = "".join("\0%s" % c for c in (username + password).upper())
|
unistr = "".join("\0%s" % c for c in (username + password).upper())
|
||||||
|
|
||||||
cipher = des(hexdecode("0123456789ABCDEF"), CBC, IV, pad)
|
cipher = des(hexdecode("0123456789ABCDEF"), CBC, IV, pad)
|
||||||
|
@ -327,7 +330,8 @@ def wordpress_passwd(password, salt, count, prefix, uppercase=False):
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
password = password.encode(UNICODE_ENCODING)
|
if isinstance(password, unicode):
|
||||||
|
password = password.encode(UNICODE_ENCODING)
|
||||||
|
|
||||||
cipher = md5(salt)
|
cipher = md5(salt)
|
||||||
cipher.update(password)
|
cipher.update(password)
|
||||||
|
|
|
@ -64,15 +64,19 @@ def pivotDumpTable(table, colList, count=None, blind=True):
|
||||||
colList = filter(None, sorted(colList, key=lambda x: len(x) if x else MAX_INT))
|
colList = filter(None, sorted(colList, key=lambda x: len(x) if x else MAX_INT))
|
||||||
|
|
||||||
if conf.pivotColumn:
|
if conf.pivotColumn:
|
||||||
if any(re.search(r"(.+\.)?%s" % re.escape(conf.pivotColumn), _, re.I) for _ in colList):
|
for _ in colList:
|
||||||
infoMsg = "using column '%s' as a pivot " % conf.pivotColumn
|
if re.search(r"(.+\.)?%s" % re.escape(conf.pivotColumn), _, re.I):
|
||||||
infoMsg += "for retrieving row data"
|
infoMsg = "using column '%s' as a pivot " % conf.pivotColumn
|
||||||
logger.info(infoMsg)
|
infoMsg += "for retrieving row data"
|
||||||
|
logger.info(infoMsg)
|
||||||
|
|
||||||
validPivotValue = True
|
colList.remove(_)
|
||||||
colList.remove(conf.pivotColumn)
|
colList.insert(0, _)
|
||||||
colList.insert(0, conf.pivotColumn)
|
|
||||||
else:
|
validPivotValue = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not validPivotValue:
|
||||||
warnMsg = "column '%s' not " % conf.pivotColumn
|
warnMsg = "column '%s' not " % conf.pivotColumn
|
||||||
warnMsg += "found in table '%s'" % table
|
warnMsg += "found in table '%s'" % table
|
||||||
logger.warn(warnMsg)
|
logger.warn(warnMsg)
|
||||||
|
|
13
sqlmapapi.py
13
sqlmapapi.py
|
@ -14,12 +14,11 @@ from sqlmap import modulePath
|
||||||
from lib.core.common import setPaths
|
from lib.core.common import setPaths
|
||||||
from lib.core.data import paths
|
from lib.core.data import paths
|
||||||
from lib.core.data import logger
|
from lib.core.data import logger
|
||||||
|
from lib.core.settings import RESTAPI_DEFAULT_ADDRESS
|
||||||
|
from lib.core.settings import RESTAPI_DEFAULT_PORT
|
||||||
from lib.utils.api import client
|
from lib.utils.api import client
|
||||||
from lib.utils.api import server
|
from lib.utils.api import server
|
||||||
|
|
||||||
RESTAPI_SERVER_HOST = "127.0.0.1"
|
|
||||||
RESTAPI_SERVER_PORT = 8775
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
"""
|
"""
|
||||||
REST-JSON API main function
|
REST-JSON API main function
|
||||||
|
@ -33,10 +32,10 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# Parse command line options
|
# Parse command line options
|
||||||
apiparser = optparse.OptionParser()
|
apiparser = optparse.OptionParser()
|
||||||
apiparser.add_option("-s", "--server", help="Act as a REST-JSON API server", default=RESTAPI_SERVER_PORT, action="store_true")
|
apiparser.add_option("-s", "--server", help="Act as a REST-JSON API server", default=RESTAPI_DEFAULT_PORT, action="store_true")
|
||||||
apiparser.add_option("-c", "--client", help="Act as a REST-JSON API client", default=RESTAPI_SERVER_PORT, action="store_true")
|
apiparser.add_option("-c", "--client", help="Act as a REST-JSON API client", default=RESTAPI_DEFAULT_PORT, action="store_true")
|
||||||
apiparser.add_option("-H", "--host", help="Host of the REST-JSON API server", default=RESTAPI_SERVER_HOST, action="store")
|
apiparser.add_option("-H", "--host", help="Host of the REST-JSON API server", default=RESTAPI_DEFAULT_ADDRESS, action="store")
|
||||||
apiparser.add_option("-p", "--port", help="Port of the the REST-JSON API server", default=RESTAPI_SERVER_PORT, type="int", action="store")
|
apiparser.add_option("-p", "--port", help="Port of the the REST-JSON API server", default=RESTAPI_DEFAULT_PORT, type="int", action="store")
|
||||||
(args, _) = apiparser.parse_args()
|
(args, _) = apiparser.parse_args()
|
||||||
|
|
||||||
# Start the client or the server
|
# Start the client or the server
|
||||||
|
|
464
thirdparty/xdot/xdot.py
vendored
464
thirdparty/xdot/xdot.py
vendored
|
@ -16,11 +16,9 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
'''Visualize dot graphs via the xdot Format.'''
|
'''Visualize dot graphs via the xdot format.'''
|
||||||
|
|
||||||
__author__ = "Jose Fonseca"
|
__author__ = "Jose Fonseca et al"
|
||||||
|
|
||||||
__version__ = "0.4"
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@ -30,6 +28,7 @@ import math
|
||||||
import colorsys
|
import colorsys
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
|
import optparse
|
||||||
|
|
||||||
import gobject
|
import gobject
|
||||||
import gtk
|
import gtk
|
||||||
|
@ -90,13 +89,12 @@ class Shape:
|
||||||
else:
|
else:
|
||||||
return self.pen
|
return self.pen
|
||||||
|
|
||||||
|
def search_text(self, regexp):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class TextShape(Shape):
|
class TextShape(Shape):
|
||||||
|
|
||||||
#fontmap = pangocairo.CairoFontMap()
|
|
||||||
#fontmap.set_resolution(72)
|
|
||||||
#context = fontmap.create_context()
|
|
||||||
|
|
||||||
LEFT, CENTER, RIGHT = -1, 0, 1
|
LEFT, CENTER, RIGHT = -1, 0, 1
|
||||||
|
|
||||||
def __init__(self, pen, x, y, j, w, t):
|
def __init__(self, pen, x, y, j, w, t):
|
||||||
|
@ -191,6 +189,33 @@ class TextShape(Shape):
|
||||||
cr.line_to(x+self.w, self.y)
|
cr.line_to(x+self.w, self.y)
|
||||||
cr.stroke()
|
cr.stroke()
|
||||||
|
|
||||||
|
def search_text(self, regexp):
|
||||||
|
return regexp.search(self.t) is not None
|
||||||
|
|
||||||
|
|
||||||
|
class ImageShape(Shape):
|
||||||
|
|
||||||
|
def __init__(self, pen, x0, y0, w, h, path):
|
||||||
|
Shape.__init__(self)
|
||||||
|
self.pen = pen.copy()
|
||||||
|
self.x0 = x0
|
||||||
|
self.y0 = y0
|
||||||
|
self.w = w
|
||||||
|
self.h = h
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
def draw(self, cr, highlight=False):
|
||||||
|
cr2 = gtk.gdk.CairoContext(cr)
|
||||||
|
pixbuf = gtk.gdk.pixbuf_new_from_file(self.path)
|
||||||
|
sx = float(self.w)/float(pixbuf.get_width())
|
||||||
|
sy = float(self.h)/float(pixbuf.get_height())
|
||||||
|
cr.save()
|
||||||
|
cr.translate(self.x0, self.y0 - self.h)
|
||||||
|
cr.scale(sx, sy)
|
||||||
|
cr2.set_source_pixbuf(pixbuf, 0, 0)
|
||||||
|
cr2.paint()
|
||||||
|
cr.restore()
|
||||||
|
|
||||||
|
|
||||||
class EllipseShape(Shape):
|
class EllipseShape(Shape):
|
||||||
|
|
||||||
|
@ -304,6 +329,12 @@ class CompoundShape(Shape):
|
||||||
for shape in self.shapes:
|
for shape in self.shapes:
|
||||||
shape.draw(cr, highlight=highlight)
|
shape.draw(cr, highlight=highlight)
|
||||||
|
|
||||||
|
def search_text(self, regexp):
|
||||||
|
for shape in self.shapes:
|
||||||
|
if shape.search_text(regexp):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Url(object):
|
class Url(object):
|
||||||
|
|
||||||
|
@ -332,6 +363,9 @@ class Element(CompoundShape):
|
||||||
def __init__(self, shapes):
|
def __init__(self, shapes):
|
||||||
CompoundShape.__init__(self, shapes)
|
CompoundShape.__init__(self, shapes)
|
||||||
|
|
||||||
|
def is_inside(self, x, y):
|
||||||
|
return False
|
||||||
|
|
||||||
def get_url(self, x, y):
|
def get_url(self, x, y):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -341,9 +375,10 @@ class Element(CompoundShape):
|
||||||
|
|
||||||
class Node(Element):
|
class Node(Element):
|
||||||
|
|
||||||
def __init__(self, x, y, w, h, shapes, url):
|
def __init__(self, id, x, y, w, h, shapes, url):
|
||||||
Element.__init__(self, shapes)
|
Element.__init__(self, shapes)
|
||||||
|
|
||||||
|
self.id = id
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
|
|
||||||
|
@ -360,7 +395,6 @@ class Node(Element):
|
||||||
def get_url(self, x, y):
|
def get_url(self, x, y):
|
||||||
if self.url is None:
|
if self.url is None:
|
||||||
return None
|
return None
|
||||||
#print (x, y), (self.x1, self.y1), "-", (self.x2, self.y2)
|
|
||||||
if self.is_inside(x, y):
|
if self.is_inside(x, y):
|
||||||
return Url(self, self.url)
|
return Url(self, self.url)
|
||||||
return None
|
return None
|
||||||
|
@ -370,6 +404,9 @@ class Node(Element):
|
||||||
return Jump(self, self.x, self.y)
|
return Jump(self, self.x, self.y)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Node %s>" % self.id
|
||||||
|
|
||||||
|
|
||||||
def square_distance(x1, y1, x2, y2):
|
def square_distance(x1, y1, x2, y2):
|
||||||
deltax = x2 - x1
|
deltax = x2 - x1
|
||||||
|
@ -387,13 +424,29 @@ class Edge(Element):
|
||||||
|
|
||||||
RADIUS = 10
|
RADIUS = 10
|
||||||
|
|
||||||
|
def is_inside_begin(self, x, y):
|
||||||
|
return square_distance(x, y, *self.points[0]) <= self.RADIUS*self.RADIUS
|
||||||
|
|
||||||
|
def is_inside_end(self, x, y):
|
||||||
|
return square_distance(x, y, *self.points[-1]) <= self.RADIUS*self.RADIUS
|
||||||
|
|
||||||
|
def is_inside(self, x, y):
|
||||||
|
if self.is_inside_begin(x, y):
|
||||||
|
return True
|
||||||
|
if self.is_inside_end(x, y):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def get_jump(self, x, y):
|
def get_jump(self, x, y):
|
||||||
if square_distance(x, y, *self.points[0]) <= self.RADIUS*self.RADIUS:
|
if self.is_inside_begin(x, y):
|
||||||
return Jump(self, self.dst.x, self.dst.y, highlight=set([self, self.dst]))
|
return Jump(self, self.dst.x, self.dst.y, highlight=set([self, self.dst]))
|
||||||
if square_distance(x, y, *self.points[-1]) <= self.RADIUS*self.RADIUS:
|
if self.is_inside_end(x, y):
|
||||||
return Jump(self, self.src.x, self.src.y, highlight=set([self, self.src]))
|
return Jump(self, self.src.x, self.src.y, highlight=set([self, self.src]))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Edge %s -> %s>" % (self.src, self.dst)
|
||||||
|
|
||||||
|
|
||||||
class Graph(Shape):
|
class Graph(Shape):
|
||||||
|
|
||||||
|
@ -424,6 +477,14 @@ class Graph(Shape):
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
node.draw(cr, highlight=(node in highlight_items))
|
node.draw(cr, highlight=(node in highlight_items))
|
||||||
|
|
||||||
|
def get_element(self, x, y):
|
||||||
|
for node in self.nodes:
|
||||||
|
if node.is_inside(x, y):
|
||||||
|
return node
|
||||||
|
for edge in self.edges:
|
||||||
|
if edge.is_inside(x, y):
|
||||||
|
return edge
|
||||||
|
|
||||||
def get_url(self, x, y):
|
def get_url(self, x, y):
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
url = node.get_url(x, y)
|
url = node.get_url(x, y)
|
||||||
|
@ -443,6 +504,14 @@ class Graph(Shape):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
BOLD = 1
|
||||||
|
ITALIC = 2
|
||||||
|
UNDERLINE = 4
|
||||||
|
SUPERSCRIPT = 8
|
||||||
|
SUBSCRIPT = 16
|
||||||
|
STRIKE_THROUGH = 32
|
||||||
|
|
||||||
|
|
||||||
class XDotAttrParser:
|
class XDotAttrParser:
|
||||||
"""Parser for xdot drawing attributes.
|
"""Parser for xdot drawing attributes.
|
||||||
See also:
|
See also:
|
||||||
|
@ -451,7 +520,7 @@ class XDotAttrParser:
|
||||||
|
|
||||||
def __init__(self, parser, buf):
|
def __init__(self, parser, buf):
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
self.buf = self.unescape(buf)
|
self.buf = buf
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
|
|
||||||
self.pen = Pen()
|
self.pen = Pen()
|
||||||
|
@ -460,11 +529,6 @@ class XDotAttrParser:
|
||||||
def __nonzero__(self):
|
def __nonzero__(self):
|
||||||
return self.pos < len(self.buf)
|
return self.pos < len(self.buf)
|
||||||
|
|
||||||
def unescape(self, buf):
|
|
||||||
buf = buf.replace('\\"', '"')
|
|
||||||
buf = buf.replace('\\n', '\n')
|
|
||||||
return buf
|
|
||||||
|
|
||||||
def read_code(self):
|
def read_code(self):
|
||||||
pos = self.buf.find(" ", self.pos)
|
pos = self.buf.find(" ", self.pos)
|
||||||
res = self.buf[self.pos:pos]
|
res = self.buf[self.pos:pos]
|
||||||
|
@ -473,19 +537,19 @@ class XDotAttrParser:
|
||||||
self.pos += 1
|
self.pos += 1
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def read_number(self):
|
def read_int(self):
|
||||||
return int(self.read_code())
|
return int(self.read_code())
|
||||||
|
|
||||||
def read_float(self):
|
def read_float(self):
|
||||||
return float(self.read_code())
|
return float(self.read_code())
|
||||||
|
|
||||||
def read_point(self):
|
def read_point(self):
|
||||||
x = self.read_number()
|
x = self.read_float()
|
||||||
y = self.read_number()
|
y = self.read_float()
|
||||||
return self.transform(x, y)
|
return self.transform(x, y)
|
||||||
|
|
||||||
def read_text(self):
|
def read_text(self):
|
||||||
num = self.read_number()
|
num = self.read_int()
|
||||||
pos = self.buf.find("-", self.pos) + 1
|
pos = self.buf.find("-", self.pos) + 1
|
||||||
self.pos = pos + num
|
self.pos = pos + num
|
||||||
res = self.buf[pos:self.pos]
|
res = self.buf[pos:self.pos]
|
||||||
|
@ -494,7 +558,7 @@ class XDotAttrParser:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def read_polygon(self):
|
def read_polygon(self):
|
||||||
n = self.read_number()
|
n = self.read_int()
|
||||||
p = []
|
p = []
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
x, y = self.read_point()
|
x, y = self.read_point()
|
||||||
|
@ -521,6 +585,9 @@ class XDotAttrParser:
|
||||||
r, g, b = colorsys.hsv_to_rgb(h, s, v)
|
r, g, b = colorsys.hsv_to_rgb(h, s, v)
|
||||||
a = 1.0
|
a = 1.0
|
||||||
return r, g, b, a
|
return r, g, b, a
|
||||||
|
elif c1 == "[":
|
||||||
|
sys.stderr.write('warning: color gradients not supported yet\n')
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
return self.lookup_color(c)
|
return self.lookup_color(c)
|
||||||
|
|
||||||
|
@ -550,7 +617,7 @@ class XDotAttrParser:
|
||||||
a = 1.0
|
a = 1.0
|
||||||
return r, g, b, a
|
return r, g, b, a
|
||||||
|
|
||||||
sys.stderr.write("unknown color '%s'\n" % c)
|
sys.stderr.write("warning: unknown color '%s'\n" % c)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
|
@ -573,7 +640,7 @@ class XDotAttrParser:
|
||||||
lw = style.split("(")[1].split(")")[0]
|
lw = style.split("(")[1].split(")")[0]
|
||||||
lw = float(lw)
|
lw = float(lw)
|
||||||
self.handle_linewidth(lw)
|
self.handle_linewidth(lw)
|
||||||
elif style in ("solid", "dashed"):
|
elif style in ("solid", "dashed", "dotted"):
|
||||||
self.handle_linestyle(style)
|
self.handle_linestyle(style)
|
||||||
elif op == "F":
|
elif op == "F":
|
||||||
size = s.read_float()
|
size = s.read_float()
|
||||||
|
@ -581,19 +648,22 @@ class XDotAttrParser:
|
||||||
self.handle_font(size, name)
|
self.handle_font(size, name)
|
||||||
elif op == "T":
|
elif op == "T":
|
||||||
x, y = s.read_point()
|
x, y = s.read_point()
|
||||||
j = s.read_number()
|
j = s.read_int()
|
||||||
w = s.read_number()
|
w = s.read_float()
|
||||||
t = s.read_text()
|
t = s.read_text()
|
||||||
self.handle_text(x, y, j, w, t)
|
self.handle_text(x, y, j, w, t)
|
||||||
|
elif op == "t":
|
||||||
|
f = s.read_int()
|
||||||
|
self.handle_font_characteristics(f)
|
||||||
elif op == "E":
|
elif op == "E":
|
||||||
x0, y0 = s.read_point()
|
x0, y0 = s.read_point()
|
||||||
w = s.read_number()
|
w = s.read_float()
|
||||||
h = s.read_number()
|
h = s.read_float()
|
||||||
self.handle_ellipse(x0, y0, w, h, filled=True)
|
self.handle_ellipse(x0, y0, w, h, filled=True)
|
||||||
elif op == "e":
|
elif op == "e":
|
||||||
x0, y0 = s.read_point()
|
x0, y0 = s.read_point()
|
||||||
w = s.read_number()
|
w = s.read_float()
|
||||||
h = s.read_number()
|
h = s.read_float()
|
||||||
self.handle_ellipse(x0, y0, w, h, filled=False)
|
self.handle_ellipse(x0, y0, w, h, filled=False)
|
||||||
elif op == "L":
|
elif op == "L":
|
||||||
points = self.read_polygon()
|
points = self.read_polygon()
|
||||||
|
@ -610,9 +680,15 @@ class XDotAttrParser:
|
||||||
elif op == "p":
|
elif op == "p":
|
||||||
points = self.read_polygon()
|
points = self.read_polygon()
|
||||||
self.handle_polygon(points, filled=False)
|
self.handle_polygon(points, filled=False)
|
||||||
|
elif op == "I":
|
||||||
|
x0, y0 = s.read_point()
|
||||||
|
w = s.read_float()
|
||||||
|
h = s.read_float()
|
||||||
|
path = s.read_text()
|
||||||
|
self.handle_image(x0, y0, w, h, path)
|
||||||
else:
|
else:
|
||||||
sys.stderr.write("unknown xdot opcode '%s'\n" % op)
|
sys.stderr.write("error: unknown xdot opcode '%s'\n" % op)
|
||||||
break
|
sys.exit(1)
|
||||||
|
|
||||||
return self.shapes
|
return self.shapes
|
||||||
|
|
||||||
|
@ -633,11 +709,18 @@ class XDotAttrParser:
|
||||||
self.pen.dash = ()
|
self.pen.dash = ()
|
||||||
elif style == "dashed":
|
elif style == "dashed":
|
||||||
self.pen.dash = (6, ) # 6pt on, 6pt off
|
self.pen.dash = (6, ) # 6pt on, 6pt off
|
||||||
|
elif style == "dotted":
|
||||||
|
self.pen.dash = (2, 4) # 2pt on, 4pt off
|
||||||
|
|
||||||
def handle_font(self, size, name):
|
def handle_font(self, size, name):
|
||||||
self.pen.fontsize = size
|
self.pen.fontsize = size
|
||||||
self.pen.fontname = name
|
self.pen.fontname = name
|
||||||
|
|
||||||
|
def handle_font_characteristics(self, flags):
|
||||||
|
# TODO
|
||||||
|
if flags != 0:
|
||||||
|
sys.stderr.write("warning: font characteristics not supported yet\n" % op)
|
||||||
|
|
||||||
def handle_text(self, x, y, j, w, t):
|
def handle_text(self, x, y, j, w, t):
|
||||||
self.shapes.append(TextShape(self.pen, x, y, j, w, t))
|
self.shapes.append(TextShape(self.pen, x, y, j, w, t))
|
||||||
|
|
||||||
|
@ -647,6 +730,9 @@ class XDotAttrParser:
|
||||||
self.shapes.append(EllipseShape(self.pen, x0, y0, w, h, filled=True))
|
self.shapes.append(EllipseShape(self.pen, x0, y0, w, h, filled=True))
|
||||||
self.shapes.append(EllipseShape(self.pen, x0, y0, w, h))
|
self.shapes.append(EllipseShape(self.pen, x0, y0, w, h))
|
||||||
|
|
||||||
|
def handle_image(self, x0, y0, w, h, path):
|
||||||
|
self.shapes.append(ImageShape(self.pen, x0, y0, w, h, path))
|
||||||
|
|
||||||
def handle_line(self, points):
|
def handle_line(self, points):
|
||||||
self.shapes.append(LineShape(self.pen, points))
|
self.shapes.append(LineShape(self.pen, points))
|
||||||
|
|
||||||
|
@ -922,10 +1008,11 @@ class DotLexer(Lexer):
|
||||||
text = text.replace('\\\r', '')
|
text = text.replace('\\\r', '')
|
||||||
text = text.replace('\\\n', '')
|
text = text.replace('\\\n', '')
|
||||||
|
|
||||||
text = text.replace('\\r', '\r')
|
# quotes
|
||||||
text = text.replace('\\n', '\n')
|
text = text.replace('\\"', '"')
|
||||||
text = text.replace('\\t', '\t')
|
|
||||||
text = text.replace('\\', '')
|
# layout engines recognize other escape codes (many non-standard)
|
||||||
|
# but we don't translate them here
|
||||||
|
|
||||||
type = ID
|
type = ID
|
||||||
|
|
||||||
|
@ -1059,6 +1146,8 @@ class DotParser(Parser):
|
||||||
|
|
||||||
class XDotParser(DotParser):
|
class XDotParser(DotParser):
|
||||||
|
|
||||||
|
XDOTVERSION = '1.6'
|
||||||
|
|
||||||
def __init__(self, xdotcode):
|
def __init__(self, xdotcode):
|
||||||
lexer = DotLexer(buf = xdotcode)
|
lexer = DotLexer(buf = xdotcode)
|
||||||
DotParser.__init__(self, lexer)
|
DotParser.__init__(self, lexer)
|
||||||
|
@ -1071,26 +1160,34 @@ class XDotParser(DotParser):
|
||||||
|
|
||||||
def handle_graph(self, attrs):
|
def handle_graph(self, attrs):
|
||||||
if self.top_graph:
|
if self.top_graph:
|
||||||
|
# Check xdot version
|
||||||
|
try:
|
||||||
|
xdotversion = attrs['xdotversion']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if float(xdotversion) > float(self.XDOTVERSION):
|
||||||
|
sys.stderr.write('warning: xdot version %s, but supported is %s\n' % (xdotversion, self.XDOTVERSION))
|
||||||
|
|
||||||
|
# Parse bounding box
|
||||||
try:
|
try:
|
||||||
bb = attrs['bb']
|
bb = attrs['bb']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not bb:
|
if bb:
|
||||||
return
|
xmin, ymin, xmax, ymax = map(float, bb.split(","))
|
||||||
|
|
||||||
xmin, ymin, xmax, ymax = map(float, bb.split(","))
|
self.xoffset = -xmin
|
||||||
|
self.yoffset = -ymax
|
||||||
|
self.xscale = 1.0
|
||||||
|
self.yscale = -1.0
|
||||||
|
# FIXME: scale from points to pixels
|
||||||
|
|
||||||
self.xoffset = -xmin
|
self.width = max(xmax - xmin, 1)
|
||||||
self.yoffset = -ymax
|
self.height = max(ymax - ymin, 1)
|
||||||
self.xscale = 1.0
|
|
||||||
self.yscale = -1.0
|
|
||||||
# FIXME: scale from points to pixels
|
|
||||||
|
|
||||||
self.width = xmax - xmin
|
self.top_graph = False
|
||||||
self.height = ymax - ymin
|
|
||||||
|
|
||||||
self.top_graph = False
|
|
||||||
|
|
||||||
for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"):
|
for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"):
|
||||||
if attr in attrs:
|
if attr in attrs:
|
||||||
|
@ -1104,15 +1201,15 @@ class XDotParser(DotParser):
|
||||||
return
|
return
|
||||||
|
|
||||||
x, y = self.parse_node_pos(pos)
|
x, y = self.parse_node_pos(pos)
|
||||||
w = float(attrs['width'])*72
|
w = float(attrs.get('width', 0))*72
|
||||||
h = float(attrs['height'])*72
|
h = float(attrs.get('height', 0))*72
|
||||||
shapes = []
|
shapes = []
|
||||||
for attr in ("_draw_", "_ldraw_"):
|
for attr in ("_draw_", "_ldraw_"):
|
||||||
if attr in attrs:
|
if attr in attrs:
|
||||||
parser = XDotAttrParser(self, attrs[attr])
|
parser = XDotAttrParser(self, attrs[attr])
|
||||||
shapes.extend(parser.parse())
|
shapes.extend(parser.parse())
|
||||||
url = attrs.get('URL', None)
|
url = attrs.get('URL', None)
|
||||||
node = Node(x, y, w, h, shapes, url)
|
node = Node(id, x, y, w, h, shapes, url)
|
||||||
self.node_by_name[id] = node
|
self.node_by_name[id] = node
|
||||||
if shapes:
|
if shapes:
|
||||||
self.nodes.append(node)
|
self.nodes.append(node)
|
||||||
|
@ -1399,6 +1496,9 @@ class DotWidget(gtk.DrawingArea):
|
||||||
self.connect("size-allocate", self.on_area_size_allocate)
|
self.connect("size-allocate", self.on_area_size_allocate)
|
||||||
|
|
||||||
self.connect('key-press-event', self.on_key_press_event)
|
self.connect('key-press-event', self.on_key_press_event)
|
||||||
|
self.last_mtime = None
|
||||||
|
|
||||||
|
gobject.timeout_add(1000, self.update)
|
||||||
|
|
||||||
self.x, self.y = 0.0, 0.0
|
self.x, self.y = 0.0, 0.0
|
||||||
self.zoom_ratio = 1.0
|
self.zoom_ratio = 1.0
|
||||||
|
@ -1411,9 +1511,9 @@ class DotWidget(gtk.DrawingArea):
|
||||||
def set_filter(self, filter):
|
def set_filter(self, filter):
|
||||||
self.filter = filter
|
self.filter = filter
|
||||||
|
|
||||||
def set_dotcode(self, dotcode, filename='<stdin>'):
|
def run_filter(self, dotcode):
|
||||||
if isinstance(dotcode, unicode):
|
if not self.filter:
|
||||||
dotcode = dotcode.encode('utf8')
|
return dotcode
|
||||||
p = subprocess.Popen(
|
p = subprocess.Popen(
|
||||||
[self.filter, '-Txdot'],
|
[self.filter, '-Txdot'],
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
|
@ -1423,6 +1523,7 @@ class DotWidget(gtk.DrawingArea):
|
||||||
universal_newlines=True
|
universal_newlines=True
|
||||||
)
|
)
|
||||||
xdotcode, error = p.communicate(dotcode)
|
xdotcode, error = p.communicate(dotcode)
|
||||||
|
sys.stderr.write(error)
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
|
dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
|
||||||
message_format=error,
|
message_format=error,
|
||||||
|
@ -1430,10 +1531,19 @@ class DotWidget(gtk.DrawingArea):
|
||||||
dialog.set_title('Dot Viewer')
|
dialog.set_title('Dot Viewer')
|
||||||
dialog.run()
|
dialog.run()
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
|
return None
|
||||||
|
return xdotcode
|
||||||
|
|
||||||
|
def set_dotcode(self, dotcode, filename=None):
|
||||||
|
self.openfilename = None
|
||||||
|
if isinstance(dotcode, unicode):
|
||||||
|
dotcode = dotcode.encode('utf8')
|
||||||
|
xdotcode = self.run_filter(dotcode)
|
||||||
|
if xdotcode is None:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
self.set_xdotcode(xdotcode)
|
self.set_xdotcode(xdotcode)
|
||||||
except ParseError, ex:
|
except ParseError as ex:
|
||||||
dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
|
dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
|
||||||
message_format=str(ex),
|
message_format=str(ex),
|
||||||
buttons=gtk.BUTTONS_OK)
|
buttons=gtk.BUTTONS_OK)
|
||||||
|
@ -1442,11 +1552,14 @@ class DotWidget(gtk.DrawingArea):
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
if filename is None:
|
||||||
|
self.last_mtime = None
|
||||||
|
else:
|
||||||
|
self.last_mtime = os.stat(filename).st_mtime
|
||||||
self.openfilename = filename
|
self.openfilename = filename
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_xdotcode(self, xdotcode):
|
def set_xdotcode(self, xdotcode):
|
||||||
#print xdotcode
|
|
||||||
parser = XDotParser(xdotcode)
|
parser = XDotParser(xdotcode)
|
||||||
self.graph = parser.parse()
|
self.graph = parser.parse()
|
||||||
self.zoom_image(self.zoom_ratio, center=True)
|
self.zoom_image(self.zoom_ratio, center=True)
|
||||||
|
@ -1460,6 +1573,14 @@ class DotWidget(gtk.DrawingArea):
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if self.openfilename is not None:
|
||||||
|
current_mtime = os.stat(self.openfilename).st_mtime
|
||||||
|
if current_mtime != self.last_mtime:
|
||||||
|
self.last_mtime = current_mtime
|
||||||
|
self.reload()
|
||||||
|
return True
|
||||||
|
|
||||||
def do_expose_event(self, event):
|
def do_expose_event(self, event):
|
||||||
cr = self.window.cairo_create()
|
cr = self.window.cairo_create()
|
||||||
|
|
||||||
|
@ -1500,6 +1621,10 @@ class DotWidget(gtk.DrawingArea):
|
||||||
self.queue_draw()
|
self.queue_draw()
|
||||||
|
|
||||||
def zoom_image(self, zoom_ratio, center=False, pos=None):
|
def zoom_image(self, zoom_ratio, center=False, pos=None):
|
||||||
|
# Constrain zoom ratio to a sane range to prevent numeric instability.
|
||||||
|
zoom_ratio = min(zoom_ratio, 1E4)
|
||||||
|
zoom_ratio = max(zoom_ratio, 1E-6)
|
||||||
|
|
||||||
if center:
|
if center:
|
||||||
self.x = self.graph.width/2
|
self.x = self.graph.width/2
|
||||||
self.y = self.graph.height/2
|
self.y = self.graph.height/2
|
||||||
|
@ -1518,10 +1643,13 @@ class DotWidget(gtk.DrawingArea):
|
||||||
rect = self.get_allocation()
|
rect = self.get_allocation()
|
||||||
width = abs(x1 - x2)
|
width = abs(x1 - x2)
|
||||||
height = abs(y1 - y2)
|
height = abs(y1 - y2)
|
||||||
self.zoom_ratio = min(
|
if width == 0 and height == 0:
|
||||||
float(rect.width)/float(width),
|
self.zoom_ratio *= self.ZOOM_INCREMENT
|
||||||
float(rect.height)/float(height)
|
else:
|
||||||
)
|
self.zoom_ratio = min(
|
||||||
|
float(rect.width)/float(width),
|
||||||
|
float(rect.height)/float(height)
|
||||||
|
)
|
||||||
self.zoom_to_fit_on_resize = False
|
self.zoom_to_fit_on_resize = False
|
||||||
self.x = (x1 + x2) / 2
|
self.x = (x1 + x2) / 2
|
||||||
self.y = (y1 + y2) / 2
|
self.y = (y1 + y2) / 2
|
||||||
|
@ -1574,11 +1702,16 @@ class DotWidget(gtk.DrawingArea):
|
||||||
self.y += self.POS_INCREMENT/self.zoom_ratio
|
self.y += self.POS_INCREMENT/self.zoom_ratio
|
||||||
self.queue_draw()
|
self.queue_draw()
|
||||||
return True
|
return True
|
||||||
if event.keyval == gtk.keysyms.Page_Up:
|
if event.keyval in (gtk.keysyms.Page_Up,
|
||||||
|
gtk.keysyms.plus,
|
||||||
|
gtk.keysyms.equal,
|
||||||
|
gtk.keysyms.KP_Add):
|
||||||
self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT)
|
self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT)
|
||||||
self.queue_draw()
|
self.queue_draw()
|
||||||
return True
|
return True
|
||||||
if event.keyval == gtk.keysyms.Page_Down:
|
if event.keyval in (gtk.keysyms.Page_Down,
|
||||||
|
gtk.keysyms.minus,
|
||||||
|
gtk.keysyms.KP_Subtract):
|
||||||
self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT)
|
self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT)
|
||||||
self.queue_draw()
|
self.queue_draw()
|
||||||
return True
|
return True
|
||||||
|
@ -1589,11 +1722,49 @@ class DotWidget(gtk.DrawingArea):
|
||||||
if event.keyval == gtk.keysyms.r:
|
if event.keyval == gtk.keysyms.r:
|
||||||
self.reload()
|
self.reload()
|
||||||
return True
|
return True
|
||||||
|
if event.keyval == gtk.keysyms.f:
|
||||||
|
win = widget.get_toplevel()
|
||||||
|
find_toolitem = win.uimanager.get_widget('/ToolBar/Find')
|
||||||
|
textentry = find_toolitem.get_children()
|
||||||
|
win.set_focus(textentry[0])
|
||||||
|
return True
|
||||||
if event.keyval == gtk.keysyms.q:
|
if event.keyval == gtk.keysyms.q:
|
||||||
gtk.main_quit()
|
gtk.main_quit()
|
||||||
return True
|
return True
|
||||||
|
if event.keyval == gtk.keysyms.p:
|
||||||
|
self.on_print()
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
print_settings = None
|
||||||
|
def on_print(self, action=None):
|
||||||
|
print_op = gtk.PrintOperation()
|
||||||
|
|
||||||
|
if self.print_settings != None:
|
||||||
|
print_op.set_print_settings(self.print_settings)
|
||||||
|
|
||||||
|
print_op.connect("begin_print", self.begin_print)
|
||||||
|
print_op.connect("draw_page", self.draw_page)
|
||||||
|
|
||||||
|
res = print_op.run(gtk.PRINT_OPERATION_ACTION_PRINT_DIALOG, self.parent.parent)
|
||||||
|
|
||||||
|
if res == gtk.PRINT_OPERATION_RESULT_APPLY:
|
||||||
|
print_settings = print_op.get_print_settings()
|
||||||
|
|
||||||
|
def begin_print(self, operation, context):
|
||||||
|
operation.set_n_pages(1)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def draw_page(self, operation, context, page_nr):
|
||||||
|
cr = context.get_cairo_context()
|
||||||
|
|
||||||
|
rect = self.get_allocation()
|
||||||
|
cr.translate(0.5*rect.width, 0.5*rect.height)
|
||||||
|
cr.scale(self.zoom_ratio, self.zoom_ratio)
|
||||||
|
cr.translate(-self.x, -self.y)
|
||||||
|
|
||||||
|
self.graph.draw(cr, highlight_items=self.highlight)
|
||||||
|
|
||||||
def get_drag_action(self, event):
|
def get_drag_action(self, event):
|
||||||
state = event.state
|
state = event.state
|
||||||
if event.button in (1, 2): # left or middle button
|
if event.button in (1, 2): # left or middle button
|
||||||
|
@ -1628,20 +1799,32 @@ class DotWidget(gtk.DrawingArea):
|
||||||
return (time.time() < self.presstime + click_timeout
|
return (time.time() < self.presstime + click_timeout
|
||||||
and math.hypot(deltax, deltay) < click_fuzz)
|
and math.hypot(deltax, deltay) < click_fuzz)
|
||||||
|
|
||||||
|
def on_click(self, element, event):
|
||||||
|
"""Override this method in subclass to process
|
||||||
|
click events. Note that element can be None
|
||||||
|
(click on empty space)."""
|
||||||
|
return False
|
||||||
|
|
||||||
def on_area_button_release(self, area, event):
|
def on_area_button_release(self, area, event):
|
||||||
self.drag_action.on_button_release(event)
|
self.drag_action.on_button_release(event)
|
||||||
self.drag_action = NullAction(self)
|
self.drag_action = NullAction(self)
|
||||||
if event.button == 1 and self.is_click(event):
|
x, y = int(event.x), int(event.y)
|
||||||
x, y = int(event.x), int(event.y)
|
if self.is_click(event):
|
||||||
url = self.get_url(x, y)
|
el = self.get_element(x, y)
|
||||||
if url is not None:
|
if self.on_click(el, event):
|
||||||
self.emit('clicked', unicode(url.url), event)
|
return True
|
||||||
else:
|
|
||||||
jump = self.get_jump(x, y)
|
if event.button == 1:
|
||||||
if jump is not None:
|
url = self.get_url(x, y)
|
||||||
self.animate_to(jump.x, jump.y)
|
if url is not None:
|
||||||
|
self.emit('clicked', unicode(url.url), event)
|
||||||
|
else:
|
||||||
|
jump = self.get_jump(x, y)
|
||||||
|
if jump is not None:
|
||||||
|
self.animate_to(jump.x, jump.y)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
return True
|
|
||||||
if event.button == 1 or event.button == 2:
|
if event.button == 1 or event.button == 2:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
@ -1679,6 +1862,10 @@ class DotWidget(gtk.DrawingArea):
|
||||||
y += self.y
|
y += self.y
|
||||||
return x, y
|
return x, y
|
||||||
|
|
||||||
|
def get_element(self, x, y):
|
||||||
|
x, y = self.window2graph(x, y)
|
||||||
|
return self.graph.get_element(x, y)
|
||||||
|
|
||||||
def get_url(self, x, y):
|
def get_url(self, x, y):
|
||||||
x, y = self.window2graph(x, y)
|
x, y = self.window2graph(x, y)
|
||||||
return self.graph.get_url(x, y)
|
return self.graph.get_url(x, y)
|
||||||
|
@ -1688,6 +1875,14 @@ class DotWidget(gtk.DrawingArea):
|
||||||
return self.graph.get_jump(x, y)
|
return self.graph.get_jump(x, y)
|
||||||
|
|
||||||
|
|
||||||
|
class FindMenuToolAction(gtk.Action):
|
||||||
|
__gtype_name__ = "FindMenuToolAction"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
gtk.Action.__init__(self, *args, **kw)
|
||||||
|
self.set_tool_item_type(gtk.ToolItem)
|
||||||
|
|
||||||
|
|
||||||
class DotWindow(gtk.Window):
|
class DotWindow(gtk.Window):
|
||||||
|
|
||||||
ui = '''
|
ui = '''
|
||||||
|
@ -1695,28 +1890,33 @@ class DotWindow(gtk.Window):
|
||||||
<toolbar name="ToolBar">
|
<toolbar name="ToolBar">
|
||||||
<toolitem action="Open"/>
|
<toolitem action="Open"/>
|
||||||
<toolitem action="Reload"/>
|
<toolitem action="Reload"/>
|
||||||
|
<toolitem action="Print"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<toolitem action="ZoomIn"/>
|
<toolitem action="ZoomIn"/>
|
||||||
<toolitem action="ZoomOut"/>
|
<toolitem action="ZoomOut"/>
|
||||||
<toolitem action="ZoomFit"/>
|
<toolitem action="ZoomFit"/>
|
||||||
<toolitem action="Zoom100"/>
|
<toolitem action="Zoom100"/>
|
||||||
|
<separator/>
|
||||||
|
<toolitem name="Find" action="Find"/>
|
||||||
</toolbar>
|
</toolbar>
|
||||||
</ui>
|
</ui>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self):
|
base_title = 'Dot Viewer'
|
||||||
|
|
||||||
|
def __init__(self, widget=None):
|
||||||
gtk.Window.__init__(self)
|
gtk.Window.__init__(self)
|
||||||
|
|
||||||
self.graph = Graph()
|
self.graph = Graph()
|
||||||
|
|
||||||
window = self
|
window = self
|
||||||
|
|
||||||
window.set_title('Dot Viewer')
|
window.set_title(self.base_title)
|
||||||
window.set_default_size(512, 512)
|
window.set_default_size(512, 512)
|
||||||
vbox = gtk.VBox()
|
vbox = gtk.VBox()
|
||||||
window.add(vbox)
|
window.add(vbox)
|
||||||
|
|
||||||
self.widget = DotWidget()
|
self.widget = widget or DotWidget()
|
||||||
|
|
||||||
# Create a UIManager instance
|
# Create a UIManager instance
|
||||||
uimanager = self.uimanager = gtk.UIManager()
|
uimanager = self.uimanager = gtk.UIManager()
|
||||||
|
@ -1733,12 +1933,17 @@ class DotWindow(gtk.Window):
|
||||||
actiongroup.add_actions((
|
actiongroup.add_actions((
|
||||||
('Open', gtk.STOCK_OPEN, None, None, None, self.on_open),
|
('Open', gtk.STOCK_OPEN, None, None, None, self.on_open),
|
||||||
('Reload', gtk.STOCK_REFRESH, None, None, None, self.on_reload),
|
('Reload', gtk.STOCK_REFRESH, None, None, None, self.on_reload),
|
||||||
|
('Print', gtk.STOCK_PRINT, None, None, "Prints the currently visible part of the graph", self.widget.on_print),
|
||||||
('ZoomIn', gtk.STOCK_ZOOM_IN, None, None, None, self.widget.on_zoom_in),
|
('ZoomIn', gtk.STOCK_ZOOM_IN, None, None, None, self.widget.on_zoom_in),
|
||||||
('ZoomOut', gtk.STOCK_ZOOM_OUT, None, None, None, self.widget.on_zoom_out),
|
('ZoomOut', gtk.STOCK_ZOOM_OUT, None, None, None, self.widget.on_zoom_out),
|
||||||
('ZoomFit', gtk.STOCK_ZOOM_FIT, None, None, None, self.widget.on_zoom_fit),
|
('ZoomFit', gtk.STOCK_ZOOM_FIT, None, None, None, self.widget.on_zoom_fit),
|
||||||
('Zoom100', gtk.STOCK_ZOOM_100, None, None, None, self.widget.on_zoom_100),
|
('Zoom100', gtk.STOCK_ZOOM_100, None, None, None, self.widget.on_zoom_100),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
find_action = FindMenuToolAction("Find", None,
|
||||||
|
"Find a node by name", None)
|
||||||
|
actiongroup.add_action(find_action)
|
||||||
|
|
||||||
# Add the actiongroup to the uimanager
|
# Add the actiongroup to the uimanager
|
||||||
uimanager.insert_action_group(actiongroup, 0)
|
uimanager.insert_action_group(actiongroup, 0)
|
||||||
|
|
||||||
|
@ -1751,45 +1956,82 @@ class DotWindow(gtk.Window):
|
||||||
|
|
||||||
vbox.pack_start(self.widget)
|
vbox.pack_start(self.widget)
|
||||||
|
|
||||||
|
self.last_open_dir = "."
|
||||||
|
|
||||||
self.set_focus(self.widget)
|
self.set_focus(self.widget)
|
||||||
|
|
||||||
|
# Add Find text search
|
||||||
|
find_toolitem = uimanager.get_widget('/ToolBar/Find')
|
||||||
|
self.textentry = gtk.Entry(max=20)
|
||||||
|
self.textentry.set_icon_from_stock(0, gtk.STOCK_FIND)
|
||||||
|
find_toolitem.add(self.textentry)
|
||||||
|
|
||||||
|
self.textentry.set_activates_default(True)
|
||||||
|
self.textentry.connect ("activate", self.textentry_activate, self.textentry);
|
||||||
|
self.textentry.connect ("changed", self.textentry_changed, self.textentry);
|
||||||
|
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
|
||||||
def update(self, filename):
|
def find_text(self, entry_text):
|
||||||
import os
|
found_items = []
|
||||||
if not hasattr(self, "last_mtime"):
|
dot_widget = self.widget
|
||||||
self.last_mtime = None
|
regexp = re.compile(entry_text)
|
||||||
|
for node in dot_widget.graph.nodes:
|
||||||
|
if node.search_text(regexp):
|
||||||
|
found_items.append(node)
|
||||||
|
return found_items
|
||||||
|
|
||||||
current_mtime = os.stat(filename).st_mtime
|
def textentry_changed(self, widget, entry):
|
||||||
if current_mtime != self.last_mtime:
|
entry_text = entry.get_text()
|
||||||
self.last_mtime = current_mtime
|
dot_widget = self.widget
|
||||||
self.open_file(filename)
|
if not entry_text:
|
||||||
|
dot_widget.set_highlight(None)
|
||||||
|
return
|
||||||
|
|
||||||
return True
|
found_items = self.find_text(entry_text)
|
||||||
|
dot_widget.set_highlight(found_items)
|
||||||
|
|
||||||
|
def textentry_activate(self, widget, entry):
|
||||||
|
entry_text = entry.get_text()
|
||||||
|
dot_widget = self.widget
|
||||||
|
if not entry_text:
|
||||||
|
dot_widget.set_highlight(None)
|
||||||
|
return;
|
||||||
|
|
||||||
|
found_items = self.find_text(entry_text)
|
||||||
|
dot_widget.set_highlight(found_items)
|
||||||
|
if(len(found_items) == 1):
|
||||||
|
dot_widget.animate_to(found_items[0].x, found_items[0].y)
|
||||||
|
|
||||||
def set_filter(self, filter):
|
def set_filter(self, filter):
|
||||||
self.widget.set_filter(filter)
|
self.widget.set_filter(filter)
|
||||||
|
|
||||||
def set_dotcode(self, dotcode, filename='<stdin>'):
|
def set_dotcode(self, dotcode, filename=None):
|
||||||
if self.widget.set_dotcode(dotcode, filename):
|
if self.widget.set_dotcode(dotcode, filename):
|
||||||
self.set_title(os.path.basename(filename) + ' - Dot Viewer')
|
self.update_title(filename)
|
||||||
self.widget.zoom_to_fit()
|
self.widget.zoom_to_fit()
|
||||||
|
|
||||||
def set_xdotcode(self, xdotcode, filename='<stdin>'):
|
def set_xdotcode(self, xdotcode, filename=None):
|
||||||
if self.widget.set_xdotcode(xdotcode):
|
if self.widget.set_xdotcode(xdotcode):
|
||||||
self.set_title(os.path.basename(filename) + ' - Dot Viewer')
|
self.update_title(filename)
|
||||||
self.widget.zoom_to_fit()
|
self.widget.zoom_to_fit()
|
||||||
|
|
||||||
|
def update_title(self, filename=None):
|
||||||
|
if filename is None:
|
||||||
|
self.set_title(self.base_title)
|
||||||
|
else:
|
||||||
|
self.set_title(os.path.basename(filename) + ' - ' + self.base_title)
|
||||||
|
|
||||||
def open_file(self, filename):
|
def open_file(self, filename):
|
||||||
try:
|
try:
|
||||||
fp = file(filename, 'rt')
|
fp = file(filename, 'rt')
|
||||||
self.set_dotcode(fp.read(), filename)
|
self.set_dotcode(fp.read(), filename)
|
||||||
fp.close()
|
fp.close()
|
||||||
except IOError, ex:
|
except IOError as ex:
|
||||||
dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
|
dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
|
||||||
message_format=str(ex),
|
message_format=str(ex),
|
||||||
buttons=gtk.BUTTONS_OK)
|
buttons=gtk.BUTTONS_OK)
|
||||||
dlg.set_title('Dot Viewer')
|
dlg.set_title(self.base_title)
|
||||||
dlg.run()
|
dlg.run()
|
||||||
dlg.destroy()
|
dlg.destroy()
|
||||||
|
|
||||||
|
@ -1801,6 +2043,7 @@ class DotWindow(gtk.Window):
|
||||||
gtk.STOCK_OPEN,
|
gtk.STOCK_OPEN,
|
||||||
gtk.RESPONSE_OK))
|
gtk.RESPONSE_OK))
|
||||||
chooser.set_default_response(gtk.RESPONSE_OK)
|
chooser.set_default_response(gtk.RESPONSE_OK)
|
||||||
|
chooser.set_current_folder(self.last_open_dir)
|
||||||
filter = gtk.FileFilter()
|
filter = gtk.FileFilter()
|
||||||
filter.set_name("Graphviz dot files")
|
filter.set_name("Graphviz dot files")
|
||||||
filter.add_pattern("*.dot")
|
filter.add_pattern("*.dot")
|
||||||
|
@ -1811,6 +2054,7 @@ class DotWindow(gtk.Window):
|
||||||
chooser.add_filter(filter)
|
chooser.add_filter(filter)
|
||||||
if chooser.run() == gtk.RESPONSE_OK:
|
if chooser.run() == gtk.RESPONSE_OK:
|
||||||
filename = chooser.get_filename()
|
filename = chooser.get_filename()
|
||||||
|
self.last_open_dir = chooser.get_current_folder()
|
||||||
chooser.destroy()
|
chooser.destroy()
|
||||||
self.open_file(filename)
|
self.open_file(filename)
|
||||||
else:
|
else:
|
||||||
|
@ -1820,17 +2064,41 @@ class DotWindow(gtk.Window):
|
||||||
self.widget.reload()
|
self.widget.reload()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
class OptionParser(optparse.OptionParser):
|
||||||
import optparse
|
|
||||||
|
|
||||||
parser = optparse.OptionParser(
|
def format_epilog(self, formatter):
|
||||||
|
# Prevent stripping the newlines in epilog message
|
||||||
|
# http://stackoverflow.com/questions/1857346/python-optparse-how-to-include-additional-info-in-usage-output
|
||||||
|
return self.epilog
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
parser = OptionParser(
|
||||||
usage='\n\t%prog [file]',
|
usage='\n\t%prog [file]',
|
||||||
version='%%prog %s' % __version__)
|
epilog='''
|
||||||
|
Shortcuts:
|
||||||
|
Up, Down, Left, Right scroll
|
||||||
|
PageUp, +, = zoom in
|
||||||
|
PageDown, - zoom out
|
||||||
|
R reload dot file
|
||||||
|
F find
|
||||||
|
Q quit
|
||||||
|
P print
|
||||||
|
Escape halt animation
|
||||||
|
Ctrl-drag zoom in/out
|
||||||
|
Shift-drag zooms an area
|
||||||
|
'''
|
||||||
|
)
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'-f', '--filter',
|
'-f', '--filter',
|
||||||
type='choice', choices=('dot', 'neato', 'twopi', 'circo', 'fdp'),
|
type='choice', choices=('dot', 'neato', 'twopi', 'circo', 'fdp'),
|
||||||
dest='filter', default='dot',
|
dest='filter', default='dot',
|
||||||
help='graphviz filter: dot, neato, twopi, circo, or fdp [default: %default]')
|
help='graphviz filter: dot, neato, twopi, circo, or fdp [default: %default]')
|
||||||
|
parser.add_option(
|
||||||
|
'-n', '--no-filter',
|
||||||
|
action='store_const', const=None, dest='filter',
|
||||||
|
help='assume input is already filtered into xdot format (use e.g. dot -Txdot)')
|
||||||
|
|
||||||
(options, args) = parser.parse_args(sys.argv[1:])
|
(options, args) = parser.parse_args(sys.argv[1:])
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
|
@ -1839,12 +2107,14 @@ def main():
|
||||||
win = DotWindow()
|
win = DotWindow()
|
||||||
win.connect('destroy', gtk.main_quit)
|
win.connect('destroy', gtk.main_quit)
|
||||||
win.set_filter(options.filter)
|
win.set_filter(options.filter)
|
||||||
if len(args) >= 1:
|
if len(args) == 0:
|
||||||
|
if not sys.stdin.isatty():
|
||||||
|
win.set_dotcode(sys.stdin.read())
|
||||||
|
else:
|
||||||
if args[0] == '-':
|
if args[0] == '-':
|
||||||
win.set_dotcode(sys.stdin.read())
|
win.set_dotcode(sys.stdin.read())
|
||||||
else:
|
else:
|
||||||
win.open_file(args[0])
|
win.open_file(args[0])
|
||||||
gobject.timeout_add(1000, win.update, args[0])
|
|
||||||
gtk.main()
|
gtk.main()
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user