Net_Socket::setTimeout()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 9
rs 10
1
<?php
2
//
3
// +----------------------------------------------------------------------+
4
// | PHP Version 4                                                        |
5
// +----------------------------------------------------------------------+
6
// | Copyright (c) 1997-2002 The PHP Group                                |
7
// +----------------------------------------------------------------------+
8
// | This source file is subject to version 2.0 of the PHP license,       |
9
// | that is bundled with this package in the file LICENSE, and is        |
10
// | available at through the world-wide-web at                           |
11
// | https://www.php.net/license/2_02.txt.                                 |
12
// | If you did not receive a copy of the PHP license and are unable to   |
13
// | obtain it through the world-wide-web, please send a note to          |
14
// | [email protected] so we can mail you a copy immediately.               |
15
// +----------------------------------------------------------------------+
16
// | Authors: Stig Bakken <[email protected]>                                   |
17
// |          Chuck Hagenbuch <[email protected]>                           |
18
// +----------------------------------------------------------------------+
19
//
20
//
21
22
require_once XHELP_PEAR_PATH . '/PEAR.php';
23
24
/**
25
 * Generalized Socket class. More docs to be written.
26
 *
27
 * @version 1.0
28
 * @author  Stig Bakken <[email protected]>
29
 * @author  Chuck Hagenbuch <[email protected]>
30
 */
