Merge branch 'master' of github.com:mitmproxy/mitmproxy into list

Conflicts:
	mitmproxy/web/static/app.js
	web/src/js/app.jsx
	web/src/js/connection.js
	web/src/js/ducks/websocket.js
This commit is contained in:
Jason 2016-06-24 21:04:39 +08:00
commit 5a1677c387
13 changed files with 239 additions and 128 deletions

View File

@ -11,8 +11,10 @@ Installation On Ubuntu
Ubuntu comes with Python but we need to install pip, python-dev and several libraries. Ubuntu comes with Python but we need to install pip, python-dev and several libraries.
This was tested on a fully patched installation of Ubuntu 14.04. This was tested on a fully patched installation of Ubuntu 14.04.
>>> sudo apt-get install python-pip python-dev libffi-dev libssl-dev libxml2-dev libxslt1-dev libjpeg8-dev zlib1g-dev .. code:: bash
>>> sudo pip install mitmproxy
sudo apt-get install python-pip python-dev libffi-dev libssl-dev libxml2-dev libxslt1-dev libjpeg8-dev zlib1g-dev
sudo pip install mitmproxy # or pip install --user mitmproxy
Once installation is complete you can run :ref:`mitmproxy` or :ref:`mitmdump` from a terminal. Once installation is complete you can run :ref:`mitmproxy` or :ref:`mitmdump` from a terminal.
@ -27,6 +29,20 @@ get set up to contribute to the project, install the dependencies as you would f
mitmproxy installation (see :ref:`install-ubuntu`). mitmproxy installation (see :ref:`install-ubuntu`).
Then see the Hacking_ section of the README on GitHub. Then see the Hacking_ section of the README on GitHub.
.. _install-fedora:
Installation On Fedora
----------------------
Fedora comes with Python but we need to install pip, python-dev and several libraries.
This was tested on a fully patched installation of Fedora 23.
.. code:: bash
sudo dnf install -y python-pip python-devel libffi-devel openssl-devel libxml2-devel libxslt-devel libpng-devel libjpeg-devel
sudo pip install mitmproxy # or pip install --user mitmproxy
Once installation is complete you can run :ref:`mitmproxy` or :ref:`mitmdump` from a terminal.
.. _install-arch: .. _install-arch:
@ -87,7 +103,9 @@ Installation On Windows
First, install the latest version of Python 2.7 from the `Python website`_. First, install the latest version of Python 2.7 from the `Python website`_.
If you already have an older version of Python 2.7 installed, make sure to install pip_ If you already have an older version of Python 2.7 installed, make sure to install pip_
(pip is included in Python 2.7.9+ by default). (pip is included in Python 2.7.9+ by default). If pip aborts with an error, make sure you are using the current version of pip.
>>> python -m pip install --upgrade pip
Next, add Python and the Python Scripts directory to your **PATH** variable. Next, add Python and the Python Scripts directory to your **PATH** variable.
You can do this easily by running the following in powershell: You can do this easily by running the following in powershell:

View File

@ -8,6 +8,7 @@ def lookup(address, port, s):
Returns an (address, port) tuple, or None. Returns an (address, port) tuple, or None.
""" """
s = s.decode()
spec = "%s:%s" % (address, port) spec = "%s:%s" % (address, port)
for i in s.split("\n"): for i in s.split("\n"):
if "ESTABLISHED:ESTABLISHED" in i and spec in i: if "ESTABLISHED:ESTABLISHED" in i and spec in i:

View File

@ -199,7 +199,7 @@ def process_proxy_options(parser, options):
password_manager = authentication.PassManHtpasswd( password_manager = authentication.PassManHtpasswd(
options.auth_htpasswd) options.auth_htpasswd)
except ValueError as v: except ValueError as v:
return parser.error(v.message) return parser.error(v)
authenticator = authentication.BasicProxyAuth(password_manager, "mitmproxy") authenticator = authentication.BasicProxyAuth(password_manager, "mitmproxy")
else: else:
authenticator = authentication.NullProxyAuth(None) authenticator = authentication.NullProxyAuth(None)

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
import codecs import codecs
import hyperframe import hyperframe
from ...exceptions import HttpException
def http2_read_raw_frame(rfile): def http2_read_raw_frame(rfile):
@ -8,7 +9,7 @@ def http2_read_raw_frame(rfile):
length = int(codecs.encode(header[:3], 'hex_codec'), 16) length = int(codecs.encode(header[:3], 'hex_codec'), 16)
if length == 4740180: if length == 4740180:
raise ValueError("Length field looks more like HTTP/1.1: %s" % rfile.peek(20)) raise HttpException("Length field looks more like HTTP/1.1:\n{}".format(rfile.read(-1)))
body = rfile.safe_read(length) body = rfile.safe_read(length)
return [header, body] return [header, body]

