Massive refactoring to split up language implementation.

This commit is contained in:
Aldo Cortesi 2015-05-02 16:17:00 +12:00
parent 601cdf70c7
commit 9109b3cc8c
13 changed files with 737 additions and 696 deletions

View File

@ -0,0 +1,88 @@
import time
import contrib.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
def parse_response(s):
"""
May raise ParseException
"""
try:
s = s.decode("ascii")
except UnicodeError:
raise exceptions.ParseException("Spec must be valid ASCII.", 0, 0)
try:
return http.Response.expr().parseString(s, parseAll=True)[0]
except pp.ParseException, v:
raise exceptions.ParseException(v.msg, v.line, v.col)
def parse_requests(s):
"""
May raise ParseException
"""
try:
s = s.decode("ascii")
except UnicodeError:
raise exceptions.ParseException("Spec must be valid ASCII.", 0, 0)
try:
return pp.OneOrMore(
pp.Or(
[
websockets.WebsocketFrame.expr(),
http.Request.expr(),
]
)
).parseString(s, parseAll=True)
except pp.ParseException, v:
raise exceptions.ParseException(v.msg, v.line, v.col)
def serve(msg, fp, settings):
"""
fp: The file pointer to write to.
request_host: If this a request, this is the connecting host. If
None, we assume it's a response. Used to decide what standard
modifications to make if raw is not set.
Calling this function may modify the object.
"""
msg = msg.resolve(settings)
started = time.time()
vals = msg.values(settings)
vals.reverse()
actions = msg.actions[:]
actions.sort()
actions.reverse()
actions = [i.intermediate(settings) for i in actions]
disconnect = writer.write_values(fp, vals, actions[:])
duration = time.time() - started
ret = dict(
disconnect = disconnect,
started = started,
duration = duration,
)
ret.update(msg.log(settings))
return ret

View File

