Add hooks for policy checks of served data.

This commit is contained in:
Aldo Cortesi 2012-07-23 14:37:00 +12:00
parent 204a556aa7
commit c7b5faf7db
3 changed files with 55 additions and 10 deletions

View File

@ -86,7 +86,7 @@ class PathodHandler(tcp.BaseHandler):
httpversion = httpversion, httpversion = httpversion,
) )
if crafted: if crafted:
response_log = crafted.serve(self.wfile) response_log = crafted.serve(self.wfile, self.check_size)
if response_log["disconnect"]: if response_log["disconnect"]:
return return
self.server.add_log( self.server.add_log(
@ -110,6 +110,9 @@ class PathodHandler(tcp.BaseHandler):
self.debug("%s %s"%(method, path)) self.debug("%s %s"%(method, path))
return True return True
def check_size(self, req, actions):
return False
def handle(self): def handle(self):
if self.server.ssloptions: if self.server.ssloptions:
try: try:
@ -145,7 +148,7 @@ class PathodHandler(tcp.BaseHandler):
class Pathod(tcp.TCPServer): class Pathod(tcp.TCPServer):
LOGBUF = 500 LOGBUF = 500
def __init__(self, addr, ssloptions=None, prefix="/p/", staticdir=None, anchors=None): def __init__(self, addr, ssloptions=None, prefix="/p/", staticdir=None, anchors=None, sizelimit=None):
""" """
addr: (address, port) tuple. If port is 0, a free port will be addr: (address, port) tuple. If port is 0, a free port will be
automatically chosen. automatically chosen.
@ -153,11 +156,13 @@ class Pathod(tcp.TCPServer):
prefix: string specifying the prefix at which to anchor response generation. prefix: string specifying the prefix at which to anchor response generation.
staticdir: path to a directory of static resources, or None. staticdir: path to a directory of static resources, or None.
anchors: A list of (regex, spec) tuples, or None. anchors: A list of (regex, spec) tuples, or None.
sizelimit: Limit size of served data.
""" """
tcp.TCPServer.__init__(self, addr) tcp.TCPServer.__init__(self, addr)
self.ssloptions = ssloptions self.ssloptions = ssloptions
self.staticdir = staticdir self.staticdir = staticdir
self.prefix = prefix self.prefix = prefix
self.sizelimit = sizelimit
self.app = app.app self.app = app.app
self.app.config["pathod"] = self self.app.config["pathod"] = self
self.log = [] self.log = []

View File

@ -25,6 +25,18 @@ class ParseException(Exception):
class ServerError(Exception): pass class ServerError(Exception): pass
def actions_log(lst):
ret = []
for i in lst:
if i[1] == "inject":
ret.append(
[i[0], i[1], repr(i[2])]
)
else:
ret.append(i)
return ret
def ready_actions(length, lst): def ready_actions(length, lst):
ret = [] ret = []
for i in lst: for i in lst:
@ -527,7 +539,15 @@ class Message:
l += len(i[2]) l += len(i[2])
return l return l
def serve(self, fp): def serve(self, fp, check):
"""
fp: The file pointer to write to.
check: A function called with the effective actions (after random
values have been calculated). If it returns False service
proceeds, otherwise the return is treated as an error message to be
sent to the client, and service stops.
"""
started = time.time() started = time.time()
if self.body and not utils.get_header("Content-Length", self.headers): if self.body and not utils.get_header("Content-Length", self.headers):
self.headers.append( self.headers.append(
@ -554,13 +574,26 @@ class Message:
vals.reverse() vals.reverse()
actions = ready_actions(self.length(), self.actions) actions = ready_actions(self.length(), self.actions)
actions.reverse() actions.reverse()
if check:
ret = check(self, actions)
if ret:
err = InternalResponse(
800,
ret
)
err.serve(fp)
return dict(
disconnect = True,
actions = actions_log(actions),
error = ret
)
disconnect = write_values(fp, vals, actions[:]) disconnect = write_values(fp, vals, actions[:])
duration = time.time() - started duration = time.time() - started
ret = dict( ret = dict(
disconnect = disconnect, disconnect = disconnect,
started = started, started = started,
duration = duration, duration = duration,
actions = actions, actions = actions_log(actions),
) )
for i in self.logattrs: for i in self.logattrs:
v = getattr(self, i) v = getattr(self, i)
@ -661,8 +694,8 @@ class CraftedRequest(Request):
for i in tokens: for i in tokens:
i.accept(settings, self) i.accept(settings, self)
def serve(self, fp): def serve(self, fp, check=None):
d = Request.serve(self, fp) d = Request.serve(self, fp, check)
d["spec"] = self.spec d["spec"] = self.spec
return d return d
@ -674,8 +707,8 @@ class CraftedResponse(Response):
for i in tokens: for i in tokens:
i.accept(settings, self) i.accept(settings, self)
def serve(self, fp): def serve(self, fp, check=None):
d = Response.serve(self, fp) d = Response.serve(self, fp, check)
d["spec"] = self.spec d["spec"] = self.spec
return d return d
@ -697,8 +730,8 @@ class InternalResponse(Response):
) )
] ]
def serve(self, fp): def serve(self, fp, check=None):
d = Response.serve(self, fp) d = Response.serve(self, fp, check)
d["internal"] = True d["internal"] = True
return d return d

View File

@ -419,6 +419,13 @@ class TestResponse:
assert r.body[:] assert r.body[:]
assert str(r) assert str(r)
def test_checkfunc(self):
s = cStringIO.StringIO()
r = rparse.parse_response({}, "400:b@100k")
def check(req, acts):
return "errmsg"
assert r.serve(s, check=check)["error"] == "errmsg"
def test_render(self): def test_render(self):
s = cStringIO.StringIO() s = cStringIO.StringIO()
r = rparse.parse_response({}, "400'msg'") r = rparse.parse_response({}, "400'msg'")