Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
namespace Ratchet\RFC6455\Handshake;
3
use Psr\Http\Message\RequestInterface;
4
use GuzzleHttp\Psr7\Response;
5
 
6
/**
7
 * The latest version of the WebSocket protocol
8
 * @todo Unicode: return mb_convert_encoding(pack("N",$u), mb_internal_encoding(), 'UCS-4BE');
9
 */
10
class ServerNegotiator implements NegotiatorInterface {
11
    /**
12
     * @var \Ratchet\RFC6455\Handshake\RequestVerifier
13
     */
14
    private $verifier;
15
 
16
    private $_supportedSubProtocols = [];
17
 
18
    private $_strictSubProtocols = false;
19
 
20
    private $enablePerMessageDeflate = false;
21
 
22
    public function __construct(RequestVerifier $requestVerifier, $enablePerMessageDeflate = false) {
23
        $this->verifier = $requestVerifier;
24
 
25
        // https://bugs.php.net/bug.php?id=73373
26
        // https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18
27
        $supported = PermessageDeflateOptions::permessageDeflateSupported();
28
        if ($enablePerMessageDeflate && !$supported) {
29
            throw new \Exception('permessage-deflate is not supported by your PHP version (need >=7.1.4 or >=7.0.18).');
30
        }
31
        if ($enablePerMessageDeflate && !function_exists('deflate_add')) {
32
            throw new \Exception('permessage-deflate is not supported because you do not have the zlib extension.');
33
        }
34
 
35
        $this->enablePerMessageDeflate = $enablePerMessageDeflate;
36
    }
37
 
38
    /**
39
     * {@inheritdoc}
40
     */
41
    public function isProtocol(RequestInterface $request) {
42
        return $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version'));
43
    }
44
 
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function getVersionNumber() {
49
        return RequestVerifier::VERSION;
50
    }
51
 
52
    /**
53
     * {@inheritdoc}
54
     */
55
    public function handshake(RequestInterface $request) {
56
        if (true !== $this->verifier->verifyMethod($request->getMethod())) {
57
            return new Response(405, ['Allow' => 'GET']);
58
        }
59
 
60
        if (true !== $this->verifier->verifyHTTPVersion($request->getProtocolVersion())) {
61
            return new Response(505);
62
        }
63
 
64
        if (true !== $this->verifier->verifyRequestURI($request->getUri()->getPath())) {
65
            return new Response(400);
66
        }
67
 
68
        if (true !== $this->verifier->verifyHost($request->getHeader('Host'))) {
69
            return new Response(400);
70
        }
71
 
72
        $upgradeSuggestion = [
73
            'Connection'             => 'Upgrade',
74
            'Upgrade'                => 'websocket',
75
            'Sec-WebSocket-Version'  => $this->getVersionNumber()
76
        ];
77
        if (count($this->_supportedSubProtocols) > 0) {
78
            $upgradeSuggestion['Sec-WebSocket-Protocol'] = implode(', ', array_keys($this->_supportedSubProtocols));
79
        }
80
        if (true !== $this->verifier->verifyUpgradeRequest($request->getHeader('Upgrade'))) {
81
            return new Response(426, $upgradeSuggestion, null, '1.1', 'Upgrade header MUST be provided');
82
        }
83
 
84
        if (true !== $this->verifier->verifyConnection($request->getHeader('Connection'))) {
85
            return new Response(400, [], null, '1.1', 'Connection Upgrade MUST be requested');
86
        }
87
 
88
        if (true !== $this->verifier->verifyKey($request->getHeader('Sec-WebSocket-Key'))) {
89
            return new Response(400, [], null, '1.1', 'Invalid Sec-WebSocket-Key');
90
        }
91
 
92
        if (true !== $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version'))) {
93
            return new Response(426, $upgradeSuggestion);
94
        }
95
 
96
        $headers = [];
97
        $subProtocols = $request->getHeader('Sec-WebSocket-Protocol');
98
        if (count($subProtocols) > 0 || (count($this->_supportedSubProtocols) > 0 && $this->_strictSubProtocols)) {
99
            $subProtocols = array_map('trim', explode(',', implode(',', $subProtocols)));
100
 
101
            $match = array_reduce($subProtocols, function($accumulator, $protocol) {
102
                return $accumulator ?: (isset($this->_supportedSubProtocols[$protocol]) ? $protocol : null);
103
            }, null);
104
 
105
            if ($this->_strictSubProtocols && null === $match) {
106
                return new Response(426, $upgradeSuggestion, null, '1.1', 'No Sec-WebSocket-Protocols requested supported');
107
            }
108
 
109
            if (null !== $match) {
110
                $headers['Sec-WebSocket-Protocol'] = $match;
111
            }
112
        }
113
 
114
        $response = new Response(101, array_merge($headers, [
115
            'Upgrade'              => 'websocket'
116
            , 'Connection'           => 'Upgrade'
117
            , 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0])
118
            , 'X-Powered-By'         => 'Ratchet'
119
        ]));
120
 
121
        try {
122
            $perMessageDeflateRequest = PermessageDeflateOptions::fromRequestOrResponse($request)[0];
123
        } catch (InvalidPermessageDeflateOptionsException $e) {
124
            return new Response(400, [], null, '1.1', $e->getMessage());
125
        }
126
 
127
        if ($this->enablePerMessageDeflate && $perMessageDeflateRequest->isEnabled()) {
128
            $response = $perMessageDeflateRequest->addHeaderToResponse($response);
129
        }
130
 
131
        return $response;
132
    }
133
 
134
    /**
135
     * Used when doing the handshake to encode the key, verifying client/server are speaking the same language
136
     * @param  string $key
137
     * @return string
138
     * @internal
139
     */
140
    public function sign($key) {
141
        return base64_encode(sha1($key . static::GUID, true));
142
    }
143
 
144
    /**
145
     * @param array $protocols
146
     */
147
    function setSupportedSubProtocols(array $protocols) {
148
        $this->_supportedSubProtocols = array_flip($protocols);
149
    }
150
 
151
    /**
152
     * If enabled and support for a subprotocol has been added handshake
153
     *  will not upgrade if a match between request and supported subprotocols
154
     * @param boolean $enable
155
     * @todo Consider extending this interface and moving this there.
156
     *       The spec does says the server can fail for this reason, but
157
     * it is not a requirement. This is an implementation detail.
158
     */
159
    function setStrictSubProtocolCheck($enable) {
160
        $this->_strictSubProtocols = (boolean)$enable;
161
    }
162
}