@ -1,34 +1,17 @@
import operator import operator
import string
import random import random
import mmap
import os import os
import time
import copy import copy
import abc import abc
import contrib.pyparsing as pp import contrib.pyparsing as pp
from netlib import http_status, tcp, http_uastrings, websockets from netlib import http_uastrings
import utils from .. import utils
from . import generators, exceptions
BLOCKSIZE = 1024
TRUNCATE = 1024 TRUNCATE = 1024
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
def quote(s): def quote(s):
quotechar = s[0] quotechar = s[0]
s = s[1:-1] s = s[1:-1]
@ -36,131 +19,6 @@ def quote(s):
return quotechar + s + quotechar return quotechar + s + quotechar
class RenderError(Exception):
pass
class FileAccessDenied(RenderError):
pass
class ParseException(Exception):
def __init__(self, msg, s, col):
Exception.__init__(self)
self.msg = msg
self.s = s
self.col = col
def marked(self):
return "%s\n%s"%(self.s, " " * (self.col - 1) + "^")
def __str__(self):
return "%s at char %s"%(self.msg, self.col)
def send_chunk(fp, val, blocksize, start, end):
"""
(start, end): Inclusive lower bound, exclusive upper bound.
"""
for i in range(start, end, blocksize):
fp.write(
val[i:min(i + blocksize, end)]
)
return end - start
def write_values(fp, vals, actions, sofar=0, blocksize=BLOCKSIZE):
"""
vals: A list of values, which may be strings or Value objects.
actions: A list of (offset, action, arg) tuples. Action may be "pause"
or "disconnect".
Both vals and actions are in reverse order, with the first items last.
Return True if connection should disconnect.
"""
sofar = 0
try:
while vals:
v = vals.pop()
offset = 0
while actions and actions[-1][0] < (sofar + len(v)):
a = actions.pop()
offset += send_chunk(
fp,
v,
blocksize,
offset,
a[0] - sofar - offset
)
if a[1] == "pause":
time.sleep(a[2])
elif a[1] == "disconnect":
return True
elif a[1] == "inject":
send_chunk(fp, a[2], blocksize, 0, len(a[2]))
send_chunk(fp, v, blocksize, offset, len(v))
sofar += len(v)
# Remainders
while actions:
a = actions.pop()
if a[1] == "pause":
time.sleep(a[2])
elif a[1] == "disconnect":
return True
elif a[1] == "inject":
send_chunk(fp, a[2], blocksize, 0, len(a[2]))
except tcp.NetLibDisconnect: # pragma: no cover
return True
def serve(msg, fp, settings):
"""
fp: The file pointer to write to.
request_host: If this a request, this is the connecting host. If
None, we assume it's a response. Used to decide what standard
modifications to make if raw is not set.
Calling this function may modify the object.
"""
msg = msg.resolve(settings)
started = time.time()
vals = msg.values(settings)
vals.reverse()
actions = msg.actions[:]
actions.sort()
actions.reverse()
actions = [i.intermediate(settings) for i in actions]
disconnect = write_values(fp, vals, actions[:])
duration = time.time() - started
ret = dict(
disconnect = disconnect,
started = started,
duration = duration,
)
ret.update(msg.log(settings))
return ret
DATATYPES = dict(
ascii_letters = string.ascii_letters,
ascii_lowercase = string.ascii_lowercase,
ascii_uppercase = string.ascii_uppercase,
digits = string.digits,
hexdigits = string.hexdigits,
octdigits = string.octdigits,
punctuation = string.punctuation,
whitespace = string.whitespace,
ascii = string.printable,
bytes = "".join(chr(i) for i in range(256))
)
v_integer = pp.Word(pp.nums)\ v_integer = pp.Word(pp.nums)\
.setName("integer")\ .setName("integer")\
.setParseAction(lambda toks: int(toks[0])) .setParseAction(lambda toks: int(toks[0]))
@ -191,89 +49,6 @@ v_naked_literal = pp.MatchFirst(
) )
class TransformGenerator:
"""
Perform a byte-by-byte transform another generator - that is, for each
input byte, the transformation must produce one output byte.
gen: A generator to wrap
transform: A function (offset, data) -> transformed
"""
def __init__(self, gen, transform):
self.gen = gen
self.transform = transform
def __len__(self):
return len(self.gen)
def __getitem__(self, x):
d = self.gen.__getitem__(x)
return self.transform(x, d)
def __getslice__(self, a, b):
d = self.gen.__getslice__(a, b)
return self.transform(a, d)
def __repr__(self):
return "'%s'"%self.gen
class LiteralGenerator:
def __init__(self, s):
self.s = s
def __len__(self):
return len(self.s)
def __getitem__(self, x):
return self.s.__getitem__(x)
def __getslice__(self, a, b):
return self.s.__getslice__(a, b)
def __repr__(self):
return "'%s'"%self.s
class RandomGenerator:
def __init__(self, dtype, length):
self.dtype = dtype
self.length = length
def __len__(self):
return self.length
def __getitem__(self, x):
return random.choice(DATATYPES[self.dtype])
def __getslice__(self, a, b):
b = min(b, self.length)
chars = DATATYPES[self.dtype]
return "".join(random.choice(chars) for x in range(a, b))
def __repr__(self):
return "%s random from %s"%(self.length, self.dtype)
class FileGenerator:
def __init__(self, path):
self.path = path
self.fp = file(path, "rb")
self.map = mmap.mmap(self.fp.fileno(), 0, access=mmap.ACCESS_READ)
def __len__(self):
return len(self.map)
def __getitem__(self, x):
return self.map.__getitem__(x)
def __getslice__(self, a, b):
return self.map.__getslice__(a, b)
def __repr__(self):
return "<%s"%self.path
class _Token(object): class _Token(object):
""" """
A specification token. Tokens are immutable. A specification token. Tokens are immutable.
@ -310,7 +85,7 @@ class _ValueLiteral(_Token):
self.val = val.decode("string_escape") self.val = val.decode("string_escape")
def get_generator(self, settings): def get_generator(self, settings):
return LiteralGenerator(self.val) return generators.LiteralGenerator(self.val)
def freeze(self, settings): def freeze(self, settings):
return self return self
@ -352,7 +127,7 @@ class ValueGenerate(_Token):
return self.usize * utils.SIZE_UNITS[self.unit] return self.usize * utils.SIZE_UNITS[self.unit]
def get_generator(self, settings): def get_generator(self, settings):
return RandomGenerator(self.datatype, self.bytes()) return generators.RandomGenerator(self.datatype, self.bytes())
def freeze(self, settings): def freeze(self, settings):
g = self.get_generator(settings) g = self.get_generator(settings)
@ -369,7 +144,10 @@ class ValueGenerate(_Token):
e = e + pp.Optional(u, default=None) e = e + pp.Optional(u, default=None)
s = pp.Literal(",").suppress() s = pp.Literal(",").suppress()
s += reduce(operator.or_, [pp.Literal(i) for i in DATATYPES.keys()]) s += reduce(
operator.or_,
[pp.Literal(i) for i in generators.DATATYPES.keys()]
)
e += pp.Optional(s, default="bytes") e += pp.Optional(s, default="bytes")
return e.setParseAction(lambda x: klass(*x)) return e.setParseAction(lambda x: klass(*x))
@ -397,19 +175,19 @@ class ValueFile(_Token):
def get_generator(self, settings): def get_generator(self, settings):
if not settings.staticdir: if not settings.staticdir:
raise FileAccessDenied("File access disabled.") raise exceptions.FileAccessDenied("File access disabled.")
s = os.path.expanduser(self.path) s = os.path.expanduser(self.path)
s = os.path.normpath( s = os.path.normpath(
os.path.abspath(os.path.join(settings.staticdir, s)) os.path.abspath(os.path.join(settings.staticdir, s))
) )
uf = settings.unconstrained_file_access uf = settings.unconstrained_file_access
if not uf and not s.startswith(settings.staticdir): if not uf and not s.startswith(settings.staticdir):
raise FileAccessDenied( raise exceptions.FileAccessDenied(
"File access outside of configured directory" "File access outside of configured directory"
) )
if not os.path.isfile(s): if not os.path.isfile(s):
raise FileAccessDenied("File not readable") raise exceptions.FileAccessDenied("File not readable")
return FileGenerator(s) return generators.FileGenerator(s)
def spec(self): def spec(self):
return "<'%s'"%self.path.encode("string_escape") return "<'%s'"%self.path.encode("string_escape")
@ -587,14 +365,15 @@ class PathodSpec(_Token):
def __init__(self, value): def __init__(self, value):
self.value = value self.value = value
try: try:
self.parsed = Response( import http
Response.expr().parseString( self.parsed = http.Response(
http.Response.expr().parseString(
value.val, value.val,
parseAll=True parseAll=True
) )
) )
except pp.ParseException, v: except pp.ParseException, v:
raise ParseException(v.msg, v.line, v.col) raise exceptions.ParseException(v.msg, v.line, v.col)
@classmethod @classmethod
def expr(klass): def expr(klass):
@ -719,7 +498,7 @@ class Code(_Component):
return e.setParseAction(lambda x: klass(*x)) return e.setParseAction(lambda x: klass(*x))
def values(self, settings): def values(self, settings):
return [LiteralGenerator(self.code)] return [generators.LiteralGenerator(self.code)]
def spec(self): def spec(self):
return "%s"%(self.code) return "%s"%(self.code)
@ -956,339 +735,17 @@ class _Message(object):
Sep = pp.Optional(pp.Literal(":")).suppress() 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)
vals.append("\r\n")
for h in self.headers:
vals.extend(h.values(settings))
vals.append("\r\n")
if self.body:
vals.append(self.body.value.get_generator(settings))
return vals
class Response(_HTTPMessage):
comps = (
Body,
Header,
PauseAt,
DisconnectAt,
InjectAt,
ShortcutContentType,
ShortcutLocation,
Raw,
Reason
)
logattrs = ["code", "reason", "version", "body"]
@property
def ws(self):
return self.tok(WS)
@property
def code(self):
return self.tok(Code)
@property
def reason(self):
return self.tok(Reason)
def preamble(self, settings):
l = [self.version, " "]
l.extend(self.code.values(settings))
code = int(self.code.code)
l.append(" ")
if self.reason:
l.extend(self.reason.values(settings))
else:
l.append(
LiteralGenerator(
http_status.RESPONSES.get(
code,
"Unknown code"
)
)
)
return l
def resolve(self, settings, msg=None):
tokens = self.tokens[:]
if self.ws:
if not settings.websocket_key:
raise RenderError(
"No websocket key - have we seen a client handshake?"
)
if not self.code:
tokens.insert(
1,
Code(101)
)
hdrs = websockets.server_handshake_headers(settings.websocket_key)
for i in hdrs.lst:
if not utils.get_header(i[0], self.headers):
tokens.append(
Header(ValueLiteral(i[0]), ValueLiteral(i[1]))
)
if not self.raw:
if not utils.get_header("Content-Length", self.headers):
if not self.body:
length = 0
else:
length = len(self.body.value.get_generator(settings))
tokens.append(
Header(
ValueLiteral("Content-Length"),
ValueLiteral(str(length)),
)
)
intermediate = self.__class__(tokens)
return self.__class__(
[i.resolve(settings, intermediate) for i in tokens]
)
@classmethod
def expr(klass):
parts = [i.expr() for i in klass.comps]
atom = pp.MatchFirst(parts)
resp = pp.And(
[
pp.MatchFirst(
[
WS.expr() + pp.Optional(Sep + Code.expr()),
Code.expr(),
]
),
pp.ZeroOrMore(Sep + atom)
]
)
resp = resp.setParseAction(klass)
return resp
def spec(self):
return ":".join([i.spec() for i in self.tokens])
class Request(_HTTPMessage):
comps = (
Body,
Header,
PauseAt,
DisconnectAt,
InjectAt,
ShortcutContentType,
ShortcutUserAgent,
Raw,
PathodSpec,
)
logattrs = ["method", "path", "body"]
@property
def ws(self):
return self.tok(WS)
@property
def method(self):
return self.tok(Method)
@property
def path(self):
return self.tok(Path)
@property
def pathodspec(self):
return self.tok(PathodSpec)
def preamble(self, settings):
v = self.method.values(settings)
v.append(" ")
v.extend(self.path.values(settings))
if self.pathodspec:
v.append(self.pathodspec.parsed.spec())
v.append(" ")
v.append(self.version)
return v
def resolve(self, settings, msg=None):
tokens = self.tokens[:]
if self.ws:
if not self.method:
tokens.insert(
1,
Method("get")
)
for i in websockets.client_handshake_headers().lst:
if not utils.get_header(i[0], self.headers):
tokens.append(
Header(ValueLiteral(i[0]), ValueLiteral(i[1]))
)
if not self.raw:
if not utils.get_header("Content-Length", self.headers):
if self.body:
length = len(self.body.value.get_generator(settings))
tokens.append(
Header(
ValueLiteral("Content-Length"),
ValueLiteral(str(length)),
)
)
if settings.request_host:
if not utils.get_header("Host", self.headers):
tokens.append(
Header(
ValueLiteral("Host"),
ValueLiteral(settings.request_host)
)
)
intermediate = self.__class__(tokens)
return self.__class__(
[i.resolve(settings, intermediate) for i in tokens]
)
@classmethod
def expr(klass):
parts = [i.expr() for i in klass.comps]
atom = pp.MatchFirst(parts)
resp = pp.And(
[
pp.MatchFirst(
[
WS.expr() + pp.Optional(Sep + Method.expr()),
Method.expr(),
]
),
Sep,
Path.expr(),
pp.ZeroOrMore(Sep + atom)
]
)
resp = resp.setParseAction(klass)
return resp
def spec(self):
return ":".join([i.spec() for i in self.tokens])
class WebsocketFrame(_Message):
comps = (
Body,
PauseAt,
DisconnectAt,
InjectAt
)
logattrs = ["body"]
@classmethod
def expr(klass):
parts = [i.expr() for i in klass.comps]
atom = pp.MatchFirst(parts)
resp = pp.And(
[
WF.expr(),
Sep,
pp.ZeroOrMore(Sep + atom)
]
)
resp = resp.setParseAction(klass)
return resp
def values(self, settings):
vals = []
if self.body:
bodygen = self.body.value.get_generator(settings)
length = len(self.body.value.get_generator(settings))
else:
bodygen = None
length = 0
frame = websockets.FrameHeader(
mask = True,
payload_length = length
)
vals = [frame.to_bytes()]
if self.body:
masker = websockets.Masker(frame.masking_key)
vals.append(
TransformGenerator(
bodygen,
masker.mask
)
)
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])
class PathodErrorResponse(Response):
pass
def make_error_response(reason, body=None):
tokens = [
Code("800"),
Header(ValueLiteral("Content-Type"), ValueLiteral("text/plain")),
Reason(ValueLiteral(reason)),
Body(ValueLiteral("pathod error: " + (body or reason))),
]
return PathodErrorResponse(tokens)
def read_file(settings, s): def read_file(settings, s):
uf = settings.get("unconstrained_file_access") uf = settings.get("unconstrained_file_access")
sd = settings.get("staticdir") sd = settings.get("staticdir")
if not sd: if not sd:
raise FileAccessDenied("File access disabled.") raise exceptions.FileAccessDenied("File access disabled.")
sd = os.path.normpath(os.path.abspath(sd)) sd = os.path.normpath(os.path.abspath(sd))
s = s[1:] s = s[1:]
s = os.path.expanduser(s) s = os.path.expanduser(s)
s = os.path.normpath(os.path.abspath(os.path.join(sd, s))) s = os.path.normpath(os.path.abspath(os.path.join(sd, s)))
if not uf and not s.startswith(sd): if not uf and not s.startswith(sd):
raise FileAccessDenied("File access outside of configured directory") raise exceptions.FileAccessDenied("File access outside of configured directory")
if not os.path.isfile(s): if not os.path.isfile(s):
raise FileAccessDenied("File not readable") raise exceptions.FileAccessDenied("File not readable")
return file(s, "rb").read() return file(s, "rb").read()
def parse_response(s):
"""
May raise ParseException
"""
try:
s = s.decode("ascii")
except UnicodeError:
raise ParseException("Spec must be valid ASCII.", 0, 0)
try:
return Response.expr().parseString(s, parseAll=True)[0]
except pp.ParseException, v:
raise ParseException(v.msg, v.line, v.col)
def parse_requests(s):
"""
May raise ParseException
"""
try:
s = s.decode("ascii")
except UnicodeError:
raise ParseException("Spec must be valid ASCII.", 0, 0)
try:
return pp.OneOrMore(
pp.Or(
[
WebsocketFrame.expr(),
Request.expr(),
]
)
).parseString(s, parseAll=True)
except pp.ParseException, v:
raise ParseException(v.msg, v.line, v.col)

