Completed
Push — master ( 5855e7...4c70a1 )
by Robin
02:57
created

Connection::sendCommand()   D

Complexity

Conditions 10
Paths 14

Size

Total Lines 44
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 14
Bugs 3 Features 0
Metric Value
c 14
b 3
f 0
dl 0
loc 44
ccs 0
cts 25
cp 0
rs 4.8196
cc 10
eloc 23
nc 14
nop 1
crap 110

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the NNTP library.
5
 *
6
 * (c) Robin van der Vleuten <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Rvdv\Nntp\Connection;
13
14
use Rvdv\Nntp\Command\CommandInterface;
15
use Rvdv\Nntp\Exception\InvalidArgumentException;
16
use Rvdv\Nntp\Exception\RuntimeException;
17
use Rvdv\Nntp\Response\MultiLineResponse;
18
use Rvdv\Nntp\Response\Response;
19
use Rvdv\Nntp\Socket\Socket;
20
use Rvdv\Nntp\Socket\SocketInterface;
21
22
/**
23
 * @author Robin van der Vleuten <[email protected]>
24
 */
25
class Connection implements ConnectionInterface
26
{
27
    /**
28
     * @var string
29
     */
30
    private $host;
31
32
    /**
33
     * @var int
34
     */
35
    private $port;
36
37
    /**
38
     * @var bool
39
     */
40
    private $secure;
41
42
    /**
43
     * @var SocketInterface
44
     */
45
    private $socket;
46
47
    /**
48
     * @var int
49
     */
50
    private $timeout;
51
52
    /**
53
     * @var string
54
     */
55
    private $url;
0 ignored issues
show
Unused Code introduced by
The property $url is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
56
57
    /**
58
     * Constructor.
59
     *
60
     * @param string          $host    The host of the NNTP server.
61
     * @param int             $port    The port of the NNTP server.
62
     * @param bool            $secure  A bool indicating if a secure connection should be established.
63
     * @param int             $timeout The socket timeout in seconds.
64
     * @param SocketInterface $socket  An optional socket wrapper instance.
65
     */
66 2
    public function __construct($host, $port, $secure = false, $timeout = null, SocketInterface $socket = null)
67
    {
68 2
        $this->host = $host;
69 2
        $this->port = $port;
70 2
        $this->secure = $secure;
71 2
        $this->timeout = $timeout;
72 2
        $this->socket = $socket ?: new Socket();
73 2
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78 2
    public function connect()
79
    {
80 2
        $this->socket
81 2
            ->connect(sprintf('tcp://%s:%d', $this->host, $this->port), $this->timeout)
82 1
            ->setBlocking(false);
83
84 1
        if ($this->secure) {
85
            $this->socket->enableCrypto(true);
86
        }
87
88 1
        return $this->getResponse();
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94
    public function disconnect()
95
    {
96
        $this->socket->disconnect();
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function sendCommand(CommandInterface $command)
103
    {
104
        $commandString = $command->execute();
105
106
        // NNTP/RFC977 only allows command up to 512 (-2 \r\n) chars.
107
        if (!strlen($commandString) > 510) {
108
            throw new InvalidArgumentException('Failed to write to socket: command exceeded 510 characters');
109
        }
110
111
        if (strlen($commandString."\r\n") !== $this->socket->write($commandString."\r\n")) {
112
            throw new RuntimeException('Failed to write to socket');
113
        }
114
115
        $response = $this->getResponse();
116
117
        if ($command->isMultiLine() && ($response->getStatusCode() >= 200 && $response->getStatusCode() <= 399)) {
118
            $response = $command->isCompressed() ? $this->getCompressedResponse($response) : $this->getMultiLineResponse($response);
119
        }
120
121
        if (in_array($response->getStatusCode(), [Response::COMMAND_UNKNOWN, Response::COMMAND_UNAVAILABLE])) {
122
            throw new RuntimeException('Sent command is either unknown or unavailable on server');
123
        }
124
125
        $expectedResponseCodes = $command->getExpectedResponseCodes();
126
127
        // Check if we received a response expected by the command.
128
        if (!isset($expectedResponseCodes[$response->getStatusCode()])) {
129
            throw new RuntimeException(sprintf(
130
                'Unexpected response received: [%d] %s',
131
                $response->getStatusCode(),
132
                $response->getMessage()
133
            ));
134
        }
135
136
        $expectedResponseHandler = $expectedResponseCodes[$response->getStatusCode()];
137
        if (!is_callable([$command, $expectedResponseHandler])) {
138
            throw new RuntimeException(sprintf('Response handler (%s) is not callable method on given command object', $expectedResponseHandler));
139
        }
140
141
        $command->setResponse($response);
142
        $command->$expectedResponseHandler($response);
143
144
        return $command;
145
    }
146
147
    public function sendArticle(CommandInterface $command)
148
    {
149
        $commandString = $command->execute();
150
151
        if (!$this->socket->selectWrite() || strlen($commandString."\r\n.\r\n") !== $this->socket->write($commandString."\r\n.\r\n")) {
0 ignored issues
show
Bug introduced by
The method selectWrite() does not seem to exist on object<Rvdv\Nntp\Socket\SocketInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
152
            throw new RuntimeException('Failed to write to socket');
153
        }
154
155
        $response = $this->getResponse();
156
157
        $expectedResponseCodes = $command->getExpectedResponseCodes();
158
159
        // Check if we received a response expected by the command.
160
        if (!isset($expectedResponseCodes[$response->getStatusCode()])) {
161
            throw new RuntimeException(sprintf(
162
                'Unexpected response received: [%d] %s',
163
                $response->getStatusCode(),
164
                $response->getMessage()
165
            ));
166
        }
167
168
        $expectedResponseHandler = $expectedResponseCodes[$response->getStatusCode()];
169
        if (!is_callable([$command, $expectedResponseHandler])) {
170
            throw new RuntimeException(sprintf('Response handler (%s) is not callable method on given command object', $expectedResponseHandler));
171
        }
172
173
        $command->setResponse($response);
174
        $command->$expectedResponseHandler($response);
175
176
        return $command;
177
    }
178
179 1
    protected function getResponse()
180
    {
181 1
        $buffer = '';
182
183 1
        while (!$this->socket->eof()) {
184 1
            $buffer .= $this->socket->read(1024);
185
186 1
            if ("\r\n" === substr($buffer, -2)) {
187 1
                break;
188
            }
189
190
            if ($buffer === false) {
191
                $this->disconnect();
192
                throw new RuntimeException('Incorrect data received from buffer');
193
            }
194
        }
195
196 1
        return Response::createFromString($buffer);
197
    }
198
199
    public function getMultiLineResponse(Response $response)
200
    {
201
        $buffer = '';
202
203
        while (!$this->socket->eof()) {
204
            $buffer .= $this->socket->read(1024);
205
206
            if ("\n.\r\n" === substr($buffer, -4)) {
207
                break;
208
            }
209
210
            if ($buffer === false) {
211
                $this->disconnect();
212
                throw new RuntimeException('Incorrect data received from buffer');
213
            }
214
        }
215
216
        $lines = explode("\r\n", trim($buffer));
217
        if (end($lines) === '.') {
218
            array_pop($lines);
219
        }
220
221
        $lines = array_filter($lines);
222
        $lines = \SplFixedArray::fromArray($lines);
223
224
        return new MultiLineResponse($response, $lines);
225
    }
226
227
    public function getCompressedResponse(Response $response)
228
    {
229
        // Determine encoding by fetching first line.
230
        $line = $this->socket->read(1024);
231
232
        $uncompressed = '';
233
234
        while (!$this->socket->eof()) {
235
            $buffer = $this->socket->read(1024);
236
237
            if (strlen($buffer) === 0) {
238
                $uncompressed = @gzuncompress($line);
239
240
                if ($uncompressed !== false) {
241
                    break;
242
                }
243
            }
244
245
            if ($buffer === false) {
246
                $this->disconnect();
247
                throw new RuntimeException('Incorrect data received from buffer');
248
            }
249
250
            $line .= $buffer;
251
        }
252
253
        $lines = explode("\r\n", trim($uncompressed));
254
        if (end($lines) === '.') {
255
            array_pop($lines);
256
        }
257
258
        $lines = array_filter($lines);
259
        $lines = \SplFixedArray::fromArray($lines);
260
261
        return new MultiLineResponse($response, $lines);
262
    }
263
}
264