mitmproxy/libpathod/pathod.py

240 lines
7.9 KiB
Python
Raw Normal View History

import urllib, threading, re, logging, socket, sys
from netlib import tcp, http, odict, wsgi
2012-06-19 22:51:37 +00:00
import version, app, rparse
2012-06-19 04:57:57 +00:00
2012-06-24 09:40:31 +00:00
2012-06-24 04:38:32 +00:00
class PathodError(Exception): pass
2012-06-19 01:23:07 +00:00
class PathodHandler(tcp.BaseHandler):
wbufsize = 0
sni = None
def debug(self, s):
logging.debug("%s:%s: %s"%(self.client_address[0], self.client_address[1], str(s)))
def info(self, s):
logging.info("%s:%s: %s"%(self.client_address[0], self.client_address[1], str(s)))
def handle_sni(self, connection):
self.sni = connection.get_servername()
def serve_crafted(self, crafted, request_log):
response_log = crafted.serve(self.wfile, self.server.check_policy)
self.server.add_log(
dict(
type = "crafted",
request=request_log,
response=response_log
)
)
if response_log["disconnect"]:
return False
return True
2012-07-21 08:50:41 +00:00
def handle_request(self):
"""
Returns True if handling should continue.
"""
line = self.rfile.readline()
if line == "\r\n" or line == "\n": # Possible leftover from previous message
line = self.rfile.readline()
if line == "":
return
parts = http.parse_init_http(line)
if not parts:
s = "Invalid first line: %s"%repr(line)
2012-07-21 08:50:41 +00:00
self.info(s)
self.server.add_log(
dict(
type = "error",
msg = s
)
)
return
method, path, httpversion = parts
2012-07-21 08:50:41 +00:00
headers = http.read_headers(self.rfile)
request_log = dict(
path = path,
method = method,
headers = headers.lst,
httpversion = httpversion,
sni = self.sni,
remote_address = self.client_address,
)
2012-07-22 00:30:10 +00:00
try:
content = http.read_http_body_request(
self.rfile, self.wfile, headers, httpversion, None
)
except http.HttpError, s:
s = str(s)
self.info(s)
self.server.add_log(
dict(
type = "error",
msg = s
2012-07-21 08:50:41 +00:00
)
2012-07-22 00:30:10 +00:00
)
return
2012-07-21 08:50:41 +00:00
for i in self.server.anchors:
if i[0].match(path):
self.info("Serving anchor: %s"%path)
return self.serve_crafted(i[1], request_log)
2012-07-21 08:50:41 +00:00
if not self.server.nocraft and path.startswith(self.server.craftanchor):
spec = urllib.unquote(path)[len(self.server.craftanchor):]
self.info("Serving spec: %s"%spec)
2012-07-21 08:50:41 +00:00
try:
crafted = rparse.parse_response(self.server.request_settings, spec)
except rparse.ParseException, v:
self.info("Parse error: %s"%v.msg)
crafted = rparse.PathodErrorResponse(
"Parse Error",
"Error parsing response spec: %s\n"%v.msg + v.marked()
)
except rparse.FileAccessDenied:
self.info("File access denied")
crafted = rparse.PathodErrorResponse("Access Denied")
return self.serve_crafted(crafted, request_log)
elif self.server.noweb:
crafted = rparse.PathodErrorResponse("Access Denied")
crafted.serve(self.wfile, self.server.check_policy)
return False
2012-07-21 08:50:41 +00:00
else:
self.info("app: %s %s"%(method, path))
2012-07-21 08:50:41 +00:00
cc = wsgi.ClientConn(self.client_address)
req = wsgi.Request(cc, "http", method, path, headers, content)
sn = self.connection.getsockname()
app = wsgi.WSGIAdaptor(
self.server.app,
sn[0],
self.server.port,
version.NAMEVERSION
)
app.serve(req, self.wfile)
return True
2012-07-21 08:50:41 +00:00
def handle(self):
2012-06-19 22:59:38 +00:00
if self.server.ssloptions:
try:
self.convert_to_ssl(
self.server.ssloptions["certfile"],
self.server.ssloptions["keyfile"],
)
except tcp.NetLibError, v:
s = str(v)
self.server.add_log(
dict(
type = "error",
msg = s
)
)
self.info(s)
2012-07-21 08:50:41 +00:00
return
2012-06-19 22:59:38 +00:00
2012-06-24 23:34:29 +00:00
while not self.finished:
2012-07-21 08:50:41 +00:00
try:
if not self.handle_request():
return
2012-07-23 05:53:17 +00:00
except tcp.NetLibDisconnect: # pragma: no cover
2012-07-21 08:50:41 +00:00
self.info("Disconnect")
self.server.add_log(
dict(
type = "error",
2012-07-21 08:50:41 +00:00
msg = "Disconnect"
)
)
2012-07-21 08:50:41 +00:00
return
2012-06-19 04:57:57 +00:00
2012-06-19 01:23:07 +00:00
class Pathod(tcp.TCPServer):
LOGBUF = 500
def __init__( self,
addr, ssloptions=None, craftanchor="/p/", staticdir=None, anchors=None,
sizelimit=None, noweb=False, nocraft=False, noapi=False, nohang=False
):
2012-06-24 04:20:50 +00:00
"""
addr: (address, port) tuple. If port is 0, a free port will be
automatically chosen.
ssloptions: a dictionary containing certfile and keyfile specifications.
craftanchor: string specifying the path under which to anchor response generation.
2012-06-24 04:20:50 +00:00
staticdir: path to a directory of static resources, or None.
anchors: A list of (regex, spec) tuples, or None.
sizelimit: Limit size of served data.
nocraft: Disable response crafting.
noapi: Disable the API.
nohang: Disable pauses.
2012-06-24 04:20:50 +00:00
"""
2012-06-19 01:23:07 +00:00
tcp.TCPServer.__init__(self, addr)
2012-06-19 22:59:38 +00:00
self.ssloptions = ssloptions
self.staticdir = staticdir
self.craftanchor = craftanchor
self.sizelimit = sizelimit
self.noweb, self.nocraft, self.noapi, self.nohang = noweb, nocraft, noapi, nohang
if not noapi:
app.api()
2012-06-19 04:57:57 +00:00
self.app = app.app
self.app.config["pathod"] = self
self.log = []
self.logid = 0
2012-06-24 04:20:50 +00:00
self.anchors = []
if anchors:
for i in anchors:
2012-06-24 04:38:32 +00:00
try:
arex = re.compile(i[0])
except re.error:
raise PathodError("Invalid regex in anchor: %s"%i[0])
try:
aresp = rparse.parse_response(self.request_settings, i[1])
2012-06-24 04:38:32 +00:00
except rparse.ParseException, v:
raise PathodError("Invalid page spec in anchor: '%s', %s"%(i[1], str(v)))
2012-06-24 04:20:50 +00:00
self.anchors.append((arex, aresp))
def check_policy(self, req, actions):
"""
A policy check that verifies the request size is withing limits.
"""
if self.sizelimit and req.effective_length(actions) > self.sizelimit:
return "Response too large."
if self.nohang and any([i[1] == "pause" for i in actions]):
return "Pauses have been disabled."
return False
@property
def request_settings(self):
return dict(
staticdir = self.staticdir
)
def handle_connection(self, request, client_address):
h = PathodHandler(request, client_address, self)
h.handle()
h.finish()
def add_log(self, d):
if not self.noapi:
lock = threading.Lock()
with lock:
d["id"] = self.logid
self.log.insert(0, d)
if len(self.log) > self.LOGBUF:
self.log.pop()
self.logid += 1
return d["id"]
def clear_log(self):
lock = threading.Lock()
with lock:
self.log = []
def log_by_id(self, id):
for i in self.log:
if i["id"] == id:
return i
def get_log(self):
return self.log