View File

@ -0,0 +1,21 @@
class RenderError(Exception):
pass
class FileAccessDenied(RenderError):
pass
class ParseException(Exception):
def __init__(self, msg, s, col):
Exception.__init__(self)
self.msg = msg
self.s = s
self.col = col
def marked(self):
return "%s\n%s"%(self.s, " " * (self.col - 1) + "^")
def __str__(self):
return "%s at char %s"%(self.msg, self.col)

View File

@ -0,0 +1,99 @@
import string
import random
import mmap
DATATYPES = dict(
ascii_letters = string.ascii_letters,
ascii_lowercase = string.ascii_lowercase,
ascii_uppercase = string.ascii_uppercase,
digits = string.digits,
hexdigits = string.hexdigits,
octdigits = string.octdigits,
punctuation = string.punctuation,
whitespace = string.whitespace,
ascii = string.printable,
bytes = "".join(chr(i) for i in range(256))
)
class TransformGenerator:
"""
Perform a byte-by-byte transform another generator - that is, for each
input byte, the transformation must produce one output byte.
gen: A generator to wrap
transform: A function (offset, data) -> transformed
"""
def __init__(self, gen, transform):
self.gen = gen
self.transform = transform
def __len__(self):
return len(self.gen)
def __getitem__(self, x):
d = self.gen.__getitem__(x)
return self.transform(x, d)
def __getslice__(self, a, b):
d = self.gen.__getslice__(a, b)
return self.transform(a, d)
def __repr__(self):
return "'%s'"%self.gen
class LiteralGenerator:
def __init__(self, s):
self.s = s
def __len__(self):
return len(self.s)
def __getitem__(self, x):
return self.s.__getitem__(x)
def __getslice__(self, a, b):
return self.s.__getslice__(a, b)
def __repr__(self):
return "'%s'"%self.s
class RandomGenerator:
def __init__(self, dtype, length):
self.dtype = dtype
self.length = length
def __len__(self):
return self.length
def __getitem__(self, x):
return random.choice(DATATYPES[self.dtype])
def __getslice__(self, a, b):
b = min(b, self.length)
chars = DATATYPES[self.dtype]
return "".join(random.choice(chars) for x in range(a, b))
def __repr__(self):
return "%s random from %s"%(self.length, self.dtype)
class FileGenerator:
def __init__(self, path):
self.path = path
self.fp = file(path, "rb")
self.map = mmap.mmap(self.fp.fileno(), 0, access=mmap.ACCESS_READ)
def __len__(self):
return len(self.map)
def __getitem__(self, x):
return self.map.__getitem__(x)
def __getslice__(self, a, b):
return self.map.__getslice__(a, b)
def __repr__(self):
return "<%s"%self.path

267
libpathod/language/http.py Normal file
View File

