Compare commits

...

18 Commits
v0.4 ... v0.5

Author SHA1 Message Date
Aldo Cortesi
46ec8f52e7 Prep for 0.5 release
- Update CHANGELOG and CONTRIBUTORS
- Bump version
- Include Apple Gamecenter highscore setting tutorial in docs
2011-06-27 16:38:00 +12:00
Aldo Cortesi
0a642f2441 Make the certificate wait time configurable.
Since OpenSSL doesn't let us set certificate start times in the past, the
client and proxy machine time must be synchronized, or the client might reject
the certificate. We can bodgy over small discrepancies by waiting a few seconds
after a new certificate is generated (i.e. the first time an SSL domain is contacted).

Make this a configurable option, and turn it off by default.
2011-06-27 16:10:17 +12:00
Aldo Cortesi
f004326855 Try not to hang when user views large request & response bodies
Two different strategies here:

    - Use a simple heuristic to detect if we're looking at XML data when indent
    mode is used. On non-XML data we can hang even on small documents.

    - Only view partial data for large bodies. At the moment the cutoff is
    100k. I might finetune this later.
2011-06-27 15:59:17 +12:00
Aldo Cortesi
2ae7808ca9 Don't redraw the screen more often than necessary. 2011-06-27 14:01:08 +12:00
Aldo Cortesi
b04d074341 Repair a problem that sometimes caused SSL connections to peg the CPU. 2011-06-23 17:00:55 +12:00
Aldo Cortesi
0d9e0eac9a Don't backup flows before replay.
This lets us revert to the original request, even after replaying an edit.
2011-06-23 14:47:34 +12:00
Aldo Cortesi
00929a51c0 Merge pull request #2 from zellux/master
Fix urwid version parsing error when it's something like 0.9.10-pre
2011-06-20 02:46:08 -07:00
Yuangxuan Wang
e56793f01e Fix urwid version parsing error when it's something like 0.9.10-pre 2011-06-20 16:18:55 +08:00
Aldo Cortesi
7d7803a4d9 Add a hideous kludge to fix not-yet-valid certificates.
- The OpenSSL x509 has no way to explicitly set the notBefore value on
certificates.

- If two systems have the same configured time, it's possible to return a
certificate before the validity start time has arrived.

- We "solve" this by waiting for one second when a certificate is first
generated before returning the cert. The alternative is to rewrite pretty much
all of our certificate generation, a thought too horrible to contemplate.
2011-06-11 15:16:16 +12:00
Aldo Cortesi
62f9864395 Merge branch 'master' of github.com:cortesi/mitmproxy 2011-06-02 10:45:17 +12:00
Aldo Cortesi
1de5209340 Add an "SSL exception" to the license.
This is to clarify that mitmproxy can be distributed with OpenSSL. It's unclear
whether this is really needed, but I've had at least one request for this, and
there's a precendent in other Open Source projects.
2011-06-02 10:43:11 +12:00
Aldo Cortesi
07110bbbf1 Anticache and refresh_server_playback options are applied before flows are loaded.
You can now use mitmdump to preview how these options work, by running mitmdump
against a set of saved flows, and viewing the output.
2011-05-15 12:23:34 +12:00
Aldo Cortesi
e285b17e3f Add -r option to mitmdump and mitmproxy.
This option reads a set of flows from a file. I've also regularized the
mitmdump and mitmproxy command-line signatures by removing mitmproxy's old way
of specifying flow loads through naked arguments.
2011-05-15 11:54:12 +12:00
Aldo Cortesi
613e9a298e Add a new flow loading mechanism.
We now simulate the normal connection flow when we load flows. That means
that we can run scripts, hooks, sticky cookies, etc.
2011-05-15 11:22:35 +12:00
Aldo Cortesi
6175d92583 Minor code cleanup - no need to recreate the master queue. 2011-05-14 12:12:03 +12:00
Aldo Cortesi
f89581be1b Add a -n option which tells the tools not to bind a proxy.
This is useful when you just want to inspect or process dumps.
2011-05-14 10:44:25 +12:00
Aldo Cortesi
c6075e1d93 Add the 30-second client replay tutorial to the docs.
It's verbatim from the blog post at the moment - I might edit it a bit before
pushing it to mitmproxy.org.
2011-03-31 11:28:58 +13:00
Aldo Cortesi
3906f06617 Changelog for 0.4. 2011-03-30 18:27:25 +13:00
26 changed files with 450 additions and 71 deletions

