2 /********************************************************************************
3 * banana/protocoleinterface.inc.php : interface for box access
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';
11 require_once dirname(__FILE__
) . '/protocoleinterface.inc.php';
12 require_once dirname(__FILE__
) . '/message.inc.php';
14 class BananaMBox
implements BananaProtocoleInterface
19 private $filesize = null
;
20 private $current_id = null
;
21 private $at_beginning = false
;
22 private $file_cache = null
;
24 private $_lasterrno = 0;
25 private $_lasterror = null
;
27 private $count = null
;
28 private $new_messages = null
;
29 private $messages = null
;
31 /** Build a protocole handler plugged on the given box
33 public function __construct($box = null
)
35 $this->boxname
= $box;
36 $filename = $this->getFileName($box);
37 if (is_null($filename)) {
40 $this->filesize
= filesize($filename);
41 $this->file
= @fopen
($filename, 'r');
43 $this->_lasterrno
= 1;
44 $this->_lasterror
= _b_('Can\'t open file');
47 $this->current_id
= 0;
48 $this->at_beginning
= true
;
53 public function __destruct()
60 /** Indicate if the Protocole handler has been succesfully built
62 public function isValid()
64 return is_null($this->boxname
) ||
!is_null($this->file
);
67 /** Indicate last error n°
69 public function lastErrNo()
71 return $this->_lasterrno
;;
74 /** Indicate last error text
76 public function lastError()
78 return $this->_lasterror
;
81 /** Return the description of the current box
83 public function getDescription()
88 /** Return the list of the boxes
89 * @param mode Kind of boxes to list
90 * @param since date of last check (for new boxes and new messages)
91 * @param withstats Indicated whether msgnum and unread must be set in the result
92 * @return Array(boxname => array(desc => boxdescripton, msgnum => number of message, unread =>number of unread messages)
94 public function getBoxList($mode = Banana
::BOXES_ALL
, $since = 0, $withstats = false
)
96 return array($this->boxname
=> array('desc' => '', 'msgnum' => 0, 'unread' => 0));
100 * @param id Id of the emssage (can be either an Message-id or a message index)
101 * @param msg_headers Headers to process
102 * @param is_msgid If is set, $id is en Message-Id
103 * @return A BananaMessage or null if the given id can't be retreived
105 public function getMessage($id, array $msg_headers = array(), $is_msgid = false
)
107 if ($is_msgid ||
!is_numeric($id)) {
108 if (is_null(Banana
::$spool)) {
111 $id = Banana
::$spool->ids
[$id];
113 $message = $this->readMessages(array($id));
114 if (empty($message)) {
117 $msg = new BananaMessage($message[$id]['message']);
121 private function getCount()
123 $this->count
= count(Banana
::$spool->overview
);
124 $max = @max
(array_keys(Banana
::$spool->overview
));
125 if ($max && Banana
::$spool->overview
[$max]->storage
['next'] == $this->filesize
) {
126 $this->new_messages
= 0;
128 $this->new_messages
= $this->countMessages($this->count
);
129 $this->count +
= $this->new_messages
;
133 /** Return the indexes of the messages presents in the Box
134 * @return Array(number of messages, MSGNUM of the first message, MSGNUM of the last message)
136 public function getIndexes()
138 if (is_null($this->count
)) {
141 return array($this->count
, 0, $this->count
- 1);
144 /** Return the message headers (in BananaMessage) for messages from firstid to lastid
145 * @return Array(id => array(headername => headervalue))
147 public function &getMessageHeaders($firstid, $lastid, array $msg_headers = array())
149 $msg_headers = array_map('strtolower', $msg_headers);
150 $messages =& $this->readMessages(range($firstid, $lastid), true
);
151 $msg_headers = array_map('strtolower', $msg_headers);
153 foreach ($msg_headers as $header) {
154 foreach ($messages as $id=>&$message) {
155 if (!isset($headers[$id])) {
156 $headers[$id] = array('beginning' => $message['beginning'], 'end' => $message['end']);
158 if ($header == 'date') {
159 $headers[$id][$header] = strtotime($message['message'][$header]);
161 $headers[$id][$header] = $message['message'][$header];
165 unset($this->messages
);
170 /** Add storage data in spool overview
172 public function updateSpool(array &$messages)
174 foreach ($messages as $id=>&$data) {
175 if (isset(Banana
::$spool->overview
[$id])) {
176 Banana
::$spool->overview
[$id]->storage
['offset'] = $data['beginning'];
177 Banana
::$spool->overview
[$id]->storage
['next'] = $data['end'];
182 /** Return the indexes of the new messages since the give date
183 * @return Array(MSGNUM of new messages)
185 public function getNewIndexes($since)
187 if (is_null($this->new_messages
)) {
190 return range($this->count
- $this->new_messages
, $this->count
- 1);
193 /** Return wether or not the protocole can be used to add new messages
195 public function canSend()
200 /** Return false because we can't cancel a mail
202 public function canCancel()
207 /** Return the list of requested headers
208 * @return Array('header1', 'header2', ...) with the key 'dest' for the destination header
209 * and 'reply' for the reply header, eg:
210 * * for a mail: Array('From', 'Subject', 'dest' => 'To', 'Cc', 'Bcc', 'reply' => 'Reply-To')
211 * * for a post: Array('From', 'Subject', 'dest' => 'Newsgroups', 'reply' => 'Followup-To')
213 public function requestedHeaders()
215 return Array('From', 'Subject', 'dest' => 'To', 'Cc', 'Bcc', 'reply' => 'Reply-To');
219 * @return true if it was successfull
221 public function send(BananaMessage
&$message)
227 * @return true if it was successfull
229 public function cancel(BananaMessage
&$message)
234 /** Return the protocole name
236 public function name()
242 # Filesystem functions
245 protected function getFileName($box)
250 @list
($mail, $domain) = explode('@', $box);
251 if ($mail == 'staff') {
252 return '/home/x2003bruneau/staff.polytechnique.org_innovation.mbox';
254 return '/var/mail/' . $mail;
262 /** Go to the given message
264 private function goTo($id)
266 if ($this->current_id
== $id && $this->at_beginning
) {
270 fseek($this->file
, 0);
271 $this->current_id
= 0;
272 $this->at_beginning
= true
;
274 } elseif (isset(Banana
::$spool->overview
[$id]) ||
isset($this->messages
[$id])) {
275 if (isset(Banana
::$spool->overview
[$id])) {
276 $pos = Banana
::$spool->overview
[$id]->storage
['offset'];
278 $pos = $this->messages
[$id]['beginning'];
280 if (fseek($this->file
, $pos) == 0) {
281 $this->current_id
= $id;
282 $this->at_beginning
= true
;
285 $this->current_id
= null
;
286 $this->_lasterrno
= 2;
287 $this->_lasterror
= _b_('Can\'t find message ') . $id;
291 $max = @max
(array_keys(Banana
::$spool->overview
));
295 if ($id <= $max && $max != 0) {
296 $this->current_id
= null
;
297 $this->_lasterrno
= 3;
298 $this->_lasterror
= _b_('Invalid message index ') . $id;
301 if (!$this->goTo($max)) {
304 if (feof($this->file
)) {
305 $this->current_id
= null
;
306 $this->_lasterrno
= 4;
307 $this->_lasterror
= _b_('Requested index does not exists or file has been truncated');
310 while ($this->readCurrentMessage(true
) && $this->current_id
< $id);
311 if ($this->current_id
== $id) {
314 $this->current_id
= null
;
315 $this->_lasterrno
= 5;
316 $this->_lasterror
= _b_('Requested index does not exists or file has been truncated');
321 private function countMessages($from = 0)
323 $this->messages
=& $this->readMessages(array($from), true
, true
);
324 return count($this->messages
);
327 /** Read the current message (identified by current_id)
328 * @param needFrom_ BOOLEAN is true if the first line *must* be a From_ line
329 * @param alignNext BOOLEAN is true if the buffer must be aligned at the beginning of the next From_ line
330 * @return message sources (without storage data)
332 private function &readCurrentMessage($stripBody = false
, $needFrom_ = true
, $alignNext = true
)
334 $file_cache =& $this->file_cache
;
335 if ($file_cache && $file_cache != ftell($this->file
)) {
341 while(!feof($this->file
)) {
342 // Process file cache
343 if ($file_cache) { // this is a From_ line
345 $this->at_beginning
= false
;
351 $line = rtrim(fgets($this->file
), "\r\n");
353 // Process From_ line
354 if ($needFrom_ ||
!$msg ||
$canFrom_) {
355 if (substr($line, 0, 5) == 'From ') { // this is a From_ line
361 $this->current_id++
; // we are finally in the next message
362 if ($alignNext) { // align the file pointer at the beginning of the new message
363 $this->at_beginning
= true
;
364 $file_cache = ftell($this->file
);
368 } elseif ($needFrom_) {
373 // Process non-From_ lines
374 if (substr($line, 0, 6) == '>From ') { // remove inline From_ quotation
375 $line = substr($line, 1);
377 if (!$stripBody ||
!$inBody) {
378 $msg[] = $line; // add the line to the message source
380 $canFrom_ = empty($line); // check if next line can be a From_ line
381 if ($canFrom_ && !$inBody && $stripBody) {
384 $this->at_beginning
= false
;
386 if (!feof($this->file
) && !$canFrom_) {
392 /** Read message with the given ids
393 * @param ids ARRAY of ids to look for
394 * @param strip BOOLEAN if true, only headers are retrieved
395 * @param from BOOLEAN if true, process all messages from max(ids) to the end of the mbox
396 * @return Array(Array('message' => message sources (or parsed message headers if $strip is true),
397 * 'beginning' => offset of message beginning,
398 * 'end' => offset of message end))
400 private function &readMessages(array $ids, $strip = false
, $from = false
)
402 if (!is_null($this->messages
)) {
403 return $this->messages
;
407 while ((count($ids) ||
$from) && !feof($this->file
)) {
409 $id = array_shift($ids);
413 if ($id != $this->current_id ||
!$this->at_beginning
) {
414 if (!$this->goTo($id)) {
418 $beginning = ftell($this->file
);
419 $message =& $this->readCurrentMessage($strip, false
);
421 $message =& BananaMimePart
::parseHeaders($message);
423 $end = ftell($this->file
);
424 $messages[$id] = array('message' => $message, 'beginning' => $beginning, 'end' => $end);
430 // vim:set et sw=4 sts=4 ts=4: