aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.rst38
-rw-r--r--pathod/pathod.py18
-rw-r--r--pathod/test.py36
-rw-r--r--setup.cfg3
-rw-r--r--test/pathod/test_app.py6
-rw-r--r--test/pathod/test_pathod.py22
-rw-r--r--test/pathod/test_test.py4
-rw-r--r--test/pathod/tutils.py25
8 files changed, 87 insertions, 65 deletions
diff --git a/README.rst b/README.rst
index 4dd97bbf..85663fc5 100644
--- a/README.rst
+++ b/README.rst
@@ -3,19 +3,24 @@ mitmproxy
|travis| |coveralls| |latest_release| |python_versions|
-This repository contains the **mitmproxy** and **pathod** projects, as well as their shared networking library, **netlib**.
+This repository contains the **mitmproxy** and **pathod** projects, as well as
+their shared networking library, **netlib**.
-``mitmproxy`` is an interactive, SSL-capable intercepting proxy with a console interface.
+``mitmproxy`` is an interactive, SSL-capable intercepting proxy with a console
+interface.
``mitmdump`` is the command-line version of mitmproxy. Think tcpdump for HTTP.
-``pathoc`` and ``pathod`` are perverse HTTP client and server applications designed to let you craft almost any conceivable HTTP request, including ones that creatively violate the standards.
+``pathoc`` and ``pathod`` are perverse HTTP client and server applications
+designed to let you craft almost any conceivable HTTP request, including ones
+that creatively violate the standards.
Documentation & Help
--------------------
-Documentation, tutorials and precompiled binaries can be found on the mitmproxy and pathod websites.
+Documentation, tutorials and precompiled binaries can be found on the mitmproxy
+and pathod websites.
|mitmproxy_site| |pathod_site|
@@ -39,8 +44,8 @@ Hacking
-------
To get started hacking on mitmproxy, make sure you have Python_ 2.7.x. with
-virtualenv_ installed (you can find installation instructions for virtualenv here_).
-Then do the following:
+virtualenv_ installed (you can find installation instructions for virtualenv
+here_). Then do the following:
.. code-block:: text
@@ -49,10 +54,11 @@ Then do the following:
./dev.sh
-The *dev* script will create a virtualenv environment in a directory called "venv",
-and install all mandatory and optional dependencies into it.
-The primary mitmproxy components - mitmproxy, netlib and pathod - are installed as "editable",
-so any changes to the source in the repository will be reflected live in the virtualenv.
+The *dev* script will create a virtualenv environment in a directory called
+"venv", and install all mandatory and optional dependencies into it. The
+primary mitmproxy components - mitmproxy, netlib and pathod - are installed as
+"editable", so any changes to the source in the repository will be reflected
+live in the virtualenv.
To confirm that you're up and running, activate the virtualenv, and run the
mitmproxy test suite:
@@ -63,9 +69,9 @@ mitmproxy test suite:
py.test
Note that the main executables for the project - ``mitmdump``, ``mitmproxy``,
-``mitmweb``, ``pathod``, and ``pathoc`` - are all created within the virtualenv. After activating the
-virtualenv, they will be on your $PATH, and you can run them like any other
-command:
+``mitmweb``, ``pathod``, and ``pathoc`` - are all created within the
+virtualenv. After activating the virtualenv, they will be on your $PATH, and
+you can run them like any other command:
.. code-block:: text
@@ -92,9 +98,9 @@ suite. The project tries to maintain 100% test coverage.
Documentation
-------------
-The mitmproxy documentation is build using Sphinx_, which is installed automatically if you set up a development
-environment as described above.
-After installation, you can render the documentation like this:
+The mitmproxy documentation is build using Sphinx_, which is installed
+automatically if you set up a development environment as described above. After
+installation, you can render the documentation like this:
.. code-block:: text
diff --git a/pathod/pathod.py b/pathod/pathod.py
index 7795df0e..0449c0c1 100644
--- a/pathod/pathod.py
+++ b/pathod/pathod.py
@@ -353,6 +353,8 @@ class Pathod(tcp.TCPServer):
staticdir=self.staticdir
)
+ self.loglock = threading.Lock()
+
def check_policy(self, req, settings):
"""
A policy check that verifies the request size is within limits.
@@ -403,8 +405,7 @@ class Pathod(tcp.TCPServer):
def add_log(self, d):
if not self.noapi:
- lock = threading.Lock()
- with lock:
+ with self.loglock:
d["id"] = self.logid
self.log.insert(0, d)
if len(self.log) > self.LOGBUF:
@@ -413,17 +414,18 @@ class Pathod(tcp.TCPServer):
return d["id"]
def clear_log(self):
- lock = threading.Lock()
- with lock:
+ with self.loglock:
self.log = []
def log_by_id(self, identifier):
- for i in self.log:
- if i["id"] == identifier:
- return i
+ with self.loglock:
+ for i in self.log:
+ if i["id"] == identifier:
+ return i
def get_log(self):
- return self.log
+ with self.loglock:
+ return self.log
def main(args): # pragma: no cover
diff --git a/pathod/test.py b/pathod/test.py
index 23b7a5b6..32b37731 100644
--- a/pathod/test.py
+++ b/pathod/test.py
@@ -1,13 +1,11 @@
from six.moves import cStringIO as StringIO
import threading
+import time
+
from six.moves import queue
-import requests
-import requests.packages.urllib3
from . import pathod
-requests.packages.urllib3.disable_warnings()
-
class Daemon:
IFACE = "127.0.0.1"
@@ -39,39 +37,40 @@ class Daemon:
"""
return "%s/p/%s" % (self.urlbase, spec)
- def info(self):
- """
- Return some basic info about the remote daemon.
- """
- resp = requests.get("%s/api/info" % self.urlbase, verify=False)
- return resp.json()
-
def text_log(self):
return self.logfp.getvalue()
+ def expect_log(self, n, timeout=5):
+ l = []
+ start = time.time()
+ while True:
+ l = self.log()
+ if time.time() - start >= timeout:
+ return None
+ if len(l) >= n:
+ break
+ return l
+
def last_log(self):
"""
Returns the last logged request, or None.
"""
- l = self.log()
+ l = self.expect_log(1)
if not l:
return None
- return l[0]
+ return l[-1]
def log(self):
"""
Return the log buffer as a list of dictionaries.
"""
- resp = requests.get("%s/api/log" % self.urlbase, verify=False)
- return resp.json()["log"]
+ return self.thread.server.get_log()
def clear_log(self):
"""
Clear the log.
"""
- self.logfp.truncate(0)
- resp = requests.get("%s/api/clear_log" % self.urlbase, verify=False)
- return resp.ok
+ return self.thread.server.clear_log()
def shutdown(self):
"""
@@ -88,6 +87,7 @@ class _PaThread(threading.Thread):
self.name = "PathodThread"
self.iface, self.q, self.ssl = iface, q, ssl
self.daemonargs = daemonargs
+ self.server = None
def run(self):
self.server = pathod.Pathod(
diff --git a/setup.cfg b/setup.cfg
index e83ca0ab..eeaac0c8 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -11,8 +11,7 @@ addopts = --capture=no
[coverage:run]
branch = True
-include = *mitmproxy*, *netlib*, *pathod*
-omit = *contrib*, *tnetstring*, *platform*, *console*, *main.py
+omit = *contrib*, *tnetstring*, *platform*, *main.py
[coverage:report]
show_missing = True
diff --git a/test/pathod/test_app.py b/test/pathod/test_app.py
index ac89c44c..fbaa773c 100644
--- a/test/pathod/test_app.py
+++ b/test/pathod/test_app.py
@@ -11,11 +11,11 @@ class TestApp(tutils.DaemonTests):
def test_about(self):
r = self.getpath("/about")
- assert r.ok
+ assert r.status_code == 200
def test_download(self):
r = self.getpath("/download")
- assert r.ok
+ assert r.status_code == 200
def test_docs(self):
assert self.getpath("/docs/pathod").status_code == 200
@@ -27,7 +27,7 @@ class TestApp(tutils.DaemonTests):
def test_log(self):
assert self.getpath("/log").status_code == 200
assert self.get("200:da").status_code == 200
- id = self.d.log()[0]["id"]
+ id = self.d.expect_log(1)[0]["id"]
assert self.getpath("/log").status_code == 200
assert self.getpath("/log/%s" % id).status_code == 200
assert self.getpath("/log/9999999").status_code == 404
diff --git a/test/pathod/test_pathod.py b/test/pathod/test_pathod.py
index 4d969158..ec9c169f 100644
--- a/test/pathod/test_pathod.py
+++ b/test/pathod/test_pathod.py
@@ -1,7 +1,6 @@
from six.moves import cStringIO as StringIO
-import pytest
-from pathod import pathod, version
+from pathod import pathod
from netlib import tcp
from netlib.exceptions import HttpException, TlsException
import tutils
@@ -129,7 +128,6 @@ class CommonTests(tutils.DaemonTests):
assert self.d.last_log()
# FIXME: Other binary data elements
- @pytest.mark.skip(reason="race condition")
def test_sizelimit(self):
r = self.get("200:b@1g")
assert r.status_code == 800
@@ -140,21 +138,15 @@ class CommonTests(tutils.DaemonTests):
r, _ = self.pathoc([r"get:'/p/200':i0,'\r\n'"])
assert r[0].status_code == 200
- def test_info(self):
- assert tuple(self.d.info()["version"]) == version.IVERSION
-
- @pytest.mark.skip(reason="race condition")
def test_logs(self):
- assert self.d.clear_log()
- assert not self.d.last_log()
+ self.d.clear_log()
assert self.get("202:da")
- assert len(self.d.log()) == 1
- assert self.d.clear_log()
+ assert self.d.expect_log(1)
+ self.d.clear_log()
assert len(self.d.log()) == 0
def test_disconnect(self):
- rsp = self.get("202:b@100k:d200")
- assert len(rsp.content) < 200
+ tutils.raises("unexpected eof", self.get, "202:b@100k:d200")
def test_parserr(self):
rsp = self.get("400:msg,b:")
@@ -166,7 +158,7 @@ class CommonTests(tutils.DaemonTests):
assert rsp.content.strip() == "testfile"
def test_anchor(self):
- rsp = self.getpath("anchor/foo")
+ rsp = self.getpath("/anchor/foo")
assert rsp.status_code == 202
def test_invalid_first_line(self):
@@ -223,7 +215,6 @@ class CommonTests(tutils.DaemonTests):
)
assert r[1].payload == "test"
- @pytest.mark.skip(reason="race condition")
def test_websocket_frame_reflect_error(self):
r, _ = self.pathoc(
["ws:/p/", "wf:-mask:knone:f'wf:b@10':i13,'a'"],
@@ -233,7 +224,6 @@ class CommonTests(tutils.DaemonTests):
# FIXME: Race Condition?
assert "Parse error" in self.d.text_log()
- @pytest.mark.skip(reason="race condition")
def test_websocket_frame_disconnect_error(self):
self.pathoc(["ws:/p/", "wf:b@10:d3"], ws_read_limit=0)
assert self.d.last_log()
diff --git a/test/pathod/test_test.py b/test/pathod/test_test.py
index cee286a4..6399894e 100644
--- a/test/pathod/test_test.py
+++ b/test/pathod/test_test.py
@@ -2,6 +2,10 @@ import logging
import requests
from pathod import test
import tutils
+
+import requests.packages.urllib3
+
+requests.packages.urllib3.disable_warnings()
logging.disable(logging.CRITICAL)
diff --git a/test/pathod/tutils.py b/test/pathod/tutils.py
index f7bb22e5..a99a2fd3 100644
--- a/test/pathod/tutils.py
+++ b/test/pathod/tutils.py
@@ -3,6 +3,7 @@ import re
import shutil
import requests
from six.moves import cStringIO as StringIO
+import urllib
from netlib import tcp
from netlib import utils
@@ -66,7 +67,7 @@ class DaemonTests(object):
if not (self.noweb or self.noapi):
self.d.clear_log()
- def getpath(self, path, params=None):
+ def _getpath(self, path, params=None):
scheme = "https" if self.ssl else "http"
resp = requests.get(
"%s://localhost:%s/%s" % (
@@ -79,8 +80,28 @@ class DaemonTests(object):
)
return resp
+ def getpath(self, path, params=None):
+ logfp = StringIO()
+ c = pathoc.Pathoc(
+ ("localhost", self.d.port),
+ ssl=self.ssl,
+ fp=logfp,
+ )
+ c.connect()
+ if params:
+ path = path + "?" + urllib.urlencode(params)
+ resp = c.request("get:%s" % path)
+ return resp
+
def get(self, spec):
- resp = requests.get(self.d.p(spec), verify=False)
+ logfp = StringIO()
+ c = pathoc.Pathoc(
+ ("localhost", self.d.port),
+ ssl=self.ssl,
+ fp=logfp,
+ )
+ c.connect()
+ resp = c.request("get:/p/%s" % urllib.quote(spec).encode("string_escape"))
return resp
def pathoc(