Completed
Pull Request — master (#64)
by
unknown
01:38
created

Client::active()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 3
nc 4
nop 0
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
46
    public function __destruct()
47
    {
48
        $this->close();
49
    }
50
51
    /**
52
     * Open a new connection to the EPP server
53
     */
54
    public function connect($newPassword = false)
55
    {
56
        $proto = $this->ssl ? 'ssl' : 'tcp';
57
58
        if ($this->ssl) {
59
60
            $context = stream_context_create();
61
            stream_context_set_option($context, 'ssl', 'verify_peer', false);
62
            stream_context_set_option($context, 'ssl', 'verify_peer_name', $this->verify_peer_name);
63
            stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
64
65
            if ($this->local_cert !== null) {
66
                stream_context_set_option($context, 'ssl', 'local_cert', $this->local_cert);
67
68
                if ($this->passphrase) {
69
                    stream_context_set_option($context, 'ssl', 'passphrase', $this->passphrase);
70
                }
71
            }
72
            if ($this->ca_cert !== null){
73
                stream_context_set_option($context, 'ssl', 'cafile', $this->ca_cert);
74
            }
75
            if ($this->pk_cert !== null){
76
                stream_context_set_option($context, 'ssl', 'local_pk', $this->pk_cert);
77
            }
78
        }
79
80
        $target = sprintf('%s://%s:%d', $proto, $this->host, $this->port ? $this->port : 700);
81
82
        $errno = 0;
83
        $errstr = '';
84
        if (isset($context) && is_resource($context)) {
85
            $this->socket = @stream_socket_client($target, $errno, $errstr, $this->connect_timeout, STREAM_CLIENT_CONNECT, $context);
86
        } else {
87
            $this->socket = @stream_socket_client($target, $errno, $errstr, $this->connect_timeout, STREAM_CLIENT_CONNECT);
88
        }
89
90
        if ($this->socket === false) {
91
            throw new Exception($errstr, $errno);
92
        }
93
94
        // set stream time out
95
        if (!stream_set_timeout($this->socket, $this->timeout)) {
96
            throw new Exception('unable to set stream timeout');
97
        }
98
99
        // set to non-blocking
100
        if (!stream_set_blocking($this->socket, 0)) {
101
            throw new Exception('unable to set blocking');
102
        }
103
104
        // get greeting
105
        $greeting = $this->getFrame();
106
107
        // login
108
        $this->login($newPassword);
109
110
        // return greeting
111
        return $greeting;
112
    }
113
114
    /**
115
     * Closes a previously opened EPP connection
116
     */
117
    public function close()
118
    {
119
        if ($this->active()) {
120
            // send logout frame
121
            $this->request(new LogoutCommand());
122
123
            return fclose($this->socket);
124
        }
125
126
        return false;
127
    }
128
129
    /**
130
     * Get an EPP frame from the server.
131
     */
132
    public function getFrame()
133
    {
134
        $header = $this->recv(4);
135
136
        // Unpack first 4 bytes which is our length
137
        $unpacked = unpack('N', $header);
138
        $length = $unpacked[1];
139
140
        if ($length < 5) {
141
            throw new Exception(sprintf('Got a bad frame header length of %d bytes from peer', $length));
142
        } else {
143
            $length -= 4;
144
145
            return ResponseFactory::build($this->recv($length));
146
        }
147
    }
148
149
    /**
150
     * sends a XML-based frame to the server
151
     *
152
     * @param FrameInterface $frame the frame to send to the server
153
     */
154
    public function sendFrame(FrameInterface $frame)
155
    {
156
        // some frames might require a client transaction identifier, so let us
157
        // inject it before sending the frame
158
        if ($frame instanceof TransactionAwareInterface) {
159
            $frame->setClientTransactionId($this->generateClientTransactionId());
160
        }
161
162
        $buffer = (string) $frame;
163
        $header = pack('N', mb_strlen($buffer, 'ASCII') + 4);
164
165
        return $this->send($header . $buffer);
166
    }
167
168
    /**
169
     * a wrapper around sendFrame() and getFrame()
170
     */
171
    public function request(FrameInterface $frame)
172
    {
173
        $this->sendFrame($frame);
174
175
        return $this->getFrame();
176
    }
177
178
179
180
    protected function log($message, $color = '0;32')
181
    {
182
        if ($message === '' || !$this->debug) {
183
            return;
184
        }
185
        echo sprintf("\033[%sm%s\033[0m", $color, $message);
186
    }
187
188
    /**
189
     * check if socket is still active
190
     *
191
     * @return bool
192
     */
193
    private function active()
194
    {
195
        return !is_resource($this->socket) || feof($this->socket) ? false : true;
196
    }
197
198
    /**
199
     * receive socket data
200
     *
201
     * @param int $length
202
     *
203
     * @throws Exception
204
     *
205
     * @return string
206
     */
207
    private function recv($length)
208
    {
209
        $result = '';
210
211
        $info = stream_get_meta_data($this->socket);
212
        $hard_time_limit = time() + $this->timeout + 2;
213
214
        while (!$info['timed_out'] && !feof($this->socket)) {
215
            // Try read remaining data from socket
216
            $buffer = @fread($this->socket, $length - mb_strlen($result, 'ASCII'));
217
218
            // If the buffer actually contains something then add it to the result
219
            if ($buffer !== false) {
220
221
                    $this->log($buffer);
222
223
224
                $result .= $buffer;
225
226
                // break if all data received
227
                if (mb_strlen($result, 'ASCII') === $length) {
228
                    break;
229
                }
230
            } else {
231
                // sleep 0.25s
232
                usleep(250000);
233
            }
234
235
            // update metadata
236
            $info = stream_get_meta_data($this->socket);
237
            if (time() >= $hard_time_limit) {
238
                throw new Exception('Timeout while reading from EPP Server');
239
            }
240
        }
241
242
        // check for timeout
243
        if ($info['timed_out']) {
244
            throw new Exception('Timeout while reading data from socket');
245
        }
246
247
        return $result;
248
    }
249
250
    /**
251
     * send data to socket
252
     *
253
     * @param string $buffer
254
     */
255
    private function send($buffer)
256
    {
257
        $info = stream_get_meta_data($this->socket);
258
        $hard_time_limit = time() + $this->timeout + 2;
259
        $length = mb_strlen($buffer, 'ASCII');
260
261
        $pos = 0;
262
        while (!$info['timed_out'] && !feof($this->socket)) {
263
            // Some servers don't like a lot of data, so keep it small per chunk
264
            $wlen = $length - $pos;
265
266
            if ($wlen > $this->chunk_size) {
267
                $wlen = $this->chunk_size;
268
            }
269
270
            // try write remaining data from socket
271
            $written = @fwrite($this->socket, mb_substr($buffer, $pos, $wlen, 'ASCII'), $wlen);
272
273
            // If we read something, bump up the position
274
            if ($written) {
275
                if ($this->debug) {
276
                    $this->log(mb_substr($buffer, $pos, $wlen, 'ASCII'), '1;31');
277
                }
278
                $pos += $written;
279
280
                // break if all written
281
                if ($pos === $length) {
282
                    break;
283
                }
284
            } else {
285
                // sleep 0.25s
286
                usleep(250000);
287
            }
288
289
            // update metadata
290
            $info = stream_get_meta_data($this->socket);
291
            if (time() >= $hard_time_limit) {
292
                throw new Exception('Timeout while writing to EPP Server');
293
            }
294
        }
295
296
        // check for timeout
297
        if ($info['timed_out']) {
298
            throw new Exception('Timeout while writing data to socket');
299
        }
300
301
        if ($pos !== $length) {
302
            throw new Exception('Writing short %d bytes', $length - $pos);
303
        }
304
305
        return $pos;
306
    }
307
}
308