thomascube
2005-12-03 1cded85790206afe084e1baff371c543711b2b18
commit | author | age
4e17e6 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/rcube_imap.inc                                        |
6  |                                                                       |
7  | This file is part of the RoundCube Webmail client                     |
8  | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
30233b 9  | Licensed under the GNU GPL                                            |
4e17e6 10  |                                                                       |
T 11  | PURPOSE:                                                              |
12  |   IMAP wrapper that implements the Iloha IMAP Library (IIL)           |
13  |   See http://ilohamail.org/ for details                               |
14  |                                                                       |
15  +-----------------------------------------------------------------------+
16  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
17  +-----------------------------------------------------------------------+
18
19  $Id$
20
21 */
22
23
24 require_once('lib/imap.inc');
25 require_once('lib/mime.inc');
a95e0e 26 require_once('lib/utf7.inc');
4e17e6 27
T 28
29 class rcube_imap
30   {
1cded8 31   var $db;
4e17e6 32   var $conn;
520c36 33   var $root_ns = '';
4e17e6 34   var $root_dir = '';
T 35   var $mailbox = 'INBOX';
36   var $list_page = 1;
37   var $page_size = 10;
597170 38   var $delimiter = NULL;
6dc026 39   var $caching_enabled = FALSE;
4e17e6 40   var $default_folders = array('inbox', 'drafts', 'sent', 'junk', 'trash');
T 41   var $cache = array();
1cded8 42   var $cache_keys = array();  
4e17e6 43   var $cache_changes = array();  
T 44   var $uid_id_map = array();
45   var $msg_headers = array();
1cded8 46   var $capabilities = array();
4e17e6 47
T 48
49   // PHP 5 constructor
1cded8 50   function __construct($db_conn)
4e17e6 51     {
1cded8 52     $this->db = $db_conn;    
4e17e6 53     }
T 54
55   // PHP 4 compatibility
1cded8 56   function rcube_imap($db_conn)
4e17e6 57     {
1cded8 58     $this->__construct($db_conn);
4e17e6 59     }
T 60
61
42b113 62   function connect($host, $user, $pass, $port=143, $use_ssl=FALSE)
4e17e6 63     {
7902df 64     global $ICL_SSL, $ICL_PORT, $CONFIG;
42b113 65     
T 66     // check for Open-SSL support in PHP build
67     if ($use_ssl && in_array('openssl', get_loaded_extensions()))
68       $ICL_SSL = TRUE;
520c36 69     else if ($use_ssl)
T 70       {
71       raise_error(array('code' => 403,
72                         'type' => 'imap',
7902df 73                         'file' => __FILE__,
520c36 74                         'message' => 'Open SSL not available;'), TRUE, FALSE);
T 75       $port = 143;
76       }
4e17e6 77
T 78     $ICL_PORT = $port;
42b113 79     $this->conn = iil_Connect($host, $user, $pass, array('imap' => 'check'));
4e17e6 80     $this->host = $host;
T 81     $this->user = $user;
82     $this->pass = $pass;
520c36 83     $this->port = $port;
T 84     $this->ssl = $use_ssl;
42b113 85     
520c36 86     // print trace mesages
42b113 87     if ($this->conn && ($CONFIG['debug_level'] & 8))
520c36 88       console($this->conn->message);
T 89     
90     // write error log
42b113 91     else if (!$this->conn && $GLOBALS['iil_error'])
T 92       {
93       raise_error(array('code' => 403,
94                        'type' => 'imap',
95                        'message' => $GLOBALS['iil_error']), TRUE, FALSE);
520c36 96       }
T 97
98     // get account namespace
99     if ($this->conn)
100       {
1cded8 101       $this->_parse_capability($this->conn->capability);
520c36 102       iil_C_NameSpace($this->conn);
T 103       
104       if (!empty($this->conn->delimiter))
105         $this->delimiter = $this->conn->delimiter;
106       if (!empty($this->conn->rootdir))
7902df 107         {
T 108         $this->set_rootdir($this->conn->rootdir);
109         $this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir);
110         }
42b113 111       }
4e17e6 112
T 113     return $this->conn ? TRUE : FALSE;
114     }
115
116
117   function close()
118     {    
119     if ($this->conn)
120       iil_Close($this->conn);
121     }
122
123
520c36 124   function reconnect()
T 125     {
126     $this->close();
127     $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
128     }
129
130
4e17e6 131   function set_rootdir($root)
T 132     {
520c36 133     if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/')
4e17e6 134       $root = substr($root, 0, -1);
T 135
136     $this->root_dir = $root;
520c36 137     
T 138     if (empty($this->delimiter))
139       $this->get_hierarchy_delimiter();
4e17e6 140     }
T 141
142
143   function set_default_mailboxes($arr)
144     {
145     if (is_array($arr))
146       {
147       $this->default_folders = array();
148       
149       // add mailbox names lower case
150       foreach ($arr as $mbox)
151         $this->default_folders[] = strtolower($mbox);
152       
153       // add inbox if not included
154       if (!in_array('inbox', $this->default_folders))
155         array_unshift($arr, 'inbox');
156       }
157     }
158
159
160   function set_mailbox($mbox)
161     {
162     $mailbox = $this->_mod_mailbox($mbox);
163
164     if ($this->mailbox == $mailbox)
165       return;
166
167     $this->mailbox = $mailbox;
168
169     // clear messagecount cache for this mailbox
170     $this->_clear_messagecount($mailbox);
171     }
172
173
174   function set_page($page)
175     {
176     $this->list_page = (int)$page;
177     }
178
179
180   function set_pagesize($size)
181     {
182     $this->page_size = (int)$size;
183     }
184
185
186   function get_mailbox_name()
187     {
188     return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
1cded8 189     }
T 190
191
192   function get_capability($cap)
193     {
194     $cap = strtoupper($cap);
195     return $this->capabilities[$cap];
4e17e6 196     }
T 197
198
597170 199   function get_hierarchy_delimiter()
T 200     {
201     if ($this->conn && empty($this->delimiter))
202       $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
203
7902df 204     if (empty($this->delimiter))
T 205       $this->delimiter = '/';
206
597170 207     return $this->delimiter;
T 208     }
209
4e17e6 210   // public method for mailbox listing
T 211   // convert mailbox name with root dir first
212   function list_mailboxes($root='', $filter='*')
213     {
214     $a_out = array();
215     $a_mboxes = $this->_list_mailboxes($root, $filter);
216
217     foreach ($a_mboxes as $mbox)
218       {
219       $name = $this->_mod_mailbox($mbox, 'out');
220       if (strlen($name))
221         $a_out[] = $name;
222       }
223
224     // sort mailboxes
225     $a_out = $this->_sort_mailbox_list($a_out);
226
227     return $a_out;
228     }
229
230   // private method for mailbox listing
231   function _list_mailboxes($root='', $filter='*')
232     {
233     $a_defaults = $a_out = array();
234     
235     // get cached folder list    
236     $a_mboxes = $this->get_cache('mailboxes');
237     if (is_array($a_mboxes))
238       return $a_mboxes;
239
240     // retrieve list of folders from IMAP server
241     $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
242     
243     if (!is_array($a_folders) || !sizeof($a_folders))
244       $a_folders = array();
245
246     // create INBOX if it does not exist
247     if (!in_array_nocase('INBOX', $a_folders))
248       {
249       $this->create_mailbox('INBOX', TRUE);
250       array_unshift($a_folders, 'INBOX');
251       }
252
253     $a_mailbox_cache = array();
254
255     // write mailboxlist to cache
256     $this->update_cache('mailboxes', $a_folders);
257     
258     return $a_folders;
259     }
260
261
262   // get message count for a specific mailbox; acceptes modes are: ALL, UNSEEN
263   function messagecount($mbox='', $mode='ALL', $force=FALSE)
264     {
265     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
266     return $this->_messagecount($mailbox, $mode, $force);
267     }
268
269   // private method for getting nr of mesages
270   function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
271     {
272     $a_mailbox_cache = FALSE;
273     $mode = strtoupper($mode);
274
275     if (!$mailbox)
276       $mailbox = $this->mailbox;
277
278     $a_mailbox_cache = $this->get_cache('messagecount');
279     
280     // return cached value
281     if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
282       return $a_mailbox_cache[$mailbox][$mode];      
283
284     // get message count and store in cache
285     if ($mode == 'UNSEEN')
286       $count = iil_C_CountUnseen($this->conn, $mailbox);
287     else
288       $count = iil_C_CountMessages($this->conn, $mailbox);
289
290     if (is_array($a_mailbox_cache[$mailbox]))
291       $a_mailbox_cache[$mailbox] = array();
292       
293     $a_mailbox_cache[$mailbox][$mode] = (int)$count;
294     
295     // write back to cache
296     $this->update_cache('messagecount', $a_mailbox_cache);
297
298     return (int)$count;
299     }
300
301
302   // public method for listing headers
303   // convert mailbox name with root dir first
304   function list_headers($mbox='', $page=NULL, $sort_field='date', $sort_order='DESC')
305     {
306     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
307     return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
308     }
309
310
311   // private method for listing message header
312   function _list_headers($mailbox='', $page=NULL, $sort_field='date', $sort_order='DESC')
313     {
314     if (!strlen($mailbox))
17fc71 315       return array();
4e17e6 316
1cded8 317     $max = $this->_messagecount($mailbox);
T 318     $start_msg = ($this->list_page-1) * $this->page_size;
319     
320     if ($page=='all')
4e17e6 321       {
1cded8 322       $begin = 0;
T 323       $end = $max;
324       }
325     else if ($sort_order=='DESC')
326       {
327       $begin = $max - $this->page_size - $start_msg;
328       $end =   $max - $start_msg;
4e17e6 329       }
T 330     else
b595c9 331       {
1cded8 332       $begin = $start_msg;
T 333       $end =   $start_msg + $this->page_size;
b595c9 334       }
4e17e6 335
1cded8 336     if ($begin < 0) $begin = 0;
T 337     if ($end < 0) $end = $max;
338     if ($end > $max) $end = $max;
b076a4 339
1cded8 340 //console("fetch headers $start_msg to ".($start_msg+$this->page_size)." (msg $begin to $end)");
T 341
342     $headers_sorted = FALSE;
343     $cache_key = $mailbox.'.msg';
344     $cache_status = $this->check_cache_status($mailbox, $cache_key);
345
346 //console("Cache status = $cache_status");
347     
348     // cache is OK, we can get all messages from local cache
349     if ($cache_status>0)
350       {
351       $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $sort_field, $sort_order);
352       $headers_sorted = TRUE;
353       }
354     else
355       {
356       // retrieve headers from IMAP
357       if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $sort_field)))
358         {
359 //console("$mailbox: ".count($msg_index));
360         
361         $msgs = $msg_index[$begin];
362         for ($i=$begin; $i < $end; $i++)
363           {
364           if ($sort_order == 'DESC')
365             $msgs = $msg_index[$i].','.$msgs;
366           else
367             $msgs = $msgs.','.$msg_index[$i];
368           }
369
370         $sorted = TRUE;
371         }
372       else
373         {
374         $msgs = sprintf("%d:%d", $begin+1, $end);
375         $sorted = FALSE;
376         }
377
378
379       // cache is dirty, sync it
380       if ($this->caching_enabled && $cache_status==-1)
381         {
382         $this->sync_header_index($mailbox);
383         return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
384         }      
385
386
387       // cache is incomplete
388       $cache_index = $this->get_message_cache_index($cache_key);
389         
390       // fetch reuested headers from server
391       $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
392       $a_msg_headers = array();
393       
394       
395       if (!empty($a_header_index))
396         {
397         foreach ($a_header_index as $i => $headers)
398           {
399           if ($headers->deleted)
400             {
401             // delete from cache
402             if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
403               $this->remove_message_cache($cache_key, $headers->id);
404
405             continue;
406             }
407
408           // add message to cache
409           if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
410             $this->add_message_cache($cache_key, $headers->id, $headers);
411
412           $a_msg_headers[$headers->uid] = $headers;
413           }
414         }
415
416       // delete cached messages with a higher index than $max
417       $this->clear_message_cache($cache_key, $max);
418
419
420       // kick child process to sync cache
421       
422       }
423
424
425     // return empty array if no messages found
426     if (!is_array($a_msg_headers) || empty($a_msg_headers))
427         return array();
428
429
430     // if not already sorted
431     if (!$headers_sorted)
432       $a_msg_headers = iil_SortHeaders($a_msg_headers, $sort_field, $sort_order);
433
434     return array_values($a_msg_headers);
4e17e6 435     }
1cded8 436
4e17e6 437
T 438   // return sorted array of message UIDs
439   function message_index($mbox='', $sort_field='date', $sort_order='DESC')
440     {
441     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
442     $a_out = array();
443
444     // get array of message headers
445     $a_headers = $this->_list_headers($mailbox, 'all', $sort_field, $sort_order);
446
447     if (is_array($a_headers))
448       foreach ($a_headers as $header)
449         $a_out[] = $header->uid;
450
451     return $a_out;
452     }
453
454
1cded8 455   function sync_header_index($mailbox)
4e17e6 456     {
1cded8 457     $cache_key = $mailbox.'.msg';
T 458     $cache_index = $this->get_message_cache_index($cache_key);
459     $msg_count = $this->_messagecount($mailbox);
460
461     // fetch complete message index
462     $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
463         
464     foreach ($a_message_index as $id => $uid)
465       {
466       // message in cache at correct position
467       if ($cache_index[$id] == $uid)
468         {
469 // console("$id / $uid: OK");
470         unset($cache_index[$id]);
471         continue;
472         }
473         
474       // message in cache but in wrong position
475       if (in_array((string)$uid, $cache_index, TRUE))
476         {
477 // console("$id / $uid: Moved");
478         unset($cache_index[$id]);        
479         }
480       
481       // other message at this position
482       if (isset($cache_index[$id]))
483         {
484 // console("$id / $uid: Delete");
485         $this->remove_message_cache($cache_key, $id);
486         unset($cache_index[$id]);
487         }
488         
489
490 // console("$id / $uid: Add");
491
492       // fetch complete headers and add to cache
493       $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
494       $this->add_message_cache($cache_key, $headers->id, $headers);
495       }
496
497     // those ids that are still in cache_index have been deleted      
498     if (!empty($cache_index))
499       {
500       foreach ($cache_index as $id => $uid)
501         $this->remove_message_cache($cache_key, $id);
502       }
4e17e6 503     }
T 504
505
506   function search($mbox='', $criteria='ALL')
507     {
508     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
509     $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
510     return $a_messages;
511     }
512
513
514   function get_headers($uid, $mbox=NULL)
515     {
516     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
1cded8 517
4e17e6 518     // get cached headers
1cded8 519     if ($headers = $this->get_cached_message($mailbox.'.msg', $uid))
T 520       return $headers;
520c36 521
4e17e6 522     $msg_id = $this->_uid2id($uid);
1cded8 523     $headers = iil_C_FetchHeader($this->conn, $mailbox, $msg_id);
520c36 524
4e17e6 525     // write headers cache
1cded8 526     if ($headers)
T 527       $this->add_message_cache($mailbox.'.msg', $msg_id, $headers);
4e17e6 528
1cded8 529     return $headers;
4e17e6 530     }
T 531
532
533   function get_body($uid, $part=1)
534     {
535     if (!($msg_id = $this->_uid2id($uid)))
536       return FALSE;
537
538     $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); 
539     $structure = iml_GetRawStructureArray($structure_str);
540     $body = iil_C_FetchPartBody($this->conn, $this->mailbox, $msg_id, $part);
541
542     $encoding = iml_GetPartEncodingCode($structure, $part);
543     
544     if ($encoding==3) $body = $this->mime_decode($body, 'base64');
545     else if ($encoding==4) $body = $this->mime_decode($body, 'quoted-printable');
546
547     return $body;
548     }
549
550
551   function get_raw_body($uid)
552     {
553     if (!($msg_id = $this->_uid2id($uid)))
554       return FALSE;
555
556     $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
557     $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
558
559     return $body;    
560     }
561
562
563   // set message flag to one or several messages
564   // possible flgs are: SEEN, DELETED, RECENT, ANSWERED, DRAFT
565   function set_flag($uids, $flag)
566     {
567     $flag = strtoupper($flag);
568     $msg_ids = array();
569     if (!is_array($uids))
570       $uids = array($uids);
571       
572     foreach ($uids as $uid)
573       $msg_ids[] = $this->_uid2id($uid);
574       
575     if ($flag=='UNSEEN')
576       $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', $msg_ids));
577     else
578       $result = iil_C_Flag($this->conn, $this->mailbox, join(',', $msg_ids), $flag);
579
580     // reload message headers if cached
581     $cache_key = $this->mailbox.'.msg';
1cded8 582     if ($this->caching_enabled)
4e17e6 583       {
1cded8 584       foreach ($msg_ids as $id)
4e17e6 585         {
1cded8 586         if ($cached_headers = $this->get_cached_message($cache_key, $id))
4e17e6 587           {
1cded8 588           $this->remove_message_cache($cache_key, $id);
T 589           //$this->get_headers($uid);
4e17e6 590           }
T 591         }
1cded8 592
T 593       // close and re-open connection
594       // this prevents connection problems with Courier 
595       $this->reconnect();
4e17e6 596       }
T 597
598     // set nr of messages that were flaged
599     $count = sizeof($msg_ids);
600
601     // clear message count cache
602     if ($result && $flag=='SEEN')
603       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
604     else if ($result && $flag=='UNSEEN')
605       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
606     else if ($result && $flag=='DELETED')
607       $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
608
609     return $result;
610     }
611
612
613   // append a mail message (source) to a specific mailbox
614   function save_message($mbox, $message)
615     {
616     $mailbox = $this->_mod_mailbox($mbox);
617
618     // make shure mailbox exists
619     if (in_array($mailbox, $this->_list_mailboxes()))
620       $saved = iil_C_Append($this->conn, $mailbox, $message);
1cded8 621
4e17e6 622     if ($saved)
T 623       {
624       // increase messagecount of the target mailbox
625       $this->_set_messagecount($mailbox, 'ALL', 1);
626       }
627           
628     return $saved;
629     }
630
631
632   // move a message from one mailbox to another
633   function move_message($uids, $to_mbox, $from_mbox='')
634     {
635     $to_mbox = $this->_mod_mailbox($to_mbox);
636     $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
637
638     // make shure mailbox exists
639     if (!in_array($to_mbox, $this->_list_mailboxes()))
640       return FALSE;
641     
642     // convert the list of uids to array
643     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
644     
645     // exit if no message uids are specified
646     if (!is_array($a_uids))
647       return false;
520c36 648
4e17e6 649     // convert uids to message ids
T 650     $a_mids = array();
651     foreach ($a_uids as $uid)
652       $a_mids[] = $this->_uid2id($uid, $from_mbox);
520c36 653
4e17e6 654     $moved = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
T 655     
656     // send expunge command in order to have the moved message
657     // really deleted from the source mailbox
658     if ($moved)
659       {
1cded8 660       $this->_expunge($from_mbox, FALSE);
4e17e6 661       $this->_clear_messagecount($from_mbox);
T 662       $this->_clear_messagecount($to_mbox);
663       }
664
665     // update cached message headers
666     $cache_key = $from_mbox.'.msg';
1cded8 667     if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
4e17e6 668       {
1cded8 669       $start_index = 100000;
4e17e6 670       foreach ($a_uids as $uid)
1cded8 671         {
T 672         $index = array_search($uid, $a_cache_index);
673         $start_index = min($index, $start_index);
674         }
4e17e6 675
1cded8 676       // clear cache from the lowest index on
T 677       $this->clear_message_cache($cache_key, $start_index);
4e17e6 678       }
T 679
680     return $moved;
681     }
682
683
684   // mark messages as deleted and expunge mailbox
685   function delete_message($uids, $mbox='')
686     {
687     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
688
689     // convert the list of uids to array
690     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
691     
692     // exit if no message uids are specified
693     if (!is_array($a_uids))
694       return false;
695
696
697     // convert uids to message ids
698     $a_mids = array();
699     foreach ($a_uids as $uid)
700       $a_mids[] = $this->_uid2id($uid, $mailbox);
701         
702     $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
703     
704     // send expunge command in order to have the deleted message
705     // really deleted from the mailbox
706     if ($deleted)
707       {
1cded8 708       $this->_expunge($mailbox, FALSE);
4e17e6 709       $this->_clear_messagecount($mailbox);
T 710       }
711
712     // remove deleted messages from cache
1cded8 713     $cache_key = $mailbox.'.msg';
T 714     if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
4e17e6 715       {
1cded8 716       $start_index = 100000;
4e17e6 717       foreach ($a_uids as $uid)
1cded8 718         {
T 719         $index = array_search($uid, $a_cache_index);
720         $start_index = min($index, $start_index);
721         }
4e17e6 722
1cded8 723       // clear cache from the lowest index on
T 724       $this->clear_message_cache($cache_key, $start_index);
4e17e6 725       }
T 726
727     return $deleted;
728     }
729
730
a95e0e 731   // clear all messages in a specific mailbox
T 732   function clear_mailbox($mbox)
733     {
734     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
735     $msg_count = $this->_messagecount($mailbox, 'ALL');
736     
737     if ($msg_count>0)
1cded8 738       {
T 739       $this->clear_message_cache($mailbox.'.msg');
a95e0e 740       return iil_C_ClearFolder($this->conn, $mailbox);
1cded8 741       }
a95e0e 742     else
T 743       return 0;
744     }
745
746
4e17e6 747   // send IMAP expunge command and clear cache
T 748   function expunge($mbox='', $clear_cache=TRUE)
749     {
750     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
1cded8 751     return $this->_expunge($mailbox, $clear_cache);
T 752     }
753
754
755   // send IMAP expunge command and clear cache
756   function _expunge($mailbox, $clear_cache=TRUE)
757     {
4e17e6 758     $result = iil_C_Expunge($this->conn, $mailbox);
T 759
760     if ($result>=0 && $clear_cache)
761       {
1cded8 762       //$this->clear_message_cache($mailbox.'.msg');
4e17e6 763       $this->_clear_messagecount($mailbox);
T 764       }
765       
766     return $result;
767     }
768
769
770   /* --------------------------------
771    *        folder managment
772    * --------------------------------*/
773
774
775   // return an array with all folders available in IMAP server
776   function list_unsubscribed($root='')
777     {
778     static $sa_unsubscribed;
779     
780     if (is_array($sa_unsubscribed))
781       return $sa_unsubscribed;
782       
783     // retrieve list of folders from IMAP server
784     $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
785
786     // modify names with root dir
787     foreach ($a_mboxes as $mbox)
788       {
789       $name = $this->_mod_mailbox($mbox, 'out');
790       if (strlen($name))
791         $a_folders[] = $name;
792       }
793
794     // filter folders and sort them
795     $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
796     return $sa_unsubscribed;
797     }
798
799
800   // subscribe to a specific mailbox(es)
801   function subscribe($mbox, $mode='subscribe')
802     {
803     if (is_array($mbox))
804       $a_mboxes = $mbox;
805     else if (is_string($mbox) && strlen($mbox))
806       $a_mboxes = explode(',', $mbox);
807     
808     // let this common function do the main work
809     return $this->_change_subscription($a_mboxes, 'subscribe');
810     }
811
812
813   // unsubscribe mailboxes
814   function unsubscribe($mbox)
815     {
816     if (is_array($mbox))
817       $a_mboxes = $mbox;
818     else if (is_string($mbox) && strlen($mbox))
819       $a_mboxes = explode(',', $mbox);
820
821     // let this common function do the main work
822     return $this->_change_subscription($a_mboxes, 'unsubscribe');
823     }
824
825
826   // create a new mailbox on the server and register it in local cache
827   function create_mailbox($name, $subscribe=FALSE)
828     {
829     $result = FALSE;
1cded8 830     
T 831     // replace backslashes
832     $name = preg_replace('/[\\\]+/', '-', $name);
833
a95e0e 834     $name_enc = UTF7EncodeString($name);
1cded8 835
T 836     // reduce mailbox name to 100 chars
837     $name_enc = substr($name_enc, 0, 100);
838
a95e0e 839     $abs_name = $this->_mod_mailbox($name_enc);
4e17e6 840     $a_mailbox_cache = $this->get_cache('mailboxes');
1cded8 841         
4e17e6 842     if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
a95e0e 843       $result = iil_C_CreateFolder($this->conn, $abs_name);
4e17e6 844
T 845     // update mailboxlist cache
846     if ($result && $subscribe)
a95e0e 847       $this->subscribe($name_enc);
4e17e6 848
7902df 849     return $result ? $name : FALSE;
4e17e6 850     }
T 851
852
853   // set a new name to an existing mailbox
854   function rename_mailbox($mbox, $new_name)
855     {
856     // not implemented yet
857     }
858
859
860   // remove mailboxes from server
861   function delete_mailbox($mbox)
862     {
863     $deleted = FALSE;
864
865     if (is_array($mbox))
866       $a_mboxes = $mbox;
867     else if (is_string($mbox) && strlen($mbox))
868       $a_mboxes = explode(',', $mbox);
869
870     if (is_array($a_mboxes))
871       foreach ($a_mboxes as $mbox)
872         {
873         $mailbox = $this->_mod_mailbox($mbox);
874
875         // unsubscribe mailbox before deleting
876         iil_C_UnSubscribe($this->conn, $mailbox);
877         
878         // send delete command to server
879         $result = iil_C_DeleteFolder($this->conn, $mailbox);
880         if ($result>=0)
881           $deleted = TRUE;
882         }
883
884     // clear mailboxlist cache
885     if ($deleted)
1cded8 886       {
T 887       $this->clear_message_cache($mailbox.'.msg');
4e17e6 888       $this->clear_cache('mailboxes');
1cded8 889       }
4e17e6 890
1cded8 891     return $deleted;
4e17e6 892     }
T 893
894
895
896
897   /* --------------------------------
1cded8 898    *   internal caching methods
4e17e6 899    * --------------------------------*/
6dc026 900
T 901
902   function set_caching($set)
903     {
1cded8 904     if ($set && is_object($this->db))
6dc026 905       $this->caching_enabled = TRUE;
T 906     else
907       $this->caching_enabled = FALSE;
908     }
1cded8 909
4e17e6 910
T 911   function get_cache($key)
912     {
913     // read cache
6dc026 914     if (!isset($this->cache[$key]) && $this->caching_enabled)
4e17e6 915       {
1cded8 916       $cache_data = $this->_read_cache_record('IMAP.'.$key);
4e17e6 917       $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
T 918       }
919     
1cded8 920     return $this->cache[$key];
4e17e6 921     }
T 922
923
924   function update_cache($key, $data)
925     {
926     $this->cache[$key] = $data;
927     $this->cache_changed = TRUE;
928     $this->cache_changes[$key] = TRUE;
929     }
930
931
932   function write_cache()
933     {
6dc026 934     if ($this->caching_enabled && $this->cache_changed)
4e17e6 935       {
T 936       foreach ($this->cache as $key => $data)
937         {
938         if ($this->cache_changes[$key])
1cded8 939           $this->_write_cache_record('IMAP.'.$key, serialize($data));
4e17e6 940         }
T 941       }    
942     }
943
944
945   function clear_cache($key=NULL)
946     {
947     if ($key===NULL)
948       {
949       foreach ($this->cache as $key => $data)
1cded8 950         $this->_clear_cache_record('IMAP.'.$key);
4e17e6 951
T 952       $this->cache = array();
953       $this->cache_changed = FALSE;
954       $this->cache_changes = array();
955       }
956     else
957       {
1cded8 958       $this->_clear_cache_record('IMAP.'.$key);
4e17e6 959       $this->cache_changes[$key] = FALSE;
T 960       unset($this->cache[$key]);
961       }
962     }
963
964
965
1cded8 966   function _read_cache_record($key)
T 967     {
968     $cache_data = FALSE;
969     
970     if ($this->db)
971       {
972       // get cached data from DB
973       $sql_result = $this->db->query(
974         "SELECT cache_id, data
975          FROM ".get_table_name('cache')."
976          WHERE  user_id=?
977          AND    cache_key=?",
978         $_SESSION['user_id'],
979         $key);
980
981       if ($sql_arr = $this->db->fetch_assoc($sql_result))
982         {
983         $cache_data = $sql_arr['data'];
984         $this->cache_keys[$key] = $sql_arr['cache_id'];
985         }
986       }
987
988     return $cache_data;    
989     }
990     
991
992   function _write_cache_record($key, $data)
993     {
994     if (!$this->db)
995       return FALSE;
996
997     // check if we already have a cache entry for this key
998     if (!isset($this->cache_keys[$key]))
999       {
1000       $sql_result = $this->db->query(
1001         "SELECT cache_id
1002          FROM ".get_table_name('cache')."
1003          WHERE  user_id=?
1004          AND    cache_key=?",
1005         $_SESSION['user_id'],
1006         $key);
1007                                      
1008       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1009         $this->cache_keys[$key] = $sql_arr['cache_id'];
1010       else
1011         $this->cache_keys[$key] = FALSE;
1012       }
1013
1014     // update existing cache record
1015     if ($this->cache_keys[$key])
1016       {
1017       $this->db->query(
1018         "UPDATE ".get_table_name('cache')."
1019          SET    created=now(),
1020                 data=?
1021          WHERE  user_id=?
1022          AND    cache_key=?",
1023         $data,
1024         $_SESSION['user_id'],
1025         $key);
1026       }
1027     // add new cache record
1028     else
1029       {
1030       $this->db->query(
1031         "INSERT INTO ".get_table_name('cache')."
1032          (created, user_id, cache_key, data)
1033          VALUES (now(), ?, ?, ?)",
1034         $_SESSION['user_id'],
1035         $key,
1036         $data);
1037       }
1038     }
1039
1040
1041   function _clear_cache_record($key)
1042     {
1043     $this->db->query(
1044       "DELETE FROM ".get_table_name('cache')."
1045        WHERE  user_id=?
1046        AND    cache_key=?",
1047       $_SESSION['user_id'],
1048       $key);
1049     }
1050
1051
1052
4e17e6 1053   /* --------------------------------
1cded8 1054    *   message caching methods
T 1055    * --------------------------------*/
1056    
1057
1058   // checks if the cache is up-to-date
1059   // return: -3 = off, -2 = incomplete, -1 = dirty
1060   function check_cache_status($mailbox, $cache_key)
1061     {
1062     if (!$this->caching_enabled)
1063       return -3;
1064
1065     $cache_index = $this->get_message_cache_index($cache_key, TRUE);
1066     $msg_count = $this->_messagecount($mailbox);
1067     $cache_count = count($cache_index);
1068
1069     // console("Cache check: $msg_count !== ".count($cache_index));
1070
1071     if ($cache_count==$msg_count)
1072       {
1073       // get highest index
1074       $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
1075       $cache_uid = array_pop($cache_index);
1076       
1077       // uids of highes message matches -> cache seems OK
1078       if ($cache_uid == $header->uid)
1079         return 1;
1080
1081       // cache is dirty
1082       return -1;
1083       }
1084     // if cache count differs less that 10% report as dirty
1085     else if (abs($msg_count - $cache_count) < $msg_count/10)
1086       return -1;
1087     else
1088       return -2;
1089     }
1090
1091
1092
1093   function get_message_cache($key, $from, $to, $sort_field, $sort_order)
1094     {
1095     $cache_key = "$key:$from:$to:$sort_field:$sort_order";
1096     $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
1097     
1098     if (!in_array($sort_field, $db_header_fields))
1099       $sort_field = 'idx';
1100     
1101     if ($this->caching_enabled && !isset($this->cache[$cache_key]))
1102       {
1103       $this->cache[$cache_key] = array();
1104       $sql_result = $this->db->limitquery(
1105         "SELECT idx, uid, headers
1106          FROM ".get_table_name('messages')."
1107          WHERE  user_id=?
1108          AND    cache_key=?
1109          ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
1110          strtoupper($sort_order),
1111         $from,
1112         $to-$from,
1113         $_SESSION['user_id'],
1114         $key);
1115
1116       while ($sql_arr = $this->db->fetch_assoc($sql_result))
1117         {
1118         $uid = $sql_arr['uid'];
1119         $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
1120         }
1121       }
1122       
1123     return $this->cache[$cache_key];
1124     }
1125
1126
1127   function get_cached_message($key, $uid, $body=FALSE)
1128     {
1129     if (!$this->caching_enabled)
1130       return FALSE;
1131
1132     $internal_key = '__single_msg';
1133     if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) || $body))
1134       {
1135       $sql_select = "idx, uid, headers";
1136       if ($body)
1137         $sql_select .= ", body";
1138       
1139       $sql_result = $this->db->query(
1140         "SELECT $sql_select
1141          FROM ".get_table_name('messages')."
1142          WHERE  user_id=?
1143          AND    cache_key=?
1144          AND    uid=?",
1145         $_SESSION['user_id'],
1146         $key,
1147         $uid);
1148       
1149       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1150         {
1151         $headers = unserialize($sql_arr['headers']);
1152         if (is_object($headers) && !empty($sql_arr['body']))
1153           $headers->body = $sql_arr['body'];
1154
1155         $this->cache[$internal_key][$uid] = $headers;
1156         }
1157       }
1158
1159     return $this->cache[$internal_key][$uid];
1160     }
1161
1162    
1163   function get_message_cache_index($key, $force=FALSE)
1164     {
1165     static $sa_message_index = array();
1166     
1167     if (!empty($sa_message_index[$key]) && !$force)
1168       return $sa_message_index[$key];
1169     
1170     $sa_message_index[$key] = array();
1171     $sql_result = $this->db->query(
1172       "SELECT idx, uid
1173        FROM ".get_table_name('messages')."
1174        WHERE  user_id=?
1175        AND    cache_key=?
1176        ORDER BY idx ASC",
1177       $_SESSION['user_id'],
1178       $key);
1179
1180     while ($sql_arr = $this->db->fetch_assoc($sql_result))
1181       $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
1182       
1183     return $sa_message_index[$key];
1184     }
1185
1186
1187   function add_message_cache($key, $index, $headers)
1188     {
1189     $this->db->query(
1190       "INSERT INTO ".get_table_name('messages')."
1191        (user_id, del, cache_key, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers)
1192        VALUES (?, 0, ?, ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?)",
1193       $_SESSION['user_id'],
1194       $key,
1195       $index,
1196       $headers->uid,
1197       $this->decode_header($headers->subject, TRUE),
1198       $this->decode_header($headers->from, TRUE),
1199       $this->decode_header($headers->to, TRUE),
1200       $this->decode_header($headers->cc, TRUE),
1201       $headers->size,
1202       serialize($headers));
1203     }
1204     
1205     
1206   function remove_message_cache($key, $index)
1207     {
1208     $this->db->query(
1209       "DELETE FROM ".get_table_name('messages')."
1210        WHERE  user_id=?
1211        AND    cache_key=?
1212        AND    idx=?",
1213       $_SESSION['user_id'],
1214       $key,
1215       $index);
1216     }
1217
1218
1219   function clear_message_cache($key, $start_index=1)
1220     {
1221     $this->db->query(
1222       "DELETE FROM ".get_table_name('messages')."
1223        WHERE  user_id=?
1224        AND    cache_key=?
1225        AND    idx>=?",
1226       $_SESSION['user_id'],
1227       $key,
1228       $start_index);
1229     }
1230
1231
1232
1233
1234   /* --------------------------------
1235    *   encoding/decoding methods
4e17e6 1236    * --------------------------------*/
T 1237
1238   
1239   function decode_address_list($input, $max=NULL)
1240     {
1241     $a = $this->_parse_address_list($input);
1242     $out = array();
1243
1244     if (!is_array($a))
1245       return $out;
1246
1247     $c = count($a);
1248     $j = 0;
1249
1250     foreach ($a as $val)
1251       {
1252       $j++;
1253       $address = $val['address'];
1254       $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
1255       $string = $name!==$address ? sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address) : $address;
1256       
1257       $out[$j] = array('name' => $name,
1258                        'mailto' => $address,
1259                        'string' => $string);
1260               
1261       if ($max && $j==$max)
1262         break;
1263       }
1264     
1265     return $out;
1266     }
1267
1268
1cded8 1269   function decode_header($input, $remove_quotes=FALSE)
4e17e6 1270     {
1cded8 1271     $str = $this->decode_mime_string($input);
T 1272     if ($str{0}=='"' && $remove_quotes)
1273       {
1274       $str = str_replace('"', '', $str);
1275       }
1276     
1277     return $str;
4b0f65 1278     }
T 1279     
1280     
1281   function decode_mime_string($input)
1282     {
4e17e6 1283     $out = '';
T 1284
1285     $pos = strpos($input, '=?');
1286     if ($pos !== false)
1287       {
1288       $out = substr($input, 0, $pos);
1289   
1290       $end_cs_pos = strpos($input, "?", $pos+2);
1291       $end_en_pos = strpos($input, "?", $end_cs_pos+1);
1292       $end_pos = strpos($input, "?=", $end_en_pos+1);
1293   
1294       $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
1295       $rest = substr($input, $end_pos+2);
1296
4b0f65 1297       $out .= rcube_imap::_decode_mime_string_part($encstr);
T 1298       $out .= rcube_imap::decode_mime_string($rest);
4e17e6 1299
T 1300       return $out;
1301       }
1302     else
1303       return $input;
1304     }
1305
1306
4b0f65 1307   function _decode_mime_string_part($str)
4e17e6 1308     {
T 1309     $a = explode('?', $str);
1310     $count = count($a);
1311
1312     // should be in format "charset?encoding?base64_string"
1313     if ($count >= 3)
1314       {
1315       for ($i=2; $i<$count; $i++)
1316         $rest.=$a[$i];
1317
1318       if (($a[1]=="B")||($a[1]=="b"))
1319         $rest = base64_decode($rest);
1320       else if (($a[1]=="Q")||($a[1]=="q"))
1321         {
1322         $rest = str_replace("_", " ", $rest);
1323         $rest = quoted_printable_decode($rest);
1324         }
1325
1326       return decode_specialchars($rest, $a[0]);
1327       }
1328     else
1329       return $str;    //we dont' know what to do with this  
1330     }
1331
1332
1333   function mime_decode($input, $encoding='7bit')
1334     {
1335     switch (strtolower($encoding))
1336       {
1337       case '7bit':
1338         return $input;
1339         break;
1340       
1341       case 'quoted-printable':
1342         return quoted_printable_decode($input);
1343         break;
1344       
1345       case 'base64':
1346         return base64_decode($input);
1347         break;
1348       
1349       default:
1350         return $input;
1351       }
1352     }
1353
1354
1355   function mime_encode($input, $encoding='7bit')
1356     {
1357     switch ($encoding)
1358       {
1359       case 'quoted-printable':
1360         return quoted_printable_encode($input);
1361         break;
1362
1363       case 'base64':
1364         return base64_encode($input);
1365         break;
1366
1367       default:
1368         return $input;
1369       }
1370     }
1371
1372
1373   // convert body chars according to the ctype_parameters
1374   function charset_decode($body, $ctype_param)
1375     {
a95e0e 1376     if (is_array($ctype_param) && !empty($ctype_param['charset']))
4e17e6 1377       return decode_specialchars($body, $ctype_param['charset']);
T 1378
1379     return $body;
1380     }
1381
1382
1cded8 1383
4e17e6 1384   /* --------------------------------
T 1385    *         private methods
1386    * --------------------------------*/
1387
1388
1389   function _mod_mailbox($mbox, $mode='in')
1390     {
f619de 1391     if ((!empty($this->root_ns) && $this->root_ns == $mbox) || ($mbox == 'INBOX' && $mode == 'in'))
7902df 1392       return $mbox;
T 1393
f619de 1394     if (!empty($this->root_dir) && $mode=='in') 
520c36 1395       $mbox = $this->root_dir.$this->delimiter.$mbox;
7902df 1396     else if (strlen($this->root_dir) && $mode=='out') 
4e17e6 1397       $mbox = substr($mbox, strlen($this->root_dir)+1);
T 1398
1399     return $mbox;
1400     }
1401
1402
1403   // sort mailboxes first by default folders and then in alphabethical order
1404   function _sort_mailbox_list($a_folders)
1405     {
1406     $a_out = $a_defaults = array();
1407
1408     // find default folders and skip folders starting with '.'
1409     foreach($a_folders as $i => $folder)
1410       {
1411       if ($folder{0}=='.')
1412           continue;
1413           
1414       if (($p = array_search(strtolower($folder), $this->default_folders))!==FALSE)
1415           $a_defaults[$p] = $folder;
1416       else
1417         $a_out[] = $folder;
1418       }
1419
1420     sort($a_out);
1421     ksort($a_defaults);
1422     
1423     return array_merge($a_defaults, $a_out);
1424     }
1425
1426
1427   function _uid2id($uid, $mbox=NULL)
1428     {
1429     if (!$mbox)
1430       $mbox = $this->mailbox;
1431       
1432     if (!isset($this->uid_id_map[$mbox][$uid]))
1433       $this->uid_id_map[$mbox][$uid] = iil_C_UID2ID($this->conn, $mbox, $uid);
1434
1435     return $this->uid_id_map[$mbox][$uid];
1436     }
1437
1438
1cded8 1439   // parse string or array of server capabilities and put them in internal array
T 1440   function _parse_capability($caps)
1441     {
1442     if (!is_array($caps))
1443       $cap_arr = explode(' ', $caps);
1444     else
1445       $cap_arr = $caps;
1446     
1447     foreach ($cap_arr as $cap)
1448       {
1449       if ($cap=='CAPABILITY')
1450         continue;
1451
1452       if (strpos($cap, '=')>0)
1453         {
1454         list($key, $value) = explode('=', $cap);
1455         if (!is_array($this->capabilities[$key]))
1456           $this->capabilities[$key] = array();
1457           
1458         $this->capabilities[$key][] = $value;
1459         }
1460       else
1461         $this->capabilities[$cap] = TRUE;
1462       }
1463     }
1464
1465
4e17e6 1466   // subscribe/unsubscribe a list of mailboxes and update local cache
T 1467   function _change_subscription($a_mboxes, $mode)
1468     {
1469     $updated = FALSE;
1470     
1471     if (is_array($a_mboxes))
1472       foreach ($a_mboxes as $i => $mbox)
1473         {
1474         $mailbox = $this->_mod_mailbox($mbox);
1475         $a_mboxes[$i] = $mailbox;
1476
1477         if ($mode=='subscribe')
1478           $result = iil_C_Subscribe($this->conn, $mailbox);
1479         else if ($mode=='unsubscribe')
1480           $result = iil_C_UnSubscribe($this->conn, $mailbox);
1481
1482         if ($result>=0)
1483           $updated = TRUE;
1484         }
1485         
1486     // get cached mailbox list    
1487     if ($updated)
1488       {
1489       $a_mailbox_cache = $this->get_cache('mailboxes');
1490       if (!is_array($a_mailbox_cache))
1491         return $updated;
1492
1493       // modify cached list
1494       if ($mode=='subscribe')
1495         $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
1496       else if ($mode=='unsubscribe')
1497         $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
1498         
1499       // write mailboxlist to cache
1500       $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
1501       }
1502
1503     return $updated;
1504     }
1505
1506
1507   // increde/decrese messagecount for a specific mailbox
1508   function _set_messagecount($mbox, $mode, $increment)
1509     {
1510     $a_mailbox_cache = FALSE;
1511     $mailbox = $mbox ? $mbox : $this->mailbox;
1512     $mode = strtoupper($mode);
1513
1514     $a_mailbox_cache = $this->get_cache('messagecount');
1515     
1516     if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
1517       return FALSE;
1518     
1519     // add incremental value to messagecount
1520     $a_mailbox_cache[$mailbox][$mode] += $increment;
1521
1522     // write back to cache
1523     $this->update_cache('messagecount', $a_mailbox_cache);
1524     
1525     return TRUE;
1526     }
1527
1528
1529   // remove messagecount of a specific mailbox from cache
1530   function _clear_messagecount($mbox='')
1531     {
1532     $a_mailbox_cache = FALSE;
1533     $mailbox = $mbox ? $mbox : $this->mailbox;
1534
1535     $a_mailbox_cache = $this->get_cache('messagecount');
1536
1537     if (is_array($a_mailbox_cache[$mailbox]))
1538       {
1539       unset($a_mailbox_cache[$mailbox]);
1540       $this->update_cache('messagecount', $a_mailbox_cache);
1541       }
1542     }
1543
1544
1545   function _parse_address_list($str)
1546     {
1547     $a = $this->_explode_quoted_string(',', $str);
1548     $result = array();
1549
1550     foreach ($a as $key => $val)
1551       {
1552       $val = str_replace("\"<", "\" <", $val);
1553       $sub_a = $this->_explode_quoted_string(' ', $val);
1554       
1555       foreach ($sub_a as $k => $v)
1556         {
1557         if ((strpos($v, '@') > 0) && (strpos($v, '.') > 0)) 
1558           $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
1559         else
1560           $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
1561         }
1562         
1563       if (empty($result[$key]['name']))
1564         $result[$key]['name'] = $result[$key]['address'];
1565         
1566       $result[$key]['name'] = $this->decode_header($result[$key]['name']);
1567       }
1568     
1569     return $result;
1570     }
1571
1572
1573   function _explode_quoted_string($delimiter, $string)
1574     {
1575     $quotes = explode("\"", $string);
1576     foreach ($quotes as $key => $val)
1577       if (($key % 2) == 1)
1578         $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
1579         
1580     $string = implode("\"", $quotes);
1581
1582     $result = explode($delimiter, $string);
1583     foreach ($result as $key => $val) 
1584       $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
1585     
1586     return $result;
1587     }
1588   }
1589
1590
1591
1592
1593
1594 function quoted_printable_encode($input="", $line_max=76, $space_conv=false)
1595   {
1596   $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
1597   $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
1598   $eol = "\r\n";
1599   $escape = "=";
1600   $output = "";
1601
1602   while( list(, $line) = each($lines))
1603     {
1604     //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
1605     $linlen = strlen($line);
1606     $newline = "";
1607     for($i = 0; $i < $linlen; $i++)
1608       {
1609       $c = substr( $line, $i, 1 );
1610       $dec = ord( $c );
1611       if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
1612         {
1613         $c = "=2E";
1614         }
1615       if ( $dec == 32 )
1616         {
1617         if ( $i == ( $linlen - 1 ) ) // convert space at eol only
1618           {
1619           $c = "=20";
1620           }
1621         else if ( $space_conv )
1622           {
1623           $c = "=20";
1624           }
1625         }
1626       else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
1627         {
1628         $h2 = floor($dec/16);
1629         $h1 = floor($dec%16);
1630         $c = $escape.$hex["$h2"].$hex["$h1"];
1631         }
1632          
1633       if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
1634         {
1635         $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
1636         $newline = "";
1637         // check if newline first character will be point or not
1638         if ( $dec == 46 )
1639           {
1640           $c = "=2E";
1641           }
1642         }
1643       $newline .= $c;
1644       } // end of for
1645     $output .= $newline.$eol;
1646     } // end of while
1647
1648   return trim($output);
1649   }
1650
1651 ?>