View File

@ -18,14 +18,14 @@ class TestInvalidRequests(tservers.HTTPProxyTest):
p = self.pathoc() p = self.pathoc()
r = p.request("connect:'%s:%s'" % ("127.0.0.1", self.server2.port)) r = p.request("connect:'%s:%s'" % ("127.0.0.1", self.server2.port))
assert r.status_code == 400 assert r.status_code == 400
assert "Invalid HTTP request form" in r.content assert b"Invalid HTTP request form" in r.content
def test_relative_request(self): def test_relative_request(self):
p = self.pathoc_raw() p = self.pathoc_raw()
p.connect() p.connect()
r = p.request("get:/p/200") r = p.request("get:/p/200")
assert r.status_code == 400 assert r.status_code == 400
assert "Invalid HTTP request form" in r.content assert b"Invalid HTTP request form" in r.content
class TestExpectHeader(tservers.HTTPProxyTest): class TestExpectHeader(tservers.HTTPProxyTest):
@ -43,8 +43,8 @@ class TestExpectHeader(tservers.HTTPProxyTest):
) )
client.wfile.flush() client.wfile.flush()
assert client.rfile.readline() == "HTTP/1.1 100 Continue\r\n" assert client.rfile.readline() == b"HTTP/1.1 100 Continue\r\n"
assert client.rfile.readline() == "\r\n" assert client.rfile.readline() == b"\r\n"
client.wfile.write(b"0123456789abcdef\r\n") client.wfile.write(b"0123456789abcdef\r\n")
client.wfile.flush() client.wfile.flush()

View File

@ -13,6 +13,7 @@ from mitmproxy.cmdline import APP_HOST, APP_PORT
import netlib import netlib
from ..netlib import tservers as netlib_tservers from ..netlib import tservers as netlib_tservers
from netlib.exceptions import HttpException
from netlib.http.http2 import framereader from netlib.http.http2 import framereader
from . import tservers from . import tservers
@ -50,6 +51,9 @@ class _Http2ServerBase(netlib_tservers.ServerTestBase):
try: try:
raw = b''.join(framereader.http2_read_raw_frame(self.rfile)) raw = b''.join(framereader.http2_read_raw_frame(self.rfile))
events = h2_conn.receive_data(raw) events = h2_conn.receive_data(raw)
except HttpException:
print(traceback.format_exc())
assert False
except: except:
break break
self.wfile.write(h2_conn.data_to_send()) self.wfile.write(h2_conn.data_to_send())
@ -60,9 +64,7 @@ class _Http2ServerBase(netlib_tservers.ServerTestBase):
if not self.server.handle_server_event(event, h2_conn, self.rfile, self.wfile): if not self.server.handle_server_event(event, h2_conn, self.rfile, self.wfile):
done = True done = True
break break
except Exception as e: except:
print(repr(e))
print(traceback.format_exc())
done = True done = True
break break
@ -200,9 +202,12 @@ class TestSimple(_Http2TestBase, _Http2ServerBase):
done = False done = False
while not done: while not done:
try: try:
events = h2_conn.receive_data(b''.join(framereader.http2_read_raw_frame(client.rfile))) raw = b''.join(framereader.http2_read_raw_frame(client.rfile))
except: events = h2_conn.receive_data(raw)
break except HttpException:
print(traceback.format_exc())
assert False
client.wfile.write(h2_conn.data_to_send()) client.wfile.write(h2_conn.data_to_send())
client.wfile.flush() client.wfile.flush()
@ -270,9 +275,12 @@ class TestWithBodies(_Http2TestBase, _Http2ServerBase):
done = False done = False
while not done: while not done:
try: try:
events = h2_conn.receive_data(b''.join(framereader.http2_read_raw_frame(client.rfile))) raw = b''.join(framereader.http2_read_raw_frame(client.rfile))
except: events = h2_conn.receive_data(raw)
break except HttpException:
print(traceback.format_exc())
assert False
client.wfile.write(h2_conn.data_to_send()) client.wfile.write(h2_conn.data_to_send())
client.wfile.flush() client.wfile.flush()
@ -364,6 +372,9 @@ class TestPushPromise(_Http2TestBase, _Http2ServerBase):
try: try:
raw = b''.join(framereader.http2_read_raw_frame(client.rfile)) raw = b''.join(framereader.http2_read_raw_frame(client.rfile))
events = h2_conn.receive_data(raw) events = h2_conn.receive_data(raw)
except HttpException:
print(traceback.format_exc())
assert False
except: except:
break break
client.wfile.write(h2_conn.data_to_send()) client.wfile.write(h2_conn.data_to_send())
@ -412,9 +423,12 @@ class TestPushPromise(_Http2TestBase, _Http2ServerBase):
responses = 0 responses = 0
while not done: while not done:
try: try:
events = h2_conn.receive_data(b''.join(framereader.http2_read_raw_frame(client.rfile))) raw = b''.join(framereader.http2_read_raw_frame(client.rfile))
except: events = h2_conn.receive_data(raw)
break except HttpException:
print(traceback.format_exc())
assert False
client.wfile.write(h2_conn.data_to_send()) client.wfile.write(h2_conn.data_to_send())
client.wfile.flush() client.wfile.flush()
@ -481,6 +495,9 @@ class TestConnectionLost(_Http2TestBase, _Http2ServerBase):
try: try:
raw = b''.join(framereader.http2_read_raw_frame(client.rfile)) raw = b''.join(framereader.http2_read_raw_frame(client.rfile))
h2_conn.receive_data(raw) h2_conn.receive_data(raw)
except HttpException:
print(traceback.format_exc())
assert False
except: except:
break break
try: try:

