PageRenderTime 71ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/NNTP/Protocol/Client.php

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

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