diff options
33 files changed, 224 insertions, 84 deletions
@@ -1,5 +1,18 @@ -x January 2013: mitmproxy 0.9: +16 June 2013: mitmproxy 0.9.1: + + * Use "correct" case for Content-Type headers added by mitmproxy. + + * Make UTF environment detection more robust. + + * Improved MIME-type detection for viewers. + + * Always read files in binary mode (Windows compatibility fix). + + * Some developer documentation. + + +15 May 2013: mitmproxy 0.9: * Upstream certs mode is now the default. diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 37d0ce1e..e7ea1fb7 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,35 +1,39 @@ - 759 Aldo Cortesi + 777 Aldo Cortesi 18 Henrik Nordstrom 13 Thomas Roth 11 Stephen Altamirano 10 András Veres-Szentkirályi 8 Rouli + 8 Jason A. Novak 7 Alexis Hildebrandt - 5 Maximilian Hils - 4 Bryan Bishop + 6 Maximilian Hils 4 Valtteri Virtanen + 4 Bryan Bishop 3 Chris Neasbitt - 2 Michael Frister 2 Heikki Hannikainen 2 Jim Lloyd 2 Mark E. Haase + 2 Michael Frister 2 Rob Wills 2 alts 2 israel - 1 Jakub Nawalaniec + 1 Mathieu Mitchell + 1 Michael Bisbjerg + 1 capt8bit + 1 Nicolas Esteves 1 Paul 1 phil plante 1 Rory McCann - 1 Henrik Nordström + 1 Jakub Nawalaniec 1 Rune Halvorsen 1 Sahn Lam - 1 Felix Wolfsteller - 1 Eric Entzel - 1 Ulrich Petri + 1 Ivaylo Popov 1 Andy Smith + 1 Ulrich Petri + 1 Henrik Nordström + 1 Felix Wolfsteller 1 Yuangxuan Wang + 1 Kit Randel + 1 Marc Liyanage 1 meeee - 1 capt8bit - 1 Mathieu Mitchell - 1 Jason A. Novak - 1 Nicolas Esteves + 1 Eric Entzel diff --git a/MANIFEST.in b/MANIFEST.in index f1465cd9..d1426c13 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include LICENSE include CHANGELOG include CONTRIBUTORS include README.txt +include setup.py exclude README.mkd recursive-include examples * recursive-include doc * @@ -55,6 +55,8 @@ The following components are needed if you plan to hack on mitmproxy: framework and requires [pathod](http://pathod.org) and [flask](http://flask.pocoo.org/). * Rendering the documentation requires [countershape](http://github.com/cortesi/countershape). +For convenience, all dependencies save countershape, can be installed from pypi to a virtualenv with 'pip install -r requirements.txt'. + Please ensure that all patches are accompanied by matching changes in the test suite. The project maintains 100% test coverage. diff --git a/doc-src/_layout.html b/doc-src/_layout.html index b64a8558..72b27cd3 100644 --- a/doc-src/_layout.html +++ b/doc-src/_layout.html @@ -58,6 +58,10 @@ <li class="nav-header">Scripting mitmproxy</li> $!nav("scripting/inlinescripts.html", this, state)!$ $!nav("scripting/libmproxy.html", this, state)!$ + + <li class="nav-header">Hacking</li> + $!nav("dev/testing.html", this, state)!$ + </ul> </div> </div> diff --git a/doc-src/_websitelayout.html b/doc-src/_websitelayout.html index f65ee059..13d71fcb 100644 --- a/doc-src/_websitelayout.html +++ b/doc-src/_websitelayout.html @@ -64,6 +64,9 @@ <li class="nav-header">Scripting mitmproxy</li> $!nav("scripting/inlinescripts.html", this, state)!$ $!nav("scripting/libmproxy.html", this, state)!$ + + <li class="nav-header">Hacking</li> + $!nav("dev/testing.html", this, state)!$ </ul> </div> </div> diff --git a/doc-src/dev/addingviews.html b/doc-src/dev/addingviews.html new file mode 100644 index 00000000..12623a31 --- /dev/null +++ b/doc-src/dev/addingviews.html @@ -0,0 +1,52 @@ +As discussed in [the Flow View section of the mitmproxy +overview](@!urlTo("mitmproxy.html")!@), mitmproxy allows you to inspect and +manipulate flows. When inspecting a single flow, mitmproxy uses a number of +heuristics to show a friendly view of various content types; if mitmproxy +cannot show a friendly view, mitmproxy defaults to a __raw__ view. + +Each content type invokes a different flow viewer to parse the data and display +the friendly view. Users can add custom content viewers by adding a view class +to contentview.py, discussed below. + +## Adding a new View class to contentview.py + +The content viewers used by mitmproxy to present a friendly view of various +content types are stored in contentview.py. Reviewing this file shows a number +of classes named ViewSomeDataType, each with the properties: __name__, +__prompt__, and __content\_types__ and a function named __\_\_call\_\___. + +Adding a new content viewer to parse a data type is as simple as writing a new +View class. Your new content viewer View class should have the same properties +as the other View classes: __name__, __prompt__, and __content\_types__ and a +__\_\_call\_\___ function to parse the content of the request/response. + +* The __name__ property should be a string describing the contents and new content viewer; +* The __prompt__ property should be a two item tuple: + + - __1__: A string that will be used to display the new content viewer's type; and + - __2__: A one character string that will be the hotkey used to select the new content viewer from the Flow View screen; + +* The __content\_types__ property should be a list of strings of HTTP Content\-Types that the new content viewer can parse. + * Note that mitmproxy will use the content\_types to try and heuristically show a friendly view of content and that you can override the built-in views by populating content\_types with values for content\_types that are already parsed -- e.g. "image/png". + +After defining the __name__, __prompt__, and __content\_types__ properties of +the class, you should write the __\_\_call\_\___ function, which will parse the +request/response data and provide a friendly view of the data. The +__\_\_call\_\___ function should take the following arguments: __self__, +__hdrs__, __content__, __limit__; __hdrs__ is a ODictCaseless object containing +the headers of the request/response; __content__ is the content of the +request/response, and __limit__ is an integer representing the amount of data +to display in the view window. + +The __\_\_call\_\___ function returns two values: (1) a string describing the +parsed data; and (2) the parsed data for friendly display. The parsed data to +be displayed should be a list of strings formatted for display. You can use +the __\_view\_text__ function in contentview.py to format text for display. +Alternatively, you can display content as a series of key-value pairs; to do +so, prepare a list of lists, where each list item is a two item list -- a key +that describes the data, and then the data itself; after preparing the list of +lists, use the __common.format\_keyvals__ function on it to prepare it as text +for display. + +If the new content viewer fails or throws an exception, mitmproxy will default +to a __raw__ view. diff --git a/doc-src/dev/index.py b/doc-src/dev/index.py new file mode 100644 index 00000000..001c2b89 --- /dev/null +++ b/doc-src/dev/index.py @@ -0,0 +1,6 @@ +from countershape import Page + +pages = [ + Page("testing.html", "Testing"), +# Page("addingviews.html", "Writing Content Views"), +] diff --git a/doc-src/dev/testing.html b/doc-src/dev/testing.html new file mode 100644 index 00000000..4cee29e8 --- /dev/null +++ b/doc-src/dev/testing.html @@ -0,0 +1,43 @@ + +All the mitmproxy projects strive to maintain 100% code coverage. In general, +patches and pull requests will be declined unless they're accompanied by a +suitable extension to the test suite. + +Our tests are written for the [nose](https://nose.readthedocs.org/en/latest/). +At the point where you send your pull request, a command like this: + +<pre class="terminal"> +> nosetests --with-cov --cov-report term-missing ./test +</pre> + +Should give output something like this: + +<pre class="terminal"> +> ---------- coverage: platform darwin, python 2.7.2-final-0 -- +> Name Stmts Miss Cover Missing +> ---------------------------------------------------- +> libmproxy/__init__ 0 0 100% +> libmproxy/app 4 0 100% +> libmproxy/cmdline 100 0 100% +> libmproxy/controller 69 0 100% +> libmproxy/dump 150 0 100% +> libmproxy/encoding 39 0 100% +> libmproxy/filt 201 0 100% +> libmproxy/flow 891 0 100% +> libmproxy/proxy 427 0 100% +> libmproxy/script 27 0 100% +> libmproxy/utils 133 0 100% +> libmproxy/version 4 0 100% +> ---------------------------------------------------- +> TOTAL 2045 0 100% +> ---------------------------------------------------- +> Ran 251 tests in 11.864s +</pre> + + +There are exceptions to the coverage requirement - for instance, much of the +console interface code can't sensibly be unit tested. These portions are +excluded from coverage analysis either in the **.coveragerc** file, or using +**#pragma no-cover** directives. To keep our coverage analysis relevant, we use +these measures as sparingly as possible. + diff --git a/doc-src/howmitmproxy.html b/doc-src/howmitmproxy.html index 09a69ec2..94afd522 100644 --- a/doc-src/howmitmproxy.html +++ b/doc-src/howmitmproxy.html @@ -246,7 +246,7 @@ mechanism has a different way of exposing this data, so this introduces the second component required for working transparent proxying: a host module that knows how to retrieve the original destination address from the router. In mitmproxy, this takes the form of a built-in set of -[modules](https://github.com/cortesi/mitmproxy/tree/master/libmproxy/platform) +[modules](https://github.com/mitmproxy/mitmproxy/tree/master/libmproxy/platform) that know how to talk to each platform's redirection mechanism. Once we have this information, the process is fairly straight-forward. diff --git a/doc-src/index.py b/doc-src/index.py index 7b84f982..6880bcae 100644 --- a/doc-src/index.py +++ b/doc-src/index.py @@ -5,7 +5,7 @@ import countershape.template sys.path.insert(0, "..") from libmproxy import filt -MITMPROXY_SRC = "~/git/public/mitmproxy" +MITMPROXY_SRC = "~/mitmproxy/mitmproxy" if ns.options.website: ns.idxpath = "doc/index.html" diff --git a/doc-src/install.html b/doc-src/install.html index 30e2774d..70003d60 100644 --- a/doc-src/install.html +++ b/doc-src/install.html @@ -25,7 +25,7 @@ pip install /path/to/source </pre> Note that if you're installing current git master, you will also have to -install the current git master of [netlib](http://github.com/cortesi/netlib) by +install the current git master of [netlib](http://github.com/mitmproxy/netlib) by hand. ## OSX diff --git a/doc-src/scripting/index.py b/doc-src/scripting/index.py index 94c71a76..b8312083 100644 --- a/doc-src/scripting/index.py +++ b/doc-src/scripting/index.py @@ -2,5 +2,5 @@ from countershape import Page pages = [ Page("inlinescripts.html", "Inline Scripts"), - Page("libmproxy.html", "libmproxy") + Page("libmproxy.html", "libmproxy"), ] diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py index b76792cf..6b6c4241 100644 --- a/libmproxy/cmdline.py +++ b/libmproxy/cmdline.py @@ -124,7 +124,7 @@ def get_common_options(options): except ParseException, e: raise OptionException(e.message) try: - v = open(path, "r").read() + v = open(path, "rb").read() except IOError, e: raise OptionException("Could not read replace file: %s"%path) reps.append((patt, rex, v)) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index fe75a047..ef799167 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -479,7 +479,7 @@ class ConsoleMaster(flow.FlowMaster): def _readflow(self, path): path = os.path.expanduser(path) try: - f = file(path, "r") + f = file(path, "rb") flows = list(flow.FlowReader(f).stream()) except (IOError, flow.FlowReadError), v: return True, v.strerror @@ -519,13 +519,14 @@ class ConsoleMaster(flow.FlowMaster): except: self.statusbar.message("Can't start editor: %s" % " ".join(c)) else: - data = open(name).read() + data = open(name,"rb").read() self.ui.start() os.unlink(name) return data def spawn_external_viewer(self, data, contenttype): if contenttype: + contenttype = contenttype.split(";")[0] ext = mimetypes.guess_extension(contenttype) or "" else: ext = "" diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 8932b912..da5a6c65 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -16,7 +16,7 @@ import os, sys import urwid import common, grideditor, contentview -from .. import utils, flow +from .. import utils, flow, controller def _mkhelp(): text = [] @@ -341,7 +341,12 @@ class FlowView(common.WWrap): conn = self.flow.request else: if not self.flow.response: - self.flow.response = flow.Response(self.flow.request, 200, "OK", flow.ODictCaseless(), "", None) + self.flow.response = flow.Response( + self.flow.request, + self.flow.request.httpversion, + 200, "OK", flow.ODictCaseless(), "", None + ) + self.flow.response.reply = controller.DummyReply() conn = self.flow.response self.flow.backup() diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index 314d6e88..55f3ba0f 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -294,7 +294,7 @@ class GridEditor(common.WWrap): if p: try: p = os.path.expanduser(p) - d = file(p, "r").read() + d = file(p, "rb").read() self.walker.set_current_value(d, unescaped) self.walker._modified() except IOError, v: diff --git a/libmproxy/dump.py b/libmproxy/dump.py index b1022ef5..06c15c63 100644 --- a/libmproxy/dump.py +++ b/libmproxy/dump.py @@ -141,7 +141,7 @@ class DumpMaster(flow.FlowMaster): def _readflow(self, path): path = os.path.expanduser(path) try: - f = file(path, "r") + f = file(path, "rb") flows = list(flow.FlowReader(f).stream()) except (IOError, flow.FlowReadError), v: raise DumpError(v.strerror) diff --git a/libmproxy/encoding.py b/libmproxy/encoding.py index 9f8acbe9..4b80d38c 100644 --- a/libmproxy/encoding.py +++ b/libmproxy/encoding.py @@ -54,7 +54,7 @@ def decode_gzip(content): gfile = gzip.GzipFile(fileobj=cStringIO.StringIO(content)) try: return gfile.read() - except IOError: + except (IOError, EOFError): return None def encode_gzip(content): diff --git a/libmproxy/flow.py b/libmproxy/flow.py index 1d15029b..10c5da5d 100644 --- a/libmproxy/flow.py +++ b/libmproxy/flow.py @@ -542,7 +542,7 @@ class Request(HTTPMsg): headers["host"] = [utils.hostport(self.scheme, self.host, self.port)] content = self.content if content: - headers["content-length"] = [str(len(content))] + headers["Content-Length"] = [str(len(content))] else: content = "" if self.close: @@ -737,7 +737,7 @@ class Response(HTTPMsg): ['proxy-connection', 'transfer-encoding'] ) if self.content: - headers["content-length"] = [str(len(self.content))] + headers["Content-Length"] = [str(len(self.content))] proto = "HTTP/%s.%s %s %s"%(self.httpversion[0], self.httpversion[1], self.code, str(self.msg)) data = (proto, str(headers)) return FMT%data @@ -1548,7 +1548,7 @@ class FlowMaster(controller.Master): if f.request: f.request._set_replay() if f.request.content: - f.request.headers["content-length"] = [str(len(f.request.content))] + f.request.headers["Content-Length"] = [str(len(f.request.content))] f.response = None f.error = None self.process_new_request(f) diff --git a/libmproxy/proxy.py b/libmproxy/proxy.py index 283072ab..8dfae77a 100644 --- a/libmproxy/proxy.py +++ b/libmproxy/proxy.py @@ -88,6 +88,9 @@ class ServerConnection(tcp.TCPClient): def terminate(self): try: self.wfile.flush() + except IOError: + pass + try: self.connection.close() except IOError: pass @@ -213,7 +216,7 @@ class ProxyHandler(tcp.BaseHandler): return else: request_reply = self.channel.ask(request) - if request_reply == KILL: + if request_reply is None or request_reply == KILL: return elif isinstance(request_reply, flow.Response): request = False diff --git a/libmproxy/version.py b/libmproxy/version.py index 3dfc9409..b3a9bd95 100644 --- a/libmproxy/version.py +++ b/libmproxy/version.py @@ -1,4 +1,4 @@ -IVERSION = (0, 9) +IVERSION = (0, 9, 1) VERSION = ".".join(str(i) for i in IVERSION) NAME = "mitmproxy" NAMEVERSION = NAME + " " + VERSION @@ -62,12 +62,14 @@ if __name__ == '__main__': opts.debug = options.debug opts.palette = options.palette - if "utf" not in os.environ.get("LANG", "").lower(): + spec = "" + for i in ["LANG", "LC_CTYPE", "LC_ALL"]: + spec += os.environ.get(i, "").lower() + if "utf" not in spec: print >> sys.stderr, "Error: mitmproxy requires a UTF console environment." print >> sys.stderr, "Set your LANG enviroment variable to something like en_US.UTF-8" sys.exit(1) - m = console.ConsoleMaster(server, opts) try: m.run() diff --git a/release/osx-binaries b/release/osx-binaries new file mode 100755 index 00000000..a5f17ba5 --- /dev/null +++ b/release/osx-binaries @@ -0,0 +1,23 @@ +#!/bin/sh + +# Quick and dangerous script for building OSX binaries. + +# First, have a recent checkout of the dev version of pyinstaller. Change into +# the pyinstaller directory, and then run this script. + +DST=/tmp/osx-mitmproxy +MITMPROXY=~/mitmproxy/mitmproxy +PYINST_CMD="./pyinstaller.py -F --clean" + +rm -rf $DST +mkdir -p $DST +rm -rf mitmproxy +rm -rf mitmdump + +$PYINST_CMD $MITMPROXY/mitmproxy +cp mitmproxy/dist/mitmproxy $DST + +$PYINST_CMD $MITMPROXY/mitmdump +cp mitmdump/dist/mitmdump $DST + +cshape $MITMPROXY/doc-src $DST/doc diff --git a/release/pyinstaller-mitmdump.spec b/release/pyinstaller-mitmdump.spec deleted file mode 100644 index a1a9b937..00000000 --- a/release/pyinstaller-mitmdump.spec +++ /dev/null @@ -1,20 +0,0 @@ -# -*- mode: python -*- - -# Copy into the pyinstaller directory -# ./pyinstaller.py --clean -F ./pyinstaller-mitmdump.spec - -a = Analysis(['/Users/aldo/git/public/mitmproxy/mitmdump'], - hiddenimports=["pyamf"], - hookspath=None, - runtime_hooks=None) -pyz = PYZ(a.pure) -exe = EXE(pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - name='mitmdump', - debug=False, - strip=None, - upx=True, - console=True ) diff --git a/release/pyinstaller-mitmproxy.spec b/release/pyinstaller-mitmproxy.spec deleted file mode 100644 index 8de6d583..00000000 --- a/release/pyinstaller-mitmproxy.spec +++ /dev/null @@ -1,16 +0,0 @@ -# -*- mode: python -*- -a = Analysis(['/Users/aldo/git/public/mitmproxy/mitmproxy'], - hiddenimports=["pyamf"], - hookspath=None, - runtime_hooks=None) -pyz = PYZ(a.pure) -exe = EXE(pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - name='mitmproxy', - debug=False, - strip=None, - upx=True, - console=True ) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..ce1659aa --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +Flask==0.9 +Jinja2==2.7 +MarkupSafe==0.18 +PIL==1.1.7 +Werkzeug==0.8.3 +lxml==3.2.1 +netlib==0.9 +nose==1.3.0 +pathod==0.9 +pyOpenSSL==0.13 +pyasn1==0.1.7 +requests==1.2.2 +urwid==1.1.1 +wsgiref==0.1.2 @@ -79,7 +79,7 @@ setup( package_data = package_data, scripts = ["mitmproxy", "mitmdump"], classifiers = [ - "License :: OSI Approved :: GNU General Public License (GPL)", + "License :: OSI Approved :: MIT License", "Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: Console :: Curses", @@ -96,7 +96,7 @@ setup( "netlib>=%s"%version.VERSION, "urwid>=1.1", "pyasn1>0.1.2", - "pyopenssl>=0.12", + "pyopenssl>=0.13", "PIL", "lxml", "flask" diff --git a/test/test_console_contentview.py b/test/test_console_contentview.py index 1798ce85..f6b45f67 100644 --- a/test/test_console_contentview.py +++ b/test/test_console_contentview.py @@ -114,16 +114,16 @@ class TestContentView: def test_view_image(self): v = cv.ViewImage() p = tutils.test_data.path("data/image.png") - assert v([], file(p).read(), sys.maxint) + assert v([], file(p,"rb").read(), sys.maxint) p = tutils.test_data.path("data/image.gif") - assert v([], file(p).read(), sys.maxint) + assert v([], file(p,"rb").read(), sys.maxint) p = tutils.test_data.path("data/image-err1.jpg") - assert v([], file(p).read(), sys.maxint) + assert v([], file(p,"rb").read(), sys.maxint) p = tutils.test_data.path("data/image.ico") - assert v([], file(p).read(), sys.maxint) + assert v([], file(p,"rb").read(), sys.maxint) assert not v([], "flibble", sys.maxint) @@ -224,22 +224,22 @@ if pyamf: v = cv.ViewAMF() p = tutils.test_data.path("data/amf01") - assert v([], file(p).read(), sys.maxint) + assert v([], file(p,"rb").read(), sys.maxint) p = tutils.test_data.path("data/amf02") - assert v([], file(p).read(), sys.maxint) + assert v([], file(p,"rb").read(), sys.maxint) def test_view_amf_response(): v = cv.ViewAMF() p = tutils.test_data.path("data/amf03") - assert v([], file(p).read(), sys.maxint) + assert v([], file(p,"rb").read(), sys.maxint) if cv.ViewProtobuf.is_available(): def test_view_protobuf_request(): v = cv.ViewProtobuf() p = tutils.test_data.path("data/protobuf01") - content_type, output = v([], file(p).read(), sys.maxint) + content_type, output = v([], file(p,"rb").read(), sys.maxint) assert content_type == "Protobuf" assert output[0].text == '1: "3bbc333c-e61c-433b-819a-0b9a8cc103b8"' diff --git a/test/test_dump.py b/test/test_dump.py index 94d0b195..6a35cdec 100644 --- a/test/test_dump.py +++ b/test/test_dump.py @@ -46,7 +46,7 @@ class TestDumpMaster: return cs.getvalue() def _flowfile(self, path): - f = open(path, "w") + f = open(path, "wb") fw = flow.FlowWriter(f) t = tutils.tflow_full() t.response = tutils.tresp(t.request) @@ -128,7 +128,7 @@ class TestDumpMaster: with tutils.tmpdir() as d: p = os.path.join(d, "a") self._dummy_cycle(1, None, "", wfile=p, verbosity=0) - assert len(list(flow.FlowReader(open(p)).stream())) == 1 + assert len(list(flow.FlowReader(open(p,"rb")).stream())) == 1 def test_write_err(self): tutils.raises( diff --git a/test/test_flow.py b/test/test_flow.py index 718f3551..977cdd4e 100644 --- a/test/test_flow.py +++ b/test/test_flow.py @@ -733,7 +733,7 @@ class TestFlowMaster: with tutils.tmpdir() as tdir: p = os.path.join(tdir, "foo") def r(): - r = flow.FlowReader(open(p)) + r = flow.FlowReader(open(p,"rb")) return list(r.stream()) s = flow.State() diff --git a/test/test_platform_pf.py b/test/test_platform_pf.py index a2e7c3c1..f048fdcc 100644 --- a/test/test_platform_pf.py +++ b/test/test_platform_pf.py @@ -5,7 +5,7 @@ from libmproxy.platform import pf class TestLookup: def test_simple(self): p = tutils.test_data.path("data/pf01") - d = open(p).read() + d = open(p,"rb").read() assert pf.lookup("192.168.1.111", 40000, d) == ("5.5.5.5", 80) assert not pf.lookup("192.168.1.112", 40000, d) assert not pf.lookup("192.168.1.111", 40001, d) diff --git a/test/tutils.py b/test/tutils.py index 1a1c8724..fbce615a 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -20,7 +20,7 @@ def tresp(req=None): req = treq() headers = flow.ODictCaseless() headers["header_response"] = ["svalue"] - cert = certutils.SSLCert.from_der(file(test_data.path("data/dercert")).read()) + cert = certutils.SSLCert.from_der(file(test_data.path("data/dercert"),"rb").read()) resp = flow.Response(req, (1, 1), 200, "message", headers, "content_response", cert) resp.reply = controller.DummyReply() return resp |