View File

@ -7,7 +7,7 @@ deps =
codecov>=2.0.5 codecov>=2.0.5
passenv = CI TRAVIS_BUILD_ID TRAVIS TRAVIS_BRANCH TRAVIS_JOB_NUMBER TRAVIS_PULL_REQUEST TRAVIS_JOB_ID TRAVIS_REPO_SLUG TRAVIS_COMMIT passenv = CI TRAVIS_BUILD_ID TRAVIS TRAVIS_BRANCH TRAVIS_JOB_NUMBER TRAVIS_PULL_REQUEST TRAVIS_JOB_ID TRAVIS_REPO_SLUG TRAVIS_COMMIT
setenv = setenv =
PY3TESTS = test/netlib test/pathod/ test/mitmproxy/script test/mitmproxy/test_contentview.py test/mitmproxy/test_custom_contentview.py test/mitmproxy/test_app.py test/mitmproxy/test_controller.py test/mitmproxy/test_fuzzing.py test/mitmproxy/test_script.py test/mitmproxy/test_web_app.py test/mitmproxy/test_utils.py test/mitmproxy/test_stateobject.py test/mitmproxy/test_cmdline.py test/mitmproxy/test_contrib_tnetstring.py PY3TESTS = test/netlib test/pathod/ test/mitmproxy/script test/mitmproxy/test_contentview.py test/mitmproxy/test_custom_contentview.py test/mitmproxy/test_app.py test/mitmproxy/test_controller.py test/mitmproxy/test_fuzzing.py test/mitmproxy/test_script.py test/mitmproxy/test_web_app.py test/mitmproxy/test_utils.py test/mitmproxy/test_stateobject.py test/mitmproxy/test_cmdline.py test/mitmproxy/test_contrib_tnetstring.py test/mitmproxy/test_proxy.py test/mitmproxy/test_protocol_http1.py test/mitmproxy/test_platform_pf.py
[testenv:py27] [testenv:py27]
commands = commands =

View File

