Completed
Pull Request — master (#64)
by
unknown
02:15
created

Client::connect()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 1
nc 1
nop 1
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 AfriCC\EPP\Frame\ResponseFactory;
16
use Exception;
17
18
/**
19
 * A high level TCP (SSL) based client for the Extensible Provisioning Protocol (EPP)
20
 *
21
 * @see http://tools.ietf.org/html/rfc5734
22
 */
23
class Client extends AbstractClient implements ClientInterface
24
{
25
    protected $socket;
26
    protected $chunk_size;
27
    protected $verify_peer_name;
28
29
    public function __construct(array $config)
30
    {
31
        parent::__construct($config);
32
33
        if (!empty($config['chunk_size'])) {
34
            $this->chunk_size = (int) $config['chunk_size'];
35
        } else {
36
            $this->chunk_size = 1024;
37
        }
38
39
        if (!empty($config['verify_peer_name'])) {
40
            $this->verify_peer_name = (bool) $config['verify_peer_name'];
41
        } else {
42
            $this->verify_peer_name = true;
43
        }
44
45
        if(!$this->port){
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->port of type integer|false is loosely compared to false; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
46
            // if not set, default port is 700
47
            $this->port = 700;
48
        }
49
    }
50
51
    public function __destruct()
52
    {
53
        $this->close();
54
    }
55
56
    /**
57
     * Setup context in case of ssl connection
58
     *
59
     * @return resource|null
60
     */
61
    private function setupContext()
62
    {
63
        if ($this->ssl) {
64
            $context = stream_context_create();
65
            stream_context_set_option($context, 'ssl', 'verify_peer', false);
66
            stream_context_set_option($context, 'ssl', 'verify_peer_name', $this->verify_peer_name);
67
            stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
68
69
            if ($this->local_cert !== null) {
70
                stream_context_set_option($context, 'ssl', 'local_cert', $this->local_cert);
71
72
                if ($this->passphrase) {
73
                    stream_context_set_option($context, 'ssl', 'passphrase', $this->passphrase);
74
                }
75
            }
76
            if ($this->ca_cert !== null) {
77
                stream_context_set_option($context, 'ssl', 'cafile', $this->ca_cert);
78
            }
79
            if ($this->pk_cert !== null) {
80
                stream_context_set_option($context, 'ssl', 'local_pk', $this->pk_cert);
81
            }
82
            return $context;
83
        }
84
        return null;
85
    }
86
87
    /**
88
     * Setup connection socket
89
     *
90
     * @param resource|null $context SSL context or null in case of tcp connection
91
     *
92
     * @throws Exception
93
     */
94
    private function setupSocket($context = null)
95
    {
96
        $proto = $this->ssl ? 'ssl' : 'tcp';
97
        $target = sprintf('%s://%s:%d', $proto, $this->host, $this->port);
98
99
        $errno = 0;
100
        $errstr = '';
101
102
        $this->socket = @stream_socket_client($target, $errno, $errstr, $this->connect_timeout, STREAM_CLIENT_CONNECT, $context);
103
104
        if ($this->socket === false) {
105
            throw new Exception($errstr, $errno);
106
        }
107
108
        // set stream time out
109
        if (!stream_set_timeout($this->socket, $this->timeout)) {
110
            throw new Exception('unable to set stream timeout');
111
        }
112
113
        // set to non-blocking
114
        if (!stream_set_blocking($this->socket, 0)) {
115
            throw new Exception('unable to set blocking');
116
        }
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     *
122
     * @see \AfriCC\EPP\ClientInterface::connect()
123
     */
124
    public function connect($newPassword = false)
125
    {
126
        $context = $this->setupContext();
127
        $this->setupSocket($context);
128
129
        // get greeting
130
        $greeting = $this->getFrame();
131
132
        // login
133
        $this->login($newPassword);
134
135
        // return greeting
136
        return $greeting;
137
    }
138
139
    /**
140
     * Closes a previously opened EPP connection
141
     */
142
    public function close()
143
    {
144
        if ($this->active()) {
145
            // send logout frame
146
            $this->request(new LogoutCommand());
147
148
            return fclose($this->socket);
149
        }
150
151
        return false;
152
    }
153
154
    /**
155
     * Get an EPP frame from the server.
156
     */
157
    public function getFrame()
158
    {
159
        $header = $this->recv(4);
160
161
        // Unpack first 4 bytes which is our length
162
        $unpacked = unpack('N', $header);
163
        $length = $unpacked[1];
164
165
        if ($length < 5) {
166
            throw new Exception(sprintf('Got a bad frame header length of %d bytes from peer', $length));
167
        } else {
168
            $length -= 4;
169
170
            return ResponseFactory::build($this->recv($length));
171
        }
172
    }
173
174
    /**
175
     * sends a XML-based frame to the server
176
     *
177
     * @param FrameInterface $frame the frame to send to the server
178
     */
179
    public function sendFrame(FrameInterface $frame)
180
    {
181
        // some frames might require a client transaction identifier, so let us
182
        // inject it before sending the frame
183
        if ($frame instanceof TransactionAwareInterface) {
184
            $frame->setClientTransactionId($this->generateClientTransactionId());
185
        }
186
187
        $buffer = (string) $frame;
188
        $header = pack('N', mb_strlen($buffer, 'ASCII') + 4);
189
190
        return $this->send($header . $buffer);
191
    }
192
193
    /**
194
     * a wrapper around sendFrame() and getFrame()
195
     */
196
    public function request(FrameInterface $frame)
197
    {
198
        $this->sendFrame($frame);
199
200
        return $this->getFrame();
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