[sans-io] split http layer into smaller modules

This commit is contained in:
Maximilian Hils 2019-11-24 18:32:50 +01:00
parent 9e6548e581
commit 03801aecb2
8 changed files with 379 additions and 318 deletions

View File

@ -1,5 +1,5 @@
from . import modes from . import modes
from .http.http import HTTPLayer from .http import HTTPLayer
from .tcp import TCPLayer from .tcp import TCPLayer
from .tls import ClientTLSLayer, ServerTLSLayer from .tls import ClientTLSLayer, ServerTLSLayer

View File

@ -0,0 +1,5 @@
from .http import HTTPLayer
__all__ = [
"HTTPLayer",
]

View File

@ -0,0 +1,37 @@
import abc
import typing
from mitmproxy.proxy2 import commands, events
StreamId = int
class HttpEvent(events.Event):
stream_id: StreamId
# we need stream ids on every event to avoid race conditions
def __init__(self, stream_id: StreamId):
self.stream_id = stream_id
def __repr__(self) -> str:
x = self.__dict__.copy()
x.pop("stream_id")
return f"{type(self).__name__}({repr(x) if x else ''})"
class HttpConnection(abc.ABC):
@abc.abstractmethod
def handle_event(self, event: events.Event) -> typing.Iterator[HttpEvent]:
yield from ()
@abc.abstractmethod
def send(self, event: HttpEvent) -> commands.TCommandGenerator:
yield from ()
__all__ = [
"HttpConnection",
"StreamId",
"HttpEvent",
]

View File

@ -0,0 +1,53 @@
from mitmproxy import http
from .base import HttpEvent, StreamId
class RequestHeaders(HttpEvent):
request: http.HTTPRequest
def __init__(self, request: http.HTTPRequest, stream_id: StreamId):
super().__init__(stream_id)
self.request = request
class ResponseHeaders(HttpEvent):
response: http.HTTPResponse
def __init__(self, response: http.HTTPResponse, stream_id: StreamId):
super().__init__(stream_id)
self.response = response
class RequestData(HttpEvent):
data: bytes
def __init__(self, data: bytes, stream_id: StreamId):
super().__init__(stream_id)
self.data = data
class ResponseData(HttpEvent):
data: bytes
def __init__(self, data: bytes, stream_id: StreamId):
super().__init__(stream_id)
self.data = data
class RequestEndOfMessage(HttpEvent):
pass
class ResponseEndOfMessage(HttpEvent):
pass
__all__ = [
"HttpEvent",
"RequestHeaders",
"RequestData",
"RequestEndOfMessage",
"ResponseHeaders",
"ResponseData",
"ResponseEndOfMessage",
]

View File

