bugfixes, etc, this is super alpha branch so your patch notes are the diff

This commit is contained in:
Rob Glew 2017-05-06 20:28:44 -05:00
parent d5dbf7b29f
commit 469cb9f52d
30 changed files with 1253 additions and 559 deletions

View file

@ -4,7 +4,8 @@ import json
default_config = """{
"listeners": [
{"iface": "127.0.0.1", "port": 8080}
]
],
"proxy": {"use_proxy": false, "host": "", "port": 0, "is_socks": false}
}"""
@ -12,6 +13,7 @@ class ProxyConfig:
def __init__(self):
self._listeners = [('127.0.0.1', '8080')]
self._proxy = {'use_proxy': False, 'host': '', 'port': 0, 'is_socks': False}
def load(self, fname):
try:
@ -39,6 +41,10 @@ class ProxyConfig:
iface = '127.0.0.1'
self._listeners.append((iface, port))
if 'proxy' in config_info:
self._proxy = config_info['proxy']
@property
def listeners(self):
@ -47,3 +53,67 @@ class ProxyConfig:
@listeners.setter
def listeners(self, val):
self._listeners = val
@property
def proxy(self):
# don't use this, use the getters to get the parsed values
return self._proxy
@proxy.setter
def proxy(self, val):
self._proxy = val
@property
def use_proxy(self):
if self._proxy is None:
return False
if 'use_proxy' in self._proxy:
if self._proxy['use_proxy']:
return True
return False
@property
def proxy_host(self):
if self._proxy is None:
return ''
if 'host' in self._proxy:
return self._proxy['host']
return ''
@property
def proxy_port(self):
if self._proxy is None:
return ''
if 'port' in self._proxy:
return self._proxy['port']
return ''
@property
def proxy_username(self):
if self._proxy is None:
return ''
if 'username' in self._proxy:
return self._proxy['username']
return ''
@property
def proxy_password(self):
if self._proxy is None:
return ''
if 'password' in self._proxy:
return self._proxy['password']
return ''
@property
def use_proxy_creds(self):
return ('username' in self._proxy or 'password' in self._proxy)
@property
def is_socks_proxy(self):
if self._proxy is None:
return False
if 'is_socks' in self._proxy:
if self._proxy['is_socks']:
return True
return False

View file

