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

Client::log()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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