View File

@@ -1,3 +1,43 @@
27 June 2011: mitmproxy 0.5:
* An -n option to start the tools without binding to a proxy port.
* Allow scripts, hooks, sticky cookies etc. to run on flows loaded from
save files.
* Regularize command-line options for mitmproxy and mitmdump.
* Add an "SSL exception" to mitmproxy's license to remove possible
distribution issues.
* Add a --cert-wait-time option to make mitmproxy pause after a new SSL
certificate is generated. This can pave over small discrepancies in
system time between the client and server.
* Handle viewing big request and response bodies more elegantly. Only
render the first 100k of large documents, and try to avoid running the
XML indenter on non-XML data.
* BUGFIX: Make the "revert" keyboard shortcut in mitmproxy work after a
flow has been replayed.
* BUGFIX: Repair a problem that sometimes caused SSL connections to consume
100% of CPU.
30 March 2011: mitmproxy 0.4
* Full serialization of HTTP conversations
* Client and server replay
* On-the-fly generation of dummy SSL certificates
* mitmdump has "grown up" into a powerful tcpdump-like tool for HTTP/S
* Dozens of improvements to the mitmproxy console interface
* Python scripting hooks for programmatic modification of traffic
1 March 2010: mitmproxy 0.2
@@ -5,7 +45,7 @@
* Big speed and responsiveness improvements, thanks to Thomas Roth
* Support urwid 0.9.9
*
* Terminal beeping based on filter expressions
* Filter expressions for terminal beeps, limits, interceptions and sticky

View File

@@ -1,4 +1,5 @@
179 Aldo Cortesi
203 Aldo Cortesi
18 Henrik Nordstrom
13 Thomas Roth
1 Yuangxuan Wang
1 Henrik Nordström

16
LICENSE
View File

@@ -1,3 +1,19 @@
mitmproxy is licensed under the terms of the GNU General Public
License version 3, with the following addition:
In addition, as a special exception, the copyright holders give
permission to link the code of this program or portions of this
program with the OpenSSL project's "OpenSSL" library (or with modified
versions of it that use the same license as the "OpenSSL" library),
and distribute linked combinations including the two.
You must obey the GNU General Public License in all respects for all
of the code used other than "OpenSSL". If you modify file(s) provided
under this license, you may extend this exception to your version of
the file, but you are not obligated to do so. If you do not wish to do
so, delete this exception statement from your version.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007

View File