@ -0,0 +1,267 @@
import abc
import contrib.pyparsing as pp
import netlib.websockets
from netlib import http_status
from . import base, generators, exceptions
def get_header(val, headers):
"""
Header keys may be Values, so we have to "generate" them as we try the
match.
"""
for h in headers:
k = h.key.get_generator({})
if len(k) == len(val) and k[:].lower() == val.lower():
return h
return None
class _HTTPMessage(base._Message):
version = "HTTP/1.1"
@abc.abstractmethod
def preamble(self, settings): # pragma: no cover
pass
def values(self, settings):
vals = self.preamble(settings)
vals.append("\r\n")
for h in self.headers:
vals.extend(h.values(settings))
vals.append("\r\n")
if self.body:
vals.append(self.body.value.get_generator(settings))
return vals
class Response(_HTTPMessage):
comps = (
base.Body,
base.Header,
base.PauseAt,
base.DisconnectAt,
base.InjectAt,
base.ShortcutContentType,
base.ShortcutLocation,
base.Raw,
base.Reason
)
logattrs = ["code", "reason", "version", "body"]
@property
def ws(self):
return self.tok(base.WS)
@property
def code(self):
return self.tok(base.Code)
@property
def reason(self):
return self.tok(base.Reason)
def preamble(self, settings):
l = [self.version, " "]
l.extend(self.code.values(settings))
code = int(self.code.code)
l.append(" ")
if self.reason:
l.extend(self.reason.values(settings))
else:
l.append(
generators.LiteralGenerator(
http_status.RESPONSES.get(
code,
"Unknown code"
)
)
)
return l
def resolve(self, settings, msg=None):
tokens = self.tokens[:]
if self.ws:
if not settings.websocket_key:
raise exceptions.RenderError(
"No websocket key - have we seen a client handshake?"
)
if not self.code:
tokens.insert(
1,
base.Code(101)
)
hdrs = netlib.websockets.server_handshake_headers(
settings.websocket_key
)
for i in hdrs.lst:
if not get_header(i[0], self.headers):
tokens.append(
base.Header(
base.ValueLiteral(i[0]),
base.ValueLiteral(i[1]))
)
if not self.raw:
if not get_header("Content-Length", self.headers):
if not self.body:
length = 0
else:
length = len(self.body.value.get_generator(settings))
tokens.append(
base.Header(
base.ValueLiteral("Content-Length"),
base.ValueLiteral(str(length)),
)
)
intermediate = self.__class__(tokens)
return self.__class__(
[i.resolve(settings, intermediate) for i in tokens]
)
@classmethod
def expr(klass):
parts = [i.expr() for i in klass.comps]
atom = pp.MatchFirst(parts)
resp = pp.And(
[
pp.MatchFirst(
[
base.WS.expr() + pp.Optional(
base.Sep + base.Code.expr()
),
base.Code.expr(),
]
),
pp.ZeroOrMore(base.Sep + atom)
]
)
resp = resp.setParseAction(klass)
return resp
def spec(self):
return ":".join([i.spec() for i in self.tokens])
class Request(_HTTPMessage):
comps = (
base.Body,
base.Header,
base.PauseAt,
base.DisconnectAt,
base.InjectAt,
base.ShortcutContentType,
base.ShortcutUserAgent,
base.Raw,
base.PathodSpec,
)
logattrs = ["method", "path", "body"]
@property
def ws(self):
return self.tok(base.WS)
@property
def method(self):
return self.tok(base.Method)
@property
def path(self):
return self.tok(base.Path)
@property
def pathodspec(self):
return self.tok(base.PathodSpec)
def preamble(self, settings):
v = self.method.values(settings)
v.append(" ")
v.extend(self.path.values(settings))
if self.pathodspec:
v.append(self.pathodspec.parsed.spec())
v.append(" ")
v.append(self.version)
return v
def resolve(self, settings, msg=None):
tokens = self.tokens[:]
if self.ws:
if not self.method:
tokens.insert(
1,
base.Method("get")
)
for i in netlib.websockets.client_handshake_headers().lst:
if not get_header(i[0], self.headers):
tokens.append(
base.Header(
base.ValueLiteral(i[0]),
base.ValueLiteral(i[1])
)
)
if not self.raw:
if not get_header("Content-Length", self.headers):
if self.body:
length = len(self.body.value.get_generator(settings))
tokens.append(
base.Header(
base.ValueLiteral("Content-Length"),
base.ValueLiteral(str(length)),
)
)
if settings.request_host:
if not get_header("Host", self.headers):
tokens.append(
base.Header(
base.ValueLiteral("Host"),
base.ValueLiteral(settings.request_host)
)
)
intermediate = self.__class__(tokens)
return self.__class__(
[i.resolve(settings, intermediate) for i in tokens]
)
@classmethod
def expr(klass):
parts = [i.expr() for i in klass.comps]
atom = pp.MatchFirst(parts)
resp = pp.And(
[
pp.MatchFirst(
[
base.WS.expr() + pp.Optional(
base.Sep + base.Method.expr()
),
base.Method.expr(),
]
),
base.Sep,
base.Path.expr(),
pp.ZeroOrMore(base.Sep + atom)
]
)
resp = resp.setParseAction(klass)
return resp
def spec(self):
return ":".join([i.spec() for i in self.tokens])
class PathodErrorResponse(Response):
pass
def make_error_response(reason, body=None):
tokens = [
base.Code("800"),
base.Header(
base.ValueLiteral("Content-Type"),
base.ValueLiteral("text/plain")
),
base.Reason(base.ValueLiteral(reason)),
base.Body(base.ValueLiteral("pathod error: " + (body or reason))),
]
return PathodErrorResponse(tokens)

View File

@ -0,0 +1,59 @@
import netlib.websockets
import contrib.pyparsing as pp
from . import base, generators
class WebsocketFrame(base._Message):
comps = (
base.Body,
base.PauseAt,
base.DisconnectAt,
base.InjectAt
)
logattrs = ["body"]
@classmethod
def expr(klass):
parts = [i.expr() for i in klass.comps]
atom = pp.MatchFirst(parts)
resp = pp.And(
[
base.WF.expr(),
base.Sep,
pp.ZeroOrMore(base.Sep + atom)
]
)
resp = resp.setParseAction(klass)
return resp
def values(self, settings):
vals = []
if self.body:
bodygen = self.body.value.get_generator(settings)
length = len(self.body.value.get_generator(settings))
else:
bodygen = None
length = 0
frame = netlib.websockets.FrameHeader(
mask = True,
payload_length = length
)
vals = [frame.to_bytes()]
if self.body:
masker = netlib.websockets.Masker(frame.masking_key)
vals.append(
generators.TransformGenerator(
bodygen,
masker.mask
)
)
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])

View File

@ -0,0 +1,61 @@
import time
import netlib.tcp
BLOCKSIZE = 1024
def send_chunk(fp, val, blocksize, start, end):
"""
(start, end): Inclusive lower bound, exclusive upper bound.
"""
for i in range(start, end, blocksize):
fp.write(
val[i:min(i + blocksize, end)]
)
return end - start
def write_values(fp, vals, actions, sofar=0, blocksize=BLOCKSIZE):
"""
vals: A list of values, which may be strings or Value objects.
actions: A list of (offset, action, arg) tuples. Action may be "pause"
or "disconnect".
Both vals and actions are in reverse order, with the first items last.
Return True if connection should disconnect.
"""
sofar = 0
try:
while vals:
v = vals.pop()
offset = 0
while actions and actions[-1][0] < (sofar + len(v)):
a = actions.pop()
offset += send_chunk(
fp,
v,
blocksize,
offset,
a[0] - sofar - offset
)
if a[1] == "pause":
time.sleep(a[2])
elif a[1] == "disconnect":
return True
elif a[1] == "inject":
send_chunk(fp, a[2], blocksize, 0, len(a[2]))
send_chunk(fp, v, blocksize, offset, len(v))
sofar += len(v)
# Remainders
while actions:
a = actions.pop()
if a[1] == "pause":
time.sleep(a[2])
elif a[1] == "disconnect":
return True
elif a[1] == "inject":
send_chunk(fp, a[2], blocksize, 0, len(a[2]))
except netlib.tcp.NetLibDisconnect: # pragma: no cover
return True

View File

@ -12,7 +12,8 @@ import OpenSSL.crypto
from netlib import tcp, http, certutils, websockets from netlib import tcp, http, certutils, websockets
import netlib.utils import netlib.utils
import language import language.http
import language.websockets
import utils import utils
@ -346,7 +347,7 @@ class Pathoc(tcp.TCPClient):
""" """
Performs a single request. Performs a single request.
r: A language.Request object, or a string representing one request. r: A language.http.Request object, or a string representing one request.
Returns Response if we have a non-ignored response. Returns Response if we have a non-ignored response.
@ -385,7 +386,7 @@ class Pathoc(tcp.TCPClient):
""" """
Performs a single request. Performs a single request.
r: A language.Request object, or a string representing one request. r: A language.http.Request object, or a string representing one request.
Returns Response if we have a non-ignored response. Returns Response if we have a non-ignored response.
@ -393,12 +394,12 @@ class Pathoc(tcp.TCPClient):
""" """
if isinstance(r, basestring): if isinstance(r, basestring):
r = language.parse_requests(r)[0] r = language.parse_requests(r)[0]
if isinstance(r, language.Request): if isinstance(r, language.http.Request):
if r.ws: if r.ws:
return self.websocket_start(r, self.websocket_get_frame) return self.websocket_start(r, self.websocket_get_frame)
else: else:
return self.http(r) return self.http(r)
elif isinstance(r, language.WebsocketFrame): elif isinstance(r, language.websockets.WebsocketFrame):
self.websocket_send_frame(r) self.websocket_send_frame(r)

View File

