mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-26 10:16:27 +00:00
adjust to netlib changes
This commit is contained in:
parent
436a9ea839
commit
0af0608978
@ -128,7 +128,7 @@ def response(context, flow):
|
|||||||
|
|
||||||
request_query_string = [{"name": k, "value": v}
|
request_query_string = [{"name": k, "value": v}
|
||||||
for k, v in flow.request.get_query()]
|
for k, v in flow.request.get_query()]
|
||||||
request_http_version = ".".join([str(v) for v in flow.request.httpversion])
|
request_http_version = flow.request.httpversion
|
||||||
# Cookies are shaped as tuples by MITMProxy.
|
# Cookies are shaped as tuples by MITMProxy.
|
||||||
request_cookies = [{"name": k.strip(), "value": v[0]}
|
request_cookies = [{"name": k.strip(), "value": v[0]}
|
||||||
for k, v in (flow.request.get_cookies() or {}).iteritems()]
|
for k, v in (flow.request.get_cookies() or {}).iteritems()]
|
||||||
|
@ -103,11 +103,15 @@ def parse_setheader(s):
|
|||||||
|
|
||||||
|
|
||||||
def parse_server_spec(url):
|
def parse_server_spec(url):
|
||||||
p = netlib.utils.parse_url(url)
|
try:
|
||||||
if not p or not p[1] or p[0] not in ("http", "https"):
|
p = netlib.utils.parse_url(url)
|
||||||
|
if p[0] not in ("http", "https"):
|
||||||
|
raise ValueError()
|
||||||
|
except ValueError:
|
||||||
raise configargparse.ArgumentTypeError(
|
raise configargparse.ArgumentTypeError(
|
||||||
"Invalid server specification: %s" % url
|
"Invalid server specification: %s" % url
|
||||||
)
|
)
|
||||||
|
|
||||||
address = Address(p[1:3])
|
address = Address(p[1:3])
|
||||||
scheme = p[0].lower()
|
scheme = p[0].lower()
|
||||||
return config.ServerSpec(scheme, address)
|
return config.ServerSpec(scheme, address)
|
||||||
|
@ -4,7 +4,7 @@ import urwid
|
|||||||
import urwid.util
|
import urwid.util
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from netlib.http.semantics import CONTENT_MISSING
|
from netlib.http import CONTENT_MISSING
|
||||||
import netlib.utils
|
import netlib.utils
|
||||||
|
|
||||||
from .. import utils
|
from .. import utils
|
||||||
|
@ -6,7 +6,7 @@ import sys
|
|||||||
import urwid
|
import urwid
|
||||||
|
|
||||||
from netlib import odict
|
from netlib import odict
|
||||||
from netlib.http.semantics import CONTENT_MISSING, Headers
|
from netlib.http import CONTENT_MISSING, Headers
|
||||||
from . import common, grideditor, signals, searchable, tabs
|
from . import common, grideditor, signals, searchable, tabs
|
||||||
from . import flowdetailview
|
from . import flowdetailview
|
||||||
from .. import utils, controller, contentviews
|
from .. import utils, controller, contentviews
|
||||||
|
@ -6,7 +6,7 @@ import traceback
|
|||||||
import click
|
import click
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from netlib.http.semantics import CONTENT_MISSING
|
from netlib.http import CONTENT_MISSING
|
||||||
import netlib.utils
|
import netlib.utils
|
||||||
from . import flow, filt, contentviews
|
from . import flow, filt, contentviews
|
||||||
from .exceptions import ContentViewException
|
from .exceptions import ContentViewException
|
||||||
|
@ -34,11 +34,7 @@ class Socks5Exception(ProtocolException):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HttpException(ProtocolException):
|
class HttpProtocolException(ProtocolException):
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidCredentials(HttpException):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -48,3 +44,7 @@ class ServerException(ProxyException):
|
|||||||
|
|
||||||
class ContentViewException(ProxyException):
|
class ContentViewException(ProxyException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ReplayException(ProxyException):
|
||||||
|
pass
|
||||||
|
@ -12,7 +12,8 @@ import urlparse
|
|||||||
|
|
||||||
|
|
||||||
from netlib import wsgi
|
from netlib import wsgi
|
||||||
from netlib.http.semantics import CONTENT_MISSING, Headers
|
from netlib.exceptions import HttpException
|
||||||
|
from netlib.http import CONTENT_MISSING, Headers, http1
|
||||||
import netlib.http
|
import netlib.http
|
||||||
from . import controller, tnetstring, filt, script, version
|
from . import controller, tnetstring, filt, script, version
|
||||||
from .onboarding import app
|
from .onboarding import app
|
||||||
@ -161,9 +162,8 @@ class StreamLargeBodies(object):
|
|||||||
|
|
||||||
def run(self, flow, is_request):
|
def run(self, flow, is_request):
|
||||||
r = flow.request if is_request else flow.response
|
r = flow.request if is_request else flow.response
|
||||||
code = flow.response.code if flow.response else None
|
expected_size = http1.expected_http_body_size(
|
||||||
expected_size = netlib.http.http1.HTTP1Protocol.expected_http_body_size(
|
flow.request, flow.response if not is_request else None
|
||||||
r.headers, is_request, flow.request.method, code
|
|
||||||
)
|
)
|
||||||
if not (0 <= expected_size <= self.max_size):
|
if not (0 <= expected_size <= self.max_size):
|
||||||
# r.stream may already be a callable, which we want to preserve.
|
# r.stream may already be a callable, which we want to preserve.
|
||||||
@ -842,7 +842,7 @@ class FlowMaster(controller.Master):
|
|||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
path,
|
path,
|
||||||
(1, 1),
|
b"HTTP/1.1",
|
||||||
headers,
|
headers,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@ -1000,7 +1000,7 @@ class FlowMaster(controller.Master):
|
|||||||
try:
|
try:
|
||||||
if self.stream_large_bodies:
|
if self.stream_large_bodies:
|
||||||
self.stream_large_bodies.run(f, False)
|
self.stream_large_bodies.run(f, False)
|
||||||
except netlib.http.HttpError:
|
except HttpException:
|
||||||
f.reply(Kill)
|
f.reply(Kill)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -6,18 +6,17 @@ import time
|
|||||||
|
|
||||||
from libmproxy import utils
|
from libmproxy import utils
|
||||||
from netlib import encoding
|
from netlib import encoding
|
||||||
from netlib.http import status_codes, Headers
|
from netlib.http import status_codes, Headers, Request, Response, CONTENT_MISSING
|
||||||
from netlib.tcp import Address
|
from netlib.tcp import Address
|
||||||
from netlib.http.semantics import Request, Response, CONTENT_MISSING
|
|
||||||
from .. import version, stateobject
|
from .. import version, stateobject
|
||||||
from .flow import Flow
|
from .flow import Flow
|
||||||
|
|
||||||
|
|
||||||
class MessageMixin(stateobject.StateObject):
|
class MessageMixin(stateobject.StateObject):
|
||||||
_stateobject_attributes = dict(
|
_stateobject_attributes = dict(
|
||||||
httpversion=tuple,
|
httpversion=bytes,
|
||||||
headers=Headers,
|
headers=Headers,
|
||||||
body=str,
|
body=bytes,
|
||||||
timestamp_start=float,
|
timestamp_start=float,
|
||||||
timestamp_end=float
|
timestamp_end=float
|
||||||
)
|
)
|
||||||
@ -120,7 +119,7 @@ class HTTPRequest(MessageMixin, Request):
|
|||||||
|
|
||||||
path: Path portion of the URL (not present in authority-form)
|
path: Path portion of the URL (not present in authority-form)
|
||||||
|
|
||||||
httpversion: HTTP version tuple, e.g. (1,1)
|
httpversion: HTTP version, e.g. "HTTP/1.1"
|
||||||
|
|
||||||
headers: Headers object
|
headers: Headers object
|
||||||
|
|
||||||
@ -186,11 +185,11 @@ class HTTPRequest(MessageMixin, Request):
|
|||||||
_stateobject_attributes = MessageMixin._stateobject_attributes.copy()
|
_stateobject_attributes = MessageMixin._stateobject_attributes.copy()
|
||||||
_stateobject_attributes.update(
|
_stateobject_attributes.update(
|
||||||
form_in=str,
|
form_in=str,
|
||||||
method=str,
|
method=bytes,
|
||||||
scheme=str,
|
scheme=bytes,
|
||||||
host=str,
|
host=bytes,
|
||||||
port=int,
|
port=int,
|
||||||
path=str,
|
path=bytes,
|
||||||
form_out=str,
|
form_out=str,
|
||||||
is_replay=bool
|
is_replay=bool
|
||||||
)
|
)
|
||||||
@ -267,7 +266,7 @@ class HTTPResponse(MessageMixin, Response):
|
|||||||
|
|
||||||
Exposes the following attributes:
|
Exposes the following attributes:
|
||||||
|
|
||||||
httpversion: HTTP version tuple, e.g. (1, 0), (1, 1), or (2, 0)
|
httpversion: HTTP version, e.g. "HTTP/1.1"
|
||||||
|
|
||||||
status_code: HTTP response status code
|
status_code: HTTP response status code
|
||||||
|
|
||||||
@ -312,7 +311,7 @@ class HTTPResponse(MessageMixin, Response):
|
|||||||
_stateobject_attributes = MessageMixin._stateobject_attributes.copy()
|
_stateobject_attributes = MessageMixin._stateobject_attributes.copy()
|
||||||
_stateobject_attributes.update(
|
_stateobject_attributes.update(
|
||||||
status_code=int,
|
status_code=int,
|
||||||
msg=str
|
msg=bytes
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -532,7 +531,7 @@ def make_error_response(status_code, message, headers=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return HTTPResponse(
|
return HTTPResponse(
|
||||||
(1, 1), # FIXME: Should be a string.
|
b"HTTP/1.1",
|
||||||
status_code,
|
status_code,
|
||||||
response,
|
response,
|
||||||
headers,
|
headers,
|
||||||
@ -543,7 +542,7 @@ def make_error_response(status_code, message, headers=None):
|
|||||||
def make_connect_request(address):
|
def make_connect_request(address):
|
||||||
address = Address.wrap(address)
|
address = Address.wrap(address)
|
||||||
return HTTPRequest(
|
return HTTPRequest(
|
||||||
"authority", "CONNECT", None, address.host, address.port, None, (1, 1),
|
"authority", "CONNECT", None, address.host, address.port, None, b"HTTP/1.1",
|
||||||
Headers(), ""
|
Headers(), ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,14 +6,14 @@ import traceback
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from netlib import tcp
|
from netlib import tcp
|
||||||
from netlib.http import http1, HttpErrorConnClosed, HttpError, Headers
|
from netlib.exceptions import HttpException, HttpReadDisconnect
|
||||||
from netlib.http.semantics import CONTENT_MISSING
|
from netlib.http import http1, Headers
|
||||||
|
from netlib.http import CONTENT_MISSING
|
||||||
from netlib.tcp import NetLibError, Address
|
from netlib.tcp import NetLibError, Address
|
||||||
from netlib.http.http1 import HTTP1Protocol
|
from netlib.http.http2.connections import HTTP2Protocol
|
||||||
from netlib.http.http2 import HTTP2Protocol
|
|
||||||
from netlib.http.http2.frame import GoAwayFrame, PriorityFrame, WindowUpdateFrame
|
from netlib.http.http2.frame import GoAwayFrame, PriorityFrame, WindowUpdateFrame
|
||||||
from .. import utils
|
from .. import utils
|
||||||
from ..exceptions import InvalidCredentials, HttpException, ProtocolException
|
from ..exceptions import HttpProtocolException, ProtocolException
|
||||||
from ..models import (
|
from ..models import (
|
||||||
HTTPFlow, HTTPRequest, HTTPResponse, make_error_response, make_connect_response, Error
|
HTTPFlow, HTTPRequest, HTTPResponse, make_error_response, make_connect_response, Error
|
||||||
)
|
)
|
||||||
@ -45,14 +45,14 @@ class _StreamingHttpLayer(_HttpLayer):
|
|||||||
def read_response_headers(self):
|
def read_response_headers(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def read_response_body(self, headers, request_method, response_code, max_chunk_size=None):
|
def read_response_body(self, request, response):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
yield "this is a generator" # pragma: no cover
|
yield "this is a generator" # pragma: no cover
|
||||||
|
|
||||||
def read_response(self, request_method):
|
def read_response(self, request):
|
||||||
response = self.read_response_headers()
|
response = self.read_response_headers()
|
||||||
response.body = "".join(
|
response.body = b"".join(
|
||||||
self.read_response_body(response.headers, request_method, response.code)
|
self.read_response_body(request, response)
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ class _StreamingHttpLayer(_HttpLayer):
|
|||||||
|
|
||||||
def send_response(self, response):
|
def send_response(self, response):
|
||||||
if response.body == CONTENT_MISSING:
|
if response.body == CONTENT_MISSING:
|
||||||
raise HttpError(502, "Cannot assemble flow with CONTENT_MISSING")
|
raise HttpException("Cannot assemble flow with CONTENT_MISSING")
|
||||||
self.send_response_headers(response)
|
self.send_response_headers(response)
|
||||||
self.send_response_body(response, [response.body])
|
self.send_response_body(response, [response.body])
|
||||||
|
|
||||||
@ -73,48 +73,31 @@ class Http1Layer(_StreamingHttpLayer):
|
|||||||
def __init__(self, ctx, mode):
|
def __init__(self, ctx, mode):
|
||||||
super(Http1Layer, self).__init__(ctx)
|
super(Http1Layer, self).__init__(ctx)
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.client_protocol = HTTP1Protocol(self.client_conn)
|
|
||||||
self.server_protocol = HTTP1Protocol(self.server_conn)
|
|
||||||
|
|
||||||
def read_request(self):
|
def read_request(self):
|
||||||
return HTTPRequest.from_protocol(
|
req = http1.read_request(self.client_conn.rfile, body_size_limit=self.config.body_size_limit)
|
||||||
self.client_protocol,
|
return HTTPRequest.wrap(req)
|
||||||
body_size_limit=self.config.body_size_limit
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_request(self, request):
|
def send_request(self, request):
|
||||||
self.server_conn.send(self.server_protocol.assemble(request))
|
self.server_conn.wfile.write(http1.assemble_request(request))
|
||||||
|
self.server_conn.wfile.flush()
|
||||||
|
|
||||||
def read_response_headers(self):
|
def read_response_headers(self):
|
||||||
return HTTPResponse.from_protocol(
|
resp = http1.read_response_head(self.server_conn.rfile)
|
||||||
self.server_protocol,
|
return HTTPResponse.wrap(resp)
|
||||||
request_method=None, # does not matter if we don't read the body.
|
|
||||||
body_size_limit=self.config.body_size_limit,
|
|
||||||
include_body=False
|
|
||||||
)
|
|
||||||
|
|
||||||
def read_response_body(self, headers, request_method, response_code, max_chunk_size=None):
|
def read_response_body(self, request, response):
|
||||||
return self.server_protocol.read_http_body_chunked(
|
expected_size = http1.expected_http_body_size(request, response)
|
||||||
headers,
|
return http1.read_body(self.server_conn.rfile, expected_size, self.config.body_size_limit)
|
||||||
self.config.body_size_limit,
|
|
||||||
request_method,
|
|
||||||
response_code,
|
|
||||||
False,
|
|
||||||
max_chunk_size
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_response_headers(self, response):
|
def send_response_headers(self, response):
|
||||||
h = self.client_protocol._assemble_response_first_line(response)
|
raw = http1.assemble_response_head(response, preserve_transfer_encoding=True)
|
||||||
self.client_conn.wfile.write(h + "\r\n")
|
self.client_conn.wfile.write(raw)
|
||||||
h = self.client_protocol._assemble_response_headers(
|
|
||||||
response,
|
|
||||||
preserve_transfer_encoding=True
|
|
||||||
)
|
|
||||||
self.client_conn.wfile.write(h + "\r\n")
|
|
||||||
self.client_conn.wfile.flush()
|
self.client_conn.wfile.flush()
|
||||||
|
|
||||||
def send_response_body(self, response, chunks):
|
def send_response_body(self, response, chunks):
|
||||||
if self.client_protocol.has_chunked_encoding(response.headers):
|
if b"chunked" in response.headers.get(b"transfer-encoding", b"").lower():
|
||||||
|
# TODO: Move this into netlib.http.http1
|
||||||
chunks = itertools.chain(
|
chunks = itertools.chain(
|
||||||
(
|
(
|
||||||
"{:x}\r\n{}\r\n".format(len(chunk), chunk)
|
"{:x}\r\n{}\r\n".format(len(chunk), chunk)
|
||||||
@ -127,36 +110,24 @@ class Http1Layer(_StreamingHttpLayer):
|
|||||||
self.client_conn.wfile.flush()
|
self.client_conn.wfile.flush()
|
||||||
|
|
||||||
def check_close_connection(self, flow):
|
def check_close_connection(self, flow):
|
||||||
close_connection = (
|
request_close = http1.connection_close(
|
||||||
http1.HTTP1Protocol.connection_close(
|
flow.request.httpversion,
|
||||||
flow.request.httpversion,
|
flow.request.headers
|
||||||
flow.request.headers
|
|
||||||
) or http1.HTTP1Protocol.connection_close(
|
|
||||||
flow.response.httpversion,
|
|
||||||
flow.response.headers
|
|
||||||
) or http1.HTTP1Protocol.expected_http_body_size(
|
|
||||||
flow.response.headers,
|
|
||||||
False,
|
|
||||||
flow.request.method,
|
|
||||||
flow.response.code) == -1
|
|
||||||
)
|
)
|
||||||
|
response_close = http1.connection_close(
|
||||||
|
flow.response.httpversion,
|
||||||
|
flow.response.headers
|
||||||
|
)
|
||||||
|
read_until_eof = http1.expected_http_body_size(flow.request, flow.response) == -1
|
||||||
|
close_connection = request_close or response_close or read_until_eof
|
||||||
if flow.request.form_in == "authority" and flow.response.code == 200:
|
if flow.request.form_in == "authority" and flow.response.code == 200:
|
||||||
# Workaround for
|
# Workaround for https://github.com/mitmproxy/mitmproxy/issues/313:
|
||||||
# https://github.com/mitmproxy/mitmproxy/issues/313: Some
|
# Charles Proxy sends a CONNECT response with HTTP/1.0
|
||||||
# proxies (e.g. Charles) send a CONNECT response with HTTP/1.0
|
|
||||||
# and no Content-Length header
|
# and no Content-Length header
|
||||||
|
|
||||||
return False
|
return False
|
||||||
return close_connection
|
return close_connection
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
self.ctx.connect()
|
|
||||||
self.server_protocol = HTTP1Protocol(self.server_conn)
|
|
||||||
|
|
||||||
def set_server(self, *args, **kwargs):
|
|
||||||
self.ctx.set_server(*args, **kwargs)
|
|
||||||
self.server_protocol = HTTP1Protocol(self.server_conn)
|
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
layer = HttpLayer(self, self.mode)
|
layer = HttpLayer(self, self.mode)
|
||||||
layer()
|
layer()
|
||||||
@ -184,10 +155,10 @@ class Http2Layer(_HttpLayer):
|
|||||||
# TODO: implement flow control and WINDOW_UPDATE frames
|
# TODO: implement flow control and WINDOW_UPDATE frames
|
||||||
self.server_conn.send(self.server_protocol.assemble(message))
|
self.server_conn.send(self.server_protocol.assemble(message))
|
||||||
|
|
||||||
def read_response(self, request_method):
|
def read_response(self, request):
|
||||||
return HTTPResponse.from_protocol(
|
return HTTPResponse.from_protocol(
|
||||||
self.server_protocol,
|
self.server_protocol,
|
||||||
request_method=request_method,
|
request_method=request.method,
|
||||||
body_size_limit=self.config.body_size_limit,
|
body_size_limit=self.config.body_size_limit,
|
||||||
include_body=True,
|
include_body=True,
|
||||||
stream_id=self._stream_id
|
stream_id=self._stream_id
|
||||||
@ -295,7 +266,7 @@ class UpstreamConnectLayer(Layer):
|
|||||||
|
|
||||||
def _send_connect_request(self):
|
def _send_connect_request(self):
|
||||||
self.send_request(self.connect_request)
|
self.send_request(self.connect_request)
|
||||||
resp = self.read_response("CONNECT")
|
resp = self.read_response(self.connect_request)
|
||||||
if resp.code != 200:
|
if resp.code != 200:
|
||||||
raise ProtocolException("Reconnect: Upstream server refuses CONNECT request")
|
raise ProtocolException("Reconnect: Upstream server refuses CONNECT request")
|
||||||
|
|
||||||
@ -337,28 +308,31 @@ class HttpLayer(Layer):
|
|||||||
self.__original_server_conn = self.server_conn
|
self.__original_server_conn = self.server_conn
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
flow = HTTPFlow(self.client_conn, self.server_conn, live=self)
|
request = self.read_request()
|
||||||
|
|
||||||
try:
|
|
||||||
request = self.read_request()
|
|
||||||
except tcp.NetLibError:
|
|
||||||
# don't throw an error for disconnects that happen
|
|
||||||
# before/between requests.
|
|
||||||
return
|
|
||||||
|
|
||||||
self.log("request", "debug", [repr(request)])
|
self.log("request", "debug", [repr(request)])
|
||||||
|
|
||||||
# Handle Proxy Authentication
|
# Handle Proxy Authentication
|
||||||
self.authenticate(request)
|
if not self.authenticate(request):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Make sure that the incoming request matches our expectations
|
||||||
|
self.validate_request(request)
|
||||||
|
|
||||||
|
except HttpReadDisconnect:
|
||||||
|
# don't throw an error for disconnects that happen before/between requests.
|
||||||
|
return
|
||||||
|
except (HttpException, NetLibError) as e:
|
||||||
|
self.send_error_response(400, repr(e))
|
||||||
|
six.reraise(ProtocolException, ProtocolException("Error in HTTP connection: %s" % repr(e)), sys.exc_info()[2])
|
||||||
|
|
||||||
|
try:
|
||||||
|
flow = HTTPFlow(self.client_conn, self.server_conn, live=self)
|
||||||
|
|
||||||
# Regular Proxy Mode: Handle CONNECT
|
# Regular Proxy Mode: Handle CONNECT
|
||||||
if self.mode == "regular" and request.form_in == "authority":
|
if self.mode == "regular" and request.form_in == "authority":
|
||||||
self.handle_regular_mode_connect(request)
|
self.handle_regular_mode_connect(request)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Make sure that the incoming request matches our expectations
|
|
||||||
self.validate_request(request)
|
|
||||||
|
|
||||||
flow.request = request
|
flow.request = request
|
||||||
self.process_request_hook(flow)
|
self.process_request_hook(flow)
|
||||||
|
|
||||||
@ -384,30 +358,26 @@ class HttpLayer(Layer):
|
|||||||
self.handle_upstream_mode_connect(flow.request.copy())
|
self.handle_upstream_mode_connect(flow.request.copy())
|
||||||
return
|
return
|
||||||
|
|
||||||
except (HttpErrorConnClosed, NetLibError, HttpError, ProtocolException) as e:
|
except (HttpException, NetLibError) as e:
|
||||||
error_propagated = False
|
self.send_error_response(502, repr(e))
|
||||||
if flow.request and not flow.response:
|
|
||||||
|
if not flow.response:
|
||||||
flow.error = Error(str(e))
|
flow.error = Error(str(e))
|
||||||
self.channel.ask("error", flow)
|
self.channel.ask("error", flow)
|
||||||
self.log(traceback.format_exc(), "debug")
|
self.log(traceback.format_exc(), "debug")
|
||||||
error_propagated = True
|
return
|
||||||
|
else:
|
||||||
try:
|
six.reraise(ProtocolException, ProtocolException("Error in HTTP connection: %s" % repr(e)), sys.exc_info()[2])
|
||||||
self.send_response(make_error_response(
|
|
||||||
getattr(e, "code", 502),
|
|
||||||
repr(e)
|
|
||||||
))
|
|
||||||
except NetLibError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not error_propagated:
|
|
||||||
if isinstance(e, ProtocolException):
|
|
||||||
six.reraise(ProtocolException, e, sys.exc_info()[2])
|
|
||||||
else:
|
|
||||||
six.reraise(ProtocolException, ProtocolException("Error in HTTP connection: %s" % repr(e)), sys.exc_info()[2])
|
|
||||||
finally:
|
finally:
|
||||||
flow.live = False
|
flow.live = False
|
||||||
|
|
||||||
|
def send_error_response(self, code, message):
|
||||||
|
try:
|
||||||
|
response = make_error_response(code, message)
|
||||||
|
self.send_response(response)
|
||||||
|
except NetLibError:
|
||||||
|
pass
|
||||||
|
|
||||||
def change_upstream_proxy_server(self, address):
|
def change_upstream_proxy_server(self, address):
|
||||||
# Make set_upstream_proxy_server always available,
|
# Make set_upstream_proxy_server always available,
|
||||||
# even if there's no UpstreamConnectLayer
|
# even if there's no UpstreamConnectLayer
|
||||||
@ -435,10 +405,8 @@ class HttpLayer(Layer):
|
|||||||
# First send the headers and then transfer the response incrementally
|
# First send the headers and then transfer the response incrementally
|
||||||
self.send_response_headers(flow.response)
|
self.send_response_headers(flow.response)
|
||||||
chunks = self.read_response_body(
|
chunks = self.read_response_body(
|
||||||
flow.response.headers,
|
flow.request,
|
||||||
flow.request.method,
|
flow.response
|
||||||
flow.response.code,
|
|
||||||
max_chunk_size=4096
|
|
||||||
)
|
)
|
||||||
if callable(flow.response.stream):
|
if callable(flow.response.stream):
|
||||||
chunks = flow.response.stream(chunks)
|
chunks = flow.response.stream(chunks)
|
||||||
@ -451,11 +419,11 @@ class HttpLayer(Layer):
|
|||||||
if self.supports_streaming:
|
if self.supports_streaming:
|
||||||
flow.response = self.read_response_headers()
|
flow.response = self.read_response_headers()
|
||||||
else:
|
else:
|
||||||
flow.response = self.read_response(flow.request.method)
|
flow.response = self.read_response(flow.request)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
get_response()
|
get_response()
|
||||||
except (tcp.NetLibError, HttpErrorConnClosed) as v:
|
except (tcp.NetLibError, HttpException) as v:
|
||||||
self.log(
|
self.log(
|
||||||
"server communication error: %s" % repr(v),
|
"server communication error: %s" % repr(v),
|
||||||
level="debug"
|
level="debug"
|
||||||
@ -485,10 +453,9 @@ class HttpLayer(Layer):
|
|||||||
if flow.response.stream:
|
if flow.response.stream:
|
||||||
flow.response.content = CONTENT_MISSING
|
flow.response.content = CONTENT_MISSING
|
||||||
else:
|
else:
|
||||||
flow.response.content = "".join(self.read_response_body(
|
flow.response.content = b"".join(self.read_response_body(
|
||||||
flow.response.headers,
|
flow.request,
|
||||||
flow.request.method,
|
flow.response
|
||||||
flow.response.code
|
|
||||||
))
|
))
|
||||||
flow.response.timestamp_end = utils.timestamp()
|
flow.response.timestamp_end = utils.timestamp()
|
||||||
|
|
||||||
@ -543,7 +510,7 @@ class HttpLayer(Layer):
|
|||||||
if not self.server_conn:
|
if not self.server_conn:
|
||||||
self.connect()
|
self.connect()
|
||||||
if tls:
|
if tls:
|
||||||
raise HttpException("Cannot change scheme in upstream proxy mode.")
|
raise HttpProtocolException("Cannot change scheme in upstream proxy mode.")
|
||||||
"""
|
"""
|
||||||
# This is a very ugly (untested) workaround to solve a very ugly problem.
|
# This is a very ugly (untested) workaround to solve a very ugly problem.
|
||||||
if self.server_conn and self.server_conn.tls_established and not ssl:
|
if self.server_conn and self.server_conn.tls_established and not ssl:
|
||||||
@ -561,12 +528,10 @@ class HttpLayer(Layer):
|
|||||||
|
|
||||||
def validate_request(self, request):
|
def validate_request(self, request):
|
||||||
if request.form_in == "absolute" and request.scheme != "http":
|
if request.form_in == "absolute" and request.scheme != "http":
|
||||||
self.send_response(
|
|
||||||
make_error_response(400, "Invalid request scheme: %s" % request.scheme))
|
|
||||||
raise HttpException("Invalid request scheme: %s" % request.scheme)
|
raise HttpException("Invalid request scheme: %s" % request.scheme)
|
||||||
|
|
||||||
expected_request_forms = {
|
expected_request_forms = {
|
||||||
"regular": ("absolute",), # an authority request would already be handled.
|
"regular": ("authority", "absolute",),
|
||||||
"upstream": ("authority", "absolute"),
|
"upstream": ("authority", "absolute"),
|
||||||
"transparent": ("relative",)
|
"transparent": ("relative",)
|
||||||
}
|
}
|
||||||
@ -576,10 +541,9 @@ class HttpLayer(Layer):
|
|||||||
err_message = "Invalid HTTP request form (expected: %s, got: %s)" % (
|
err_message = "Invalid HTTP request form (expected: %s, got: %s)" % (
|
||||||
" or ".join(allowed_request_forms), request.form_in
|
" or ".join(allowed_request_forms), request.form_in
|
||||||
)
|
)
|
||||||
self.send_response(make_error_response(400, err_message))
|
|
||||||
raise HttpException(err_message)
|
raise HttpException(err_message)
|
||||||
|
|
||||||
if self.mode == "regular":
|
if self.mode == "regular" and request.form_in == "absolute":
|
||||||
request.form_out = "relative"
|
request.form_out = "relative"
|
||||||
|
|
||||||
def authenticate(self, request):
|
def authenticate(self, request):
|
||||||
@ -592,4 +556,5 @@ class HttpLayer(Layer):
|
|||||||
"Proxy Authentication Required",
|
"Proxy Authentication Required",
|
||||||
Headers(**self.config.authenticator.auth_challenge_headers())
|
Headers(**self.config.authenticator.auth_challenge_headers())
|
||||||
))
|
))
|
||||||
raise InvalidCredentials("Proxy Authentication Required")
|
return False
|
||||||
|
return True
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
from __future__ import (absolute_import, print_function, division)
|
from __future__ import (absolute_import, print_function, division)
|
||||||
import threading
|
import threading
|
||||||
|
from libmproxy.exceptions import ReplayException
|
||||||
|
from netlib.exceptions import HttpException
|
||||||
|
from netlib.http import http1
|
||||||
|
|
||||||
from netlib.http import HttpError
|
|
||||||
from netlib.http.http1 import HTTP1Protocol
|
|
||||||
from netlib.tcp import NetLibError
|
from netlib.tcp import NetLibError
|
||||||
from ..controller import Channel
|
from ..controller import Channel
|
||||||
from ..models import Error, HTTPResponse, ServerConnection, make_connect_request
|
from ..models import Error, HTTPResponse, ServerConnection, make_connect_request
|
||||||
@ -47,13 +48,17 @@ class RequestReplayThread(threading.Thread):
|
|||||||
server_address = self.config.upstream_server.address
|
server_address = self.config.upstream_server.address
|
||||||
server = ServerConnection(server_address)
|
server = ServerConnection(server_address)
|
||||||
server.connect()
|
server.connect()
|
||||||
protocol = HTTP1Protocol(server)
|
|
||||||
if r.scheme == "https":
|
if r.scheme == "https":
|
||||||
connect_request = make_connect_request((r.host, r.port))
|
connect_request = make_connect_request((r.host, r.port))
|
||||||
server.send(protocol.assemble(connect_request))
|
server.wfile.write(http1.assemble_request(connect_request))
|
||||||
resp = protocol.read_response("CONNECT")
|
server.wfile.flush()
|
||||||
|
resp = http1.read_response(
|
||||||
|
server.rfile,
|
||||||
|
connect_request,
|
||||||
|
body_size_limit=self.config.body_size_limit
|
||||||
|
)
|
||||||
if resp.code != 200:
|
if resp.code != 200:
|
||||||
raise HttpError(502, "Upstream server refuses CONNECT request")
|
raise ReplayException("Upstream server refuses CONNECT request")
|
||||||
server.establish_ssl(
|
server.establish_ssl(
|
||||||
self.config.clientcerts,
|
self.config.clientcerts,
|
||||||
sni=self.flow.server_conn.sni
|
sni=self.flow.server_conn.sni
|
||||||
@ -65,7 +70,6 @@ class RequestReplayThread(threading.Thread):
|
|||||||
server_address = (r.host, r.port)
|
server_address = (r.host, r.port)
|
||||||
server = ServerConnection(server_address)
|
server = ServerConnection(server_address)
|
||||||
server.connect()
|
server.connect()
|
||||||
protocol = HTTP1Protocol(server)
|
|
||||||
if r.scheme == "https":
|
if r.scheme == "https":
|
||||||
server.establish_ssl(
|
server.establish_ssl(
|
||||||
self.config.clientcerts,
|
self.config.clientcerts,
|
||||||
@ -73,18 +77,19 @@ class RequestReplayThread(threading.Thread):
|
|||||||
)
|
)
|
||||||
r.form_out = "relative"
|
r.form_out = "relative"
|
||||||
|
|
||||||
server.send(protocol.assemble(r))
|
server.wfile.write(http1.assemble_request(r))
|
||||||
|
server.wfile.flush()
|
||||||
self.flow.server_conn = server
|
self.flow.server_conn = server
|
||||||
self.flow.response = HTTPResponse.from_protocol(
|
self.flow.response = http1.read_response(
|
||||||
protocol,
|
server.rfile,
|
||||||
r.method,
|
r,
|
||||||
body_size_limit=self.config.body_size_limit,
|
body_size_limit=self.config.body_size_limit
|
||||||
)
|
)
|
||||||
if self.channel:
|
if self.channel:
|
||||||
response_reply = self.channel.ask("response", self.flow)
|
response_reply = self.channel.ask("response", self.flow)
|
||||||
if response_reply == Kill:
|
if response_reply == Kill:
|
||||||
raise Kill()
|
raise Kill()
|
||||||
except (HttpError, NetLibError) as v:
|
except (ReplayException, HttpException, NetLibError) as v:
|
||||||
self.flow.error = Error(repr(v))
|
self.flow.error = Error(repr(v))
|
||||||
if self.channel:
|
if self.channel:
|
||||||
self.channel.ask("error", self.flow)
|
self.channel.ask("error", self.flow)
|
||||||
|
@ -7,7 +7,7 @@ from construct import ConstructError
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from netlib.tcp import NetLibError, NetLibInvalidCertificateError
|
from netlib.tcp import NetLibError, NetLibInvalidCertificateError
|
||||||
from netlib.http.http1 import HTTP1Protocol
|
from netlib.http import ALPN_PROTO_HTTP1
|
||||||
from ..contrib.tls._constructs import ClientHello
|
from ..contrib.tls._constructs import ClientHello
|
||||||
from ..exceptions import ProtocolException, TlsException, ClientHandshakeException
|
from ..exceptions import ProtocolException, TlsException, ClientHandshakeException
|
||||||
from .base import Layer
|
from .base import Layer
|
||||||
@ -367,8 +367,8 @@ class TlsLayer(Layer):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# This gets triggered if we haven't established an upstream connection yet.
|
# This gets triggered if we haven't established an upstream connection yet.
|
||||||
default_alpn = HTTP1Protocol.ALPN_PROTO_HTTP1
|
default_alpn = ALPN_PROTO_HTTP1
|
||||||
# alpn_preference = netlib.http.http2.HTTP2Protocol.ALPN_PROTO_H2
|
# alpn_preference = ALPN_PROTO_H2
|
||||||
|
|
||||||
if self.alpn_for_client_connection in options:
|
if self.alpn_for_client_connection in options:
|
||||||
choice = bytes(self.alpn_for_client_connection)
|
choice = bytes(self.alpn_for_client_connection)
|
||||||
|
@ -5,8 +5,7 @@ import sys
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from libmproxy.exceptions import ProtocolException
|
from libmproxy.exceptions import ProtocolException
|
||||||
from netlib.http.http1 import HTTP1Protocol
|
from netlib.http import ALPN_PROTO_H2, ALPN_PROTO_HTTP1
|
||||||
from netlib.http.http2 import HTTP2Protocol
|
|
||||||
from netlib.tcp import NetLibError
|
from netlib.tcp import NetLibError
|
||||||
from ..protocol import (
|
from ..protocol import (
|
||||||
RawTCPLayer, TlsLayer, Http1Layer, Http2Layer, is_tls_record_magic, ServerConnectionMixin
|
RawTCPLayer, TlsLayer, Http1Layer, Http2Layer, is_tls_record_magic, ServerConnectionMixin
|
||||||
@ -85,9 +84,9 @@ class RootContext(object):
|
|||||||
# 5. Check for TLS ALPN (HTTP1/HTTP2)
|
# 5. Check for TLS ALPN (HTTP1/HTTP2)
|
||||||
if isinstance(top_layer, TlsLayer):
|
if isinstance(top_layer, TlsLayer):
|
||||||
alpn = top_layer.client_conn.get_alpn_proto_negotiated()
|
alpn = top_layer.client_conn.get_alpn_proto_negotiated()
|
||||||
if alpn == HTTP2Protocol.ALPN_PROTO_H2:
|
if alpn == ALPN_PROTO_H2:
|
||||||
return Http2Layer(top_layer, 'transparent')
|
return Http2Layer(top_layer, 'transparent')
|
||||||
if alpn == HTTP1Protocol.ALPN_PROTO_HTTP1:
|
if alpn == ALPN_PROTO_HTTP1:
|
||||||
return Http1Layer(top_layer, 'transparent')
|
return Http1Layer(top_layer, 'transparent')
|
||||||
|
|
||||||
# 6. Check for raw tcp mode
|
# 6. Check for raw tcp mode
|
||||||
|
@ -6,7 +6,7 @@ import socket
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from netlib import tcp
|
from netlib import tcp
|
||||||
from netlib.http.http1 import HTTP1Protocol
|
from netlib.http.http1 import assemble_response
|
||||||
from netlib.tcp import NetLibError
|
from netlib.tcp import NetLibError
|
||||||
from ..exceptions import ProtocolException, ServerException, ClientHandshakeException
|
from ..exceptions import ProtocolException, ServerException, ClientHandshakeException
|
||||||
from ..protocol import Kill
|
from ..protocol import Kill
|
||||||
@ -138,7 +138,7 @@ class ConnectionHandler(object):
|
|||||||
# understandable by HTTP clients and humans.
|
# understandable by HTTP clients and humans.
|
||||||
try:
|
try:
|
||||||
error_response = make_error_response(502, repr(e))
|
error_response = make_error_response(502, repr(e))
|
||||||
self.client_conn.send(HTTP1Protocol().assemble(error_response))
|
self.client_conn.send(assemble_response(error_response))
|
||||||
except NetLibError:
|
except NetLibError:
|
||||||
pass
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -128,12 +128,10 @@ class FlowHandler(RequestHandler):
|
|||||||
if a == "request":
|
if a == "request":
|
||||||
request = flow.request
|
request = flow.request
|
||||||
for k, v in b.iteritems():
|
for k, v in b.iteritems():
|
||||||
if k in ["method", "scheme", "host", "path"]:
|
if k in ["method", "scheme", "host", "path", "httpversion"]:
|
||||||
setattr(request, k, str(v))
|
setattr(request, k, str(v))
|
||||||
elif k == "port":
|
elif k == "port":
|
||||||
request.port = int(v)
|
request.port = int(v)
|
||||||
elif k == "httpversion":
|
|
||||||
request.httpversion = tuple(int(x) for x in v)
|
|
||||||
elif k == "headers":
|
elif k == "headers":
|
||||||
request.headers.load_state(v)
|
request.headers.load_state(v)
|
||||||
else:
|
else:
|
||||||
@ -147,7 +145,7 @@ class FlowHandler(RequestHandler):
|
|||||||
elif k == "code":
|
elif k == "code":
|
||||||
response.code = int(v)
|
response.code = int(v)
|
||||||
elif k == "httpversion":
|
elif k == "httpversion":
|
||||||
response.httpversion = tuple(int(x) for x in v)
|
response.httpversion = str(v)
|
||||||
elif k == "headers":
|
elif k == "headers":
|
||||||
response.headers.load_state(v)
|
response.headers.load_state(v)
|
||||||
else:
|
else:
|
||||||
|
@ -4,7 +4,7 @@ from libmproxy.exceptions import ContentViewException
|
|||||||
from libmproxy.models import HTTPResponse
|
from libmproxy.models import HTTPResponse
|
||||||
|
|
||||||
import netlib.tutils
|
import netlib.tutils
|
||||||
from netlib.http.semantics import CONTENT_MISSING
|
from netlib.http import CONTENT_MISSING
|
||||||
|
|
||||||
from libmproxy import dump, flow
|
from libmproxy import dump, flow
|
||||||
from libmproxy.proxy import Log
|
from libmproxy.proxy import Log
|
||||||
@ -38,13 +38,13 @@ def test_strfuncs():
|
|||||||
flow.request.stickycookie = True
|
flow.request.stickycookie = True
|
||||||
flow.client_conn = mock.MagicMock()
|
flow.client_conn = mock.MagicMock()
|
||||||
flow.client_conn.address.host = "foo"
|
flow.client_conn.address.host = "foo"
|
||||||
flow.response = netlib.tutils.tresp(content=CONTENT_MISSING)
|
flow.response = netlib.tutils.tresp(body=CONTENT_MISSING)
|
||||||
flow.response.is_replay = True
|
flow.response.is_replay = True
|
||||||
flow.response.code = 300
|
flow.response.code = 300
|
||||||
m.echo_flow(flow)
|
m.echo_flow(flow)
|
||||||
|
|
||||||
|
|
||||||
flow = tutils.tflow(resp=netlib.tutils.tresp("{"))
|
flow = tutils.tflow(resp=netlib.tutils.tresp(body="{"))
|
||||||
flow.response.headers["content-type"] = "application/json"
|
flow.response.headers["content-type"] = "application/json"
|
||||||
flow.response.code = 400
|
flow.response.code = 400
|
||||||
m.echo_flow(flow)
|
m.echo_flow(flow)
|
||||||
@ -62,14 +62,14 @@ def test_contentview(get_content_view):
|
|||||||
|
|
||||||
class TestDumpMaster:
|
class TestDumpMaster:
|
||||||
def _cycle(self, m, content):
|
def _cycle(self, m, content):
|
||||||
f = tutils.tflow(req=netlib.tutils.treq(content))
|
f = tutils.tflow(req=netlib.tutils.treq(body=content))
|
||||||
l = Log("connect")
|
l = Log("connect")
|
||||||
l.reply = mock.MagicMock()
|
l.reply = mock.MagicMock()
|
||||||
m.handle_log(l)
|
m.handle_log(l)
|
||||||
m.handle_clientconnect(f.client_conn)
|
m.handle_clientconnect(f.client_conn)
|
||||||
m.handle_serverconnect(f.server_conn)
|
m.handle_serverconnect(f.server_conn)
|
||||||
m.handle_request(f)
|
m.handle_request(f)
|
||||||
f.response = HTTPResponse.wrap(netlib.tutils.tresp(content))
|
f.response = HTTPResponse.wrap(netlib.tutils.tresp(body=content))
|
||||||
f = m.handle_response(f)
|
f = m.handle_response(f)
|
||||||
m.handle_clientdisconnect(f.client_conn)
|
m.handle_clientdisconnect(f.client_conn)
|
||||||
return f
|
return f
|
||||||
|
@ -84,7 +84,7 @@ class TestMatching:
|
|||||||
"host",
|
"host",
|
||||||
80,
|
80,
|
||||||
"/path",
|
"/path",
|
||||||
(1, 1),
|
b"HTTP/1.1",
|
||||||
headers,
|
headers,
|
||||||
"content_request",
|
"content_request",
|
||||||
None,
|
None,
|
||||||
@ -99,8 +99,7 @@ class TestMatching:
|
|||||||
|
|
||||||
headers = Headers([["header_response", "svalue"]])
|
headers = Headers([["header_response", "svalue"]])
|
||||||
f.response = http.HTTPResponse(
|
f.response = http.HTTPResponse(
|
||||||
(1,
|
b"HTTP/1.1",
|
||||||
1),
|
|
||||||
200,
|
200,
|
||||||
"OK",
|
"OK",
|
||||||
headers,
|
headers,
|
||||||
|
@ -8,7 +8,7 @@ import mock
|
|||||||
|
|
||||||
import netlib.utils
|
import netlib.utils
|
||||||
from netlib import odict
|
from netlib import odict
|
||||||
from netlib.http.semantics import CONTENT_MISSING, HDR_FORM_URLENCODED, Headers
|
from netlib.http import CONTENT_MISSING, HDR_FORM_URLENCODED, Headers
|
||||||
from libmproxy import filt, protocol, controller, tnetstring, flow
|
from libmproxy import filt, protocol, controller, tnetstring, flow
|
||||||
from libmproxy.models import Error, Flow, HTTPRequest, HTTPResponse, HTTPFlow, decoded
|
from libmproxy.models import Error, Flow, HTTPRequest, HTTPResponse, HTTPFlow, decoded
|
||||||
from libmproxy.proxy.config import HostMatcher
|
from libmproxy.proxy.config import HostMatcher
|
||||||
@ -849,7 +849,7 @@ class TestFlowMaster:
|
|||||||
s = flow.State()
|
s = flow.State()
|
||||||
|
|
||||||
f = tutils.tflow()
|
f = tutils.tflow()
|
||||||
f.response = HTTPResponse.wrap(netlib.tutils.tresp(f.request))
|
f.response = HTTPResponse.wrap(netlib.tutils.tresp(body=f.request))
|
||||||
pb = [f]
|
pb = [f]
|
||||||
|
|
||||||
fm = flow.FlowMaster(None, s)
|
fm = flow.FlowMaster(None, s)
|
||||||
@ -903,7 +903,7 @@ class TestFlowMaster:
|
|||||||
def test_server_playback_kill(self):
|
def test_server_playback_kill(self):
|
||||||
s = flow.State()
|
s = flow.State()
|
||||||
f = tutils.tflow()
|
f = tutils.tflow()
|
||||||
f.response = HTTPResponse.wrap(netlib.tutils.tresp(f.request))
|
f.response = HTTPResponse.wrap(netlib.tutils.tresp(body=f.request))
|
||||||
pb = [f]
|
pb = [f]
|
||||||
fm = flow.FlowMaster(None, s)
|
fm = flow.FlowMaster(None, s)
|
||||||
fm.refresh_server_playback = True
|
fm.refresh_server_playback = True
|
||||||
@ -1043,7 +1043,7 @@ class TestRequest:
|
|||||||
|
|
||||||
def test_getset_form_urlencoded(self):
|
def test_getset_form_urlencoded(self):
|
||||||
d = odict.ODict([("one", "two"), ("three", "four")])
|
d = odict.ODict([("one", "two"), ("three", "four")])
|
||||||
r = HTTPRequest.wrap(netlib.tutils.treq(content=netlib.utils.urlencode(d.lst)))
|
r = HTTPRequest.wrap(netlib.tutils.treq(body=netlib.utils.urlencode(d.lst)))
|
||||||
r.headers["content-type"] = HDR_FORM_URLENCODED
|
r.headers["content-type"] = HDR_FORM_URLENCODED
|
||||||
assert r.get_form_urlencoded() == d
|
assert r.get_form_urlencoded() == d
|
||||||
|
|
||||||
|
@ -17,15 +17,11 @@ class TestFuzzy(tservers.HTTPProxTest):
|
|||||||
p = self.pathoc()
|
p = self.pathoc()
|
||||||
assert p.request(req % self.server.port).status_code == 400
|
assert p.request(req % self.server.port).status_code == 400
|
||||||
|
|
||||||
def test_invalid_ports(self):
|
|
||||||
req = 'get:"http://localhost:999999"'
|
|
||||||
p = self.pathoc()
|
|
||||||
assert p.request(req).status_code == 400
|
|
||||||
|
|
||||||
def test_invalid_ipv6_url(self):
|
def test_invalid_ipv6_url(self):
|
||||||
req = 'get:"http://localhost:%s":i13,"["'
|
req = 'get:"http://localhost:%s":i13,"["'
|
||||||
p = self.pathoc()
|
p = self.pathoc()
|
||||||
assert p.request(req % self.server.port).status_code == 400
|
resp = p.request(req % self.server.port)
|
||||||
|
assert resp.status_code == 400
|
||||||
|
|
||||||
# def test_invalid_upstream(self):
|
# def test_invalid_upstream(self):
|
||||||
# req = r"get:'http://localhost:%s/p/200:i10,\x27+\x27'"
|
# req = r"get:'http://localhost:%s/p/200:i10,\x27+\x27'"
|
||||||
|
@ -1,46 +1,36 @@
|
|||||||
import cStringIO
|
from io import BytesIO
|
||||||
from cStringIO import StringIO
|
from netlib.exceptions import HttpSyntaxException
|
||||||
|
|
||||||
from mock import MagicMock
|
|
||||||
|
|
||||||
from libmproxy.protocol.http import *
|
|
||||||
import netlib.http
|
|
||||||
from netlib.http import http1
|
from netlib.http import http1
|
||||||
from netlib.http.semantics import CONTENT_MISSING
|
from netlib.tutils import treq, raises
|
||||||
|
|
||||||
import tutils
|
import tutils
|
||||||
import tservers
|
import tservers
|
||||||
|
|
||||||
def mock_protocol(data=''):
|
|
||||||
rfile = cStringIO.StringIO(data)
|
|
||||||
wfile = cStringIO.StringIO()
|
|
||||||
return http1.HTTP1Protocol(rfile=rfile, wfile=wfile)
|
|
||||||
|
|
||||||
|
|
||||||
class TestHTTPResponse:
|
class TestHTTPResponse:
|
||||||
def test_read_from_stringio(self):
|
def test_read_from_stringio(self):
|
||||||
s = "HTTP/1.1 200 OK\r\n" \
|
s = (
|
||||||
"Content-Length: 7\r\n" \
|
b"HTTP/1.1 200 OK\r\n"
|
||||||
"\r\n"\
|
b"Content-Length: 7\r\n"
|
||||||
"content\r\n" \
|
b"\r\n"
|
||||||
"HTTP/1.1 204 OK\r\n" \
|
b"content\r\n"
|
||||||
"\r\n"
|
b"HTTP/1.1 204 OK\r\n"
|
||||||
|
b"\r\n"
|
||||||
protocol = mock_protocol(s)
|
|
||||||
r = HTTPResponse.from_protocol(protocol, "GET")
|
|
||||||
assert r.status_code == 200
|
|
||||||
assert r.content == "content"
|
|
||||||
assert HTTPResponse.from_protocol(protocol, "GET").status_code == 204
|
|
||||||
|
|
||||||
protocol = mock_protocol(s)
|
|
||||||
# HEAD must not have content by spec. We should leave it on the pipe.
|
|
||||||
r = HTTPResponse.from_protocol(protocol, "HEAD")
|
|
||||||
assert r.status_code == 200
|
|
||||||
assert r.content == ""
|
|
||||||
tutils.raises(
|
|
||||||
"Invalid server response: 'content",
|
|
||||||
HTTPResponse.from_protocol, protocol, "GET"
|
|
||||||
)
|
)
|
||||||
|
rfile = BytesIO(s)
|
||||||
|
r = http1.read_response(rfile, treq())
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.content == b"content"
|
||||||
|
assert http1.read_response(rfile, treq()).status_code == 204
|
||||||
|
|
||||||
|
rfile = BytesIO(s)
|
||||||
|
# HEAD must not have content by spec. We should leave it on the pipe.
|
||||||
|
r = http1.read_response(rfile, treq(method=b"HEAD"))
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.content == b""
|
||||||
|
|
||||||
|
with raises(HttpSyntaxException):
|
||||||
|
http1.read_response(rfile, treq())
|
||||||
|
|
||||||
|
|
||||||
class TestHTTPFlow(object):
|
class TestHTTPFlow(object):
|
||||||
|
@ -9,6 +9,7 @@ from libmproxy.proxy.server import DummyServer, ProxyServer, ConnectionHandler
|
|||||||
import tutils
|
import tutils
|
||||||
from libpathod import test
|
from libpathod import test
|
||||||
from netlib import http, tcp
|
from netlib import http, tcp
|
||||||
|
from netlib.http import http1
|
||||||
|
|
||||||
|
|
||||||
class TestServerConnection:
|
class TestServerConnection:
|
||||||
@ -26,11 +27,10 @@ class TestServerConnection:
|
|||||||
f.request.path = "/p/200:da"
|
f.request.path = "/p/200:da"
|
||||||
|
|
||||||
# use this protocol just to assemble - not for actual sending
|
# use this protocol just to assemble - not for actual sending
|
||||||
protocol = http.http1.HTTP1Protocol(rfile=sc.rfile)
|
sc.wfile.write(http1.assemble_request(f.request))
|
||||||
sc.send(protocol.assemble(f.request))
|
sc.wfile.flush()
|
||||||
|
|
||||||
protocol = http.http1.HTTP1Protocol(rfile=sc.rfile)
|
assert http1.read_response(sc.rfile, f.request, 1000)
|
||||||
assert protocol.read_response(f.request.method, 1000)
|
|
||||||
assert self.d.last_log()
|
assert self.d.last_log()
|
||||||
|
|
||||||
sc.finish()
|
sc.finish()
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
from OpenSSL import SSL
|
from OpenSSL import SSL
|
||||||
|
from netlib.exceptions import HttpReadDisconnect, HttpException
|
||||||
from netlib.tcp import Address
|
from netlib.tcp import Address
|
||||||
|
|
||||||
import netlib.tutils
|
import netlib.tutils
|
||||||
from netlib import tcp, http, socks
|
from netlib import tcp, http, socks
|
||||||
from netlib.certutils import SSLCert
|
from netlib.certutils import SSLCert
|
||||||
from netlib.http import authentication
|
from netlib.http import authentication, CONTENT_MISSING, http1
|
||||||
from netlib.http.semantics import CONTENT_MISSING
|
from netlib.tutils import raises
|
||||||
from libpathod import pathoc, pathod
|
from libpathod import pathoc, pathod
|
||||||
|
|
||||||
from libmproxy.proxy.config import HostMatcher
|
from libmproxy.proxy.config import HostMatcher
|
||||||
@ -143,10 +144,9 @@ class TcpMixin:
|
|||||||
# mitmproxy responds with bad gateway
|
# mitmproxy responds with bad gateway
|
||||||
assert self.pathod(spec).status_code == 502
|
assert self.pathod(spec).status_code == 502
|
||||||
self._ignore_on()
|
self._ignore_on()
|
||||||
tutils.raises(
|
with raises(HttpException):
|
||||||
"invalid server response",
|
self.pathod(spec) # pathoc tries to parse answer as HTTP
|
||||||
self.pathod,
|
|
||||||
spec) # pathoc tries to parse answer as HTTP
|
|
||||||
self._ignore_off()
|
self._ignore_off()
|
||||||
|
|
||||||
def _tcpproxy_on(self):
|
def _tcpproxy_on(self):
|
||||||
@ -250,11 +250,6 @@ class TestHTTP(tservers.HTTPProxTest, CommonMixin, AppMixin):
|
|||||||
assert p.request(req % self.server2.urlbase)
|
assert p.request(req % self.server2.urlbase)
|
||||||
assert switched(self.proxy.log)
|
assert switched(self.proxy.log)
|
||||||
|
|
||||||
def test_get_connection_err(self):
|
|
||||||
p = self.pathoc()
|
|
||||||
ret = p.request("get:'http://localhost:0'")
|
|
||||||
assert ret.status_code == 502
|
|
||||||
|
|
||||||
def test_blank_leading_line(self):
|
def test_blank_leading_line(self):
|
||||||
p = self.pathoc()
|
p = self.pathoc()
|
||||||
req = "get:'%s/p/201':i0,'\r\n'"
|
req = "get:'%s/p/201':i0,'\r\n'"
|
||||||
@ -262,8 +257,8 @@ class TestHTTP(tservers.HTTPProxTest, CommonMixin, AppMixin):
|
|||||||
|
|
||||||
def test_invalid_headers(self):
|
def test_invalid_headers(self):
|
||||||
p = self.pathoc()
|
p = self.pathoc()
|
||||||
req = p.request("get:'http://foo':h':foo'='bar'")
|
resp = p.request("get:'http://foo':h':foo'='bar'")
|
||||||
assert req.status_code == 400
|
assert resp.status_code == 400
|
||||||
|
|
||||||
def test_empty_chunked_content(self):
|
def test_empty_chunked_content(self):
|
||||||
"""
|
"""
|
||||||
@ -570,17 +565,23 @@ class TestProxy(tservers.HTTPProxTest):
|
|||||||
connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
connection.connect(("localhost", self.proxy.port))
|
connection.connect(("localhost", self.proxy.port))
|
||||||
connection.send(
|
connection.send(
|
||||||
"GET http://localhost:%d/p/304:b@1k HTTP/1.1\r\n" %
|
"GET http://localhost:%d/p/200:b@1k HTTP/1.1\r\n" %
|
||||||
self.server.port)
|
self.server.port)
|
||||||
connection.send("\r\n")
|
connection.send("\r\n")
|
||||||
connection.recv(5000)
|
# a bit hacky: make sure that we don't just read the headers only.
|
||||||
|
recvd = 0
|
||||||
|
while recvd < 1024:
|
||||||
|
recvd += len(connection.recv(5000))
|
||||||
connection.send(
|
connection.send(
|
||||||
"GET http://localhost:%d/p/304:b@1k HTTP/1.1\r\n" %
|
"GET http://localhost:%d/p/200:b@1k HTTP/1.1\r\n" %
|
||||||
self.server.port)
|
self.server.port)
|
||||||
connection.send("\r\n")
|
connection.send("\r\n")
|
||||||
connection.recv(5000)
|
recvd = 0
|
||||||
|
while recvd < 1024:
|
||||||
|
recvd += len(connection.recv(5000))
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
|
print(self.master.state.view._list)
|
||||||
first_flow = self.master.state.view[0]
|
first_flow = self.master.state.view[0]
|
||||||
second_flow = self.master.state.view[1]
|
second_flow = self.master.state.view[1]
|
||||||
assert first_flow.server_conn.timestamp_tcp_setup
|
assert first_flow.server_conn.timestamp_tcp_setup
|
||||||
@ -718,15 +719,12 @@ class TestStreamRequest(tservers.HTTPProxTest):
|
|||||||
(self.server.urlbase, spec))
|
(self.server.urlbase, spec))
|
||||||
connection.send("\r\n")
|
connection.send("\r\n")
|
||||||
|
|
||||||
protocol = http.http1.HTTP1Protocol(rfile=fconn)
|
resp = http1.read_response_head(fconn)
|
||||||
resp = protocol.read_response("GET", None, include_body=False)
|
|
||||||
|
|
||||||
assert resp.headers["Transfer-Encoding"] == 'chunked'
|
assert resp.headers["Transfer-Encoding"] == 'chunked'
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
|
||||||
chunks = list(protocol.read_http_body_chunked(
|
chunks = list(http1.read_body(fconn, None))
|
||||||
resp.headers, None, "GET", 200, False
|
|
||||||
))
|
|
||||||
assert chunks == ["this", "isatest__reachhex"]
|
assert chunks == ["this", "isatest__reachhex"]
|
||||||
|
|
||||||
connection.close()
|
connection.close()
|
||||||
@ -743,7 +741,7 @@ class TestFakeResponse(tservers.HTTPProxTest):
|
|||||||
|
|
||||||
def test_fake(self):
|
def test_fake(self):
|
||||||
f = self.pathod("200")
|
f = self.pathod("200")
|
||||||
assert "header_response" in f.headers
|
assert "header-response" in f.headers
|
||||||
|
|
||||||
|
|
||||||
class TestServerConnect(tservers.HTTPProxTest):
|
class TestServerConnect(tservers.HTTPProxTest):
|
||||||
@ -766,7 +764,8 @@ class TestKillRequest(tservers.HTTPProxTest):
|
|||||||
masterclass = MasterKillRequest
|
masterclass = MasterKillRequest
|
||||||
|
|
||||||
def test_kill(self):
|
def test_kill(self):
|
||||||
tutils.raises("server disconnect", self.pathod, "200")
|
with raises(HttpReadDisconnect):
|
||||||
|
self.pathod("200")
|
||||||
# Nothing should have hit the server
|
# Nothing should have hit the server
|
||||||
assert not self.server.last_log()
|
assert not self.server.last_log()
|
||||||
|
|
||||||
@ -780,7 +779,8 @@ class TestKillResponse(tservers.HTTPProxTest):
|
|||||||
masterclass = MasterKillResponse
|
masterclass = MasterKillResponse
|
||||||
|
|
||||||
def test_kill(self):
|
def test_kill(self):
|
||||||
tutils.raises("server disconnect", self.pathod, "200")
|
with raises(HttpReadDisconnect):
|
||||||
|
self.pathod("200")
|
||||||
# The server should have seen a request
|
# The server should have seen a request
|
||||||
assert self.server.last_log()
|
assert self.server.last_log()
|
||||||
|
|
||||||
@ -907,7 +907,7 @@ class TestUpstreamProxySSL(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def handle_request(f):
|
def handle_request(f):
|
||||||
f.request.httpversion = (1, 0)
|
f.request.httpversion = b"HTTP/1.1"
|
||||||
del f.request.headers["Content-Length"]
|
del f.request.headers["Content-Length"]
|
||||||
f.reply()
|
f.reply()
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ def tflowview(request_contents=None):
|
|||||||
if request_contents is None:
|
if request_contents is None:
|
||||||
flow = tflow()
|
flow = tflow()
|
||||||
else:
|
else:
|
||||||
flow = tflow(req=netlib.tutils.treq(request_contents))
|
flow = tflow(req=netlib.tutils.treq(body=request_contents))
|
||||||
|
|
||||||
fv = FlowView(m, cs, flow)
|
fv = FlowView(m, cs, flow)
|
||||||
return fv
|
return fv
|
||||||
|
@ -121,7 +121,7 @@ var RequestLine = React.createClass({
|
|||||||
render: function () {
|
render: function () {
|
||||||
var flow = this.props.flow;
|
var flow = this.props.flow;
|
||||||
var url = flowutils.RequestUtils.pretty_url(flow.request);
|
var url = flowutils.RequestUtils.pretty_url(flow.request);
|
||||||
var httpver = "HTTP/" + flow.request.httpversion.join(".");
|
var httpver = flow.request.httpversion;
|
||||||
|
|
||||||
return <div className="first-line request-line">
|
return <div className="first-line request-line">
|
||||||
<ValueEditor
|
<ValueEditor
|
||||||
@ -175,7 +175,7 @@ var RequestLine = React.createClass({
|
|||||||
var ResponseLine = React.createClass({
|
var ResponseLine = React.createClass({
|
||||||
render: function () {
|
render: function () {
|
||||||
var flow = this.props.flow;
|
var flow = this.props.flow;
|
||||||
var httpver = "HTTP/" + flow.response.httpversion.join(".");
|
var httpver = flow.response.httpversion;
|
||||||
return <div className="first-line response-line">
|
return <div className="first-line response-line">
|
||||||
<ValueEditor
|
<ValueEditor
|
||||||
ref="httpVersion"
|
ref="httpVersion"
|
||||||
|
Loading…
Reference in New Issue
Block a user