@ -1,6 +1,6 @@
from itertools import groupby
from ..proxy import InvalidQuery
from ..proxy import InvalidQuery, time_to_nsecs
from ..colors import Colors, Styles
# class BuiltinFilters(object):
@ -71,6 +71,11 @@ def filtercmd(client, args):
"""
try:
phrases = [list(group) for k, group in groupby(args, lambda x: x == "OR") if not k]
for phrase in phrases:
# we do before/after by id not by timestamp
if phrase[0] in ('before', 'b4', 'after', 'af') and len(phrase) > 1:
r = client.req_by_id(phrase[1], headers_only=True)
phrase[1] = str(time_to_nsecs(r.time_start))
client.context.apply_phrase(phrases)
except InvalidQuery as e:
print(e)

View file

@ -7,31 +7,32 @@ import string
import urllib
from ..util import hexdump, printable_data, copy_to_clipboard, clipboard_contents, encode_basic_auth, parse_basic_auth
from ..console import CommandError
from io import StringIO
def print_maybe_bin(s):
binary = False
for c in s:
if str(c) not in string.printable:
if chr(c) not in string.printable:
binary = True
break
if binary:
print(hexdump(s))
else:
print(s)
print(s.decode())
def asciihex_encode_helper(s):
return ''.join('{0:x}'.format(c) for c in s)
return ''.join('{0:x}'.format(c) for c in s).encode()
def asciihex_decode_helper(s):
ret = []
try:
for a, b in zip(s[0::2], s[1::2]):
c = a+b
c = chr(a)+chr(b)
ret.append(chr(int(c, 16)))
return ''.join(ret)
return ''.join(ret).encode()
except Exception as e:
raise PappyException(e)
raise CommandError(e)
def gzip_encode_helper(s):
out = StringIO.StringIO()
@ -54,13 +55,21 @@ def base64_decode_helper(s):
return s_padded
except:
pass
raise PappyException("Unable to base64 decode string")
raise CommandError("Unable to base64 decode string")
def url_decode_helper(s):
bs = s.decode()
return urllib.parse.unquote(bs).encode()
def url_encode_helper(s):
bs = s.decode()
return urllib.parse.quote_plus(bs).encode()
def html_encode_helper(s):
return ''.join(['&#x{0:x};'.format(c) for c in s])
return ''.join(['&#x{0:x};'.format(c) for c in s]).encode()
def html_decode_helper(s):
return html.unescape(s)
return html.unescape(s.decode()).encode()
def _code_helper(args, func, copy=True):
if len(args) == 0:
@ -107,7 +116,7 @@ def url_decode(client, args):
If no string is given, will decode the contents of the clipboard.
Results are copied to the clipboard.
"""
print_maybe_bin(_code_helper(args, urllib.unquote))
print_maybe_bin(_code_helper(args, url_decode_helper))
def url_encode(client, args):
"""
@ -115,7 +124,7 @@ def url_encode(client, args):
If no string is given, will encode the contents of the clipboard.
Results are copied to the clipboard.
"""
print_maybe_bin(_code_helper(args, urllib.quote_plus))
print_maybe_bin(_code_helper(args, url_encode_helper))
def asciihex_decode(client, args):
"""
@ -187,7 +196,7 @@ def url_decode_raw(client, args):
results will not be copied. It is suggested you redirect the output
to a file.
"""
print(_code_helper(args, urllib.unquote, copy=False))
print(_code_helper(args, url_decode_helper, copy=False))
def url_encode_raw(client, args):
"""
@ -195,7 +204,7 @@ def url_encode_raw(client, args):
results will not be copied. It is suggested you redirect the output
to a file.
"""
print(_code_helper(args, urllib.quote_plus, copy=False))
print(_code_helper(args, url_encode_helper, copy=False))
def asciihex_decode_raw(client, args):
"""
@ -254,9 +263,8 @@ def unix_time_decode(client, args):
print(_code_helper(args, unix_time_decode_helper))
def http_auth_encode(client, args):
args = shlex.split(args[0])
if len(args) != 2:
raise PappyException('Usage: http_auth_encode <username> <password>')
raise CommandError('Usage: http_auth_encode <username> <password>')
username, password = args
print(encode_basic_auth(username, password))

View file

@ -23,7 +23,7 @@ class WatchMacro(InterceptMacro):
printstr = "< "
printstr += verb_color(request.method) + request.method + Colors.ENDC + ' '
printstr += url_formatter(request, colored=True)
printstr += " -> "
printstr += " \u2192 "
response_code = str(response.status_code) + ' ' + response.reason
response_code = scode_color(response_code) + response_code + Colors.ENDC
printstr += response_code

View file

@ -1524,7 +1524,7 @@ def update_buffers(req):
# Save the port, ssl, host setting
vim.command("let s:dest_port=%d" % req.dest_port)
vim.command("let s:dest_host='%s'" % req.dest_host)
vim.command("let s:dest_host='%s'" % escape(req.dest_host))
if req.use_tls:
vim.command("let s:use_tls=1")
@ -1544,6 +1544,8 @@ def set_up_windows():
reqid = vim.eval("a:2")
storage_id = vim.eval("a:3")
msg_addr = vim.eval("a:4")
vim.command("let s:storage_id=%d" % int(storage_id))
# Get the left buffer
vim.command("new")
@ -1568,11 +1570,12 @@ def dest_loc():
dest_host = vim.eval("s:dest_host")
dest_port = int(vim.eval("s:dest_port"))
tls_num = vim.eval("s:use_tls")
storage_id = int(vim.eval("s:storage_id"))
if tls_num == "1":
use_tls = True
else:
use_tls = False
return (dest_host, dest_port, use_tls)
return (dest_host, dest_port, use_tls, storage_id)
def submit_current_buffer():
curbuf = vim.current.buffer
@ -1586,14 +1589,15 @@ def submit_current_buffer():
full_request = '\n'.join(curbuf)
req = parse_request(full_request)
dest_host, dest_port, use_tls = dest_loc()
dest_host, dest_port, use_tls, storage_id = dest_loc()
req.dest_host = dest_host
req.dest_port = dest_port
req.use_tls = use_tls
comm_type, comm_addr = get_conn_addr()
with ProxyConnection(kind=comm_type, addr=comm_addr) as conn:
new_req = conn.submit(req)
new_req = conn.submit(req, storage=storage_id)
conn.add_tag(new_req.db_id, "repeater", storage_id)
update_buffers(new_req)
# (left, right) = set_up_windows()

View file

@ -481,17 +481,23 @@ def site_map(client, args):
paths = True
else:
paths = False
reqs = client.in_context_requests(headers_only=True)
paths_set = set()
for req in reqs:
if req.response and req.response.status_code != 404:
paths_set.add(path_tuple(req.url))
tree = sorted(list(paths_set))
if paths:
for p in tree:
print ('/'.join(list(p)))
else:
print_tree(tree)
all_reqs = client.in_context_requests(headers_only=True)
reqs_by_host = {}
for req in all_reqs:
reqs_by_host.setdefault(req.dest_host, []).append(req)
for host, reqs in reqs_by_host.items():
paths_set = set()
for req in reqs:
if req.response and req.response.status_code != 404:
paths_set.add(path_tuple(req.url))
tree = sorted(list(paths_set))
print(host)
if paths:
for p in tree:
print ('/'.join(list(p)))
else:
print_tree(tree)
print("")
def dump_response(client, args):
"""
@ -515,6 +521,78 @@ def dump_response(client, args):
else:
print('Request {} does not have a response'.format(req.reqid))
def get_surrounding_lines(s, n, lines):
left = n
right = n
lines_left = 0
lines_right = 0
# move left until we find enough lines or hit the edge
while left > 0 and lines_left < lines:
if s[left] == '\n':
lines_left += 1
left -= 1
# move right until we find enough lines or hit the edge
while right < len(s) and lines_right < lines:
if s[right] == '\n':
lines_right += 1
right += 1
return s[left:right]
def print_search_header(reqid, locstr):
printstr = Styles.TABLE_HEADER
printstr += "Result(s) for request {} ({})".format(reqid, locstr)
printstr += Colors.ENDC
print(printstr)
def highlight_str(s, substr):
highlighted = Colors.BGYELLOW + Colors.BLACK + Colors.BOLD + substr + Colors.ENDC
return s.replace(substr, highlighted)
def search_message(mes, substr, lines, reqid, locstr):
header_printed = False
for m in re.finditer(substr, mes):
if not header_printed:
print_search_header(reqid, locstr)
header_printed = True
n = m.start()
linestr = get_surrounding_lines(mes, n, lines)
linelist = linestr.split('\n')
linestr = '\n'.join(line[:500] for line in linelist)
toprint = highlight_str(linestr, substr)
print(toprint)
print('-'*50)
def search(client, args):
search_str = args[0]
lines = 2
if len(args) > 1:
lines = int(args[1])
for req in client.in_context_requests_iter():
reqid = client.get_reqid(req)
reqheader_printed = False
try:
mes = req.full_message().decode()
search_message(mes, search_str, lines, reqid, "Request")
except UnicodeDecodeError:
pass
if req.response:
try:
mes = req.response.full_message().decode()
search_message(mes, search_str, lines, reqid, "Response")
except UnicodeDecodeError:
pass
wsheader_printed = False
for wsm in req.ws_messages:
if not wsheader_printed:
print_search_header(client.get_reqid(req), reqid, "Websocket Messages")
wsheader_printed = True
if search_str in wsm.message:
print(highlight_str(wsm.message, search_str))
# @crochet.wait_for(timeout=None)
# @defer.inlineCallbacks
@ -572,6 +650,7 @@ def load_cmds(cmd):
'urls': (find_urls, None),
'site_map': (site_map, None),
'dump_response': (dump_response, None),
'search': (search, None),
# 'view_request_bytes': (view_request_bytes, None),
# 'view_response_bytes': (view_response_bytes, None),
})

