Completed
Pull Request — master (#100)
by Maxime
02:21
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
21
class ClientHandshake implements HandshakeInterface
22
{
23
    /**
24
     * {@inheritdoc}
25
     */
26
    public function sign(AbstractHttpMessage $request, string $key = null)
27
    {
28
        if (!$request instanceof Request) {
29
            throw new RuntimeException(sprintf('Expected request at first argument but got %s', gettype($request)));
30
        }
31
32
        $key = '';
33
        for ($i = 0; $i < 16; $i++) {
34
            $key .= chr(mt_rand(0,255));
35
        }
36
37
        $key = base64_encode($key);
38
39
        $request->setKey($key);
40
41
        return $key;
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     *
47
     * Learn more here: https://tools.ietf.org/html/rfc6455#section-4.1
48
     */
49
    public function verify(AbstractHttpMessage $response, string $key = null)
50
    {
51
        if (!$response instanceof Response) {
52
            throw new RuntimeException(
53
                sprintf('The client handshake cannot verify something else than a Response object, %s given.', get_class($response))
54
            );
55
        }
56
57
        // If the http code is not 101 there's a problem.
58
        if ($response->getStatusCode() !== 101) {
59
            if ($response->getStatusCode() === 401) {
60
                throw new UnsupportedException(
61
                    'This WebSocket server needs an HTTP authentication but Woketo doesn\'t supports it for now.'
62
                );
63
            }
64
65
            throw new WebsocketException(
66
                sprintf('Attempting to get a 101 response from the server but got %s.', $response->getStatusCode())
67
            );
68
        }
69
70
        $remoteSignature = $response->getHeader('Sec-WebSocket-Accept');
71
        if (
72
            \strtolower($response->getHeader('Upgrade')) !== 'websocket'
73
            || \strtolower($response->getHeader('Connection')) !== 'upgrade'
74
            || empty($remoteSignature)
75
        ) {
76
            throw new WebsocketException('The server doesn\'t answer properly to the handshake.');
77
        }
78
79
        $signature = \base64_encode(\sha1($key . HandshakeInterface::GUID, true));
80
81
        if ($remoteSignature !== $signature) {
82
            throw new WebsocketException('The remote signature is invalid.');
83
        }
84
85
        if (!empty($response->getHeader('Sec-WebSocket-Protocol', null))) {
86
            throw new WebsocketException('The server specified a WebSocket subprotocol but none is supported by Woketo.');
87
        }
88
89
        return true;
90
    }
91
92
    /**
93
     * @param string $uri
94
     * @param string $host
95
     * @return Request
96
     */
97
    public function getRequest(string $uri, string $host)
98
    {
99
        $request = Request::createClientRequest($uri, $host);
100
        $request->setVersion(13);
101
        $request->setKey(base64_encode(ClientHandshake::generateRandom16BytesKey()));
102
103
        return $request;
104
    }
105
106
    /**
107
     * @return string
108
     */
109
    public static function generateRandom16BytesKey()
110
    {
111
        $bytes = '';
112
113
        for($i = 0; $i < 16; $i++) {
114
            $bytes .= chr(mt_rand(0, 255));
115
        }
116
117
        return $bytes;
118
    }
119
}
120