Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
namespace Ratchet\WebSocket\Version;
3
use Ratchet\ConnectionInterface;
4
use Ratchet\MessageInterface;
5
use Ratchet\WebSocket\Version\RFC6455\HandshakeVerifier;
6
use Ratchet\WebSocket\Version\RFC6455\Message;
7
use Ratchet\WebSocket\Version\RFC6455\Frame;
8
use Ratchet\WebSocket\Version\RFC6455\Connection;
9
use Ratchet\WebSocket\Encoding\ValidatorInterface;
10
use Ratchet\WebSocket\Encoding\Validator;
11
use Guzzle\Http\Message\RequestInterface;
12
use Guzzle\Http\Message\Response;
13
 
14
/**
15
 * The latest version of the WebSocket protocol
16
 * @link http://tools.ietf.org/html/rfc6455
17
 * @todo Unicode: return mb_convert_encoding(pack("N",$u), mb_internal_encoding(), 'UCS-4BE');
18
 */
19
class RFC6455 implements VersionInterface {
20
    const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
21
 
22
    /**
23
     * @var RFC6455\HandshakeVerifier
24
     */
25
    protected $_verifier;
26
 
27
    /**
28
     * A lookup of the valid close codes that can be sent in a frame
29
     * @var array
30
     */
31
    private $closeCodes = array();
32
 
33
    /**
34
     * @var \Ratchet\WebSocket\Encoding\ValidatorInterface
35
     */
36
    protected $validator;
37
 
38
    public function __construct(ValidatorInterface $validator = null) {
39
        $this->_verifier = new HandshakeVerifier;
40
        $this->setCloseCodes();
41
 
42
        if (null === $validator) {
43
            $validator = new Validator;
44
        }
45
 
46
        $this->validator = $validator;
47
    }
48
 
49
    /**
50
     * {@inheritdoc}
51
     */
52
    public function isProtocol(RequestInterface $request) {
53
        $version = (int)(string)$request->getHeader('Sec-WebSocket-Version');
54
 
55
        return ($this->getVersionNumber() === $version);
56
    }
57
 
58
    /**
59
     * {@inheritdoc}
60
     */
61
    public function getVersionNumber() {
62
        return 13;
63
    }
64
 
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function handshake(RequestInterface $request) {
69
        if (true !== $this->_verifier->verifyAll($request)) {
70
            return new Response(400);
71
        }
72
 
73
        return new Response(101, array(
74
            'Upgrade'              => 'websocket'
75
          , 'Connection'           => 'Upgrade'
76
          , 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key'))
77
        ));
78
    }
79
 
80
    /**
81
     * @param  \Ratchet\ConnectionInterface $conn
82
     * @param  \Ratchet\MessageInterface    $coalescedCallback
83
     * @return \Ratchet\WebSocket\Version\RFC6455\Connection
84
     */
85
    public function upgradeConnection(ConnectionInterface $conn, MessageInterface $coalescedCallback) {
86
        $upgraded = new Connection($conn);
87
 
88
        if (!isset($upgraded->WebSocket)) {
89
            $upgraded->WebSocket = new \StdClass;
90
        }
91
 
92
        $upgraded->WebSocket->coalescedCallback = $coalescedCallback;
93
 
94
        return $upgraded;
95
    }
96
 
97
    /**
98
     * @param \Ratchet\WebSocket\Version\RFC6455\Connection $from
99
     * @param string                                        $data
100
     */
101
    public function onMessage(ConnectionInterface $from, $data) {
102
        $overflow = '';
103
 
104
        if (!isset($from->WebSocket->message)) {
105
            $from->WebSocket->message = $this->newMessage();
106
        }
107
 
108
        // There is a frame fragment attached to the connection, add to it
109
        if (!isset($from->WebSocket->frame)) {
110
            $from->WebSocket->frame = $this->newFrame();
111
        }
112
 
113
        $from->WebSocket->frame->addBuffer($data);
114
        if ($from->WebSocket->frame->isCoalesced()) {
115
            $frame = $from->WebSocket->frame;
116
 
117
            if (false !== $frame->getRsv1() ||
118
                false !== $frame->getRsv2() ||
119
                false !== $frame->getRsv3()
120
            ) {
121
                return $from->close($frame::CLOSE_PROTOCOL);
122
            }
123
 
124
            if (!$frame->isMasked()) {
125
                return $from->close($frame::CLOSE_PROTOCOL);
126
            }
127
 
128
            $opcode = $frame->getOpcode();
129
 
130
            if ($opcode > 2) {
131
                if ($frame->getPayloadLength() > 125 || !$frame->isFinal()) {
132
                    return $from->close($frame::CLOSE_PROTOCOL);
133
                }
134
 
135
                switch ($opcode) {
136
                    case $frame::OP_CLOSE:
137
                        $closeCode = 0;
138
 
139
                        $bin = $frame->getPayload();
140
 
141
                        if (empty($bin)) {
142
                            return $from->close();
143
                        }
144
 
145
                        if (strlen($bin) >= 2) {
146
                            list($closeCode) = array_merge(unpack('n*', substr($bin, 0, 2)));
147
                        }
148
 
149
                        if (!$this->isValidCloseCode($closeCode)) {
150
                            return $from->close($frame::CLOSE_PROTOCOL);
151
                        }
152
 
153
                        if (!$this->validator->checkEncoding(substr($bin, 2), 'UTF-8')) {
154
                            return $from->close($frame::CLOSE_BAD_PAYLOAD);
155
                        }
156
 
157
                        $frame->unMaskPayload();
158
 
159
                        return $from->close($frame);
160
                    break;
161
                    case $frame::OP_PING:
162
                        $from->send($this->newFrame($frame->getPayload(), true, $frame::OP_PONG));
163
                    break;
164
                    case $frame::OP_PONG:
165
                    break;
166
                    default:
167
                        return $from->close($frame::CLOSE_PROTOCOL);
168
                    break;
169
                }
170
 
171
                $overflow = $from->WebSocket->frame->extractOverflow();
172
 
173
                unset($from->WebSocket->frame, $frame, $opcode);
174
 
175
                if (strlen($overflow) > 0) {
176
                    $this->onMessage($from, $overflow);
177
                }
178
 
179
                return;
180
            }
181
 
182
            $overflow = $from->WebSocket->frame->extractOverflow();
183
 
184
            if ($frame::OP_CONTINUE == $frame->getOpcode() && 0 == count($from->WebSocket->message)) {
185
                return $from->close($frame::CLOSE_PROTOCOL);
186
            }
187
 
188
            if (count($from->WebSocket->message) > 0 && $frame::OP_CONTINUE != $frame->getOpcode()) {
189
                return $from->close($frame::CLOSE_PROTOCOL);
190
            }
191
 
192
            $from->WebSocket->message->addFrame($from->WebSocket->frame);
193
            unset($from->WebSocket->frame);
194
        }
195
 
196
        if ($from->WebSocket->message->isCoalesced()) {
197
            $parsed = $from->WebSocket->message->getPayload();
198
            unset($from->WebSocket->message);
199
 
200
            if (!$this->validator->checkEncoding($parsed, 'UTF-8')) {
201
                return $from->close(Frame::CLOSE_BAD_PAYLOAD);
202
            }
203
 
204
            $from->WebSocket->coalescedCallback->onMessage($from, $parsed);
205
        }
206
 
207
        if (strlen($overflow) > 0) {
208
            $this->onMessage($from, $overflow);
209
        }
210
    }
211
 
212
    /**
213
     * @return RFC6455\Message
214
     */
215
    public function newMessage() {
216
        return new Message;
217
    }
218
 
219
    /**
220
     * @param string|null $payload
221
     * @param bool|null   $final
222
     * @param int|null    $opcode
223
     * @return RFC6455\Frame
224
     */
225
    public function newFrame($payload = null, $final = null, $opcode = null) {
226
        return new Frame($payload, $final, $opcode);
227
    }
228
 
229
    /**
230
     * Used when doing the handshake to encode the key, verifying client/server are speaking the same language
231
     * @param  string $key
232
     * @return string
233
     * @internal
234
     */
235
    public function sign($key) {
236
        return base64_encode(sha1($key . static::GUID, true));
237
    }
238
 
239
    /**
240
     * Determine if a close code is valid
241
     * @param int|string
242
     * @return bool
243
     */
244
    public function isValidCloseCode($val) {
245
        if (array_key_exists($val, $this->closeCodes)) {
246
            return true;
247
        }
248
 
249
        if ($val >= 3000 && $val <= 4999) {
250
            return true;
251
        }
252
 
253
        return false;
254
    }
255
 
256
    /**
257
     * Creates a private lookup of valid, private close codes
258
     */
259
    protected function setCloseCodes() {
260
        $this->closeCodes[Frame::CLOSE_NORMAL]      = true;
261
        $this->closeCodes[Frame::CLOSE_GOING_AWAY]  = true;
262
        $this->closeCodes[Frame::CLOSE_PROTOCOL]    = true;
263
        $this->closeCodes[Frame::CLOSE_BAD_DATA]    = true;
264
        //$this->closeCodes[Frame::CLOSE_NO_STATUS]   = true;
265
        //$this->closeCodes[Frame::CLOSE_ABNORMAL]    = true;
266
        $this->closeCodes[Frame::CLOSE_BAD_PAYLOAD] = true;
267
        $this->closeCodes[Frame::CLOSE_POLICY]      = true;
268
        $this->closeCodes[Frame::CLOSE_TOO_BIG]     = true;
269
        $this->closeCodes[Frame::CLOSE_MAND_EXT]    = true;
270
        $this->closeCodes[Frame::CLOSE_SRV_ERR]     = true;
271
        //$this->closeCodes[Frame::CLOSE_TLS]         = true;
272
    }
273
}