aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mitmproxy/console/window.py2
-rw-r--r--mitmproxy/protocol/http2.py76
-rw-r--r--mitmproxy/protocol/tls.py34
-rw-r--r--mitmproxy/proxy/config.py2
-rw-r--r--test/mitmproxy/test_protocol_http2.py6
-rw-r--r--test/mitmproxy/test_proxy.py26
-rw-r--r--test/mitmproxy/test_server.py5
-rw-r--r--web/src/js/__tests__/ducks/ui/flowSpec.js76
-rw-r--r--web/src/js/__tests__/ducks/utils/listSpec.js1
-rw-r--r--web/src/js/components/ContentView/ShowFullContentButton.jsx2
-rw-r--r--web/src/js/ducks/ui/flow.js21
11 files changed, 167 insertions, 84 deletions
diff --git a/mitmproxy/console/window.py b/mitmproxy/console/window.py
index b24718be..35593643 100644
--- a/mitmproxy/console/window.py
+++ b/mitmproxy/console/window.py
@@ -27,7 +27,7 @@ class Window(urwid.Frame):
if not k:
if args[1] == "mouse drag":
signals.status_message.send(
- message = "Hold down shift, alt or ctrl to select text.",
+ message = "Hold down fn, shift, alt or ctrl to select text or use the --no-mouse parameter.",
expire = 1
)
elif args[1] == "mouse press" and args[2] == 4:
diff --git a/mitmproxy/protocol/http2.py b/mitmproxy/protocol/http2.py
index eb5586cb..0e42d619 100644
--- a/mitmproxy/protocol/http2.py
+++ b/mitmproxy/protocol/http2.py
@@ -3,6 +3,7 @@ from __future__ import absolute_import, print_function, division
import threading
import time
import traceback
+import functools
import h2.exceptions
import six
@@ -54,21 +55,18 @@ class SafeH2Connection(connection.H2Connection):
self.update_settings(new_settings)
self.conn.send(self.data_to_send())
- def safe_send_headers(self, is_zombie, stream_id, headers, **kwargs):
+ def safe_send_headers(self, raise_zombie, stream_id, headers, **kwargs):
with self.lock:
- if is_zombie(): # pragma: no cover
- raise exceptions.Http2ProtocolException("Zombie Stream")
+ raise_zombie()
self.send_headers(stream_id, headers.fields, **kwargs)
self.conn.send(self.data_to_send())
- def safe_send_body(self, is_zombie, stream_id, chunks):
+ def safe_send_body(self, raise_zombie, stream_id, chunks):
for chunk in chunks:
position = 0
while position < len(chunk):
self.lock.acquire()
- if is_zombie(): # pragma: no cover
- self.lock.release()
- raise exceptions.Http2ProtocolException("Zombie Stream")
+ raise_zombie(self.lock.release)
max_outbound_frame_size = self.max_outbound_frame_size
frame_chunk = chunk[position:position + max_outbound_frame_size]
if self.local_flow_control_window(stream_id) < len(frame_chunk):
@@ -84,8 +82,7 @@ class SafeH2Connection(connection.H2Connection):
self.lock.release()
position += max_outbound_frame_size
with self.lock:
- if is_zombie(): # pragma: no cover
- raise exceptions.Http2ProtocolException("Zombie Stream")
+ raise_zombie()
self.end_stream(stream_id)
self.conn.send(self.data_to_send())
@@ -344,6 +341,17 @@ class Http2Layer(base.Layer):
self._kill_all_streams()
+def detect_zombie_stream(func):
+ @functools.wraps(func)
+ def wrapper(self, *args, **kwargs):
+ self.raise_zombie()
+ result = func(self, *args, **kwargs)
+ self.raise_zombie()
+ return result
+
+ return wrapper
+
+
class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread):
def __init__(self, ctx, stream_id, request_headers):
@@ -412,15 +420,16 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread)
def queued_data_length(self, v):
self.request_queued_data_length = v
- def is_zombie(self):
- return self.zombie is not None
+ def raise_zombie(self, pre_command=None):
+ if self.zombie is not None:
+ if pre_command is not None:
+ pre_command()
+ raise exceptions.Http2ProtocolException("Zombie Stream")
+ @detect_zombie_stream
def read_request(self):
self.request_data_finished.wait()
- if self.zombie: # pragma: no cover
- raise exceptions.Http2ProtocolException("Zombie Stream")
-
data = []
while self.request_data_queue.qsize() > 0:
data.append(self.request_data_queue.get())
@@ -445,15 +454,14 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread)
def read_request_body(self, request): # pragma: no cover
raise NotImplementedError()
+ @detect_zombie_stream
def send_request(self, message):
if self.pushed:
# nothing to do here
return
while True:
- if self.zombie: # pragma: no cover
- raise exceptions.Http2ProtocolException("Zombie Stream")
-
+ self.raise_zombie()
self.server_conn.h2.lock.acquire()
max_streams = self.server_conn.h2.remote_settings.max_concurrent_streams
@@ -467,8 +475,7 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread)
break
# We must not assign a stream id if we are already a zombie.
- if self.zombie: # pragma: no cover
- raise exceptions.Http2ProtocolException("Zombie Stream")
+ self.raise_zombie()
self.server_stream_id = self.server_conn.h2.get_next_available_stream_id()
self.server_to_client_stream_ids[self.server_stream_id] = self.client_stream_id
@@ -490,7 +497,7 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread)
try:
self.server_conn.h2.safe_send_headers(
- self.is_zombie,
+ self.raise_zombie,
self.server_stream_id,
headers,
end_stream=self.no_body,
@@ -505,19 +512,16 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread)
if not self.no_body:
self.server_conn.h2.safe_send_body(
- self.is_zombie,
+ self.raise_zombie,
self.server_stream_id,
[message.body]
)
- if self.zombie: # pragma: no cover
- raise exceptions.Http2ProtocolException("Zombie Stream")
-
+ @detect_zombie_stream
def read_response_headers(self):
self.response_arrived.wait()
- if self.zombie: # pragma: no cover
- raise exceptions.Http2ProtocolException("Zombie Stream")
+ self.raise_zombie()
status_code = int(self.response_headers.get(':status', 502))
headers = self.response_headers.copy()
@@ -533,6 +537,7 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread)
timestamp_end=self.timestamp_end,
)
+ @detect_zombie_stream
def read_response_body(self, request, response):
while True:
try:
@@ -540,14 +545,13 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread)
except queue.Empty: # pragma: no cover
pass
if self.response_data_finished.is_set():
- if self.zombie: # pragma: no cover
- raise exceptions.Http2ProtocolException("Zombie Stream")
+ self.raise_zombie()
while self.response_data_queue.qsize() > 0:
yield self.response_data_queue.get()
break
- if self.zombie: # pragma: no cover
- raise exceptions.Http2ProtocolException("Zombie Stream")
+ self.raise_zombie()
+ @detect_zombie_stream
def send_response_headers(self, response):
headers = response.headers.copy()
headers.insert(0, ":status", str(response.status_code))
@@ -556,21 +560,21 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread)
del headers[forbidden_header]
with self.client_conn.h2.lock:
self.client_conn.h2.safe_send_headers(
- self.is_zombie,
+ self.raise_zombie,
self.client_stream_id,
headers
)
- if self.zombie: # pragma: no cover
- raise exceptions.Http2ProtocolException("Zombie Stream")
+ @detect_zombie_stream
def send_response_body(self, _response, chunks):
self.client_conn.h2.safe_send_body(
- self.is_zombie,
+ self.raise_zombie,
self.client_stream_id,
chunks
)
- if self.zombie: # pragma: no cover
- raise exceptions.Http2ProtocolException("Zombie Stream")
+
+ def __call__(self):
+ raise EnvironmentError('Http2SingleStreamLayer must be run as thread')
def run(self):
layer = http.HttpLayer(self, self.mode)
diff --git a/mitmproxy/protocol/tls.py b/mitmproxy/protocol/tls.py
index d08e2e32..e41a9af0 100644
--- a/mitmproxy/protocol/tls.py
+++ b/mitmproxy/protocol/tls.py
@@ -369,8 +369,10 @@ class TlsLayer(base.Layer):
not self.config.options.no_upstream_cert and
(
self.config.options.add_upstream_certs_to_client_chain or
- self._client_hello.alpn_protocols or
- not self._client_hello.sni
+ self._client_tls and (
+ self._client_hello.alpn_protocols or
+ not self._client_hello.sni
+ )
)
)
establish_server_tls_now = (
@@ -434,7 +436,7 @@ class TlsLayer(base.Layer):
if self._custom_server_sni is False:
return None
else:
- return self._custom_server_sni or self._client_hello.sni
+ return self._custom_server_sni or self._client_hello and self._client_hello.sni
@property
def alpn_for_client_connection(self):
@@ -509,21 +511,18 @@ class TlsLayer(base.Layer):
def _establish_tls_with_server(self):
self.log("Establish TLS with server", "debug")
try:
- # We only support http/1.1 and h2.
- # If the server only supports spdy (next to http/1.1), it may select that
- # and mitmproxy would enter TCP passthrough mode, which we want to avoid.
- def deprecated_http2_variant(x):
- return x.startswith(b"h2-") or x.startswith(b"spdy")
-
- if self._client_hello.alpn_protocols:
- alpn = [x for x in self._client_hello.alpn_protocols if not deprecated_http2_variant(x)]
- else:
- alpn = None
- if alpn and b"h2" in alpn and not self.config.options.http2:
- alpn.remove(b"h2")
+ alpn = None
+ if self._client_tls:
+ if self._client_hello.alpn_protocols:
+ # We only support http/1.1 and h2.
+ # If the server only supports spdy (next to http/1.1), it may select that
+ # and mitmproxy would enter TCP passthrough mode, which we want to avoid.
+ alpn = [x for x in self._client_hello.alpn_protocols if not (x.startswith(b"h2-") or x.startswith(b"spdy"))]
+ if alpn and b"h2" in alpn and not self.config.options.http2:
+ alpn.remove(b"h2")
ciphers_server = self.config.options.ciphers_server
- if not ciphers_server:
+ if not ciphers_server and self._client_tls:
ciphers_server = []
for id in self._client_hello.cipher_suites:
if id in CIPHER_ID_NAME_MAP.keys():
@@ -562,7 +561,8 @@ class TlsLayer(base.Layer):
sys.exc_info()[2]
)
- self.log("ALPN selected by server: %s" % self.alpn_for_client_connection, "debug")
+ proto = self.alpn_for_client_connection.decode() if self.alpn_for_client_connection else '-'
+ self.log("ALPN selected by server: {}".format(proto), "debug")
def _find_cert(self):
"""
diff --git a/mitmproxy/proxy/config.py b/mitmproxy/proxy/config.py
index 26f6a236..2cf8410a 100644
--- a/mitmproxy/proxy/config.py
+++ b/mitmproxy/proxy/config.py
@@ -188,4 +188,4 @@ class ProxyConfig:
self.authenticator = authentication.BasicProxyAuth(
password_manager,
"mitmproxy"
- ) \ No newline at end of file
+ )
diff --git a/test/mitmproxy/test_protocol_http2.py b/test/mitmproxy/test_protocol_http2.py
index f0fa9a40..873c89c3 100644
--- a/test/mitmproxy/test_protocol_http2.py
+++ b/test/mitmproxy/test_protocol_http2.py
@@ -849,15 +849,15 @@ class TestMaxConcurrentStreams(_Http2Test):
def test_max_concurrent_streams(self):
client, h2_conn = self._setup_connection()
new_streams = [1, 3, 5, 7, 9, 11]
- for id in new_streams:
+ for stream_id in new_streams:
# this will exceed MAX_CONCURRENT_STREAMS on the server connection
# and cause mitmproxy to throttle stream creation to the server
- self._send_request(client.wfile, h2_conn, stream_id=id, headers=[
+ self._send_request(client.wfile, h2_conn, stream_id=stream_id, headers=[
(':authority', "127.0.0.1:{}".format(self.server.server.address.port)),
(':method', 'GET'),
(':scheme', 'https'),
(':path', '/'),
- ('X-Stream-ID', str(id)),
+ ('X-Stream-ID', str(stream_id)),
])
ended_streams = 0
diff --git a/test/mitmproxy/test_proxy.py b/test/mitmproxy/test_proxy.py
index 84838018..f7c64e50 100644
--- a/test/mitmproxy/test_proxy.py
+++ b/test/mitmproxy/test_proxy.py
@@ -85,22 +85,22 @@ class TestProcessProxyOptions:
@mock.patch("mitmproxy.platform.resolver")
def test_modes(self, _):
- # self.assert_noerr("-R", "http://localhost")
- # self.assert_err("expected one argument", "-R")
- # self.assert_err("Invalid server specification", "-R", "reverse")
- #
- # self.assert_noerr("-T")
- #
- # self.assert_noerr("-U", "http://localhost")
- # self.assert_err("expected one argument", "-U")
- # self.assert_err("Invalid server specification", "-U", "upstream")
- #
- # self.assert_noerr("--upstream-auth", "test:test")
- # self.assert_err("expected one argument", "--upstream-auth")
+ self.assert_noerr("-R", "http://localhost")
+ self.assert_err("expected one argument", "-R")
+ self.assert_err("Invalid server specification", "-R", "reverse")
+
+ self.assert_noerr("-T")
+
+ self.assert_noerr("-U", "http://localhost")
+ self.assert_err("expected one argument", "-U")
+ self.assert_err("Invalid server specification", "-U", "upstream")
+
+ self.assert_noerr("--upstream-auth", "test:test")
+ self.assert_err("expected one argument", "--upstream-auth")
self.assert_err(
"Invalid upstream auth specification", "--upstream-auth", "test"
)
- # self.assert_err("mutually exclusive", "-R", "http://localhost", "-T")
+ self.assert_err("mutually exclusive", "-R", "http://localhost", "-T")
def test_socks_auth(self):
self.assert_err(
diff --git a/test/mitmproxy/test_server.py b/test/mitmproxy/test_server.py
index 78a97dc3..e0a8da47 100644
--- a/test/mitmproxy/test_server.py
+++ b/test/mitmproxy/test_server.py
@@ -472,6 +472,11 @@ class TestReverse(tservers.ReverseProxyTest, CommonMixin, TcpMixin):
reverse = True
+class TestReverseSSL(tservers.ReverseProxyTest, CommonMixin, TcpMixin):
+ reverse = True
+ ssl = True
+
+
class TestSocks5(tservers.SocksModeTest):
def test_simple(self):
diff --git a/web/src/js/__tests__/ducks/ui/flowSpec.js b/web/src/js/__tests__/ducks/ui/flowSpec.js
new file mode 100644
index 00000000..f838fbaa
--- /dev/null
+++ b/web/src/js/__tests__/ducks/ui/flowSpec.js
@@ -0,0 +1,76 @@
+jest.unmock('../../../ducks/ui/flow')
+jest.unmock('../../../ducks/flows')
+jest.unmock('lodash')
+
+import _ from 'lodash'
+import reducer, {
+ startEdit,
+ setContentViewDescription,
+ setShowFullContent,
+ setContent,
+ updateEdit
+ } from '../../../ducks/ui/flow'
+
+import { select, updateFlow } from '../../../ducks/flows'
+
+describe('flow reducer', () => {
+ it('should change to edit mode', () => {
+ let testFlow = {flow : 'foo'}
+ const newState = reducer(undefined, startEdit({ flow: 'foo' }))
+ expect(newState.contentView).toEqual('Edit')
+ expect(newState.modifiedFlow).toEqual(testFlow)
+ expect(newState.showFullContent).toEqual(true)
+ })
+ it('should set the view description', () => {
+ expect(reducer(undefined, setContentViewDescription('description')).viewDescription)
+ .toEqual('description')
+ })
+
+ it('should set show full content', () => {
+ expect(reducer({showFullContent: false}, setShowFullContent()).showFullContent)
+ .toBeTruthy()
+ })
+
+ it('should set showFullContent to true', () => {
+ let maxLines = 10
+ let content = _.range(maxLines)
+ const newState = reducer({maxContentLines: maxLines}, setContent(content) )
+ expect(newState.showFullContent).toBeTruthy()
+ expect(newState.content).toEqual(content)
+ })
+
+ it('should set showFullContent to false', () => {
+ let maxLines = 5
+ let content = _.range(maxLines+1);
+ const newState = reducer({maxContentLines: maxLines}, setContent(_.range(maxLines+1)))
+ expect(newState.showFullContent).toBeFalsy()
+ expect(newState.content).toEqual(content)
+ })
+
+ it('should not change the contentview mode', () => {
+ expect(reducer({contentView: 'foo'}, select(1)).contentView).toEqual('foo')
+ })
+
+ it('should change the contentview mode to auto after editing when a new flow will be selected', () => {
+ expect(reducer({contentView: 'foo', modifiedFlow : 'test_flow'}, select(1)).contentView).toEqual('Auto')
+ })
+
+ it('should set update and merge the modifiedflow with the update values', () => {
+ let modifiedFlow = {headers: []}
+ let updateValues = {content: 'bar'}
+ let result = {headers: [], content: 'bar'}
+ expect(reducer({modifiedFlow}, updateEdit(updateValues)).modifiedFlow).toEqual(result)
+ })
+
+ it('should not change the state when a flow is updated which is not selected', () => {
+ let modifiedFlow = {id: 1}
+ let updatedFlow = {id: 0}
+ expect(reducer({modifiedFlow}, updateFlow(updatedFlow)).modifiedFlow).toEqual(modifiedFlow)
+ })
+
+ it('should stop editing when the selected flow is updated', () => {
+ let modifiedFlow = {id: 1}
+ let updatedFlow = {id: 1}
+ expect(reducer({modifiedFlow}, updateFlow(updatedFlow)).modifiedFlow).toBeFalsy()
+ })
+})
diff --git a/web/src/js/__tests__/ducks/utils/listSpec.js b/web/src/js/__tests__/ducks/utils/listSpec.js
index 72d162f2..0f5d0f34 100644
--- a/web/src/js/__tests__/ducks/utils/listSpec.js
+++ b/web/src/js/__tests__/ducks/utils/listSpec.js
@@ -2,6 +2,7 @@ jest.unmock('lodash')
jest.unmock('../../../ducks/utils/list')
import reduce, * as list from '../../../ducks/utils/list'
+import _ from 'lodash'
describe('list reduce', () => {
diff --git a/web/src/js/components/ContentView/ShowFullContentButton.jsx b/web/src/js/components/ContentView/ShowFullContentButton.jsx
index 676068e9..cfd96dd8 100644
--- a/web/src/js/components/ContentView/ShowFullContentButton.jsx
+++ b/web/src/js/components/ContentView/ShowFullContentButton.jsx
@@ -16,7 +16,7 @@ function ShowFullContentButton ( {setShowFullContent, showFullContent, visibleLi
return (
!showFullContent &&
<div>
- <Button className="view-all-content-btn btn-xs" onClick={() => setShowFullContent(true)} text="Show full content"/>
+ <Button className="view-all-content-btn btn-xs" onClick={() => setShowFullContent()} text="Show full content"/>
<span className="pull-right"> {visibleLines}/{contentLines} are visible &nbsp; </span>
</div>
)
diff --git a/web/src/js/ducks/ui/flow.js b/web/src/js/ducks/ui/flow.js
index e65c39a3..4a6d64cd 100644
--- a/web/src/js/ducks/ui/flow.js
+++ b/web/src/js/ducks/ui/flow.js
@@ -16,7 +16,7 @@ export const SET_CONTENT_VIEW = 'UI_FLOWVIEW_SET_CONTENT_VIEW',
const defaultState = {
displayLarge: false,
- contentViewDescription: '',
+ viewDescription: '',
showFullContent: false,
modifiedFlow: false,
contentView: 'Auto',
@@ -27,6 +27,10 @@ const defaultState = {
export default function reducer(state = defaultState, action) {
let wasInEditMode = !!(state.modifiedFlow)
+
+ let content = action.content || state.content
+ let isFullContentShown = content && content.length <= state.maxContentLines
+
switch (action.type) {
case START_EDIT:
@@ -49,8 +53,7 @@ export default function reducer(state = defaultState, action) {
modifiedFlow: false,
displayLarge: false,
contentView: (wasInEditMode ? 'Auto' : state.contentView),
- viewDescription: '',
- showFullContent: false,
+ showFullContent: isFullContentShown,
}
case flowsActions.UPDATE:
@@ -63,7 +66,6 @@ export default function reducer(state = defaultState, action) {
modifiedFlow: false,
displayLarge: false,
contentView: (wasInEditMode ? 'Auto' : state.contentView),
- viewDescription: '',
showFullContent: false
}
} else {
@@ -79,7 +81,7 @@ export default function reducer(state = defaultState, action) {
case SET_SHOW_FULL_CONTENT:
return {
...state,
- showFullContent: action.show
+ showFullContent: true
}
case SET_TAB:
@@ -98,7 +100,6 @@ export default function reducer(state = defaultState, action) {
}
case SET_CONTENT:
- let isFullContentShown = action.content.length < state.maxContentLines
return {
...state,
content: action.content,
@@ -139,12 +140,8 @@ export function setContentViewDescription(description) {
return { type: SET_CONTENT_VIEW_DESCRIPTION, description }
}
-export function setShowFullContent(show) {
- return { type: SET_SHOW_FULL_CONTENT, show }
-}
-
-export function updateEdit(update) {
- return { type: UPDATE_EDIT, update }
+export function setShowFullContent() {
+ return { type: SET_SHOW_FULL_CONTENT }
}
export function setContent(content){