Completed
Push — master ( 703ea3...18d4bf )
by
unknown
01:23
created

Client::send()   B

Complexity

Conditions 10
Paths 39

Size

Total Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 52
rs 7.1806
c 0
b 0
f 0
cc 10
nc 39
nop 1

How to fix   Long Method    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 php-epp2 library.
5
 *
6
 * (c) Gunter Grodotzki <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE file
9
 * that was distributed with this source code.
10
 */
11
12
namespace AfriCC\EPP;
13
14
use AfriCC\EPP\Frame\Command\Logout as LogoutCommand;
15
use Exception;
16
17
/**
18
 * A high level TCP (SSL) based client for the Extensible Provisioning Protocol (EPP)
19
 *
20
 * @see http://tools.ietf.org/html/rfc5734
21
 *
22
 * As this class deals directly with sockets it's untestable
23
 * @codeCoverageIgnore
24
 */
25
class Client extends AbstractClient implements ClientInterface
26
{
27
    protected $socket;
28
    protected $chunk_size;
29
    protected $verify_peer_name;
30
31
    public function __construct(array $config, ObjectSpec $objectSpec = null)
32
    {
33
        parent::__construct($config, $objectSpec);
34
35
        $this->chunk_size = (int) $this->getConfigDefault($config, 'chunk_size', 1024);
36
        $this->verify_peer_name = $this->getConfigDefaultBool($config, 'verify_peer_name', true);
37
38
        // override 'port' default to false from AbstractClient::prepareConnectionOptions
39
        $this->port = (int) $this->getConfigDefault($config, 'port', 700);
40
    }
41
42
    public function __destruct()
43
    {
44
        $this->close();
45
    }
46
47
    /**
48
     * Setup context in case of ssl connection
49
     *
50
     * @return resource|null
51
     */
52
    private function setupContext()
53
    {
54
        if (!$this->ssl) {
55
            return null;
56
        }
57
58
        $context = stream_context_create();
59
60
        $options_array = [
61
            'verify_peer' => false,
62
            'verify_peer_name' => $this->verify_peer_name,
63
            'allow_self_signed' => true,
64
            'local_cert' => $this->local_cert,
65
            'passphrase' => $this->passphrase,
66
            'cafile' => $this->ca_cert,
67
            'local_pk' => $this->pk_cert,
68
        ];
69
70
        // filter out empty user provided values
71
        $options_array = array_filter(
72
            $options_array,
73
            function ($var) {
74
                return !is_null($var);
75
            }
76
        );
77
78
        $options = ['ssl' => $options_array];
79
80
        // stream_context_set_option accepts array of options in form of $arr['wrapper']['option'] = value
81
        stream_context_set_option($context, $options);
82
83
        return $context;
84
    }
85
86
    /**
87
     * Setup connection socket
88
     *
89
     * @param resource|null $context SSL context or null in case of tcp connection
90
     *
91
     * @throws Exception on socket errors
92
     */
93
    private function setupSocket($context = null)
94
    {
95
        $proto = $this->ssl ? 'ssl' : 'tcp';
96
        $target = sprintf('%s://%s:%d', $proto, $this->host, $this->port);
97
98
        $errno = 0;
99
        $errstr = '';
100
101
        $this->socket = @stream_socket_client($target, $errno, $errstr, $this->connect_timeout, STREAM_CLIENT_CONNECT, $context);
102
103
        if ($this->socket === false) {
104
            // Socket initialization may fail, before system call connect()
105
            // so the $errno is 0 and $errstr isn't populated .
106
            // see https://www.php.net/manual/en/function.stream-socket-client.php#refsect1-function.stream-socket-client-errors
107
            throw new Exception(sprintf('problem initializing socket: %s code: [%d]', $errstr, $errno), $errno);
108
        }
109
110
        // set stream time out
111
        if (!stream_set_timeout($this->socket, $this->timeout)) {
112
            throw new Exception('unable to set stream timeout');
113
        }
114
115
        // set to non-blocking
116
        if (!stream_set_blocking($this->socket, 0)) {
117
            throw new Exception('unable to set blocking');
118
        }
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     *
124
     * @see \AfriCC\EPP\ClientInterface::connect()
125
     */
126
    public function connect($newPassword = false)
127
    {
128
        $context = $this->setupContext();
129
        $this->setupSocket($context);
130
131
        // get greeting
132
        $greeting = $this->getFrame();
133
134
        // login
135
        $this->login($newPassword);
136
137
        // return greeting
138
        return $greeting;
139
    }
140
141
    /**
142
     * Closes a previously opened EPP connection
143
     */
144
    public function close()
145
    {
146
        if ($this->active()) {
147
            // send logout frame
148
            $this->request(new LogoutCommand($this->objectSpec));
149
150
            return fclose($this->socket);
151
        }
152
153
        return false;
154
    }
155
156
    public function getFrame()
157
    {
158
        $hard_time_limit = time() + $this->timeout + 2;
159
        do {
160
            $header = $this->recv(4);
161
        } while (empty($header) && (time() < $hard_time_limit));
162
163
        if (time() >= $hard_time_limit) {
164
            throw new Exception('Timeout while reading header from EPP Server');
165
        }
166
167
        // Unpack first 4 bytes which is our length
168
        $unpacked = unpack('N', $header);
169
        $length = $unpacked[1];
170
171
        if ($length < 5) {
172
            throw new Exception(sprintf('Got a bad frame header length of %d bytes from peer', $length));
173
        } else {
174
            $length -= 4;
175
176
            return $this->recv($length);
177
        }
178
    }
179
180
    public function sendFrame(FrameInterface $frame)
181
    {
182
        $buffer = (string) $frame;
183
        $header = pack('N', mb_strlen($buffer, 'ASCII') + 4);
184
185
        return $this->send($header . $buffer);
186
    }
187
188
    protected function log($message, $color = '0;32')
189
    {
190
        if ($message === '' || !$this->debug) {
191
            return;
192
        }
193
        echo sprintf("\033[%sm%s\033[0m", $color, $message);
194
    }
195
196
    /**
197
     * check if socket is still active
198
     *
199
     * @return bool
200
     */
201
    private function active()
202
    {
203
        return !is_resource($this->socket) || feof($this->socket) ? false : true;
204
    }
205
206
    /**
207
     * receive socket data
208
     *
209
     * @param int $length
210
     *
211
     * @throws Exception
212
     *
213
     * @return string
214
     */
215
    private function recv($length)
216
    {
217
        $result = '';
218
219
        $info = stream_get_meta_data($this->socket);
220
        $hard_time_limit = time() + $this->timeout + 2;
221
222
        while (!$info['timed_out'] && !feof($this->socket)) {
223
            // Try read remaining data from socket
224
            $buffer = @fread($this->socket, $length - mb_strlen($result, 'ASCII'));
225
226
            // If the buffer actually contains something then add it to the result
227
            if ($buffer !== false) {
228
                $this->log($buffer);
229
                $result .= $buffer;
230
231
                // break if all data received
232
                if (mb_strlen($result, 'ASCII') === $length) {
233
                    break;
234
                }
235
            } else {
236
                // sleep 0.25s
237
                usleep(250000);
238
            }
239
240
            // update metadata
241
            $info = stream_get_meta_data($this->socket);
242
            if (time() >= $hard_time_limit) {
243
                throw new Exception('Timeout while reading from EPP Server');
244
            }
245
        }
246
247
        // check for timeout
248
        if ($info['timed_out']) {
249
            throw new Exception('Timeout while reading data from socket');
250
        }
251
252
        return $result;
253
    }
254
255
    /**
256
     * send data to socket
257
     *
258
     * @param string $buffer
259
     */
260
    private function send($buffer)
261
    {
262
        $info = stream_get_meta_data($this->socket);
263
        $hard_time_limit = time() + $this->timeout + 2;
264
        $length = mb_strlen($buffer, 'ASCII');
265
266
        $pos = 0;
267
        while (!$info['timed_out'] && !feof($this->socket)) {
268
            // Some servers don't like a lot of data, so keep it small per chunk
269
            $wlen = $length - $pos;
270
271
            if ($wlen > $this->chunk_size) {
272
                $wlen = $this->chunk_size;
273
            }
274
275
            // try write remaining data from socket
276
            $written = @fwrite($this->socket, mb_substr($buffer, $pos, $wlen, 'ASCII'), $wlen);
277
278
            // If we read something, bump up the position
279
            if ($written) {
280
                if ($this->debug) {
281
                    $this->log(mb_substr($buffer, $pos, $wlen, 'ASCII'), '1;31');
282
                }
283
                $pos += $written;
284
285
                // break if all written
286
                if ($pos === $length) {
287
                    break;
288
                }
289
            } else {
290
                // sleep 0.25s
291
                usleep(250000);
292
            }
293
294
            // update metadata
295
            $info = stream_get_meta_data($this->socket);
296
            if (time() >= $hard_time_limit) {
297
                throw new Exception('Timeout while writing to EPP Server');
298
            }
299
        }
300
301
        // check for timeout
302
        if ($info['timed_out']) {
303
            throw new Exception('Timeout while writing data to socket');
304
        }
305
306
        if ($pos !== $length) {
307
            throw new Exception('Writing short %d bytes', $length - $pos);
308
        }
309
310
        return $pos;
311
    }
312
}
313