def test_parse_err(self): tutils.raises(language.ParseException, language.parse_pathod, "400:msg,b:") try: language.parse_pathod("400'msg':b:") except language.ParseException as v: assert v.marked() assert str(v)
def test_parse_pathod(self): a = next(parse_pathod("400:ir,@100")).actions[0] assert a.offset == "r" assert a.value.datatype == "bytes" assert a.value.usize == 100 a = next(parse_pathod("400:ia,@100")).actions[0] assert a.offset == "a"
def test_shortcuts(): assert next(language.parse_pathod( "400:c'foo'")).headers[0].key.val == b"Content-Type" assert next(language.parse_pathod( "400:l'foo'")).headers[0].key.val == b"Location" assert b"Android" in tservers.render(parse_request("get:/:ua")) assert b"User-Agent" in tservers.render(parse_request("get:/:ua"))
def test_render(self): s = io.BytesIO() r = next(language.parse_pathod("400:m'msg'")) assert language.serve(r, s, {}) r = next(language.parse_pathod("400:p0,100:dr")) assert "p0" in r.spec() s = r.preview_safe() assert "p0" not in s.spec()
def test_length(self): def testlen(x): s = io.BytesIO() x = next(x) language.serve(x, s, language.Settings()) assert x.length(language.Settings()) == len(s.getvalue()) testlen(language.parse_pathod("400:m'msg':r")) testlen(language.parse_pathod("400:m'msg':h'foo'='bar':r")) testlen(language.parse_pathod("400:m'msg':h'foo'='bar':b@100b:r"))
def test_raw(self): s = io.BytesIO() r = next(language.parse_pathod("400:b'foo'")) language.serve(r, s, {}) v = s.getvalue() assert b"Content-Length" in v s = io.BytesIO() r = next(language.parse_pathod("400:b'foo':r")) language.serve(r, s, {}) v = s.getvalue() assert b"Content-Length" not in v
def test_raw(self): s = StringIO() r = language.parse_pathod("400:b'foo'").next() language.serve(r, s, {}) v = s.getvalue() assert "Content-Length" in v s = StringIO() r = language.parse_pathod("400:b'foo':r").next() language.serve(r, s, {}) v = s.getvalue() assert "Content-Length" not in v
def test_write_values_after(): s = StringIO() r = language.parse_pathod("400:da").next() language.serve(r, s, {}) s = StringIO() r = language.parse_pathod("400:pa,0").next() language.serve(r, s, {}) s = StringIO() r = language.parse_pathod("400:ia,'xx'").next() language.serve(r, s, {}) assert s.getvalue().endswith("xx")
def test_write_values_after(): s = io.BytesIO() r = next(language.parse_pathod("400:da")) language.serve(r, s, {}) s = io.BytesIO() r = next(language.parse_pathod("400:pa,0")) language.serve(r, s, {}) s = io.BytesIO() r = next(language.parse_pathod("400:ia,'xx'")) language.serve(r, s, {}) assert s.getvalue().endswith(b'xx')
def test_response(self): r = next(language.parse_pathod("400:m'msg'")) assert r.status_code.string() == b"400" assert r.reason.string() == b"msg" r = next(language.parse_pathod("400:m'msg':b@100b")) assert r.reason.string() == b"msg" assert r.body.values({}) assert str(r) r = next(language.parse_pathod("200")) assert r.status_code.string() == b"200" assert not r.reason assert b"OK" in [i[:] for i in r.preamble({})]
def test_parse_stress(self): # While larger values are known to work on linux, len() technically # returns an int and a python 2.7 int on windows has 32bit precision. # Therefore, we should keep the body length < 2147483647 bytes in our # tests. r = next(language.parse_pathod("400:b@1g")) assert r.length({})
def test_maximum_length(self): def testlen(x): x = next(x) s = io.BytesIO() m = x.maximum_length({}) language.serve(x, s, {}) assert m >= len(s.getvalue()) r = language.parse_pathod("400:m'msg':b@100:d0") testlen(r) r = language.parse_pathod("400:m'msg':b@100:d0:i0,'foo'") testlen(r) r = language.parse_pathod("400:m'msg':b@100:d0:i0,'foo'") testlen(r)
def rt(s): s = language.parse_pathod(s).next().spec() assert language.parse_pathod(s).next().spec() == s
def test_websockets(self): r = language.parse_pathod("ws").next() tutils.raises("no websocket key", r.resolve, language.Settings()) res = r.resolve(language.Settings(websocket_key="foo")) assert res.status_code.string() == "101"
def rt(s): s = next(language.parse_pathod(s)).spec() assert next(language.parse_pathod(s)).spec() == s
def test_websockets(self): r = next(language.parse_pathod("ws")) with pytest.raises("no websocket key"): r.resolve(language.Settings()) res = r.resolve(language.Settings(websocket_key=b"foo")) assert res.status_code.string() == b"101"
def test_websockets(self): r = next(language.parse_pathod("ws")) with pytest.raises(Exception, match="No websocket key"): r.resolve(language.Settings()) res = r.resolve(language.Settings(websocket_key=b"foo")) assert res.status_code.string() == b"101"
def test_parse_pause_random(self): r = next(language.parse_pathod("400:pr,10")) assert r.actions[0].spec() == "pr,10"
def test_parse_pause_before(self): r = next(language.parse_pathod("400:p0,10")) assert r.actions[0].spec() == "p0,10"
def test_parse_pause_after(self): r = next(language.parse_pathod("400:pa,10")) assert r.actions[0].spec() == "pa,10"
def test_parse_header(self): r = next(language.parse_pathod('400:h"foo"="bar"')) assert http.get_header(b"foo", r.headers)
def test_nonascii(self): with pytest.raises(Exception, match="ASCII"): language.parse_pathod("foo:b\xf0")
def dummy_response(self): return next(language.parse_pathod("400'msg'"))
def handle_http_request(self, logger): """ Returns a (handler, log) tuple. handler: Handler for the next request, or None to disconnect log: A dictionary, or None """ with logger.ctx() as lg: try: req = self.protocol.read_request(self.rfile) except exceptions.HttpReadDisconnect: return None, None except exceptions.HttpException as s: s = str(s) lg(s) return None, dict(type="error", msg=s) if req.method == 'CONNECT': return self.protocol.handle_http_connect( [req.host, req.port, req.http_version], lg) method = req.method path = req.path http_version = req.http_version headers = req.headers first_line_format = req.first_line_format clientcert = None if self.clientcert: clientcert = dict( cn=self.clientcert.cn, subject=self.clientcert.subject, serial=self.clientcert.serial, notbefore=self.clientcert.notbefore.isoformat(), notafter=self.clientcert.notafter.isoformat(), keyinfo=self.clientcert.keyinfo, ) retlog = dict( type="crafted", protocol="http", request=dict(path=path, method=method, headers=headers.fields, http_version=http_version, sni=self.sni, remote_address=self.address, clientcert=clientcert, first_line_format=first_line_format), cipher=None, ) if self.ssl_established: retlog["cipher"] = self.get_current_cipher() m = utils.MemBool() valid_websocket_handshake = websockets.check_handshake(headers) self.settings.websocket_key = websockets.get_client_key(headers) # If this is a websocket initiation, we respond with a proper # server response, unless over-ridden. if valid_websocket_handshake: anchor_gen = language.parse_pathod("ws") else: anchor_gen = None for regex, spec in self.server.anchors: if regex.match(path): anchor_gen = language.parse_pathod(spec, self.use_http2) break else: if m(path.startswith(self.server.craftanchor)): spec = urllib.parse.unquote( path)[len(self.server.craftanchor):] if spec: try: anchor_gen = language.parse_pathod( spec, self.use_http2) except language.ParseException as v: lg("Parse error: %s" % v.msg) anchor_gen = iter([ self.make_http_error_response( "Parse Error", "Error parsing response spec: %s\n" % (v.msg + v.marked())) ]) else: if self.use_http2: anchor_gen = iter([ self.make_http_error_response( "Spec Error", "HTTP/2 only supports request/response with the craft anchor point: %s" % self.server.craftanchor) ]) if not anchor_gen: anchor_gen = iter([ self.make_http_error_response( "Not found", "No valid craft request found") ]) spec = next(anchor_gen) if self.use_http2 and isinstance(spec, language.http2.Response): spec.stream_id = req.stream_id lg("crafting spec: %s" % spec) nexthandler, retlog["response"] = self.http_serve_crafted(spec, lg) if nexthandler and valid_websocket_handshake: self.protocol = protocols.websockets.WebsocketsProtocol(self) return self.protocol.handle_websocket, retlog else: return nexthandler, retlog
def parse_response(s): return next(language.parse_pathod(s, True))
def test_unique_components(): with pytest.raises(Exception, match="multiple body clauses"): language.parse_pathod("400:b@1:b@1")
def test_request(self): r = next(parse_pathod('400:p10,10')) assert r.actions[0].spec() == "p10,10"
def test_serve(self): s = StringIO() r = language.parse_pathod("400:i0,'foo'").next() assert language.serve(r, s, {})
def parse_response(s): return language.parse_pathod(s, True).next()
def test_unique_components(): with pytest.raises("multiple body clauses"): language.parse_pathod("400:b@1:b@1")
def test_websockets(self): r = next(language.parse_pathod("ws")) tutils.raises("no websocket key", r.resolve, language.Settings()) res = r.resolve(language.Settings(websocket_key=b"foo")) assert res.status_code.string() == b"101"
def test_parse_pathod(self): a = language.parse_pathod("400:d0").next().actions[0] assert a.spec() == "d0" a = language.parse_pathod("400:dr").next().actions[0] assert a.spec() == "dr"
def test_request(self): r = language.parse_pathod('400:p10,10').next() assert r.actions[0].spec() == "p10,10"
def test_serve(self): s = BytesIO() r = next(parse_pathod("400:i0,'foo'")) assert serve(r, s, {})
def test_nonascii(self): with pytest.raises("ascii"): language.parse_pathod("foo:b\xf0")
def test_parse_pathod(self): a = next(parse_pathod("400:d0")).actions[0] assert a.spec() == "d0" a = next(parse_pathod("400:dr")).actions[0] assert a.spec() == "dr"
def handle_http_request(self, logger): """ Returns a (handler, log) tuple. handler: Handler for the next request, or None to disconnect log: A dictionary, or None """ with logger.ctx() as lg: try: req = self.protocol.read_request(self.rfile) except exceptions.HttpReadDisconnect: return None, None except exceptions.HttpException as s: s = str(s) lg(s) return None, dict(type="error", msg=s) if req.method == 'CONNECT': return self.protocol.handle_http_connect([req.host, req.port, req.http_version], lg) method = req.method path = req.path http_version = req.http_version headers = req.headers first_line_format = req.first_line_format clientcert = None if self.clientcert: clientcert = dict( cn=self.clientcert.cn, subject=self.clientcert.subject, serial=self.clientcert.serial, notbefore=self.clientcert.notbefore.isoformat(), notafter=self.clientcert.notafter.isoformat(), keyinfo=self.clientcert.keyinfo, ) retlog = dict( type="crafted", protocol="http", request=dict( path=path, method=method, headers=headers.fields, http_version=http_version, sni=self.sni, remote_address=self.address, clientcert=clientcert, first_line_format=first_line_format ), cipher=None, ) if self.ssl_established: retlog["cipher"] = self.get_current_cipher() m = utils.MemBool() valid_websocket_handshake = websockets.check_handshake(headers) self.settings.websocket_key = websockets.get_client_key(headers) # If this is a websocket initiation, we respond with a proper # server response, unless over-ridden. if valid_websocket_handshake: anchor_gen = language.parse_pathod("ws") else: anchor_gen = None for regex, spec in self.server.anchors: if regex.match(path): anchor_gen = language.parse_pathod(spec, self.use_http2) break else: if m(path.startswith(self.server.craftanchor)): spec = urllib.parse.unquote(path)[len(self.server.craftanchor):] if spec: try: anchor_gen = language.parse_pathod(spec, self.use_http2) except language.ParseException as v: lg("Parse error: %s" % v.msg) anchor_gen = iter([self.make_http_error_response( "Parse Error", "Error parsing response spec: %s\n" % ( v.msg + v.marked() ) )]) else: if self.use_http2: anchor_gen = iter([self.make_http_error_response( "Spec Error", "HTTP/2 only supports request/response with the craft anchor point: %s" % self.server.craftanchor )]) if not anchor_gen: anchor_gen = iter([self.make_http_error_response( "Not found", "No valid craft request found" )]) spec = next(anchor_gen) if self.use_http2 and isinstance(spec, language.http2.Response): spec.stream_id = req.stream_id lg("crafting spec: %s" % spec) nexthandler, retlog["response"] = self.http_serve_crafted( spec, lg ) if nexthandler and valid_websocket_handshake: self.protocol = protocols.websockets.WebsocketsProtocol(self) return self.protocol.handle_websocket, retlog else: return nexthandler, retlog