Completed
Push — master ( e26ab3...1dd3f4 )
by Maxime
9s
created

ClientHandshake::verify()   D

Complexity

Conditions 9
Paths 7

Size

Total Lines 42
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
dl 0
loc 42
ccs 0
cts 34
cp 0
rs 4.909
c 0
b 0
f 0
cc 9
eloc 22
nc 7
nop 2
crap 90
1
<?php
2
3
/**
4
 * This file is a part of Woketo package.
5
 *
6
 * (c) Nekland <[email protected]>
7
 *
8
 * For the full license, take a look to the LICENSE file
9
 * on the root directory of this project
10
 */
11
12
namespace Nekland\Woketo\Rfc6455\Handshake;
13
14
use Nekland\Woketo\Exception\RuntimeException;
15
use Nekland\Woketo\Exception\UnsupportedException;
16
use Nekland\Woketo\Exception\WebsocketException;
17
use Nekland\Woketo\Http\AbstractHttpMessage;
18
use Nekland\Woketo\Http\Request;
19
use Nekland\Woketo\Http\Response;
20
use Nekland\Woketo\Http\Url;
21
22
class ClientHandshake implements HandshakeInterface
23
{
24
    const WEBSOCKET_VERSION = 13;
25
    
26
    /**
27
     * {@inheritdoc}
28
     */
29
    public function sign(AbstractHttpMessage $request, string $key = null)
30
    {
31
        if (!$request instanceof Request) {
32
            throw new RuntimeException(sprintf('Expected request at first argument but got %s', gettype($request)));
33
        }
34
35
        $key = '';
36
        for ($i = 0; $i < 16; $i++) {
37
            $key .= chr(mt_rand(0,255));
38
        }
39
40
        $key = base64_encode($key);
41
42
        $request->setKey($key);
43
44
        return $key;
45
    }
46
47
    /**
48
     * {@inheritdoc}
49
     *
50
     * Learn more here: https://tools.ietf.org/html/rfc6455#section-4.1
51
     */
52
    public function verify(AbstractHttpMessage $response, string $key = null)
53
    {
54
        if (!$response instanceof Response) {
55
            throw new RuntimeException(
56
                sprintf('The client handshake cannot verify something else than a Response object, %s given.', get_class($response))
57
            );
58
        }
59
60
        // If the http code is not 101 there's a problem.
61
        if ($response->getStatusCode() !== 101) {
62
            if ($response->getStatusCode() === 401) {
63
                throw new UnsupportedException(
64
                    'This WebSocket server needs an HTTP authentication but Woketo doesn\'t supports it for now.'
65
                );
66
            }
67
68
            throw new WebsocketException(
69
                sprintf('Attempting to get a 101 response from the server but got %s.', $response->getStatusCode())
70
            );
71
        }
72
73
        $remoteSignature = $response->getHeader('Sec-WebSocket-Accept');
74
        if (
75
            \strtolower($response->getHeader('Upgrade')) !== 'websocket'
76
            || \strtolower($response->getHeader('Connection')) !== 'upgrade'
77
            || empty($remoteSignature)
78
        ) {
79
            throw new WebsocketException('The server doesn\'t answer properly to the handshake.');
80
        }
81
82
        $signature = \base64_encode(\sha1($key . HandshakeInterface::GUID, true));
83
84
        if ($remoteSignature !== $signature) {
85
            throw new WebsocketException('The remote signature is invalid.');
86
        }
87
88
        if (!empty($response->getHeader('Sec-WebSocket-Protocol', null))) {
89
            throw new WebsocketException('The server specified a WebSocket subprotocol but none is supported by Woketo.');
90
        }
91
92
        return true;
93
    }
94
95
    /**
96
     * @param Url $url
97
     * @return Request
98
     */
99
    public function getRequest(Url $url)
100
    {
101
        $request = Request::createClientRequest($url->getUri(), $url->getHost(), $url->getPort());
102
        $request->setVersion(ClientHandshake::WEBSOCKET_VERSION);
103
        $request->setKey(base64_encode(ClientHandshake::generateRandom16BytesKey()));
104
105
        return $request;
106
    }
107
108
    /**
109
     * @return string
110
     */
111
    public static function generateRandom16BytesKey()
112
    {
113
        $bytes = '';
114
115
        for($i = 0; $i < 16; $i++) {
116
            $bytes .= chr(mt_rand(0, 255));
117
        }
118
119
        return $bytes;
120
    }
121
}
122