@@ -67,6 +67,13 @@ pre {
background-color: #dddddd;
}
.terminal {
color: #ffffff;
background: #000000;
}
.docindex, .docindex ul {
margin-top: 0.1em;
margin-bottom: 0;

View File

@@ -18,10 +18,8 @@
<li><a href="@!urlTo("library.html")!@">libmproxy</a></li>
<li>Tutorials</li>
<ul>
<li> Client replay: a 30 second example [coming soon] </li>
<li> Scripting: On-the-fly modifications to HTTP conversations [coming soon] </li>
<li> Sticky cookies [coming soon] </li>
<li> Breaking iPhone apps for fun and profit [coming soon] </li>
<li> <a href="@!urlTo("tutorials/30second.html")!@">Client replay: a 30 second example</a> </li>
<li> <a href="@!urlTo("tutorials/gamecenter.html")!@">Setting highscores on Apple's GameCenter</a> </li>
</ul>
<li><a href="@!urlTo("faq.html")!@">FAQ</a></li>
<li><a href="@!urlTo("admin.html")!@">Administrivia</a></li>

View File

@@ -1,7 +1,7 @@
import os, sys
import countershape
from countershape import Page, Directory, PythonModule, markup
import countershape.grok, countershape.template
import countershape.template
sys.path.insert(0, "..")
from libmproxy import filt
@@ -33,7 +33,7 @@ ns.index_contents = file(mpath("README.mkd")).read()
top = os.path.abspath(os.getcwd())
def example(s):
d = file(mpath(s)).read()
return countershape.template.pySyntax(d)
return countershape.template.Syntax("py")(d)
ns.example = example
@@ -74,9 +74,10 @@ pages = [
Page("anticache.html", "Anticache"),
Page("filters.html", "Filter expressions"),
Page("scripts.html", "External scripts"),
Page("library.html", "libmproxy: mitmproxy as a library"),
Page("ssl.html", "SSL"),
Directory("certinstall"),
Page("library.html", "libmproxy: mitmproxy as a library"),
Directory("tutorials"),
Page("faq.html", "FAQ"),
Page("admin.html", "Administrivia")
]

View File

@@ -0,0 +1,61 @@
My local cafe is serviced by a rickety and unreliable wireless network,
generously sponsored with ratepayers' money by our city council. After
connecting, you are redirected to an SSL-protected page that prompts you for a
username and password. Once you've entered your details, you are free to enjoy
the intermittent dropouts, treacle-like speeds and incorrectly configured
transparent proxy.
I tend to automate this kind of thing at the first opportunity, on the theory
that time spent now will be more than made up in the long run. In this case, I
might use [Firebug](http://getfirebug.com/) to ferret out the form post
parameters and target URL, then fire up an editor to write a little script
using Python's [urllib](http://docs.python.org/library/urllib.html) to simulate
a submission. That's a lot of futzing about. With mitmproxy we can do the job
in literally 30 seconds, without having to worry about any of the details.
Here's how.
## 1. Run mitmdump to record our HTTP conversation to a file.
<pre class="terminal">
> mitmdump -w wireless-login
</pre>
## 2. Point your browser at the mitmdump instance.
I use a tiny Firefox addon called [Toggle
Proxy](https://addons.mozilla.org/en-us/firefox/addon/toggle-proxy-51740/) to
switch quickly to and from mitmproxy. I'm assuming you've already [configured
your browser with mitmproxy's SSL certificate
authority](http://mitmproxy.org/doc/ssl.html).
## 3. Log in as usual.
And that's it! You now have a serialized version of the login process in the
file wireless-login, and you can replay it at any time like this:
<pre class="terminal">
> mitmdump -c wireless-login
</pre>
## Embellishments
We're really done at this point, but there are a couple of embellishments we
could make if we wanted. I use [wicd](http://wicd.sourceforge.net/) to
automatically join wireless networks I frequent, and it lets me specify a
command to run after connecting. I used the client replay command above and
voila! - totally hands-free wireless network startup.
We might also want to prune requests that download CSS, JS, images and so
forth. These add only a few moments to the time it takes to replay, but they're
not really needed and I somehow feel compelled trim them anyway. So, we fire up
the mitmproxy console tool on our serialized conversation, like so:
<pre class="terminal">
> mitmproxy wireless-login
</pre>
We can now go through and manually delete (using the __d__ keyboard shortcut)
everything we want to trim. When we're done, we use __S__ to save the
conversation back to the file.

View File

@@ -0,0 +1,105 @@
## The setup
In this tutorial, I'm going to show you how simple it is to creatively
interfere with Apple Game Center traffic using mitmproxy. To set things up, I
registered my mitmproxy CA certificate with my iPhone - there's a [step by step
set of instructions](@!urlTo("certinstall/ios.html")!@) elsewhere in this manual. I then
started mitmproxy on my desktop, and configured the iPhone to use it as a
proxy.
## Taking a look at the Game Center traffic
Lets take a first look at the Game Center traffic. The game I'll use in this
tutorial is [Super Mega
Worm](http://itunes.apple.com/us/app/super-mega-worm/id388541990?mt=8) - a
great little retro-apocalyptic sidescroller for the iPhone:
<center>
<img src="@!urlTo("tutorials/supermega.png")!@"/>
</center>
After finishing a game (take your time), watch the traffic flowing through
mitmproxy:
<center>
<img src="@!urlTo("tutorials/one.png")!@"/>
</center>
We see a bunch of things we might expect - initialisation, the retrieval of
leaderboards and so forth. Then, right at the end, there's a POST to this
tantalising URL:
<pre>
https://service.gc.apple.com/WebObjects/GKGameStatsService.woa/wa/submitScore
</pre>
The contents of the submission are particularly interesting:
<!--(block|syntax("xml"))-->
<plist version="1.0">
<dict>
<key>category</key>
<string>SMW_Adv_USA1</string>
<key>score-value</key>
<integer>55</integer>
<key>timestamp</key>
<integer>1301553284461</integer>
</dict>
</plist>
<!--(end)-->
This is a [property list](http://en.wikipedia.org/wiki/Property_list),
containing an identifier for the game, a score (55, in this case), and a
timestamp. Looks pretty simple to mess with.
## Modifying and replaying the score submission
Lets edit the score submission. First, select it in mitmproxy, then press
__enter__ to view it. Make sure you're viewing the request, not the response -
you can use __tab__ to flick between the two. Now press __e__ for edit. You'll
be prompted for the part of the request you want to change - press __b__ for
body. Your preferred editor (taken from the EDITOR environment variable) will
now fire up. Lets bump the score up to something a bit more ambitious:
<!--(block|syntax("xml"))-->
<plist version="1.0">
<dict>
<key>category</key>
<string>SMW_Adv_USA1</string>
<key>score-value</key>
<integer>2200272667</integer>
<key>timestamp</key>
<integer>1301553284461</integer>
</dict>
</plist>
<!--(end)-->
Save the file and exit your editor.
The final step is to replay this modified request. Simply press __r__ for
replay.
## The glorious result and some intrigue
<center>
<img src="@!urlTo("tutorials/leaderboard.png")!@"/>
</center>
And that's it - according to the records, I am the greatest Super Mega Worm
player of all time.
Curiously, the top competitors' scores are all the same: 2,147,483,647. If you
think that number seems familiar, you're right: it's 2^31-1, the maximum value
you can fit into a signed 32-bit int. Now let me tell you another peculiar
thing about Super Mega Worm - at the end of every game, it submits your highest
previous score to the Game Center, not your current score. This means that it
stores your highscore somewhere, and I'm guessing that it reads that stored
score back into a signed integer. So, if you _were_ to cheat by the relatively
pedestrian means of modifying the saved score on your jailbroken phone, then
2^31-1 might well be the maximum score you could get. Then again, if the game
itself stores its score in a signed 32-bit int, you could get the same score
through perfect play, effectively beating the game. So, which is it in this
case? I'll leave that for you to decide.

View File

@@ -0,0 +1,6 @@
from countershape import Page
pages = [
Page("30second.html", "Client playback: a 30 second example"),
Page("gamecenter.html", "Setting highscores on Apple's GameCenter"),
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

BIN
doc-src/tutorials/one.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -15,18 +15,20 @@ def get_common_options(options):
stickyauth = options.stickyauth_filt
return dict(
verbosity = options.verbose,
wfile = options.wfile,
anticache = options.anticache,
client_replay = options.client_replay,
kill = options.kill,
no_server = options.no_server,
refresh_server_playback = not options.norefresh,
rheaders = options.rheaders,
rfile = options.rfile,
request_script = options.request_script,
response_script = options.response_script,
server_replay = options.server_replay,
kill = options.kill,
rheaders = options.rheaders,
client_replay = options.client_replay,
stickycookie = stickycookie,
stickyauth = stickyauth,
anticache = options.anticache,
refresh_server_playback = not options.norefresh,
wfile = options.wfile,
verbosity = options.verbose,
)
@@ -41,6 +43,11 @@ def common_options(parser):
action="store", type = "str", dest="confdir", default='~/.mitmproxy',
help = "Configuration directory. (~/.mitmproxy)"
)
parser.add_option(
"-n",
action="store_true", dest="no_server",
help="Don't start a proxy server."
)
parser.add_option(
"-p",
action="store", type = "int", dest="port", default=8080,
@@ -51,6 +58,11 @@ def common_options(parser):
action="store_true", dest="quiet",
help="Quiet."
)
parser.add_option(
"-r",
action="store", dest="rfile", default=None,
help="Read flows from file."
)
parser.add_option(
"--anticache",
action="store_true", dest="anticache", default=False,
@@ -104,6 +116,12 @@ def common_options(parser):
)
parser.add_option_group(group)
parser.add_option(
"--cert-wait-time",
action="store", dest="cert_wait_time", default=0,
help="Wait for specified number of seconds after a new cert is generated. This can smooth over small discrepancies between the client and server times."
)
group = optparse.OptionGroup(parser, "Server Replay")
group.add_option(
"-s",

View File

@@ -13,13 +13,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import Queue, mailcap, mimetypes, tempfile, os, subprocess, glob, time
import mailcap, mimetypes, tempfile, os, subprocess, glob, time
import os.path, sys
import cStringIO
import urwid.raw_display
import urwid
import controller, utils, filt, proxy, flow
VIEW_CUTOFF = 1024*100
class Stop(Exception): pass
@@ -114,7 +116,7 @@ def format_flow(f, focus, extended=False, padding=2):
def int_version(v):
SIG = 3
v = urwid.__version__.split(".")
v = urwid.__version__.split("-")[0].split(".")
x = 0
for i in range(min(SIG, len(v))):
x += int(v[i]) * 10**(SIG-i)
@@ -664,16 +666,19 @@ class StatusBar(WWrap):
('statusbar_text', ("[%s]"%len(self.master.state.flow_list)).ljust(7)),
]
t.extend(self.get_status())
if self.master.server:
boundaddr = "[%s:%s]"%(self.master.server.address or "*", self.master.server.port)
else:
boundaddr = "[no proxy]"
status = urwid.AttrWrap(urwid.Columns([
urwid.Text(t),
urwid.Text(
[
self.helptext,
" ",
(
'statusbar_text',
"[%s:%s]"%(self.master.server.address or "*", self.master.server.port)
),
('statusbar_text', boundaddr),
],
align="right"
),
@@ -775,9 +780,11 @@ class Options(object):
"kill",
"intercept",
"limit",
"no_server",
"refresh_server_playback",
"request_script",
"response_script",
"rfile",
"rheaders",
"server_replay",
"stickycookie",
@@ -814,6 +821,7 @@ class ConsoleMaster(flow.FlowMaster):
def __init__(self, server, options):
flow.FlowMaster.__init__(self, server, ConsoleState())
self.looptime = 0
self.options = options
self.conn_list_view = None
self.set_palette()
@@ -873,14 +881,27 @@ class ConsoleMaster(flow.FlowMaster):
self.spawn_external_viewer(serr, None)
self.refresh_connection(f)
def _trailer(self, content, txt):
rem = len(content) - VIEW_CUTOFF
if rem > 0:
txt.append(urwid.Text(""))
txt.append(
urwid.Text(
[
("highlight", "... %s of data not shown"%utils.pretty_size(rem))
]
)
)
def _view_conn_normal(self, content, txt):
for i in content.splitlines():
for i in content[:VIEW_CUTOFF].splitlines():
txt.append(
urwid.Text(("text", i))
)
self._trailer(content, txt)
def _view_conn_binary(self, content, txt):
for offset, hex, s in utils.hexdump(content):
for offset, hex, s in utils.hexdump(content[:VIEW_CUTOFF]):
txt.append(urwid.Text([
("offset", offset),
" ",
@@ -888,12 +909,14 @@ class ConsoleMaster(flow.FlowMaster):
" ",
("text", s),
]))
self._trailer(content, txt)
def _view_conn_pretty(self, content, txt):
for i in utils.pretty_xmlish(content):
for i in utils.pretty_xmlish(content[:VIEW_CUTOFF]):
txt.append(
urwid.Text(("text", i)),
)
self._trailer(content, txt)
@utils.LRUCache(20)
def _cached_conn_text(self, content, hdrItems, viewmode):
@@ -912,7 +935,13 @@ class ConsoleMaster(flow.FlowMaster):
if viewmode == VIEW_BODY_BINARY:
self._view_conn_binary(content, txt)
elif viewmode == VIEW_BODY_INDENT:
self._view_conn_pretty(content, txt)
if utils.isXML(content):
self._view_conn_pretty(content, txt)
else:
if utils.isBin(content):
self._view_conn_binary(content, txt)
else:
self._view_conn_normal(content, txt)
else:
if utils.isBin(content):
self._view_conn_binary(content, txt)
@@ -1023,9 +1052,12 @@ class ConsoleMaster(flow.FlowMaster):
self.onekey = False
self.view_connlist()
self.masterq = Queue.Queue()
slave = controller.Slave(self.masterq, self.server)
slave.start()
if self.server:
slave = controller.Slave(self.masterq, self.server)
slave.start()
if self.options.rfile:
self.load_flows(self.options.rfile)
self.ui.run_wrapper(self.loop)
# If True, quit just pops out to connection list view.
@@ -1123,11 +1155,10 @@ class ConsoleMaster(flow.FlowMaster):
try:
f = file(path, "r")
fr = flow.FlowReader(f)
data = list(fr.stream())
f.close()
except IOError, v:
return v.strerror
self.state.load_flows(data)
flow.FlowMaster.load_flows(self, fr)
f.close()
if self.conn_list_view:
self.sync_list_view()
self.focus_current()
@@ -1298,14 +1329,18 @@ class ConsoleMaster(flow.FlowMaster):
return size
def loop(self):
changed = True
try:
while not controller.exit:
startloop = time.time()
self.statusbar.redraw()
size = self.drawscreen()
self.tick(self.masterq)
if changed:
self.statusbar.redraw()
size = self.drawscreen()
changed = self.tick(self.masterq)
self.ui.set_input_timeouts(max_wait=0.1)
keys = self.ui.get_input()
if keys:
changed = True
for k in keys:
if self.prompting:
if k == "esc":

View File

@@ -58,10 +58,14 @@ class Slave(threading.Thread):
class Master:
def __init__(self, server):
"""
server may be None if no server is needed.
"""
self.server = server
self.masterq = Queue.Queue()
def tick(self, q):
changed = False
try:
# This endless loop runs until the 'Queue.Empty'
# exception is thrown. If more than one request is in
@@ -71,12 +75,15 @@ class Master:
# Small timeout to prevent pegging the CPU
msg = q.get(timeout=0.01)
self.handle(msg)
changed = True
except Queue.Empty:
pass
return changed
def run(self):
slave = Slave(self.masterq, self.server)
slave.start()
if self.server:
slave = Slave(self.masterq, self.server)
slave.start()
while not exit:
self.tick(self.masterq)
self.shutdown()

View File

@@ -10,9 +10,11 @@ class Options(object):
"client_replay",
"keepserving",
"kill",
"no_server",
"refresh_server_playback",
"request_script",
"response_script",
"rfile",
"rheaders",
"server_replay",
"stickycookie",
@@ -51,6 +53,8 @@ class DumpMaster(flow.FlowMaster):
flow.FlowMaster.__init__(self, server, flow.State())
self.outfile = outfile
self.o = options
self.anticache = options.anticache
self.refresh_server_playback = options.refresh_server_playback
if filtstr:
self.filt = filt.parse(filtstr)
@@ -83,14 +87,21 @@ class DumpMaster(flow.FlowMaster):
not options.keepserving
)
if options.rfile:
path = os.path.expanduser(options.rfile)
try:
f = file(path, "r")
freader = flow.FlowReader(f)
except IOError, v:
raise DumpError(v.strerror)
self.load_flows(freader)
if options.client_replay:
self.start_client_playback(
self._readflow(options.client_replay),
not options.keepserving
)
self.anticache = options.anticache
self.refresh_server_playback = options.refresh_server_playback
def _readflow(self, path):
path = os.path.expanduser(path)
@@ -188,6 +199,8 @@ class DumpMaster(flow.FlowMaster):
# begin nocover
def run(self):
if self.o.rfile and not self.o.keepserving:
return
try:
return flow.FlowMaster.run(self)
except BaseException, v:

View File

@@ -540,7 +540,19 @@ class FlowMaster(controller.Master):
if self.server_playback.exit and self.server_playback.count() == 0:
self.shutdown()
controller.Master.tick(self, q)
return controller.Master.tick(self, q)
def load_flows(self, fr):
"""
Load flows from a FlowReader object.
"""
for i in fr.stream():
if i.request:
self.handle_request(i.request)
if i.response:
self.handle_response(i.response)
if i.error:
self.handle_error(i.error)
def process_new_request(self, f):
if self.stickycookie_state:
@@ -560,6 +572,12 @@ class FlowMaster(controller.Master):
else:
f.request.ack()
def process_new_response(self, f):
if self.stickycookie_state:
self.stickycookie_state.handle_response(f)
if "response" in self.scripts:
self._runscript(f, self.scripts["response"])
def replay_request(self, f):
"""
Returns None if successful, or error message if not.
@@ -568,7 +586,6 @@ class FlowMaster(controller.Master):
if f.intercepting:
return "Can't replay while intercepting..."
if f.request:
f.backup()
f.request.set_replay()
if f.request.content:
f.request.headers["content-length"] = [str(len(f.request.content))]
@@ -605,10 +622,7 @@ class FlowMaster(controller.Master):
self.client_playback.clear(f)
if not f:
r.ack()
if self.stickycookie_state:
self.stickycookie_state.handle_response(f)
if "response" in self.scripts:
self._runscript(f, self.scripts["response"])
self.process_new_response(f)
return f

View File

@@ -23,11 +23,12 @@ class ProxyError(Exception):
class SSLConfig:
def __init__(self, certfile = None, ciphers = None, cacert = None):
def __init__(self, certfile = None, ciphers = None, cacert = None, cert_wait_time=None):
self.certfile = certfile
self.ciphers = ciphers
self.cacert = cacert
self.certdir = None
self.cert_wait_time = cert_wait_time
def read_chunked(fp):
@@ -472,7 +473,10 @@ class FileLike:
def read(self, length):
result = ''
while len(result) < length:
data = self.o.read(length)
try:
data = self.o.read(length)
except AttributeError:
break
if not data:
break
result += data
@@ -596,7 +600,7 @@ class ProxyHandler(SocketServer.StreamRequestHandler):
return
self.send_response(response)
except IOError:
pass
cc.close = True
except ProxyError, e:
err = Error(request, e.msg)
err.send(self.mqueue)
@@ -610,6 +614,7 @@ class ProxyHandler(SocketServer.StreamRequestHandler):
return self.config.certfile
else:
ret = utils.dummy_cert(self.config.certdir, self.config.cacert, host)
time.sleep(self.config.cert_wait_time)
if not ret:
raise ProxyError(400, "mitmproxy: Unable to generate dummy cert.")
return ret
@@ -714,7 +719,7 @@ class ProxyHandler(SocketServer.StreamRequestHandler):
self.wfile.write('<html><head>\n<title>%d %s</title>\n</head>\n'
'<body>\n%s\n</body>\n</html>' % (code, response, body))
self.wfile.flush()
except IOError:
except:
pass
@@ -781,5 +786,6 @@ def process_certificate_option_group(parser, options):
return SSLConfig(
certfile = options.cert,
cacert = cacert,
ciphers = options.ciphers
ciphers = options.ciphers,
cert_wait_time = options.cert_wait_time
)

View File

@@ -14,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re, os, subprocess, datetime, textwrap, errno, sys, time, functools
CERT_SLEEP_TIME = 1
def timestamp():
"""
@@ -43,6 +44,16 @@ def isBin(s):
return False
def isXML(s):
for i in s:
if i in "\n \t":
continue
elif i == "<":
return True
else:
return False
def cleanBin(s):
parts = []
for i in s:
@@ -50,7 +61,8 @@ def cleanBin(s):
if o > 31 and o < 127:
parts.append(i)
else:
parts.append(".")
if i not in "\n\r\t":
parts.append(".")
return "".join(parts)

View File

@@ -1,2 +1,2 @@
IVERSION = (0, 4)
IVERSION = (0, 5)
VERSION = ".".join([str(i) for i in IVERSION])

View File

@@ -30,7 +30,7 @@ if __name__ == '__main__':
parser.add_option(
"--keepserving",
action="store_true", dest="keepserving", default=False,
help="Continue serving after playback. We exit by default."
help="Continue serving after client playback or file read. We exit by default."
)
options, args = parser.parse_args()
@@ -39,11 +39,15 @@ if __name__ == '__main__':
options.verbose = 0
config = proxy.process_certificate_option_group(parser, options)
try:
server = proxy.ProxyServer(config, options.port, options.addr)
except proxy.ProxyServerError, v:
print >> sys.stderr, "mitmdump:", v.args[0]
sys.exit(1)
if options.no_server:
server = None
else:
try:
server = proxy.ProxyServer(config, options.port, options.addr)
except proxy.ProxyServerError, v:
print >> sys.stderr, "mitmdump:", v.args[0]
sys.exit(1)
dumpopts = dump.Options(**cmdline.get_common_options(options))
dumpopts.keepserving = options.keepserving

View File

@@ -23,7 +23,7 @@ from optparse import OptionParser, OptionGroup
if __name__ == '__main__':
parser = OptionParser(
usage = "%prog [options] [flowdump path]",
usage = "%prog [options]",
version="%%prog %s"%VERSION,
)
cmdline.common_options(parser)
@@ -49,11 +49,15 @@ if __name__ == '__main__':
options, args = parser.parse_args()
config = proxy.process_certificate_option_group(parser, options)
try:
server = proxy.ProxyServer(config, options.port, options.addr)
except proxy.ProxyServerError, v:
print >> sys.stderr, "mitmproxy:", v.args[0]
sys.exit(1)
if options.no_server:
server = None
else:
try:
server = proxy.ProxyServer(config, options.port, options.addr)
except proxy.ProxyServerError, v:
print >> sys.stderr, "mitmproxy:", v.args[0]
sys.exit(1)
opts = console.Options(**cmdline.get_common_options(options))
opts.intercept = options.intercept
@@ -61,13 +65,6 @@ if __name__ == '__main__':
opts.debug = options.debug
m = console.ConsoleMaster(server, opts)
for i in args:
try:
m.load_flows(i)
except flow.FlowReadError, v:
print >> sys.stderr, "mitmproxy:", v.args[0]
sys.exit(1)
m.run()

View File

@@ -369,6 +369,19 @@ class uState(libpry.AutoTree):
class uSerialize(libpry.AutoTree):
def _treader(self):
sio = StringIO()
w = flow.FlowWriter(sio)
for i in range(3):
f = tutils.tflow_full()
w.add(f)
for i in range(3):
f = tutils.tflow_err()
w.add(f)
sio.seek(0)
return flow.FlowReader(sio)
def test_roundtrip(self):
sio = StringIO()
f = tutils.tflow()
@@ -381,6 +394,14 @@ class uSerialize(libpry.AutoTree):
assert len(l) == 1
assert l[0] == f
def test_load_flows(self):
r = self._treader()
s = flow.State()
fm = flow.FlowMaster(None, s)
fm.load_flows(r)
assert len(s.flow_list) == 6
def test_error(self):
sio = StringIO()
sio.write("bogus")

View File

@@ -2,6 +2,8 @@ import textwrap, cStringIO, os, time, re
import libpry
from libmproxy import utils
utils.CERT_SLEEP_TIME = 0
class uformat_timestamp(libpry.AutoTree):
def test_simple(self):
@@ -16,6 +18,13 @@ class uisBin(libpry.AutoTree):
assert utils.isBin("testing\x7f")
class uisXML(libpry.AutoTree):
def test_simple(self):
assert not utils.isXML("foo")
assert utils.isXML("<foo")
assert utils.isXML(" \n<foo")
class uhexdump(libpry.AutoTree):
def test_simple(self):
assert utils.hexdump("one\0"*10)
@@ -350,6 +359,7 @@ class uLRUCache(libpry.AutoTree):
tests = [
uformat_timestamp(),
uisBin(),
uisXML(),
uhexdump(),
upretty_size(),
uisStringLike(),

View File

@@ -32,6 +32,13 @@ def tflow_full():
return f
def tflow_err():
r = treq()
f = flow.Flow(r)
f.error = proxy.Error(r, "error")
return f
# Yes, the random ports are horrible. During development, sockets are often not
# properly closed during error conditions, which means you have to wait until
# you can re-bind to the same port. This is a pain in the ass, so we just pick