parent
f4274e1e82
commit
9a58a915c2
36 changed files with 1886 additions and 820 deletions
@ -0,0 +1 @@ |
|||||||
|
__version__ = '0.2.8' |
@ -1,227 +1,234 @@ |
|||||||
""" |
import json |
||||||
The configuration settings for the proxy. |
import os |
||||||
|
import shutil |
||||||
.. data:: CERT_DIR |
|
||||||
|
|
||||||
|
class PappyConfig(object): |
||||||
|
""" |
||||||
|
The configuration settings for the proxy. To access the config object for the |
||||||
|
current session (eg from plugins) use ``pappyproxy.pappy.session.config``. |
||||||
|
|
||||||
|
.. data:: cert_dir |
||||||
|
|
||||||
The location of the CA certs that Pappy will use. This can be configured in the |
The location of the CA certs that Pappy will use. This can be configured in the |
||||||
``config.json`` file for a project. |
``config.json`` file for a project. |
||||||
|
|
||||||
:Default: ``{DATADIR}/certs`` |
:Default: ``{DATADIR}/certs`` |
||||||
|
|
||||||
.. data:: PAPPY_DIR |
.. data:: pappy_dir |
||||||
|
|
||||||
The file where pappy's scripts are located. Don't write anything here, and you |
The file where pappy's scripts are located. Don't write anything here, and you |
||||||
probably don't need to write anything here. Use DATA_DIR instead. |
probably don't need to write anything here. Use DATA_DIR instead. |
||||||
|
|
||||||
:Default: Wherever the scripts are installed |
:Default: Wherever the scripts are installed |
||||||
|
|
||||||
.. data:: DATA_DIR |
.. data:: data_dir |
||||||
|
|
||||||
The data directory. This is where files that have to be read by Pappy every time |
The data directory. This is where files that have to be read by Pappy every time |
||||||
it's run are put. For example, plugins are stored in ``{DATADIR}/plugins`` and |
it's run are put. For example, plugins are stored in ``{DATADIR}/plugins`` and |
||||||
certs are by default stored in ``{DATADIR}/certs``. This defaults to ``~/.pappy`` |
certs are by default stored in ``{DATADIR}/certs``. This defaults to ``~/.pappy`` |
||||||
and isn't configurable right now. |
and isn't configurable right now. |
||||||
|
|
||||||
:Default: ``~/.pappy`` |
:Default: ``~/.pappy`` |
||||||
|
|
||||||
.. data:: DATAFILE |
.. data:: datafile |
||||||
|
|
||||||
The location of the CA certs that Pappy will use. This can be configured in the |
The location of the CA certs that Pappy will use. This can be configured in the |
||||||
``config.json`` file for a project. |
``config.json`` file for a project. |
||||||
|
|
||||||
:Default: ``data.db`` |
:Default: ``data.db`` |
||||||
|
|
||||||
.. data:: DEBUG_DIR |
.. data:: debug_dir |
||||||
|
|
||||||
The directory to write debug output to. Don't put this outside the project folder |
The directory to write debug output to. Don't put this outside the project folder |
||||||
since it writes all the request data to this directory. You probably won't need |
since it writes all the request data to this directory. You probably won't need |
||||||
to use this. Configured in the ``config.json`` file for the project. |
to use this. Configured in the ``config.json`` file for the project. |
||||||
|
|
||||||
:Default: None |
:Default: None |
||||||
|
|
||||||
.. data: LISTENERS |
.. data: listeners |
||||||
|
|
||||||
The list of active listeners. It is a list of tuples of the format (port, interface) |
The list of active listeners. It is a list of tuples of the format (port, interface) |
||||||
Not modifiable after startup. Configured in the ``config.json`` file for the project. |
Not modifiable after startup. Configured in the ``config.json`` file for the project. |
||||||
|
|
||||||
:Default: ``[(8000, '127.0.0.1')]`` |
:Default: ``[(8000, '127.0.0.1')]`` |
||||||
|
|
||||||
.. data: SOCKS_PROXY |
.. data: socks_proxy |
||||||
|
|
||||||
Details for a SOCKS proxy. It is a dict with the following key/values:: |
Details for a SOCKS proxy. It is a dict with the following key/values:: |
||||||
|
|
||||||
host: The SOCKS proxy host |
host: The SOCKS proxy host |
||||||
port: The proxy port |
port: The proxy port |
||||||
username: Username (optional) |
username: Username (optional) |
||||||
password: Password (optional) |
password: Password (optional) |
||||||
|
|
||||||
If null, no proxy will be used. |
If null, no proxy will be used. |
||||||
|
|
||||||
:Default: ``null`` |
:Default: ``null`` |
||||||
|
|
||||||
|
.. data: http_proxy |
||||||
|
|
||||||
|
Details for an upstream HTTP proxy. It is a dict with the following key/values:: |
||||||
|
|
||||||
|
host: The proxy host |
||||||
|
port: The proxy port |
||||||
|
username: Username (optional) |
||||||
|
password: Password (optional) |
||||||
|
|
||||||
|
If null, no proxy will be used. |
||||||
|
|
||||||
.. data: PLUGIN_DIRS |
.. data: plugin_dirs |
||||||
|
|
||||||
List of directories that plugins are loaded from. Not modifiable. |
List of directories that plugins are loaded from. Not modifiable. |
||||||
|
|
||||||
:Default: ``['{DATA_DIR}/plugins', '{PAPPY_DIR}/plugins']`` |
:Default: ``['{DATA_DIR}/plugins', '{PAPPY_DIR}/plugins']`` |
||||||
|
|
||||||
.. data: SAVE_HISTORY |
.. data: save_history |
||||||
|
|
||||||
Whether command history should be saved to a file/loaded at startup. |
Whether command history should be saved to a file/loaded at startup. |
||||||
|
|
||||||
:Default: True |
:Default: True |
||||||
|
|
||||||
.. data: CONFIG_DICT |
.. data: config_dict |
||||||
|
|
||||||
The dictionary read from config.json. When writing plugins, use this to load |
The dictionary read from config.json. When writing plugins, use this to load |
||||||
configuration options for your plugin. |
configuration options for your plugin. |
||||||
|
|
||||||
.. data: GLOBAL_CONFIG_DICT |
.. data: global_config_dict |
||||||
|
|
||||||
The dictionary from ~/.pappy/global_config.json. It contains settings for |
The dictionary from ~/.pappy/global_config.json. It contains settings for |
||||||
Pappy that are specific to the current computer. Avoid putting settings here, |
Pappy that are specific to the current computer. Avoid putting settings here, |
||||||
especially if it involves specific projects. |
especially if it involves specific projects. |
||||||
|
""" |
||||||
|
|
||||||
""" |
def __init__(self): |
||||||
|
self.pappy_dir = os.path.dirname(os.path.realpath(__file__)) |
||||||
import json |
self.data_dir = os.path.join(os.path.expanduser('~'), '.pappy') |
||||||
import os |
|
||||||
import shutil |
|
||||||
|
|
||||||
PAPPY_DIR = os.path.dirname(os.path.realpath(__file__)) |
|
||||||
DATA_DIR = os.path.join(os.path.expanduser('~'), '.pappy') |
|
||||||
|
|
||||||
CERT_DIR = os.path.join(DATA_DIR, 'certs') |
|
||||||
|
|
||||||
DATAFILE = 'data.db' |
|
||||||
|
|
||||||
DEBUG_DIR = None |
|
||||||
DEBUG_TO_FILE = False |
|
||||||
DEBUG_VERBOSITY = 0 |
|
||||||
|
|
||||||
LISTENERS = [(8000, '127.0.0.1')] |
|
||||||
SOCKS_PROXY = None |
|
||||||
|
|
||||||
SSL_CA_FILE = 'certificate.crt' |
|
||||||
SSL_PKEY_FILE = 'private.key' |
|
||||||
|
|
||||||
HISTSIZE = 1000 |
|
||||||
|
|
||||||
PLUGIN_DIRS = [os.path.join(DATA_DIR, 'plugins'), os.path.join(PAPPY_DIR, 'plugins')] |
self.cert_dir = os.path.join(self.data_dir, 'certs') |
||||||
|
|
||||||
CONFIG_DICT = {} |
self.datafile = 'data.db' |
||||||
GLOBAL_CONFIG_DICT = {} |
|
||||||
|
|
||||||
def get_default_config(): |
self.debug_dir = None |
||||||
default_config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), |
self.debug_to_file = False |
||||||
'default_user_config.json') |
self.debug_verbosity = 0 |
||||||
with open(default_config_file) as f: |
|
||||||
settings = json.load(f) |
|
||||||
return settings |
|
||||||
|
|
||||||
def load_settings(proj_config): |
self.listeners = [(8000, '127.0.0.1')] |
||||||
global CERT_DIR |
self.socks_proxy = None |
||||||
global DATAFILE |
self.http_proxy = None |
||||||
global DEBUG_DIR |
|
||||||
global DEBUG_TO_FILE |
|
||||||
global DEBUG_VERBOSITY |
|
||||||
global LISTENERS |
|
||||||
global SOCKS_PROXY |
|
||||||
global PAPPY_DIR |
|
||||||
global DATA_DIR |
|
||||||
global SSL_CA_FILE |
|
||||||
global SSL_PKEY_FILE |
|
||||||
global HISTSIZE |
|
||||||
|
|
||||||
# Substitution dictionary |
self.ssl_ca_file = 'certificate.crt' |
||||||
subs = {} |
self.ssl_pkey_file = 'private.key' |
||||||
subs['PAPPYDIR'] = PAPPY_DIR |
|
||||||
subs['DATADIR'] = DATA_DIR |
|
||||||
|
|
||||||
# Data file settings |
self.histsize = 1000 |
||||||
if 'data_file' in proj_config: |
|
||||||
DATAFILE = proj_config["data_file"].format(**subs) |
|
||||||
|
|
||||||
# Debug settings |
self.plugin_dirs = [os.path.join(self.data_dir, 'plugins'), os.path.join(self.pappy_dir, 'plugins')] |
||||||
if 'debug_dir' in proj_config: |
|
||||||
if proj_config['debug_dir']: |
|
||||||
DEBUG_TO_FILE = True |
|
||||||
DEBUG_DIR = proj_config["debug_dir"].format(**subs) |
|
||||||
|
|
||||||
# Cert directory settings |
self.config_dict = {} |
||||||
if 'cert_dir' in proj_config: |
self.global_config_dict = {} |
||||||
CERT_DIR = proj_config["cert_dir"].format(**subs) |
|
||||||
|
def get_default_config(self): |
||||||
# Listener settings |
|
||||||
if "proxy_listeners" in proj_config: |
|
||||||
LISTENERS = [] |
|
||||||
for l in proj_config["proxy_listeners"]: |
|
||||||
ll = {} |
|
||||||
if 'forward_host_ssl' in l: |
|
||||||
l['forward_host_ssl'] = l['forward_host_ssl'].encode('utf-8') |
|
||||||
if 'forward_host' in l: |
|
||||||
l['forward_host'] = l['forward_host'].encode('utf-8') |
|
||||||
LISTENERS.append(l) |
|
||||||
|
|
||||||
# SOCKS proxy settings |
|
||||||
if "socks_proxy" in proj_config: |
|
||||||
SOCKS_PROXY = None |
|
||||||
if proj_config['socks_proxy'] is not None: |
|
||||||
conf = proj_config['socks_proxy'] |
|
||||||
if 'host' in conf and 'port' in conf: |
|
||||||
SOCKS_PROXY = {} |
|
||||||
SOCKS_PROXY['host'] = conf['host'].encode('utf-8') |
|
||||||
SOCKS_PROXY['port'] = conf['port'] |
|
||||||
if 'username' in conf: |
|
||||||
if 'password' in conf: |
|
||||||
SOCKS_PROXY['username'] = conf['username'].encode('utf-8') |
|
||||||
SOCKS_PROXY['password'] = conf['password'].encode('utf-8') |
|
||||||
else: |
|
||||||
print 'SOCKS proxy has a username but no password. Ignoring creds.' |
|
||||||
else: |
|
||||||
print 'Host is missing host/port.' |
|
||||||
|
|
||||||
# History saving settings |
|
||||||
if "history_size" in proj_config: |
|
||||||
HISTSIZE = proj_config['history_size'] |
|
||||||
|
|
||||||
def load_global_settings(global_config): |
|
||||||
from .http import Request |
|
||||||
global CACHE_SIZE |
|
||||||
|
|
||||||
if "cache_size" in global_config: |
|
||||||
CACHE_SIZE = global_config['cache_size'] |
|
||||||
else: |
|
||||||
CACHE_SIZE = 2000 |
|
||||||
Request.cache.resize(CACHE_SIZE) |
|
||||||
|
|
||||||
def load_from_file(fname): |
|
||||||
global CONFIG_DICT |
|
||||||
# Make sure we have a config file |
|
||||||
if not os.path.isfile(fname): |
|
||||||
print "Copying default config to %s" % fname |
|
||||||
default_config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), |
default_config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), |
||||||
'default_user_config.json') |
'default_user_config.json') |
||||||
shutil.copyfile(default_config_file, fname) |
with open(default_config_file) as f: |
||||||
|
settings = json.load(f) |
||||||
# Load local project config |
return settings |
||||||
with open(fname, 'r') as f: |
|
||||||
CONFIG_DICT = json.load(f) |
@staticmethod |
||||||
load_settings(CONFIG_DICT) |
def _parse_proxy_login(conf): |
||||||
|
proxy = {} |
||||||
def global_load_from_file(): |
if 'host' in conf and 'port' in conf: |
||||||
global GLOBAL_CONFIG_DICT |
proxy = {} |
||||||
global DATA_DIR |
proxy['host'] = conf['host'].encode('utf-8') |
||||||
# Make sure we have a config file |
proxy['port'] = conf['port'] |
||||||
fname = os.path.join(DATA_DIR, 'global_config.json') |
if 'username' in conf: |
||||||
if not os.path.isfile(fname): |
if 'password' in conf: |
||||||
print "Copying default global config to %s" % fname |
proxy['username'] = conf['username'].encode('utf-8') |
||||||
default_global_config_file = os.path.join(PAPPY_DIR, |
proxy['password'] = conf['password'].encode('utf-8') |
||||||
'default_global_config.json') |
else: |
||||||
shutil.copyfile(default_global_config_file, fname) |
print 'Proxy has a username but no password. Ignoring creds.' |
||||||
|
else: |
||||||
# Load local project config |
print 'Host is missing host/port.' |
||||||
with open(fname, 'r') as f: |
return None |
||||||
GLOBAL_CONFIG_DICT = json.load(f) |
return proxy |
||||||
load_global_settings(GLOBAL_CONFIG_DICT) |
|
||||||
|
def load_settings(self, proj_config): |
||||||
|
# Substitution dictionary |
||||||
|
subs = {} |
||||||
|
subs['PAPPYDIR'] = self.pappy_dir |
||||||
|
subs['DATADIR'] = self.data_dir |
||||||
|
|
||||||
|
# Data file settings |
||||||
|
if 'data_file' in proj_config: |
||||||
|
self.datafile = proj_config["data_file"].format(**subs) |
||||||
|
|
||||||
|
# Debug settings |
||||||
|
if 'debug_dir' in proj_config: |
||||||
|
if proj_config['debug_dir']: |
||||||
|
self.debug_to_file = True |
||||||
|
self.debug_dir = proj_config["debug_dir"].format(**subs) |
||||||
|
|
||||||
|
# Cert directory settings |
||||||
|
if 'cert_dir' in proj_config: |
||||||
|
self.cert_dir = proj_config["cert_dir"].format(**subs) |
||||||
|
|
||||||
|
# Listener settings |
||||||
|
if "proxy_listeners" in proj_config: |
||||||
|
self.listeners = [] |
||||||
|
for l in proj_config["proxy_listeners"]: |
||||||
|
if 'forward_host_ssl' in l: |
||||||
|
l['forward_host_ssl'] = l['forward_host_ssl'].encode('utf-8') |
||||||
|
if 'forward_host' in l: |
||||||
|
l['forward_host'] = l['forward_host'].encode('utf-8') |
||||||
|
self.listeners.append(l) |
||||||
|
|
||||||
|
# SOCKS proxy settings |
||||||
|
self.socks_proxy = None |
||||||
|
if "socks_proxy" in proj_config: |
||||||
|
if proj_config['socks_proxy'] is not None: |
||||||
|
self.socks_proxy = PappyConfig._parse_proxy_login(proj_config['socks_proxy']) |
||||||
|
|
||||||
|
# HTTP proxy settings |
||||||
|
self.http_proxy = None |
||||||
|
if "http_proxy" in proj_config: |
||||||
|
if proj_config['http_proxy'] is not None: |
||||||
|
self.http_proxy = PappyConfig._parse_proxy_login(proj_config['http_proxy']) |
||||||
|
|
||||||
|
# History saving settings |
||||||
|
if "history_size" in proj_config: |
||||||
|
self.histsize = proj_config['history_size'] |
||||||
|
|
||||||
|
def load_global_settings(self, global_config): |
||||||
|
from .http import Request |
||||||
|
|
||||||
|
if "cache_size" in global_config: |
||||||
|
self.cache_size = global_config['cache_size'] |
||||||
|
else: |
||||||
|
self.cache_size = 2000 |
||||||
|
Request.cache.resize(self.cache_size) |
||||||
|
|
||||||
|
def load_from_file(self, fname): |
||||||
|
# Make sure we have a config file |
||||||
|
if not os.path.isfile(fname): |
||||||
|
print "Copying default config to %s" % fname |
||||||
|
default_config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), |
||||||
|
'default_user_config.json') |
||||||
|
shutil.copyfile(default_config_file, fname) |
||||||
|
|
||||||
|
# Load local project config |
||||||
|
with open(fname, 'r') as f: |
||||||
|
self.config_dict = json.load(f) |
||||||
|
self.load_settings(self.config_dict) |
||||||
|
|
||||||
|
def global_load_from_file(self): |
||||||
|
# Make sure we have a config file |
||||||
|
fname = os.path.join(self.data_dir, 'global_config.json') |
||||||
|
if not os.path.isfile(fname): |
||||||
|
print "Copying default global config to %s" % fname |
||||||
|
default_global_config_file = os.path.join(self.pappy_dir, |
||||||
|
'default_global_config.json') |
||||||
|
shutil.copyfile(default_global_config_file, fname) |
||||||
|
|
||||||
|
# Load local project config |
||||||
|
with open(fname, 'r') as f: |
||||||
|
self.global_config_dict = json.load(f) |
||||||
|
self.load_global_settings(self.global_config_dict) |
||||||
|
@ -0,0 +1,179 @@ |
|||||||
|
import os |
||||||
|
import mimetypes |
||||||
|
|
||||||
|
from .http import Request, Response |
||||||
|
from .util import PappyStringTransport, PappyException |
||||||
|
|
||||||
|
from twisted.test.proto_helpers import StringTransport |
||||||
|
from twisted.web.server import Site, NOT_DONE_YET |
||||||
|
from twisted.web import static |
||||||
|
from twisted.web.resource import Resource, NoResource |
||||||
|
from jinja2 import Environment, FileSystemLoader |
||||||
|
from twisted.internet import defer |
||||||
|
|
||||||
|
## The web server class |
||||||
|
|
||||||
|
class PappyWebServer(object): |
||||||
|
""" |
||||||
|
A class that is used to serve pages for requests to http://pappy. It is a |
||||||
|
ghetto wrapper around a twisted web Site object. Give it a request object |
||||||
|
and it will add a response to it. |
||||||
|
|
||||||
|
NOINDEX |
||||||
|
""" |
||||||
|
|
||||||
|
from pappyproxy.pappy import session |
||||||
|
site_dir = session.config.pappy_dir+'/site' |
||||||
|
loader = FileSystemLoader(site_dir) |
||||||
|
env = Environment(loader=loader) |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
root = RootResource(self.site_dir) |
||||||
|
self.site = Site(root) |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def render_template(*args, **kwargs): |
||||||
|
return PappyWebServer.env.get_template(args[0]).render(args[1:], **kwargs).encode('utf-8') |
||||||
|
|
||||||
|
@defer.inlineCallbacks |
||||||
|
def handle_request(self, req): |
||||||
|
protocol = self.site.buildProtocol(None) |
||||||
|
tr = PappyStringTransport() |
||||||
|
protocol.makeConnection(tr) |
||||||
|
protocol.dataReceived(req.full_request) |
||||||
|
tr.waitForProducers() |
||||||
|
## WORKING HERE |
||||||
|
# use loading functions to load response |
||||||
|
yield tr.complete_deferred |
||||||
|
rsp_raw = tr.value() |
||||||
|
rsp = Response(rsp_raw) |
||||||
|
req.response = rsp |
||||||
|
|
||||||
|
## functions |
||||||
|
def blocking_string_request(func): |
||||||
|
""" |
||||||
|
Wrapper for blocking request handlers in resources. The custom string |
||||||
|
transport has a deferred that must be called back when the messege is |
||||||
|
complete. If the message blocks though, you can just call it back right away |
||||||
|
|
||||||
|
NOINDEX |
||||||
|
""" |
||||||
|
def f(self, request): |
||||||
|
request.transport.complete_deferred.callback(None) |
||||||
|
return func(self, request) |
||||||
|
return f |
||||||
|
|
||||||
|
## Resources |
||||||
|
|
||||||
|
class PappyResource(Resource): |
||||||
|
""" |
||||||
|
Helper class for site resources. |
||||||
|
NOINDEX |
||||||
|
""" |
||||||
|
|
||||||
|
def getChild(self, name, request): |
||||||
|
if name == '': |
||||||
|
return self |
||||||
|
return Resource.getChild(self, name, request) |
||||||
|
|
||||||
|
class RootResource(PappyResource): |
||||||
|
|
||||||
|
def __init__(self, site_dir): |
||||||
|
PappyResource.__init__(self) |
||||||
|
self.site_dir = site_dir |
||||||
|
self.dirListing = False |
||||||
|
|
||||||
|
# Static resource |
||||||
|
self.static_resource = NoDirFile(self.site_dir + '/static') |
||||||
|
self.putChild('static', self.static_resource) |
||||||
|
|
||||||
|
# Cert download resource |
||||||
|
self.putChild('certs', CertResource()) |
||||||
|
|
||||||
|
# Response viewing resource |
||||||
|
self.putChild('rsp', ResponseResource()) |
||||||
|
|
||||||
|
@blocking_string_request |
||||||
|
def render_GET(self, request): |
||||||
|
return PappyWebServer.render_template('index.html') |
||||||
|
|
||||||
|
class NoDirFile(static.File): |
||||||
|
|
||||||
|
def directoryListing(self): |
||||||
|
return NoResource() |
||||||
|
|
||||||
|
@blocking_string_request |
||||||
|
def render_GET(self, request): |
||||||
|
return static.File.render_GET(self, request) |
||||||
|
|
||||||
|
## Cert resources |
||||||
|
|
||||||
|
class CertResource(PappyResource): |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
PappyResource.__init__(self) |
||||||
|
|
||||||
|
self.putChild('download', CertDownload()) |
||||||
|
|
||||||
|
@blocking_string_request |
||||||
|
def render_GET(self, request): |
||||||
|
return PappyWebServer.render_template('certs.html') |
||||||
|
|
||||||
|
class CertDownload(PappyResource): |
||||||
|
|
||||||
|
@blocking_string_request |
||||||
|
def render_GET(self, request): |
||||||
|
from .pappy import session |
||||||
|
|
||||||
|
cert_dir = session.config.cert_dir |
||||||
|
ssl_ca_file = session.config.ssl_ca_file |
||||||
|
with open(os.path.join(cert_dir, ssl_ca_file), 'r') as f: |
||||||
|
ca_raw = f.read() |
||||||
|
request.responseHeaders.addRawHeader("Content-Type", "application/x-x509-ca-cert") |
||||||
|
return ca_raw |
||||||
|
|
||||||
|
## View responses |
||||||
|
|
||||||
|
class ResponseResource(PappyResource): |
||||||
|
|
||||||
|
def getChild(self, name, request): |
||||||
|
if name == '': |
||||||
|
return self |
||||||
|
return ViewResponseResource(name) |
||||||
|
|
||||||
|
@blocking_string_request |
||||||
|
def render_GET(self, request): |
||||||
|
return PappyWebServer.render_template('viewrsp.html') |
||||||
|
|
||||||
|
class ViewResponseResource(PappyResource): |
||||||
|
|
||||||
|
def __init__(self, reqid): |
||||||
|
PappyResource.__init__(self) |
||||||
|
self.reqid = reqid |
||||||
|
|
||||||
|
def render_GET(self, request): |
||||||
|
d = Request.load_request(self.reqid) |
||||||
|
d.addCallback(self._render_response, request) |
||||||
|
d.addErrback(self._render_response_err, request) |
||||||
|
d.addCallback(lambda _: request.transport.complete_deferred.callback(None)) |
||||||
|
return NOT_DONE_YET |
||||||
|
|
||||||
|
def _render_response(self, req, tw_request): |
||||||
|
if req.response: |
||||||
|
if not req.response.body: |
||||||
|
raise PappyException("Response has no body") |
||||||
|
if 'content-type' in req.response.headers: |
||||||
|
tw_request.responseHeaders.addRawHeader("Content-Type", req.response.headers['content-type']) |
||||||
|
else: |
||||||
|
guess = mimetypes.guess_type(req.url) |
||||||
|
if guess[0]: |
||||||
|
tw_request.responseHeaders.addRawHeader("Content-Type", guess[0]) |
||||||
|
tw_request.write(req.response.body) |
||||||
|
else: |
||||||
|
tw_request.write(PappyWebServer.render_template('norsp.html')) |
||||||
|
tw_request.finish() |
||||||
|
|
||||||
|
def _render_response_err(self, err, tw_request): |
||||||
|
tw_request.write(PappyWebServer.render_template('norsp.html', errmsg=err.getErrorMessage())) |
||||||
|
tw_request.finish() |
||||||
|
err.trap(Exception) |
@ -0,0 +1,11 @@ |
|||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>Pappy</title> |
||||||
|
</head> |
||||||
|
<body style="background-color: #414141"> |
||||||
|
<div style="padding: 12pt; width:960px; margin:auto; background-color: #AAA"> |
||||||
|
<h1>Pappy</h1> |
||||||
|
{% block body %}{% endblock %} |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,6 @@ |
|||||||
|
{% extends "base.html" %} |
||||||
|
|
||||||
|
{% block body %} |
||||||
|
<h2>Cert Download</h2> |
||||||
|
Click <a href="/certs/download">here to download the CA cert.</a> |
||||||
|
{% endblock %} |
@ -0,0 +1,8 @@ |
|||||||
|
{% extends "base.html" %} |
||||||
|
|
||||||
|
{% block body %} |
||||||
|
<ul> |
||||||
|
<li><a href="/certs">Certs</a></li> |
||||||
|
<li>View responses in browser from <a href="http://pappy/rsp">http://pappy/rsp/<reqid></a> |
||||||
|
</ul> |
||||||
|
{% endblock %} |
@ -0,0 +1,8 @@ |
|||||||
|
{% extends "base.html" %} |
||||||
|
|
||||||
|
{% block body %} |
||||||
|
<h2>Unable To Return Response Body</h2> |
||||||
|
{% if errmsg %} |
||||||
|
<p>{{ errmsg }}</p> |
||||||
|
{% endif %} |
||||||
|
{% endblock %} |
@ -0,0 +1 @@ |
|||||||
|
asdfasdfasdf |
@ -0,0 +1,6 @@ |
|||||||
|
{% extends "base.html" %} |
||||||
|
|
||||||
|
{% block body %} |
||||||
|
<h2>View Response</h2> |
||||||
|
<p>View http://pappy/rsp/<id> to view a response in your browser. The body of the response returned to your browser will be the same, but the headers will not.</p> |
||||||
|
{% endblock %} |
@ -0,0 +1,112 @@ |
|||||||
|
import base64 |
||||||
|
import pytest |
||||||
|
import mock |
||||||
|
import json |
||||||
|
import datetime |
||||||
|
import pappyproxy |
||||||
|
|
||||||
|
from pappyproxy.util import PappyException |
||||||
|
from pappyproxy.comm import CommServer |
||||||
|
from pappyproxy.http import Request, Response |
||||||
|
from testutil import mock_deferred, func_deleted, TLSStringTransport, freeze, mock_int_macro, no_tcp |
||||||
|
|
||||||
|
@pytest.fixture |
||||||
|
def http_request(): |
||||||
|
req = Request('GET / HTTP/1.1\r\n\r\n') |
||||||
|
req.host = 'www.foo.faketld' |
||||||
|
req.port = '1337' |
||||||
|
req.is_ssl = True |
||||||
|
req.reqid = 123 |
||||||
|
|
||||||
|
rsp = Response('HTTP/1.1 200 OK\r\n\r\n') |
||||||
|
req.response = rsp |
||||||
|
return req |
||||||
|
|
||||||
|
def perform_comm(line): |
||||||
|
serv = CommServer() |
||||||
|
serv.transport = TLSStringTransport() |
||||||
|
serv.lineReceived(line) |
||||||
|
n = datetime.datetime.now() |
||||||
|
while serv.transport.value() == '': |
||||||
|
t = datetime.datetime.now() |
||||||
|
if (t-n).total_seconds() > 5: |
||||||
|
raise Exception("Request timed out") |
||||||
|
return serv.transport.value() |
||||||
|
|
||||||
|
def test_simple(): |
||||||
|
v = perform_comm('{"action": "ping"}') |
||||||
|
assert json.loads(v) == {'ping': 'pong', 'success': True} |
||||||
|
|
||||||
|
def mock_loader(rsp): |
||||||
|
def f(*args, **kwargs): |
||||||
|
return rsp |
||||||
|
return classmethod(f) |
||||||
|
|
||||||
|
def mock_loader_fail(): |
||||||
|
def f(*args, **kwargs): |
||||||
|
raise PappyException("lololo message don't exist dawg") |
||||||
|
return classmethod(f) |
||||||
|
|
||||||
|
def test_get_request(mocker, http_request): |
||||||
|
mocker.patch.object(pappyproxy.http.Request, 'load_request', new=mock_loader(http_request)) |
||||||
|
v = perform_comm('{"action": "get_request", "reqid": "1"}') |
||||||
|
|
||||||
|
expected_data = json.loads(http_request.to_json()) |
||||||
|
expected_data['success'] = True |
||||||
|
assert json.loads(v) == expected_data |
||||||
|
|
||||||
|
def test_get_request_fail(mocker, http_request): |
||||||
|
mocker.patch.object(pappyproxy.http.Request, 'load_request', new=mock_loader_fail()) |
||||||
|
v = json.loads(perform_comm('{"action": "get_request", "reqid": "1"}')) |
||||||
|
|
||||||
|
assert v['success'] == False |
||||||
|
assert 'message' in v |
||||||
|
|
||||||
|
def test_get_response(mocker, http_request): |
||||||
|
mocker.patch.object(pappyproxy.http.Request, 'load_request', new=mock_loader(http_request)) |
||||||
|
mocker.patch.object(pappyproxy.http.Response, 'load_response', new=mock_loader(http_request.response)) |
||||||
|
v = perform_comm('{"action": "get_response", "reqid": "1"}') |
||||||
|
|
||||||
|
expected_data = json.loads(http_request.response.to_json()) |
||||||
|
expected_data['success'] = True |
||||||
|
assert json.loads(v) == expected_data |
||||||
|
|
||||||
|
def test_get_response_fail(mocker, http_request): |
||||||
|
mocker.patch.object(pappyproxy.http.Request, 'load_request', new=mock_loader(http_request)) |
||||||
|
mocker.patch.object(pappyproxy.http.Response, 'load_response', new=mock_loader_fail()) |
||||||
|
v = json.loads(perform_comm('{"action": "get_response", "reqid": "1"}')) |
||||||
|
|
||||||
|
assert v['success'] == False |
||||||
|
assert 'message' in v |
||||||
|
|
||||||
|
def test_submit_request(mocker, http_request): |
||||||
|
mocker.patch.object(pappyproxy.http.Request, 'submit_new', new=mock_loader(http_request)) |
||||||
|
mocker.patch('pappyproxy.http.Request.async_deep_save').return_value = mock_deferred() |
||||||
|
|
||||||
|
comm_data = {"action": "submit"} |
||||||
|
comm_data['host'] = http_request.host |
||||||
|
comm_data['port'] = http_request.port |
||||||
|
comm_data['is_ssl'] = http_request.is_ssl |
||||||
|
comm_data['full_message'] = base64.b64encode(http_request.full_message) |
||||||
|
comm_data['tags'] = ['footag'] |
||||||
|
v = perform_comm(json.dumps(comm_data)) |
||||||
|
|
||||||
|
expected_data = {} |
||||||
|
expected_data['request'] = json.loads(http_request.to_json()) |
||||||
|
expected_data['response'] = json.loads(http_request.response.to_json()) |
||||||
|
expected_data['success'] = True |
||||||
|
expected_data['request']['tags'] = ['footag'] |
||||||
|
assert json.loads(v) == expected_data |
||||||
|
|
||||||
|
def test_submit_request_fail(mocker, http_request): |
||||||
|
mocker.patch.object(pappyproxy.http.Request, 'submit_new', new=mock_loader_fail()) |
||||||
|
mocker.patch('pappyproxy.http.Request.async_deep_save').return_value = mock_deferred() |
||||||
|
|
||||||
|
comm_data = {"action": "submit"} |
||||||
|
comm_data['full_message'] = base64.b64encode('HELLO THIS IS REQUEST\r\nWHAT IS HEADER FORMAT\r\n') |
||||||
|
v = json.loads(perform_comm(json.dumps(comm_data))) |
||||||
|
print v |
||||||
|
|
||||||
|
assert v['success'] == False |
||||||
|
assert 'message' in v |
||||||
|
|
Loading…
Reference in new issue