mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-24 00:31:33 +00:00
0ffd14372a
This patch makes progress on language/http.py
297 lines
7.7 KiB
Python
297 lines
7.7 KiB
Python
|
|
import abc
|
|
|
|
import contrib.pyparsing as pp
|
|
|
|
import netlib.websockets
|
|
from netlib import http_status
|
|
from . import base, generators, exceptions
|
|
|
|
|
|
class WS(base.CaselessLiteral):
|
|
TOK = "ws"
|
|
|
|
|
|
class Raw(base.CaselessLiteral):
|
|
TOK = "r"
|
|
|
|
|
|
class Path(base.SimpleValue):
|
|
pass
|
|
|
|
|
|
class Method(base.OptionsOrValue):
|
|
options = [
|
|
"get",
|
|
"head",
|
|
"post",
|
|
"put",
|
|
"delete",
|
|
"options",
|
|
"trace",
|
|
"connect",
|
|
]
|
|
|
|
|
|
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"
|
|
@property
|
|
def raw(self):
|
|
return bool(self.tok(Raw))
|
|
|
|
|
|
@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,
|
|
Raw,
|
|
base.Reason
|
|
)
|
|
logattrs = ["code", "reason", "version", "body"]
|
|
|
|
@property
|
|
def ws(self):
|
|
return self.tok(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(
|
|
[
|
|
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,
|
|
Raw,
|
|
base.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(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,
|
|
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(
|
|
[
|
|
WS.expr() + pp.Optional(
|
|
base.Sep + Method.expr()
|
|
),
|
|
Method.expr(),
|
|
]
|
|
),
|
|
base.Sep,
|
|
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)
|