mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-27 02:24:18 +00:00
Massive refactoring to split up language implementation.
This commit is contained in:
parent
601cdf70c7
commit
9109b3cc8c
88
libpathod/language/__init__.py
Normal file
88
libpathod/language/__init__.py
Normal 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
|
@ -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)
|
|
21
libpathod/language/exceptions.py
Normal file
21
libpathod/language/exceptions.py
Normal 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)
|
99
libpathod/language/generators.py
Normal file
99
libpathod/language/generators.py
Normal 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
267
libpathod/language/http.py
Normal 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)
|
59
libpathod/language/websockets.py
Normal file
59
libpathod/language/websockets.py
Normal 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])
|
61
libpathod/language/writer.py
Normal file
61
libpathod/language/writer.py
Normal 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
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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")
|
||||||
|
Loading…
Reference in New Issue
Block a user