Implements the mail sending in the mbox module
[banana.git] / banana / nntpcore.inc.php
CommitLineData
7027794f 1<?php
2/********************************************************************************
3* include/nntpcore.inc.php : NNTP subroutines
4* -------------------------
5*
6* This file is part of the banana distribution
7* Copyright: See COPYING files that comes with this distribution
8********************************************************************************/
9
10require_once dirname(__FILE__) . '/banana.inc.php';
11
12/** Class NNTPCore
13 * implements some basic functions for NNTP protocol
14 */
15class BananaNNTPCore
16{
17 /** socket filehandle */
18 private $ns;
19 /** posting allowed */
20 private $posting;
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;
29
30 /** debug mode */
31 private $debug = false;
32
33 /** constructor
34 * @param $host STRING NNTP host
35 * @parma $port STRING NNTP port
36 * @param $timeout INTEGER socket timeout
37 * @param $reader BOOLEAN sends a "MODE READER" at connection if true
38 */
39 public function __construct($host, $port = 119, $timeout = 120, $reader = true)
40 {
41 if (Banana::$debug_nntp) {
42 $this->debug = true;
43 }
44 $this->ns = fsockopen($host, $port, $errno, $errstr, $timeout);
45 $this->lasterrorcode = $errno;
46 $this->lasterrortext = $errstr;
47 if (is_null($this->ns)) {
48 return;
49 }
50
51 $this->checkState();
52 $this->posting = ($this->lastresultcode == '200');
53 if ($reader && $this->posting) {
54 $this->execLine('MODE READER');
55 $this->posting = ($this->lastresultcode == '200');
56 }
57 if (!$this->posting) {
58 $this->quit();
59 }
60 }
61
62 public function __destruct()
63 {
64 $this->quit();
65 }
66
67# Accessors
68
69 public function isValid()
70 {
71 return !is_null($this->ns) && $this->posting;
72 }
73
74 public function lastErrNo()
75 {
76 return $this->lasterrorcode;
77 }
78
79 public function lastError()
80 {
81 return $this->lasterrortext;
82 }
83
84# Socket functions
85
86 /** get a line from server
87 * @return STRING
88 */
89 private function getLine()
90 {
91 return rtrim(fgets($this->ns, 1200));
92 }
93
94 /** fetch data (and on delimitor)
95 * @param STRING $delim string indicating and of transmission
96 */
97 private function fetchResult($callback = null)
98 {
99 $array = Array();
100 while (($result = $this->getLine()) != '.') {
101 if (!is_null($callback)) {
102 list($key, $result) = call_user_func($callback, $result);
103 if (is_null($result)) {
104 continue;
105 }
106 if (is_null($key)) {
107 $array[] = $result;
108 } else {
109 $array[$key] = $result;
110 }
111 } else {
112 $array[] = $result;
113 }
114 }
115 return $array;
116 }
117
118 /** puts a line on server
119 * @param STRING $line line to put
120 */
121 private function putLine($line, $format = false)
122 {
123 if ($format) {
124 $line = str_replace(array("\r", "\n"), '', $line);
125 $line .= "\r\n";
126 }
127 if ($this->debug) {
128 $db_line = preg_replace('/PASS .*/', 'PASS *******', $line);
129 echo $db_line;
130 }
131 return fputs($this->ns, $line, strlen($line));
132 }
133
134 /** put a message (multiline)
135 */
136 private function putMessage($message = false)
137 {
138 if (is_array($message)) {
139 $message = join("\n", $_message);
140 }
141 if ($message) {
142 $this->putLine("$message\r\n", false);
143 }
144 return $this->execLine('.');
145 }
146
147
148 /** exec a command a check result
149 * @param STRING $line line to exec
150 */
151 private function execLine($line, $strict_state = true)
152 {
153 if (!$this->putLine($line, true)) {
154 return null;
155 }
156 return $this->checkState($strict_state);
157 }
158
159 /** check if last command was successfull (read one line)
160 * @param BOOL $strict indicate if 1XX codes are interpreted as errors (true) or success (false)
161 */
162 private function checkState($strict = true)
163 {
164 $result = $this->getLine();
165 if ($this->debug) {
166 echo "$result\n";
167 }
168 $this->lastresultcode = substr($result, 0, 3);
169 $this->lastresulttext = substr($result, 4);
170 $c = $this->lastresultcode{0};
171 if ($c == '2' || (($c == '1' || $c == '3') && !$strict)) {
172 return true;
173 } else {
174 $this->lasterrorcode = $this->lastresultcode;
175 $this->lasterrortext = $this->lastresulttext;
176 return false;
177 }
178 }
179
180# strict NNTP Functions [RFC 977]
181# see http://www.faqs.org/rfcs/rfc977.html
182
183 /** authentification
184 * @param $user STRING login
185 * @param $pass INTEGER password
186 * @return BOOLEAN true if authentication was successful
187 */
188 protected function authinfo($user, $pass)
189 {
190 if ($this->execLine("AUTHINFO USER $user", false)) {
191 return $this->execline("AUTHINFO PASS $pass");
192 }
193 return false;
194 }
195
196 /** retrieves an article
197 * MSGID is a numeric ID a shown in article's headers. MSGNUM is a
198 * server-dependent ID (see X-Ref on many servers) and retriving
199 * an article by this way will change the current article pointer.
200 * If an error occur, false is returned.
201 * @param $_msgid STRING MSGID or MSGNUM of article
202 * @return ARRAY lines of the article
203 * @see body
204 * @see head
205 */
206 protected function article($msgid = "")
207 {
208 if (!$this->execLine("ARTICLE $msgid")) {
209 return false;
210 }
211 return $this->fetchResult();
212 }
213
214 /** post a message
215 * if an error occur, false is returned
216 * @param $_message STRING message to post
217 * @return STRING MSGID of article
218 */
219 protected function post($message)
220 {
221 if (!$this->execLine("POST ", false)) {
222 return false;
223 }
224 if (!$this->putMessage($message)) {
225 return false;
226 }
227 if (preg_match("/(<[^@>]+@[^@>]+>)/", $this->lastresulttext, $regs)) {
228 return $regs[0];
229 } else {
230 return true;
231 }
232 }
233
234 /** fetches the body of an article
235 * params are the same as article
236 * @param $_msgid STRING MSGID or MSGNUM of article
237 * @return ARRAY lines of the article
238 * @see article
239 * @see head
240 */
241 protected function body($msgid = '')
242 {
243 if ($this->execLine("BODY $msgid")) {
244 return false;
245 }
246 return $this->fetchResult();
247 }
248
249 /** fetches the headers of an article
250 * params are the same as article
251 * @param $_msgid STRING MSGID or MSGNUM of article
252 * @return ARRAY lines of the article
253 * @see article
254 * @see body
255 */
256 protected function head($msgid = '')
257 {
258 if (!$this->execLine("HEAD $msgid")) {
259 return false;
260 }
261 return $this->fetchResult();
262 }
263
264 /** set current group
265 * @param $_group STRING
266 * @return ARRAY array : nb of articles in group, MSGNUM of first article, MSGNUM of last article, and group name
267 */
268 protected function group($group)
269 {
270 if (!$this->execLine("GROUP $group")) {
271 return false;
272 }
273 $array = explode(' ', $this->lastresulttext);
274 if (count($array) >= 4) {
275 return array_slice($array, 0, 4);
276 }
277 return false;
278 }
279
280 /** set the article pointer to the previous article in current group
281 * @return STRING MSGID of article
282 * @see next
283 */
284 protected function last()
285 {
286 if (!$this->execLine("LAST ")) {
287 return false;
288 }
289 if (preg_match("/^\d+ (<[^>]+>)/", $this->lastresulttext, $regs)) {
290 return $regs[1];
291 }
292 return false;
293 }
294
295 /** set the article pointer to the next article in current group
296 * @return STRING MSGID of article
297 * @see last
298 */
299
300 protected function next()
301 {
302 if (!$this->execLine('NEXT ')) {
303 return false;
304 }
305 if (preg_match("/^\d+ (<[^>]+>)/", $this->lastresulttext, $regs)) {
306 return $regs[1];
307 }
308 return false;
309 }
310
311 /** set the current article pointer
312 * @param $_msgid STRING MSGID or MSGNUM of article
313 * @return BOOLEAN true if authentication was successful, error code otherwise
314 * @see article
315 * @see body
316 */
317 protected function nntpstat($msgid)
318 {
319 if (!$this->execLine("STAT $msgid")) {
320 return false;
321 }
322 if (preg_match("/^\d+ (<[^>]+>)/", $this->lastresulttext, $regs)) {
323 return $regs[1];
324 }
325 return false;
326 }
327
328 /** filter group list
329 */
330 private function filterGroups()
331 {
332 $list = $this->fetchResult();
333
334 $groups = array();
335 foreach ($list as $result) {
336 list($group, $last, $first, $p) = explode(' ', $result, 4);
e9360b11 337 if (!is_null(Banana::$boxpattern) || preg_match('@' . Banana::$boxpattern . '@i', $group)) {
7027794f 338 $groups[$group] = array(intval($last), intval($first), $p);
339 }
340 }
341 return $groups;
342 }
343
344 /** gets information about all active newsgroups
345 * @return ARRAY group name => (MSGNUM of first article, MSGNUM of last article, NNTP flags)
346 * @see newgroups
347 */
348 protected function listGroups()
349 {
350 if (!$this->execLine('LIST')) {
351 return false;
352 }
353 return $this->filterGroups();
354 }
355
356 /** format date for news server
357 * @param since UNIX TIMESTAMP
358 */
359 protected function formatDate($since)
360 {
361 return gmdate("ymd His", $since) . ' GMT';
362 }
363
364 /** get information about recent newsgroups
365 * same as list, but information are limited to newgroups created after $_since
366 * @param $_since INTEGER unix timestamp
367 * @param $_distributions STRING distributions
368 * @return ARRAY same format as liste
369 * @see liste
370 */
371 protected function newgroups($since, $distributions = '')
372 {
373 if (!($since = $this->formatDate($since))) {
374 return false;
375 }
376 if (!$this->execLine("NEWGROUPS $since $distributions")) {
377 return false;
378 }
379 return $this->filterGroups();
380 }
381
382 /** gets a list of new articles
383 * @param $_since INTEGER unix timestamp
384 * @parma $_groups STRING pattern of intersting groups
385 * @return ARRAY MSGID of new articles
386 */
387 protected function newnews($groups = '*', $since = 0, $distributions = '')
388 {
389 if (!($since = $this->formatDate($since))) {
390 return false;
391 }
392 if (!$this->execLine("NEWNEWS $groups $since $distributions")) {
393 return false;
394 }
395 return $this->fetchResult();
396 }
397
398 /** Tell the remote server that I am not a user client, but probably another news server
399 * @return BOOLEAN true if sucessful
400 */
401 protected function slave()
402 {
403 return $this->execLine("SLAVE ");
404 }
405
406 /** implements IHAVE method
407 * @param $_msgid STRING MSGID of article
408 * @param $_message STRING article
409 * @return BOOLEAN
410 */
411 protected function ihave($msgid, $message = false)
412 {
413 if (!$this->execLine("IHAVE $msgid ")) {
414 return false;
415 }
416 return $this->putMessage($message);
417 }
418
419 /** closes connection to server
420 */
421 protected function quit()
422 {
423 $this->execLine('QUIT');
424 fclose($this->ns);
425 $this->ns = null;
426 $this->posting = false;
427 }
428
429# NNTP Extensions [RFC 2980]
430
431 /** Returns the date on the remote server
432 * @return INTEGER timestamp
433 */
434
435 protected function date()
436 {
437 if (!$this->execLine('DATE ', false)) {
438 return false;
439 }
440 if (preg_match("/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/", $this->lastresulttext, $r)) {
441 return gmmktime($r[4], $r[5], $r[6], $r[2], $r[3], $r[1]);
442 }
443 return false;
444 }
445
446 /** returns group descriptions
447 * @param $_pattern STRING pattern of intersting groups
448 * @return ARRAY group name => description
449 */
450
451 protected function xgtitle($pattern = '*')
452 {
453 if (!$this->execLine("XGTITLE $pattern ")) {
454 return false;
455 }
456 $array = $this->fetchResult();
457 $groups = array();
458 foreach ($array as $result) {
459 list($group, $desc) = split("[ \t]", $result, 2);
460 $groups[$group] = $desc;
461 }
462 return $groups;
463 }
464
465 /** obtain the header field $hdr for all the messages specified
466 * @param $_hdr STRING name of the header (eg: 'From')
467 * @param $_range STRING range of articles
468 * @return ARRAY MSGNUM => header value
469 */
470 protected function xhdr($hdr, $first = null, $last = null)
471 {
472 if (is_null($first) && is_null($last)) {
473 $range = "";
474 } else {
475 $range = $first . '-' . $last;
476 }
477 if (!$this->execLine("XHDR $hdr $range ")) {
478 return false;
479 }
480 $array = $this->fetchResult();
481 $headers = array();
482 foreach ($array as &$result) {
483 @list($head, $value) = explode(' ', $result, 2);
484 $headers[$head] = $value;
485 }
486 return $headers;
487 }
488
489 /** obtain the header field $_hdr matching $_pat for all the messages specified
490 * @param $_hdr STRING name of the header (eg: 'From')
491 * @param $_range STRING range of articles
492 * @param $_pat STRING pattern
493 * @return ARRAY MSGNUM => header value
494 */
495 protected function xpat($_hdr, $_range, $_pat)
496 {
497 if (!$this->execLine("XPAT $hdr $range $pat")) {
498 return false;
499 }
500 $array = $this->fetchResult();
501 $headers = array();
502 foreach ($array as &$result) {
503 list($head, $value) = explode(' ', $result, 2);
504 $headers[$head] = $result;
505 }
506 return $headers;
507 }
508}
509
598a1c53 510// vim:set et sw=4 sts=4 ts=4 enc=utf-8:
7027794f 511?>