mitmproxy/pathod/language/http2.py

307 lines
7.0 KiB
Python
Raw Normal View History

2015-06-08 08:45:17 +00:00
import pyparsing as pp
2017-05-26 14:14:20 +00:00
from mitmproxy.net import http
from mitmproxy.net.http import user_agents, Headers
2015-06-18 16:05:09 +00:00
from . import base, message
2015-06-08 08:45:17 +00:00
"""
Normal HTTP requests:
<method>:<path>:<header>:<body>
e.g.:
GET:/
2015-06-18 09:07:33 +00:00
GET:/:h"foo"="bar"
POST:/:h"foo"="bar":b'content body payload'
Normal HTTP responses:
<code>:<header>:<body>
e.g.:
200
302:h"foo"="bar"
404:h"foo"="bar":b'content body payload'
2015-06-08 08:45:17 +00:00
Individual HTTP/2 frames:
h2f:<payload_length>:<type>:<flags>:<stream_id>:<payload>
e.g.:
h2f:0:PING
h2f:42:HEADERS:END_HEADERS:0x1234567:foo=bar,host=example.com
h2f:42:DATA:END_STREAM,PADDED:0x1234567:'content body payload'
"""
2016-05-28 20:25:54 +00:00
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
2016-10-17 04:29:45 +00:00
class _HeaderMixin:
2017-05-26 14:14:20 +00:00
@property
def unique_name(self):
return None
def values(self, settings):
return (
self.key.get_generator(settings),
self.value.get_generator(settings),
)
2016-05-28 20:25:54 +00:00
class _HTTP2Message(message.Message):
@property
def actions(self):
return [] # self.toks(actions._Action)
@property
def headers(self):
headers = self.toks(_HeaderMixin)
if not self.raw:
if not get_header(b"content-length", headers):
if not self.body:
length = 0
else:
length = len(self.body.string())
headers.append(
Header(
base.TokValueLiteral("content-length"),
base.TokValueLiteral(str(length)),
)
)
return headers
@property
def raw(self):
return bool(self.tok(Raw))
@property
def body(self):
return self.tok(Body)
def resolve(self, settings):
return self
class StatusCode(base.Integer):
pass
2015-06-08 08:45:17 +00:00
class Method(base.OptionsOrValue):
options = [
"GET",
"HEAD",
"POST",
"PUT",
"DELETE",
]
class Path(base.Value):
pass
class Header(_HeaderMixin, base.KeyValue):
2015-06-08 08:45:17 +00:00
preamble = "h"
class ShortcutContentType(_HeaderMixin, base.Value):
preamble = "c"
key = base.TokValueLiteral("content-type")
class ShortcutLocation(_HeaderMixin, base.Value):
preamble = "l"
key = base.TokValueLiteral("location")
class ShortcutUserAgent(_HeaderMixin, base.OptionsOrValue):
preamble = "u"
2015-07-15 20:04:25 +00:00
options = [i[1] for i in user_agents.UASTRINGS]
key = base.TokValueLiteral("user-agent")
def values(self, settings):
value = self.value.val
if self.option_used:
value = user_agents.get_by_shortcut(value.lower().decode())[2].encode()
return (
self.key.get_generator(settings),
value
)
2015-06-08 08:45:17 +00:00
class Raw(base.CaselessLiteral):
TOK = "r"
2015-06-08 08:45:17 +00:00
class Body(base.Value):
preamble = "b"
class Times(base.Integer):
preamble = "x"
2015-06-22 14:11:55 +00:00
class Response(_HTTP2Message):
2017-05-26 14:14:20 +00:00
unique_name = None
2015-06-08 08:45:17 +00:00
comps = (
Header,
2015-06-22 14:11:55 +00:00
Body,
ShortcutContentType,
2015-06-22 14:11:55 +00:00
ShortcutLocation,
Raw,
2015-06-08 08:45:17 +00:00
)
2015-06-11 14:13:22 +00:00
def __init__(self, tokens):
2015-06-22 14:11:55 +00:00
super(Response, self).__init__(tokens)
2015-06-11 14:13:22 +00:00
self.rendered_values = None
2015-07-29 09:26:10 +00:00
self.stream_id = 2
2015-06-08 08:45:17 +00:00
@property
def status_code(self):
return self.tok(StatusCode)
2015-06-08 08:45:17 +00:00
@classmethod
2015-06-18 09:07:33 +00:00
def expr(cls):
parts = [i.expr() for i in cls.comps]
2015-06-08 08:45:17 +00:00
atom = pp.MatchFirst(parts)
resp = pp.And(
[
StatusCode.expr(),
2015-06-08 08:45:17 +00:00
pp.ZeroOrMore(base.Sep + atom)
]
)
2015-06-18 09:07:33 +00:00
resp = resp.setParseAction(cls)
2015-06-08 08:45:17 +00:00
return resp
2015-06-11 14:13:22 +00:00
def values(self, settings):
if self.rendered_values:
return self.rendered_values
else:
2015-09-05 16:16:08 +00:00
headers = Headers([header.values(settings) for header in self.headers])
2015-06-11 14:13:22 +00:00
body = self.body
if body:
body = body.string()
2015-09-16 16:44:34 +00:00
resp = http.Response(
http_version=b'HTTP/2.0',
status_code=int(self.status_code.string()),
reason=b'',
headers=headers,
content=body,
trailers=None,
timestamp_start=0,
timestamp_end=0
2015-07-29 09:26:10 +00:00
)
resp.stream_id = self.stream_id
self.rendered_values = settings.protocol.assemble(resp)
2015-06-11 14:13:22 +00:00
return self.rendered_values
def spec(self):
return ":".join([i.spec() for i in self.tokens])
2017-05-26 14:22:13 +00:00
class NestedResponse(message.NestedMessage):
2015-06-22 14:11:55 +00:00
preamble = "s"
nest_type = Response
class Request(_HTTP2Message):
2015-06-11 14:13:22 +00:00
comps = (
Header,
ShortcutContentType,
2015-06-22 14:11:55 +00:00
ShortcutUserAgent,
Raw,
2015-06-22 14:11:55 +00:00
NestedResponse,
Body,
Times,
2015-06-11 14:13:22 +00:00
)
2015-06-22 14:11:55 +00:00
logattrs = ["method", "path"]
2015-06-11 14:13:22 +00:00
def __init__(self, tokens):
2015-06-22 14:11:55 +00:00
super(Request, self).__init__(tokens)
2015-06-11 14:13:22 +00:00
self.rendered_values = None
2015-07-29 09:26:10 +00:00
self.stream_id = 1
2015-06-11 14:13:22 +00:00
@property
2015-06-22 14:11:55 +00:00
def method(self):
return self.tok(Method)
@property
def path(self):
return self.tok(Path)
@property
def nested_response(self):
return self.tok(NestedResponse)
@property
def times(self):
return self.tok(Times)
2015-06-11 14:13:22 +00:00
@classmethod
2015-06-18 09:07:33 +00:00
def expr(cls):
parts = [i.expr() for i in cls.comps]
2015-06-11 14:13:22 +00:00
atom = pp.MatchFirst(parts)
resp = pp.And(
[
2015-06-22 14:11:55 +00:00
Method.expr(),
base.Sep,
Path.expr(),
2015-06-11 14:13:22 +00:00
pp.ZeroOrMore(base.Sep + atom)
]
2015-06-08 08:45:17 +00:00
)
2015-06-18 09:07:33 +00:00
resp = resp.setParseAction(cls)
2015-06-11 14:13:22 +00:00
return resp
2015-06-08 08:45:17 +00:00
def values(self, settings):
2015-06-11 14:13:22 +00:00
if self.rendered_values:
return self.rendered_values
else:
2015-06-22 14:11:55 +00:00
path = self.path.string()
if self.nested_response:
path += self.nested_response.parsed.spec().encode()
2015-06-22 14:11:55 +00:00
2015-09-05 16:16:08 +00:00
headers = Headers([header.values(settings) for header in self.headers])
2015-06-11 14:13:22 +00:00
body = self.body
if body:
2015-06-16 09:33:10 +00:00
body = body.string()
2015-06-11 14:13:22 +00:00
2015-09-16 16:44:34 +00:00
req = http.Request(
"",
0,
2015-06-22 14:11:55 +00:00
self.method.string(),
2016-07-02 00:49:18 +00:00
b'http',
b'',
2015-06-22 14:11:55 +00:00
path,
b"HTTP/2.0",
headers,
2015-07-29 09:26:10 +00:00
body,
None,
0,
0,
2015-07-29 09:26:10 +00:00
)
req.stream_id = self.stream_id
self.rendered_values = settings.protocol.assemble(req)
2015-06-11 14:13:22 +00:00
return self.rendered_values
2015-06-08 08:45:17 +00:00
def spec(self):
return ":".join([i.spec() for i in self.tokens])
2016-05-28 20:25:54 +00:00
2015-06-11 14:13:22 +00:00
def make_error_response(reason, body=None):
2015-06-16 09:33:10 +00:00
tokens = [
StatusCode("800"),
2015-06-16 09:33:10 +00:00
Body(base.TokValueLiteral("pathod error: " + (body or reason))),
]
return Response(tokens)