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

/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
  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 $distribution (string or array of strings)
  1302. *
  1303. * @return mixed
  1304. * @access protected
  1305. */
  1306. function cmdNewnews($time, $newsgroups, $distribution = null)
  1307. {
  1308. $date = gmdate('ymd His', $time);
  1309. if (is_array($newsgroups)) {
  1310. $newsgroups = implode(',', $newsgroups);
  1311. }
  1312. if (is_null($distribution)) {
  1313. $command = 'NEWNEWS ' . $newsgroups . ' ' . $date . ' GMT';
  1314. } else {
  1315. if (is_array($distribution)) {
  1316. $distribution = implode(',', $distribution);
  1317. }
  1318. $command = 'NEWNEWS ' . $newsgroups . ' ' . $date . ' GMT <' . $distribution . '>';
  1319. }
  1320. // TODO: the lenght of the request string may not exceed 510 chars
  1321. $response = $this->_sendCommand($command);
  1322. switch ($response) {
  1323. case NET_NNTP_PROTOCOL_RESPONSECODE_NEW_ARTICLES_FOLLOW: // 230, RFC977: 'list of new articles by message-id follows'
  1324. $messages = array();
  1325. foreach($this->_getTextResponse() as $line) {
  1326. $messages[] = $line;
  1327. }
  1328. return $messages;
  1329. break;
  1330. default:
  1331. return $this->_handleUnexpectedResponse($response);
  1332. }
  1333. }
  1334. // }}}
  1335. /* The LIST commands */
  1336. // {{{ cmdList()
  1337. /**
  1338. * Fetches a list of all avaible newsgroups
  1339. *
  1340. * @return mixed (array) nested array with informations about existing newsgroups on success or (object) pear_error on failure
  1341. * @access protected
  1342. */
  1343. function cmdList()
  1344. {
  1345. $response = $this->_sendCommand('LIST');
  1346. switch ($response) {
  1347. case NET_NNTP_PROTOCOL_RESPONSECODE_GROUPS_FOLLOW: // 215, RFC977: 'list of newsgroups follows'
  1348. $data = $this->_getTextResponse();
  1349. $groups = array();
  1350. foreach($data as $line) {
  1351. $arr = explode(' ', trim($line));
  1352. $group = array('group' => $arr[0],
  1353. 'last' => $arr[1],
  1354. 'first' => $arr[2],
  1355. 'posting' => $arr[3]);
  1356. $groups[$group['group']] = $group;
  1357. }
  1358. return $groups;
  1359. break;
  1360. default:
  1361. return $this->_handleUnexpectedResponse($response);
  1362. }
  1363. }
  1364. // }}}
  1365. // {{{ cmdListActive()
  1366. /**
  1367. * Fetches a list of all avaible newsgroups
  1368. *
  1369. * @param string $wildmat
  1370. *
  1371. * @return mixed (array) nested array with informations about existing newsgroups on success or (object) pear_error on failure
  1372. * @access protected
  1373. */
  1374. function cmdListActive($wildmat = null)
  1375. {
  1376. if (is_null($wildmat)) {
  1377. $command = 'LIST ACTIVE';
  1378. } else {
  1379. $command = 'LIST ACTIVE ' . $wildmat;
  1380. }
  1381. $response = $this->_sendCommand($command);
  1382. switch ($response) {
  1383. case NET_NNTP_PROTOCOL_RESPONSECODE_GROUPS_FOLLOW: // 215, RFC977: 'list of newsgroups follows'
  1384. $data = $this->_getTextResponse();
  1385. $groups = array();
  1386. foreach($data as $line) {
  1387. $arr = explode(' ', trim($line));
  1388. $group = array('group' => $arr[0],
  1389. 'last' => $arr[1],
  1390. 'first' => $arr[2],
  1391. 'posting' => $arr[3]);
  1392. $groups[$group['group']] = $group;
  1393. }
  1394. if ($this->_logger) {
  1395. $this->_logger->info('Fetched list of available groups');
  1396. }
  1397. return $groups;
  1398. break;
  1399. default:
  1400. return $this->_handleUnexpectedResponse($response);
  1401. }
  1402. }
  1403. // }}}
  1404. // {{{ cmdListNewsgroups()
  1405. /**
  1406. * Fetches a list of (all) avaible newsgroup descriptions.
  1407. *
  1408. * @param string $wildmat Wildmat of the groups, that is to be listed, defaults to null;
  1409. *
  1410. * @return mixed (array) nested array with description of existing newsgroups on success or (object) pear_error on failure
  1411. * @access protected
  1412. */
  1413. function cmdListNewsgroups($wildmat = null)
  1414. {
  1415. if (is_null($wildmat)) {
  1416. $command = 'LIST NEWSGROUPS';
  1417. } else {
  1418. $command = 'LIST NEWSGROUPS ' . $wildmat;
  1419. }
  1420. $response = $this->_sendCommand($command);
  1421. switch ($response) {
  1422. case NET_NNTP_PROTOCOL_RESPONSECODE_GROUPS_FOLLOW: // 215, RFC2980: 'information follows'
  1423. $data = $this->_getTextResponse();
  1424. $groups = array();
  1425. foreach($data as $line) {
  1426. if (preg_match("/^(\S+)\s+(.*)$/", ltrim($line), $matches)) {
  1427. $groups[$matches[1]] = (string) $matches[2];
  1428. } else {
  1429. if ($this->_logger) {
  1430. $this->_logger->warning("Recieved non-standard line: '$line'");
  1431. }
  1432. }
  1433. }
  1434. if ($this->_logger) {
  1435. $this->_logger->info('Fetched group descriptions');
  1436. }
  1437. return $groups;
  1438. break;
  1439. case 503: // RFC2980: 'program error, function not performed'
  1440. $this->throwError('Internal server error, function not performed', $response, $this->_currentStatusResponse());
  1441. break;
  1442. default:
  1443. return $this->_handleUnexpectedResponse($response);
  1444. }
  1445. }
  1446. // }}}
  1447. /* Article field access commands */
  1448. // {{{ cmdOver()
  1449. /**
  1450. * Fetch message header from message number $first until $last
  1451. *
  1452. * The format of the returned array is:
  1453. * $messages[][header_name]
  1454. *
  1455. * @param optional string $range articles to fetch
  1456. *
  1457. * @return mixed (array) nested array of message and there headers on success or (object) pear_error on failure
  1458. * @access protected
  1459. */
  1460. function cmdOver($range = null)
  1461. {
  1462. if (is_null($range)) {
  1463. $command = 'OVER';
  1464. } else {
  1465. $command = 'OVER ' . $range;
  1466. }
  1467. $response = $this->_sendCommand($command);
  1468. switch ($response) {
  1469. case NET_NNTP_PROTOCOL_RESPONSECODE_OVERVIEW_FOLLOWS: // 224, RFC2980: 'Overview information follows'
  1470. $data = $this->_getTextResponse();
  1471. foreach ($data as $key => $value) {
  1472. $data[$key] = explode("\t", trim($value));
  1473. }
  1474. if ($this->_logger) {
  1475. $this->_logger->info('Fetched overview ' . ($range == null ? 'for current article' : 'for range: '.$range));
  1476. }
  1477. return $data;
  1478. break;
  1479. case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC2980: 'No news group current selected'
  1480. $this->throwError('No news group current selected', $response, $this->_currentStatusResponse());
  1481. break;
  1482. case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC2980: 'No article(s) selected'
  1483. $this->throwError('No article(s) selected', $response, $this->_currentStatusResponse());
  1484. break;
  1485. case NET_NNTP_PROTOCOL_RESPONSECODE_NO_SUCH_ARTICLE_NUMBER: // 423:, Draft27: 'No articles in that range'
  1486. $this->throwError('No articles in that range', $response, $this->_currentStatusResponse());
  1487. break;
  1488. case 502: // RFC2980: 'no permission'
  1489. $this->throwError('No permission', $response, $this->_currentStatusResponse());
  1490. break;
  1491. default:
  1492. return $this->_handleUnexpectedResponse($response);
  1493. }
  1494. }
  1495. // }}}
  1496. // {{{ cmdXOver()
  1497. /**
  1498. * Fetch message header from message number $first until $last
  1499. *
  1500. * The format of the returned array is:
  1501. * $messages[message_id][header_name]
  1502. *
  1503. * @param optional string $range articles to fetch
  1504. *
  1505. * @return mixed (array) nested array of message and there headers on success or (object) pear_error on failure
  1506. * @access protected
  1507. */
  1508. function cmdXOver($range = null)
  1509. {
  1510. // deprecated API (the code _is_ still in alpha state)
  1511. if (func_num_args() > 1 ) {
  1512. die('The second parameter in cmdXOver() has been deprecated! Use x-y instead...');
  1513. }
  1514. if (is_null($range)) {
  1515. $command = 'XOVER';
  1516. } else {
  1517. $command = 'XOVER ' . $range;
  1518. }
  1519. $response = $this->_sendCommand($command);
  1520. switch ($response) {
  1521. case NET_NNTP_PROTOCOL_RESPONSECODE_OVERVIEW_FOLLOWS: // 224, RFC2980: 'Overview information follows'
  1522. $data = $this->_getTextResponse();
  1523. foreach ($data as $key => $value) {
  1524. $data[$key] = explode("\t", trim($value));
  1525. }
  1526. if ($this->_logger) {
  1527. $this->_logger->info('Fetched overview ' . ($range == null ? 'for current article' : 'for range: '.$range));
  1528. }
  1529. return $data;
  1530. break;
  1531. case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC2980: 'No news group current selected'
  1532. $this->throwError('No news group current selected', $response, $this->_currentStatusResponse());
  1533. break;
  1534. case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC2980: 'No article(s) selected'
  1535. $this->throwError('No article(s) selected', $response, $this->_currentStatusResponse());
  1536. break;
  1537. case 502: // RFC2980: 'no permission'
  1538. $this->throwError('No permission', $response, $this->_currentStatusResponse());
  1539. break;
  1540. default:
  1541. return $this->_handleUnexpectedResponse($response);
  1542. }
  1543. }
  1544. // }}}
  1545. // {{{ cmdListOverviewFmt()
  1546. // }}}
  1547. // {{{ cmdXZver()
  1548. /*
  1549. * Based on code from http://wonko.com/software/yenc/, but
  1550. * simplified because XZVER and the likes don't implement
  1551. * yenc properly
  1552. */
  1553. private function yencDecode($string, $destination = "") {
  1554. $encoded = array();
  1555. $header = array();
  1556. $decoded = '';
  1557. # Extract the yEnc string itself
  1558. preg_match("/^(=ybegin.*=yend[^$]*)$/ims", $string, $encoded);
  1559. $encoded = $encoded[1];
  1560. # Extract the filesize and filename from the yEnc header
  1561. preg_match("/^=ybegin.*size=([^ $]+).*name=([^\\r\\n]+)/im", $encoded, $header);
  1562. $filesize = $header[1];
  1563. $filename = $header[2];
  1564. # Remove the header and footer from the string before parsing it.
  1565. $encoded = preg_replace("/(^=ybegin.*\\r\\n)/im", "", $encoded, 1);
  1566. $encoded = preg_replace("/(^=yend.*)/im", "", $encoded, 1);
  1567. # Remove linebreaks and whitespace from the string
  1568. $encoded = trim(str_replace("\r\n", "", $encoded));
  1569. // Decode
  1570. $strLength = strlen($encoded);
  1571. for($i = 0; $i < $strLength; $i++) {
  1572. $c = $encoded[$i];
  1573. if ($c == '=') {
  1574. $i++;
  1575. $decoded .= chr((ord($encoded[$i]) - 64) - 42);
  1576. } else {
  1577. $decoded .= chr(ord($c) - 42);
  1578. } # else
  1579. } # for
  1580. // Make sure the decoded filesize is the same as the size specified in the header.
  1581. if (strlen($decoded) != $filesize) {
  1582. throw new Exception("Filesize in yEnc header en filesize found do not match up");
  1583. } # if
  1584. return $decoded;
  1585. } # yencDecode()
  1586. /**
  1587. * Fetch message header from message number $first until $last
  1588. *
  1589. * The format of the returned array is:
  1590. * $messages[message_id][header_name]
  1591. *
  1592. * @param optional string $range articles to fetch
  1593. *
  1594. * @return mixed (array) nested array of message and there headers on success or (object) pear_error on failure
  1595. * @access protected
  1596. */
  1597. function cmdXZver($range = null)
  1598. {
  1599. if (is_null($range)) {
  1600. $command = 'XZVER';
  1601. } else {
  1602. $command = 'XZVER ' . $range;
  1603. }
  1604. $response = $this->_sendCommand($command);
  1605. switch ($response) {
  1606. case NET_NNTP_PROTOCOL_RESPONSECODE_OVERVIEW_FOLLOWS: // 224, RFC2980: 'Overview information follows'
  1607. $data = $this->_getCompressedResponse();
  1608. foreach ($data as $key => $value) {
  1609. $data[$key] = explode("\t", trim($value));
  1610. }
  1611. if ($this->_logger) {
  1612. $this->_logger->info('Fetched overview ' . ($range == null ? 'for current article' : 'for range: '.$range));
  1613. }
  1614. return $data;
  1615. break;
  1616. case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC2980: 'No news group current selected'
  1617. $this->throwError('No news group current selected', $response, $this->_currentStatusResponse());
  1618. break;
  1619. case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC2980: 'No article(s) selected'
  1620. $this->throwError('No article(s) selected', $response, $this->_currentStatusResponse());
  1621. break;
  1622. case 502: // RFC2980: 'no permission'
  1623. $this->throwError('No permission', $response, $this->_currentStatusResponse());
  1624. break;
  1625. default:
  1626. return $this->_handleUnexpectedResponse($response);
  1627. }
  1628. }
  1629. // }}}
  1630. // {{{ cmdListOverviewFmt()
  1631. /**
  1632. * Returns a list of avaible headers which are send from newsserver to client for every news message
  1633. *
  1634. * @return mixed (array) of header names on success or (object) pear_error on failure
  1635. * @access protected
  1636. */
  1637. function cmdListOverviewFmt()
  1638. {
  1639. $response = $this->_sendCommand('LIST OVERVIEW.FMT');
  1640. switch ($response) {
  1641. case NET_NNTP_PROTOCOL_RESPONSECODE_GROUPS_FOLLOW: // 215, RFC2980: 'information follows'
  1642. $data = $this->_getTextResponse();
  1643. $format = array();
  1644. foreach ($data as $line) {
  1645. // Check if postfixed by ':full' (case-insensitive)
  1646. if (0 == strcasecmp(substr($line, -5, 5), ':full')) {
  1647. // ':full' is _not_ included in tag, but value set to true
  1648. $format[substr($line, 0, -5)] = true;
  1649. } else {
  1650. // ':' is _not_ included in tag; value set to false
  1651. $format[substr($line, 0, -1)] = false;
  1652. }
  1653. }
  1654. if ($this->_logger) {
  1655. $this->_logger->info('Fetched overview format');
  1656. }
  1657. return $format;
  1658. break;
  1659. case 503: // RFC2980: 'program error, function not performed'
  1660. $this->throwError('Internal server error, function not performed', $response, $this->_currentStatusResponse());
  1661. break;
  1662. default:
  1663. return $this->_handleUnexpectedResponse($response);
  1664. }
  1665. }
  1666. // }}}
  1667. // {{{ cmdXHdr()
  1668. /**
  1669. *
  1670. *
  1671. * The format of the returned array is:
  1672. * $messages[message_id]
  1673. *
  1674. * @param optional string $field
  1675. * @param optional string $range articles to fetch
  1676. *
  1677. * @return mixed (array) nested array of message and there headers on success or (object) pear_error on failure
  1678. * @access protected
  1679. */
  1680. function cmdXHdr($field, $range = null)
  1681. {
  1682. if (is_null($range)) {
  1683. $command = 'XHDR ' . $field;
  1684. } else {
  1685. $command = 'XHDR ' . $field . ' ' . $range;
  1686. }
  1687. $response = $this->_sendCommand($command);
  1688. switch ($response) {
  1689. case 221: // 221, RFC2980: 'Header follows'
  1690. $data = $this->_getTextResponse();
  1691. $return = array();
  1692. foreach($data as $line) {
  1693. $line = explode(' ', trim($line), 2);
  1694. $return[$line[0]] = $line[1];
  1695. }
  1696. return $return;
  1697. break;
  1698. case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC2980: 'No news group current selected'
  1699. $this->throwError('No news group current selected', $response, $this->_currentStatusResponse());
  1700. break;
  1701. case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC2980: 'No current article selected'
  1702. $this->throwError('No current article selected', $response, $this->_currentStatusResponse());
  1703. break;
  1704. case 430: // 430, RFC2980: 'No such article'
  1705. $this->throwError('No such article', $response, $this->_currentStatusResponse());
  1706. break;
  1707. case 502: // RFC2980: 'no permission'
  1708. $this->throwError('No permission', $response, $this->_currentStatusResponse());
  1709. break;
  1710. default:
  1711. return $this->_handleUnexpectedResponse($response);
  1712. }
  1713. }
  1714. // }}}
  1715. /**
  1716. * Fetches a list of (all) avaible newsgroup descriptions.
  1717. * Depresated as of RFC2980.
  1718. *
  1719. * @param string $wildmat Wildmat of the groups, that is to be listed, defaults to '*';
  1720. *
  1721. * @return mixed (array) nested array with description of existing newsgroups on success or (object) pear_error on failure
  1722. * @access protected
  1723. */
  1724. function cmdXGTitle($wildmat = '*')
  1725. {
  1726. $response = $this->_sendCommand('XGTITLE '.$wildmat);
  1727. switch ($response) {
  1728. case 282: // RFC2980: 'list of groups and descriptions follows'
  1729. $data = $this->_getTextResponse();
  1730. $groups = array();
  1731. foreach($data as $line) {
  1732. preg_match("/^(.*?)\s(.*?$)/", trim($line), $matches);
  1733. $groups[$matches[1]] = (string) $matches[2];
  1734. }
  1735. return $groups;
  1736. break;
  1737. case 481: // RFC2980: 'Groups and descriptions unavailable'
  1738. $this->throwError('Groups and descriptions unavailable', $response, $this->_currentStatusResponse());
  1739. break;
  1740. default:
  1741. return $this->_handleUnexpectedResponse($response);
  1742. }
  1743. }
  1744. // }}}
  1745. // {{{ cmdXROver()
  1746. /**
  1747. * Fetch message references from message number $first to $last
  1748. *
  1749. * @param optional string $range articles to fetch
  1750. *
  1751. * @return mixed (array) assoc. array of message references on success or (object) pear_error on failure
  1752. * @access protected
  1753. */
  1754. function cmdXROver($range = null)
  1755. {
  1756. // Warn about deprecated API (the code _is_ still in alpha state)
  1757. if (func_num_args() > 1 ) {
  1758. die('The second parameter in cmdXROver() has been deprecated! Use x-y instead...');
  1759. }
  1760. if (is_null($range)) {
  1761. $command = 'XROVER';
  1762. } else {
  1763. $command = 'XROVER ' . $range;
  1764. }
  1765. $response = $this->_sendCommand($command);
  1766. switch ($response) {
  1767. case NET_NNTP_PROTOCOL_RESPONSECODE_OVERVIEW_FOLLOWS: // 224, RFC2980: 'Overview information follows'
  1768. $data = $this->_getTextResponse();
  1769. $return = array();
  1770. foreach($data as $line) {
  1771. $line = explode(' ', trim($line), 2);
  1772. $return[$line[0]] = $line[1];
  1773. }
  1774. return $return;
  1775. break;
  1776. case NET_NNTP_PROTOCOL_RESPONSECODE_NO_GROUP_SELECTED: // 412, RFC2980: 'No news group current selected'
  1777. $this->throwError('No news group current selected', $response, $this->_currentStatusResponse());
  1778. break;
  1779. case NET_NNTP_PROTOCOL_RESPONSECODE_NO_ARTICLE_SELECTED: // 420, RFC2980: 'No article(s) selected'
  1780. $this->throwError('No article(s) selected', $response, $this->_currentStatusResponse());
  1781. break;
  1782. case 502: // RFC2980: 'no permission'
  1783. $this->throwError('No permission', $response, $this->_currentStatusResponse());
  1784. break;
  1785. default:
  1786. return $this->_handleUnexpectedResponse($response);
  1787. }
  1788. }
  1789. // }}}
  1790. // {{{ cmdXPat()
  1791. /**
  1792. *
  1793. *
  1794. * @param string $field
  1795. * @param string $range
  1796. * @param mixed $wildmat
  1797. *
  1798. * @return mixed (array) nested array of message and there headers on success or (object) pear_error on failure
  1799. * @access protected
  1800. */
  1801. function cmdXPat($field, $range, $wildmat)
  1802. {
  1803. if (is_array($wildmat)) {
  1804. $wildmat = implode(' ', $wildmat);
  1805. }
  1806. $response = $this->_sendCommand('XPAT ' . $field . ' ' . $range . ' ' . $wildmat);
  1807. switch ($response) {
  1808. case 221: // 221, RFC2980: 'Header follows'
  1809. $data = $this->_getTextResponse();
  1810. $return = array();
  1811. foreach($data as $line) {
  1812. $line = explode(' ', trim($line), 2);
  1813. $return[$line[0]] = $line[1];
  1814. }
  1815. return $return;
  1816. break;
  1817. case 430: // 430, RFC2980: 'No such article'
  1818. $this->throwError('No current article selected', $response, $this->_currentStatusResponse());
  1819. break;
  1820. case 502: // RFC2980: 'no permission'
  1821. $this->throwError('No permission', $response, $this->_currentStatusResponse());
  1822. break;
  1823. default:
  1824. return $this->_handleUnexpectedResponse($response);
  1825. }
  1826. }
  1827. // }}}
  1828. // {{{ cmdAuthinfo()
  1829. /**
  1830. * Authenticate using 'original' method
  1831. *
  1832. * @param string $user The username to authenticate as.
  1833. * @param string $pass The password to authenticate with.
  1834. *
  1835. * @return mixed (bool) true on success or (object) pear_error on failure
  1836. * @access protected
  1837. */
  1838. function cmdAuthinfo($user, $pass)
  1839. {
  1840. // Send the username
  1841. $response = $this->_sendCommand('AUTHINFO user '.$user);
  1842. // Send the password, if the server asks
  1843. if (($response == 381) && ($pass !== null)) {
  1844. // Send the password
  1845. $response = $this->_sendCommand('AUTHINFO pass '.$pass);
  1846. }
  1847. switch ($response) {
  1848. case 281: // RFC2980: 'Authentication accepted'
  1849. if ($this->_logger) {
  1850. $this->_logger->info("Authenticated (as user '$user')");
  1851. }
  1852. // TODO: Set some variable before return
  1853. return true;
  1854. break;
  1855. case 381: // RFC2980: 'More authentication information required'
  1856. $this->throwError('Authentication uncompleted', $response, $this->_currentStatusResponse());
  1857. break;
  1858. case 482: // RFC2980: 'Authentication rejected'
  1859. $this->throwError('Authentication rejected', $response, $this->_currentStatusResponse());
  1860. break;
  1861. case 502: // RFC2980: 'No permission'
  1862. $this->throwError('Authentication rejected', $response, $this->_currentStatusResponse());
  1863. break;
  1864. // case 500:
  1865. // case 501:
  1866. // $this->throwError('Authentication failed', $response, $this->_currentStatusResponse());
  1867. // break;
  1868. default:
  1869. return $this->_handleUnexpectedResponse($response);
  1870. }
  1871. }
  1872. // }}}
  1873. // {{{ cmdAuthinfoSimple()
  1874. /**
  1875. * Authenticate using 'simple' method
  1876. *
  1877. * @param string $user The username to authenticate as.
  1878. * @param string $pass The password to authenticate with.
  1879. *
  1880. * @return mixed (bool) true on success or (object) pear_error on failure
  1881. * @access protected
  1882. */
  1883. function cmdAuthinfoSimple($user, $pass)
  1884. {
  1885. $this->throwError("The auth mode: 'simple' is has not been implemented yet", null);
  1886. }
  1887. // }}}
  1888. // {{{ cmdAuthinfoGeneric()
  1889. /**
  1890. * Authenticate using 'generic' method
  1891. *
  1892. * @param string $user The username to authenticate as.
  1893. * @param string $pass The password to authenticate with.
  1894. *
  1895. * @return mixed (bool) true on success or (object) pear_error on failure
  1896. * @access protected
  1897. */
  1898. function cmdAuthinfoGeneric($user, $pass)
  1899. {
  1900. $this->throwError("The auth mode: 'generic' is has not been implemented yet", null);
  1901. }
  1902. // }}}
  1903. // {{{ _isConnected()
  1904. /**
  1905. * Test whether we are connected or not.
  1906. *
  1907. * @return bool true or false
  1908. * @access protected
  1909. */
  1910. function _isConnected()
  1911. {
  1912. return (is_resource($this->_socket) && (!feof($this->_socket)));
  1913. }
  1914. // }}}
  1915. function throwError($detail, $code = -1, $response = "") {
  1916. throw new NntpException($detail, $code, $response);
  1917. } # throwError
  1918. }
  1919. // }}}
  1920. /*
  1921. * Local variables:
  1922. * tab-width: 4
  1923. * c-basic-offset: 4
  1924. * c-hanging-comment-ender-p: nil
  1925. * End:
  1926. */