31
class Net_Socket extends PEAR
32
{
33
    // {{{ properties
34
    /** Socket file pointer. */
35
    public $fp = null;
36
    /** Whether the socket is blocking. */
37
    public $blocking = true;
38
    /** Whether the socket is persistent. */
39
    public $persistent = false;
40
    /** The IP address to connect to. */
41
    public $addr = '';
42
    /** The port number to connect to. */
43
    public $port = 0;
44
    /** Number of seconds to wait on socket connections before
45
     * assuming there's no more data. */
46
    public $timeout = false;
47
    /** Number of bytes to read at a time in readLine() and
48
     * readAll(). */
49
    public $lineLength = 2048;
50
    // }}}
51
52
    // {{{ constructor
53
54
    /**
55
     * Constructs a new Net_Socket object.
56
     */
57
    public function __construct()
58
    {
59
        parent::__construct();
60
    }
61
62
    // }}}
63
64
    // {{{ connect()
65
66
    /**
67
     * Connect to the specified port. If called when the socket is
68
     * already connected, it disconnects and connects again.
69
     *
70
     * @param string    $addr       IP address or host name
71
     * @param int       $port       TCP port number
72
     * @param bool|null $persistent (optional) whether the connection is
73
     *                              persistent (kept open between requests by the web server)
74
     * @param int|null  $timeout    (optional) how long to wait for data
75
     * @return bool|object|\PEAR_Error true on success or error object
76
     */
77
    public function connect(string $addr, int $port, $persistent = null, $timeout = null)
78
    {
79
        if (is_resource($this->fp)) {
80
            @fclose($this->fp);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for fclose(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

80
            /** @scrutinizer ignore-unhandled */ @fclose($this->fp);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
81
            $this->fp = null;
82
        }
83
84
        if (strspn($addr, '.0123456789') == mb_strlen($addr)) {
85
            $this->addr = $addr;
86
        } else {
87
            $this->addr = gethostbyname($addr);
88
        }
89
        $this->port = $port % 65536;
90
        if (null !== $persistent) {
91
            $this->persistent = $persistent;
92
        }
93
        if (null !== $timeout) {
94
            $this->timeout = $timeout;
95
        }
96
        $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen';
97
        $errno    = 0;
98
        $errstr   = '';
99
        if ($this->timeout) {
100
            $fp = $openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout);
101
        } else {
102
            $fp = $openfunc($this->addr, $this->port, $errno, $errstr);
103
        }
104
105
        if (!$fp) {
106
            return $this->raiseError($errstr, $errno);
107
        }
108
109
        $this->fp = $fp;
110
111
        return $this->setBlocking($this->blocking);
112
    }
113
114
    // }}}
115
116
    // {{{ disconnect()
117
118
    /**
119
     * Disconnects from the peer, closes the socket.
120
     *
121
     * @return bool|object|\PEAR_Error true on success or an error object otherwise
122
     */
123
    public function disconnect()
124
    {
125
        if (is_resource($this->fp)) {
126
            fclose($this->fp);
127
            $this->fp = null;
128
129
            return true;
130
        }
131
132
        return $this->raiseError('not connected');
133
    }
134
135
    // }}}
136
137
    // {{{ isBlocking()
138
139
    /**
140
     * Find out if the socket is in blocking mode.
141
     *
142
     * @return bool the current blocking mode.
143
     */
144
    public function isBlocking(): bool
145
    {
146
        return $this->blocking;
147
    }
148
149
    // }}}
150
151
    // {{{ setBlocking()
152
153
    /**
154
     * Sets whether the socket connection should be blocking or
155
     * not. A read call to a non-blocking socket will return immediately
156
     * if there is no data available, whereas it will block until there
157
     * is data for blocking sockets.
158
     *
159
     * @param bool $mode true for blocking sockets, false for nonblocking
160
     * @return bool|object|\PEAR_Error true on success or an error object otherwise
161
     */
162
    public function setBlocking(bool $mode)
163
    {
164
        if (is_resource($this->fp)) {
165
            $this->blocking = $mode;
166
            stream_set_blocking($this->fp, $this->blocking);
167
168
            return true;
169
        }
170
171
        return $this->raiseError('not connected');
172
    }
173
174
    // }}}
175
176
    // {{{ setTimeout()
177
178
    /**
179
     * Sets the timeout value on socket descriptor,
180
     * expressed in the sum of seconds and microseconds
181
     *
182
     * @param int $seconds      seconds
183
     * @param int $microseconds microseconds
184
     * @return bool|object|\PEAR_Error true on success or an error object otherwise
185
     */
186
    public function setTimeout(int $seconds, int $microseconds)
187
    {
188
        if (is_resource($this->fp)) {
189
            stream_set_timeout($this->fp, $seconds, $microseconds);
190
191
            return true;
192
        }
193
194
        return $this->raiseError('not connected');
195
    }
196
197
    // }}}
198
199
    // {{{ getStatus()
200
201
    /**
202
     * Returns information about an existing socket resource.
203
     * Currently returns four entries in the result array:
204
     *
205
     * <p>
206
     * timed_out (bool) - The socket timed out waiting for data<br>
207
     * blocked (bool) - The socket was blocked<br>
208
     * eof (bool) - Indicates EOF event<br>
209
     * unread_bytes (int) - Number of bytes left in the socket buffer<br>
210
     * </p>
211
     *
212
     * @return array|object|\PEAR_Error Array containing information about existing socket resource or an error object otherwise
213
     */
214
    public function getStatus()
215
    {
216
        if (is_resource($this->fp)) {
217
            return stream_get_meta_data($this->fp);
218
        }
219
220
        return $this->raiseError('not connected');
221
    }
222
223
    // }}}
224
225
    // {{{ gets()
226
227
    /**
228
     * Get a specified line of data
229
     *
230
     * @param int|null $size
231
     * @return bool|object|string bytes of data from the socket, or a PEAR_Error if
232
     *               not connected.
233
     */
234
    public function gets(?int $size)
235
    {
236
        if (is_resource($this->fp)) {
237
            return fgets($this->fp, $size);
238
        }
239
240
        return $this->raiseError('not connected');
241
    }
242
243
    // }}}
244
245
    // {{{ read()
246
247
    /**
248
     * Read a specified amount of data. This is guaranteed to return,
249
     * and has the added benefit of getting everything in one fread()
250
     * chunk; if you know the size of the data you're getting
251
     * beforehand, this is definitely the way to go.
252
     *
253
     * @param int $size number of bytes to read from the socket.
254
     * @return bool|object|string|PEAR_Error bytes of data from the socket, or a PEAR_Error if not connected.
255
     */
256
    public function read(int $size)
257
    {
258
        if (is_resource($this->fp)) {
259
            return fread($this->fp, $size);
260
        }
261
262
        return $this->raiseError('not connected');
263
    }
264
265
    // }}}
266
267
    // {{{ write()
268
269
    /**
270
     * Write a specified amount of data.
271
     *
272
     * @param string $data
273
     * @return false|int|object|\PEAR_Error true on success or an error object otherwise
274
     */
275
    public function write(string $data)
276
    {
277
        if (is_resource($this->fp)) {
278
            return fwrite($this->fp, $data);
279
        }
280
281
        return $this->raiseError('not connected');
282
    }
283
284
    // }}}
285
286
    // {{{ writeLine()
287
288
    /**
289
     * Write a line of data to the socket, followed by a trailing "\r\n".
290
     *
291
     * @param string $data
292
     * @return false|int|object|\PEAR_Error fputs result, or an error
293
     */
294
    public function writeLine(string $data)
295
    {
296
        if (is_resource($this->fp)) {
297
            return $this->write($data . "\r\n");
298
        }
299
300
        return $this->raiseError('not connected');
301
    }
302
303
    // }}}
304
305
    // {{{ eof()
306
307
    /**
308
     * Tests for end-of-file on a socket descriptor
309
     *
310
     * @return bool
311
     */
312
    public function eof(): bool
313
    {
314
        return (is_resource($this->fp) && feof($this->fp));
315
    }
316
317
    // }}}
318
319
    // {{{ readByte()
320
321
    /**
322
     * Reads a byte of data
323
     *
324
     * @return int|object 1 byte of data from the socket, or a PEAR_Error if
325
     *           not connected.
326
     */
327
    public function readByte()
328
    {
329
        if (is_resource($this->fp)) {
330
            return ord($this->read(1));
0 ignored issues
show
Bug introduced by
It seems like $this->read(1) can also be of type PEAR_Error; however, parameter $character of ord() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

330
            return ord(/** @scrutinizer ignore-type */ $this->read(1));
Loading history...
331
        }
332
333
        return $this->raiseError('not connected');
334
    }
335
336
    // }}}
337
338
    // {{{ readWord()
339
340
    /**
341
     * Reads a word of data
342
     *
343
     * @return int|object 1 word of data from the socket, or a PEAR_Error if
344
     *           not connected.
345
     */
346
    public function readWord()
347
    {
348
        if (is_resource($this->fp)) {
349
            $buf = $this->read(2);
350
351
            return (ord($buf[0]) + (ord($buf[1]) << 8));
352
        }
353
354
        return $this->raiseError('not connected');
355
    }
356
357
    // }}}
358
359
    // {{{ readInt()
360
361
    /**
362
     * Reads an int of data
363
     *
364
     * @return int|object 1 int of data from the socket, or a PEAR_Error if
365
     *           not connected.
366
     */
367
    public function readInt()
368
    {
369
        if (is_resource($this->fp)) {
370
            $buf = $this->read(4);
371
372
            return (ord($buf[0]) + (ord($buf[1]) << 8) + (ord($buf[2]) << 16) + (ord($buf[3]) << 24));
373
        }
374
375
        return $this->raiseError('not connected');
376
    }
377
378
    // }}}
379
380
    // {{{ readString()
381
382
    /**
383
     * Reads a zeroterminated string of data
384
     *
385
     * @return string, or a PEAR_Error if
386
     *                 not connected.
387
     */
388
    public function readString(): string
389
    {
390
        if (is_resource($this->fp)) {
391
            $string = '';
392
            while ("\x00" !== ($char = $this->read(1))) {
393
                $string .= $char;
394
            }
395
396
            return $string;
397
        }
398
399
        return $this->raiseError('not connected');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->raiseError('not connected') returns the type PEAR_Error which is incompatible with the type-hinted return string.
Loading history...
400
    }
401
402
    // }}}
403
404
    // {{{ readIPAddress()
405
406
    /**
407
     * Reads an IP Address and returns it in a dot formated string
408
     *
409
     * @return string|\PEAR_Error Dot formated string, or a PEAR_Error if
410
     *             not connected.
411
     */
412
    public function readIPAddress()
413
    {
414
        if (is_resource($this->fp)) {
415
            $buf = $this->read(4);
416
417
            return sprintf('%s.%s.%s.%s', ord($buf[0]), ord($buf[1]), ord($buf[2]), ord($buf[3]));
418
        }
419
420
        return $this->raiseError('not connected');
421
    }
422
423
    // }}}
424
425
    // {{{ readLine()
426
427
    /**
428
     * Read until either the end of the socket or a newline, whichever
429
     * comes first. Strips the trailing newline from the returned data.
430
     *
431
     * @return object|\PEAR_Error|string  All available data up to a newline, without that
432
     *             newline, or until the end of the socket, or a PEAR_Error if
433
     *             not connected.
434
     */
435
    public function readLine()
436
    {
437
        if (is_resource($this->fp)) {
438
            $line    = '';
439
            $timeout = time() + $this->timeout;
440
            while (!$this->eof() && (!$this->timeout || time() < $timeout)) {
441
                $line .= $this->gets($this->lineLength);
442
                if (mb_strlen($line) >= 2
443
                    && ("\r\n" === mb_substr($line, -2)
444
                        || "\n" === mb_substr($line, -1))) {
445
                    return rtrim($line);
446
                }
447
            }
448
449
            return $line;
450
        }
451
452
        return $this->raiseError('not connected');
453
    }
454
455
    // }}}
456
457
    // {{{ readAll()
458
459
    /**
460
     * Read until the socket closes. THIS FUNCTION WILL NOT EXIT if the
461
     * socket is in blocking mode until the socket closes.
462
     *
463
     * @return object|PEAR_Error|string All data until the socket closes, or a PEAR_Error if
464
     *             not connected.
465
     */
466
    public function readAll()
467
    {
468
        if (is_resource($this->fp)) {
469
            $data = '';
470
            while (!$this->eof()) {
471
                $data .= $this->read($this->lineLength);
472
            }
473
474
            return $data;
475
        }
476
477
        return $this->raiseError('not connected');
478
    }
479
    // }}}
480
}
481