Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
 
3
namespace React\Socket;
4
 
5
use React\EventLoop\LoopInterface;
6
use React\Promise\Deferred;
7
use RuntimeException;
8
use UnexpectedValueException;
9
 
10
/**
11
 * This class is considered internal and its API should not be relied upon
12
 * outside of Socket.
13
 *
14
 * @internal
15
 */
16
class StreamEncryption
17
{
18
    private $loop;
19
    private $method;
20
    private $server;
21
 
22
    public function __construct(LoopInterface $loop, $server = true)
23
    {
24
        $this->loop = $loop;
25
        $this->server = $server;
26
 
27
        // support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3.
28
        // As of PHP 7.2+ the main crypto method constant includes all TLS versions.
29
        // As of PHP 5.6+ the crypto method is a bitmask, so we explicitly include all TLS versions.
30
        // For legacy PHP < 5.6 the crypto method is a single value only and this constant includes all TLS versions.
31
        // @link https://3v4l.org/9PSST
32
        if ($server) {
33
            $this->method = \STREAM_CRYPTO_METHOD_TLS_SERVER;
34
 
35
            if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) {
36
                $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; // @codeCoverageIgnore
37
            }
38
        } else {
39
            $this->method = \STREAM_CRYPTO_METHOD_TLS_CLIENT;
40
 
41
            if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) {
42
                $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; // @codeCoverageIgnore
43
            }
44
        }
45
    }
46
 
47
    public function enable(Connection $stream)
48
    {
49
        return $this->toggle($stream, true);
50
    }
51
 
52
    public function toggle(Connection $stream, $toggle)
53
    {
54
        // pause actual stream instance to continue operation on raw stream socket
55
        $stream->pause();
56
 
57
        // TODO: add write() event to make sure we're not sending any excessive data
58
 
59
        // cancelling this leaves this stream in an inconsistent state…
60
        $deferred = new Deferred(function () {
61
            throw new \RuntimeException();
62
        });
63
 
64
        // get actual stream socket from stream instance
65
        $socket = $stream->stream;
66
 
67
        // get crypto method from context options or use global setting from constructor
68
        $method = $this->method;
69
        $context = \stream_context_get_options($socket);
70
        if (isset($context['ssl']['crypto_method'])) {
71
            $method = $context['ssl']['crypto_method'];
72
        }
73
 
74
        $that = $this;
75
        $toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) {
76
            $that->toggleCrypto($socket, $deferred, $toggle, $method);
77
        };
78
 
79
        $this->loop->addReadStream($socket, $toggleCrypto);
80
 
81
        if (!$this->server) {
82
            $toggleCrypto();
83
        }
84
 
85
        $loop = $this->loop;
86
 
87
        return $deferred->promise()->then(function () use ($stream, $socket, $loop, $toggle) {
88
            $loop->removeReadStream($socket);
89
 
90
            $stream->encryptionEnabled = $toggle;
91
            $stream->resume();
92
 
93
            return $stream;
94
        }, function($error) use ($stream, $socket, $loop) {
95
            $loop->removeReadStream($socket);
96
            $stream->resume();
97
            throw $error;
98
        });
99
    }
100
 
101
    public function toggleCrypto($socket, Deferred $deferred, $toggle, $method)
102
    {
103
        $error = null;
104
        \set_error_handler(function ($_, $errstr) use (&$error) {
105
            $error = \str_replace(array("\r", "\n"), ' ', $errstr);
106
 
107
            // remove useless function name from error message
108
            if (($pos = \strpos($error, "): ")) !== false) {
109
                $error = \substr($error, $pos + 3);
110
            }
111
        });
112
 
113
        $result = \stream_socket_enable_crypto($socket, $toggle, $method);
114
 
115
        \restore_error_handler();
116
 
117
        if (true === $result) {
118
            $deferred->resolve();
119
        } else if (false === $result) {
120
            // overwrite callback arguments for PHP7+ only, so they do not show
121
            // up in the Exception trace and do not cause a possible cyclic reference.
122
            $d = $deferred;
123
            $deferred = null;
124
 
125
            if (\feof($socket) || $error === null) {
126
                // EOF or failed without error => connection closed during handshake
127
                $d->reject(new \UnexpectedValueException(
128
                    'Connection lost during TLS handshake',
129
                    \defined('SOCKET_ECONNRESET') ? \SOCKET_ECONNRESET : 0
130
                ));
131
            } else {
132
                // handshake failed with error message
133
                $d->reject(new \UnexpectedValueException(
134
                    'Unable to complete TLS handshake: ' . $error
135
                ));
136
            }
137
        } else {
138
            // need more data, will retry
139
        }
140
    }
141
}