From fc1fc80469dca11ff0241c4b263e4b39e5506ddd Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 26 Oct 2014 10:50:32 +1300 Subject: [PATCH] Allow nesting of pathod response specs in pathoc specs This opens the door to really neat, repeatable, client-side driven fuzzing, especially of proxies. --- libpathod/language.py | 48 +++++++++++++++++++++++++++++++++++++++++-- test/test_language.py | 25 ++++++++++++++++++++-- test/test_pathoc.py | 5 ++++- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 56cbc18b3..d8e871458 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -1,3 +1,4 @@ +from __future__ import print_function import operator import string import random @@ -6,7 +7,6 @@ import os import time import copy import abc -from email.utils import formatdate import contrib.pyparsing as pp from netlib import http_status, tcp, http_uastrings @@ -527,6 +527,43 @@ class Body(_Component): return Body(self.value.freeze(settings)) +class PathodSpec(_Token): + def __init__(self, value): + self.value = value + try: + self.parsed = Response( + Response.expr().parseString( + value.val, + parseAll=True + ) + ) + except pp.ParseException, v: + raise ParseException(v.msg, v.line, v.col) + + @classmethod + def expr(klass): + e = pp.Literal("s").suppress() + e = e + ValueLiteral.expr() + return e.setParseAction(lambda x: klass(*x)) + + def values(self, settings): + return [ + self.value.get_generator(settings), + ] + + def quote(self, s): + quotechar = s[0] + s = s[1:-1] + s = s.replace(quotechar, "\\" + quotechar) + return quotechar + s + quotechar + + def spec(self): + return "s%s"%(self.quote(self.value.spec())) + + def freeze(self, settings): + return PathodSpec(ValueLiteral(self.parsed.freeze(settings).spec())) + + class Path(_Component): def __init__(self, value): if isinstance(value, basestring): @@ -934,7 +971,8 @@ class Request(_Message): InjectAt, ShortcutContentType, ShortcutUserAgent, - Raw + Raw, + PathodSpec, ) logattrs = ["method", "path", "body"] @@ -946,10 +984,16 @@ class Request(_Message): def path(self): return self._get_token(Path) + @property + def pathodspec(self): + return self._get_token(PathodSpec) + def preamble(self, settings): v = self.method.values(settings) v.append(" ") v.extend(self.path.values(settings)) + if self.pathodspec: + v.append(self.pathodspec.parsed.spec()) v.append(" ") v.append(self.version) return v diff --git a/test/test_language.py b/test/test_language.py index 0818c587f..f3cfa5a98 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -223,6 +223,29 @@ class TestMisc: s = v.spec() assert s == e.parseString(s)[0].spec() + def test_pathodspec(self): + e = language.PathodSpec.expr() + v = e.parseString("s'200'")[0] + assert v.value.val == "200" + tutils.raises( + language.ParseException, + e.parseString, + "s'foo'" + ) + + v = e.parseString('s"200:b@1"')[0] + assert "@1" in v.spec() + f = v.freeze({}) + assert "@1" not in f.spec() + + r = parse_request('GET:"/foo":s"200"') + assert "200" in r.preamble({}) + + f = r.freeze({}) + assert parse_request(f.spec()) + + + def test_code(self): e = language.Code.expr() v = e.parseString("200")[0] @@ -661,14 +684,12 @@ class TestResponse: language.serve(r, s, {}) v = s.getvalue() assert "Content-Length" in v - assert "Date" in v s = cStringIO.StringIO() r = language.parse_response("400:b'foo':r") language.serve(r, s, {}) v = s.getvalue() assert not "Content-Length" in v - assert not "Date" in v def test_length(self): def testlen(x): diff --git a/test/test_pathoc.py b/test/test_pathoc.py index 2542b622c..23b42994f 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -59,8 +59,11 @@ class _TestDaemon: c.settimeout(timeout) s = cStringIO.StringIO() for i in requests: + r = language.parse_requests(i)[0] + if explain: + r = r.freeze({}) c.print_request( - language.parse_requests(i)[0], + r, showreq = showreq, showresp = showresp, explain = explain,