💾 Archived View for code.lanterne.chilliet.eu › vendor › mcmic › gemini-server › src › Server.php captured on 2021-12-17 at 13:26:06.

View Raw

More Information

-=-=-=-=-=-=-

<?php

declare(strict_types=1);

namespace MCMic\Gemini;

use Psr\Log;

class Server
{
    protected string $transport = 'tcp';
    protected int $port = 1965;
    protected RequestLogger $requestLogger;

    /**
     * @var array<string,RequestHandler>
     */
    protected array $handlers;

    public function __construct(int $port = 1965, ?RequestLogger $logger = null)
    {
        $this->port = $port;
        $this->requestLogger = ($logger ?? new RequestLogger());
    }

    public function addHandler(RequestHandler $handler): void
    {
        $key = \idn_to_ascii($handler->host);
        if ($key === false) {
            throw new \Exception('Failed to punycode IDN');
        }
        $this->handlers[$key] = $handler;
    }

    public function serve(): void
    {
        $context = stream_context_create(
            [
                'ssl' => [
                    'verify_peer'       => false,
                    'verify_peer_name'  => false,
                    'allow_self_signed' => true,
                    'verify_depth'      => 0,
                    'SNI_enabled'       => true,
                    'SNI_server_certs' => array_map(
                        function (RequestHandler $handler): string {
                            return $handler->certPath;
                        },
                        $this->handlers
                    ),
                    //~ If set to true a peer_certificate context option will be created containing the peer certificate.
                    'capture_peer_cert' => true,
                ]
            ]
        );
        $socket = stream_socket_server(
            $this->transport . '://[::]:' . $this->port,
            $errno,
            $errstr,
            STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
            $context,
        );


        if ($socket === false) {
            throw new \Exception($errstr, $errno);
        } else {
            $this->requestLogger->logger->info('Listening…');
            while ($conn = stream_socket_accept($socket, -1, $peername)) {
                /* Enable TLS, any method but 1.0/1.1, meaning 1.2, and 1.3 if available in the PHP version */
                $tlsSuccess = stream_socket_enable_crypto(
                    $conn,
                    true,
                    STREAM_CRYPTO_METHOD_TLS_SERVER
                    & ~STREAM_CRYPTO_METHOD_TLSv1_0_SERVER
                    & ~STREAM_CRYPTO_METHOD_TLSv1_1_SERVER
                );
                if ($tlsSuccess !== true) {
                    fclose($conn);
                    continue;
                }
                //~ print_r(stream_context_get_options($conn));
                $requestString = fread($conn, 1024);
                try {
                    if ($requestString === false) {
                        $this->requestLogger->logRequest($peername, '');
                        throw new Exception('Bad request', 50);
                    }
                    $this->requestLogger->logRequest($peername, $requestString);
                    $request = new Request($requestString);
                    $request->assertValid();
                    if (isset($this->handlers[$request->punycodedHost])) {
                        $response = $this->handlers[$request->punycodedHost]->handleWrapper($request);
                        fwrite($conn, (string)$response);
                        $this->requestLogger->logResponse($peername, $request, $response);
                    } else {
                        throw new Exception('Bad hostname', 50);
                    }
                } catch (Exception $e) {
                    fwrite($conn, (string)$e);
                    if (isset($request)) {
                        $this->requestLogger->logException($peername, $request, $e);
                    } elseif (is_string($requestString)) {
                        $this->requestLogger->logException($peername, $requestString, $e);
                    } else {
                        $this->requestLogger->logException($peername, '', $e);
                    }
                }
                fclose($conn);
            }
            fclose($socket);
        }
    }
}