diff options
Diffstat (limited to 'libpathod/protocols')
-rw-r--r-- | libpathod/protocols/__init__.py | 1 | ||||
-rw-r--r-- | libpathod/protocols/http.py | 110 | ||||
-rw-r--r-- | libpathod/protocols/http2.py | 20 | ||||
-rw-r--r-- | libpathod/protocols/websockets.py | 54 |
4 files changed, 185 insertions, 0 deletions
diff --git a/libpathod/protocols/__init__.py b/libpathod/protocols/__init__.py new file mode 100644 index 00000000..1a8c7dab --- /dev/null +++ b/libpathod/protocols/__init__.py @@ -0,0 +1 @@ +from . import http, http2, websockets diff --git a/libpathod/protocols/http.py b/libpathod/protocols/http.py new file mode 100644 index 00000000..bccdc786 --- /dev/null +++ b/libpathod/protocols/http.py @@ -0,0 +1,110 @@ +from netlib import tcp, http, http2, wsgi, certutils, websockets, odict +from .. import version, app, language, utils, log + +class HTTPProtocol: + + def __init__(self, pathod_handler): + self.pathod_handler = pathod_handler + + def make_error_response(self, reason, body): + return language.http.make_error_response(reason, body) + + def handle_http_app(self, method, path, headers, content, 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, headers, content) + 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. + """ + http.read_headers(self.pathod_handler.rfile) + self.pathod_handler.wfile.write( + 'HTTP/1.1 200 Connection established\r\n' + + ('Proxy-agent: %s\r\n' % version.NAMEVERSION) + + '\r\n' + ) + self.pathod_handler.wfile.flush() + if not self.pathod_handler.server.ssloptions.not_after_connect: + try: + cert, key, chain_file_ = self.pathod_handler.server.ssloptions.get_cert( + connect[0] + ) + self.pathod_handler.convert_to_ssl( + cert, + key, + handle_sni=self.pathod_handler._handle_sni, + request_client_cert=self.pathod_handler.server.ssloptions.request_client_cert, + cipher_list=self.pathod_handler.server.ssloptions.ciphers, + method=self.pathod_handler.server.ssloptions.ssl_version, + alpn_select=self.pathod_handler.server.ssloptions.alpn_select, + ) + except tcp.NetLibError as v: + s = str(v) + lg(s) + return None, dict(type="error", msg=s) + return self.pathod_handler.handle_http_request, None + + def read_request(self, lg): + line = http.get_request_line(self.pathod_handler.rfile) + if not line: + # Normal termination + return dict() + + m = utils.MemBool() + if m(http.parse_init_connect(line)): + return dict(next_handle=self.handle_http_connect(m.v, lg)) + elif m(http.parse_init_proxy(line)): + method, _, _, _, path, httpversion = m.v + elif m(http.parse_init_http(line)): + method, path, httpversion = m.v + else: + s = "Invalid first line: %s" % repr(line) + lg(s) + return dict(errors=dict(type="error", msg=s)) + + headers = http.read_headers(self.pathod_handler.rfile) + if headers is None: + s = "Invalid headers" + lg(s) + return dict(errors=dict(type="error", msg=s)) + + try: + body = http.read_http_body( + self.pathod_handler.rfile, + headers, + None, + method, + None, + True, + ) + except http.HttpError as s: + s = str(s) + lg(s) + return dict(errors=dict(type="error", msg=s)) + + return dict( + method=method, + path=path, + headers=headers, + body=body, + httpversion=httpversion) diff --git a/libpathod/protocols/http2.py b/libpathod/protocols/http2.py new file mode 100644 index 00000000..29c4e556 --- /dev/null +++ b/libpathod/protocols/http2.py @@ -0,0 +1,20 @@ +from netlib import tcp, http, http2, wsgi, certutils, websockets, odict +from .. import version, app, language, utils, log + +class HTTP2Protocol: + + def __init__(self, pathod_handler): + self.pathod_handler = pathod_handler + self.wire_protocol = http2.HTTP2Protocol( + self.pathod_handler, is_server=True, dump_frames=self.pathod_handler.http2_framedump + ) + + def make_error_response(self, reason, body): + return language.http2.make_error_response(reason, body) + + def read_request(self): + self.wire_protocol.perform_server_connection_preface() + return self.wire_protocol.read_request() + + def create_response(self, code, stream_id, headers, body): + return self.wire_protocol.create_response(code, stream_id, headers, body) diff --git a/libpathod/protocols/websockets.py b/libpathod/protocols/websockets.py new file mode 100644 index 00000000..334f9e9c --- /dev/null +++ b/libpathod/protocols/websockets.py @@ -0,0 +1,54 @@ +import time + +from netlib import tcp, http, http2, wsgi, certutils, websockets, odict +from .. import version, app, language, utils, log + +class WebsocketsProtocol: + + def __init__(self, pathod_handler): + self.pathod_handler = pathod_handler + + def handle_websocket(self, logger): + while True: + with logger.ctx() as lg: + started = time.time() + try: + frm = websockets.Frame.from_file(self.pathod_handler.rfile) + except tcp.NetLibIncomplete as e: + lg("Error reading websocket frame: %s" % e) + break + ended = time.time() + lg(frm.human_readable()) + retlog = dict( + type="inbound", + protocol="websockets", + started=started, + duration=ended - started, + frame=dict( + ), + cipher=None, + ) + if self.pathod_handler.ssl_established: + retlog["cipher"] = self.pathod_handler.get_current_cipher() + self.pathod_handler.addlog(retlog) + ld = language.websockets.NESTED_LEADER + if frm.payload.startswith(ld): + nest = frm.payload[len(ld):] + try: + wf_gen = language.parse_websocket_frame(nest) + except language.exceptions.ParseException as v: + logger.write( + "Parse error in reflected frame specifcation:" + " %s" % v.msg + ) + return None, None + for frm in wf_gen: + with logger.ctx() as lg: + frame_log = language.serve( + frm, + self.pathod_handler.wfile, + self.pathod_handler.settings + ) + lg("crafting websocket spec: %s" % frame_log["spec"]) + self.pathod_handler.addlog(frame_log) + return self.handle_websocket, None |