2022-06-21 17:20:08 -04:00
|
|
|
"""
|
|
|
|
Support for listening with both HTTP and Foolscap on the same port.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from enum import Enum
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
from twisted.internet.protocol import Protocol
|
|
|
|
from twisted.python.failure import Failure
|
2022-06-22 14:19:29 -04:00
|
|
|
from twisted.internet.ssl import CertificateOptions
|
|
|
|
from twisted.web.server import Site
|
|
|
|
from twisted.protocols.tls import TLSMemoryBIOFactory
|
2022-06-21 17:20:08 -04:00
|
|
|
|
|
|
|
from foolscap.negotiate import Negotiation
|
|
|
|
|
2022-06-22 14:19:29 -04:00
|
|
|
from .storage.http_server import HTTPServer
|
|
|
|
|
2022-06-22 10:23:23 -04:00
|
|
|
|
2022-06-21 17:20:08 -04:00
|
|
|
class ProtocolMode(Enum):
|
|
|
|
"""Listening mode."""
|
2022-06-22 10:23:23 -04:00
|
|
|
|
2022-06-21 17:20:08 -04:00
|
|
|
UNDECIDED = 0
|
|
|
|
FOOLSCAP = 1
|
|
|
|
HTTP = 2
|
|
|
|
|
|
|
|
|
|
|
|
class PretendToBeNegotiation(type):
|
|
|
|
"""😱"""
|
|
|
|
|
|
|
|
def __instancecheck__(self, instance):
|
|
|
|
return (instance.__class__ == self) or isinstance(instance, Negotiation)
|
|
|
|
|
|
|
|
|
|
|
|
class FoolscapOrHttp(Protocol, metaclass=PretendToBeNegotiation):
|
|
|
|
"""
|
|
|
|
Based on initial query, decide whether we're talking Foolscap or HTTP.
|
|
|
|
|
|
|
|
Pretends to be a ``foolscap.negotiate.Negotiation`` instance.
|
|
|
|
"""
|
2022-06-22 10:23:23 -04:00
|
|
|
|
|
|
|
_foolscap: Optional[Negotiation] = None
|
|
|
|
_protocol_mode: ProtocolMode = ProtocolMode.UNDECIDED
|
2022-06-21 17:20:08 -04:00
|
|
|
_buffer: bytes = b""
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
self._foolscap = Negotiation(*args, **kwargs)
|
|
|
|
|
|
|
|
def __setattr__(self, name, value):
|
2022-06-22 10:23:23 -04:00
|
|
|
if name in {
|
|
|
|
"_foolscap",
|
|
|
|
"_protocol_mode",
|
|
|
|
"_buffer",
|
|
|
|
"transport",
|
|
|
|
"__class__",
|
2022-06-22 14:19:29 -04:00
|
|
|
"_http",
|
2022-06-22 10:23:23 -04:00
|
|
|
}:
|
2022-06-21 17:20:08 -04:00
|
|
|
object.__setattr__(self, name, value)
|
|
|
|
else:
|
|
|
|
setattr(self._foolscap, name, value)
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
return getattr(self._foolscap, name)
|
|
|
|
|
|
|
|
def makeConnection(self, transport):
|
|
|
|
Protocol.makeConnection(self, transport)
|
|
|
|
self._foolscap.makeConnection(transport)
|
|
|
|
|
|
|
|
def initClient(self, *args, **kwargs):
|
2022-06-22 10:23:23 -04:00
|
|
|
# After creation, a Negotiation instance either has initClient() or
|
|
|
|
# initServer() called. SInce this is a client, we're never going to do
|
|
|
|
# HTTP. Relying on __getattr__/__setattr__ doesn't work, for some
|
|
|
|
# reason, so just mutate ourselves appropriately.
|
2022-06-21 17:20:08 -04:00
|
|
|
assert not self._buffer
|
2022-06-22 10:23:23 -04:00
|
|
|
self.__class__ = Negotiation
|
|
|
|
self.__dict__ = self._foolscap.__dict__
|
|
|
|
return self.initClient(*args, **kwargs)
|
2022-06-21 17:20:08 -04:00
|
|
|
|
|
|
|
def dataReceived(self, data: bytes) -> None:
|
|
|
|
if self._protocol_mode == ProtocolMode.FOOLSCAP:
|
|
|
|
return self._foolscap.dataReceived(data)
|
|
|
|
if self._protocol_mode == ProtocolMode.HTTP:
|
2022-06-22 14:19:29 -04:00
|
|
|
return self._http.dataReceived(data)
|
2022-06-21 17:20:08 -04:00
|
|
|
|
|
|
|
# UNDECIDED mode.
|
|
|
|
self._buffer += data
|
|
|
|
if len(self._buffer) < 8:
|
|
|
|
return
|
|
|
|
|
2022-06-22 10:23:23 -04:00
|
|
|
# Check if it looks like a Foolscap request. If so, it can handle this
|
2022-06-21 17:20:08 -04:00
|
|
|
# and later data:
|
|
|
|
if self._buffer.startswith(b"GET /id/"):
|
2022-06-22 14:19:29 -04:00
|
|
|
# TODO or maybe just self.__class__ here too?
|
2022-06-21 17:20:08 -04:00
|
|
|
self._protocol_mode = ProtocolMode.FOOLSCAP
|
|
|
|
buf, self._buffer = self._buffer, b""
|
|
|
|
return self._foolscap.dataReceived(buf)
|
|
|
|
else:
|
|
|
|
self._protocol_mode = ProtocolMode.HTTP
|
2022-06-22 14:19:29 -04:00
|
|
|
|
|
|
|
certificate_options = CertificateOptions(
|
|
|
|
privateKey=self.certificate.privateKey.original,
|
|
|
|
certificate=self.certificate.original,
|
|
|
|
)
|
|
|
|
http_server = HTTPServer(self.storage_server, self.swissnum)
|
|
|
|
factory = TLSMemoryBIOFactory(
|
|
|
|
certificate_options, False, Site(http_server.get_resource())
|
|
|
|
)
|
|
|
|
protocol = factory.buildProtocol(self.transport.getPeer())
|
|
|
|
protocol.makeConnection(self.transport)
|
|
|
|
protocol.dataReceived(self._buffer)
|
2022-06-22 14:20:42 -04:00
|
|
|
# TODO maybe change the __class__
|
2022-06-22 14:19:29 -04:00
|
|
|
self._http = protocol
|
2022-06-21 17:20:08 -04:00
|
|
|
|
|
|
|
def connectionLost(self, reason: Failure) -> None:
|
|
|
|
if self._protocol_mode == ProtocolMode.FOOLSCAP:
|
|
|
|
return self._foolscap.connectionLost(reason)
|