@ -1,16 +1,15 @@
import React from "react" import React from 'react'
import { render } from 'react-dom' import { render } from 'react-dom'
import { applyMiddleware, createStore } from 'redux' import { applyMiddleware, createStore } from 'redux'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import createLogger from 'redux-logger' import createLogger from 'redux-logger'
import thunkMiddleware from 'redux-thunk' import thunkMiddleware from 'redux-thunk'
import { Route, Router as ReactRouter, hashHistory, Redirect } from "react-router" import { Route, Router as ReactRouter, hashHistory, Redirect } from 'react-router'
import Connection from "./connection" import ProxyApp from './components/ProxyApp'
import ProxyApp from "./components/ProxyApp"
import MainView from './components/MainView' import MainView from './components/MainView'
import rootReducer from './ducks/index' import rootReducer from './ducks/index'
import { add as addLog } from "./ducks/eventLog" import { add as addLog } from './ducks/eventLog'
// logger must be last // logger must be last
const store = createStore( const store = createStore(
@ -18,14 +17,13 @@ const store = createStore(
applyMiddleware(thunkMiddleware, createLogger()) applyMiddleware(thunkMiddleware, createLogger())
) )
// @todo move to ProxyApp
window.addEventListener('error', msg => { window.addEventListener('error', msg => {
store.dispatch(addLog(msg)) store.dispatch(addLog(msg))
}) })
// @todo remove this // @todo remove this
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
window.ws = new Connection("/updates", store.dispatch)
render( render(
<Provider store={store}> <Provider store={store}>
<ReactRouter history={hashHistory}> <ReactRouter history={hashHistory}>

View File

@ -3,6 +3,7 @@ import ReactDOM from 'react-dom'
import _ from 'lodash' import _ from 'lodash'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { init as appInit, destruct as appDestruct } from '../ducks/app'
import Header from './Header' import Header from './Header'
import EventLog from './EventLog' import EventLog from './EventLog'
import Footer from './Footer' import Footer from './Footer'
@ -26,27 +27,8 @@ class ProxyAppMain extends Component {
this.updateLocation = this.updateLocation.bind(this) this.updateLocation = this.updateLocation.bind(this)
} }
/** componentWillMount() {
* @todo move to actions this.props.appInit()
*/
updateLocation(pathname, queryUpdate) {
if (pathname === undefined) {
pathname = this.props.location.pathname
}
const query = this.props.location.query
for (const key of Object.keys(queryUpdate || {})) {
query[key] = queryUpdate[key] || undefined
}
this.context.router.replace({ pathname, query })
}
/**
* @todo pass in with props
*/
getQuery() {
// For whatever reason, react-router always returns the same object, which makes comparing
// the current props with nextProps impossible. As a workaround, we just clone the query object.
return _.clone(this.props.location.query)
} }
/** /**
@ -56,6 +38,10 @@ class ProxyAppMain extends Component {
this.focus() this.focus()
} }
componentWillUnmount() {
this.props.appDestruct()
}
/** /**
* @todo use props * @todo use props
*/ */
@ -110,6 +96,29 @@ class ProxyAppMain extends Component {
e.preventDefault() e.preventDefault()
} }
/**
* @todo move to actions
*/
updateLocation(pathname, queryUpdate) {
if (pathname === undefined) {
pathname = this.props.location.pathname
}
const query = this.props.location.query
for (const key of Object.keys(queryUpdate || {})) {
query[key] = queryUpdate[key] || undefined
}
this.context.router.replace({ pathname, query })
}
/**
* @todo pass in with props
*/
getQuery() {
// For whatever reason, react-router always returns the same object, which makes comparing
// the current props with nextProps impossible. As a workaround, we just clone the query object.
return _.clone(this.props.location.query)
}
render() { render() {
const { showEventLog, location, children } = this.props const { showEventLog, location, children } = this.props
const query = this.getQuery() const query = this.getQuery()
@ -132,5 +141,10 @@ class ProxyAppMain extends Component {
export default connect( export default connect(
state => ({ state => ({
showEventLog: state.eventLog.visible, showEventLog: state.eventLog.visible,
}) settings: state.settings.settings,
}),
{
appInit,
appDestruct,
}
)(ProxyAppMain) )(ProxyAppMain)

View File

@ -1,49 +0,0 @@
import {ConnectionActions} from "./actions.js";
import {AppDispatcher} from "./dispatcher.js";
import * as webSocketActions from "./ducks/websocket"
import * as eventLogActions from "./ducks/eventLog"
import * as flowActions from "./ducks/flows"
import * as settingsActions from './ducks/settings'
export default function Connection(url, dispatch) {
if (url[0] === "/") {
url = location.origin.replace("http", "ws") + url;
}
var ws = new WebSocket(url);
ws.onopen = function () {
dispatch(webSocketActions.connected())
dispatch(settingsActions.fetchSettings())
dispatch(eventLogActions.fetchData())
dispatch(flowActions.fetchData())
// workaround to make sure that our state is already available.
.then(() => {
console.log("flows are loaded now")
ConnectionActions.open()
})
};
ws.onmessage = function (m) {
var message = JSON.parse(m.data);
AppDispatcher.dispatchServerAction(message);
switch (message.type) {
case eventLogActions.WS_MSG_TYPE:
return dispatch(eventLogActions.handleWsMsg(message))
case flowActions.WS_MSG_TYPE:
return dispatch(flowActions.handleWsMsg(message))
case settingsActions.UPDATE_SETTINGS:
return dispatch(settingsActions.handleWsMsg(message))
default:
console.warn("unknown message", message)
}
};
ws.onerror = function () {
ConnectionActions.error();
dispatch(eventLogActions.add("WebSocket connection error."));
};
ws.onclose = function () {
ConnectionActions.close();
dispatch(eventLogActions.add("WebSocket connection closed."));
dispatch(webSocketActions.disconnected());
};
return ws;
}

27
web/src/js/ducks/app.js Normal file
View File

@ -0,0 +1,27 @@
import { connect as wsConnect, disconnect as wsDisconnect } from './websocket'
export const INIT = 'APP_INIT'
const defaultState = {}
export function reduce(state = defaultState, action) {
switch (action.type) {
default:
return state
}
}
export function init() {
return dispatch => {
dispatch(wsConnect())
dispatch({ type: INIT })
}
}
export function destruct() {
return dispatch => {
dispatch(wsDisconnect())
dispatch({ type: DESTRUCT })
}
}

View File

@ -1,34 +1,118 @@
const CONNECTED = 'WEBSOCKET_CONNECTED' import { ConnectionActions } from '../actions.js'
const DISCONNECTED = 'WEBSOCKET_DISCONNECTED' import { AppDispatcher } from '../dispatcher.js'
import * as eventLogActions from './eventLog'
import * as flowsActions from './flows'
import * as settingsActions from './settings'
export const CMD_ADD = 'add' export const CMD_ADD = 'add'
export const CMD_UPDATE = 'update' export const CMD_UPDATE = 'update'
export const CMD_REMOVE = 'remove' export const CMD_REMOVE = 'remove'
export const CMD_RESET = 'reset' export const CMD_RESET = 'reset'
const defaultState = { export const SYM_SOCKET = Symbol('WEBSOCKET_SYM_SOCKET')
connected: false,
/* we may want to have an error message attribute here at some point */ export const CONNECT = 'WEBSOCKET_CONNECT'
} export const CONNECTED = 'WEBSOCKET_CONNECTED'
export default function reducer(state = defaultState, action) { export const DISCONNECT = 'WEBSOCKET_DISCONNECT'
export const DISCONNECTED = 'WEBSOCKET_DISCONNECTED'
export const ERROR = 'WEBSOCKET_ERROR'
export const MESSAGE = 'WEBSOCKET_MESSAGE'
/* we may want to have an error message attribute here at some point */
const defaultState = { connected: false, socket: null }
export default function reduce(state = defaultState, action) {
switch (action.type) { switch (action.type) {
case CONNECT:
return { ...state, [SYM_SOCKET]: action.socket }
case CONNECTED: case CONNECTED:
return { return { ...state, connected: true }
connected: true
} case DISCONNECT:
return { ...state, connected: false }
case DISCONNECTED: case DISCONNECTED:
return { return { ...state, [SYM_SOCKET]: null, connected: false }
connected: false
}
default: default:
return state return state
} }
} }
export function connect() {
return dispatch => {
const socket = new WebSocket(location.origin.replace('http', 'ws') + '/updates')
export function connected() { // @todo remove this
return {type: CONNECTED} window.ws = socket
socket.addEventListener('open', () => dispatch(onConnect()))
socket.addEventListener('close', () => dispatch(onDisconnect()))
socket.addEventListener('message', msg => dispatch(onMessage(msg)))
socket.addEventListener('error', error => dispatch(onError(error)))
dispatch({ type: CONNECT, socket })
return socket
}
} }
export function disconnected() {
return {type: DISCONNECTED} export function disconnect() {
return (dispatch, getState) => {
getState().settings[SYM_SOCKET].close()
dispatch({ type: DISCONNECT })
}
}
export function onConnect() {
// workaround to make sure that our state is already available.
return dispatch => {
dispatch({ type: CONNECTED })
dispatch(settingsActions.fetchSettings())
dispatch(flowsActions.fetchFlows()).then(() => ConnectionActions.open())
}
}
export function onMessage(msg) {
return dispatch => {
const data = JSON.parse(msg.data)
AppDispatcher.dispatchServerAction(data)
switch (data.type) {
case eventLogActions.WS_MSG_TYPE:
return dispatch(eventLogActions.handleWsMsg(data))
case flowsActions.WS_MSG_TYPE:
return dispatch(flowsActions.handleWsMsg(data))
case settingsActions.UPDATE_SETTINGS:
return dispatch(settingsActions.handleWsMsg(data))
default:
console.warn('unknown message', data)
}
dispatch({ type: MESSAGE, msg })
}
}
export function onDisconnect() {
return dispatch => {
ConnectionActions.close()
dispatch(eventLogActions.addLogEntry('WebSocket connection closed.'))
dispatch({ type: DISCONNECTED })
}
}
export function onError(error) {
// @todo let event log subscribe WebSocketActions.ERROR
return dispatch => {
ConnectionActions.error()
dispatch(eventLogActions.addLogEntry('WebSocket connection error.'))
dispatch({ type: ERROR, error })
}
} }