Completed
Push — master ( 390e4e...b0ec12 )
by Taosikai
12:57
created

TunnelClient::handleProxyConnection()   B

Complexity

Conditions 4
Paths 1

Size

Total Lines 26
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 26
rs 8.5806
cc 4
eloc 18
nc 1
nop 1
1
<?php
2
/**
3
 * Spike library
4
 * @author Tao <[email protected]>
5
 */
6
namespace Spike\Client\TunnelClient;
7
8
use React\EventLoop\LoopInterface;
9
use React\Socket\ConnectionInterface;
10
use React\Socket\Connector;
11
use Spike\Client\Client;
12
use Spike\Tunnel\HttpTunnel;
13
use Spike\Tunnel\TunnelInterface;
14
use Spike\Parser\SpikeParser;
15
use Spike\Protocol\Spike;
16
17
abstract class TunnelClient implements TunnelClientInterface
18
{
19
    /**
20
     * @var TunnelInterface
21
     */
22
    protected $tunnel;
23
24
    /**
25
     * @var LoopInterface
26
     */
27
    protected $loop;
28
29
    /**
30
     * @var string
31
     */
32
    protected $serverAddress;
33
34
    /**
35
     * The proxy connection
36
     * @var ConnectionInterface
37
     */
38
    protected $proxyConnection;
39
40
    /**
41
     * The local connection
42
     * @var ConnectionInterface
43
     */
44
    protected $localConnection;
45
46
    /**
47
     * @var string
48
     */
49
    protected $proxyConnectionId;
50
51
    /**
52
     * @var string
53
     */
54
    protected $initBuffer;
55
56
    protected $client;
57
58
    public function __construct(Client $client, TunnelInterface $tunnel, $proxyConnectionId, $serverAddress, LoopInterface $loop)
59
    {
60
        $this->client = $client;
61
        $this->tunnel = $tunnel;
62
        $this->proxyConnectionId = $proxyConnectionId;
63
        $this->serverAddress = $serverAddress;
64
        $this->loop = $loop;
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function run()
71
    {
72
        $serverConnector = new Connector($this->loop);
73
        $serverConnector->connect($this->serverAddress)
74
            ->then([$this, 'handleProxyConnection']);
75
    }
76
77
    /**
78
     * {@inheritdoc}
79
     */
80
    public function close()
81
    {
82
        if ($this->localConnection) {
83
            $this->localConnection->end();
84
        }
85
        if ($this->proxyConnection) {
86
            $this->proxyConnection->end();
87
        }
88
        $this->client->getTunnelClients()->removeElement($this);
89
    }
90
91
    /**
92
     * Handles the proxy connection
93
     * @param ConnectionInterface $connection
94
     */
95
    public function handleProxyConnection(ConnectionInterface $connection)
96
    {
97
        $this->proxyConnection = $connection;
98
        $connection->write(new Spike('register_proxy', $this->tunnel->toArray(), [
99
            'Proxy-Connection-ID' => $this->proxyConnectionId
100
        ]));
101
102
        $parser = new SpikeParser();
103
        $connection->on('data', function($data) use($parser, $connection){
104
            $parser->pushIncoming($data);
105
            $protocol = $parser->parseFirst();
106
            if ($protocol) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $protocol of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
107
                $connection->removeAllListeners('data');
108
                $message = Spike::fromString($protocol);
109
                if ($message->getAction() == 'start_proxy') {
110
                    $this->initBuffer = $parser->getRestData();
111
                    if ($this->tunnel instanceof HttpTunnel) {
112
                        $localAddress = $this->tunnel->getForwardHost($this->tunnel->getProxyHost());
113
                    }  else {
114
                        $localAddress = $this->tunnel->getHost();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Spike\Tunnel\TunnelInterface as the method getHost() does only exist in the following implementations of said interface: Spike\Tunnel\TcpTunnel.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
115
                    }
116
                    $this->createLocalConnector($localAddress);
117
                }
118
            }
119
        });
120
    }
121
122
    /**
123
     * Connect the local server
124
     * @param string $address
125
     */
126
    protected function createLocalConnector($address)
127
    {
128
        $localConnector = new Connector($this->loop);
129
        $localConnector->connect($address)->then([$this, 'handleLocalConnection'],
130
            [$this, 'handleConnectLocalError']
131
        );
132
    }
133
134
    /**
135
     * Handles connect local error
136
     */
137
    abstract protected function handleConnectLocalError(\Exception $exception);
138
139
    /**
140
     * {@inheritdoc}
141
     */
142
    public function handleLocalConnection(ConnectionInterface $localConnection)
143
    {
144
        $this->localConnection = $localConnection;
145
        $localConnection->pipe($this->proxyConnection);
146
        $this->proxyConnection->pipe($localConnection);
147
        $localConnection->write($this->initBuffer);
148
149
        //Handles the local connection close
150
        $handleLocalConnectionClose = function(){
151
            $this->close();
152
        };
153
        $localConnection->on('close', $handleLocalConnectionClose);
154
        $localConnection->on('error', $handleLocalConnectionClose);
155
156
        //Handles the proxy connection close
157
        $handleProxyConnectionClose = function(){
158
            $this->close();
159
        };
160
        $this->proxyConnection->on('close', $handleProxyConnectionClose);
161
        $this->proxyConnection->on('error', $handleProxyConnectionClose);
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167
    public function getTunnel()
168
    {
169
        return $this->tunnel;
170
    }
171
}