View file

@ -85,17 +85,23 @@ class SockBuffer:
class Headers:
def __init__(self, headers=None):
if headers is None:
self.headers = {}
else:
self.headers = headers
self.headers = {}
if headers is not None:
if isinstance(headers, Headers):
for _, pairs in headers.headers.items():
for k, v in pairs:
self.add(k, v)
else:
for k, vs in headers.items():
for v in vs:
self.add(k, v)
def __contains__(self, hd):
for k, _ in self.headers.items():
if k.lower() == hd.lower():
return True
return False
def add(self, k, v):
try:
l = self.headers[k.lower()]
@ -265,11 +271,7 @@ class HTTPRequest:
self.proto_major = proto_major
self.proto_minor = proto_minor
self.headers = Headers()
if headers is not None:
for k, vs in headers.items():
for v in vs:
self.headers.add(k, v)
self.headers = Headers(headers)
self.headers_only = headers_only
self._body = bytes()
@ -280,8 +282,8 @@ class HTTPRequest:
self.dest_host = dest_host
self.dest_port = dest_port
self.use_tls = use_tls
self.time_start = time_start or datetime.datetime(1970, 1, 1)
self.time_end = time_end or datetime.datetime(1970, 1, 1)
self.time_start = time_start
self.time_end = time_end
self.response = None
self.unmangled = None
@ -412,7 +414,7 @@ class HTTPRequest:
path=self.url.geturl(),
proto_major=self.proto_major,
proto_minor=self.proto_minor,
headers=self.headers.headers,
headers=self.headers,
body=self.body,
dest_host=self.dest_host,
dest_port=self.dest_port,
@ -928,6 +930,21 @@ class ProxyConnection:
for ss in result["Storages"]:
ret.append(SavedStorage(ss["Id"], ss["Description"]))
return ret
@messagingFunction
def set_proxy(self, use_proxy=False, proxy_host="", proxy_port=0, use_creds=False,
username="", password="", is_socks=False):
cmd = {
"Command": "SetProxy",
"UseProxy": use_proxy,
"ProxyHost": proxy_host,
"ProxyPort": proxy_port,
"ProxyIsSOCKS": is_socks,
"UseCredentials": use_creds,
"Username": username,
"Password": password,
}
self.reqrsp_cmd(cmd)
@messagingFunction
def intercept(self, macro):
@ -1086,6 +1103,7 @@ class ProxyClient:
# "add_in_memory_storage",
# "close_storage",
# "set_proxy_storage",
"set_proxy"
}
def __enter__(self):
@ -1162,7 +1180,7 @@ class ProxyClient:
stype, prefix = s.description.split("|")
storage = ActiveStorage(stype, s.storage_id, prefix)
self._add_storage(storage, prefix)
def parse_reqid(self, reqid):
if reqid[0].isalpha():
prefix = reqid[0]
@ -1172,6 +1190,10 @@ class ProxyClient:
realid = reqid
storage = self.storage_by_prefix[prefix]
return storage, realid
def get_reqid(self, req):
storage = self.storage_by_id[req.storage_id]
return storage.prefix + req.db_id
def storage_iter(self):
for _, s in self.storage_by_id.items():
@ -1190,6 +1212,17 @@ class ProxyClient:
if max_results > 0 and len(results) > max_results:
ret = results[:max_results]
return ret
def in_context_requests_iter(self, headers_only=False, max_results=0):
results = self.query_storage(self.context.query,
headers_only=headers_only,
max_results=max_results)
ret = results
if max_results > 0 and len(results) > max_results:
ret = results[:max_results]
for reqh in ret:
req = self.req_by_id(reqh.db_id, storage_id=reqh.storage_id)
yield req
def prefixed_reqid(self, req):
prefix = ""
@ -1246,10 +1279,14 @@ class ProxyClient:
results = [r for r in reversed(results)]
return results
def req_by_id(self, reqid, headers_only=False):
storage, rid = self.parse_reqid(reqid)
return self.msg_conn.req_by_id(rid, headers_only=headers_only,
storage=storage.storage_id)
def req_by_id(self, reqid, storage_id=None, headers_only=False):
if storage_id is None:
storage, db_id = self.parse_reqid(reqid)
storage_id = storage.storage_id
else:
db_id = reqid
return self.msg_conn.req_by_id(db_id, headers_only=headers_only,
storage=storage_id)
# for these and submit, might need storage stored on the request itself
def add_tag(self, reqid, tag, storage=None):
@ -1275,12 +1312,12 @@ class ProxyClient:
def decode_req(result, headers_only=False):
if "StartTime" in result:
if "StartTime" in result and result["StartTime"] > 0:
time_start = time_from_nsecs(result["StartTime"])
else:
time_start = None
if "EndTime" in result:
if "EndTime" in result and result["EndTime"] > 0:
time_end = time_from_nsecs(result["EndTime"])
else:
time_end = None

View file

@ -114,6 +114,13 @@ def main():
client.add_listener(iface, port)
except MessageError as e:
print(str(e))
# Set upstream proxy
if config.use_proxy:
client.set_proxy(config.use_proxy,
config.proxy_host,
config.proxy_port,
config.is_socks_proxy)
interface_loop(client)
except MessageError as e:
print(str(e))

View file

@ -2,6 +2,7 @@ import sys
import string
import time
import datetime
import base64
from pygments.formatters import TerminalFormatter
from pygments.lexers import get_lexer_for_mimetype, HttpLexer
from pygments import highlight
@ -275,8 +276,8 @@ def clipboard_contents():
def encode_basic_auth(username, password):
decoded = '%s:%s' % (username, password)
encoded = base64.b64encode(decoded)
header = 'Basic %s' % encoded
encoded = base64.b64encode(decoded.encode())
header = 'Basic %s' % encoded.decode()
return header
def parse_basic_auth(header):