PageRenderTime 63ms CodeModel.GetById 20ms 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
Possible License(s): BSD-3-Clause, GPL-2.0, Apache-2.0, LGPL-3.0

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

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