1<?php 2/** 3 * Net_Socket 4 * 5 * PHP Version 5 6 * 7 * LICENSE: 8 * 9 * Copyright (c) 1997-2017 The PHP Group 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 16 * o Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * o Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 * 34 * @category Net 35 * @package Net_Socket 36 * @author Stig Bakken <ssb@php.net> 37 * @author Chuck Hagenbuch <chuck@horde.org> 38 * @copyright 1997-2017 The PHP Group 39 * @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause 40 * @link http://pear.php.net/packages/Net_Socket 41 */ 42 43require_once 'PEAR.php'; 44 45define('NET_SOCKET_READ', 1); 46define('NET_SOCKET_WRITE', 2); 47define('NET_SOCKET_ERROR', 4); 48 49/** 50 * Generalized Socket class. 51 * 52 * @category Net 53 * @package Net_Socket 54 * @author Stig Bakken <ssb@php.net> 55 * @author Chuck Hagenbuch <chuck@horde.org> 56 * @copyright 1997-2017 The PHP Group 57 * @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause 58 * @link http://pear.php.net/packages/Net_Socket 59 */ 60class Net_Socket extends PEAR 61{ 62 /** 63 * Socket file pointer. 64 * @var resource $fp 65 */ 66 public $fp = null; 67 68 /** 69 * Whether the socket is blocking. Defaults to true. 70 * @var boolean $blocking 71 */ 72 public $blocking = true; 73 74 /** 75 * Whether the socket is persistent. Defaults to false. 76 * @var boolean $persistent 77 */ 78 public $persistent = false; 79 80 /** 81 * The IP address to connect to. 82 * @var string $addr 83 */ 84 public $addr = ''; 85 86 /** 87 * The port number to connect to. 88 * @var integer $port 89 */ 90 public $port = 0; 91 92 /** 93 * Number of seconds to wait on socket operations before assuming 94 * there's no more data. Defaults to no timeout. 95 * @var integer|float $timeout 96 */ 97 public $timeout = null; 98 99 /** 100 * Number of bytes to read at a time in readLine() and 101 * readAll(). Defaults to 2048. 102 * @var integer $lineLength 103 */ 104 public $lineLength = 2048; 105 106 /** 107 * The string to use as a newline terminator. Usually "\r\n" or "\n". 108 * @var string $newline 109 */ 110 public $newline = "\r\n"; 111 112 /** 113 * Connect to the specified port. If called when the socket is 114 * already connected, it disconnects and connects again. 115 * 116 * @param string $addr IP address or host name (may be with protocol prefix). 117 * @param integer $port TCP port number. 118 * @param boolean $persistent (optional) Whether the connection is 119 * persistent (kept open between requests 120 * by the web server). 121 * @param integer $timeout (optional) Connection socket timeout. 122 * @param array $options See options for stream_context_create. 123 * 124 * @access public 125 * 126 * @return boolean|PEAR_Error True on success or a PEAR_Error on failure. 127 */ 128 public function connect( 129 $addr, 130 $port = 0, 131 $persistent = null, 132 $timeout = null, 133 $options = null 134 ) { 135 if (is_resource($this->fp)) { 136 @fclose($this->fp); 137 $this->fp = null; 138 } 139 140 if (!$addr) { 141 return $this->raiseError('$addr cannot be empty'); 142 } else { 143 if (strspn($addr, ':.0123456789') === strlen($addr)) { 144 $this->addr = strpos($addr, ':') !== false ? '[' . $addr . ']' : $addr; 145 } else { 146 $this->addr = $addr; 147 } 148 } 149 150 $this->port = $port % 65536; 151 152 if ($persistent !== null) { 153 $this->persistent = $persistent; 154 } 155 156 $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen'; 157 $errno = 0; 158 $errstr = ''; 159 160 if (function_exists('error_clear_last')) { 161 error_clear_last(); 162 } else { 163 $old_track_errors = @ini_set('track_errors', 1); 164 } 165 166 if ($timeout <= 0) { 167 $timeout = @ini_get('default_socket_timeout'); 168 } 169 170 if ($options && function_exists('stream_context_create')) { 171 $context = stream_context_create($options); 172 173 // Since PHP 5 fsockopen doesn't allow context specification 174 if (function_exists('stream_socket_client')) { 175 $flags = STREAM_CLIENT_CONNECT; 176 177 if ($this->persistent) { 178 $flags = STREAM_CLIENT_PERSISTENT; 179 } 180 181 $addr = $this->addr . ':' . $this->port; 182 $fp = @stream_socket_client($addr, $errno, $errstr, 183 $timeout, $flags, $context); 184 } else { 185 $fp = @$openfunc($this->addr, $this->port, $errno, 186 $errstr, $timeout, $context); 187 } 188 } else { 189 $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout); 190 } 191 192 if (!$fp) { 193 if ($errno === 0 && !strlen($errstr)) { 194 $errstr = ''; 195 if (isset($old_track_errors)) { 196 $errstr = $php_errormsg ?: ''; 197 @ini_set('track_errors', $old_track_errors); 198 } else { 199 $lastError = error_get_last(); 200 if (isset($lastError['message'])) { 201 $errstr = $lastError['message']; 202 } 203 } 204 } 205 206 return $this->raiseError($errstr, $errno); 207 } 208 209 if (isset($old_track_errors)) { 210 @ini_set('track_errors', $old_track_errors); 211 } 212 213 $this->fp = $fp; 214 $this->setTimeout(); 215 216 return $this->setBlocking($this->blocking); 217 } 218 219 /** 220 * Disconnects from the peer, closes the socket. 221 * 222 * @access public 223 * @return mixed true on success or a PEAR_Error instance otherwise 224 */ 225 public function disconnect() 226 { 227 if (!is_resource($this->fp)) { 228 return $this->raiseError('not connected'); 229 } 230 231 @fclose($this->fp); 232 $this->fp = null; 233 234 return true; 235 } 236 237 /** 238 * Set the newline character/sequence to use. 239 * 240 * @param string $newline Newline character(s) 241 * @return boolean True 242 */ 243 public function setNewline($newline) 244 { 245 $this->newline = $newline; 246 247 return true; 248 } 249 250 /** 251 * Find out if the socket is in blocking mode. 252 * 253 * @access public 254 * @return boolean The current blocking mode. 255 */ 256 public function isBlocking() 257 { 258 return $this->blocking; 259 } 260 261 /** 262 * Sets whether the socket connection should be blocking or 263 * not. A read call to a non-blocking socket will return immediately 264 * if there is no data available, whereas it will block until there 265 * is data for blocking sockets. 266 * 267 * @param boolean $mode True for blocking sockets, false for nonblocking. 268 * 269 * @access public 270 * @return mixed true on success or a PEAR_Error instance otherwise 271 */ 272 public function setBlocking($mode) 273 { 274 if (!is_resource($this->fp)) { 275 return $this->raiseError('not connected'); 276 } 277 278 $this->blocking = $mode; 279 stream_set_blocking($this->fp, (int)$this->blocking); 280 281 return true; 282 } 283 284 /** 285 * Sets the timeout value on socket descriptor, 286 * expressed in the sum of seconds and microseconds 287 * 288 * @param integer $seconds Seconds. 289 * @param integer $microseconds Microseconds, optional. 290 * 291 * @access public 292 * @return mixed True on success or false on failure or 293 * a PEAR_Error instance when not connected 294 */ 295 public function setTimeout($seconds = null, $microseconds = null) 296 { 297 if (!is_resource($this->fp)) { 298 return $this->raiseError('not connected'); 299 } 300 301 if ($seconds === null && $microseconds === null) { 302 $seconds = (int)$this->timeout; 303 $microseconds = (int)(($this->timeout - $seconds) * 1000000); 304 } else { 305 $this->timeout = $seconds + $microseconds / 1000000; 306 } 307 308 if ($this->timeout > 0) { 309 return stream_set_timeout($this->fp, (int)$seconds, (int)$microseconds); 310 } else { 311 return false; 312 } 313 } 314 315 /** 316 * Sets the file buffering size on the stream. 317 * See php's stream_set_write_buffer for more information. 318 * 319 * @param integer $size Write buffer size. 320 * 321 * @access public 322 * @return mixed on success or an PEAR_Error object otherwise 323 */ 324 public function setWriteBuffer($size) 325 { 326 if (!is_resource($this->fp)) { 327 return $this->raiseError('not connected'); 328 } 329 330 $returned = stream_set_write_buffer($this->fp, $size); 331 if ($returned === 0) { 332 return true; 333 } 334 335 return $this->raiseError('Cannot set write buffer.'); 336 } 337 338 /** 339 * Returns information about an existing socket resource. 340 * Currently returns four entries in the result array: 341 * 342 * <p> 343 * timed_out (bool) - The socket timed out waiting for data<br> 344 * blocked (bool) - The socket was blocked<br> 345 * eof (bool) - Indicates EOF event<br> 346 * unread_bytes (int) - Number of bytes left in the socket buffer<br> 347 * </p> 348 * 349 * @access public 350 * @return mixed Array containing information about existing socket 351 * resource or a PEAR_Error instance otherwise 352 */ 353 public function getStatus() 354 { 355 if (!is_resource($this->fp)) { 356 return $this->raiseError('not connected'); 357 } 358 359 return stream_get_meta_data($this->fp); 360 } 361 362 /** 363 * Get a specified line of data 364 * 365 * @param int $size Reading ends when size - 1 bytes have been read, 366 * or a newline or an EOF (whichever comes first). 367 * If no size is specified, it will keep reading from 368 * the stream until it reaches the end of the line. 369 * 370 * @access public 371 * @return mixed $size bytes of data from the socket, or a PEAR_Error if 372 * not connected. If an error occurs, FALSE is returned. 373 */ 374 public function gets($size = null) 375 { 376 if (!is_resource($this->fp)) { 377 return $this->raiseError('not connected'); 378 } 379 380 if (null === $size) { 381 return @fgets($this->fp); 382 } else { 383 return @fgets($this->fp, $size); 384 } 385 } 386 387 /** 388 * Read a specified amount of data. This is guaranteed to return, 389 * and has the added benefit of getting everything in one fread() 390 * chunk; if you know the size of the data you're getting 391 * beforehand, this is definitely the way to go. 392 * 393 * @param integer $size The number of bytes to read from the socket. 394 * 395 * @access public 396 * @return string $size bytes of data from the socket, or a PEAR_Error if 397 * not connected. 398 */ 399 public function read($size) 400 { 401 if (!is_resource($this->fp)) { 402 return $this->raiseError('not connected'); 403 } 404 405 return @fread($this->fp, $size); 406 } 407 408 /** 409 * Write a specified amount of data. 410 * 411 * @param string $data Data to write. 412 * @param integer $blocksize Amount of data to write at once. 413 * NULL means all at once. 414 * 415 * @access public 416 * @return mixed If the socket is not connected, returns an instance of 417 * PEAR_Error. 418 * If the write succeeds, returns the number of bytes written. 419 * If the write fails, returns false. 420 * If the socket times out, returns an instance of PEAR_Error. 421 */ 422 public function write($data, $blocksize = null) 423 { 424 if (!is_resource($this->fp)) { 425 return $this->raiseError('not connected'); 426 } 427 428 if (null === $blocksize && !OS_WINDOWS) { 429 $written = @fwrite($this->fp, $data); 430 431 // Check for timeout or lost connection 432 if ($written === false) { 433 $meta_data = $this->getStatus(); 434 435 if (!is_array($meta_data)) { 436 return $meta_data; // PEAR_Error 437 } 438 439 if (!empty($meta_data['timed_out'])) { 440 return $this->raiseError('timed out'); 441 } 442 } 443 444 return $written; 445 } else { 446 if (null === $blocksize) { 447 $blocksize = 1024; 448 } 449 450 $pos = 0; 451 $size = strlen($data); 452 while ($pos < $size) { 453 $written = @fwrite($this->fp, substr($data, $pos, $blocksize)); 454 455 // Check for timeout or lost connection 456 if ($written === false) { 457 $meta_data = $this->getStatus(); 458 459 if (!is_array($meta_data)) { 460 return $meta_data; // PEAR_Error 461 } 462 463 if (!empty($meta_data['timed_out'])) { 464 return $this->raiseError('timed out'); 465 } 466 467 return $written; 468 } 469 470 $pos += $written; 471 } 472 473 return $pos; 474 } 475 } 476 477 /** 478 * Write a line of data to the socket, followed by a trailing newline. 479 * 480 * @param string $data Data to write 481 * 482 * @access public 483 * @return mixed fwrite() result, or PEAR_Error when not connected 484 */ 485 public function writeLine($data) 486 { 487 if (!is_resource($this->fp)) { 488 return $this->raiseError('not connected'); 489 } 490 491 return fwrite($this->fp, $data . $this->newline); 492 } 493 494 /** 495 * Tests for end-of-file on a socket descriptor. 496 * 497 * Also returns true if the socket is disconnected. 498 * 499 * @access public 500 * @return bool 501 */ 502 public function eof() 503 { 504 return (!is_resource($this->fp) || feof($this->fp)); 505 } 506 507 /** 508 * Reads a byte of data 509 * 510 * @access public 511 * @return integer 1 byte of data from the socket, or a PEAR_Error if 512 * not connected. 513 */ 514 public function readByte() 515 { 516 if (!is_resource($this->fp)) { 517 return $this->raiseError('not connected'); 518 } 519 520 return ord(@fread($this->fp, 1)); 521 } 522 523 /** 524 * Reads a word of data 525 * 526 * @access public 527 * @return integer 1 word of data from the socket, or a PEAR_Error if 528 * not connected. 529 */ 530 public function readWord() 531 { 532 if (!is_resource($this->fp)) { 533 return $this->raiseError('not connected'); 534 } 535 536 $buf = @fread($this->fp, 2); 537 538 return (ord($buf[0]) + (ord($buf[1]) << 8)); 539 } 540 541 /** 542 * Reads an int of data 543 * 544 * @access public 545 * @return integer 1 int of data from the socket, or a PEAR_Error if 546 * not connected. 547 */ 548 public function readInt() 549 { 550 if (!is_resource($this->fp)) { 551 return $this->raiseError('not connected'); 552 } 553 554 $buf = @fread($this->fp, 4); 555 556 return (ord($buf[0]) + (ord($buf[1]) << 8) + 557 (ord($buf[2]) << 16) + (ord($buf[3]) << 24)); 558 } 559 560 /** 561 * Reads a zero-terminated string of data 562 * 563 * @access public 564 * @return string, or a PEAR_Error if 565 * not connected. 566 */ 567 public function readString() 568 { 569 if (!is_resource($this->fp)) { 570 return $this->raiseError('not connected'); 571 } 572 573 $string = ''; 574 while (($char = @fread($this->fp, 1)) !== "\x00") { 575 $string .= $char; 576 } 577 578 return $string; 579 } 580 581 /** 582 * Reads an IP Address and returns it in a dot formatted string 583 * 584 * @access public 585 * @return string Dot formatted string, or a PEAR_Error if 586 * not connected. 587 */ 588 public function readIPAddress() 589 { 590 if (!is_resource($this->fp)) { 591 return $this->raiseError('not connected'); 592 } 593 594 $buf = @fread($this->fp, 4); 595 596 return sprintf('%d.%d.%d.%d', ord($buf[0]), ord($buf[1]), 597 ord($buf[2]), ord($buf[3])); 598 } 599 600 /** 601 * Read until either the end of the socket or a newline, whichever 602 * comes first. Strips the trailing newline from the returned data. 603 * 604 * @access public 605 * @return string All available data up to a newline, without that 606 * newline, or until the end of the socket, or a PEAR_Error if 607 * not connected. 608 */ 609 public function readLine() 610 { 611 if (!is_resource($this->fp)) { 612 return $this->raiseError('not connected'); 613 } 614 615 $line = ''; 616 617 $timeout = time() + $this->timeout; 618 619 while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) { 620 $line .= @fgets($this->fp, $this->lineLength); 621 if (substr($line, -1) == "\n") { 622 return rtrim($line, $this->newline); 623 } 624 } 625 626 return $line; 627 } 628 629 /** 630 * Read until the socket closes, or until there is no more data in 631 * the inner PHP buffer. If the inner buffer is empty, in blocking 632 * mode we wait for at least 1 byte of data. Therefore, in 633 * blocking mode, if there is no data at all to be read, this 634 * function will never exit (unless the socket is closed on the 635 * remote end). 636 * 637 * @access public 638 * 639 * @return string All data until the socket closes, or a PEAR_Error if 640 * not connected. 641 */ 642 public function readAll() 643 { 644 if (!is_resource($this->fp)) { 645 return $this->raiseError('not connected'); 646 } 647 648 $data = ''; 649 $timeout = time() + $this->timeout; 650 651 while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) { 652 $data .= @fread($this->fp, $this->lineLength); 653 } 654 655 return $data; 656 } 657 658 /** 659 * Runs the equivalent of the select() system call on the socket 660 * with a timeout specified by tv_sec and tv_usec. 661 * 662 * @param integer $state Which of read/write/error to check for. 663 * @param integer $tv_sec Number of seconds for timeout. 664 * @param integer $tv_usec Number of microseconds for timeout. 665 * 666 * @access public 667 * @return False if select fails, integer describing which of read/write/error 668 * are ready, or PEAR_Error if not connected. 669 */ 670 public function select($state, $tv_sec, $tv_usec = 0) 671 { 672 if (!is_resource($this->fp)) { 673 return $this->raiseError('not connected'); 674 } 675 676 $read = null; 677 $write = null; 678 $except = null; 679 if ($state & NET_SOCKET_READ) { 680 $read[] = $this->fp; 681 } 682 if ($state & NET_SOCKET_WRITE) { 683 $write[] = $this->fp; 684 } 685 if ($state & NET_SOCKET_ERROR) { 686 $except[] = $this->fp; 687 } 688 if (false === ($sr = stream_select($read, $write, $except, 689 $tv_sec, $tv_usec)) 690 ) { 691 return false; 692 } 693 694 $result = 0; 695 if (count($read)) { 696 $result |= NET_SOCKET_READ; 697 } 698 if (count($write)) { 699 $result |= NET_SOCKET_WRITE; 700 } 701 if (count($except)) { 702 $result |= NET_SOCKET_ERROR; 703 } 704 705 return $result; 706 } 707 708 /** 709 * Turns encryption on/off on a connected socket. 710 * 711 * @param bool $enabled Set this parameter to true to enable encryption 712 * and false to disable encryption. 713 * @param integer $type Type of encryption. See stream_socket_enable_crypto() 714 * for values. 715 * 716 * @see http://se.php.net/manual/en/function.stream-socket-enable-crypto.php 717 * @access public 718 * @return false on error, true on success and 0 if there isn't enough data 719 * and the user should try again (non-blocking sockets only). 720 * A PEAR_Error object is returned if the socket is not 721 * connected 722 */ 723 public function enableCrypto($enabled, $type) 724 { 725 if (version_compare(phpversion(), '5.1.0', '>=')) { 726 if (!is_resource($this->fp)) { 727 return $this->raiseError('not connected'); 728 } 729 730 return @stream_socket_enable_crypto($this->fp, $enabled, $type); 731 } else { 732 $msg = 'Net_Socket::enableCrypto() requires php version >= 5.1.0'; 733 734 return $this->raiseError($msg); 735 } 736 } 737 738} 739