@ -1,37 +1,19 @@
import collections import collections
import typing import typing
from abc import ABC, abstractmethod
import h11
from h11._readers import ChunkedReader, ContentLengthReader, Http10Reader
from h11._receivebuffer import ReceiveBuffer
from mitmproxy import flow, http from mitmproxy import flow, http
from mitmproxy.net.http import http1
from mitmproxy.net.http.http1 import read_sansio as http1_sansio
from mitmproxy.proxy.protocol.http import HTTPMode from mitmproxy.proxy.protocol.http import HTTPMode
from mitmproxy.proxy2 import commands, events from mitmproxy.proxy2 import commands, events
from mitmproxy.proxy2.context import Client, Connection, Context, Server from mitmproxy.proxy2.context import Connection, Context, Server
from mitmproxy.proxy2.layer import Layer, NextLayer from mitmproxy.proxy2.layer import Layer, NextLayer
from mitmproxy.proxy2.layers.tls import EstablishServerTLS, EstablishServerTLSReply, HTTP_ALPNS from mitmproxy.proxy2.layers.tls import EstablishServerTLS, EstablishServerTLSReply, HTTP_ALPNS
from mitmproxy.proxy2.utils import expect from mitmproxy.proxy2.utils import expect
from mitmproxy.utils import human from mitmproxy.utils import human
from .base import HttpConnection, StreamId
StreamId = int from .events import HttpEvent, RequestData, RequestEndOfMessage, RequestHeaders, ResponseData, ResponseEndOfMessage, \
ResponseHeaders
from .http1 import Http1Client, Http1Server
class HttpEvent(events.Event): from .http2 import Http2Client
stream_id: StreamId
# we need stream ids on every event to avoid race conditions
def __init__(self, stream_id: StreamId):
self.stream_id = stream_id
def __repr__(self) -> str:
x = self.__dict__.copy()
x.pop("stream_id")
return f"{type(self).__name__}({repr(x) if x else ''})"
class HttpCommand(commands.Command): class HttpCommand(commands.Command):
@ -76,287 +58,6 @@ class SendHttp(HttpCommand):
return f"Send({self.event})" return f"Send({self.event})"
HttpEventGenerator = typing.Iterator[HttpEvent]
class RequestHeaders(HttpEvent):
request: http.HTTPRequest
def __init__(self, request: http.HTTPRequest, stream_id: StreamId):
super().__init__(stream_id)
self.request = request
class ResponseHeaders(HttpEvent):
response: http.HTTPResponse
def __init__(self, response: http.HTTPResponse, stream_id: StreamId):
super().__init__(stream_id)
self.response = response
class RequestData(HttpEvent):
data: bytes
def __init__(self, data: bytes, stream_id: StreamId):
super().__init__(stream_id)
self.data = data
class ResponseData(HttpEvent):
data: bytes
def __init__(self, data: bytes, stream_id: StreamId):
super().__init__(stream_id)
self.data = data
class RequestEndOfMessage(HttpEvent):
pass
class ResponseEndOfMessage(HttpEvent):
pass
TBodyReader = typing.Union[ChunkedReader, Http10Reader, ContentLengthReader]
class Http1Connection(ABC):
conn: Connection
stream_id: StreamId = None
request: http.HTTPRequest
response: http.HTTPResponse
state: typing.Callable[[events.Event], HttpEventGenerator]
body_reader: TBodyReader
buf: ReceiveBuffer
def __init__(self, conn: Connection):
assert isinstance(conn, Connection)
self.conn = conn
self.buf = ReceiveBuffer()
def handle_event(self, event: events.Event) -> HttpEventGenerator:
if isinstance(event, events.DataReceived):
self.buf += event.data
yield from self.state(event)
@abstractmethod
def send(self, event: HttpEvent) -> commands.TCommandGenerator:
yield from ()
def make_body_reader(self, expected_size: typing.Optional[int]) -> TBodyReader:
if expected_size is None:
return ChunkedReader()
elif expected_size == -1:
return Http10Reader()
else:
return ContentLengthReader(expected_size)
def read_body(self, event: events.Event, is_request: bool) -> typing.Iterator[HttpEvent]:
while True:
try:
if isinstance(event, events.DataReceived):
h11_event = self.body_reader(self.buf)
elif isinstance(event, events.ConnectionClosed):
h11_event = self.body_reader.read_eof()
else:
raise ValueError(f"Unexpected event: {event}")
except h11.ProtocolError:
raise # FIXME
if h11_event is None:
return
elif isinstance(h11_event, h11.Data):
h11_event.data: bytearray # type checking
if is_request:
yield RequestData(bytes(h11_event.data), self.stream_id)
else:
yield ResponseData(bytes(h11_event.data), self.stream_id)
elif isinstance(h11_event, h11.EndOfMessage):
if is_request:
yield RequestEndOfMessage(self.stream_id)
else:
yield ResponseEndOfMessage(self.stream_id)
return
def wait(self, event: events.Event) -> HttpEventGenerator:
"""
We wait for the current flow to be finished before parsing the next message,
as we may want to upgrade to WebSocket or plain TCP before that.
"""
if isinstance(event, events.DataReceived):
return
elif isinstance(event, events.ConnectionClosed):
return
else:
yield from ()
raise ValueError(f"Unexpected event: {event}")
class Http1Server(Http1Connection):
"""A simple HTTP/1 server with no pipelining support."""
conn: Client
def __init__(self, conn: Client):
super().__init__(conn)
self.state = self.read_request_headers
self.stream_id = -1
def send(self, event: HttpEvent) -> commands.TCommandGenerator:
if isinstance(event, ResponseHeaders):
self.response = event.response
raw = http1.assemble_response_head(event.response)
if self.request.first_line_format == "authority":
assert self.state == self.wait
self.body_reader = self.make_body_reader(-1)
self.state = self.read_request_body
yield from self.state(events.DataReceived(self.conn, b""))
elif isinstance(event, ResponseData):
if "chunked" in self.response.headers.get("transfer-encoding", "").lower():
raw = b"%x\r\n%s\r\n" % (len(event.data), event.data)
else:
raw = event.data
elif isinstance(event, ResponseEndOfMessage):
if "chunked" in self.response.headers.get("transfer-encoding", "").lower():
raw = b"0\r\n\r\n"
elif http1.expected_http_body_size(self.request, self.response) == -1:
yield commands.CloseConnection(self.conn)
return
else:
raw = False
self.request = None
self.response = None
self.stream_id += 2
self.state = self.read_request_headers
yield from self.state(events.DataReceived(self.conn, b""))
else:
raise NotImplementedError(f"{event}")
if raw:
yield commands.SendData(self.conn, raw)
def read_request_headers(self, event: events.Event) -> HttpEventGenerator:
if isinstance(event, events.DataReceived):
request_head = self.buf.maybe_extract_lines()
if request_head:
request_head = [bytes(x) for x in request_head] # TODO: Make url.parse compatible with bytearrays
self.request = http.HTTPRequest.wrap(http1_sansio.read_request_head(request_head))
yield RequestHeaders(self.request, self.stream_id)
if self.request.first_line_format == "authority":
# The previous proxy server implementation tried to read the request body here:
# https://github.com/mitmproxy/mitmproxy/blob/45e3ae0f9cb50b0edbf4180fd969ea99d40bdf7b/mitmproxy/proxy/protocol/http.py#L251-L255
# We don't do this to be compliant with the h2 spec:
# https://http2.github.io/http2-spec/#CONNECT
self.state = self.wait
else:
expected_size = http1.expected_http_body_size(self.request)
self.body_reader = self.make_body_reader(expected_size)
self.state = self.read_request_body
yield from self.state(event)
elif isinstance(event, events.ConnectionClosed):
pass # TODO: Better handling, tear everything down.
else:
raise ValueError(f"Unexpected event: {event}")
def read_request_body(self, event: events.Event) -> HttpEventGenerator:
for e in self.read_body(event, True):
if isinstance(e, RequestEndOfMessage):
self.state = self.wait
yield from self.state(event)
yield e
class Http1Client(Http1Connection):
conn: Server
send_queue: typing.List[HttpEvent]
"""A queue of send events for flows other than the one that is currently being transmitted."""
def __init__(self, conn: Server):
super().__init__(conn)
self.state = self.read_response_headers
self.send_queue = []
def send(self, event: HttpEvent) -> commands.TCommandGenerator:
if not self.stream_id:
assert isinstance(event, RequestHeaders)
self.stream_id = event.stream_id
self.request = event.request
if self.stream_id != event.stream_id:
# Assuming an h2 server, we may have multiple Streams that try to send requests
# over a single h1 connection. To keep things relatively simple, we don't do any HTTP/1 pipelining
# but keep a queue of still-to-send requests.
self.send_queue.append(event)
return
if isinstance(event, RequestHeaders):
raw = http1.assemble_request_head(event.request)
elif isinstance(event, RequestData):
if "chunked" in self.request.headers.get("transfer-encoding", "").lower():
raw = b"%x\r\n%s\r\n" % (len(event.data), event.data)
else:
raw = event.data
elif isinstance(event, RequestEndOfMessage):
if "chunked" in self.request.headers.get("transfer-encoding", "").lower():
raw = b"0\r\n\r\n"
elif http1.expected_http_body_size(self.request) == -1:
assert not self.send_queue
yield commands.CloseConnection(self.conn)
return
else:
raw = False
else:
raise NotImplementedError(f"{event}")
if raw:
yield commands.SendData(self.conn, raw)
def read_response_headers(self, event: events.ConnectionEvent) -> HttpEventGenerator:
if isinstance(event, events.DataReceived):
response_head = self.buf.maybe_extract_lines()
if response_head:
response_head = [bytes(x) for x in response_head]
self.response = http.HTTPResponse.wrap(http1_sansio.read_response_head(response_head))
yield ResponseHeaders(self.response, self.stream_id)
expected_size = http1.expected_http_body_size(self.request, self.response)
self.body_reader = self.make_body_reader(expected_size)
self.state = self.read_response_body
yield from self.state(event)
elif isinstance(event, events.ConnectionClosed):
if self.stream_id:
raise NotImplementedError(f"{event}")
else:
return
else:
raise ValueError(f"Unexpected event: {event}")
def read_response_body(self, event: events.ConnectionEvent) -> HttpEventGenerator:
for e in self.read_body(event, False):
yield e
if isinstance(e, ResponseEndOfMessage):
self.state = self.read_response_headers
self.stream_id = None
self.request = None
self.response = None
if self.send_queue:
events = self.send_queue
self.send_queue = []
for e in events:
yield from self.send(e)
class Http2Server:
pass # TODO
class Http2Client:
pass # TODO
class HttpStream(Layer): class HttpStream(Layer):
request_body_buf: bytes request_body_buf: bytes
response_body_buf: bytes response_body_buf: bytes
@ -556,7 +257,7 @@ class HTTPLayer(Layer):
mode: HTTPMode mode: HTTPMode
stream_by_command: typing.Dict[commands.Command, HttpStream] stream_by_command: typing.Dict[commands.Command, HttpStream]
streams: typing.Dict[int, HttpStream] streams: typing.Dict[int, HttpStream]
connections: typing.Dict[Connection, typing.Union[Http1Connection, HttpStream]] connections: typing.Dict[Connection, typing.Union[HttpConnection, HttpStream]]
waiting_for_connection: typing.DefaultDict[Connection, typing.List[GetHttpConnection]] waiting_for_connection: typing.DefaultDict[Connection, typing.List[GetHttpConnection]]
event_queue: typing.Deque[ event_queue: typing.Deque[
typing.Union[HttpEvent, HttpCommand, commands.Command] typing.Union[HttpEvent, HttpCommand, commands.Command]
@ -714,7 +415,7 @@ class HTTPLayer(Layer):
def event_to_child( def event_to_child(
self, self,
stream: typing.Union[Http1Connection, HttpStream], stream: typing.Union[HttpConnection, HttpStream],
event: events.Event, event: events.Event,
) -> None: ) -> None:
stream_events = list(stream.handle_event(event)) stream_events = list(stream.handle_event(event))

View File

@ -0,0 +1,253 @@
import typing
from abc import abstractmethod
import h11
from h11._readers import ChunkedReader, ContentLengthReader, Http10Reader
from h11._receivebuffer import ReceiveBuffer
from mitmproxy import http
from mitmproxy.net.http import http1
from mitmproxy.net.http.http1 import read_sansio as http1_sansio
from mitmproxy.proxy2 import commands, events
from mitmproxy.proxy2.context import Client, Connection, Server
from mitmproxy.proxy2.layers.http.base import StreamId
from .base import HttpConnection
from .events import HttpEvent, RequestData, RequestEndOfMessage, RequestHeaders, ResponseData, ResponseEndOfMessage, \
ResponseHeaders
TBodyReader = typing.Union[ChunkedReader, Http10Reader, ContentLengthReader]
class Http1Connection(HttpConnection):
conn: Connection
stream_id: StreamId = None
request: http.HTTPRequest
response: http.HTTPResponse
state: typing.Callable[[events.Event], typing.Iterator[HttpEvent]]
body_reader: TBodyReader
buf: ReceiveBuffer
def __init__(self, conn: Connection):
assert isinstance(conn, Connection)
self.conn = conn
self.buf = ReceiveBuffer()
def handle_event(self, event: events.Event) -> typing.Iterator[HttpEvent]:
if isinstance(event, events.DataReceived):
self.buf += event.data
yield from self.state(event)
@abstractmethod
def send(self, event: HttpEvent) -> commands.TCommandGenerator:
yield from ()
def make_body_reader(self, expected_size: typing.Optional[int]) -> TBodyReader:
if expected_size is None:
return ChunkedReader()
elif expected_size == -1:
return Http10Reader()
else:
return ContentLengthReader(expected_size)
def read_body(self, event: events.Event, is_request: bool) -> typing.Iterator[HttpEvent]:
while True:
try:
if isinstance(event, events.DataReceived):
h11_event = self.body_reader(self.buf)
elif isinstance(event, events.ConnectionClosed):
h11_event = self.body_reader.read_eof()
else:
raise ValueError(f"Unexpected event: {event}")
except h11.ProtocolError:
raise # FIXME
if h11_event is None:
return
elif isinstance(h11_event, h11.Data):
h11_event.data: bytearray # type checking
if is_request:
yield RequestData(bytes(h11_event.data), self.stream_id)
else:
yield ResponseData(bytes(h11_event.data), self.stream_id)
elif isinstance(h11_event, h11.EndOfMessage):
if is_request:
yield RequestEndOfMessage(self.stream_id)
else:
yield ResponseEndOfMessage(self.stream_id)
return
def wait(self, event: events.Event) -> typing.Iterator[HttpEvent]:
"""
We wait for the current flow to be finished before parsing the next message,
as we may want to upgrade to WebSocket or plain TCP before that.
"""
if isinstance(event, events.DataReceived):
return
elif isinstance(event, events.ConnectionClosed):
return
else:
yield from ()
raise ValueError(f"Unexpected event: {event}")
class Http1Server(Http1Connection):
"""A simple HTTP/1 server with no pipelining support."""
conn: Client
def __init__(self, conn: Client):
super().__init__(conn)
self.state = self.read_request_headers
self.stream_id = -1
def send(self, event: HttpEvent) -> commands.TCommandGenerator:
if isinstance(event, ResponseHeaders):
self.response = event.response
raw = http1.assemble_response_head(event.response)
if self.request.first_line_format == "authority":
assert self.state == self.wait
self.body_reader = self.make_body_reader(-1)
self.state = self.read_request_body
yield from self.state(events.DataReceived(self.conn, b""))
elif isinstance(event, ResponseData):
if "chunked" in self.response.headers.get("transfer-encoding", "").lower():
raw = b"%x\r\n%s\r\n" % (len(event.data), event.data)
else:
raw = event.data
elif isinstance(event, ResponseEndOfMessage):
if "chunked" in self.response.headers.get("transfer-encoding", "").lower():
raw = b"0\r\n\r\n"
elif http1.expected_http_body_size(self.request, self.response) == -1:
yield commands.CloseConnection(self.conn)
return
else:
raw = False
self.request = None
self.response = None
self.stream_id += 2
self.state = self.read_request_headers
yield from self.state(events.DataReceived(self.conn, b""))
else:
raise NotImplementedError(f"{event}")
if raw:
yield commands.SendData(self.conn, raw)
def read_request_headers(self, event: events.Event) -> typing.Iterator[HttpEvent]:
if isinstance(event, events.DataReceived):
request_head = self.buf.maybe_extract_lines()
if request_head:
request_head = [bytes(x) for x in request_head] # TODO: Make url.parse compatible with bytearrays
self.request = http.HTTPRequest.wrap(http1_sansio.read_request_head(request_head))
yield RequestHeaders(self.request, self.stream_id)
if self.request.first_line_format == "authority":
# The previous proxy server implementation tried to read the request body here:
# https://github.com/mitmproxy/mitmproxy/blob/45e3ae0f9cb50b0edbf4180fd969ea99d40bdf7b/mitmproxy/proxy/protocol/http.py#L251-L255
# We don't do this to be compliant with the h2 spec:
# https://http2.github.io/http2-spec/#CONNECT
self.state = self.wait
else:
expected_size = http1.expected_http_body_size(self.request)
self.body_reader = self.make_body_reader(expected_size)
self.state = self.read_request_body
yield from self.state(event)
elif isinstance(event, events.ConnectionClosed):
pass # TODO: Better handling, tear everything down.
else:
raise ValueError(f"Unexpected event: {event}")
def read_request_body(self, event: events.Event) -> typing.Iterator[HttpEvent]:
for e in self.read_body(event, True):
if isinstance(e, RequestEndOfMessage):
self.state = self.wait
yield from self.state(event)
yield e
class Http1Client(Http1Connection):
conn: Server
send_queue: typing.List[HttpEvent]
"""A queue of send events for flows other than the one that is currently being transmitted."""
def __init__(self, conn: Server):
super().__init__(conn)
self.state = self.read_response_headers
self.send_queue = []
def send(self, event: HttpEvent) -> commands.TCommandGenerator:
if not self.stream_id:
assert isinstance(event, RequestHeaders)
self.stream_id = event.stream_id
self.request = event.request
if self.stream_id != event.stream_id:
# Assuming an h2 server, we may have multiple Streams that try to send requests
# over a single h1 connection. To keep things relatively simple, we don't do any HTTP/1 pipelining
# but keep a queue of still-to-send requests.
self.send_queue.append(event)
return
if isinstance(event, RequestHeaders):
raw = http1.assemble_request_head(event.request)
elif isinstance(event, RequestData):
if "chunked" in self.request.headers.get("transfer-encoding", "").lower():
raw = b"%x\r\n%s\r\n" % (len(event.data), event.data)
else:
raw = event.data
elif isinstance(event, RequestEndOfMessage):
if "chunked" in self.request.headers.get("transfer-encoding", "").lower():
raw = b"0\r\n\r\n"
elif http1.expected_http_body_size(self.request) == -1:
assert not self.send_queue
yield commands.CloseConnection(self.conn)
return
else:
raw = False
else:
raise NotImplementedError(f"{event}")
if raw:
yield commands.SendData(self.conn, raw)
def read_response_headers(self, event: events.Event) -> typing.Iterator[HttpEvent]:
assert isinstance(event, events.ConnectionEvent)
if isinstance(event, events.DataReceived):
response_head = self.buf.maybe_extract_lines()
if response_head:
response_head = [bytes(x) for x in response_head]
self.response = http.HTTPResponse.wrap(http1_sansio.read_response_head(response_head))
yield ResponseHeaders(self.response, self.stream_id)
expected_size = http1.expected_http_body_size(self.request, self.response)
self.body_reader = self.make_body_reader(expected_size)
self.state = self.read_response_body
yield from self.state(event)
elif isinstance(event, events.ConnectionClosed):
if self.stream_id:
raise NotImplementedError(f"{event}")
else:
return
else:
raise ValueError(f"Unexpected event: {event}")
def read_response_body(self, event: events.Event) -> typing.Iterator[HttpEvent]:
assert isinstance(event, events.ConnectionEvent)
for e in self.read_body(event, False):
yield e
if isinstance(e, ResponseEndOfMessage):
self.state = self.read_response_headers
self.stream_id = None
self.request = None
self.response = None
if self.send_queue:
send_queue = self.send_queue
self.send_queue = []
for ev in send_queue:
yield from self.send(ev)
__all__ = [
"Http1Client",
"Http1Server",
]

View File

@ -0,0 +1,12 @@
class Http2Server:
pass # TODO
class Http2Client:
pass # TODO
__all__ = [
"Http2Client",
"Http2Server",
]

View File

@ -4,8 +4,8 @@ from mitmproxy.http import HTTPResponse
from mitmproxy.proxy.protocol.http import HTTPMode from mitmproxy.proxy.protocol.http import HTTPMode
from mitmproxy.proxy2.commands import Hook, OpenConnection, SendData from mitmproxy.proxy2.commands import Hook, OpenConnection, SendData
from mitmproxy.proxy2.events import ConnectionClosed, DataReceived from mitmproxy.proxy2.events import ConnectionClosed, DataReceived
from mitmproxy.proxy2 import layers
from mitmproxy.proxy2.layers import tls from mitmproxy.proxy2.layers import tls
from mitmproxy.proxy2.layers.http import http
from test.mitmproxy.proxy2.tutils import Placeholder, Playbook, reply, reply_establish_server_tls, reply_next_layer from test.mitmproxy.proxy2.tutils import Placeholder, Playbook, reply, reply_establish_server_tls, reply_next_layer
@ -14,7 +14,7 @@ def test_http_proxy(tctx):
server = Placeholder() server = Placeholder()
flow = Placeholder() flow = Placeholder()
assert ( assert (
Playbook(http.HTTPLayer(tctx, HTTPMode.regular)) Playbook(layers.HTTPLayer(tctx, HTTPMode.regular))
>> DataReceived(tctx.client, b"GET http://example.com/foo?hello=1 HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived(tctx.client, b"GET http://example.com/foo?hello=1 HTTP/1.1\r\nHost: example.com\r\n\r\n")
<< Hook("requestheaders", flow) << Hook("requestheaders", flow)
>> reply() >> reply()
@ -39,7 +39,7 @@ def test_https_proxy(strategy, tctx):
"""Test a CONNECT request, followed by a HTTP GET /""" """Test a CONNECT request, followed by a HTTP GET /"""
server = Placeholder() server = Placeholder()
flow = Placeholder() flow = Placeholder()
playbook = Playbook(http.HTTPLayer(tctx, HTTPMode.regular)) playbook = Playbook(layers.HTTPLayer(tctx, HTTPMode.regular))
tctx.options.connection_strategy = strategy tctx.options.connection_strategy = strategy
(playbook (playbook
@ -54,7 +54,7 @@ def test_https_proxy(strategy, tctx):
<< SendData(tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n') << SendData(tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n')
>> DataReceived(tctx.client, b"GET /foo?hello=1 HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived(tctx.client, b"GET /foo?hello=1 HTTP/1.1\r\nHost: example.com\r\n\r\n")
<< Hook("next_layer", Placeholder()) << Hook("next_layer", Placeholder())
>> reply_next_layer(lambda ctx: http.HTTPLayer(ctx, HTTPMode.transparent)) >> reply_next_layer(lambda ctx: layers.HTTPLayer(ctx, HTTPMode.transparent))
<< Hook("requestheaders", flow) << Hook("requestheaders", flow)
>> reply() >> reply()
<< Hook("request", flow) << Hook("request", flow)
@ -82,7 +82,7 @@ def test_redirect(strategy, https_server, https_client, tctx):
server = Placeholder() server = Placeholder()
flow = Placeholder() flow = Placeholder()
tctx.options.connection_strategy = strategy tctx.options.connection_strategy = strategy
p = Playbook(http.HTTPLayer(tctx, HTTPMode.regular), hooks=False) p = Playbook(layers.HTTPLayer(tctx, HTTPMode.regular), hooks=False)
def redirect(hook: Hook): def redirect(hook: Hook):
if https_server: if https_server:
@ -98,7 +98,7 @@ def test_redirect(strategy, https_server, https_client, tctx):
p << SendData(tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n') p << SendData(tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n')
p >> DataReceived(tctx.client, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") p >> DataReceived(tctx.client, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
p << Hook("next_layer", Placeholder()) p << Hook("next_layer", Placeholder())
p >> reply_next_layer(lambda ctx: http.HTTPLayer(ctx, HTTPMode.transparent)) p >> reply_next_layer(lambda ctx: layers.HTTPLayer(ctx, HTTPMode.transparent))
else: else:
p >> DataReceived(tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") p >> DataReceived(tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n")
p << Hook("request", flow) p << Hook("request", flow)
@ -131,7 +131,7 @@ def test_multiple_server_connections(tctx):
return side_effect return side_effect
assert ( assert (
Playbook(http.HTTPLayer(tctx, HTTPMode.regular), hooks=False) Playbook(layers.HTTPLayer(tctx, HTTPMode.regular), hooks=False)
>> DataReceived(tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived(tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n")
<< Hook("request", Placeholder()) << Hook("request", Placeholder())
>> reply(side_effect=redirect("http://one.redirect/")) >> reply(side_effect=redirect("http://one.redirect/"))
@ -160,7 +160,7 @@ def test_http_reply_from_proxy(tctx):
hook.data.response = HTTPResponse.make(418) hook.data.response = HTTPResponse.make(418)
assert ( assert (
Playbook(http.HTTPLayer(tctx, HTTPMode.regular), hooks=False) Playbook(layers.HTTPLayer(tctx, HTTPMode.regular), hooks=False)
>> DataReceived(tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived(tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n")
<< Hook("request", Placeholder()) << Hook("request", Placeholder())
>> reply(side_effect=reply_from_proxy) >> reply(side_effect=reply_from_proxy)
@ -177,7 +177,7 @@ def test_disconnect_while_intercept(tctx):
flow = Placeholder() flow = Placeholder()
assert ( assert (
Playbook(http.HTTPLayer(tctx, HTTPMode.regular), hooks=False) Playbook(layers.HTTPLayer(tctx, HTTPMode.regular), hooks=False)
>> DataReceived(tctx.client, b"CONNECT example.com:80 HTTP/1.1\r\n\r\n") >> DataReceived(tctx.client, b"CONNECT example.com:80 HTTP/1.1\r\n\r\n")
<< Hook("http_connect", Placeholder()) << Hook("http_connect", Placeholder())
>> reply() >> reply()
@ -186,7 +186,7 @@ def test_disconnect_while_intercept(tctx):
<< SendData(tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n') << SendData(tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n')
>> DataReceived(tctx.client, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived(tctx.client, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
<< Hook("next_layer", Placeholder()) << Hook("next_layer", Placeholder())
>> reply_next_layer(lambda ctx: http.HTTPLayer(ctx, HTTPMode.transparent)) >> reply_next_layer(lambda ctx: layers.HTTPLayer(ctx, HTTPMode.transparent))
<< Hook("request", flow) << Hook("request", flow)
>> ConnectionClosed(server1) >> ConnectionClosed(server1)
>> reply(to=-2) >> reply(to=-2)