diff options
-rw-r--r-- | libpathod/language/base.py | 63 | ||||
-rw-r--r-- | libpathod/language/http.py | 53 | ||||
-rw-r--r-- | libpathod/language/websockets.py | 15 | ||||
-rw-r--r-- | test/test_language_base.py (renamed from test/test_language.py) | 70 | ||||
-rw-r--r-- | test/test_language_http.py | 8 |
5 files changed, 113 insertions, 96 deletions
diff --git a/libpathod/language/base.py b/libpathod/language/base.py index eceeae26..f6530d75 100644 --- a/libpathod/language/base.py +++ b/libpathod/language/base.py @@ -213,19 +213,6 @@ Offset = pp.MatchFirst( ) -class Raw(_Token): - @classmethod - def expr(klass): - e = pp.Literal("r").suppress() - return e.setParseAction(lambda x: klass(*x)) - - def spec(self): - return "r" - - def freeze(self, settings): - return self - - class _Component(_Token): """ A value component of the primary specification of an HTTP message. @@ -386,7 +373,10 @@ class PathodSpec(_Token): return PathodSpec(ValueLiteral(f.encode("string_escape"))) -class Path(_Component): +class SimpleValue(_Component): + """ + A simple value - i.e. one without a preface. + """ def __init__(self, value): if isinstance(value, basestring): value = ValueLiteral(value) @@ -406,10 +396,13 @@ class Path(_Component): return "%s"%(self.value.spec()) def freeze(self, settings): - return Path(self.value.freeze(settings)) + return self.__class__(self.value.freeze(settings)) -class _Token(_Component): +class CaselessLiteral(_Component): + """ + A caseless token that can take only one value. + """ def __init__(self, value): self.value = value @@ -429,28 +422,12 @@ class _Token(_Component): return self -class WS(_Token): - TOK = "ws" - - -class WF(_Token): - TOK = "wf" - - -class Method(_Component): - methods = [ - "get", - "head", - "post", - "put", - "delete", - "options", - "trace", - "connect", - ] - +class OptionsOrValue(_Component): + """ + Can be any of a specified set of options, or a value specifier. + """ def __init__(self, value): - # If it's a string, we were passed one of the methods, so we upper-case + # If it's a string, we were passed one of the options, so we upper-case # it to be canonical. The user can specify a different case by using a # string value literal. if isinstance(value, basestring): @@ -459,7 +436,7 @@ class Method(_Component): @classmethod def expr(klass): - parts = [pp.CaselessLiteral(i) for i in klass.methods] + parts = [pp.CaselessLiteral(i) for i in klass.options] m = pp.MatchFirst(parts) spec = m | Value.copy() spec = spec.setParseAction(lambda x: klass(*x)) @@ -472,12 +449,12 @@ class Method(_Component): def spec(self): s = self.value.spec() - if s[1:-1].lower() in self.methods: + if s[1:-1].lower() in self.options: s = s[1:-1].lower() return "%s"%s def freeze(self, settings): - return Method(self.value.freeze(settings)) + return self.__class__(self.value.freeze(settings)) class Code(_Component): @@ -496,7 +473,7 @@ class Code(_Component): return "%s"%(self.code) def freeze(self, settings): - return Code(self.code) + return self class Reason(_Component): @@ -653,10 +630,6 @@ class _Message(object): return l[0] @property - def raw(self): - return bool(self.tok(Raw)) - - @property def actions(self): return self.toks(_Action) diff --git a/libpathod/language/http.py b/libpathod/language/http.py index df5d8ba8..7966b914 100644 --- a/libpathod/language/http.py +++ b/libpathod/language/http.py @@ -8,6 +8,31 @@ from netlib import http_status from . import base, generators, exceptions +class WS(base.CaselessLiteral): + TOK = "ws" + + +class Raw(base.CaselessLiteral): + TOK = "r" + + +class Path(base.SimpleValue): + pass + + +class Method(base.OptionsOrValue): + options = [ + "get", + "head", + "post", + "put", + "delete", + "options", + "trace", + "connect", + ] + + def get_header(val, headers): """ Header keys may be Values, so we have to "generate" them as we try the @@ -22,6 +47,10 @@ def get_header(val, headers): class _HTTPMessage(base._Message): version = "HTTP/1.1" + @property + def raw(self): + return bool(self.tok(Raw)) + @abc.abstractmethod def preamble(self, settings): # pragma: no cover @@ -47,14 +76,14 @@ class Response(_HTTPMessage): base.InjectAt, base.ShortcutContentType, base.ShortcutLocation, - base.Raw, + Raw, base.Reason ) logattrs = ["code", "reason", "version", "body"] @property def ws(self): - return self.tok(base.WS) + return self.tok(WS) @property def code(self): @@ -129,7 +158,7 @@ class Response(_HTTPMessage): [ pp.MatchFirst( [ - base.WS.expr() + pp.Optional( + WS.expr() + pp.Optional( base.Sep + base.Code.expr() ), base.Code.expr(), @@ -154,22 +183,22 @@ class Request(_HTTPMessage): base.InjectAt, base.ShortcutContentType, base.ShortcutUserAgent, - base.Raw, + Raw, base.PathodSpec, ) logattrs = ["method", "path", "body"] @property def ws(self): - return self.tok(base.WS) + return self.tok(WS) @property def method(self): - return self.tok(base.Method) + return self.tok(Method) @property def path(self): - return self.tok(base.Path) + return self.tok(Path) @property def pathodspec(self): @@ -191,7 +220,7 @@ class Request(_HTTPMessage): if not self.method: tokens.insert( 1, - base.Method("get") + Method("get") ) for i in netlib.websockets.client_handshake_headers().lst: if not get_header(i[0], self.headers): @@ -232,14 +261,14 @@ class Request(_HTTPMessage): [ pp.MatchFirst( [ - base.WS.expr() + pp.Optional( - base.Sep + base.Method.expr() + WS.expr() + pp.Optional( + base.Sep + Method.expr() ), - base.Method.expr(), + Method.expr(), ] ), base.Sep, - base.Path.expr(), + Path.expr(), pp.ZeroOrMore(base.Sep + atom) ] ) diff --git a/libpathod/language/websockets.py b/libpathod/language/websockets.py index 29b7311c..7ec4a2b1 100644 --- a/libpathod/language/websockets.py +++ b/libpathod/language/websockets.py @@ -3,6 +3,19 @@ import netlib.websockets import contrib.pyparsing as pp from . import base, generators +""" + wf:ctext:b'foo' + wf:c15:r'foo' + wf:fin:rsv1:rsv2:rsv3:mask + wf:-fin:-rsv1:-rsv2:-rsv3:-mask + wf:p234 + wf:m"mask" +""" + + +class WF(base.CaselessLiteral): + TOK = "wf" + class WebsocketFrame(base._Message): comps = ( @@ -19,7 +32,7 @@ class WebsocketFrame(base._Message): atom = pp.MatchFirst(parts) resp = pp.And( [ - base.WF.expr(), + WF.expr(), base.Sep, pp.ZeroOrMore(base.Sep + atom) ] diff --git a/test/test_language.py b/test/test_language_base.py index ffa5e82c..10f11d42 100644 --- a/test/test_language.py +++ b/test/test_language_base.py @@ -9,11 +9,12 @@ def parse_request(s): return language.parse_requests(s)[0] -class TestWS: - def test_expr(self): - v = base.WS("foo") - assert v.expr() - assert v.values(language.Settings()) +def test_caseless_literal(): + class CL(base.CaselessLiteral): + TOK = "foo" + v = CL("foo") + assert v.expr() + assert v.values(language.Settings()) class TestValueNakedLiteral: @@ -166,11 +167,11 @@ class TestMisc: assert base.Value.parseString('"val"')[0].val == "val" assert base.Value.parseString('"\'val\'"')[0].val == "'val'" - def test_path(self): - e = base.Path.expr() + def test_simplevalue(self): + e = base.SimpleValue.expr() assert e.parseString('"/foo"')[0].value.val == "/foo" - v = base.Path("/foo") + v = base.SimpleValue("/foo") assert v.value.val == "/foo" v = e.parseString("@100")[0] @@ -182,32 +183,6 @@ class TestMisc: s = v.spec() assert s == v.expr().parseString(s)[0].spec() - def test_method(self): - e = base.Method.expr() - assert e.parseString("get")[0].value.val == "GET" - assert e.parseString("'foo'")[0].value.val == "foo" - assert e.parseString("'get'")[0].value.val == "get" - - assert e.parseString("get")[0].spec() == "get" - assert e.parseString("'foo'")[0].spec() == "'foo'" - - s = e.parseString("get")[0].spec() - assert s == e.parseString(s)[0].spec() - - s = e.parseString("'foo'")[0].spec() - assert s == e.parseString(s)[0].spec() - - v = e.parseString("@100")[0] - v2 = v.freeze({}) - v3 = v2.freeze({}) - assert v2.value.val == v3.value.val - - def test_raw(self): - e = base.Raw.expr().parseString("r")[0] - assert e - assert e.spec() == "r" - assert e.freeze({}).spec() == "r" - def test_body(self): e = base.Body.expr() v = e.parseString("b'foo'")[0] @@ -470,3 +445,30 @@ class TestPauses: def test_freeze(self): l = base.PauseAt("r", 5) assert l.freeze({}).spec() == l.spec() + + +def test_options_or_value(): + class TT(base.OptionsOrValue): + options = [ + "one", + "two", + "three" + ] + e = TT.expr() + assert e.parseString("one")[0].value.val == "ONE" + assert e.parseString("'foo'")[0].value.val == "foo" + assert e.parseString("'get'")[0].value.val == "get" + + assert e.parseString("one")[0].spec() == "one" + assert e.parseString("'foo'")[0].spec() == "'foo'" + + s = e.parseString("one")[0].spec() + assert s == e.parseString(s)[0].spec() + + s = e.parseString("'foo'")[0].spec() + assert s == e.parseString(s)[0].spec() + + v = e.parseString("@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val diff --git a/test/test_language_http.py b/test/test_language_http.py index 50473339..f2528da4 100644 --- a/test/test_language_http.py +++ b/test/test_language_http.py @@ -129,15 +129,15 @@ class TestRequest: r = parse_request('ws:/path/') res = r.resolve(language.Settings()) assert res.method.string().lower() == "get" - assert res.tok(base.Path).value.val == "/path/" - assert res.tok(base.Method).value.val.lower() == "get" + assert res.tok(http.Path).value.val == "/path/" + assert res.tok(http.Method).value.val.lower() == "get" assert http.get_header("Upgrade", res.headers).value.val == "websocket" r = parse_request('ws:put:/path/') res = r.resolve(language.Settings()) assert r.method.string().lower() == "put" - assert res.tok(base.Path).value.val == "/path/" - assert res.tok(base.Method).value.val.lower() == "put" + assert res.tok(http.Path).value.val == "/path/" + assert res.tok(http.Method).value.val.lower() == "put" assert http.get_header("Upgrade", res.headers).value.val == "websocket" |