Completed
Pull Request — master (#84)
by
unknown
01:28
created

Client::__construct()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.584
c 0
b 0
f 0
nc 8
cc 4
nop 2
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
        if (!empty($config['chunk_size'])) {
36
            $this->chunk_size = (int) $config['chunk_size'];
37
        } else {
38
            $this->chunk_size = 1024;
39
        }
40
41
        if (isset($config['verify_peer_name'])) {
42
            $this->verify_peer_name = (bool) $config['verify_peer_name'];
43
        } else {
44
            $this->verify_peer_name = true;
45
        }
46
47
        if ($this->port === false) {
48
            // if not set, default port is 700
49
            $this->port = 700;
50
        }
51
    }
52
53
    public function __destruct()
54
    {
55
        $this->close();
56
    }
57
58
    /**
59
     * Setup context in case of ssl connection
60
     *
61
     * @return resource|null
62
     */
63
    private function setupContext()
64
    {
65
        if (!$this->ssl) {
66
            return null;
67
        }
68
69
        $context = stream_context_create();
70
71
        $options_array = [
72
            'verify_peer' => false,
73
            'verify_peer_name' => $this->verify_peer_name,
74
            'allow_self_signed' => true,
75
            'local_cert' => $this->local_cert,
76
            'passphrase' => $this->passphrase,
77
            'cafile' => $this->ca_cert,
78
            'local_pk' => $this->pk_cert,
79
        ];
80
81
        // filter out empty user provided values
82
        $options_array = array_filter(
83
            $options_array,
84
            function ($var) {
85
                return !is_null($var);
86
            }
87
        );
88
89
        $options = ['ssl' => $options_array];
90
91
        // stream_context_set_option accepts array of options in form of $arr['wrapper']['option'] = value
92
        stream_context_set_option($context, $options);
93
94
        return $context;
95
    }
96
97
    /**
98
     * Setup connection socket
99
     *
100
     * @param resource|null $context SSL context or null in case of tcp connection
101
     *
102
     * @throws Exception on socket errors
103
     */
104
    private function setupSocket($context = null)
