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\Hixie76\Connection;
6
use Guzzle\Http\Message\RequestInterface;
7
use Guzzle\Http\Message\Response;
8
use Ratchet\WebSocket\Version\Hixie76\Frame;
9
 
10
/**
11
 * FOR THE LOVE OF BEER, PLEASE PLEASE PLEASE DON'T allow the use of this in your application!
12
 * Hixie76 is bad for 2 (there's more) reasons:
13
 *  1) The handshake is done in HTTP, which includes a key for signing in the body...
14
 *     BUT there is no Length defined in the header (as per HTTP spec) so the TCP buffer can't tell when the message is done!
15
 *  2) By nature it's insecure.  Google did a test study where they were able to do a
16
 *     man-in-the-middle attack on 10%-15% of the people who saw their ad who had a browser (currently only Safari) supporting the Hixie76 protocol.
17
 *     This was exploited by taking advantage of proxy servers in front of the user who ignored some HTTP headers in the handshake
18
 * The Hixie76 is currently implemented by Safari
19
 * @link http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
20
 */
21
class Hixie76 implements VersionInterface {
22
    /**
23
     * {@inheritdoc}
24
     */
25
    public function isProtocol(RequestInterface $request) {
26
        return !(null === $request->getHeader('Sec-WebSocket-Key2'));
27
    }
28
 
29
    /**
30
     * {@inheritdoc}
31
     */
32
    public function getVersionNumber() {
33
        return 0;
34
    }
35
 
36
    /**
37
     * @param  \Guzzle\Http\Message\RequestInterface $request
38
     * @return \Guzzle\Http\Message\Response
39
     * @throws \UnderflowException If there hasn't been enough data received
40
     */
41
    public function handshake(RequestInterface $request) {
42
        $body = substr($request->getBody(), 0, 8);
43
        if (8 !== strlen($body)) {
44
            throw new \UnderflowException("Not enough data received to issue challenge response");
45
        }
46
 
47
        $challenge = $this->sign((string)$request->getHeader('Sec-WebSocket-Key1'), (string)$request->getHeader('Sec-WebSocket-Key2'), $body);
48
 
49
        $headers = array(
50
            'Upgrade'                => 'WebSocket'
51
          , 'Connection'             => 'Upgrade'
52
          , 'Sec-WebSocket-Origin'   => (string)$request->getHeader('Origin')
53
          , 'Sec-WebSocket-Location' => 'ws://' . (string)$request->getHeader('Host') . $request->getPath()
54
        );
55
 
56
        $response = new Response(101, $headers, $challenge);
57
        $response->setStatus(101, 'WebSocket Protocol Handshake');
58
 
59
        return $response;
60
    }
61
 
62
    /**
63
     * {@inheritdoc}
64
     */
65
    public function upgradeConnection(ConnectionInterface $conn, MessageInterface $coalescedCallback) {
66
        $upgraded = new Connection($conn);
67
 
68
        if (!isset($upgraded->WebSocket)) {
69
            $upgraded->WebSocket = new \StdClass;
70
        }
71
 
72
        $upgraded->WebSocket->coalescedCallback = $coalescedCallback;
73
 
74
        return $upgraded;
75
    }
76
 
77
    public function onMessage(ConnectionInterface $from, $data) {
78
        $overflow = '';
79
 
80
        if (!isset($from->WebSocket->frame)) {
81
            $from->WebSocket->frame = $this->newFrame();
82
        }
83
 
84
        $from->WebSocket->frame->addBuffer($data);
85
        if ($from->WebSocket->frame->isCoalesced()) {
86
            $overflow = $from->WebSocket->frame->extractOverflow();
87
 
88
            $parsed = $from->WebSocket->frame->getPayload();
89
            unset($from->WebSocket->frame);
90
 
91
            $from->WebSocket->coalescedCallback->onMessage($from, $parsed);
92
 
93
            unset($from->WebSocket->frame);
94
        }
95
 
96
        if (strlen($overflow) > 0) {
97
            $this->onMessage($from, $overflow);
98
        }
99
    }
100
 
101
    public function newFrame() {
102
        return new Frame;
103
    }
104
 
105
    public function generateKeyNumber($key) {
106
        if (0 === substr_count($key, ' ')) {
107
            return 0;
108
        }
109
 
110
        return preg_replace('[\D]', '', $key) / substr_count($key, ' ');
111
    }
112
 
113
    protected function sign($key1, $key2, $code) {
114
        return md5(
115
            pack('N', $this->generateKeyNumber($key1))
116
          . pack('N', $this->generateKeyNumber($key2))
117
          . $code
118
        , true);
119
    }
120
}