@ -8,6 +8,7 @@ from netlib import tcp, http, wsgi, certutils, websockets
import netlib.utils import netlib.utils
from . import version, app, language, utils from . import version, app, language, utils
import language.http
DEFAULT_CERT_DOMAIN = "pathod.net" DEFAULT_CERT_DOMAIN = "pathod.net"
@ -75,7 +76,7 @@ class PathodHandler(tcp.BaseHandler):
crafted, self.settings crafted, self.settings
) )
if error: if error:
err = language.make_error_response(error) err = language.http.make_error_response(error)
language.serve(err, self.wfile, self.settings) language.serve(err, self.wfile, self.settings)
log = dict( log = dict(
type="error", type="error",
@ -83,7 +84,7 @@ class PathodHandler(tcp.BaseHandler):
) )
return False, log return False, log
if self.server.explain and not isinstance(crafted, language.PathodErrorResponse): if self.server.explain and not isinstance(crafted, language.http.PathodErrorResponse):
crafted = crafted.freeze(self.settings) crafted = crafted.freeze(self.settings)
self.info(">> Spec: %s" % crafted.spec()) self.info(">> Spec: %s" % crafted.spec())
response_log = language.serve( response_log = language.serve(
@ -212,7 +213,7 @@ class PathodHandler(tcp.BaseHandler):
crafted = language.parse_response(spec) crafted = language.parse_response(spec)
except language.ParseException, v: except language.ParseException, v:
self.info("Parse error: %s" % v.msg) self.info("Parse error: %s" % v.msg)
crafted = language.make_error_response( crafted = language.http.make_error_response(
"Parse Error", "Parse Error",
"Error parsing response spec: %s\n" % v.msg + v.marked() "Error parsing response spec: %s\n" % v.msg + v.marked()
) )
@ -220,7 +221,7 @@ class PathodHandler(tcp.BaseHandler):
self.addlog(retlog) self.addlog(retlog)
return again return again
elif self.server.noweb: elif self.server.noweb:
crafted = language.make_error_response("Access Denied") crafted = language.http.make_error_response("Access Denied")
language.serve(crafted, self.wfile, self.settings) language.serve(crafted, self.wfile, self.settings)
self.addlog(dict( self.addlog(dict(
type="error", type="error",
@ -364,7 +365,8 @@ class Pathod(tcp.TCPServer):
return "File access denied.", None return "File access denied.", None
if self.sizelimit and l > self.sizelimit: if self.sizelimit and l > self.sizelimit:
return "Response too large.", None return "Response too large.", None
if self.nohang and any([isinstance(i, language.PauseAt) for i in req.actions]): pauses = [isinstance(i, language.base.PauseAt) for i in req.actions]
if self.nohang and any(pauses):
return "Pauses have been disabled.", None return "Pauses have been disabled.", None
return None, req return None, req

View File

@ -44,17 +44,6 @@ def parse_size(s):
raise ValueError("Invalid size specification.") raise ValueError("Invalid size specification.")
def get_header(val, headers):
"""
Header keys may be Values, so we have to "generate" them as we try the match.
"""
for h in headers:
k = h.key.get_generator({})
if len(k) == len(val) and k[:].lower() == val.lower():
return h
return None
def parse_anchor_spec(s): def parse_anchor_spec(s):
""" """
Return a tuple, or None on error. Return a tuple, or None on error.

View File

@ -1,63 +1,60 @@
import os import os
import cStringIO import cStringIO
from libpathod import language, utils from libpathod import language
from libpathod.language import generators, base, http, websockets, writer, exceptions
import tutils import tutils
language.TESTING = True language.TESTING = True
def test_quote():
assert language.quote("'\\\\'")
def parse_request(s): def parse_request(s):
return language.parse_requests(s)[0] return language.parse_requests(s)[0]
class TestWS: class TestWS:
def test_expr(self): def test_expr(self):
v = language.WS("foo") v = base.WS("foo")
assert v.expr() assert v.expr()
assert v.values(language.Settings()) assert v.values(language.Settings())
class TestValueNakedLiteral: class TestValueNakedLiteral:
def test_expr(self): def test_expr(self):
v = language.ValueNakedLiteral("foo") v = base.ValueNakedLiteral("foo")
assert v.expr() assert v.expr()
def test_spec(self): def test_spec(self):
v = language.ValueNakedLiteral("foo") v = base.ValueNakedLiteral("foo")
assert v.spec() == repr(v) == "foo" assert v.spec() == repr(v) == "foo"
v = language.ValueNakedLiteral("f\x00oo") v = base.ValueNakedLiteral("f\x00oo")
assert v.spec() == repr(v) == r"f\x00oo" assert v.spec() == repr(v) == r"f\x00oo"
class TestValueLiteral: class TestValueLiteral:
def test_espr(self): def test_espr(self):
v = language.ValueLiteral("foo") v = base.ValueLiteral("foo")
assert v.expr() assert v.expr()
assert v.val == "foo" assert v.val == "foo"
v = language.ValueLiteral("foo\n") v = base.ValueLiteral("foo\n")
assert v.expr() assert v.expr()
assert v.val == "foo\n" assert v.val == "foo\n"
assert repr(v) assert repr(v)
def test_spec(self): def test_spec(self):
v = language.ValueLiteral("foo") v = base.ValueLiteral("foo")
assert v.spec() == r"'foo'" assert v.spec() == r"'foo'"
v = language.ValueLiteral("f\x00oo") v = base.ValueLiteral("f\x00oo")
assert v.spec() == repr(v) == r"'f\x00oo'" assert v.spec() == repr(v) == r"'f\x00oo'"
v = language.ValueLiteral("\"") v = base.ValueLiteral("\"")
assert v.spec() == repr(v) == '\'"\'' assert v.spec() == repr(v) == '\'"\''
def roundtrip(self, spec): def roundtrip(self, spec):
e = language.ValueLiteral.expr() e = base.ValueLiteral.expr()
v = language.ValueLiteral(spec) v = base.ValueLiteral(spec)
v2 = e.parseString(v.spec()) v2 = e.parseString(v.spec())
assert v.val == v2[0].val assert v.val == v2[0].val
assert v.spec() == v2[0].spec() assert v.spec() == v2[0].spec()
@ -73,56 +70,56 @@ class TestValueLiteral:
class TestValueGenerate: class TestValueGenerate:
def test_basic(self): def test_basic(self):
v = language.Value.parseString("@10b")[0] v = base.Value.parseString("@10b")[0]
assert v.usize == 10 assert v.usize == 10
assert v.unit == "b" assert v.unit == "b"
assert v.bytes() == 10 assert v.bytes() == 10
v = language.Value.parseString("@10")[0] v = base.Value.parseString("@10")[0]
assert v.unit == "b" assert v.unit == "b"
v = language.Value.parseString("@10k")[0] v = base.Value.parseString("@10k")[0]
assert v.bytes() == 10240 assert v.bytes() == 10240
v = language.Value.parseString("@10g")[0] v = base.Value.parseString("@10g")[0]
assert v.bytes() == 1024**3 * 10 assert v.bytes() == 1024**3 * 10
v = language.Value.parseString("@10g,digits")[0] v = base.Value.parseString("@10g,digits")[0]
assert v.datatype == "digits" assert v.datatype == "digits"
g = v.get_generator({}) g = v.get_generator({})
assert g[:100] assert g[:100]
v = language.Value.parseString("@10,digits")[0] v = base.Value.parseString("@10,digits")[0]
assert v.unit == "b" assert v.unit == "b"
assert v.datatype == "digits" assert v.datatype == "digits"
def test_spec(self): def test_spec(self):
v = language.ValueGenerate(1, "b", "bytes") v = base.ValueGenerate(1, "b", "bytes")
assert v.spec() == repr(v) == "@1" assert v.spec() == repr(v) == "@1"
v = language.ValueGenerate(1, "k", "bytes") v = base.ValueGenerate(1, "k", "bytes")
assert v.spec() == repr(v) == "@1k" assert v.spec() == repr(v) == "@1k"
v = language.ValueGenerate(1, "k", "ascii") v = base.ValueGenerate(1, "k", "ascii")
assert v.spec() == repr(v) == "@1k,ascii" assert v.spec() == repr(v) == "@1k,ascii"
v = language.ValueGenerate(1, "b", "ascii") v = base.ValueGenerate(1, "b", "ascii")
assert v.spec() == repr(v) == "@1,ascii" assert v.spec() == repr(v) == "@1,ascii"
def test_freeze(self): def test_freeze(self):
v = language.ValueGenerate(100, "b", "ascii") v = base.ValueGenerate(100, "b", "ascii")
f = v.freeze(language.Settings()) f = v.freeze(language.Settings())
assert len(f.val) == 100 assert len(f.val) == 100
class TestValueFile: class TestValueFile:
def test_file_value(self): def test_file_value(self):
v = language.Value.parseString("<'one two'")[0] v = base.Value.parseString("<'one two'")[0]
assert str(v) assert str(v)
assert v.path == "one two" assert v.path == "one two"
v = language.Value.parseString("<path")[0] v = base.Value.parseString("<path")[0]
assert v.path == "path" assert v.path == "path"
def test_access_control(self): def test_access_control(self):
v = language.Value.parseString("<path")[0] v = base.Value.parseString("<path")[0]
with tutils.tmpdir() as t: with tutils.tmpdir() as t:
p = os.path.join(t, "path") p = os.path.join(t, "path")
with open(p, "wb") as f: with open(p, "wb") as f:
@ -130,9 +127,9 @@ class TestValueFile:
assert v.get_generator(language.Settings(staticdir=t)) assert v.get_generator(language.Settings(staticdir=t))
v = language.Value.parseString("<path2")[0] v = base.Value.parseString("<path2")[0]
tutils.raises( tutils.raises(
language.FileAccessDenied, exceptions.FileAccessDenied,
v.get_generator, v.get_generator,
language.Settings(staticdir=t) language.Settings(staticdir=t)
) )
@ -142,7 +139,7 @@ class TestValueFile:
language.Settings() language.Settings()
) )
v = language.Value.parseString("</outside")[0] v = base.Value.parseString("</outside")[0]
tutils.raises( tutils.raises(
"outside", "outside",
v.get_generator, v.get_generator,
@ -150,24 +147,24 @@ class TestValueFile:
) )
def test_spec(self): def test_spec(self):
v = language.Value.parseString("<'one two'")[0] v = base.Value.parseString("<'one two'")[0]
v2 = language.Value.parseString(v.spec())[0] v2 = base.Value.parseString(v.spec())[0]
assert v2.path == "one two" assert v2.path == "one two"
def test_freeze(self): def test_freeze(self):
v = language.Value.parseString("<'one two'")[0] v = base.Value.parseString("<'one two'")[0]
v2 = v.freeze({}) v2 = v.freeze({})
assert v2.path == v.path assert v2.path == v.path
class TestMisc: class TestMisc:
def test_generators(self): def test_generators(self):
v = language.Value.parseString("'val'")[0] v = base.Value.parseString("'val'")[0]
g = v.get_generator({}) g = v.get_generator({})
assert g[:] == "val" assert g[:] == "val"
def test_randomgenerator(self): def test_randomgenerator(self):
g = language.RandomGenerator("bytes", 100) g = generators.RandomGenerator("bytes", 100)
assert repr(g) assert repr(g)
assert len(g[:10]) == 10 assert len(g[:10]) == 10
assert len(g[1:10]) == 9 assert len(g[1:10]) == 9
@ -176,7 +173,7 @@ class TestMisc:
assert g[0] assert g[0]
def test_literalgenerator(self): def test_literalgenerator(self):
g = language.LiteralGenerator("one") g = generators.LiteralGenerator("one")
assert repr(g) assert repr(g)
assert g[:] == "one" assert g[:] == "one"
assert g[1] == "n" assert g[1] == "n"
@ -187,7 +184,7 @@ class TestMisc:
f = open(path, "wb") f = open(path, "wb")
f.write("x"*10000) f.write("x"*10000)
f.close() f.close()
g = language.FileGenerator(path) g = generators.FileGenerator(path)
assert len(g) == 10000 assert len(g) == 10000
assert g[0] == "x" assert g[0] == "x"
assert g[-1] == "x" assert g[-1] == "x"
@ -196,15 +193,15 @@ class TestMisc:
del g # remove all references to FileGenerator instance to close the file handle. del g # remove all references to FileGenerator instance to close the file handle.
def test_value(self): def test_value(self):
assert language.Value.parseString("'val'")[0].val == "val" assert base.Value.parseString("'val'")[0].val == "val"
assert language.Value.parseString('"val"')[0].val == "val" assert base.Value.parseString('"val"')[0].val == "val"
assert language.Value.parseString('"\'val\'"')[0].val == "'val'" assert base.Value.parseString('"\'val\'"')[0].val == "'val'"
def test_path(self): def test_path(self):
e = language.Path.expr() e = base.Path.expr()
assert e.parseString('"/foo"')[0].value.val == "/foo" assert e.parseString('"/foo"')[0].value.val == "/foo"
v = language.Path("/foo") v = base.Path("/foo")
assert v.value.val == "/foo" assert v.value.val == "/foo"
v = e.parseString("@100")[0] v = e.parseString("@100")[0]
@ -217,7 +214,7 @@ class TestMisc:
assert s == v.expr().parseString(s)[0].spec() assert s == v.expr().parseString(s)[0].spec()
def test_method(self): def test_method(self):
e = language.Method.expr() e = base.Method.expr()
assert e.parseString("get")[0].value.val == "GET" assert e.parseString("get")[0].value.val == "GET"
assert e.parseString("'foo'")[0].value.val == "foo" assert e.parseString("'foo'")[0].value.val == "foo"
assert e.parseString("'get'")[0].value.val == "get" assert e.parseString("'get'")[0].value.val == "get"
@ -237,13 +234,13 @@ class TestMisc:
assert v2.value.val == v3.value.val assert v2.value.val == v3.value.val
def test_raw(self): def test_raw(self):
e = language.Raw.expr().parseString("r")[0] e = base.Raw.expr().parseString("r")[0]
assert e assert e
assert e.spec() == "r" assert e.spec() == "r"
assert e.freeze({}).spec() == "r" assert e.freeze({}).spec() == "r"
def test_body(self): def test_body(self):
e = language.Body.expr() e = base.Body.expr()
v = e.parseString("b'foo'")[0] v = e.parseString("b'foo'")[0]
assert v.value.val == "foo" assert v.value.val == "foo"
@ -261,7 +258,7 @@ class TestMisc:
assert s == e.parseString(s)[0].spec() assert s == e.parseString(s)[0].spec()
def test_pathodspec(self): def test_pathodspec(self):
e = language.PathodSpec.expr() e = base.PathodSpec.expr()
v = e.parseString("s'200'")[0] v = e.parseString("s'200'")[0]
assert v.value.val == "200" assert v.value.val == "200"
tutils.raises( tutils.raises(
@ -276,8 +273,8 @@ class TestMisc:
assert "@1" not in f.spec() assert "@1" not in f.spec()
def test_pathodspec_freeze(self): def test_pathodspec_freeze(self):
e = language.PathodSpec( e = base.PathodSpec(
language.ValueLiteral( base.ValueLiteral(
"200:b'foo':i10,'\\''".encode( "200:b'foo':i10,'\\''".encode(
"string_escape" "string_escape"
) )
@ -287,7 +284,7 @@ class TestMisc:
assert e.values({}) assert e.values({})
def test_code(self): def test_code(self):
e = language.Code.expr() e = base.Code.expr()
v = e.parseString("200")[0] v = e.parseString("200")[0]
assert v.string() == "200" assert v.string() == "200"
assert v.spec() == "200" assert v.spec() == "200"
@ -295,7 +292,7 @@ class TestMisc:
assert v.freeze({}).code == v.code assert v.freeze({}).code == v.code
def test_reason(self): def test_reason(self):
e = language.Reason.expr() e = base.Reason.expr()
v = e.parseString("m'msg'")[0] v = e.parseString("m'msg'")[0]
assert v.value.val == "msg" assert v.value.val == "msg"
@ -309,13 +306,13 @@ class TestMisc:
def test_internal_response(self): def test_internal_response(self):
d = cStringIO.StringIO() d = cStringIO.StringIO()
s = language.make_error_response("foo") s = http.make_error_response("foo")
language.serve(s, d, {}) language.serve(s, d, {})
class TestHeaders: class TestHeaders:
def test_header(self): def test_header(self):
e = language.Header.expr() e = base.Header.expr()
v = e.parseString("h'foo'='bar'")[0] v = e.parseString("h'foo'='bar'")[0]
assert v.key.val == "foo" assert v.key.val == "foo"
assert v.value.val == "bar" assert v.value.val == "bar"
@ -328,7 +325,7 @@ class TestHeaders:
assert s == e.parseString(s)[0].spec() assert s == e.parseString(s)[0].spec()
def test_header_freeze(self): def test_header_freeze(self):
e = language.Header.expr() e = base.Header.expr()
v = e.parseString("h@10=@10'")[0] v = e.parseString("h@10=@10'")[0]
v2 = v.freeze({}) v2 = v.freeze({})
v3 = v2.freeze({}) v3 = v2.freeze({})
@ -336,7 +333,7 @@ class TestHeaders:
assert v2.value.val == v3.value.val assert v2.value.val == v3.value.val
def test_ctype_shortcut(self): def test_ctype_shortcut(self):
e = language.ShortcutContentType.expr() e = base.ShortcutContentType.expr()
v = e.parseString("c'foo'")[0] v = e.parseString("c'foo'")[0]
assert v.key.val == "Content-Type" assert v.key.val == "Content-Type"
assert v.value.val == "foo" assert v.value.val == "foo"
@ -344,14 +341,14 @@ class TestHeaders:
s = v.spec() s = v.spec()
assert s == e.parseString(s)[0].spec() assert s == e.parseString(s)[0].spec()
e = language.ShortcutContentType.expr() e = base.ShortcutContentType.expr()
v = e.parseString("c@100")[0] v = e.parseString("c@100")[0]
v2 = v.freeze({}) v2 = v.freeze({})
v3 = v2.freeze({}) v3 = v2.freeze({})
assert v2.value.val == v3.value.val assert v2.value.val == v3.value.val
def test_location_shortcut(self): def test_location_shortcut(self):
e = language.ShortcutLocation.expr() e = base.ShortcutLocation.expr()
v = e.parseString("l'foo'")[0] v = e.parseString("l'foo'")[0]
assert v.key.val == "Location" assert v.key.val == "Location"
assert v.value.val == "foo" assert v.value.val == "foo"
@ -359,7 +356,7 @@ class TestHeaders:
s = v.spec() s = v.spec()
assert s == e.parseString(s)[0].spec() assert s == e.parseString(s)[0].spec()
e = language.ShortcutLocation.expr() e = base.ShortcutLocation.expr()
v = e.parseString("l@100")[0] v = e.parseString("l@100")[0]
v2 = v.freeze({}) v2 = v.freeze({})
v3 = v2.freeze({}) v3 = v2.freeze({})
@ -375,7 +372,7 @@ class TestHeaders:
class TestShortcutUserAgent: class TestShortcutUserAgent:
def test_location_shortcut(self): def test_location_shortcut(self):
e = language.ShortcutUserAgent.expr() e = base.ShortcutUserAgent.expr()
v = e.parseString("ua")[0] v = e.parseString("ua")[0]
assert "Android" in str(v.value) assert "Android" in str(v.value)
assert v.spec() == "ua" assert v.spec() == "ua"
@ -394,9 +391,9 @@ class TestShortcutUserAgent:
class Test_Action: class Test_Action:
def test_cmp(self): def test_cmp(self):
a = language.DisconnectAt(0) a = base.DisconnectAt(0)
b = language.DisconnectAt(1) b = base.DisconnectAt(1)
c = language.DisconnectAt(0) c = base.DisconnectAt(0)
assert a < b assert a < b
assert a == c assert a == c
l = [b, a] l = [b, a]
@ -405,16 +402,16 @@ class Test_Action:
def test_resolve(self): def test_resolve(self):
r = parse_request('GET:"/foo"') r = parse_request('GET:"/foo"')
e = language.DisconnectAt("r") e = base.DisconnectAt("r")
ret = e.resolve({}, r) ret = e.resolve({}, r)
assert isinstance(ret.offset, int) assert isinstance(ret.offset, int)
def test_repr(self): def test_repr(self):
e = language.DisconnectAt("r") e = base.DisconnectAt("r")
assert repr(e) assert repr(e)
def test_freeze(self): def test_freeze(self):
l = language.DisconnectAt(5) l = base.DisconnectAt(5)
assert l.freeze({}).spec() == l.spec() assert l.freeze({}).spec() == l.spec()
@ -426,21 +423,21 @@ class TestDisconnects:
assert a.spec() == "dr" assert a.spec() == "dr"
def test_at(self): def test_at(self):
e = language.DisconnectAt.expr() e = base.DisconnectAt.expr()
v = e.parseString("d0")[0] v = e.parseString("d0")[0]
assert isinstance(v, language.DisconnectAt) assert isinstance(v, base.DisconnectAt)
assert v.offset == 0 assert v.offset == 0
v = e.parseString("d100")[0] v = e.parseString("d100")[0]
assert v.offset == 100 assert v.offset == 100
e = language.DisconnectAt.expr() e = base.DisconnectAt.expr()
v = e.parseString("dr")[0] v = e.parseString("dr")[0]
assert v.offset == "r" assert v.offset == "r"
def test_spec(self): def test_spec(self):
assert language.DisconnectAt("r").spec() == "dr" assert base.DisconnectAt("r").spec() == "dr"
assert language.DisconnectAt(10).spec() == "d10" assert base.DisconnectAt(10).spec() == "d10"
class TestInject: class TestInject:
@ -454,11 +451,11 @@ class TestInject:
assert a.offset == "a" assert a.offset == "a"
def test_at(self): def test_at(self):
e = language.InjectAt.expr() e = base.InjectAt.expr()
v = e.parseString("i0,'foo'")[0] v = e.parseString("i0,'foo'")[0]
assert v.value.val == "foo" assert v.value.val == "foo"
assert v.offset == 0 assert v.offset == 0
assert isinstance(v, language.InjectAt) assert isinstance(v, base.InjectAt)
v = e.parseString("ir,'foo'")[0] v = e.parseString("ir,'foo'")[0]
assert v.offset == "r" assert v.offset == "r"
@ -469,12 +466,12 @@ class TestInject:
assert language.serve(r, s, {}) assert language.serve(r, s, {})
def test_spec(self): def test_spec(self):
e = language.InjectAt.expr() e = base.InjectAt.expr()
v = e.parseString("i0,'foo'")[0] v = e.parseString("i0,'foo'")[0]
assert v.spec() == 'i0,"foo"' assert v.spec() == 'i0,"foo"'
def test_spec(self): def test_spec(self):
e = language.InjectAt.expr() e = base.InjectAt.expr()
v = e.parseString("i0,@100")[0] v = e.parseString("i0,@100")[0]
v2 = v.freeze({}) v2 = v.freeze({})
v3 = v2.freeze({}) v3 = v2.freeze({})
@ -483,7 +480,7 @@ class TestInject:
class TestPauses: class TestPauses:
def test_parse_response(self): def test_parse_response(self):
e = language.PauseAt.expr() e = base.PauseAt.expr()
v = e.parseString("p10,10")[0] v = e.parseString("p10,10")[0]
assert v.seconds == 10 assert v.seconds == 10
assert v.offset == 10 assert v.offset == 10
@ -502,12 +499,12 @@ class TestPauses:
assert r.actions[0].spec() == "p10,10" assert r.actions[0].spec() == "p10,10"
def test_spec(self): def test_spec(self):
assert language.PauseAt("r", 5).spec() == "pr,5" assert base.PauseAt("r", 5).spec() == "pr,5"
assert language.PauseAt(0, 5).spec() == "p0,5" assert base.PauseAt(0, 5).spec() == "p0,5"
assert language.PauseAt(0, "f").spec() == "p0,f" assert base.PauseAt(0, "f").spec() == "p0,f"
def test_freeze(self): def test_freeze(self):
l = language.PauseAt("r", 5) l = base.PauseAt("r", 5)
assert l.freeze({}).spec() == l.spec() assert l.freeze({}).spec() == l.spec()
@ -567,7 +564,7 @@ class TestRequest:
r = language.parse_requests(l) r = language.parse_requests(l)
assert len(r) == 1 assert len(r) == 1
assert len(r[0].tokens) == 3 assert len(r[0].tokens) == 3
assert isinstance(r[0].tokens[2], language.PathodSpec) assert isinstance(r[0].tokens[2], base.PathodSpec)
assert r[0].values({}) assert r[0].values({})
def test_render(self): def test_render(self):
@ -625,21 +622,21 @@ class TestRequest:
r = parse_request('ws:/path/') r = parse_request('ws:/path/')
res = r.resolve(language.Settings()) res = r.resolve(language.Settings())
assert res.method.string().lower() == "get" assert res.method.string().lower() == "get"
assert res.tok(language.Path).value.val == "/path/" assert res.tok(base.Path).value.val == "/path/"
assert res.tok(language.Method).value.val.lower() == "get" assert res.tok(base.Method).value.val.lower() == "get"
assert utils.get_header("Upgrade", res.headers).value.val == "websocket" assert http.get_header("Upgrade", res.headers).value.val == "websocket"
r = parse_request('ws:put:/path/') r = parse_request('ws:put:/path/')
res = r.resolve(language.Settings()) res = r.resolve(language.Settings())
assert r.method.string().lower() == "put" assert r.method.string().lower() == "put"
assert res.tok(language.Path).value.val == "/path/" assert res.tok(base.Path).value.val == "/path/"
assert res.tok(language.Method).value.val.lower() == "put" assert res.tok(base.Method).value.val.lower() == "put"
assert utils.get_header("Upgrade", res.headers).value.val == "websocket" assert http.get_header("Upgrade", res.headers).value.val == "websocket"
class TestWebsocketFrame: class TestWebsocketFrame:
def test_spec(self): def test_spec(self):
e = language.WebsocketFrame.expr() e = websockets.WebsocketFrame.expr()
wf = e.parseString("wf:b'foo'") wf = e.parseString("wf:b'foo'")
assert wf assert wf
@ -656,45 +653,45 @@ class TestWriteValues:
v = "foobarfoobar" v = "foobarfoobar"
for bs in range(1, len(v) + 2): for bs in range(1, len(v) + 2):
s = cStringIO.StringIO() s = cStringIO.StringIO()
language.send_chunk(s, v, bs, 0, len(v)) writer.send_chunk(s, v, bs, 0, len(v))
assert s.getvalue() == v assert s.getvalue() == v
for start in range(len(v)): for start in range(len(v)):
for end in range(len(v)): for end in range(len(v)):
s = cStringIO.StringIO() s = cStringIO.StringIO()
language.send_chunk(s, v, bs, start, end) writer.send_chunk(s, v, bs, start, end)
assert s.getvalue() == v[start:end] assert s.getvalue() == v[start:end]
def test_write_values_inject(self): def test_write_values_inject(self):
tst = "foo" tst = "foo"
s = cStringIO.StringIO() s = cStringIO.StringIO()
language.write_values(s, [tst], [(0, "inject", "aaa")], blocksize=5) writer.write_values(s, [tst], [(0, "inject", "aaa")], blocksize=5)
assert s.getvalue() == "aaafoo" assert s.getvalue() == "aaafoo"
s = cStringIO.StringIO() s = cStringIO.StringIO()
language.write_values(s, [tst], [(1, "inject", "aaa")], blocksize=5) writer.write_values(s, [tst], [(1, "inject", "aaa")], blocksize=5)
assert s.getvalue() == "faaaoo" assert s.getvalue() == "faaaoo"
s = cStringIO.StringIO() s = cStringIO.StringIO()
language.write_values(s, [tst], [(1, "inject", "aaa")], blocksize=5) writer.write_values(s, [tst], [(1, "inject", "aaa")], blocksize=5)
assert s.getvalue() == "faaaoo" assert s.getvalue() == "faaaoo"
def test_write_values_disconnects(self): def test_write_values_disconnects(self):
s = cStringIO.StringIO() s = cStringIO.StringIO()
tst = "foo" * 100 tst = "foo" * 100
language.write_values(s, [tst], [(0, "disconnect")], blocksize=5) writer.write_values(s, [tst], [(0, "disconnect")], blocksize=5)
assert not s.getvalue() assert not s.getvalue()
def test_write_values(self): def test_write_values(self):
tst = "foobarvoing" tst = "foobarvoing"
s = cStringIO.StringIO() s = cStringIO.StringIO()
language.write_values(s, [tst], []) writer.write_values(s, [tst], [])
assert s.getvalue() == tst assert s.getvalue() == tst
for bs in range(1, len(tst) + 2): for bs in range(1, len(tst) + 2):
for off in range(len(tst)): for off in range(len(tst)):
s = cStringIO.StringIO() s = cStringIO.StringIO()
language.write_values( writer.write_values(
s, [tst], [(off, "disconnect")], blocksize=bs s, [tst], [(off, "disconnect")], blocksize=bs
) )
assert s.getvalue() == tst[:off] assert s.getvalue() == tst[:off]
@ -703,20 +700,20 @@ class TestWriteValues:
tst = "".join(str(i) for i in range(10)) tst = "".join(str(i) for i in range(10))
for i in range(2, 10): for i in range(2, 10):
s = cStringIO.StringIO() s = cStringIO.StringIO()
language.write_values( writer.write_values(
s, [tst], [(2, "pause", 0), (1, "pause", 0)], blocksize=i s, [tst], [(2, "pause", 0), (1, "pause", 0)], blocksize=i
) )
assert s.getvalue() == tst assert s.getvalue() == tst
for i in range(2, 10): for i in range(2, 10):
s = cStringIO.StringIO() s = cStringIO.StringIO()
language.write_values(s, [tst], [(1, "pause", 0)], blocksize=i) writer.write_values(s, [tst], [(1, "pause", 0)], blocksize=i)
assert s.getvalue() == tst assert s.getvalue() == tst
tst = ["".join(str(i) for i in range(10))] * 5 tst = ["".join(str(i) for i in range(10))] * 5
for i in range(2, 10): for i in range(2, 10):
s = cStringIO.StringIO() s = cStringIO.StringIO()
language.write_values(s, tst[:], [(1, "pause", 0)], blocksize=i) writer.write_values(s, tst[:], [(1, "pause", 0)], blocksize=i)
assert s.getvalue() == "".join(tst) assert s.getvalue() == "".join(tst)
def test_write_values_after(self): def test_write_values_after(self):
@ -816,7 +813,7 @@ class TestResponse:
def test_parse_header(self): def test_parse_header(self):
r = language.parse_response('400:h"foo"="bar"') r = language.parse_response('400:h"foo"="bar"')
assert utils.get_header("foo", r.headers) assert http.get_header("foo", r.headers)
def test_parse_pause_before(self): def test_parse_pause_before(self):
r = language.parse_response("400:p0,10") r = language.parse_response("400:p0,10")
@ -854,28 +851,28 @@ class TestResponse:
def test_read_file(): def test_read_file():
tutils.raises(language.FileAccessDenied, language.read_file, {}, "=/foo") tutils.raises(exceptions.FileAccessDenied, base.read_file, {}, "=/foo")
p = tutils.test_data.path("data") p = tutils.test_data.path("data")
d = dict(staticdir=p) d = dict(staticdir=p)
assert language.read_file(d, "+./file").strip() == "testfile" assert base.read_file(d, "+./file").strip() == "testfile"
assert language.read_file(d, "+file").strip() == "testfile" assert base.read_file(d, "+file").strip() == "testfile"
tutils.raises( tutils.raises(
language.FileAccessDenied, exceptions.FileAccessDenied,
language.read_file, base.read_file,
d, d,
"+./nonexistent" "+./nonexistent"
) )
tutils.raises( tutils.raises(
language.FileAccessDenied, exceptions.FileAccessDenied,
language.read_file, base.read_file,
d, d,
"+/nonexistent" "+/nonexistent"
) )
tutils.raises( tutils.raises(
language.FileAccessDenied, exceptions.FileAccessDenied,
language.read_file, base.read_file,
d, d,
"+../test_language.py" "+../test_language.py"
) )
d["unconstrained_file_access"] = True d["unconstrained_file_access"] = True
assert language.read_file(d, "+../test_language.py") assert base.read_file(d, "+../test_language.py")