PageRenderTime 94ms CodeModel.GetById 58ms app.highlight 26ms RepoModel.GetById 0ms app.codeStats 1ms

/NNTP/Protocol/Client.php

http://github.com/spotweb/spotweb
PHP | 2292 lines | 1155 code | 366 blank | 771 comment | 177 complexity | 4307915c439b69a9235e87b29c72d82c MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
   3
   4/**
   5 *
   6 *
   7 * PHP versions 4 and 5
   8 *
   9 * <pre>
  10 * +-----------------------------------------------------------------------+
  11 * |                                                                       |
  12 * | W3C� SOFTWARE NOTICE AND LICENSE                                      |
  13 * | http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231   |
  14 * |                                                                       |
  15 * | This work (and included software, documentation such as READMEs,      |
  16 * | or other related items) is being provided by the copyright holders    |
  17 * | under the following license. By obtaining, using and/or copying       |
  18 * | this work, you (the licensee) agree that you have read, understood,   |
  19 * | and will comply with the following terms and conditions.              |
  20 * |                                                                       |
  21 * | Permission to copy, modify, and distribute this software and its      |
  22 * | documentation, with or without modification, for any purpose and      |
  23 * | without fee or royalty is hereby granted, provided that you include   |
  24 * | the following on ALL copies of the software and documentation or      |
  25 * | portions thereof, including modifications:                            |
  26 * |                                                                       |
  27 * | 1. The full text of this NOTICE in a location viewable to users       |
  28 * |    of the redistributed or derivative work.                           |
  29 * |                                                                       |
  30 * | 2. Any pre-existing intellectual property disclaimers, notices,       |
  31 * |    or terms and conditions. If none exist, the W3C Software Short     |
  32 * |    Notice should be included (hypertext is preferred, text is         |
  33 * |    permitted) within the body of any redistributed or derivative      |
  34 * |    code.                                                              |
  35 * |                                                                       |
  36 * | 3. Notice of any changes or modifications to the files, including     |
  37 * |    the date changes were made. (We recommend you provide URIs to      |
  38 * |    the location from which the code is derived.)                      |
  39 * |                                                                       |
  40 * | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT    |
  41 * | HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED,    |
  42 * | INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR        |
  43 * | FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE    |
  44 * | OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS,           |
  45 * | COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.                               |
  46 * |                                                                       |
  47 * | COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT,        |
  48 * | SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE        |
  49 * | SOFTWARE OR DOCUMENTATION.                                            |
  50 * |                                                                       |
  51 * | The name and trademarks of copyright holders may NOT be used in       |
  52 * | advertising or publicity pertaining to the software without           |
  53 * | specific, written prior permission. Title to copyright in this        |
  54 * | software and any associated documentation will at all times           |
  55 * | remain with copyright holders.                                        |
  56 * |                                                                       |
  57 * +-----------------------------------------------------------------------+
  58 * </pre>
  59 *
  60 * @category   Net
  61 * @package    Net_NNTP
  62 * @author     Heino H. Gehlsen <heino@gehlsen.dk>
  63 * @copyright  2002-2011 Heino H. Gehlsen <heino@gehlsen.dk>. All Rights Reserved.
  64 * @license    http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 W3C� SOFTWARE NOTICE AND LICENSE
  65 * @version    SVN: $Id: Client.php 306619 2010-12-24 12:16:07Z heino $
  66 * @link       http://pear.php.net/package/Net_NNTP
  67 * @see
  68 */
  69
  70// Warn about PHP bugs
  71if (version_compare(PHP_VERSION, '5.2.11', 'eq') === 1) {
  72    trigger_error('PHP bug #16657 breaks feof() on socket streams! Connection consistency might be compromised: ' . PHP_VERSION, E_USER_WARNING);
  73}
  74
  75/**
  76 *
  77 */
  78//require_once 'Net/NNTP/Error.php';
  79require_once './NNTP/Protocol/Responsecode.php';
  80
  81
  82// {{{ constants
  83
  84/**
  85 * Default host
  86 *
  87 * @access     public
  88 * @ignore
  89 */
  90define('NET_NNTP_PROTOCOL_CLIENT_DEFAULT_HOST', 'localhost');
  91
  92/**
  93 * Default port
  94 *
  95 * @access     public
  96 * @ignore
  97 */
  98define('NET_NNTP_PROTOCOL_CLIENT_DEFAULT_PORT', '119');
  99
 100// }}}
 101// {{{ Net_NNTP_Protocol_Client
 102
 103/**
 104 * Low level NNTP Client
 105 *
 106 * Implements the client part of the NNTP standard acording to:
 107 *  - RFC 977,
 108 *  - RFC 2980,
 109 *  - RFC 850/1036, and
 110 *  - RFC 822/2822
 111 *
 112 * Each NNTP command is represented by a method: cmd*()
 113 *
 114 * WARNING: The Net_NNTP_Protocol_Client class is considered an internal class
 115 *          (and should therefore currently not be extended directly outside of
 116 *          the Net_NNTP package). Therefore its API is NOT required to be fully
 117 *          stable, for as long as such changes doesn't affect the public API of
 118 *          the Net_NNTP_Client class, which is considered stable.
 119 *
 120 * TODO:	cmdListActiveTimes()
 121 *      	cmdDistribPats()
 122 *
 123 * @category   Net
 124 * @package    Net_NNTP
 125 * @author     Heino H. Gehlsen <heino@gehlsen.dk>
 126 * @version    package: 1.5.0RC1 (beta)
 127 * @version    api: 0.9.0 (alpha)
 128 * @access     private
 129 * @see        Net_NNTP_Client
 130 */
 131class Net_NNTP_Protocol_Client 
 132{
 133    // {{{ properties
 134
 135    /**
 136     * The socket resource being used to connect to the NNTP server.
 137     *
 138     * @var resource
 139     * @access private
 140     */
 141    var $_socket = null;
 142
 143    /**
 144     * Contains the last recieved status response code and text
 145     *
 146     * @var array
 147     * @access private
 148     */
 149    var $_currentStatusResponse = null;
 150
 151    /**
 152     *
 153     *
 154     * @var     object
 155     * @access  private
 156     */
 157    var $_logger = null;
 158
 159    /**
 160     * Contains false on non-ssl connection and true when ssl
 161     *
 162     * @var     object
 163     * @access  private
 164     */
 165    var $_ssl = false;
 166
 167    // }}}
 168    // {{{ constructor
 169
 170    /**
 171     * Constructor
 172     *
 173     * @access public
 174     */
 175    function __construct()
 176    {
 177
 178    	//
 179//    	parent::PEAR('Net_NNTP_Error');
 180    	//parent::PEAR();
 181    }
 182
 183    // }}}
 184    // {{{ getPackageVersion()
 185
 186    /**
 187     *
 188     *
 189     * @access public
 190     */
 191    function getPackageVersion() {
 192	return '1.5.0RC1';
 193    }
 194
 195    // }}}
 196    // {{{ getApiVersion()
 197
 198    /**
 199     *
 200     *
 201     * @access public
 202     */
 203    function getApiVersion() {
 204	return '0.9.0';
 205    }
 206
 207    // }}}
 208    // {{{ setLogger()
 209
 210    /**
 211     *
 212     *
 213     * @param object $logger
 214     *
 215     * @access protected
 216     */
 217    function setLogger($logger)
 218    {
 219        $this->_logger = $logger;
 220    }
 221
 222    // }}}
 223    // {{{ setDebug()
 224
 225    /**
 226     * @deprecated
 227     */
 228    function setDebug($debug = true)
 229    {
 230    	trigger_error('You are using deprecated API v1.0 in Net_NNTP_Protocol_Client: setDebug() ! Debugging in now automatically handled when a logger is given.', E_USER_NOTICE);
 231    }
 232
 233    // }}}
 234    // {{{ _clearSSLErrors()
 235
 236    /**
 237     * Clears ssl errors from the openssl error stack
 238     */
 239    function _clearSSLErrors()
 240    {
 241	if ($this->_ssl) {
 242		while ($msg = openssl_error_string()) {};
 243	}
 244    }
 245
 246    // }}}
 247    // {{{ _sendCommand()
 248
 249    /**
 250     * Send command
 251     *
 252     * Send a command to the server. A carriage return / linefeed (CRLF) sequence
 253     * will be appended to each command string before it is sent to the IMAP server.
 254     *
 255     * @param string $cmd The command to launch, ie: "ARTICLE 1004853"
 256     *
 257     * @return mixed (int) response code on success or (object) pear_error on failure
 258     * @access private
 259     */
 260    function _sendCommand($cmd)
 261    {
 262        // NNTP/RFC977 only allows command up to 512 (-2) chars.
 263        if (!strlen($cmd) > 510) {
 264            $this->throwError('Failed writing to socket! (Command to long - max 510 chars)');
 265        }
 266
 267/***************************************************************************************/
 268/* Credit: Thanks to Brendan Coles <bcoles@gmail.com> (http://itsecuritysolutions.org) */
 269/*         for pointing out possibility to inject pipelined NNTP commands into pretty  */
 270/*         much any Net_NNTP command-sending function with user input, by appending    */
 271/*         a new line character followed by the injection.                             */
 272/***************************************************************************************/
 273        // Prevent new line (and possible future) characters in the NNTP commands
 274        // Net_NNTP does not support pipelined commands. Inserting a new line charecter
 275        // allows sending multiple commands and thereby making the communication between
 276        // NET_NNTP and the server out of sync...
 277        if (preg_match_all('/\r?\n/', $cmd, $matches, PREG_PATTERN_ORDER)) {
 278            foreach ($matches[0] as $key => $match) {
 279                $this->_logger->debug("Illegal character in command: ". htmlentities(str_replace(array("\r","\n"), array("'Carriage Return'", "'New Line'"), $match)));
 280            }
 281            $this->throwError("Illegal character(s) in NNTP command!");
 282        }
 283
 284    	// Check if connected
 285    	if (!$this->_isConnected()) {
 286            $this->throwError('Failed to write to socket! (connection lost!)', -999);
 287        }
 288
 289    	// Send the command
 290	$this->_clearSSLErrors();
 291    	$R = @fwrite($this->_socket, $cmd . "\r\n");
 292        if ($R === false) {
 293            $this->throwError('Failed to write to socket!');
 294        }
 295
 296    	//
 297    	if ($this->_logger && $this->_logger->_isMasked(PEAR_LOG_DEBUG)) {
 298    	    $this->_logger->debug('C: ' . $cmd);
 299        }
 300
 301    	//
 302    	return $this->_getStatusResponse();
 303    }
 304
 305    // }}}
 306    // {{{ _getStatusResponse()
 307
 308    /**
 309     * Get servers status response after a command.
 310     *
 311     * @return mixed (int) statuscode on success or (object) pear_error on failure
 312     * @access private
 313     */
 314    function _getStatusResponse()
 315    {
 316    	// Retrieve a line (terminated by "\r\n") from the server.
 317	$this->_clearSSLErrors();
 318    	$response = @fgets($this->_socket);
 319        if ($response === false) {
 320            $this->throwError('Failed to read from socket...!');
 321        }
 322
 323        $streamStatus = stream_get_meta_data($this->_socket);
 324        if ($streamStatus['timed_out']) {
 325            $this->throwError('Connection timed out');
 326        }
 327
 328    	//
 329    	if ($this->_logger && $this->_logger->_isMasked(PEAR_LOG_DEBUG)) {
 330    	    $this->_logger->debug('S: ' . rtrim($response, "\r\n"));
 331        }
 332
 333    	// Trim the start of the response in case of misplased whitespace (should not be needen!!!)
 334    	$response = ltrim($response);
 335
 336        $this->_currentStatusResponse = array(
 337    	    	    	    	    	      (int) substr($response, 0, 3),
 338    	                                      (string) rtrim(substr($response, 4))
 339    	    	    	    	    	     );
 340
 341    	//
 342    	return $this->_currentStatusResponse[0];
 343    }
 344
 345    // }}}
 346    // {{{ _getTextResponse()
 347
 348    /**
 349     * Retrieve textural data
 350     *
 351     * Get data until a line with only a '.' in it is read and return data.
 352     *
 353     * @return mixed (array) text response on success or (object) pear_error on failure
 354     * @access private
 355     */
 356    function _getTextResponse()
 357    {
 358        $data = array();
 359        $line = '';
 360
 361    	//
 362    	$debug = $this->_logger && $this->_logger->_isMasked(PEAR_LOG_DEBUG);
 363
 364        // Continue until connection is lost
 365        while (!feof($this->_socket)) {
 366
 367            // Retrieve and append up to 8192 characters from the server.
 368	    $this->_clearSSLErrors();
 369            $recieved = @fgets($this->_socket, 8192);
 370
 371            if ($recieved === false) {
 372                $this->throwError('Failed to read line from socket.', null);
 373    	    }
 374
 375            $streamStatus = stream_get_meta_data($this->_socket);
 376            if ($streamStatus['timed_out']) {
 377                $this->throwError('Connection timed out');
 378            }
 379
 380
 381            $line .= $recieved;
 382			
 383            // Continue if the line is not terminated by CRLF
 384            if (substr($line, -2) != "\r\n" || strlen($line) < 2) {
 385				usleep(25000);
 386                continue;
 387            }
 388
 389            // Validate recieved line
 390            if (false) {
 391                // Lines should/may not be longer than 998+2 chars (RFC2822 2.3)
 392                if (strlen($line) > 1000) {
 393    	    	    if ($this->_logger) {
 394    	    	    	$this->_logger->notice('Max line length...');
 395    	    	    }
 396                    $this->throwError('Invalid line recieved!', null);
 397                }
 398            }
 399
 400            // Remove CRLF from the end of the line
 401            $line = substr($line, 0, -2);
 402
 403            // Check if the line terminates the textresponse
 404            if ($line == '.') {
 405
 406    	    	if ($this->_logger) {
 407    	    	    $this->_logger->debug('T: ' . $line);
 408    	    	}
 409
 410                // return all previous lines
 411                return $data;
 412            }
 413
 414            // If 1st char is '.' it's doubled (NNTP/RFC977 2.4.1)
 415            if (substr($line, 0, 2) == '..') {
 416                $line = substr($line, 1);
 417            }
 418
 419    	    //
 420    	    if ($debug) {
 421    	    	$this->_logger->debug('T: ' . $line);
 422    	    }
 423
 424            // Add the line to the array of lines
 425            $data[] = $line;
 426
 427            // Reset/empty $line
 428            $line = '';
 429        }
 430
 431    	if ($this->_logger) {
 432    	    $this->_logger->warning('Broke out of reception loop! This souldn\'t happen unless connection has been lost?');
 433    	}
 434
 435
 436
 437    	//
 438    	$this->throwError('End of stream! Connection lost?', null);
 439    }
 440
 441    /**
 442     * Retrieve blob
 443     *
 444     * Get data and assume we do not hit any blindspots
 445     *
 446     * @return mixed (array) text response on success or (object) pear_error on failure
 447     * @access private
 448     */
 449    function _getCompressedResponse()
 450    {
 451        $data = array();
 452		
 453		// We can have two kinds of compressed support:
 454		//
 455		// - yEnc encoding
 456		// - Just a gzip drop
 457		//
 458		// We try to autodetect which one this uses
 459//echo "DEBUG: Entering getCompressedResponse()" . PHP_EOL;
 460		
 461		$line = @fread($this->_socket, 1024);
 462
 463//		echo "DEBUG: getCompressedResponse(): called "  . PHP_EOL;
 464		
 465		if (substr($line, 0, 7) == '=ybegin') {
 466			$data = $this->_getTextResponse();
 467			$data = $line . "\r\n" . implode("", $data);
 468   	    	$data = $this->yencDecode($data);
 469			$data = explode("\r\n", gzinflate($data));
 470			
 471			return $data;
 472		} # if
 473
 474		// We cannot use blocked I/O on this one
 475//echo "DEBUG: Unsetting blocking calls "  . PHP_EOL;
 476		$streamMetadata = stream_get_meta_data($this->_socket);
 477		stream_set_blocking($this->_socket, false);
 478//echo "DEBUG: Unset blocking calls "  . PHP_EOL;
 479		
 480        // Continue until connection is lost or we don't receive any data anymore
 481		$tries = 0;
 482		$uncompressed = '';
 483//echo "DEBUG: Before while loop: " . feof($this->_socket) . PHP_EOL;
 484        while (!feof($this->_socket)) {
 485
 486            # Retrieve and append up to 32k characters from the server
 487            $received = @fread($this->_socket, 32768);
 488			if (strlen($received) == 0) {
 489//				echo "DEBUG: In while loop, received 0 : " . feof($this->_socket) . PHP_EOL;
 490				$tries++;
 491				
 492				# Try decompression
 493				$uncompressed = @gzuncompress($line);
 494				if (($uncompressed !== false) || ($tries > 500)) {
 495//					echo "DEBUG: In while loop, received 0 and tries: " . feof($this->_socket) . ' / ' . $tries . PHP_EOL;
 496					break;
 497				} # if
 498				
 499				if ($tries % 50 == 0) {
 500//					echo "DEBUG: In while loop, sleeping: " . feof($this->_socket) . ' / ' . $tries . PHP_EOL;
 501					usleep(50000);
 502				} # if
 503			} # if
 504			
 505			# an error occured
 506			if ($received === false) {
 507				@fclose($this->_socket);
 508				$this->_socket = false;
 509			} # if
 510			
 511//echo "DEBUG: In while loop: " . feof($this->_socket) . ' / ' . strlen($line) . PHP_EOL;
 512
 513            $line .= $received;
 514        } # while
 515
 516//echo "DEBUG: Out of while loo " . feof($this->_socket) . ' / ' . $uncompressed . PHP_EOL;
 517		
 518		# and set the stream to its original blocked(?) value
 519		stream_set_blocking($this->_socket, $streamMetadata['blocked']);
 520		$data = explode("\r\n", $uncompressed);
 521		$dataCount = count($data);
 522
 523		# Gzipped compress includes the "." and linefeed in the compressed stream
 524		# skip those.
 525		if ($dataCount >= 2) {
 526			if (($data[($dataCount - 2)] == ".") && (empty($data[($dataCount - 1)]))) {
 527				array_pop($data);
 528				array_pop($data);
 529			} # if
 530			
 531			$data = array_filter($data);
 532		} # if
 533		
 534		return $data;
 535    } # _getCompressedResponse
 536	
 537    // }}}
 538    // {{{ _sendText()
 539
 540    /**
 541     *
 542     *
 543     * @access private
 544     */
 545    function _sendArticle($article)
 546    {
 547    	/* data should be in the format specified by RFC850 */
 548
 549    	switch (true) {
 550    	case is_string($article):
 551    	    //
 552	    $this->_clearSSLErrors();
 553    	    @fwrite($this->_socket, $article);
 554	    $this->_clearSSLErrors();
 555    	    @fwrite($this->_socket, "\r\n.\r\n");
 556
 557    	    //
 558    	    if ($this->_logger && $this->_logger->_isMasked(PEAR_LOG_DEBUG)) {
 559    	        foreach (explode("\r\n", $article) as $line) {
 560    		    $this->_logger->debug('D: ' . $line);
 561    	        }
 562    	    	$this->_logger->debug('D: .');
 563    	    }
 564	    break;
 565
 566    	case is_array($article):
 567    	    //
 568    	    $header = reset($article);
 569    	    $body = next($article);
 570
 571/* Experimental...
 572    	    // If header is an array, implode it.
 573    	    if (is_array($header)) {
 574    	        $header = implode("\r\n", $header) . "\r\n";
 575    	    }
 576*/
 577
 578    	    // Send header (including separation line)
 579	    $this->_clearSSLErrors();
 580    	    @fwrite($this->_socket, $header);
 581	    $this->_clearSSLErrors();
 582    	    @fwrite($this->_socket, "\r\n");
 583
 584    	    //
 585    	    if ($this->_logger && $this->_logger->_isMasked(PEAR_LOG_DEBUG)) {
 586    	        foreach (explode("\r\n", $header) as $line) {
 587    	    	    $this->_logger->debug('D: ' . $line);
 588    	    	}
 589    	    }
 590
 591
 592/* Experimental...
 593    	    // If body is an array, implode it.
 594    	    if (is_array($body)) {
 595    	        $header = implode("\r\n", $body) . "\r\n";
 596    	    }
 597*/
 598
 599    	    // Send body
 600	    $this->_clearSSLErrors();
 601    	    @fwrite($this->_socket, $body);
 602	    $this->_clearSSLErrors();
 603    	    @fwrite($this->_socket, "\r\n.\r\n");
 604
 605    	    //
 606    	    if ($this->_logger && $this->_logger->_isMasked(PEAR_LOG_DEBUG)) {
 607    	        foreach (explode("\r\n", $body) as $line) {
 608    	    	    $this->_logger->debug('D: ' . $line);
 609    	    	}
 610    	        $this->_logger->debug('D: .');
 611    	    }
 612	    break;
 613
 614	default:
 615    	    $this->throwError('Ups...', null, null);
 616    	}
 617
 618	return true;
 619    }
 620
 621    // }}}
 622    // {{{ _currentStatusResponse()
 623
 624    /**
 625     *
 626     *
 627     * @return string status text
 628     * @access private
 629     */
 630    function _currentStatusResponse()
 631    {
 632    	return $this->_currentStatusResponse[1];
 633    }
 634
 635    // }}}
 636    // {{{ _handleUnexpectedResponse()
 637
 638    /**
 639     *
 640     *
 641     * @param int $code Status code number
 642     * @param string $text Status text
 643     *
 644     * @return mixed
 645     * @access private
 646     */
 647    function _handleUnexpectedResponse($code = null, $text = null)
 648    {
 649    	if ($code === null) {
 650    	    $code = $this->_currentStatusResponse[0];
 651	}
 652
 653    	if ($text === null) {
 654    	    $text = $this->_currentStatusResponse();
 655	}
 656
 657    	switch ($code) {
 658    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NOT_PERMITTED: // 502, 'access restriction or permission denied' / service permanently unavailable
 659    	    	$this->throwError('Command not permitted / Access restriction / Permission denied', $code, $text);
 660    	    	break;
 661    	    default:
 662    	    	$this->throwError("Unexpected response", $code, $text);
 663    	}
 664    }
 665
 666    // }}}
 667
 668/* Session administration commands */
 669
 670    // {{{ Connect()
 671
 672    /**
 673     * Connect to a NNTP server
 674     *
 675     * @param string	$host	(optional) The address of the NNTP-server to connect to, defaults to 'localhost'.
 676     * @param mixed	$encryption	(optional)
 677     * @param int	$port	(optional) The port number to connect to, defaults to 119.
 678     * @param int	$timeout	(optional)
 679     *
 680     * @return mixed (bool) on success (true when posting allowed, otherwise false) or (object) pear_error on failure
 681     * @access protected
 682     */
 683    function connect($host = null, $encryption = null, $port = null, $timeout = null)
 684    {
 685    	//
 686        if ($this->_isConnected() ) {
 687    	    $this->throwError('Already connected, disconnect first!', null);
 688    	}
 689
 690    	// v1.0.x API
 691    	if (is_int($encryption)) {
 692	    trigger_error('You are using deprecated API v1.0 in Net_NNTP_Protocol_Client: connect() !', E_USER_NOTICE);
 693    	    $port = $encryption;
 694	    $encryption = false;
 695    	}
 696
 697    	//
 698    	if (is_null($host)) {
 699    	    $host = 'localhost';
 700    	}
 701
 702    	// Choose transport based on encryption, and if no port is given, use default for that encryption
 703    	switch ($encryption) {
 704	    case null:
 705	    case false:
 706		$transport = 'tcp';
 707    	    	$port = is_null($port) ? 119 : $port;
 708		break;
 709	    case 'ssl':
 710	    case 'tls':
 711		$transport = $encryption;
 712    	    	$port = is_null($port) ? 563 : $port;
 713		$this->_ssl = true;
 714		break;
 715	    default:
 716    	    	trigger_error('$encryption parameter must be either tcp, tls or ssl.', E_USER_ERROR);
 717    	}
 718
 719    	//
 720    	if (is_null($timeout)) {
 721    	    $timeout = 15;
 722    	}
 723
 724    	// Open Connection
 725    	$R = stream_socket_client($transport . '://' . $host . ':' . $port, $errno, $errstr, $timeout);
 726    	if ($R === false) {
 727    	    if ($this->_logger) {
 728    	        $this->_logger->notice("Connection to $transport://$host:$port failed.");
 729    	    }
 730    	    return $R;
 731    	}
 732
 733    	$this->_socket = $R;
 734
 735    	//
 736    	if ($this->_logger) {
 737    	    $this->_logger->info("Connection to $transport://$host:$port has been established.");
 738    	}
 739
 740        // set a stream timeout for each operation
 741        stream_set_timeout($this->_socket, 240);
 742
 743        // Retrive the server's initial response.
 744    	$response = $this->_getStatusResponse();
 745
 746        switch ($response) {
 747    	    case NET_NNTP_PROTOCOL_RESPONSECODE_READY_POSTING_ALLOWED: // 200, Posting allowed
 748    	    	// TODO: Set some variable before return
 749
 750    	        return true;
 751    	        break;
 752    	    case NET_NNTP_PROTOCOL_RESPONSECODE_READY_POSTING_PROHIBITED: // 201, Posting NOT allowed
 753    	        //
 754    	    	if ($this->_logger) {
 755    	    	    $this->_logger->info('Posting not allowed!');
 756    	    	}
 757
 758	    	// TODO: Set some variable before return
 759
 760    	    	return true;
 761    	        break;
 762    	    case 400:
 763    	    	$this->throwError('Server refused connection', $response, $this->_currentStatusResponse());
 764    	    	break;
 765    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NOT_PERMITTED: // 502, 'access restriction or permission denied' / service permanently unavailable
 766    	    	$this->throwError('Server refused connection', $response, $this->_currentStatusResponse());
 767    	    	break;
 768    	    default:
 769    	    	return $this->_handleUnexpectedResponse($response);
 770    	}
 771    }
 772
 773    // }}}
 774    // {{{ disconnect()
 775
 776    /**
 777     * alias for cmdQuit()
 778     *
 779     * @access protected
 780     */
 781    function disconnect()
 782    {
 783    	return $this->cmdQuit();
 784    }
 785
 786    // }}}
 787    // {{{ cmdCapabilities()
 788
 789    /**
 790     * Returns servers capabilities
 791     *
 792     * @return mixed (array) list of capabilities on success or (object) pear_error on failure
 793     * @access protected
 794     */
 795    function cmdCapabilities()
 796    {
 797        // tell the newsserver we want an article
 798        $response = $this->_sendCommand('CAPABILITIES');
 799
 800    	switch ($response) {
 801            case NET_NNTP_PROTOCOL_RESPONSECODE_CAPABILITIES_FOLLOW: // 101, Draft: 'Capability list follows'
 802    	    	$data = $this->_getTextResponse();
 803    	    	return $data;
 804    	        break;
 805    	    default:
 806    	    	return $this->_handleUnexpectedResponse($response);
 807    	}
 808    }
 809
 810    // }}}
 811    // {{{ cmdModeReader()
 812
 813    /**
 814     *
 815     *
 816     * @return mixed (bool) true when posting allowed, false when postind disallowed or (object) pear_error on failure
 817     * @access protected
 818     */
 819    function cmdModeReader()
 820    {
 821        // tell the newsserver we want an article
 822        $response = $this->_sendCommand('MODE READER');
 823
 824    	switch ($response) {
 825            case NET_NNTP_PROTOCOL_RESPONSECODE_READY_POSTING_ALLOWED: // 200, RFC2980: 'Hello, you can post'
 826
 827	    	// TODO: Set some variable before return
 828
 829    	    	return true;
 830    	        break;
 831    	    case NET_NNTP_PROTOCOL_RESPONSECODE_READY_POSTING_PROHIBITED: // 201, RFC2980: 'Hello, you can't post'
 832    	    	if ($this->_logger) {
 833    	    	    $this->_logger->info('Posting not allowed!');
 834    	    	}
 835
 836	    	// TODO: Set some variable before return
 837
 838    	    	return false;
 839    	    	break;
 840    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NOT_PERMITTED: // 502, 'access restriction or permission denied' / service permanently unavailable
 841    	    	$this->throwError('Connection being closed, since service so permanently unavailable', $response, $this->_currentStatusResponse());
 842    	    	break;
 843    	    default:
 844    	    	return $this->_handleUnexpectedResponse($response);
 845    	}
 846    }
 847
 848    // }}}
 849    // {{{ cmdQuit()
 850
 851    /**
 852     * Disconnect from the NNTP server
 853     *
 854     * @return mixed (bool) true on success or (object) pear_error on failure
 855     * @access protected
 856     */
 857    function cmdQuit()
 858    {
 859    	// Tell the server to close the connection
 860    	$response = $this->_sendCommand('QUIT');
 861
 862        switch ($response) {
 863    	    case 205: // RFC977: 'closing connection - goodbye!'
 864    	    	// If socket is still open, close it.
 865    	    	if ($this->_isConnected()) {
 866    	    	    fclose($this->_socket);
 867    	    	}
 868
 869    	    	if ($this->_logger) {
 870    	    	    $this->_logger->info('Connection closed.');
 871    	    	}
 872
 873    	    	return true;
 874    	    	break;
 875    	    default:
 876    	    	return $this->_handleUnexpectedResponse($response);
 877    	}
 878    }
 879
 880    // }}}
 881
 882/* */
 883
 884    // {{{ cmdStartTLS()
 885
 886    /**
 887     *
 888     *
 889     * @return mixed (bool) on success or (object) pear_error on failure
 890     * @access protected
 891     */
 892    function cmdStartTLS()
 893    {
 894        $response = $this->_sendCommand('STARTTLS');
 895
 896    	switch ($response) {
 897    	    case 382: // RFC4642: 'continue with TLS negotiation'
 898    	    	$encrypted = stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
 899    	    	switch (true) {
 900    	    	    case $encrypted === true:
 901    	    	    	if ($this->_logger) {
 902    	    	    	    $this->_logger->info('TLS encryption started.');
 903    	    	    	}
 904    	    	    	return true;
 905    	    	    	break;
 906    	    	    case $encrypted === false:
 907    	    	    	if ($this->_logger) {
 908    	    	    	    $this->_logger->info('TLS encryption failed.');
 909    	    	    	}
 910    	    	    	$this->throwError('Could not initiate TLS negotiation', $response, $this->_currentStatusResponse());
 911    	    	    	break;
 912    	    	    case is_int($encrypted):
 913    	    	    	$this->throwError('', $response, $this->_currentStatusResponse());
 914    	    	    	break;
 915    	    	    default:
 916    	    	    	$this->throwError('Internal error - unknown response from stream_socket_enable_crypto()', $response, $this->_currentStatusResponse());
 917    	    	}
 918    	    	break;
 919    	    case 580: // RFC4642: 'can not initiate TLS negotiation'
 920    	    	$this->throwError('', $response, $this->_currentStatusResponse());
 921    	    	break;
 922    	    default:
 923    	    	return $this->_handleUnexpectedResponse($response);
 924    	}
 925    }
 926
 927    // }}}
 928
 929/* Article posting and retrieval */
 930
 931    /* Group and article selection */
 932
 933    // {{{ cmdGroup()
 934
 935    /**
 936     * Selects a news group (issue a GROUP command to the server)
 937     *
 938     * @param string $newsgroup The newsgroup name
 939     *
 940     * @return mixed (array) groupinfo on success or (object) pear_error on failure
 941     * @access protected
 942     */
 943    function cmdGroup($newsgroup)
 944    {
 945        $response = $this->_sendCommand('GROUP '.$newsgroup);
 946
 947    	switch ($response) {
 948    	    case NET_NNTP_PROTOCOL_RESPONSECODE_GROUP_SELECTED: // 211, RFC977: 'n f l s group selected'
 949    	    	$response_arr = explode(' ', trim($this->_currentStatusResponse()));
 950
 951    	    	if ($this->_logger) {
 952    	    	    $this->_logger->info('Group selected: '.$response_arr[3]);
 953    	    	}
 954
 955    	    	return array('group' => $response_arr[3],
 956    	                     'first' => $response_arr[1],
 957    	    	             'last'  => $response_arr[2],
 958    	                     'count' => $response_arr[0]);
 959    	    	break;
 960    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_GROUP: // 411, RFC977: 'no such news group'
 961    	    	$this->throwError('No such news group', $response, $this->_currentStatusResponse());
 962    	    	break;
 963    	    default:
 964    	    	return $this->_handleUnexpectedResponse($response);
 965    	}
 966    }
 967
 968    // }}}
 969    // {{{ cmdListgroup()
 970
 971    /**
 972     *
 973     *
 974     * @param optional string $newsgroup
 975     * @param optional mixed $range
 976     *
 977     * @return array | optional mixed (array) on success or (object) pear_error on failure
 978     * @access protected
 979     */
 980    function cmdListgroup($newsgroup = null, $range = null)
 981    {
 982        if (is_null($newsgroup)) {
 983    	    $command = 'LISTGROUP';
 984    	} else {
 985    	    if (is_null($range)) {
 986    	        $command = 'LISTGROUP ' . $newsgroup;
 987    	    } else {
 988    	        $command = 'LISTGROUP ' . $newsgroup . ' ' . $range;
 989    	    }
 990        }
 991
 992        $response = $this->_sendCommand($command);
 993
 994    	switch ($response) {
 995    	    case NET_NNTP_PROTOCOL_RESPONSECODE_GROUP_SELECTED: // 211, RFC2980: 'list of article numbers follow'
 996
 997    	    	$articles = $this->_getTextResponse();
 998    	        $response_arr = explode(' ', trim($this->_currentStatusResponse()), 4);
 999
1000		// If server does not return group summary in status response, return null'ed array
1001    	    	if (!is_numeric($response_arr[0]) || !is_numeric($response_arr[1]) || !is_numeric($response_arr[2]) || empty($response_arr[3])) {
1002    	    	    return array('group'    => null,
1003    	        	         'first'    => null,
1004    	    	    	         'last'     => null,
1005    	    	    		 'count'    => null,
1006    	    	    	         'articles' => $articles);
1007		}
1008
1009    	    	return array('group'    => $response_arr[3],
1010    	                     'first'    => $response_arr[1],
1011    	    	             'last'     => $response_arr[2],
1012    	    	             'count'    => $response_arr[0],
1013    	    	             'articles' => $articles);
1014    	    	break;
1015    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC2980: 'Not currently in newsgroup'
1016    	    	$this->throwError('Not currently in newsgroup', $response, $this->_currentStatusResponse());
1017    	    	break;
1018    	    case 502: // RFC2980: 'no permission'
1019    	    	$this->throwError('No permission', $response, $this->_currentStatusResponse());
1020    	    	break;
1021    	    default:
1022    	    	return $this->_handleUnexpectedResponse($response);
1023    	}
1024    }
1025
1026    // }}}
1027    // {{{ cmdLast()
1028
1029    /**
1030     *
1031     *
1032     * @return mixed (array) or (string) or (int) or (object) pear_error on failure
1033     * @access protected
1034     */
1035    function cmdLast()
1036    {
1037        //
1038        $response = $this->_sendCommand('LAST');
1039    	switch ($response) {
1040    	    case NET_NNTP_PROTOCOL_RESPONSECODE_ARTICLE_SELECTED: // 223, RFC977: 'n a article retrieved - request text separately (n = article number, a = unique article id)'
1041    	    	$response_arr = explode(' ', trim($this->_currentStatusResponse()));
1042
1043    	    	if ($this->_logger) {
1044    	    	    $this->_logger->info('Selected previous article: ' . $response_arr[0] .' - '. $response_arr[1]);
1045    	    	}
1046
1047    	    	return array($response_arr[0], (string) $response_arr[1]);
1048    	    	break;
1049    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC977: 'no newsgroup selected'
1050    	    	$this->throwError('No newsgroup has been selected', $response, $this->_currentStatusResponse());
1051    	    	break;
1052    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC977: 'no current article has been selected'
1053    	    	$this->throwError('No current article has been selected', $response, $this->_currentStatusResponse());
1054    	    	break;
1055    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_PREVIOUS_ARTICLE: // 422, RFC977: 'no previous article in this group'
1056    	    	$this->throwError('No previous article in this group', $response, $this->_currentStatusResponse());
1057    	    	break;
1058    	    default:
1059    	    	return $this->_handleUnexpectedResponse($response);
1060    	}
1061    }
1062
1063    // }}}
1064    // {{{ cmdNext()
1065
1066    /**
1067     *
1068     *
1069     * @return mixed (array) or (string) or (int) or (object) pear_error on failure
1070     * @access protected
1071     */
1072    function cmdNext()
1073    {
1074        //
1075        $response = $this->_sendCommand('NEXT');
1076
1077    	switch ($response) {
1078    	    case NET_NNTP_PROTOCOL_RESPONSECODE_ARTICLE_SELECTED: // 223, RFC977: 'n a article retrieved - request text separately (n = article number, a = unique article id)'
1079    	    	$response_arr = explode(' ', trim($this->_currentStatusResponse()));
1080
1081    	    	if ($this->_logger) {
1082    	    	    $this->_logger->info('Selected previous article: ' . $response_arr[0] .' - '. $response_arr[1]);
1083    	    	}
1084
1085    	    	return array($response_arr[0], (string) $response_arr[1]);
1086    	    	break;
1087    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC977: 'no newsgroup selected'
1088    	    	$this->throwError('No newsgroup has been selected', $response, $this->_currentStatusResponse());
1089    	    	break;
1090    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC977: 'no current article has been selected'
1091    	    	$this->throwError('No current article has been selected', $response, $this->_currentStatusResponse());
1092    	    	break;
1093    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_NEXT_ARTICLE: // 421, RFC977: 'no next article in this group'
1094    	    	$this->throwError('No next article in this group', $response, $this->_currentStatusResponse());
1095    	    	break;
1096    	    default:
1097    	    	return $this->_handleUnexpectedResponse($response);
1098    	}
1099    }
1100
1101    // }}}
1102
1103    /* Retrieval of articles and article sections */
1104
1105    // {{{ cmdArticle()
1106
1107    /**
1108     * Get an article from the currently open connection.
1109     *
1110     * @param mixed $article Either a message-id or a message-number of the article to fetch. If null or '', then use current article.
1111     *
1112     * @return mixed (array) article on success or (object) pear_error on failure
1113     * @access protected
1114     */
1115    function cmdArticle($article = null)
1116    {
1117        if (is_null($article)) {
1118    	    $command = 'ARTICLE';
1119    	} else {
1120            $command = 'ARTICLE ' . $article;
1121        }
1122
1123        // tell the newsserver we want an article
1124        $response = $this->_sendCommand($command);
1125
1126    	switch ($response) {
1127    	    case NET_NNTP_PROTOCOL_RESPONSECODE_ARTICLE_FOLLOWS:  // 220, RFC977: 'n <a> article retrieved - head and body follow (n = article number, <a> = message-id)'
1128    	    	$data = $this->_getTextResponse();
1129
1130    	    	if ($this->_logger) {
1131    	    	    $this->_logger->info(($article == null ? 'Fetched current article' : 'Fetched article: '.$article));
1132    	    	}
1133    	    	return $data;
1134    	    	break;
1135    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC977: 'no newsgroup has been selected'
1136    	    	$this->throwError('No newsgroup has been selected', $response, $this->_currentStatusResponse());
1137    	    	break;
1138    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC977: 'no current article has been selected'
1139    	    	$this->throwError('No current article has been selected', $response, $this->_currentStatusResponse());
1140    	    	break;
1141    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_NUMBER: // 423, RFC977: 'no such article number in this group'
1142    	    	$this->throwError('No such article number in this group', $response, $this->_currentStatusResponse());
1143    	    	break;
1144    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_ID: // 430, RFC977: 'no such article found'
1145    	    	$this->throwError('No such article found', $response, $this->_currentStatusResponse());
1146    	    	break;
1147    	    default:
1148    	    	return $this->_handleUnexpectedResponse($response);
1149    	}
1150    }
1151
1152    // }}}
1153    // {{{ cmdHead()
1154
1155    /**
1156     * Get the headers of an article from the currently open connection.
1157     *
1158     * @param mixed $article Either a message-id or a message-number of the article to fetch the headers from. If null or '', then use current article.
1159     *
1160     * @return mixed (array) headers on success or (object) pear_error on failure
1161     * @access protected
1162     */
1163    function cmdHead($article = null)
1164    {
1165        if (is_null($article)) {
1166    	    $command = 'HEAD';
1167    	} else {
1168            $command = 'HEAD ' . $article;
1169        }
1170
1171        // tell the newsserver we want the header of an article
1172        $response = $this->_sendCommand($command);
1173
1174    	switch ($response) {
1175    	    case NET_NNTP_PROTOCOL_RESPONSECODE_HEAD_FOLLOWS:     // 221, RFC977: 'n <a> article retrieved - head follows'
1176    	    	$data = $this->_getTextResponse();
1177
1178    	    	if ($this->_logger) {
1179    	    	    $this->_logger->info(($article == null ? 'Fetched current article header' : 'Fetched article header for article: '.$article));
1180    	    	}
1181
1182    	        return $data;
1183    	    	break;
1184    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC977: 'no newsgroup has been selected'
1185    	    	$this->throwError('No newsgroup has been selected', $response, $this->_currentStatusResponse());
1186    	    	break;
1187    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC977: 'no current article has been selected'
1188    	    	$this->throwError('No current article has been selected', $response, $this->_currentStatusResponse());
1189    	    	break;
1190    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_NUMBER: // 423, RFC977: 'no such article number in this group'
1191    	    	$this->throwError('No such article number in this group', $response, $this->_currentStatusResponse());
1192    	    	break;
1193    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_ID: // 430, RFC977: 'no such article found'
1194    	    	$this->throwError('No such article found', $response, $this->_currentStatusResponse());
1195    	    	break;
1196    	    default:
1197    	    	return $this->_handleUnexpectedResponse($response);
1198    	}
1199    }
1200
1201    // }}}
1202    // {{{ cmdBody()
1203
1204    /**
1205     * Get the body of an article from the currently open connection.
1206     *
1207     * @param mixed $article Either a message-id or a message-number of the article to fetch the body from. If null or '', then use current article.
1208     *
1209     * @return mixed (array) body on success or (object) pear_error on failure
1210     * @access protected
1211     */
1212    function cmdBody($article = null)
1213    {
1214        if (is_null($article)) {
1215    	    $command = 'BODY';
1216    	} else {
1217            $command = 'BODY ' . $article;
1218        }
1219
1220        // tell the newsserver we want the body of an article
1221        $response = $this->_sendCommand($command);
1222
1223    	switch ($response) {
1224    	    case NET_NNTP_PROTOCOL_RESPONSECODE_BODY_FOLLOWS:     // 222, RFC977: 'n <a> article retrieved - body follows'
1225    	    	$data = $this->_getTextResponse();
1226
1227    	    	if ($this->_logger) {
1228    	    	    $this->_logger->info(($article == null ? 'Fetched current article body' : 'Fetched article body for article: '.$article));
1229    	    	}
1230
1231    	        return $data;
1232    	    	break;
1233    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC977: 'no newsgroup has been selected'
1234    	    	$this->throwError('No newsgroup has been selected', $response, $this->_currentStatusResponse());
1235    	    	break;
1236    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC977: 'no current article has been selected'
1237    	    	$this->throwError('No current article has been selected', $response, $this->_currentStatusResponse());
1238    	    	break;
1239    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_NUMBER: // 423, RFC977: 'no such article number in this group'
1240    	    	$this->throwError('No such article number in this group', $response, $this->_currentStatusResponse());
1241    	    	break;
1242    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_ID: // 430, RFC977: 'no such article found'
1243    	    	$this->throwError('No such article found', $response, $this->_currentStatusResponse());
1244    	    	break;
1245    	    default:
1246    	    	return $this->_handleUnexpectedResponse($response);
1247    	}
1248    }
1249
1250    // }}}
1251    // {{{ cmdStat
1252
1253    /**
1254     *
1255     *
1256     * @param mixed $article
1257     *
1258     * @return mixed (array) or (string) or (int) or (object) pear_error on failure
1259     * @access protected
1260     */
1261    function cmdStat($article = null)
1262    {
1263        if (is_null($article)) {
1264    	    $command = 'STAT';
1265    	} else {
1266            $command = 'STAT ' . $article;
1267        }
1268
1269        // tell the newsserver we want an article
1270        $response = $this->_sendCommand($command);
1271
1272    	switch ($response) {
1273    	    case NET_NNTP_PROTOCOL_RESPONSECODE_ARTICLE_SELECTED: // 223, RFC977: 'n <a> article retrieved - request text separately' (actually not documented, but copied from the ARTICLE command)
1274    	    	$response_arr = explode(' ', trim($this->_currentStatusResponse()));
1275
1276    	    	if ($this->_logger) {
1277    	    	    $this->_logger->info('Selected article: ' . $response_arr[0].' - '.$response_arr[1]);
1278    	    	}
1279
1280    	    	return array($response_arr[0], (string) $response_arr[1]);
1281    	    	break;
1282    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC977: 'no newsgroup has been selected' (actually not documented, but copied from the ARTICLE command)
1283    	    	$this->throwError('No newsgroup has been selected', $response, $this->_currentStatusResponse());
1284    	    	break;
1285    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_NUMBER: // 423, RFC977: 'no such article number in this group' (actually not documented, but copied from the ARTICLE command)
1286    	    	$this->throwError('No such article number in this group', $response, $this->_currentStatusResponse());
1287    	    	break;
1288    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_ID: // 430, RFC977: 'no such article found' (actually not documented, but copied from the ARTICLE command)
1289    	    	$this->throwError('No such article found', $response, $this->_currentStatusResponse());
1290    	    	break;
1291    	    default:
1292    	    	return $this->_handleUnexpectedResponse($response);
1293    	}
1294    }
1295
1296    // }}}
1297
1298    /* Article posting */
1299
1300    // {{{ cmdPost()
1301
1302    /**
1303     * Post an article to a newsgroup.
1304     *
1305     * @return mixed (bool) true on success or (object) pear_error on failure
1306     * @access protected
1307     */
1308    function cmdPost()
1309    {
1310        // tell the newsserver we want to post an article
1311    	$response = $this->_sendCommand('POST');
1312
1313    	switch ($response) {
1314    	    case NET_NNTP_PROTOCOL_RESPONSECODE_POSTING_SEND: // 340, RFC977: 'send article to be posted. End with <CR-LF>.<CR-LF>'
1315    	    	return true;
1316    	    	break;
1317    	    case NET_NNTP_PROTOCOL_RESPONSECODE_POSTING_PROHIBITED: // 440, RFC977: 'posting not allowed'
1318    	    	$this->throwError('Posting not allowed', $response, $this->_currentStatusResponse());
1319    	    	break;
1320    	    default:
1321    	    	return $this->_handleUnexpectedResponse($response);
1322    	}
1323
1324    }
1325
1326    // }}}
1327    // {{{ cmdPost2()
1328
1329    /**
1330     * Post an article to a newsgroup.
1331     *
1332     * @param mixed $article (string/array)
1333     *
1334     * @return mixed (bool) true on success or (object) pear_error on failure
1335     * @access protected
1336     */
1337    function cmdPost2($article)
1338    {
1339    	/* should be presented in the format specified by RFC850 */
1340
1341    	//
1342    	$this->_sendArticle($article);
1343
1344    	// Retrive server's response.
1345    	$response = $this->_getStatusResponse();
1346
1347    	switch ($response) {
1348    	    case NET_NNTP_PROTOCOL_RESPONSECODE_POSTING_SUCCESS: // 240, RFC977: 'article posted ok'
1349    	    	return true;
1350    	    	break;
1351    	    case NET_NNTP_PROTOCOL_RESPONSECODE_POSTING_FAILURE: // 441, RFC977: 'posting failed'
1352    	    	$this->throwError('Posting failed', $response, $this->_currentStatusResponse());
1353    	    	break;
1354    	    default:
1355    	    	return $this->_handleUnexpectedResponse($response);
1356    	}
1357    }
1358
1359    // }}}
1360    // {{{ cmdIhave()
1361
1362    /**
1363     *
1364     *
1365     * @param string $id
1366     *
1367     * @return mixed (bool) true on success or (object) pear_error on failure
1368     * @access protected
1369     */
1370    function cmdIhave($id)
1371    {
1372        // tell the newsserver we want to post an article
1373    	$response = $this->_sendCommand('IHAVE ' . $id);
1374
1375    	switch ($response) {
1376    	    case NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_SEND: // 335
1377    	    	return true;
1378    	    	break;
1379    	    case NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_UNWANTED: // 435
1380    	    	$this->throwError('Article not wanted', $response, $this->_currentStatusResponse());
1381    	    	break;
1382    	    case NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_FAILURE: // 436
1383    	    	$this->throwError('Transfer not possible; try again later', $response, $this->_currentStatusResponse());
1384    	    	break;
1385    	    default:
1386    	    	return $this->_handleUnexpectedResponse($response);
1387    	}
1388    }
1389
1390    // }}}
1391    // {{{ cmdIhave2()
1392
1393    /**
1394     *
1395     *
1396     * @param mixed $article (string/array)
1397     *
1398     * @return mixed (bool) true on success or (object) pear_error on failure
1399     * @access protected
1400     */
1401    function cmdIhave2($article)
1402    {
1403    	/* should be presented in the format specified by RFC850 */
1404
1405    	//
1406    	$this->_sendArticle($article);
1407
1408    	// Retrive server's response.
1409    	$response = $this->_getStatusResponse();
1410
1411    	switch ($response) {
1412    	    case NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_SUCCESS: // 235
1413    	    	return true;
1414    	    	break;
1415    	    case NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_FAILURE: // 436
1416    	    	$this->throwError('Transfer not possible; try again later', $response, $this->_currentStatusResponse());
1417    	    	break;
1418    	    case NET_NNTP_PROTOCOL_RESPONSECODE_TRANSFER_REJECTED: // 437
1419    	    	$this->throwError('Transfer rejected; do not retry', $response, $this->_currentStatusResponse());
1420    	    	break;
1421    	    default:
1422    	    	return $this->_handleUnexpectedResponse($response);
1423    	}
1424    }
1425
1426    // }}}
1427
1428/* Information commands */
1429
1430    // {{{ cmdDate()
1431
1432    /**
1433     * Get the date from the newsserver format of returned date
1434     *
1435     * @return mixed (string) 'YYYYMMDDhhmmss' / (int) timestamp on success or (object) pear_error on failure
1436     * @access protected
1437     */
1438    function cmdDate()
1439    {
1440        $response = $this->_sendCommand('DATE');
1441
1442    	switch ($response) {
1443    	    case NET_NNTP_PROTOCOL_RESPONSECODE_SERVER_DATE: // 111, RFC2980: 'YYYYMMDDhhmmss'
1444    	        return $this->_currentStatusResponse();
1445    	    	break;
1446    	    default:
1447    	    	return $this->_handleUnexpectedResponse($response);
1448    	}
1449    }
1450    // }}}
1451    // {{{ cmdHelp()
1452
1453    /**
1454     * Returns the server's help text
1455     *
1456     * @return mixed (array) help text on success or (object) pear_error on failure
1457     * @access protected
1458     */
1459    function cmdHelp()
1460    {
1461        // tell the newsserver we want an article
1462        $response = $this->_sendCommand('HELP');
1463
1464    	switch ($response) {
1465            case NET_NNTP_PROTOCOL_RESPONSECODE_HELP_FOLLOWS: // 100
1466    	    	$data = $this->_getTextResponse();
1467    	    	return $data;
1468    	        break;
1469    	    default:
1470    	    	return $this->_handleUnexpectedResponse($response);
1471    	}
1472    }
1473
1474    // }}}
1475    // {{{ cmdNewgroups()
1476
1477    /**
1478     * Fetches a list of all newsgroups created since a specified date.
1479     *
1480     * @param int $time Last time you checked for groups (timestamp).
1481     * @param optional string $distributions (deprecaded in rfc draft)
1482     *
1483     * @return mixed (array) nested array with informations about existing newsgroups on success or (object) pear_error on failure
1484     * @access protected
1485     */
1486    function cmdNewgroups($time, $distributions = null)
1487    {
1488	$date = gmdate('ymd His', $time);
1489
1490        if (is_null($distributions)) {
1491    	    $command = 'NEWGROUPS ' . $date . ' GMT';
1492    	} else {
1493    	    $command = 'NEWGROUPS ' . $date . ' GMT <' . $distributions . '>';
1494        }
1495
1496        $response = $this->_sendCommand($command);
1497
1498    	switch ($response) {
1499    	    case NET_NNTP_PROTOCOL_RESPONSECODE_NEW_GROUPS_FOLLOW: // 231, REF977: 'list of new newsgroups follows'
1500    	    	$data = $this->_getTextResponse();
1501
1502    	    	$groups = array();
1503    	    	foreach($data as $line) {
1504    	    	    $arr = explode(' ', trim($line));
1505
1506    	    	    $group = array('group'   => $arr[0],
1507    	    	                   'last'    => $arr[1],
1508    	    	                   'first'   => $arr[2],
1509    	    	                   'posting' => $arr[3]);
1510
1511    	    	    $groups[$group['group']] = $group;
1512    	    	}
1513    	        return $groups;
1514
1515
1516
1517    	    default:
1518    	    	return $this->_handleUnexpectedResponse($response);
1519    	}
1520    }
1521
1522    // }}}
1523    // {{{ cmdNewnews()
1524
1525    /**
1526     *
1527     *
1528     * @param $time
1529     * @param mixed $newsgroups (string or array of strings)
1530     * @param mixed $dis…

Large files files are truncated, but you can click here to view the full file