aboutsummaryrefslogtreecommitdiffstats
path: root/libpathod/language/http.py
diff options
context:
space:
mode:
Diffstat (limited to 'libpathod/language/http.py')
-rw-r--r--libpathod/language/http.py267
1 files changed, 267 insertions, 0 deletions
diff --git a/libpathod/language/http.py b/libpathod/language/http.py
new file mode 100644
index 00000000..df5d8ba8
--- /dev/null
+++ b/libpathod/language/http.py
@@ -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)