💾 Archived View for code.lanterne.chilliet.eu › vendor › mcmic › gemini-server › src › Request.php captured on 2024-07-08 at 23:37:11.

View Raw

More Information

⬅️ Previous capture (2021-12-17)

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

<?php

declare(strict_types=1);

namespace MCMic\Gemini;

class Request
{
    public ?string $scheme;
    public ?string $host;
    public ?string $punycodedHost;
    public ?int $port;
    public ?string $user;
    public ?string $pass;
    public ?string $path;
    public ?string $query;
    public ?string $fragment;

    public function __construct(string $request)
    {
        $url = parse_url(trim($request));

        if ($url === false) {
            throw new Exception('Malformed URL', 59);
        }

        $this->scheme   = $url['scheme'] ?? null;
        $this->host     = $url['host'] ?? null;
        $this->port     = $url['port'] ?? null;
        $this->user     = $url['user'] ?? null;
        $this->pass     = $url['pass'] ?? null;
        $this->path     = $url['path'] ?? null;
        $this->query    = $url['query'] ?? null;
        $this->fragment = $url['fragment'] ?? null;

        if (isset($this->scheme)) {
            $this->scheme = mb_strtolower($this->scheme);
        }
        if (isset($this->path)) {
            $this->path = rawurldecode($this->path);
        }
        if (isset($this->query)) {
            $this->query = rawurldecode($this->query);
        }
        if (isset($this->host)) {
            $this->host = mb_strtolower($this->host);
            $idn        = \idn_to_utf8($this->host);
            $punycode   = \idn_to_ascii($this->host);
            if (($idn === false) || ($punycode === false)) {
                throw new Exception('Error while attempting to decode IDN', 59);
            }
            $this->host             = $idn;
            $this->punycodedHost    = $punycode;
        }
    }

    /**
     * @throws \MCMic\Gemini\Exception
     */
    public function assertValid(): void
    {
        if (!isset($this->scheme)) {
            throw new Exception('Missing scheme', 59);
        }
        if ($this->scheme !== 'gemini') {
            throw new Exception('Bad protocol', 53);
        }
    }

    public function __toString(): string
    {
        return $this->buildUrl();
    }

    public static function urlEncode(string $str): string
    {
        return join('/', array_map('rawurlencode', explode('/', ($str))));
    }

    public function resolveLink(string $link): Request
    {
        $request = new Request($link);
        $request->scheme ??= $this->scheme;
        $request->host ??= $this->host;
        if (!isset($request->path)) {
            $request->path = $this->path;
        } elseif (!isset($request->path[0]) || ($request->path[0] != '/')) {
            $request->path = $this->path . '/' . $request->path;
        }
        return $request;
    }

    public function buildUrl(?string $path = null): string
    {
        $url = '';
        if (isset($this->scheme)) {
            $url .= $this->scheme . '://';
        }
        if (isset($this->user)) {
            $url .= $this->user;
        }
        if (isset($this->pass)) {
            $url .= ':' . $this->pass;
        }
        if (isset($this->user) || isset($this->pass)) {
            $url .= '@';
        }
        if (isset($this->host)) {
            $url .= $this->host;
        }
        if (isset($this->port)) {
            $url .= ':' . $this->port;
        }
        $url .= static::urlEncode($path ?? $this->path ?? '');
        if (isset($this->query)) {
            $url .= '?' . rawurlencode($this->query);
        }
        if (isset($this->fragment)) {
            $url .= '#' . $this->fragment;
        }
        return $url;
    }

    public function sendTo(?string $host = null, ?int $port = null): Response
    {
        $host = $host ?? $this->host;
        $port = $port ?? $this->port ?? 1965;
        $context = stream_context_create(
            [
                'ssl' => [
                    'verify_peer'       => true,
                    'verify_peer_name'  => false,
                    'allow_self_signed' => true,
                    'verify_depth'      => 0,
                    'SNI_enabled'       => true,
                    'capture_peer_cert' => true,
                ]
            ]
        );
        $timeout = ini_get('default_socket_timeout');
        if (($timeout === false) || $timeout === '') {
            $timeout = 60;
        }
        $socket = stream_socket_client(
            'tcp://' . $host . ':' . $port,
            $errno,
            $errstr,
            (float) $timeout,
            STREAM_CLIENT_CONNECT,
            $context,
        );
        if ($socket === false) {
            throw new \Exception($errstr, $errno);
        } else {
            $tlsSuccess = stream_socket_enable_crypto(
                $socket,
                true,
                STREAM_CRYPTO_METHOD_TLS_CLIENT
                & ~STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT
                & ~STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
                | 1
            );
            if ($tlsSuccess !== true) {
                fclose($socket);
                throw new \Exception('Failed TLS');
            }
            fwrite($socket, $this->buildUrl() . "\r\n");
            $header = fgets($socket, 1024);
            if ($header === false) {
                throw new \Exception('No answer');
            }
            [$status, $meta] = explode(' ', $header, 2);
            $status = (int)$status;
            $body = null;
            if (($status >= 20) && ($status < 30)) {
                $body = '';
                while (!feof($socket)) {
                    $body .= fgets($socket, 1024);
                }
            }
            fclose($socket);
            return new Response($status, $meta, $body);
        }
    }
}