Completed
Push — master ( c87337...703ea3 )
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
            // Socket initialization may fail, before system call connect()
116
            // so the $errno is 0 and $errstr isn't populated .
117
            // see https://www.php.net/manual/en/function.stream-socket-client.php#refsect1-function.stream-socket-client-errors
118
            throw new Exception(sprintf('problem initializing socket: %s code: [%d]', $errstr, $errno), $errno);
119
        }
120
121
        // set stream time out
122
        if (!stream_set_timeout($this->socket, $this->timeout)) {
123
            throw new Exception('unable to set stream timeout');
124
        }
125
126
        // set to non-blocking
127
        if (!stream_set_blocking($this->socket, 0)) {
128
            throw new Exception('unable to set blocking');
129
        }
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     *
135
     * @see \AfriCC\EPP\ClientInterface::connect()
136
     */
137
    public function connect($newPassword = false)
138
    {
139
        $context = $this->setupContext();
140
        $this->setupSocket($context);
141
142
        // get greeting
143
        $greeting = $this->getFrame();
144
145
        // login
146
        $this->login($newPassword);
147
148
        // return greeting
149
        return $greeting;
150
    }
151
152
    /**
153
     * Closes a previously opened EPP connection
154
     */
155
    public function close()
156
    {
157
        if ($this->active()) {
158
            // send logout frame
159
            $this->request(new LogoutCommand($this->objectSpec));
160
161
            return fclose($this->socket);
162
        }
163
164
        return false;
165
    }
166
167
    public function getFrame()
168
    {
169
        $hard_time_limit = time() + $this->timeout + 2;
170
        do {
171
            $header = $this->recv(4);
172
        } while (empty($header) && (time() < $hard_time_limit));
173
174
        if (time() >= $hard_time_limit) {
175
            throw new Exception('Timeout while reading header from EPP Server');
176
        }
177
178
        // Unpack first 4 bytes which is our length
179
        $unpacked = unpack('N', $header);
180
        $length = $unpacked[1];
181
182
        if ($length < 5) {
183
            throw new Exception(sprintf('Got a bad frame header length of %d bytes from peer', $length));
184
        } else {
185
            $length -= 4;
186
187
            return $this->recv($length);
188
        }
189
    }
190
191
    public function sendFrame(FrameInterface $frame)
192
    {
193
        $buffer = (string) $frame;
194
        $header = pack('N', mb_strlen($buffer, 'ASCII') + 4);
195
196
        return $this->send($header . $buffer);
197
    }
198
199
    protected function log($message, $color = '0;32')
200
    {
201
        if ($message === '' || !$this->debug) {
202
            return;
203
        }
204
        echo sprintf("\033[%sm%s\033[0m", $color, $message);
205
    }
206
207
    /**
208
     * check if socket is still active
209
     *
210
     * @return bool
211
     */
212
    private function active()
213
    {
214
        return !is_resource($this->socket) || feof($this->socket) ? false : true;
215
    }
216
217
    /**
218
     * receive socket data
219
     *
220
     * @param int $length
221
     *
222
     * @throws Exception
223
     *
224
     * @return string
225
     */
226
    private function recv($length)
227
    {
228
        $result = '';
229
230
        $info = stream_get_meta_data($this->socket);
231
        $hard_time_limit = time() + $this->timeout + 2;
232
233
        while (!$info['timed_out'] && !feof($this->socket)) {
234
            // Try read remaining data from socket
235
            $buffer = @fread($this->socket, $length - mb_strlen($result, 'ASCII'));
236
237
            // If the buffer actually contains something then add it to the result
238
            if ($buffer !== false) {
239
                $this->log($buffer);
240
                $result .= $buffer;
241
242
                // break if all data received
243
                if (mb_strlen($result, 'ASCII') === $length) {
244
                    break;
245
                }
246
            } else {
247
                // sleep 0.25s
248
                usleep(250000);
249
            }
250
251
            // update metadata
252
            $info = stream_get_meta_data($this->socket);
253
            if (time() >= $hard_time_limit) {
254
                throw new Exception('Timeout while reading from EPP Server');
255
            }
256
        }
257
258
        // check for timeout
259
        if ($info['timed_out']) {
260
            throw new Exception('Timeout while reading data from socket');
261
        }
262
263
        return $result;
264
    }
265
266
    /**
267
     * send data to socket
268
     *
269
     * @param string $buffer
270
     */
271
    private function send($buffer)
272
    {
273
        $info = stream_get_meta_data($this->socket);
274
        $hard_time_limit = time() + $this->timeout + 2;
275
        $length = mb_strlen($buffer, 'ASCII');
276
277
        $pos = 0;
278
        while (!$info['timed_out'] && !feof($this->socket)) {
279
            // Some servers don't like a lot of data, so keep it small per chunk
280
            $wlen = $length - $pos;
281
282
            if ($wlen > $this->chunk_size) {
283
                $wlen = $this->chunk_size;
284
            }
285
286
            // try write remaining data from socket
287
            $written = @fwrite($this->socket, mb_substr($buffer, $pos, $wlen, 'ASCII'), $wlen);
288
289
            // If we read something, bump up the position
290
            if ($written) {
291
                if ($this->debug) {
292
                    $this->log(mb_substr($buffer, $pos, $wlen, 'ASCII'), '1;31');
293
                }
294
                $pos += $written;
295
296
                // break if all written
297
                if ($pos === $length) {
298
                    break;
299
                }
300
            } else {
301
                // sleep 0.25s
302
                usleep(250000);
303
            }
304
305
            // update metadata
306
            $info = stream_get_meta_data($this->socket);
307
            if (time() >= $hard_time_limit) {
308
                throw new Exception('Timeout while writing to EPP Server');
309
            }
310
        }
311
312
        // check for timeout
313
        if ($info['timed_out']) {
314
            throw new Exception('Timeout while writing data to socket');
315
        }
316
317
        if ($pos !== $length) {
318
            throw new Exception('Writing short %d bytes', $length - $pos);
319
        }
320
321
        return $pos;
322
    }
323
}
324