105
    {
106
        $proto = $this->ssl ? 'ssl' : 'tcp';
107
        $target = sprintf('%s://%s:%d', $proto, $this->host, $this->port);
108
109
        $errno = 0;
110
        $errstr = '';
111
112
        $this->socket = @stream_socket_client($target, $errno, $errstr, $this->connect_timeout, STREAM_CLIENT_CONNECT, $context);
113
114
        if ($this->socket === false) {
115
            if ($errno === 0) {
116
                // Socket initialization may fail, before system call connect()
117
                // so the $errno isn't populated.
118
                // see https://www.php.net/manual/en/function.stream-socket-client.php#refsect1-function.stream-socket-client-errors
119
                throw new Exception("problem initializing the socket");
120
            }
121
            throw new Exception($errstr, $errno);
122
        }
123
124
        // set stream time out
125
        if (!stream_set_timeout($this->socket, $this->timeout)) {
126
            throw new Exception('unable to set stream timeout');
127
        }
128
129
        // set to non-blocking
130
        if (!stream_set_blocking($this->socket, 0)) {
131
            throw new Exception('unable to set blocking');
132
        }
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     *
138
     * @see \AfriCC\EPP\ClientInterface::connect()
139
     */
140
    public function connect($newPassword = false)
141
    {
142
        $context = $this->setupContext();
143
        $this->setupSocket($context);
144
145
        // get greeting
146
        $greeting = $this->getFrame();
147
148
        // login
149
        $this->login($newPassword);
150
151
        // return greeting
152
        return $greeting;
153
    }
154
155
    /**
156
     * Closes a previously opened EPP connection
157
     */
158
    public function close()
159
    {
160
        if ($this->active()) {
161
            // send logout frame
162
            $this->request(new LogoutCommand($this->objectSpec));
163
164
            return fclose($this->socket);
165
        }
166
167
        return false;
168
    }
169
170
    public function getFrame()
171
    {
172
        $hard_time_limit = time() + $this->timeout + 2;
173
        $header = '';
0 ignored issues
show
Unused Code introduced by
$header is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
174
        do {
175
            $header = $this->recv(4);
176
        } while (empty($header) && (time()<$hard_time_limit));
177
178
        if (time() >= $hard_time_limit) {
179
            throw new Exception('Timeout while reading header from EPP Server');
180
        }
181
182
        // Unpack first 4 bytes which is our length
183
        $unpacked = unpack('N', $header);
184
        $length = $unpacked[1];
185
186
        if ($length < 5) {
187
            throw new Exception(sprintf('Got a bad frame header length of %d bytes from peer', $length));
188
        } else {
189
            $length -= 4;
190
191
            return $this->recv($length);
192
        }
193
    }
194
195
    public function sendFrame(FrameInterface $frame)
196
    {
197
        $buffer = (string) $frame;
198
        $header = pack('N', mb_strlen($buffer, 'ASCII') + 4);
199
200
        return $this->send($header . $buffer);
201
    }
202
203
    protected function log($message, $color = '0;32')
204
    {
205
        if ($message === '' || !$this->debug) {
206
            return;
207
        }
208
        echo sprintf("\033[%sm%s\033[0m", $color, $message);
209
    }
210
211
    /**
212
     * check if socket is still active
213
     *
214
     * @return bool
215
     */
216
    private function active()
217
    {
218
        return !is_resource($this->socket) || feof($this->socket) ? false : true;
219
    }
220
221
    /**
222
     * receive socket data
223
     *
224
     * @param int $length
225
     *
226
     * @throws Exception
227
     *
228
     * @return string
229
     */
230
    private function recv($length)
231
    {
232
        $result = '';
233
234
        $info = stream_get_meta_data($this->socket);
235
        $hard_time_limit = time() + $this->timeout + 2;
236
237
        while (!$info['timed_out'] && !feof($this->socket)) {
238
            // Try read remaining data from socket
239
            $buffer = @fread($this->socket, $length - mb_strlen($result, 'ASCII'));
240
241
            // If the buffer actually contains something then add it to the result
242
            if ($buffer !== false) {
243
                $this->log($buffer);
244
                $result .= $buffer;
245
246
                // break if all data received
247
                if (mb_strlen($result, 'ASCII') === $length) {
248
                    break;
249
                }
250
            } else {
251
                // sleep 0.25s
252
                usleep(250000);
253
            }
254
255
            // update metadata
256
            $info = stream_get_meta_data($this->socket);
257
            if (time() >= $hard_time_limit) {
258
                throw new Exception('Timeout while reading from EPP Server');
259
            }
260
        }
261
262
        // check for timeout
263
        if ($info['timed_out']) {
264
            throw new Exception('Timeout while reading data from socket');
265
        }
266
267
        return $result;
268
    }
269
270
    /**
271
     * send data to socket
272
     *
273
     * @param string $buffer
274
     */
275
    private function send($buffer)
276
    {
277
        $info = stream_get_meta_data($this->socket);
278
        $hard_time_limit = time() + $this->timeout + 2;
279
        $length = mb_strlen($buffer, 'ASCII');
280
281
        $pos = 0;
282
        while (!$info['timed_out'] && !feof($this->socket)) {
283
            // Some servers don't like a lot of data, so keep it small per chunk
284
            $wlen = $length - $pos;
285
286
            if ($wlen > $this->chunk_size) {
287
                $wlen = $this->chunk_size;
288
            }
289
290
            // try write remaining data from socket
291
            $written = @fwrite($this->socket, mb_substr($buffer, $pos, $wlen, 'ASCII'), $wlen);
292
293
            // If we read something, bump up the position
294
            if ($written) {
295
                if ($this->debug) {
296
                    $this->log(mb_substr($buffer, $pos, $wlen, 'ASCII'), '1;31');
297
                }
298
                $pos += $written;
299
300
                // break if all written
301
                if ($pos === $length) {
302
                    break;
303
                }
304
            } else {
305
                // sleep 0.25s
306
                usleep(250000);
307
            }
308
309
            // update metadata
310
            $info = stream_get_meta_data($this->socket);
311
            if (time() >= $hard_time_limit) {
312
                throw new Exception('Timeout while writing to EPP Server');
313
            }
314
        }
315
316
        // check for timeout
317
        if ($info['timed_out']) {
318
            throw new Exception('Timeout while writing data to socket');
319
        }
320
321
        if ($pos !== $length) {
322
            throw new Exception('Writing short %d bytes', $length - $pos);
323
        }
324
325
        return $pos;
326
    }
327
}
328