Version 0.2.10

This commit is contained in:
Rob Glew 2016-03-30 14:37:50 -05:00
parent 9648bc44cc
commit d2f0e5c222
17 changed files with 625 additions and 1061 deletions

View file

@ -1 +1 @@
__version__ = '0.2.9'
__version__ = '0.2.10'

View file

@ -127,7 +127,7 @@ def repeatable_parse_qs(s):
@crochet.wait_for(timeout=180.0)
@defer.inlineCallbacks
def request_by_id(reqid):
req = Request.load_request(str(reqid))
req = yield Request.load_request(str(reqid))
defer.returnValue(req)
##########
@ -2133,7 +2133,9 @@ class Request(HTTPMessage):
def submit_request(request,
save_request=False,
intercepting_macros={},
stream_transport=None):
stream_transport=None,
_factory_string_transport=False,
_conn_info=None):
"""
submit_request(request, save_request=False, intercepting_macros={}, stream_transport=None)
@ -2152,6 +2154,9 @@ class Request(HTTPMessage):
from .proxy import ProxyClientFactory, get_next_connection_id, get_endpoint
from .pappy import session
from .tests.testutil import TLSStringTransport
# _factory__string_transport, _conn_classes are only for unit tests. Do not use.
factory = None
if stream_transport is None:
@ -2164,6 +2169,16 @@ class Request(HTTPMessage):
save_all=save_request,
stream_response=True,
return_transport=stream_transport)
# Set up stuff for unit test if needed
if _factory_string_transport:
factory._use_string_transport = True
if _conn_info is not None:
# Pass factory back to unit test
_conn_info['factory'] = factory
factory._conn_info = _conn_info
# Set up factory settings
factory.intercepting_macros = intercepting_macros
factory.connection_id = get_next_connection_id()
factory.connect()

View file

@ -10,7 +10,9 @@ import imp
import os
import pappyproxy
import stat
import crochet
from twisted.internet import defer
from .proxy import add_intercepting_macro as proxy_add_intercepting_macro
from .proxy import remove_intercepting_macro as proxy_remove_intercepting_macro
from .colors import Colors
@ -146,7 +148,7 @@ def req_history(num=-1, ids=None, include_unmangled=False):
"""
return pappyproxy.Request.cache.req_it(num=num, ids=ids, include_unmangled=include_unmangled)
def main_context_ids(n=-1):
def async_main_context_ids(n=-1):
"""
Returns a deferred that resolves into a list of up to ``n`` of the
most recent requests in the main context. You can then use
@ -156,6 +158,17 @@ def main_context_ids(n=-1):
"""
return pappyproxy.pappy.main_context.get_reqs(n)
@crochet.wait_for(timeout=None)
@defer.inlineCallbacks
def main_context_ids(*args, **kwargs):
"""
Same as :func:`pappyproxy.plugin.async_main_context_ids` but can be called
from macros and other non-async only functions. Cannot be called in async
functions.
"""
ret = yield async_main_context_ids(*args, **kwargs)
defer.returnValue(ret)
def run_cmd(cmd):
"""
Run a command as if you typed it into the console. Try and use

View file

@ -1,7 +1,7 @@
import crochet
import pappyproxy
from pappyproxy.util import PappyException, confirm
from pappyproxy.util import PappyException, confirm, autocomplete_startswith
from pappyproxy.http import Request
from twisted.internet import defer
@ -40,6 +40,11 @@ class BuiltinFilters(object):
return pappyproxy.context.Filter(BuiltinFilters._filters[name][1])
def complete_filtercmd(text, line, begidx, endidx):
strs = [k for k, v in pappyproxy.context.Filter._filter_functions.iteritems()]
strs += [k for k, v in pappyproxy.context.Filter._async_filter_functions.iteritems()]
return autocomplete_startswith(text, strs)
@crochet.wait_for(timeout=None)
@defer.inlineCallbacks
def filtercmd(line):
@ -179,7 +184,7 @@ def load_cmds(cmd):
'filter_clear': (filter_clear, None),
'filter_up': (filter_up, None),
'builtin_filter': (builtin_filter, complete_builtin_filter),
'filter': (filtercmd, None),
'filter': (filtercmd, complete_filtercmd),
})
cmd.add_aliases([
#('filter_prune', ''),

View file

@ -4,7 +4,7 @@ import shlex
from pappyproxy.plugin import active_intercepting_macros, add_intercepting_macro, remove_intercepting_macro
from pappyproxy.macros import load_macros, macro_from_requests, gen_imacro
from pappyproxy.util import PappyException, load_reqlist
from pappyproxy.util import PappyException, load_reqlist, autocomplete_startswith
from twisted.internet import defer
loaded_macros = []
@ -64,6 +64,11 @@ def load_macros_cmd(line):
int_macro_dict[macro.short_name] = macro
loaded_int_macros.append(macro)
print 'Loaded "%s"' % macro
def complete_run_macro(text, line, begidx, endidx):
global macro_dict
strs = [k for k,v in macro_dict.iteritems()]
return autocomplete_startswith(text, strs)
def run_macro(line):
"""
@ -81,6 +86,24 @@ def run_macro(line):
macro = macro_dict[mname]
macro.execute(args[1:])
def complete_run_int_macro(text, line, begidx, endidx):
global int_macro_dict
global loaded_int_macros
running = []
not_running = []
for macro in loaded_int_macros:
if macro.name in [m.name for k, m in active_intercepting_macros().iteritems()]:
running.append(macro)
else:
not_running.append(macro)
strs = []
for m in not_running:
strs.append(macro.name)
strs.append(macro.file_name)
if macro.short_name:
strs.append(macro.short_name)
return autocomplete_startswith(text, strs)
def run_int_macro(line):
"""
Activate an intercepting macro
@ -103,6 +126,24 @@ def run_int_macro(line):
print 'Error initializing macro:'
raise e
def complete_stop_int_macro(text, line, begidx, endidx):
global int_macro_dict
global loaded_int_macros
running = []
not_running = []
for macro in loaded_int_macros:
if macro.name in [m.name for k, m in active_intercepting_macros().iteritems()]:
running.append(macro)
else:
not_running.append(macro)
strs = []
for m in running:
strs.append(macro.name)
strs.append(macro.file_name)
if macro.short_name:
strs.append(macro.short_name)
return autocomplete_startswith(text, strs)
def stop_int_macro(line):
"""
Stop a running intercepting macro
@ -201,9 +242,9 @@ def load_cmds(cmd):
'generate_int_macro': (generate_int_macro, None),
'generate_macro': (generate_macro, None),
'list_int_macros': (list_int_macros, None),
'stop_int_macro': (stop_int_macro, None),
'run_int_macro': (run_int_macro, None),
'run_macro': (run_macro, None),
'stop_int_macro': (stop_int_macro, complete_stop_int_macro),
'run_int_macro': (run_int_macro, complete_run_int_macro),
'run_macro': (run_macro, complete_run_macro),
'load_macros': (load_macros_cmd, None),
})
cmd.add_aliases([

View file

@ -186,6 +186,10 @@ def run_without_color(line):
with Capturing() as output:
session.cons.onecmd(line.strip())
print remove_color(output.val)
def version(line):
import pappyproxy
print pappyproxy.__version__
def load_cmds(cmd):
cmd.set_cmds({
@ -197,6 +201,7 @@ def load_cmds(cmd):
'merge': (merge_datafile, None),
'nocolor': (run_without_color, None),
'watch': (watch_proxy, None),
'version': (version, None),
})
cmd.add_aliases([
#('rpy', ''),

View file

@ -2,7 +2,7 @@ import crochet
import pappyproxy
import shlex
from pappyproxy.plugin import main_context_ids
from pappyproxy.plugin import async_main_context_ids
from pappyproxy.util import PappyException, load_reqlist
from twisted.internet import defer
from pappyproxy.http import Request
@ -26,7 +26,7 @@ def tag(line):
print 'Tagging %s with %s' % (', '.join(reqids), tag)
else:
print "Tagging all in-context requests with %s" % tag
reqids = yield main_context_ids()
reqids = yield async_main_context_ids()
for reqid in reqids:
req = yield Request.load_request(reqid)
@ -58,7 +58,7 @@ def untag(line):
print 'Removing tag %s from %s' % (tag, ', '.join(reqids))
else:
print "Removing tag %s from all in-context requests" % tag
reqids = yield main_context_ids()
reqids = yield async_main_context_ids()
for reqid in reqids:
req = yield Request.load_request(reqid)

View file

@ -10,7 +10,7 @@ import urllib
from pappyproxy.util import PappyException, utc2local, load_reqlist, print_table, print_request_rows, get_req_data_row
from pappyproxy.http import Request, repeatable_parse_qs
from twisted.internet import defer
from pappyproxy.plugin import main_context_ids
from pappyproxy.plugin import async_main_context_ids
from pappyproxy.colors import Colors, Styles, verb_color, scode_color, path_formatter, host_color
from pygments.formatters import TerminalFormatter
from pygments.lexers.data import JsonLexer
@ -255,7 +255,7 @@ def list_reqs(line):
print_count = 25
rows = []
ids = yield main_context_ids(print_count)
ids = yield async_main_context_ids(print_count)
for i in ids:
req = yield Request.load_request(i)
rows.append(get_req_data_row(req))
@ -477,7 +477,7 @@ def get_param_info(line):
found_params = {}
ids = yield main_context_ids()
ids = yield async_main_context_ids()
for i in ids:
req = yield Request.load_request(i)
for k, v in req.url_params.all_pairs():
@ -501,17 +501,20 @@ def dump_response(line):
"""
# dump the data of a response
args = shlex.split(line)
reqid = args[0]
req = yield Request.load_request(reqid)
rsp = req.response
if len(args) >= 2:
fname = args[1]
else:
fname = req.path.split('/')[-1]
reqs = yield load_reqlist(args[0])
for req in reqs:
if req.response:
rsp = req.response
if len(args) >= 2:
fname = args[1]
else:
fname = req.path.split('/')[-1]
with open(fname, 'w') as f:
f.write(rsp.body)
print 'Response data written to %s' % fname
with open(fname, 'w') as f:
f.write(rsp.body)
print 'Response data written to %s' % fname
else:
print 'Request %s does not have a response' % req.reqid
@crochet.wait_for(timeout=None)
@defer.inlineCallbacks
@ -525,7 +528,7 @@ def site_map(line):
paths = True
else:
paths = False
ids = yield main_context_ids()
ids = yield async_main_context_ids()
paths_set = set()
for reqid in ids:
req = yield Request.load_request(reqid)

View file

@ -262,13 +262,17 @@ class ProxyClientFactory(ClientFactory):
self.intercepting_macros = {}
self.use_as_proxy = False
self.sendback_function = None
self.dropped_request = False
# Only used for unit tests. Do not use.
self._use_string_transport = False
self._conn_info = None
def log(self, message, symbol='*', verbosity_level=1):
log(message, id=self.connection_id, symbol=symbol, verbosity_level=verbosity_level)
def buildProtocol(self, addr, _do_callback=True):
def buildProtocol(self, addr):
from pappyproxy.pappy import session
# _do_callback is intended to help with testing and should not be modified
if self.use_as_proxy and context.in_scope(self.request):
p = UpstreamHTTPProxyClient(self.request)
if 'username' in session.config.http_proxy and 'password' in session.config.http_proxy:
@ -279,8 +283,7 @@ class ProxyClientFactory(ClientFactory):
p = ProxyClient(self.request)
p.factory = self
self.log("Building protocol", verbosity_level=3)
if _do_callback:
p.data_defer.addCallback(self.return_request_pair)
p.data_defer.addCallback(self.return_request_pair)
return p
def clientConnectionFailed(self, connector, reason):
@ -310,17 +313,24 @@ class ProxyClientFactory(ClientFactory):
else:
yield self.request.async_deep_save()
(sendreq, mangled) = yield macros.mangle_request(sendreq, mangle_macros)
(mangreq, mangled) = yield macros.mangle_request(sendreq, mangle_macros)
if mangreq is None:
self.log("Request dropped. Closing connections.")
self.request.tags.add('dropped')
self.request.response = None
self.dropped_request = True
defer.returnValue(None)
else:
sendreq = mangreq
if sendreq and mangled and self.save_all:
self.start_time = datetime.datetime.utcnow()
sendreq.time_start = self.start_time
yield sendreq.async_deep_save()
if sendreq and mangled and self.save_all:
self.start_time = datetime.datetime.utcnow()
sendreq.time_start = self.start_time
yield sendreq.async_deep_save()
if session.config.http_proxy:
self.use_as_proxy = True
if (not self.stream_response) and self.sendback_function:
self.data_defer.addCallback(self.sendback_function)
if session.config.http_proxy:
self.use_as_proxy = True
if (not self.stream_response) and self.sendback_function:
self.data_defer.addCallback(self.sendback_function)
else:
self.log("Request out of scope, passing along unmangled")
self.request = sendreq
@ -337,7 +347,7 @@ class ProxyClientFactory(ClientFactory):
from pappyproxy.pappy import session
self.end_time = datetime.datetime.utcnow()
if session.config.debug_to_file or session.config.debug_verbosity > 0:
if session.config.debug_to_file or session.config.debug_verbosity > 0 and request.response:
log_request(printable_data(request.response.full_response), id=self.connection_id, symbol='<m', verbosity_level=3)
request.time_start = self.start_time
@ -351,14 +361,15 @@ class ProxyClientFactory(ClientFactory):
else:
yield request.async_deep_save()
mangled = yield macros.mangle_response(request, mangle_macros)
if request.response:
mangled = yield macros.mangle_response(request, mangle_macros)
if mangled and self.save_all:
yield request.async_deep_save()
if mangled and self.save_all:
yield request.async_deep_save()
if request.response and (session.config.debug_to_file or session.config.debug_verbosity > 0):
log_request(printable_data(request.response.full_response),
id=self.connection_id, symbol='<', verbosity_level=3)
if request.response and (session.config.debug_to_file or session.config.debug_verbosity > 0):
log_request(printable_data(request.response.full_response),
id=self.connection_id, symbol='<', verbosity_level=3)
else:
self.log("Response out of scope, passing along unmangled")
self.data_defer.callback(request)
@ -369,6 +380,10 @@ class ProxyClientFactory(ClientFactory):
from pappyproxy.pappy import session
yield self.prepare_request()
if self.dropped_request:
self.data_defer.callback(self.request)
defer.returnValue(None)
if context.in_scope(self.request):
# Get connection using config
endpoint = get_endpoint(self.request.host,
@ -380,12 +395,26 @@ class ProxyClientFactory(ClientFactory):
# Just forward it normally
endpoint = get_endpoint(self.request.host,
self.request.port,
self.request.is_ssl)
self.request.is_ssl,
socks_config=None,
use_http_proxy=False)
# Connect via the endpoint
self.log("Accessing using endpoint")
yield endpoint.connect(self)
self.log("Connected")
if self._use_string_transport:
from pappyproxy.tests.testutil import TLSStringTransport
# "Connect" via string transport
protocol = self.buildProtocol(('127.0.0.1', 0))
# Pass the protocol back to the test
if self._conn_info:
self._conn_info['protocol'] = protocol
tr = TLSStringTransport()
protocol.makeConnection(tr)
else:
# Connect via the endpoint
self.log("Accessing using endpoint")
yield endpoint.connect(self)
self.log("Connected")
class ProxyServerFactory(ServerFactory):
@ -529,6 +558,14 @@ class ProxyServer(LineReceiver):
def send_response_back(self, request):
if request.response is not None:
self.transport.write(request.response.full_response)
else:
droppedrsp = http.Response(('HTTP/1.1 200 OK\r\n'
'Connection: close\r\n'
'Cache-control: no-cache\r\n'
'Pragma: no-cache\r\n'
'Cache-control: no-store\r\n'
'X-Frame-Options: DENY\r\n\r\n'))
self.transport.write(droppedrsp.full_message)
self.log("Response sent back, losing connection")
self.transport.loseConnection()

View file

@ -1,9 +1,24 @@
from .http import ResponseCookie
class Session(object):
"""
A class used to maintain a session over multiple requests. Can remember cookies
and apply a specific header to requests. It is also possible to give the session
a list of cookie names and it will only save those cookies.
"""
def __init__(self, cookie_names=None, header_names=None,
cookie_vals=None, header_vals=None):
"""
Session(self, cookie_names=None, header_names=None, cookie_vals=None, header_vals=None)
Constructor
:param cookie_names: A whitelist for cookies that should be saved from :func:`~pappyproxy.session.Session.save_req` and :func:`~pappyproxy.session.Session.save_rsp` in the session. If no values are given, all cookies will be saved.
:param header_names: A whitelist for headers that should be saved from :func:`~pappyproxy.session.Session.save_req` in the session. If no values are given, no headers will be saved.
:param cookie_vals: A dictionary of cookies to populate the session session with. The key should be the cookie name, and the value can be either a string or a :class:`~pappyproxy.http.ResponseCookie`. If a :class:`~pappyproxy.http.ResponseCookie` is given, its flags will be used in :func:`~pappyproxy.session.Session.apply_rsp`.
:param header_vals: A dictionary of header values to populate the session with. The key should be the header name and the value should be a string which should be the header value.
"""
self.cookies = cookie_names or []
self.headers = header_names or []
self.cookie_vals = cookie_vals or {}
@ -19,25 +34,61 @@ class Session(object):
if k not in self.headers:
self.headers.append(k)
def _cookie_obj(k, v):
"""
Returns the value as a cookie object regardless of if the cookie is a string or a ResponseCookie.
"""
if isinstance(v, ResponseCookie):
return v
else:
cookie_str = '%s=%s' % (k, v)
return ResponseCookie(cookie_str)
def _cookie_val(v):
"""
Returns the value of the cookie regardless of if the value is a string or a ResponseCookie
"""
if isinstance(v, ResponseCookie):
return v.val
else:
return v
def apply_req(self, req):
"""
apply_req(request)
Apply saved headers and cookies to the request
"""
for k, v in self.cookie_vals.iteritems():
if isinstance(v, ResponseCookie):
req.cookies[v.key] = v.val
else:
req.cookies[k] = v
req.cookies[k] = self._cookie_val(v)
for k, v in self.header_vals.iteritems():
req.headers[k] = v
def apply_rsp(self, rsp):
"""
apply_rsp(response)
Will add a Set-Cookie header for each saved cookie. Will not
apply any saved headers. If the cookie was added from a call to
:func:`~pappyproxy.session.Session.save_rsp`, the Set-Cookie flags
will be the same as the original response.
"""
for k, v in self.cookie_vals.iteritems():
if isinstance(v, ResponseCookie):
rsp.set_cookie(v)
else:
cookie_str = '%s=%s' % (k, v)
rsp.set_cookie(ResponseCookie(cookie_str))
val = self._cookie_obj(v)
rsp.set_cookie(val)
# Don't apply headers to responses
def get_req(self, req, cookies=None, headers=None):
def save_req(self, req, cookies=None, headers=None):
"""
save_req(req, cookies=None, headers=None)
Updates the state of the session from the given request.
Cookie and headers can be added to their whitelists by passing in a list
for either ``cookies`` or ``headers``.
"""
if cookies:
for c in cookies:
if c not in self.cookies:
@ -64,7 +115,14 @@ class Session(object):
if header in self.headers:
self.header_vals[header] = req.headers[header]
def get_rsp(self, rsp, cookies=None):
def save_rsp(self, rsp, cookies=None):
"""
save_rsp(rsp, cookies=None)
Update the state of the session from the response. Only cookies can be
updated from a response. Additional values can be added to the whitelist
by passing in a list of values for the ``cookies`` parameter.
"""
if cookies:
for c in cookies:
if c not in self.cookies:
@ -80,3 +138,38 @@ class Session(object):
for k, v in rsp.cookies.all_pairs():
if v.key in self.cookies:
self.cookie_vals[v.key] = v
def set_cookie(key, val):
"""
set_cookie(key, val)
Set a cookie in the session. ``val`` can be either a string or a :class:`~pappyproxy.http.ResponseCookie`.
If a :class:`~pappyproxy.http.ResponseCookie` is used, make sure its ``key`` value is the same as
the key passed in to the function.
"""
self.cookie_vals[key] = val
def get_cookie(key):
"""
get_cookie(key)
Returns a string with the value of the cookie with the given string, even if the value is a :class:`~pappyproxy.http.ResponseCookie`.
If you want to get a :class:`~pappyproxy.http.ResponseCookie`, use :func:`~pappyproxy.session.Session.get_rsp_cookie`.
"""
if not key in self.cookie_vals:
raise KeyError('Cookie is not stored in session.')
v = self.cookie_vals[key]
return self._cookie_val(v)
def get_rsp_cookie(key):
"""
get_rsp_cookie(key)
Returns the :class:`~pappyproxy.http.ResponseCookie` associated with the key
regardless of if the value is stored as a string or a :class:`~pappyproxy.http.ResponseCookie`.
"""
if not key in self.cookie_vals:
raise KeyError('Cookie is not stored in session.')
v = self.cookie_vals[key]
return self._cookie_obj(v)

View file

@ -1,4 +1,5 @@
from pappyproxy.http import Request, get_request, post_request, request_by_id
from pappyproxy.plugin import main_context_ids
from pappyproxy.context import set_tag
from pappyproxy.iter import *
@ -12,7 +13,7 @@ from pappyproxy.iter import *
MACRO_NAME = '{{macro_name}}'
SHORT_NAME = '{{short_name}}'
{% if req_lines %}
###########
## Requests
# It's suggested that you call .copy() on these and then edit attributes
@ -23,7 +24,7 @@ SHORT_NAME = '{{short_name}}'
req{{ count }} = Request(({% for line in lines %}
'{{ line }}'{% endfor %}{% set count = count+1 %}
){{ params }})
{% endfor %}
{% endfor %}{% endif %}
def run_macro(args):
# Example:

View file

@ -1,209 +0,0 @@
import pytest
import mock
import pappyproxy
from pappyproxy.mangle import async_mangle_request, async_mangle_response
from pappyproxy.http import Request, Response
from testutil import no_tcp, no_database, func_deleted, mock_deferred, mock_deep_save, fake_saving
def retf(r):
return False
@pytest.fixture
def ignore_edit(mocker):
new_edit = mock.MagicMock()
new_edit.return_value = mock_deferred(None)
mocker.patch('pappyproxy.console.edit_file', new=new_edit)
@pytest.fixture
def ignore_delete(mocker):
new_os_remove = mock.MagicMock()
mocker.patch('os.remove', new=new_os_remove)
return new_os_remove
@pytest.fixture(autouse=True)
def no_logging(mocker):
mocker.patch('pappyproxy.proxy.log')
@pytest.fixture
def req():
r = Request()
r.status_line = 'GET / HTTP/1.1'
r.host = 'www.ffffff.eeeeee'
r.raw_data = 'AAAA'
return r
@pytest.fixture
def req_w_rsp(req):
r = Response()
r.status_line = 'HTTP/1.1 200 OK'
r.headers['Test-Header'] = 'ABC123'
r.raw_data = 'AAAA'
req.response = r
return req
@pytest.fixture
def mock_tempfile(mocker):
new_tfile_obj = mock.MagicMock()
tfile_instance = mock.MagicMock()
new_tfile_obj.return_value.__enter__.return_value = tfile_instance
tfile_instance.name = 'mockTemporaryFile'
mocker.patch('tempfile.NamedTemporaryFile', new=new_tfile_obj)
new_open = mock.MagicMock()
fake_file = mock.MagicMock(spec=file)
new_open.return_value.__enter__.return_value = fake_file
mocker.patch('__builtin__.open', new_open)
return (new_tfile_obj, tfile_instance, new_open, fake_file)
########################
## Test request mangling
@pytest.inlineCallbacks
def test_mangle_request_edit(req, mock_deep_save, mock_tempfile,
ignore_edit, ignore_delete):
tfile_obj, tfile_instance, new_open, fake_file = mock_tempfile
r = req
new_contents = ('GET / HTTP/1.1\r\n'
'Content-Length: 4\r\n\r\n'
'BBBB')
fake_file.read.return_value = new_contents
new_req = yield async_mangle_request(r)
assert not mock_deep_save.called
assert tfile_obj.called
assert tfile_instance.write.called
assert tfile_instance.write.call_args == ((r.full_request,),)
assert new_open.called
assert fake_file.read.called
assert new_req.full_request == new_contents
@pytest.inlineCallbacks
def test_mangle_request_edit_newlines(req, mock_deep_save, mock_tempfile,
ignore_edit, ignore_delete):
# Intercepting is off, request in scope
tfile_obj, tfile_instance, new_open, fake_file = mock_tempfile
r = req
new_contents = ('GET / HTTP/1.1\r\n'
'Test-Head: FOOBIE\n'
'Content-Length: 4\n\r\n'
'BBBB')
fake_file.read.return_value = new_contents
new_req = yield async_mangle_request(r)
assert new_req.full_request == ('GET / HTTP/1.1\r\n'
'Test-Head: FOOBIE\r\n'
'Content-Length: 4\r\n\r\n'
'BBBB')
assert new_req.headers['Test-Head'] == 'FOOBIE'
@pytest.inlineCallbacks
def test_mangle_request_drop(req, mock_deep_save, mock_tempfile,
ignore_edit, ignore_delete):
# Intercepting is off, request in scope
tfile_obj, tfile_instance, new_open, fake_file = mock_tempfile
r = req
new_contents = ''
fake_file.read.return_value = new_contents
new_req = yield async_mangle_request(r)
assert new_req is None
@pytest.inlineCallbacks
def test_mangle_request_edit_len(req, mock_deep_save, mock_tempfile,
ignore_edit, ignore_delete):
# Intercepting is off, request in scope
tfile_obj, tfile_instance, new_open, fake_file = mock_tempfile
r = req
new_contents = ('GET / HTTP/1.1\r\n'
'Test-Head: FOOBIE\n'
'Content-Length: 4\n\r\n'
'BBBBAAAA')
fake_file.read.return_value = new_contents
new_req = yield async_mangle_request(r)
assert new_req.full_request == ('GET / HTTP/1.1\r\n'
'Test-Head: FOOBIE\r\n'
'Content-Length: 8\r\n\r\n'
'BBBBAAAA')
#########################
## Test response mangling
@pytest.inlineCallbacks
def test_mangle_response_edit(req_w_rsp, mock_deep_save, mock_tempfile,
ignore_edit, ignore_delete):
# Intercepting is on, edit
tfile_obj, tfile_instance, new_open, fake_file = mock_tempfile
r = req_w_rsp
old_rsp = r.response.full_response
new_contents = ('HTTP/1.1 403 NOTOKIEDOKIE\r\n'
'Content-Length: 4\r\n'
'Other-Header: foobles\r\n\r\n'
'BBBB')
fake_file.read.return_value = new_contents
mangled_rsp = yield async_mangle_response(r)
assert not mock_deep_save.called
assert tfile_obj.called
assert tfile_instance.write.called
assert tfile_instance.write.call_args == ((old_rsp,),)
assert new_open.called
assert fake_file.read.called
assert mangled_rsp.full_response == new_contents
@pytest.inlineCallbacks
def test_mangle_response_newlines(req_w_rsp, mock_deep_save, mock_tempfile,
ignore_edit, ignore_delete):
# Intercepting is off, request in scope
tfile_obj, tfile_instance, new_open, fake_file = mock_tempfile
r = req_w_rsp
old_rsp = r.response.full_response
new_contents = ('HTTP/1.1 403 NOTOKIEDOKIE\n'
'Content-Length: 4\n'
'Other-Header: foobles\r\n\n'
'BBBB')
fake_file.read.return_value = new_contents
mangled_rsp = yield async_mangle_response(r)
assert mangled_rsp.full_response == ('HTTP/1.1 403 NOTOKIEDOKIE\r\n'
'Content-Length: 4\r\n'
'Other-Header: foobles\r\n\r\n'
'BBBB')
assert mangled_rsp.headers['Other-Header'] == 'foobles'
@pytest.inlineCallbacks
def test_mangle_response_drop(req_w_rsp, mock_deep_save, mock_tempfile,
ignore_edit, ignore_delete):
# Intercepting is off, request in scope
tfile_obj, tfile_instance, new_open, fake_file = mock_tempfile
r = req_w_rsp
old_rsp = r.response.full_response
new_contents = ''
fake_file.read.return_value = new_contents
mangled_rsp = yield async_mangle_response(r)
assert mangled_rsp is None
@pytest.inlineCallbacks
def test_mangle_response_new_len(req_w_rsp, mock_deep_save, mock_tempfile,
ignore_edit, ignore_delete):
# Intercepting is off, request in scope
tfile_obj, tfile_instance, new_open, fake_file = mock_tempfile
r = req_w_rsp
old_rsp = r.response.full_response
new_contents = ('HTTP/1.1 403 NOTOKIEDOKIE\n'
'Content-Length: 4\n'
'Other-Header: foobles\r\n\n'
'BBBBAAAA')
fake_file.read.return_value = new_contents
mangled_rsp = yield async_mangle_response(r)
assert mangled_rsp.full_response == ('HTTP/1.1 403 NOTOKIEDOKIE\r\n'
'Content-Length: 8\r\n'
'Other-Header: foobles\r\n\r\n'
'BBBBAAAA')

File diff suppressed because it is too large Load diff

View file

@ -12,9 +12,7 @@ class ClassDeleted():
pass
class TLSStringTransport(StringTransport):
def startTLS(self, context, factory):
pass
startTLS = mock.MagicMock()
class PappySession(object):

View file

@ -119,12 +119,21 @@ def load_reqlist(line, allow_special=True, ids_only=False):
:Returns: Twisted deferred
"""
from .http import Request
from .plugin import async_main_context_ids
# Parses a comma separated list of ids and returns a list of those requests
# prints any errors
if not line:
raise PappyException('Request id(s) required')
ids = re.split(',\s*', line)
reqs = []
if line == '*':
ids = yield async_main_context_ids()
for i in ids:
req = yield Request.load_request(i)
reqs.append(req)
defer.returnValue(reqs)
ids = re.split(',\s*', line)
if not ids_only:
for reqid in ids:
try:
@ -336,3 +345,14 @@ def copy_to_clipboard(text):
def clipboard_contents():
return pyperclip.paste()
def autocomplete_startswith(text, lst, allow_spaces=False):
ret = None
if not text:
ret = lst[:]
else:
ret = [n for n in lst if n.startswith(text)]
if not allow_spaces:
ret = [s for s in ret if ' ' not in s]
return ret