aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libpathod/language/__init__.py15
-rw-r--r--libpathod/language/base.py55
-rw-r--r--libpathod/language/websockets.py11
-rw-r--r--libpathod/templates/docs_lang_websockets.html39
-rw-r--r--test/test_language_base.py26
-rw-r--r--test/test_language_websocket.py6
6 files changed, 120 insertions, 32 deletions
diff --git a/libpathod/language/__init__.py b/libpathod/language/__init__.py
index 8837a174..36ec19e2 100644
--- a/libpathod/language/__init__.py
+++ b/libpathod/language/__init__.py
@@ -5,20 +5,7 @@ import pyparsing as pp
from . import base, http, websockets, writer, exceptions
from exceptions import *
-
-
-class Settings:
- def __init__(
- self,
- staticdir = None,
- unconstrained_file_access = False,
- request_host = None,
- websocket_key = None
- ):
- self.staticdir = staticdir
- self.unconstrained_file_access = unconstrained_file_access
- self.request_host = request_host
- self.websocket_key = websocket_key
+from base import Settings
def parse_response(s):
diff --git a/libpathod/language/base.py b/libpathod/language/base.py
index 4179fa7d..41ad639a 100644
--- a/libpathod/language/base.py
+++ b/libpathod/language/base.py
@@ -7,6 +7,21 @@ from .. import utils
from . import generators, exceptions
+class Settings:
+ def __init__(
+ self,
+ staticdir = None,
+ unconstrained_file_access = False,
+ request_host = None,
+ websocket_key = None
+ ):
+ self.staticdir = staticdir
+ self.unconstrained_file_access = unconstrained_file_access
+ self.request_host = request_host
+ self.websocket_key = websocket_key
+
+
+
Sep = pp.Optional(pp.Literal(":")).suppress()
@@ -375,6 +390,46 @@ class Value(_Component):
return self.__class__(self.value.freeze(settings))
+class FixedLengthValue(Value):
+ """
+ A value component lead by an optional preamble.
+ """
+ preamble = ""
+ length = None
+
+ def __init__(self, value):
+ Value.__init__(self, value)
+ lenguess = None
+ try:
+ lenguess = len(value.get_generator(Settings()))
+ except exceptions.RenderError:
+ pass
+ # This check will fail if we know the length upfront
+ if lenguess is not None and lenguess != self.length:
+ raise exceptions.RenderError(
+ "Invalid value length: '%s' is %s bytes, should be %s."%(
+ self.spec(),
+ lenguess,
+ self.length
+ )
+ )
+
+ def values(self, settings):
+ ret = Value.values(self, settings)
+ l = sum(len(i) for i in ret)
+ # This check will fail if we don't know the length upfront - i.e. for
+ # file inputs
+ if l != self.length:
+ raise exceptions.RenderError(
+ "Invalid value length: '%s' is %s bytes, should be %s."%(
+ self.spec(),
+ l,
+ self.length
+ )
+ )
+ return ret
+
+
class Boolean(_Component):
"""
A boolean flag.
diff --git a/libpathod/language/websockets.py b/libpathod/language/websockets.py
index a6674988..3abdf9d8 100644
--- a/libpathod/language/websockets.py
+++ b/libpathod/language/websockets.py
@@ -8,8 +8,9 @@ from . import base, generators, actions, message
wf:c15:r'foo'
wf:fin:rsv1:rsv2:rsv3:mask
wf:-fin:-rsv1:-rsv2:-rsv3:-mask
- wf:p234
- wf:m"mask"
+
+ wf:k"mask"
+ wf:l234
"""
@@ -58,6 +59,11 @@ class Mask(base.Boolean):
name = "mask"
+class Key(base.FixedLengthValue):
+ preamble = "k"
+ length = 4
+
+
class WebsocketFrame(message.Message):
comps = (
Body,
@@ -72,6 +78,7 @@ class WebsocketFrame(message.Message):
actions.PauseAt,
actions.DisconnectAt,
actions.InjectAt,
+ Key,
Raw,
)
diff --git a/libpathod/templates/docs_lang_websockets.html b/libpathod/templates/docs_lang_websockets.html
index 9b595e74..c50d081f 100644
--- a/libpathod/templates/docs_lang_websockets.html
+++ b/libpathod/templates/docs_lang_websockets.html
@@ -24,6 +24,13 @@
</tr>
<tr>
+ <td> d<a href="#offsetspec">OFFSET</a> </td>
+ <td>
+ Disconnect after OFFSET bytes.
+ </td>
+ </tr>
+
+ <tr>
<td> [-]fin </td>
<td>
Set or un-set the <b>fin</b> bit.
@@ -31,54 +38,56 @@
</tr>
<tr>
- <td> [-]rsv1 </td>
+ <td> i<a href="#offsetspec">OFFSET</a>,<a href="#valuespec">VALUE</a> </td>
<td>
- Set or un-set the <b>rsv1</b> bit.
+ Inject the specified value at the offset.
</td>
</tr>
<tr>
- <td> [-]rsv2 </td>
+ <td> [-]mask </td>
<td>
- Set or un-set the <b>rsv2</b> bit.
+ Set or un-set the <b>mask</b> bit.
</td>
</tr>
<tr>
- <td> [-]rsv3 </td>
+ <td> p<a href="#offsetspec">OFFSET</a>,SECONDS </td>
<td>
- Set or un-set the <b>rsv3</b> bit.
+ Pause for SECONDS seconds after OFFSET bytes. SECONDS can
+ be an integer or "f" to pause forever.
</td>
</tr>
<tr>
- <td> [-]mask </td>
+ <td> r </td>
<td>
- Set or un-set the <b>mask</b> bit.
+ Create a "raw" frame - disables auto-generation of the masking
+ key if the mask bit is on.
</td>
</tr>
<tr>
- <td> d<a href="#offsetspec">OFFSET</a> </td>
+ <td> [-]rsv1 </td>
<td>
- Disconnect after OFFSET bytes.
+ Set or un-set the <b>rsv1</b> bit.
</td>
</tr>
<tr>
- <td> i<a href="#offsetspec">OFFSET</a>,<a href="#valuespec">VALUE</a> </td>
+ <td> [-]rsv2 </td>
<td>
- Inject the specified value at the offset.
+ Set or un-set the <b>rsv2</b> bit.
</td>
</tr>
<tr>
- <td> p<a href="#offsetspec">OFFSET</a>,SECONDS </td>
+ <td> [-]rsv3 </td>
<td>
- Pause for SECONDS seconds after OFFSET bytes. SECONDS can
- be an integer or "f" to pause forever.
+ Set or un-set the <b>rsv3</b> bit.
</td>
</tr>
+
</tbody>
</table>
diff --git a/test/test_language_base.py b/test/test_language_base.py
index c6cee7b3..bd67c010 100644
--- a/test/test_language_base.py
+++ b/test/test_language_base.py
@@ -168,7 +168,7 @@ class TestMisc:
assert base.TokValue.parseString('"val"')[0].val == "val"
assert base.TokValue.parseString('"\'val\'"')[0].val == "'val'"
- def test_prevalue(self):
+ def test_value(self):
class TT(base.Value):
preamble = "m"
e = TT.expr()
@@ -183,6 +183,30 @@ class TestMisc:
v3 = v2.freeze({})
assert v2.value.val == v3.value.val
+ def test_fixedlengthvalue(self):
+ class TT(base.FixedLengthValue):
+ preamble = "m"
+ length = 4
+
+ e = TT.expr()
+ assert e.parseString("m@4")
+ tutils.raises("invalid value length", e.parseString, "m@100")
+ tutils.raises("invalid value length", e.parseString, "m@1")
+
+ with tutils.tmpdir() as t:
+ p = os.path.join(t, "path")
+ s = base.Settings(staticdir=t)
+ with open(p, "wb") as f:
+ f.write("a" * 20)
+ v = e.parseString("m<path")[0]
+ tutils.raises("invalid value length", v.values, s)
+
+ p = os.path.join(t, "path")
+ with open(p, "wb") as f:
+ f.write("a" * 4)
+ v = e.parseString("m<path")[0]
+ assert v.values(s)
+
class TKeyValue(base.KeyValue):
preamble = "h"
diff --git a/test/test_language_websocket.py b/test/test_language_websocket.py
index 8abb55aa..4b384f61 100644
--- a/test/test_language_websocket.py
+++ b/test/test_language_websocket.py
@@ -21,6 +21,7 @@ class TestWebsocketFrame:
"wf:fin",
"wf:fin:rsv1:rsv2:rsv3:mask",
"wf:-fin:-rsv1:-rsv2:-rsv3:-mask",
+ "wf:k@4",
]
for i in specs:
wf = parse_request(i)
@@ -62,3 +63,8 @@ class TestWebsocketFrame:
frm = netlib.websockets.Frame.from_bytes(tutils.render(wf))
assert wf.opcode.value == frm.header.opcode
assert wf.opcode.value == netlib.websockets.OPCODE.BINARY
+
+ def test_auto_raw(self):
+ wf = parse_request("wf:b'foo':mask")
+ frm = netlib.websockets.Frame.from_bytes(tutils.render(wf))
+ print frm.human_readable()