diff options
author | Aldo Cortesi <aldo@nullcube.com> | 2016-06-05 13:04:13 +1200 |
---|---|---|
committer | Aldo Cortesi <aldo@nullcube.com> | 2016-06-05 13:04:13 +1200 |
commit | 48da24ae7e7be7af94162d35a463f174e22504f6 (patch) | |
tree | 1559a5847123d98a68cfd752689c0bf5c5669577 | |
parent | c0c45c051a6eadf34d38f1b0d3d96c6a9196f733 (diff) | |
download | mitmproxy-48da24ae7e7be7af94162d35a463f174e22504f6.tar.gz mitmproxy-48da24ae7e7be7af94162d35a463f174e22504f6.tar.bz2 mitmproxy-48da24ae7e7be7af94162d35a463f174e22504f6.zip |
First-order removal of pathod API and app
-rw-r--r-- | pathod/app.py | 180 | ||||
-rw-r--r-- | pathod/pathod.py | 38 | ||||
-rw-r--r-- | pathod/protocols/http.py | 25 | ||||
-rw-r--r-- | test/pathod/test_app.py | 82 | ||||
-rw-r--r-- | test/pathod/test_pathoc.py | 125 | ||||
-rw-r--r-- | test/pathod/test_pathod.py | 10 |
6 files changed, 62 insertions, 398 deletions
diff --git a/pathod/app.py b/pathod/app.py deleted file mode 100644 index e3216c58..00000000 --- a/pathod/app.py +++ /dev/null @@ -1,180 +0,0 @@ -import logging -import pprint -import io -import copy -from flask import Flask, jsonify, render_template, request, abort, make_response -from . import version, language -from netlib.http import user_agents -from netlib import strutils - -logging.basicConfig(level="DEBUG") -EXAMPLE_HOST = "example.com" -EXAMPLE_WEBSOCKET_KEY = "examplekey" - -# pylint: disable=unused-variable - - -def make_app(noapi, debug): - app = Flask(__name__) - app.debug = debug - - if not noapi: - @app.route('/api/info') - def api_info(): - return jsonify( - version=version.IVERSION - ) - - @app.route('/api/log') - def api_log(): - return jsonify( - log=app.config["pathod"].get_log() - ) - - @app.route('/api/clear_log') - def api_clear_log(): - app.config["pathod"].clear_log() - return "OK" - - def render(s, cacheable, **kwargs): - kwargs["noapi"] = app.config["pathod"].noapi - kwargs["nocraft"] = app.config["pathod"].nocraft - kwargs["craftanchor"] = app.config["pathod"].craftanchor - resp = make_response(render_template(s, **kwargs), 200) - if cacheable: - resp.headers["Cache-control"] = "public, max-age=4320" - return resp - - @app.route('/') - @app.route('/index.html') - def index(): - return render( - "index.html", - True, - section="main", - version=version.VERSION - ) - - @app.route('/download') - @app.route('/download.html') - def download(): - return render( - "download.html", True, section="download", version=version.VERSION - ) - - @app.route('/about') - @app.route('/about.html') - def about(): - return render("about.html", True, section="about") - - @app.route('/docs/pathod') - def docs_pathod(): - return render( - "docs_pathod.html", True, section="docs", subsection="pathod" - ) - - @app.route('/docs/language') - def docs_language(): - return render( - "docs_lang.html", True, - section="docs", uastrings=user_agents.UASTRINGS, - subsection="lang" - ) - - @app.route('/docs/pathoc') - def docs_pathoc(): - return render( - "docs_pathoc.html", True, section="docs", subsection="pathoc" - ) - - @app.route('/docs/lib_pathod') - def docs_lib_pathod(): - return render( - "docs_lib_pathod.html", True, section="docs", subsection="pathod" - ) - - @app.route('/docs/test') - def docs_test(): - return render( - "docs_test.html", True, section="docs", subsection="test" - ) - - @app.route('/log') - def log(): - if app.config["pathod"].noapi: - abort(404) - return render( - "log.html", - False, - section="log", - log=app.config["pathod"].get_log() - ) - - @app.route('/log/<int:lid>') - def onelog(lid): - item = app.config["pathod"].log_by_id(int(lid)) - if not item: - abort(404) - l = pprint.pformat(item) - return render("onelog.html", False, section="log", alog=l, lid=lid) - - def _preview(is_request): - if is_request: - template = "request_preview.html" - else: - template = "response_preview.html" - - spec = request.args["spec"] - - args = dict( - spec=spec, - section="main", - syntaxerror=None, - error=None, - ) - if not spec.strip(): - args["error"] = "Can't parse an empty spec." - return render(template, False, **args) - - try: - if is_request: - r = language.parse_pathoc(spec).next() - else: - r = language.parse_pathod(spec).next() - except language.ParseException as v: - args["syntaxerror"] = str(v) - args["marked"] = v.marked() - return render(template, False, **args) - - s = io.BytesIO() - - settings = copy.copy(app.config["pathod"].settings) - settings.request_host = EXAMPLE_HOST - settings.websocket_key = EXAMPLE_WEBSOCKET_KEY - - safe = r.preview_safe() - err, safe = app.config["pathod"].check_policy( - safe, - settings - ) - if err: - args["error"] = err - return render(template, False, **args) - if is_request: - settings.request_host = EXAMPLE_HOST - language.serve(safe, s, settings) - else: - settings.websocket_key = EXAMPLE_WEBSOCKET_KEY - language.serve(safe, s, settings) - - args["output"] = strutils.bytes_to_escaped_str(s.getvalue()) - return render(template, False, **args) - - @app.route('/response_preview') - def response_preview(): - return _preview(False) - - @app.route('/request_preview') - def request_preview(): - return _preview(True) - return app diff --git a/pathod/pathod.py b/pathod/pathod.py index 0449c0c1..add33944 100644 --- a/pathod/pathod.py +++ b/pathod/pathod.py @@ -10,7 +10,7 @@ from netlib import tcp, certutils, websockets from netlib.exceptions import HttpException, HttpReadDisconnect, TcpTimeout, TcpDisconnect, \ TlsException -from . import version, app, language, utils, log, protocols +from . import version, language, utils, log, protocols DEFAULT_CERT_DOMAIN = "pathod.net" @@ -136,7 +136,6 @@ class PathodHandler(tcp.BaseHandler): path = req.path http_version = req.http_version headers = req.headers - body = req.content clientcert = None if self.clientcert: @@ -203,24 +202,27 @@ class PathodHandler(tcp.BaseHandler): self.server.craftanchor )]) - if anchor_gen: - spec = anchor_gen.next() + if not anchor_gen: + anchor_gen = iter([self.make_http_error_response( + "Not found", + "No valid craft request found" + )]) - if self.use_http2 and isinstance(spec, language.http2.Response): - spec.stream_id = req.stream_id + spec = anchor_gen.next() - lg("crafting spec: %s" % spec) - nexthandler, retlog["response"] = self.http_serve_crafted( - spec, - lg - ) - if nexthandler and websocket_key: - self.protocol = protocols.websockets.WebsocketsProtocol(self) - return self.protocol.handle_websocket, retlog - else: - return nexthandler, retlog + if self.use_http2 and isinstance(spec, language.http2.Response): + spec.stream_id = req.stream_id + + lg("crafting spec: %s" % spec) + nexthandler, retlog["response"] = self.http_serve_crafted( + spec, + lg + ) + if nexthandler and websocket_key: + self.protocol = protocols.websockets.WebsocketsProtocol(self) + return self.protocol.handle_websocket, retlog else: - return self.protocol.handle_http_app(method, path, headers, body, lg) + return nexthandler, retlog def make_http_error_response(self, reason, body=None): resp = self.protocol.make_error_response(reason, body) @@ -343,8 +345,6 @@ class Pathod(tcp.TCPServer): self.explain = explain self.logfp = logfp - self.app = app.make_app(noapi, webdebug) - self.app.config["pathod"] = self self.log = [] self.logid = 0 self.anchors = anchors diff --git a/pathod/protocols/http.py b/pathod/protocols/http.py index d09b5bf2..6eefb34f 100644 --- a/pathod/protocols/http.py +++ b/pathod/protocols/http.py @@ -1,4 +1,3 @@ -from netlib import wsgi from netlib.exceptions import TlsException from netlib.http import http1 from .. import version, language @@ -11,30 +10,6 @@ class HTTPProtocol(object): def make_error_response(self, reason, body): return language.http.make_error_response(reason, body) - def handle_http_app(self, method, path, headers, body, lg): - """ - Handle a request to the built-in app. - """ - if self.pathod_handler.server.noweb: - crafted = self.pathod_handler.make_http_error_response("Access Denied") - language.serve(crafted, self.pathod_handler.wfile, self.pathod_handler.settings) - return None, dict( - type="error", - msg="Access denied: web interface disabled" - ) - lg("app: %s %s" % (method, path)) - req = wsgi.Request("http", method, path, b"HTTP/1.1", headers, body) - flow = wsgi.Flow(self.pathod_handler.address, req) - sn = self.pathod_handler.connection.getsockname() - a = wsgi.WSGIAdaptor( - self.pathod_handler.server.app, - sn[0], - self.pathod_handler.server.address.port, - version.NAMEVERSION - ) - a.serve(flow, self.pathod_handler.wfile) - return self.pathod_handler.handle_http_request, None - def handle_http_connect(self, connect, lg): """ Handle a CONNECT request. diff --git a/test/pathod/test_app.py b/test/pathod/test_app.py deleted file mode 100644 index 19888c75..00000000 --- a/test/pathod/test_app.py +++ /dev/null @@ -1,82 +0,0 @@ -import tutils - - -class TestApp(tutils.DaemonTests): - SSL = False - - def test_index(self): - r = self.getpath("/") - assert r.status_code == 200 - assert r.content - - def test_about(self): - r = self.getpath("/about") - assert r.status_code == 200 - - def test_download(self): - r = self.getpath("/download") - assert r.status_code == 200 - - def test_docs(self): - assert self.getpath("/docs/pathod").status_code == 200 - assert self.getpath("/docs/pathoc").status_code == 200 - assert self.getpath("/docs/language").status_code == 200 - assert self.getpath("/docs/pathod").status_code == 200 - assert self.getpath("/docs/test").status_code == 200 - - def test_log(self): - assert self.getpath("/log").status_code == 200 - assert self.get("200:da").status_code == 200 - 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 - - def test_response_preview(self): - r = self.getpath("/response_preview", params=dict(spec="200")) - assert r.status_code == 200 - assert 'Response' in r.content - - r = self.getpath("/response_preview", params=dict(spec="foo")) - assert r.status_code == 200 - assert 'Error' in r.content - - r = self.getpath("/response_preview", params=dict(spec="200:b@100m")) - assert r.status_code == 200 - assert "too large" in r.content - - r = self.getpath("/response_preview", params=dict(spec="200:b@5k")) - assert r.status_code == 200 - assert 'Response' in r.content - - r = self.getpath( - "/response_preview", - params=dict( - spec="200:b<nonexistent")) - assert r.status_code == 200 - assert 'File access denied' in r.content - - r = self.getpath("/response_preview", params=dict(spec="200:b<file")) - assert r.status_code == 200 - assert 'testfile' in r.content - - def test_request_preview(self): - r = self.getpath("/request_preview", params=dict(spec="get:/")) - assert r.status_code == 200 - assert 'Request' in r.content - - r = self.getpath("/request_preview", params=dict(spec="foo")) - assert r.status_code == 200 - assert 'Error' in r.content - - r = self.getpath("/request_preview", params=dict(spec="get:/:b@100m")) - assert r.status_code == 200 - assert "too large" in r.content - - r = self.getpath("/request_preview", params=dict(spec="get:/:b@5k")) - assert r.status_code == 200 - assert 'Request' in r.content - - r = self.getpath("/request_preview", params=dict(spec="")) - assert r.status_code == 200 - assert 'empty spec' in r.content diff --git a/test/pathod/test_pathoc.py b/test/pathod/test_pathoc.py index 18d7f672..a88cfa91 100644 --- a/test/pathod/test_pathoc.py +++ b/test/pathod/test_pathoc.py @@ -1,7 +1,4 @@ -import json from six.moves import cStringIO as StringIO -import re -import pytest from mock import Mock from netlib import http @@ -9,10 +6,9 @@ from netlib import tcp from netlib.exceptions import NetlibException from netlib.http import http1, http2 -from pathod import pathoc, test, version, pathod, language +from pathod import pathoc, language from netlib.tutils import raises import tutils -from test.mitmproxy.tutils import skip_windows def test_response(): @@ -20,37 +16,7 @@ def test_response(): assert repr(r) -class _TestDaemon: - ssloptions = pathod.SSLOptions() - - @classmethod - def setup_class(cls): - cls.d = test.Daemon( - ssl=cls.ssl, - ssloptions=cls.ssloptions, - staticdir=tutils.test_data.path("data"), - anchors=[ - (re.compile("/anchor/.*"), "202") - ] - ) - - @classmethod - def teardown_class(cls): - cls.d.shutdown() - - def setUp(self): - self.d.clear_log() - - def test_info(self): - c = pathoc.Pathoc( - ("127.0.0.1", self.d.port), - ssl=self.ssl, - fp=None - ) - c.connect() - resp = c.request("get:/api/info") - assert tuple(json.loads(resp.content)["version"]) == version.IVERSION - +class PathocTestDaemon(tutils.DaemonTests): def tval( self, requests, @@ -77,23 +43,23 @@ class _TestDaemon: showsummary=showsummary, fp=s ) - c.connect(showssl=showssl, fp=s) - if timeout: - c.settimeout(timeout) - for i in requests: - r = language.parse_pathoc(i).next() - if explain: - r = r.freeze(language.Settings()) - try: - c.request(r) - except NetlibException: - pass + with c.connect(showssl=showssl, fp=s): + if timeout: + c.settimeout(timeout) + for i in requests: + r = language.parse_pathoc(i).next() + if explain: + r = r.freeze(language.Settings()) + try: + c.request(r) + except NetlibException: + pass return s.getvalue() -class TestDaemonSSL(_TestDaemon): +class TestDaemonSSL(PathocTestDaemon): ssl = True - ssloptions = pathod.SSLOptions( + ssloptions = dict( request_client_cert=True, sans=["test1.com", "test2.com"], alpn_select=b'h2', @@ -106,11 +72,10 @@ class TestDaemonSSL(_TestDaemon): sni="foobar.com", fp=None ) - c.connect() - c.request("get:/p/200") - r = c.request("get:/api/log") - d = json.loads(r.content) - assert d["log"][0]["request"]["sni"] == "foobar.com" + with c.connect(): + c.request("get:/p/200") + log = self.d.log() + assert log[0]["request"]["sni"] == "foobar.com" def test_showssl(self): assert "certificate chain" in self.tval(["get:/p/200"], showssl=True) @@ -122,11 +87,11 @@ class TestDaemonSSL(_TestDaemon): clientcert=tutils.test_data.path("data/clientcert/client.pem"), fp=None ) - c.connect() - c.request("get:/p/200") - r = c.request("get:/api/log") - d = json.loads(r.content) - assert d["log"][0]["request"]["clientcert"]["keyinfo"] + with c.connect(): + c.request("get:/p/200") + + log = self.d.log() + assert log[0]["request"]["clientcert"]["keyinfo"] def test_http2_without_ssl(self): fp = StringIO() @@ -139,7 +104,7 @@ class TestDaemonSSL(_TestDaemon): tutils.raises(NotImplementedError, c.connect) -class TestDaemon(_TestDaemon): +class TestDaemon(PathocTestDaemon): ssl = False def test_ssl_error(self): @@ -163,7 +128,7 @@ class TestDaemon(_TestDaemon): 201]) assert "202" in self.tval(["get:'/p/202:b@1'"], ignorecodes=[200, 201]) - def test_timeout(self): + def _test_timeout(self): assert "Timeout" in self.tval(["get:'/p/200:p0,100'"], timeout=0.01) assert "HTTP" in self.tval( ["get:'/p/200:p5,100'"], @@ -178,8 +143,8 @@ class TestDaemon(_TestDaemon): ) def test_showresp(self): - reqs = ["get:/api/info:p0,0", "get:/api/info:p0,0"] - assert self.tval(reqs).count("200") == 2 + reqs = ["get:/p/200:da", "get:/p/200:da"] + assert self.tval(reqs).count("200 OK") == 2 assert self.tval(reqs, showresp=True).count("HTTP/1.1 200 OK") == 2 assert self.tval( reqs, showresp=True, hexdump=True @@ -195,8 +160,8 @@ class TestDaemon(_TestDaemon): assert "b@100" not in self.tval(reqs, explain=True) def test_showreq(self): - reqs = ["get:/api/info:p0,0", "get:/api/info:p0,0"] - assert self.tval(reqs, showreq=True).count("GET /api") == 2 + reqs = ["get:/p/200:da", "get:/p/200:da"] + assert self.tval(reqs, showreq=True).count("GET /p/200") == 2 assert self.tval( reqs, showreq=True, hexdump=True ).count("0000000000") == 2 @@ -206,23 +171,20 @@ class TestDaemon(_TestDaemon): def test_websocket_shutdown(self): c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None) - c.connect() - c.request("ws:/") - c.stop() + with c.connect(): + c.request("ws:/") - @skip_windows - @pytest.mark.skip(reason="race condition") def test_wait_finish(self): c = pathoc.Pathoc( ("127.0.0.1", self.d.port), fp=None, ws_read_limit=1 ) - c.connect() - c.request("ws:/") - c.request("wf:f'wf:x100'") - [i for i in c.wait(timeout=0, finish=False)] - [i for i in c.wait(timeout=0)] + with c.connect(): + c.request("ws:/") + c.request("wf:f'wf:x100'") + [i for i in c.wait(timeout=0, finish=False)] + [i for i in c.wait(timeout=0)] def test_connect_fail(self): to = ("foobar", 80) @@ -264,7 +226,7 @@ class TestDaemon(_TestDaemon): c.socks_connect(("example.com", 0xDEAD)) -class TestDaemonHTTP2(_TestDaemon): +class TestDaemonHTTP2(PathocTestDaemon): ssl = True if tcp.HAS_ALPN: @@ -295,10 +257,9 @@ class TestDaemonHTTP2(_TestDaemon): tmp_convert_to_ssl = c.convert_to_ssl c.convert_to_ssl = Mock() c.convert_to_ssl.side_effect = tmp_convert_to_ssl - c.connect() - - _, kwargs = c.convert_to_ssl.call_args - assert set(kwargs['alpn_protos']) == set([b'http/1.1', b'h2']) + with c.connect(): + _, kwargs = c.convert_to_ssl.call_args + assert set(kwargs['alpn_protos']) == set([b'http/1.1', b'h2']) def test_request(self): c = pathoc.Pathoc( @@ -307,6 +268,6 @@ class TestDaemonHTTP2(_TestDaemon): ssl=True, use_http2=True, ) - c.connect() - resp = c.request("get:/p/200") + with c.connect(): + resp = c.request("get:/p/200") assert resp.status_code == 200 diff --git a/test/pathod/test_pathod.py b/test/pathod/test_pathod.py index ec9c169f..f64b0f61 100644 --- a/test/pathod/test_pathod.py +++ b/test/pathod/test_pathod.py @@ -43,16 +43,6 @@ class TestTimeout(tutils.DaemonTests): assert self.d.last_log()["type"] == "timeout" -class TestNoApi(tutils.DaemonTests): - noapi = True - - def test_noapi(self): - assert self.getpath("/log").status_code == 404 - r = self.getpath("/") - assert r.status_code == 200 - assert "Log" not in r.content - - class TestNotAfterConnect(tutils.DaemonTests): ssl = False ssloptions = dict( |