aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libpathod/language.py73
-rw-r--r--libpathod/pathoc.py89
-rw-r--r--test/test_language.py12
3 files changed, 141 insertions, 33 deletions
diff --git a/libpathod/language.py b/libpathod/language.py
index e66f987f..d4c5b880 100644
--- a/libpathod/language.py
+++ b/libpathod/language.py
@@ -267,7 +267,7 @@ class _Token(object):
"""
return None
- def resolve(self, msg, settings):
+ def resolve(self, settings, msg):
"""
Resolves this token to ready it for transmission. This means that
the calculated offsets of actions are fixed.
@@ -371,8 +371,6 @@ class ValueFile(_Token):
def get_generator(self, settings):
if not settings.staticdir:
raise FileAccessDenied("File access disabled.")
- sd = os.path.normpath(os.path.abspath(settings.staticdir))
-
s = os.path.expanduser(self.path)
s = os.path.normpath(
os.path.abspath(os.path.join(settings.staticdir, s))
@@ -613,26 +611,34 @@ class Path(_Component):
return Path(self.value.freeze(settings))
-class WS(_Component):
+class _Token(_Component):
def __init__(self, value):
self.value = value
@classmethod
def expr(klass):
- spec = pp.CaselessLiteral("ws")
+ spec = pp.CaselessLiteral(klass.TOK)
spec = spec.setParseAction(lambda x: klass(*x))
return spec
def values(self, settings):
- return "ws"
+ return self.TOK
def spec(self):
- return "ws"
+ return self.TOK
def freeze(self, settings):
return self
+class WS(_Token):
+ TOK = "ws"
+
+
+class WF(_Token):
+ TOK = "wf"
+
+
class Method(_Component):
methods = [
"get",
@@ -724,7 +730,7 @@ class _Action(_Token):
def __init__(self, offset):
self.offset = offset
- def resolve(self, msg, settings):
+ def resolve(self, settings, msg):
"""
Resolves offset specifications to a numeric offset. Returns a copy
of the action object.
@@ -889,10 +895,6 @@ class _Message(object):
l += len(i.value.get_generator(settings))
return l
- @abc.abstractmethod
- def preamble(self, settings): # pragma: no cover
- pass
-
@classmethod
def expr(klass): # pragma: no cover
pass
@@ -929,6 +931,10 @@ Sep = pp.Optional(pp.Literal(":")).suppress()
class _HTTPMessage(_Message):
version = "HTTP/1.1"
+ @abc.abstractmethod
+ def preamble(self, settings): # pragma: no cover
+ pass
+
def values(self, settings):
vals = self.preamble(settings)
@@ -985,7 +991,7 @@ class Response(_HTTPMessage):
)
return l
- def resolve(self, settings):
+ def resolve(self, settings, msg=None):
tokens = self.tokens[:]
if self.ws:
if not settings.websocket_key:
@@ -1017,7 +1023,7 @@ class Response(_HTTPMessage):
)
intermediate = self.__class__(tokens)
return self.__class__(
- [i.resolve(intermediate, settings) for i in tokens]
+ [i.resolve(settings, intermediate) for i in tokens]
)
@classmethod
@@ -1035,6 +1041,7 @@ class Response(_HTTPMessage):
pp.ZeroOrMore(Sep + atom)
]
)
+ resp = resp.setParseAction(klass)
return resp
def spec(self):
@@ -1081,7 +1088,7 @@ class Request(_HTTPMessage):
v.append(self.version)
return v
- def resolve(self, settings):
+ def resolve(self, settings, msg=None):
tokens = self.tokens[:]
if self.ws:
if not self.method:
@@ -1114,7 +1121,7 @@ class Request(_HTTPMessage):
)
intermediate = self.__class__(tokens)
return self.__class__(
- [i.resolve(intermediate, settings) for i in tokens]
+ [i.resolve(settings, intermediate) for i in tokens]
)
@classmethod
@@ -1134,6 +1141,7 @@ class Request(_HTTPMessage):
pp.ZeroOrMore(Sep + atom)
]
)
+ resp = resp.setParseAction(klass)
return resp
def spec(self):
@@ -1155,13 +1163,27 @@ class WebsocketFrame(_Message):
atom = pp.MatchFirst(parts)
resp = pp.And(
[
- pp.Literal("wf"),
+ WF.expr(),
Sep,
pp.ZeroOrMore(Sep + atom)
]
)
+ resp = resp.setParseAction(klass)
return resp
+ def values(self, settings):
+ vals = [
+ websockets.FrameHeader().to_bytes()
+ ]
+ if self.body:
+ vals.append(self.body.value.get_generator(settings))
+ return vals
+
+ def resolve(self, settings, msg=None):
+ return self.__class__(
+ [i.resolve(settings, msg) for i in self.tokens]
+ )
+
def spec(self):
return ":".join([i.spec() for i in self.tokens])
@@ -1205,7 +1227,7 @@ def parse_response(s):
except UnicodeError:
raise ParseException("Spec must be valid ASCII.", 0, 0)
try:
- return Response(Response.expr().parseString(s, parseAll=True))
+ return Response.expr().parseString(s, parseAll=True)[0]
except pp.ParseException, v:
raise ParseException(v.msg, v.line, v.col)
@@ -1219,16 +1241,13 @@ def parse_requests(s):
except UnicodeError:
raise ParseException("Spec must be valid ASCII.", 0, 0)
try:
- parts = pp.OneOrMore(
- pp.Group(
- pp.Or(
- [
- Request.expr(),
- WebsocketFrame.expr(),
- ]
- )
+ return pp.OneOrMore(
+ pp.Or(
+ [
+ WebsocketFrame.expr(),
+ Request.expr(),
+ ]
)
).parseString(s, parseAll=True)
- return [Request(i) for i in parts]
except pp.ParseException, v:
raise ParseException(v.msg, v.line, v.col)
diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py
index cf9be5b9..89a8280b 100644
--- a/libpathod/pathoc.py
+++ b/libpathod/pathoc.py
@@ -3,10 +3,11 @@ import os
import hashlib
import random
import time
+import threading
import OpenSSL.crypto
-from netlib import tcp, http, certutils
+from netlib import tcp, http, certutils, websockets
import netlib.utils
import language
@@ -75,6 +76,17 @@ class Response:
return "Response(%s - %s)"%(self.status_code, self.msg)
+class WebsocketFrameReader(threading.Thread):
+ def __init__(self, rfile, callback):
+ threading.Thread.__init__(self)
+ self.rfile, self.callback = rfile, callback
+ self.daemon = True
+
+ def run(self):
+ while 1:
+ print websockets.Frame.from_file(self.rfile)
+
+
class Pathoc(tcp.TCPClient):
def __init__(
self,
@@ -184,13 +196,64 @@ class Pathoc(tcp.TCPClient):
print >> fp, "%s (unprintables escaped):"%header
print >> fp, netlib.utils.cleanBin(data)
- def request(self, r):
+ def websocket_get_frame(self, frame):
+ """
+ Called when a frame is received from the server.
+ """
+ pass
+
+ def websocket_send_frame(self, r):
+ """
+ Sends a single websocket frame.
+ """
+ req = None
+ if isinstance(r, basestring):
+ r = language.parse_requests(r)[0]
+ if self.showreq:
+ self.wfile.start_log()
+ try:
+ req = language.serve(r, self.wfile, self.settings)
+ self.wfile.flush()
+ except tcp.NetLibTimeout:
+ if self.ignoretimeout:
+ return None
+ if self.showsummary:
+ print >> self.fp, "<<", "Timeout"
+ raise
+ except tcp.NetLibDisconnect: # pragma: nocover
+ if self.showsummary:
+ print >> self.fp, "<<", "Disconnect"
+ raise
+ finally:
+ if req:
+ if self.explain:
+ print >> self.fp, ">> Spec:", r.spec()
+ if self.showreq:
+ self._show(
+ self.fp, ">> Request",
+ self.wfile.get_log(),
+ self.hexdump
+ )
+
+ def websocket_start(self, r, callback=None):
+ """
+ Performs an HTTP request, and attempts to drop into websocket
+ connection.
+ """
+ resp = self.http(r)
+ if resp.status_code == 101:
+ if self.showsummary:
+ print >> self.fp, "Websocket connection established..."
+ WebsocketFrameReader(self.rfile, self.websocket_get_frame).start()
+ return resp
+
+ def http(self, r):
"""
Performs a single request.
r: A language.Request object, or a string representing one request.
- Returns True if we have a non-ignored response.
+ Returns Response if we have a non-ignored response.
May raise http.HTTPError, tcp.NetLibError
"""
@@ -253,6 +316,26 @@ class Pathoc(tcp.TCPClient):
)
return resp
+ def request(self, r):
+ """
+ Performs a single request.
+
+ r: A language.Request object, or a string representing one request.
+
+ Returns Response if we have a non-ignored response.
+
+ May raise http.HTTPError, tcp.NetLibError
+ """
+ if isinstance(r, basestring):
+ r = language.parse_requests(r)[0]
+ if isinstance(r, language.Request):
+ if r.ws:
+ return self.websocket_start(r, self.websocket_get_frame)
+ else:
+ return self.http(r)
+ elif isinstance(r, language.WebsocketFrame):
+ self.websocket_send_frame(r)
+
def main(args): # pragma: nocover
memo = set([])
diff --git a/test/test_language.py b/test/test_language.py
index 919f5f65..0fb8479d 100644
--- a/test/test_language.py
+++ b/test/test_language.py
@@ -406,7 +406,7 @@ class Test_Action:
def test_resolve(self):
r = parse_request('GET:"/foo"')
e = language.DisconnectAt("r")
- ret = e.resolve(r, {})
+ ret = e.resolve({}, r)
assert isinstance(ret.offset, int)
def test_repr(self):
@@ -637,12 +637,18 @@ class TestRequest:
assert utils.get_header("Upgrade", res.headers).value.val == "websocket"
-
class TestWebsocketFrame:
def test_spec(self):
e = language.WebsocketFrame.expr()
- assert e.parseString("wf:foo")
+ wf = e.parseString("wf:b'foo'")
+ assert wf
+
+ assert parse_request("wf:b'foo'")
+
+ def test_values(self):
+ r = parse_request("wf:b'foo'")
+ assert r.values(language.Settings())
class TestWriteValues: