From d66dedc6e7bdc201cd4d3fa5f04c9636a8ad57f4 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 16 May 2015 09:42:47 +1200 Subject: websockets: mask specification - Add a FixedLengthValue - Use it to implement a 4-byte mask specifier - Adjust docs --- libpathod/language/__init__.py | 15 +------- libpathod/language/base.py | 55 +++++++++++++++++++++++++++ libpathod/language/websockets.py | 11 +++++- libpathod/templates/docs_lang_websockets.html | 39 +++++++++++-------- test/test_language_base.py | 26 ++++++++++++- test/test_language_websocket.py | 6 +++ 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 @@ -23,6 +23,13 @@ + + dOFFSET + + Disconnect after OFFSET bytes. + + + [-]fin @@ -31,54 +38,56 @@ - [-]rsv1 + iOFFSET,VALUE - Set or un-set the rsv1 bit. + Inject the specified value at the offset. - [-]rsv2 + [-]mask - Set or un-set the rsv2 bit. + Set or un-set the mask bit. - [-]rsv3 + pOFFSET,SECONDS - Set or un-set the rsv3 bit. + Pause for SECONDS seconds after OFFSET bytes. SECONDS can + be an integer or "f" to pause forever. - [-]mask + r - Set or un-set the mask bit. + Create a "raw" frame - disables auto-generation of the masking + key if the mask bit is on. - dOFFSET + [-]rsv1 - Disconnect after OFFSET bytes. + Set or un-set the rsv1 bit. - iOFFSET,VALUE + [-]rsv2 - Inject the specified value at the offset. + Set or un-set the rsv2 bit. - pOFFSET,SECONDS + [-]rsv3 - Pause for SECONDS seconds after OFFSET bytes. SECONDS can - be an integer or "f" to pause forever. + Set or un-set the rsv3 bit. + 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