mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-27 02:24:18 +00:00
commit
05efcf0a78
@ -65,6 +65,17 @@ def args_pathoc(argv, stdout=sys.stdout, stderr=sys.stderr):
|
|||||||
"-t", dest="timeout", type=int, default=None,
|
"-t", dest="timeout", type=int, default=None,
|
||||||
help="Connection timeout"
|
help="Connection timeout"
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--http2", dest="use_http2", action="store_true", default=False,
|
||||||
|
help='Perform all requests over a single HTTP/2 connection.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--http2-skip-connection-preface",
|
||||||
|
dest="http2_skip_connection_preface",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help='Skips the HTTP/2 connection preface before sending requests.')
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'host', type=str,
|
'host', type=str,
|
||||||
metavar = "host[:port]",
|
metavar = "host[:port]",
|
||||||
@ -77,6 +88,7 @@ def args_pathoc(argv, stdout=sys.stdout, stderr=sys.stderr):
|
|||||||
specifcations
|
specifcations
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
group = parser.add_argument_group(
|
group = parser.add_argument_group(
|
||||||
'SSL',
|
'SSL',
|
||||||
)
|
)
|
||||||
@ -189,7 +201,7 @@ def args_pathoc(argv, stdout=sys.stdout, stderr=sys.stderr):
|
|||||||
data = open(r).read()
|
data = open(r).read()
|
||||||
r = data
|
r = data
|
||||||
try:
|
try:
|
||||||
reqs.append(language.parse_pathoc(r))
|
reqs.append(language.parse_pathoc(r, args.use_http2))
|
||||||
except language.ParseException as v:
|
except language.ParseException as v:
|
||||||
print >> stderr, "Error parsing request spec: %s" % v.msg
|
print >> stderr, "Error parsing request spec: %s" % v.msg
|
||||||
print >> stderr, v.marked()
|
print >> stderr, v.marked()
|
||||||
|
@ -3,7 +3,7 @@ import time
|
|||||||
|
|
||||||
import pyparsing as pp
|
import pyparsing as pp
|
||||||
|
|
||||||
from . import http, websockets, writer, exceptions
|
from . import http, http2, websockets, writer, exceptions
|
||||||
|
|
||||||
from exceptions import *
|
from exceptions import *
|
||||||
from base import Settings
|
from base import Settings
|
||||||
@ -39,20 +39,24 @@ def parse_pathod(s):
|
|||||||
return itertools.chain(*[expand(i) for i in reqs])
|
return itertools.chain(*[expand(i) for i in reqs])
|
||||||
|
|
||||||
|
|
||||||
def parse_pathoc(s):
|
def parse_pathoc(s, use_http2=False):
|
||||||
try:
|
try:
|
||||||
s = s.decode("ascii")
|
s = s.decode("ascii")
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
raise exceptions.ParseException("Spec must be valid ASCII.", 0, 0)
|
raise exceptions.ParseException("Spec must be valid ASCII.", 0, 0)
|
||||||
try:
|
try:
|
||||||
reqs = pp.OneOrMore(
|
if use_http2:
|
||||||
pp.Or(
|
expressions = [
|
||||||
[
|
# http2.Frame.expr(),
|
||||||
websockets.WebsocketClientFrame.expr(),
|
http2.Request.expr(),
|
||||||
http.Request.expr(),
|
]
|
||||||
]
|
else:
|
||||||
)
|
expressions = [
|
||||||
).parseString(s, parseAll=True)
|
websockets.WebsocketClientFrame.expr(),
|
||||||
|
http.Request.expr(),
|
||||||
|
]
|
||||||
|
|
||||||
|
reqs = pp.OneOrMore(pp.Or(expressions)).parseString(s, parseAll=True)
|
||||||
except pp.ParseException as v:
|
except pp.ParseException as v:
|
||||||
raise exceptions.ParseException(v.msg, v.line, v.col)
|
raise exceptions.ParseException(v.msg, v.line, v.col)
|
||||||
return itertools.chain(*[expand(i) for i in reqs])
|
return itertools.chain(*[expand(i) for i in reqs])
|
||||||
|
@ -15,13 +15,15 @@ class Settings:
|
|||||||
staticdir = None,
|
staticdir = None,
|
||||||
unconstrained_file_access = False,
|
unconstrained_file_access = False,
|
||||||
request_host = None,
|
request_host = None,
|
||||||
websocket_key = None
|
websocket_key = None,
|
||||||
|
protocol = None,
|
||||||
):
|
):
|
||||||
|
self.is_client = is_client
|
||||||
self.staticdir = staticdir
|
self.staticdir = staticdir
|
||||||
self.unconstrained_file_access = unconstrained_file_access
|
self.unconstrained_file_access = unconstrained_file_access
|
||||||
self.request_host = request_host
|
self.request_host = request_host
|
||||||
self.websocket_key = websocket_key
|
self.websocket_key = websocket_key # TODO: refactor this into the protocol
|
||||||
self.is_client = is_client
|
self.protocol = protocol
|
||||||
|
|
||||||
|
|
||||||
Sep = pp.Optional(pp.Literal(":")).suppress()
|
Sep = pp.Optional(pp.Literal(":")).suppress()
|
||||||
|
119
libpathod/language/http2.py
Normal file
119
libpathod/language/http2.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import os
|
||||||
|
import netlib.http2
|
||||||
|
import pyparsing as pp
|
||||||
|
from . import base, generators, actions, message
|
||||||
|
|
||||||
|
"""
|
||||||
|
Normal HTTP requests:
|
||||||
|
<method>:<path>:<header>:<body>
|
||||||
|
e.g.:
|
||||||
|
GET:/
|
||||||
|
GET:/:foo=bar
|
||||||
|
POST:/:foo=bar:'content body payload'
|
||||||
|
|
||||||
|
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'
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Method(base.OptionsOrValue):
|
||||||
|
options = [
|
||||||
|
"GET",
|
||||||
|
"HEAD",
|
||||||
|
"POST",
|
||||||
|
"PUT",
|
||||||
|
"DELETE",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Path(base.Value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Header(base.KeyValue):
|
||||||
|
preamble = "h"
|
||||||
|
|
||||||
|
|
||||||
|
class Body(base.Value):
|
||||||
|
preamble = "b"
|
||||||
|
|
||||||
|
|
||||||
|
class Times(base.Integer):
|
||||||
|
preamble = "x"
|
||||||
|
|
||||||
|
|
||||||
|
class Request(message.Message):
|
||||||
|
comps = (
|
||||||
|
Header,
|
||||||
|
Body,
|
||||||
|
|
||||||
|
Times,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def method(self):
|
||||||
|
return self.tok(Method)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return self.tok(Path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def headers(self):
|
||||||
|
return self.toks(Header)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def body(self):
|
||||||
|
return self.tok(Body)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def times(self):
|
||||||
|
return self.tok(Times)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def actions(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def expr(klass):
|
||||||
|
parts = [i.expr() for i in klass.comps]
|
||||||
|
atom = pp.MatchFirst(parts)
|
||||||
|
resp = pp.And(
|
||||||
|
[
|
||||||
|
Method.expr(),
|
||||||
|
base.Sep,
|
||||||
|
Path.expr(),
|
||||||
|
base.Sep,
|
||||||
|
pp.ZeroOrMore(base.Sep + atom)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
resp = resp.setParseAction(klass)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def resolve(self, settings, msg=None):
|
||||||
|
tokens = self.tokens[:]
|
||||||
|
return self.__class__(
|
||||||
|
[i.resolve(settings, self) for i in tokens]
|
||||||
|
)
|
||||||
|
|
||||||
|
def values(self, settings):
|
||||||
|
return settings.protocol.create_request(
|
||||||
|
self.method.value.get_generator(settings),
|
||||||
|
self.path,
|
||||||
|
self.headers,
|
||||||
|
self.body)
|
||||||
|
|
||||||
|
def spec(self):
|
||||||
|
return ":".join([i.spec() for i in self.tokens])
|
||||||
|
|
||||||
|
|
||||||
|
# class H2F(base.CaselessLiteral):
|
||||||
|
# TOK = "h2f"
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class WebsocketFrame(message.Message):
|
||||||
|
# pass
|
@ -11,23 +11,32 @@ import threading
|
|||||||
|
|
||||||
import OpenSSL.crypto
|
import OpenSSL.crypto
|
||||||
|
|
||||||
from netlib import tcp, http, certutils, websockets
|
from netlib import tcp, http, http2, certutils, websockets
|
||||||
|
|
||||||
import language.http
|
import language.http
|
||||||
import language.websockets
|
import language.websockets
|
||||||
from . import utils, log
|
from . import utils, log
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logging.getLogger("hpack").setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
|
||||||
class PathocError(Exception):
|
class PathocError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SSLInfo:
|
class SSLInfo:
|
||||||
def __init__(self, certchain, cipher):
|
def __init__(self, certchain, cipher, alp):
|
||||||
self.certchain, self.cipher = certchain, cipher
|
self.certchain, self.cipher, self.alp = certchain, cipher, alp
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
if self.alp:
|
||||||
|
alp = self.alp
|
||||||
|
else:
|
||||||
|
alp = '<no protocol negotiated>'
|
||||||
|
|
||||||
parts = [
|
parts = [
|
||||||
|
"Application Layer Protocol: %s" % alp,
|
||||||
"Cipher: %s, %s bit, %s" % self.cipher,
|
"Cipher: %s, %s bit, %s" % self.cipher,
|
||||||
"SSL certificate chain:"
|
"SSL certificate chain:"
|
||||||
]
|
]
|
||||||
@ -150,6 +159,10 @@ class Pathoc(tcp.TCPClient):
|
|||||||
clientcert=None,
|
clientcert=None,
|
||||||
ciphers=None,
|
ciphers=None,
|
||||||
|
|
||||||
|
# HTTP/2
|
||||||
|
use_http2=False,
|
||||||
|
http2_skip_connection_preface=False,
|
||||||
|
|
||||||
# Websockets
|
# Websockets
|
||||||
ws_read_limit = None,
|
ws_read_limit = None,
|
||||||
|
|
||||||
@ -177,18 +190,16 @@ class Pathoc(tcp.TCPClient):
|
|||||||
ignorecodes: Sequence of return codes to ignore
|
ignorecodes: Sequence of return codes to ignore
|
||||||
"""
|
"""
|
||||||
tcp.TCPClient.__init__(self, address)
|
tcp.TCPClient.__init__(self, address)
|
||||||
self.settings = language.Settings(
|
|
||||||
staticdir = os.getcwd(),
|
|
||||||
unconstrained_file_access = True,
|
|
||||||
request_host = self.address.host,
|
|
||||||
is_client = True
|
|
||||||
)
|
|
||||||
self.ssl, self.sni = ssl, sni
|
self.ssl, self.sni = ssl, sni
|
||||||
self.clientcert = clientcert
|
self.clientcert = clientcert
|
||||||
self.sslversion = utils.SSLVERSIONS[sslversion]
|
self.sslversion = utils.SSLVERSIONS[sslversion]
|
||||||
self.ciphers = ciphers
|
self.ciphers = ciphers
|
||||||
self.sslinfo = None
|
self.sslinfo = None
|
||||||
|
|
||||||
|
self.use_http2 = use_http2
|
||||||
|
self.http2_skip_connection_preface = http2_skip_connection_preface
|
||||||
|
|
||||||
self.ws_read_limit = ws_read_limit
|
self.ws_read_limit = ws_read_limit
|
||||||
|
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
@ -204,6 +215,20 @@ class Pathoc(tcp.TCPClient):
|
|||||||
|
|
||||||
self.ws_framereader = None
|
self.ws_framereader = None
|
||||||
|
|
||||||
|
if self.use_http2:
|
||||||
|
self.protocol = http2.HTTP2Protocol(self)
|
||||||
|
else:
|
||||||
|
# TODO: create HTTP or Websockets protocol
|
||||||
|
self.protocol = None
|
||||||
|
|
||||||
|
self.settings = language.Settings(
|
||||||
|
is_client = True,
|
||||||
|
staticdir = os.getcwd(),
|
||||||
|
unconstrained_file_access = True,
|
||||||
|
request_host = self.address.host,
|
||||||
|
protocol = self.protocol,
|
||||||
|
)
|
||||||
|
|
||||||
def log(self):
|
def log(self):
|
||||||
return log.Log(
|
return log.Log(
|
||||||
self.fp,
|
self.fp,
|
||||||
@ -233,26 +258,44 @@ class Pathoc(tcp.TCPClient):
|
|||||||
connect_to: A (host, port) tuple, which will be connected to with
|
connect_to: A (host, port) tuple, which will be connected to with
|
||||||
an HTTP CONNECT request.
|
an HTTP CONNECT request.
|
||||||
"""
|
"""
|
||||||
|
if self.use_http2 and not self.ssl:
|
||||||
|
raise ValueError("HTTP2 without SSL is not supported.")
|
||||||
|
|
||||||
tcp.TCPClient.connect(self)
|
tcp.TCPClient.connect(self)
|
||||||
|
|
||||||
if connect_to:
|
if connect_to:
|
||||||
self.http_connect(connect_to)
|
self.http_connect(connect_to)
|
||||||
|
|
||||||
self.sslinfo = None
|
self.sslinfo = None
|
||||||
if self.ssl:
|
if self.ssl:
|
||||||
try:
|
try:
|
||||||
|
alpn_protos = [b'http1.1'] # TODO: move to a new HTTP1 protocol
|
||||||
|
if self.use_http2:
|
||||||
|
alpn_protos.append(http2.HTTP2Protocol.ALPN_PROTO_H2)
|
||||||
|
|
||||||
self.convert_to_ssl(
|
self.convert_to_ssl(
|
||||||
sni=self.sni,
|
sni=self.sni,
|
||||||
cert=self.clientcert,
|
cert=self.clientcert,
|
||||||
method=self.sslversion,
|
method=self.sslversion,
|
||||||
cipher_list = self.ciphers
|
cipher_list=self.ciphers,
|
||||||
|
alpn_protos=alpn_protos
|
||||||
)
|
)
|
||||||
except tcp.NetLibError as v:
|
except tcp.NetLibError as v:
|
||||||
raise PathocError(str(v))
|
raise PathocError(str(v))
|
||||||
|
|
||||||
self.sslinfo = SSLInfo(
|
self.sslinfo = SSLInfo(
|
||||||
self.connection.get_peer_cert_chain(),
|
self.connection.get_peer_cert_chain(),
|
||||||
self.get_current_cipher()
|
self.get_current_cipher(),
|
||||||
|
self.get_alpn_proto_negotiated()
|
||||||
)
|
)
|
||||||
if showssl:
|
if showssl:
|
||||||
print >> fp, str(self.sslinfo)
|
print >> fp, str(self.sslinfo)
|
||||||
|
|
||||||
|
if self.use_http2:
|
||||||
|
self.protocol.check_alpn()
|
||||||
|
if not self.http2_skip_connection_preface:
|
||||||
|
self.protocol.perform_connection_preface()
|
||||||
|
|
||||||
if self.timeout:
|
if self.timeout:
|
||||||
self.settimeout(self.timeout)
|
self.settimeout(self.timeout)
|
||||||
|
|
||||||
@ -337,15 +380,20 @@ class Pathoc(tcp.TCPClient):
|
|||||||
try:
|
try:
|
||||||
req = language.serve(r, self.wfile, self.settings)
|
req = language.serve(r, self.wfile, self.settings)
|
||||||
self.wfile.flush()
|
self.wfile.flush()
|
||||||
resp = list(
|
|
||||||
http.read_response(
|
if self.use_http2:
|
||||||
self.rfile,
|
status_code, headers, body = self.protocol.read_response()
|
||||||
req["method"],
|
resp = Response("HTTP/2", status_code, "", headers, body, self.sslinfo)
|
||||||
None
|
else:
|
||||||
|
resp = list(
|
||||||
|
http.read_response(
|
||||||
|
self.rfile,
|
||||||
|
req["method"],
|
||||||
|
None
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
resp.append(self.sslinfo)
|
||||||
resp.append(self.sslinfo)
|
resp = Response(*resp)
|
||||||
resp = Response(*resp)
|
|
||||||
except http.HttpError, v:
|
except http.HttpError, v:
|
||||||
log("Invalid server response: %s" % v)
|
log("Invalid server response: %s" % v)
|
||||||
raise
|
raise
|
||||||
@ -374,7 +422,8 @@ class Pathoc(tcp.TCPClient):
|
|||||||
May raise http.HTTPError, tcp.NetLibError
|
May raise http.HTTPError, tcp.NetLibError
|
||||||
"""
|
"""
|
||||||
if isinstance(r, basestring):
|
if isinstance(r, basestring):
|
||||||
r = language.parse_pathoc(r).next()
|
r = language.parse_pathoc(r, self.use_http2).next()
|
||||||
|
|
||||||
if isinstance(r, language.http.Request):
|
if isinstance(r, language.http.Request):
|
||||||
if r.ws:
|
if r.ws:
|
||||||
return self.websocket_start(r)
|
return self.websocket_start(r)
|
||||||
@ -382,6 +431,10 @@ class Pathoc(tcp.TCPClient):
|
|||||||
return self.http(r)
|
return self.http(r)
|
||||||
elif isinstance(r, language.websockets.WebsocketFrame):
|
elif isinstance(r, language.websockets.WebsocketFrame):
|
||||||
self.websocket_send_frame(r)
|
self.websocket_send_frame(r)
|
||||||
|
elif isinstance(r, language.http2.Request):
|
||||||
|
return self.http(r)
|
||||||
|
# elif isinstance(r, language.http2.Frame):
|
||||||
|
# TODO: do something
|
||||||
|
|
||||||
|
|
||||||
def main(args): # pragma: nocover
|
def main(args): # pragma: nocover
|
||||||
@ -407,6 +460,8 @@ def main(args): # pragma: nocover
|
|||||||
sslversion = args.sslversion,
|
sslversion = args.sslversion,
|
||||||
clientcert = args.clientcert,
|
clientcert = args.clientcert,
|
||||||
ciphers = args.ciphers,
|
ciphers = args.ciphers,
|
||||||
|
use_http2 = args.use_http2,
|
||||||
|
http2_skip_connection_preface = args.http2_skip_connection_preface,
|
||||||
showreq = args.showreq,
|
showreq = args.showreq,
|
||||||
showresp = args.showresp,
|
showresp = args.showresp,
|
||||||
explain = args.explain,
|
explain = args.explain,
|
||||||
|
Loading…
Reference in New Issue
Block a user