diff --git a/.travis.yml b/.travis.yml index fd2fba3d6..cdc578519 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,4 +67,4 @@ cache: - /home/travis/virtualenv/python2.7.9/lib/python2.7/site-packages - /home/travis/virtualenv/python2.7.9/bin - /home/travis/virtualenv/pypy-2.5.0/site-packages - - /home/travis/virtualenv/pypy-2.5.0/bin \ No newline at end of file + - /home/travis/virtualenv/pypy-2.5.0/bin diff --git a/libpathod/language/http2.py b/libpathod/language/http2.py index 2c3f17869..829a05db6 100644 --- a/libpathod/language/http2.py +++ b/libpathod/language/http2.py @@ -1,6 +1,7 @@ import pyparsing as pp -from netlib.http import user_agents, semantics, Headers +from netlib import http +from netlib.http import user_agents, Headers from . import base, message """ @@ -184,7 +185,7 @@ class Response(_HTTP2Message): if body: body = body.string() - resp = semantics.Response( + resp = http.Response( (2, 0), self.code.string(), '', @@ -267,7 +268,7 @@ class Request(_HTTP2Message): if body: body = body.string() - req = semantics.Request( + req = http.Request( '', self.method.string(), '', diff --git a/libpathod/log.py b/libpathod/log.py index 69229adf1..f203542f9 100644 --- a/libpathod/log.py +++ b/libpathod/log.py @@ -63,7 +63,7 @@ class LogCtx(object): for line in netlib.utils.hexdump(data): self("\t%s %s %s" % line) else: - for i in netlib.utils.cleanBin(data).split("\n"): + for i in netlib.utils.clean_bin(data).split("\n"): self("\t%s" % i) def __call__(self, line): diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index ac0b0e4d3..d616761e8 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -10,8 +10,10 @@ import time import threading import OpenSSL.crypto +import six from netlib import tcp, http, certutils, websockets, socks +from netlib.exceptions import HttpException from netlib.http import http1, http2 import language.http @@ -19,7 +21,7 @@ import language.websockets from . import utils, log import logging -from netlib.http.http1 import HTTP1Protocol +from netlib.tutils import treq logging.getLogger("hpack").setLevel(logging.WARNING) @@ -213,7 +215,7 @@ class Pathoc(tcp.TCPClient): ) self.protocol = http2.HTTP2Protocol(self, dump_frames=self.http2_framedump) else: - self.protocol = http1.HTTP1Protocol(self) + self.protocol = http1 self.settings = language.Settings( is_client=True, @@ -229,15 +231,14 @@ class Pathoc(tcp.TCPClient): '\r\n' ) self.wfile.flush() - l = self.rfile.readline() - if not l: - raise PathocError("Proxy CONNECT failed") - parsed = self.protocol.parse_response_line(l) - if not parsed[1] == 200: - raise PathocError( - "Proxy CONNECT failed: %s - %s" % (parsed[1], parsed[2]) - ) - self.protocol.read_headers() + try: + resp = self.protocol.read_response(self.rfile, treq(method="CONNECT")) + if resp.status_code != 200: + raise HttpException("Unexpected status code: %s" % resp.status_code) + except HttpException as e: + six.reraise(PathocError, PathocError( + "Proxy CONNECT failed: %s" % repr(e) + )) def socks_connect(self, connect_to): try: @@ -288,9 +289,9 @@ class Pathoc(tcp.TCPClient): self.sslinfo = None if self.ssl: try: - alpn_protos = [HTTP1Protocol.ALPN_PROTO_HTTP1] + alpn_protos = [http.ALPN_PROTO_HTTP1] if self.use_http2: - alpn_protos.append(http2.HTTP2Protocol.ALPN_PROTO_H2) + alpn_protos.append(http.ALPN_PROTO_H2) self.convert_to_ssl( sni=self.sni, @@ -408,9 +409,9 @@ class Pathoc(tcp.TCPClient): req = language.serve(r, self.wfile, self.settings) self.wfile.flush() - resp = self.protocol.read_response(req["method"], None) + resp = self.protocol.read_response(self.rfile, treq(method=req["method"])) resp.sslinfo = self.sslinfo - except http.HttpError as v: + except HttpException as v: lg("Invalid server response: %s" % v) raise except tcp.NetLibTimeout: diff --git a/libpathod/pathod.py b/libpathod/pathod.py index 4b94ec91f..6478fe4f3 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -6,7 +6,8 @@ import threading import urllib from netlib import tcp, http, certutils, websockets -from netlib.http import http1, http2 +from netlib.exceptions import HttpException, HttpReadDisconnect +from netlib.http import ALPN_PROTO_HTTP1, ALPN_PROTO_H2 from . import version, app, language, utils, log, protocols import language.http @@ -40,7 +41,7 @@ class SSLOptions(object): ssl_options=tcp.SSL_DEFAULT_OPTIONS, ciphers=None, certs=None, - alpn_select=http2.HTTP2Protocol.ALPN_PROTO_H2, + alpn_select=ALPN_PROTO_H2, ): self.confdir = confdir self.cn = cn @@ -124,15 +125,14 @@ class PathodHandler(tcp.BaseHandler): """ with logger.ctx() as lg: try: - req = self.protocol.read_request() - except http.HttpError as s: + req = self.protocol.read_request(self.rfile) + except HttpReadDisconnect: + return None, None + except HttpException as s: s = str(s) lg(s) return None, dict(type="error", msg=s) - if isinstance(req, http.EmptyRequest): - return None, None - if req.method == 'CONNECT': return self.protocol.handle_http_connect([req.host, req.port, req.httpversion], lg) @@ -259,7 +259,7 @@ class PathodHandler(tcp.BaseHandler): return alp = self.get_alpn_proto_negotiated() - if alp == http2.HTTP2Protocol.ALPN_PROTO_H2: + if alp == ALPN_PROTO_H2: self.protocol = protocols.http2.HTTP2Protocol(self) self.use_http2 = True diff --git a/libpathod/protocols/http.py b/libpathod/protocols/http.py index 0539b68d2..531854d6a 100644 --- a/libpathod/protocols/http.py +++ b/libpathod/protocols/http.py @@ -1,14 +1,12 @@ -from netlib import tcp, http, wsgi -from netlib.http import http1 -from .. import version, app, language, utils, log +from netlib import tcp, wsgi +from netlib.exceptions import HttpReadDisconnect +from netlib.http import http1, Request +from .. import version, language -class HTTPProtocol: +class HTTPProtocol(object): def __init__(self, pathod_handler): self.pathod_handler = pathod_handler - self.wire_protocol = http1.HTTP1Protocol( - self.pathod_handler - ) def make_error_response(self, reason, body): return language.http.make_error_response(reason, body) @@ -70,4 +68,4 @@ class HTTPProtocol: return self.pathod_handler.handle_http_request, None def read_request(self, lg=None): - return self.wire_protocol.read_request(allow_empty=True) + return http1.read_request(self.pathod_handler.rfile) diff --git a/libpathod/protocols/http2.py b/libpathod/protocols/http2.py index f57f56f85..a098a14e6 100644 --- a/libpathod/protocols/http2.py +++ b/libpathod/protocols/http2.py @@ -14,7 +14,7 @@ class HTTP2Protocol: def read_request(self, lg=None): self.wire_protocol.perform_server_connection_preface() - return self.wire_protocol.read_request() + return self.wire_protocol.read_request(self.pathod_handler.rfile) def assemble(self, message): return self.wire_protocol.assemble(message) diff --git a/test/test_pathoc.py b/test/test_pathoc.py index ec68424a9..1e15c9eb1 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -5,9 +5,11 @@ import OpenSSL from mock import Mock from netlib import tcp, http, socks +from netlib.exceptions import HttpException from netlib.http import http1, http2 from libpathod import pathoc, test, version, pathod, language +from netlib.tutils import raises import tutils @@ -82,7 +84,7 @@ class _TestDaemon: r = r.freeze(language.Settings()) try: c.request(r) - except (http.HttpError, tcp.NetLibError): + except (HttpException, tcp.NetLibError): pass return s.getvalue() @@ -92,7 +94,7 @@ class TestDaemonSSL(_TestDaemon): ssloptions = pathod.SSLOptions( request_client_cert=True, sans=["test1.com", "test2.com"], - alpn_select=http2.HTTP2Protocol.ALPN_PROTO_H2, + alpn_select=http.ALPN_PROTO_H2, ) def test_sni(self): @@ -222,11 +224,13 @@ class TestDaemon(_TestDaemon): to = ("foobar", 80) c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None) c.rfile, c.wfile = cStringIO.StringIO(), cStringIO.StringIO() - tutils.raises("connect failed", c.http_connect, to) + with raises("connect failed"): + c.http_connect(to) c.rfile = cStringIO.StringIO( "HTTP/1.1 500 OK\r\n" ) - tutils.raises("connect failed", c.http_connect, to) + with raises("connect failed"): + c.http_connect(to) c.rfile = cStringIO.StringIO( "HTTP/1.1 200 OK\r\n" ) @@ -273,7 +277,7 @@ class TestDaemonHTTP2(_TestDaemon): c = pathoc.Pathoc( ("127.0.0.1", self.d.port), ) - assert isinstance(c.protocol, http1.HTTP1Protocol) + assert c.protocol == http1 def test_http2_alpn(self): c = pathoc.Pathoc( diff --git a/test/test_pathod.py b/test/test_pathod.py index c25de41fb..ed37385d1 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -4,6 +4,7 @@ import OpenSSL from libpathod import pathod, version from netlib import tcp, http +from netlib.exceptions import HttpException import tutils @@ -180,16 +181,16 @@ class CommonTests(tutils.DaemonTests): def test_invalid_content_length(self): tutils.raises( - http.HttpError, + HttpException, self.pathoc, ["get:/:h'content-length'='foo'"] ) l = self.d.last_log() assert l["type"] == "error" - assert "Content-Length unknown" in l["msg"] + assert "Unparseable Content Length" in l["msg"] def test_invalid_headers(self): - tutils.raises(http.HttpError, self.pathoc, ["get:/:h'\t'='foo'"]) + tutils.raises(HttpException, self.pathoc, ["get:/:h'\t'='foo'"]) l = self.d.last_log() assert l["type"] == "error" assert "Invalid headers" in l["msg"] @@ -247,7 +248,7 @@ class TestDaemon(CommonTests): def test_connect_err(self): tutils.raises( - http.HttpError, + HttpException, self.pathoc, [r"get:'http://foo.com/p/202':da"], connect_to=("localhost", self.d.port)