2 /********************************************************************************
3 * include/nntpcore.inc.php : NNTP subroutines
4 * -------------------------
6 * This file is part of the banana distribution
7 * Copyright: See COPYING files that comes with this distribution
8 ********************************************************************************/
10 require_once dirname(__FILE__
) . '/banana.inc.php';
13 * implements some basic functions for NNTP protocol
17 /** socket filehandle */
19 /** posting allowed */
21 /** last NNTP error code */
22 private $lasterrorcode;
23 /** last NNTP error text */
24 private $lasterrortext;
25 /** last NNTP result code */
26 private $lastresultcode;
27 /** last NNTP result */
28 private $lastresulttext;
31 private $debug = false
;
32 private $bt = array();
35 * @param $host STRING NNTP host
36 * @parma $port STRING NNTP port
37 * @param $timeout INTEGER socket timeout
38 * @param $reader BOOLEAN sends a "MODE READER" at connection if true
40 public function __construct($host, $port = 119, $timeout = 120, $reader = true
)
42 if (Banana
::$debug_nntp) {
45 $this->ns
= fsockopen($host, $port, $errno, $errstr, $timeout);
46 $this->lasterrorcode
= $errno;
47 $this->lasterrortext
= $errstr;
48 if (is_null($this->ns
)) {
53 $this->posting
= ($this->lastresultcode
== '200');
54 if ($reader && $this->posting
) {
55 $this->execLine('MODE READER');
56 $this->posting
= ($this->lastresultcode
== '200');
58 if (!$this->posting
) {
63 public function __destruct()
70 public function isValid()
72 return !is_null($this->ns
) && $this->posting
;
75 public function lastErrNo()
77 return $this->lasterrorcode
;
80 public function lastError()
82 return $this->lasterrortext
;
85 public function backtrace()
96 /** get a line from server
99 private function getLine()
101 return rtrim(@fgets
($this->ns
, 1200), "\r\n");
104 /** fetch data (and on delimitor)
105 * @param STRING $delim string indicating and of transmission
107 private function &fetchResult()
110 while (($result = $this->getLine()) != '.') {
113 if ($this->debug
&& $this->bt
) {
114 $this->bt
[count($this->bt
) - 1]['response'] = count($array);
119 /** puts a line on server
120 * @param STRING $line line to put
122 private function putLine($line, $format = false
)
125 $line = str_replace(array("\r", "\n"), '', $line);
129 $db_line = preg_replace('/PASS .*/', 'PASS *******', $line);
130 $this->bt
[] = array('action' => $db_line, 'time' => microtime(true
));
132 return @fputs
($this->ns
, $line, strlen($line));
135 /** put a message (multiline)
137 private function putMessage($message = false
)
139 if (is_array($message)) {
140 $message = join("\n", $_message);
143 $message = preg_replace("/(^|\n)\./", '\1..', $message);
144 $this->putLine("$message\r\n", false
);
146 return $this->execLine('.');
150 /** exec a command a check result
151 * @param STRING $line line to exec
153 private function execLine($line, $strict_state = true
)
155 if (!$this->putLine($line, true
)) {
158 return $this->checkState($strict_state);
161 /** check if last command was successfull (read one line)
162 * @param BOOL $strict indicate if 1XX codes are interpreted as errors (true) or success (false)
164 private function checkState($strict = true
)
166 $result = $this->getLine();
167 $this->lastresultcode
= substr($result, 0, 3);
168 $this->lastresulttext
= substr($result, 4);
169 if ($this->debug
&& $this->bt
) {
170 $trace =& $this->bt
[count($this->bt
) - 1];
171 $trace['time'] = microtime(true
) - $trace['time'];
172 $trace['code'] = $this->lastresultcode
;
173 $trace['message'] = $this->lastresulttext
;
174 $trace['response'] = 0;
176 $c = $this->lastresultcode
{0};
177 if ($c == '2' ||
(($c == '1' ||
$c == '3') && !$strict)) {
180 $this->lasterrorcode
= $this->lastresultcode
;
181 $this->lasterrortext
= $this->lastresulttext
;
186 # strict NNTP Functions [RFC 977]
187 # see http://www.faqs.org/rfcs/rfc977.html
190 * @param $user STRING login
191 * @param $pass INTEGER password
192 * @return BOOLEAN true if authentication was successful
194 protected function authinfo($user, $pass)
196 if ($this->execLine("AUTHINFO USER $user", false
)) {
197 return $this->execline("AUTHINFO PASS $pass");
202 /** retrieves an article
203 * MSGID is a numeric ID a shown in article's headers. MSGNUM is a
204 * server-dependent ID (see X-Ref on many servers) and retriving
205 * an article by this way will change the current article pointer.
206 * If an error occur, false is returned.
207 * @param $_msgid STRING MSGID or MSGNUM of article
208 * @return ARRAY lines of the article
212 protected function article($msgid = "")
214 if (!$this->execLine("ARTICLE $msgid")) {
217 return $this->fetchResult();
221 * if an error occur, false is returned
222 * @param $_message STRING message to post
223 * @return STRING MSGID of article
225 protected function post($message)
227 if (!$this->execLine("POST ", false
)) {
230 if (!$this->putMessage($message)) {
233 if (preg_match("/(<[^@>]+@[^@>]+>)/", $this->lastresulttext
, $regs)) {
240 /** fetches the body of an article
241 * params are the same as article
242 * @param $_msgid STRING MSGID or MSGNUM of article
243 * @return ARRAY lines of the article
247 protected function body($msgid = '')
249 if ($this->execLine("BODY $msgid")) {
252 return $this->fetchResult();
255 /** fetches the headers of an article
256 * params are the same as article
257 * @param $_msgid STRING MSGID or MSGNUM of article
258 * @return ARRAY lines of the article
262 protected function head($msgid = '')
264 if (!$this->execLine("HEAD $msgid")) {
267 return $this->fetchResult();
270 /** set current group
271 * @param $_group STRING
272 * @return ARRAY array : nb of articles in group, MSGNUM of first article, MSGNUM of last article, and group name
274 protected function group($group)
276 if (!$this->execLine("GROUP $group")) {
279 $array = explode(' ', $this->lastresulttext
);
280 if (count($array) >= 4) {
281 return array_slice($array, 0, 4);
286 /** set the article pointer to the previous article in current group
287 * @return STRING MSGID of article
290 protected function last()
292 if (!$this->execLine("LAST ")) {
295 if (preg_match("/^\d+ (<[^>]+>)/", $this->lastresulttext
, $regs)) {
301 /** set the article pointer to the next article in current group
302 * @return STRING MSGID of article
306 protected function next()
308 if (!$this->execLine('NEXT ')) {
311 if (preg_match("/^\d+ (<[^>]+>)/", $this->lastresulttext
, $regs)) {
317 /** set the current article pointer
318 * @param $_msgid STRING MSGID or MSGNUM of article
319 * @return BOOLEAN true if authentication was successful, error code otherwise
323 protected function nntpstat($msgid)
325 if (!$this->execLine("STAT $msgid")) {
328 if (preg_match("/^\d+ (<[^>]+>)/", $this->lastresulttext
, $regs)) {
334 /** filter group list
336 private function filterGroups()
338 $list = $this->fetchResult();
341 foreach ($list as $result) {
342 list($group, $last, $first, $p) = explode(' ', $result, 4);
343 if (!Banana
::$boxpattern ||
preg_match('@' . Banana
::$boxpattern . '@i', $group)) {
344 $groups[$group] = array(intval($last), intval($first), $p);
350 /** gets information about all active newsgroups
351 * @return ARRAY group name => (MSGNUM of first article, MSGNUM of last article, NNTP flags)
354 protected function listGroups()
356 if (!$this->execLine('LIST')) {
359 return $this->filterGroups();
362 /** format date for news server
363 * @param since UNIX TIMESTAMP
365 protected function formatDate($since)
367 return gmdate("ymd His", $since) . ' GMT';
370 /** get information about recent newsgroups
371 * same as list, but information are limited to newgroups created after $_since
372 * @param $_since INTEGER unix timestamp
373 * @param $_distributions STRING distributions
374 * @return ARRAY same format as liste
377 protected function newgroups($since, $distributions = '')
379 if (!($since = $this->formatDate($since))) {
382 if (!$this->execLine("NEWGROUPS $since $distributions")) {
385 return $this->filterGroups();
388 /** gets a list of new articles
389 * @param $_since INTEGER unix timestamp
390 * @parma $_groups STRING pattern of intersting groups
391 * @return ARRAY MSGID of new articles
393 protected function newnews($groups = '*', $since = 0, $distributions = '')
395 if (!($since = $this->formatDate($since))) {
398 if (!$this->execLine("NEWNEWS $groups $since $distributions")) {
401 return $this->fetchResult();
404 /** Tell the remote server that I am not a user client, but probably another news server
405 * @return BOOLEAN true if sucessful
407 protected function slave()
409 return $this->execLine("SLAVE ");
412 /** implements IHAVE method
413 * @param $_msgid STRING MSGID of article
414 * @param $_message STRING article
417 protected function ihave($msgid, $message = false
)
419 if (!$this->execLine("IHAVE $msgid ")) {
422 return $this->putMessage($message);
425 /** closes connection to server
427 protected function quit()
429 $this->execLine('QUIT');
432 $this->posting
= false
;
435 # NNTP Extensions [RFC 2980]
437 /** Returns the date on the remote server
438 * @return INTEGER timestamp
441 protected function date()
443 if (!$this->execLine('DATE ', false
)) {
446 if (preg_match("/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/", $this->lastresulttext
, $r)) {
447 return gmmktime($r[4], $r[5], $r[6], $r[2], $r[3], $r[1]);
452 /** returns group descriptions
453 * @param $_pattern STRING pattern of intersting groups
454 * @return ARRAY group name => description
457 protected function xgtitle($pattern = '*')
459 if (!$this->execLine("XGTITLE $pattern ")) {
462 $array =& $this->fetchResult();
464 foreach ($array as &$result) {
465 list($group, $desc) = split("[ \t]", $result, 2);
466 $groups[$group] = $desc;
471 /** obtain the header field $hdr for all the messages specified
472 * @param $_hdr STRING name of the header (eg: 'From')
473 * @param $_range STRING range of articles
474 * @return ARRAY MSGNUM => header value
476 protected function xhdr($hdr, $first = null
, $last = null
)
478 if (is_null($first) && is_null($last)) {
481 $range = $first . '-' . $last;
483 if (!$this->execLine("XHDR $hdr $range ")) {
486 $array =& $this->fetchResult();
488 foreach ($array as &$result) {
489 @list
($head, $value) = explode(' ', $result, 2);
490 $headers[$head] = $value;
495 /** obtain the header field $_hdr matching $_pat for all the messages specified
496 * @param $_hdr STRING name of the header (eg: 'From')
497 * @param $_range STRING range of articles
498 * @param $_pat STRING pattern
499 * @return ARRAY MSGNUM => header value
501 protected function xpat($_hdr, $_range, $_pat)
503 if (!$this->execLine("XPAT $hdr $range $pat")) {
506 $array =& $this->fetchResult();
508 foreach ($array as &$result) {
509 list($head, $value) = explode(' ', $result, 2);
510 $headers[$head] = $result;
516 // vim:set et sw=4 sts=4 ts=4 enc=utf-8: