aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libpathod/language/base.py63
-rw-r--r--libpathod/language/http.py53
-rw-r--r--libpathod/language/websockets.py15
-rw-r--r--test/test_language_base.py (renamed from test/test_language.py)70
-rw-r--r--test/test_language_http.py8
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"