Location: PHPKode > projects > CsWebmail > cswebmail-3.10/cswebmail-3.10/include/mail_protocols.php
<?
if(VALID_DOCUMENT != 1) die('what?');

define('DELETED_FLAG','\Deleted');
define('ANSWERED_FLAG','\Answered');
define('SEEN_FLAG','\Seen');

define(SORT_BY_DATE,'date');
define(SORT_BY_SIZE,'size');
define(SORT_BY_FROM,'from');
define(SORT_BY_TO,'to');
define(SORT_BY_SUBJECT,'subject');
define(SORT_BY_NUMBER,'number');

function _header_sort_by_date(&$h1,&$h2){
  return $h2['date'] - $h1['date'];
}
function _header_sort_by_size(&$h1,&$h2){
  return $h2['size'] - $h1['size'];
}
function _header_sort_by_subject(&$h1,&$h2){
  return strcmp($h1['subject'],$h2['subject']);
}
function _header_sort_by_from(&$h1,&$h2){
  return strcmp($h1['from'][0]['personal'].$h1['from'][0]['mailbox'].$h1['from'][0]['host'],
                $h2['from'][0]['personal'].$h2['from'][0]['mailbox'].$h2['from'][0]['host']);
}
function _header_sort_by_to(&$h1,&$h2){
  return strcmp($h1['to'][0]['personal'].$h1['to'][0]['mailbox'].$h1['to'][0]['host'],
                $h2['to'][0]['personal'].$h2['to'][0]['mailbox'].$h2['to'][0]['host']);
}
function mime_header_decode($str){
  $d = imap_string_decode(trim($str));
  return $d['decoded'];
}
function imap_string_decode($str){
  $res = $str;
  $chr='default';
  // hack against segmentation fault
  if(eregi("=[ ]?\?.+\?[ ]?=",$str) && eregi("[ ]",$str))
    $str = str_replace(' ','',$str);
  if(eregi("=\?[^ ]+\?=",$str)){
    $arr = imap_mime_header_decode($str);
    if(is_array($arr)){
      $res = '';
      foreach($arr as $a){
        $res .= $a->text;
        if($a->charset != 'default') $chr = $a->charset;
      }
    }
  }
  if($chr != 'default' && $chr != $GLOBALS['DEFAULT_CHARSET']){
    $res = to_default_charset($res,$chr);
    $res = str_replace("\x1a",'',$res);
  }
  return array('decoded' => chop($res),'charset' => $chr);
}

$__one_imap = false;
$__one_nntp = false;
function getIMAP(){
  global $__one_imap;
  if(!$__one_imap)
    $__one_imap = new IMAP($GLOBALS['MAIL_USER_NAME'],$GLOBALS['MAIL_USER_PASSWORD']);
  return $__one_imap;
}
function getNNTP(){
  global $__one_nntp;
  if(!$__one_nntp)
    $__one_nntp = new NNTP;
  return $__one_nntp;
}
function closeMP(){
  global $__one_imap,$__one_nntp;
  if($__one_imap)
    $__one_imap->close();
  $__one_imap=false;
  if($__one_nntp)
    $__one_nntp->close();
  $__one_nntp=false;
}

//////////////////////////////////////
// Knows to parse RFC-822 mails
/////////////////////////////////////
class MailParser{
  function MailParser(){ }

  // separate header and body
  function separate_header_body($whole_mail){
    $NL = "\r\n";
    $nl_pos = strpos($whole_mail,$NL.$NL);
    if($nl_pos === false){
      $NL = "\n";
      $nl_pos = strpos($whole_mail,$NL.$NL);
    }
    if($nl_pos > 0){
      $_header = substr($whole_mail,0,$nl_pos);
      $_body = substr($whole_mail,$nl_pos+strlen($NL));
    }
    else{
      $_header = $whole_mail;
      $_body = '';
    }
    return array($_header,$_body);
  }

  // parse whole mail
  // if not in mail format, returns false
  function parse_mail($whole_mail){
    list($_header,$_body) = $this->separate_header_body($whole_mail);
    $header = $this->parse_header($_header);
    if(array_not_empty($header)){
      return $this->parse_mail_body($header,$_body);
    }
    else
      return false;
  }

  // getting header value
  function get_header_value($header,&$line,&$res){
    $p = stripos($line,$header.':');
    if($p === 0){
      $res = substr($line,strlen($header)+1);
      return true;
    }
    return false;
  }

  // parse header
  function parse_header($_header){
    $lines = explode("\n",$_header);
    $header = array();
    $last = false;
    $res = null;
    foreach($lines as $line){
      if($this->get_header_value('Date',$line,$res)){
        $res = trim($res);
        $header['date'] = strtotime($res);
        // workaound for baggy dates , like 'Mon, 26 Jun 2006 22:57:49 0800' (without +/- in timezone)
        if($header['date'] <= 0 && preg_match('/[^+-]\d{2,4}$/',$res)){
          $header['date'] = strtotime(preg_replace('/(\d+)$/','+$1',$res));
        }
        else if($header['date'] <= 0 && preg_match('/UT$/',$res)){
          $header['date'] = strtotime(preg_replace('/UT$/','',$res));
        }
        $last = 'date';
      }
      else if($this->get_header_value('Content-Type',$line,$res)){
        $last = 'content-type';
        $header[$last] = $line;
      }
      else if($this->get_header_value('Content-Id',$line,$res)){
        $last = 'content-id';
        $header[$last] = $res;
      }
      else if($this->get_header_value('Content-Transfer-Encoding',$line,$res)){
        $last = 'encoding';
        $header[$last] = strtolower($res);
      }
      else if($this->get_header_value('Content-Disposition',$line,$res)){
        $last = 'content-disposition';
        $header[$last] = trim($res);
      }
      else if($this->get_header_value('Subject',$line,$res)){
        $last = 'subject';
        $header[$last] = ltrim($res);
      }
      else if($this->get_header_value('From',$line,$res)){
        $last = 'fromaddress';
        $header[$last] = ltrim($res);
      }
      else if($this->get_header_value('To',$line,$res)){
        $last = 'toaddress';
        $header[$last] = ltrim($res);
      }
      else if($this->get_header_value('Cc',$line,$res)){
        $last = 'ccaddress';
        $header[$last] = ltrim($res);
      }
      else if($this->get_header_value('Bcc',$line,$res)){
        $last = 'bccaddress';
        $header[$last] = ltrim($res);
      }
      else if($this->get_header_value('Newsgroups',$line,$res)){
        $last = 'newsgroups';
        $header[$last] = ltrim($res);
      }
      else if($this->get_header_value('Disposition-Notification-To',$line,$res)){
        $last = 'notification';
        $header[$last] = ltrim($res);
      }
      else if($this->get_header_value('Message-Id',$line,$res)){
        $last = 'message-id';
        $header[$last] = ltrim($res);
      }
      else if($this->get_header_value('In-Reply-To',$line,$res)){
        $last = 'in-reply-to';
        $header[$last] = ltrim($res);
      }
      else if($this->get_header_value('Priority',$line,$res)){
        $last = 'priority';
        $header[$last] = ltrim($res);
      }
      else if(strpos($line," ") === 0 || strpos($line,"\t") === 0){
        if($last)
          $header[$last] .= ltrim($line);
      }
      else
        $last = false;
    }
    $header['all'] = $_header;
    if(isset($header['toaddress']))
      $header['to'] = $this->parse_emails($header['toaddress']);
    if(isset($header['fromaddress']))
      $header['from'] = $this->parse_emails($header['fromaddress']);
    if(isset($header['ccaddress']))
      $header['cc'] = $this->parse_emails($header['ccaddress']);
    if(isset($header['bccaddress']))
      $header['bcc'] = $this->parse_emails($header['bccaddress']);
    if(isset($header['newsgroups']))
      $header['newsgroups'] = $header['newsgroups'];
    if(isset($header['content-type']))
      $header['content-type'] = $this->parse_content_type($header['content-type']);
    if(isset($header['content-id']))
      $header['content-id'] = trim(eregi_replace('[<>]','',$header['content-id']));
    if(isset($header['content-disposition']))
      $header['content-disposition'] = $this->parse_content_disposition($header['content-disposition']);
    if(preg_match('/<([^>]+)>/',$header['message-id'],$m))
      $header['message-id'] = trim($m[1]);
    if(preg_match('/<([^>]+)>/',$header['in-reply-to'],$m))
      $header['in-reply-to'] = trim($m[1]);
    if(isset($header['priority']))
      $header['priority'] = trim($header['priority']);
    
    if(isset($header['subject'])){
      $arr = imap_string_decode($header['subject']);
      $header['subject-decoded'] = $arr['decoded'];
      $header['subject-charset'] = $arr['charset'];
    }
    // if anly 'all' index defined
    if(count($header) <= 1) $header = array();
    
    return $header;
  }

  // parse mail body
  function parse_mail_body($header,$_mail){
    if(count($header) <= 0) return array();
    
    $mail = array('header'=>$header);
    
    // mutipart
    if(isset($header['content-type']['boundary']) &&
       $header['content-type']['boundary'] != ''){

      $boundary = $header['content-type']['boundary'];
      
      // will be prepared
      $_prepared_mail = $_mail;
      // solve non-newline startings
      if($_prepared_mail[0] != "\r" && $_prepared_mail[0] != "\n")
        $_prepared_mail = "\r\n".$_prepared_mail;
      
      // array of parts strings
      $_parts = array();
      
      //devide to parts
      if(strpos($_prepared_mail,"\r\n--".$boundary."\r\n") !== false){
        // replace boundary end to nothing
        $_prepared_mail = str_replace("--".$boundary."--\r\n",'',$_prepared_mail);
        $_parts = explode("\r\n--".$boundary."\r\n",$_prepared_mail);
      }
      else if(strpos($_prepared_mail,"\n--".$boundary."\n") !== false){
        // replace boundary end to nothing
        $_prepared_mail = str_replace("--".$boundary."--\n",'',$_prepared_mail);
        $_parts = explode("\n--".$boundary."\n",$_prepared_mail);
      }
      else{
        debug($_prepared_mail);
        debug("BAD BOUNDARY: ".$boundary);
      }
      
      // aggregate parts
      $parts = array();
      $part_index = 1;
      $was_first = false;
      $parts = array();
      foreach($_parts as $_part){
        // first is body
        if(!$was_first){
          $mail['body'] = $_part;
          $was_first = true;
        }
        else{
          // recursive parsing
          list($_part_header,$_part_mail) = $this->separate_header_body($_part);
          $part_header = $this->parse_header($_part_header);
          // if rfc-822 header: encapsulated email
          if($part_header['content-type']['mime-type'] === 'message/rfc822'){
            list($_part_header2,$_part_mail2) = $this->separate_header_body($_part_mail);
            $part_header2 = $this->parse_header($_part_header2);
            // save headers as differetnt part
            $part_header['content-disposition']['attachment'] = true;
            $part_header['content-disposition']['filename'] = 'RFC-822 Message Headers';
            $parts[$part_index++] = array('header'=>$part_header,'body'=>$_part_header2);
            // parse recursive
            $parts[$part_index++] = $this->parse_mail_body($part_header2,$_part_mail2);
          }
          else
            $parts[$part_index++] = $this->parse_mail_body($part_header,$_part_mail);
        }
      }
      if(array_not_empty($parts))
        $mail['parts'] = $parts;
    }
    else
      $mail['body'] = $_mail;

    // take source
    $mail['source'] = $header['all']."\r\n".$_mail;
    
    return $mail;
  }

  // parse emails
  function parse_emails($emails_str){
    $emails = array();
    $emails_str = preg_replace('/"([^"]+)"/e',
                               "'\"'.str_replace(',',' ','$1').'\"'",
                               $emails_str);
    $ems = preg_split('/,/',$emails_str);
    // parsing for weird case with , in personal
    // like: "me,myself" <hide@address.com>
    $new_ems = array();
    $miss_one = false;
    foreach($ems as $e){
      if($miss_one){
        $take .=  $e;
        $miss_one = false;
        $new_ems[] = $take;
      }
      else if(!preg_match('/".*"/',$e) && eregi('"',$e)){
        $take = $e;
        $miss_one = true;
      }
      else
        $new_ems[] = $e;
    }
    foreach($new_ems as $e){
      $e = trim($e);
      if($e && $e != ''){
        $email = array();
        if(preg_match('/"([^"]+)"\s*<(.*)@(.*)>/',$e,$m) ||
           preg_match('/(.*)\s*<(.*)@(.*)>/',$e,$m)){
          $email['personal'] = trim($m[1]);
          $email['mailbox'] = trim($m[2]);
          $email['host'] = trim($m[3]);
        }
        else if(preg_match('/"([^"]*)"\s*<(.*)>/',$e,$m) ||
                preg_match('/(.*)\s*<(.*)>/',$e,$m)){
          $email['personal'] = trim($m[1]);
          $email['mailbox'] = trim($m[2]);
          $email['host'] = 'UNKNOWN_HOST';
        }
        else if(preg_match("/<(.*)@(.*)>/",$e,$m)){
          $email['mailbox'] = trim($m[1]);
          $email['host'] = trim($m[2]);
        }
        else if(preg_match("/(.*)@(.*)/",$e,$m)){
          $email['mailbox'] = trim($m[1]);
          $email['host'] = trim($m[2]);
        }
        else{
          $email['personal'] = $e;
          $email['mailbox'] = '';//'UNKNOWN_MAILBOX';
          $email['host'] = '';//'UNKNOWN_HOST';
        }
        if($email['personal']){
          $arr = imap_string_decode($email['personal']);
          $email['personal-decoded'] = $arr['decoded'];
          $email['personal-charset'] = $arr['charset'];
        }
        $email['email'] = $email['mailbox'].'@'.$email['host'];
        $emails[] = $email;
      }
    }
    return $emails;
  }
  // parse content-type
  function parse_content_type($ct){
    $res = array();
    
    if(preg_match('/^Content-Type:\s*([^;]+)\/([^;]+);?/i',$ct,$m)){
      // mime type
      $res['mime-type'] = strtolower(trim($m[1])).'/'.strtolower(trim($m[2]));
      // charset
      if(preg_match('/charset="?([^";]*)"?/i',$ct,$m))
        $res['charset'] = strtolower(trim($m[1]));
      // name
      if(preg_match('/name="?([^";]*)"?/i',$ct,$m))
        $res['name'] = trim($m[1]);
      // boundary
      if(preg_match('/boundary="?([^";]*)"?/i',$ct,$m))
        $res['boundary'] = trim($m[1]);
    }
    else if(preg_match('/Content-Type:\s*(.*)\/(.*)/i',$ct,$m))
      $res['mime-type'] = strtolower(trim($m[1])).'/'.strtolower(trim($m[2]));
    
    return $res;
  }
  // parse content-disposition
  function parse_content_disposition($cd){
    $res = array();
    // if attachment
    if(preg_match('/attachment;?/i',$cd,$m))
      $res['attachment'] = true;
    // filename
    if(preg_match('/filename="?([^"]*)"?;?/i',$cd,$m))
      $res['filename'] = $m[1];
    
    return $res;
  }
}

//////////////////////////////////////
// Abstract Mail Protocol, knows to parse
// RFC-822 mails
/////////////////////////////////////
class MailProtocol{
  var $sock;
  var $parser;
  function MailProtocol($host,$port){
    $this->sock = @fsockopen($host,$port,$errno,$errstr);
    if(!$this->sock){
      throw new Exception("Connection to [$host:$port] failed.<br/>[$errno] $errstr");
    }
    $this->parser = new MailParser();
  }
  //
  // must be implemented
  //
  function all_headers($name){
    return array();
  }
  function range_headers($name,$from,$count){
    return array();
  }
  function get_header($name,$key){
    return array();
  }
  function get_mail($name,$key){
    return array();
  }
  function listing($wildcard){
    return array();
  }
  function status(){
    return array();
  }
  // returns sorted headers
  function sorted_headers($mbox,$sort_order=SORT_BY_NUMBER,$from=-1,$count=-1){
    if($from > 0 && $count >= 0){
      $headers = $this->range_headers($mbox,$from,$count);
    }
    else{
      $headers = $this->all_headers($mbox);
    }
    $func = false;
    if($sort_order == SORT_BY_DATE)
      $func = '_header_sort_by_date';
    else if($sort_order == SORT_BY_SIZE)
      $func = '_header_sort_by_size';
    else if($sort_order == SORT_BY_SUBJECT)
      $func = '_header_sort_by_subject';
    else if($sort_order == SORT_BY_FROM)
      $func = '_header_sort_by_from';
    else if($sort_order == SORT_BY_TO)
      $func = '_header_sort_by_to';
    
    if($func)
      usort($headers,$func);
    else
      $headers = array_reverse($headers);
    
    return $headers;
  }
  
  // parse header
  function parse_header($_header){
    return $this->parser->parse_header($_header);
  }
  // parse mail body
  function parse_mail_body($header,$_mail){
    return $this->parser->parse_mail_body($header,$_mail);
  }

  function close(){
    fclose($this->sock);
  }
}
/////////////////////////////////
// IMAP protocol implementation
////////////////////////////////
class IMAP extends MailProtocol{
  var $num;
  function IMAP($user,$password){
    parent::MailProtocol(IMAPHOST,IMAPPORT);
    $this->num = 0;
    $this->login($user,$password);
  }
  function close(){
    $this->logout();
    parent::close();
  }
  function login($user,$password){
    // $password = str_replace('\\','\\\\',str_replace('"','\"',$password));
    $password = str_replace('"','\"',str_replace('\\','\\\\',$password));
    $data = $this->exec('LOGIN '.$user.' "'.$password.'"');
    if(eregi('^'.$data['id'].' (no|bad)',$data['last'])){
      throw_error('login error',
                  "$user's password don't match.",
                  $data['last']/*."\n".'LOGIN '.$user.' "'.$password.'"'*/);
    }
  }
  function logout(){
    $data = $this->exec('LOGOUT');
    if(eregi('^'.$data['id'].' (no|bad)',$data['last'])){
      throw new Exception('Logout error<br/>'.$data['last']);
    }
  }
  // list of mailboxes
  function listing($wildcard){
    $data = $this->exec("LIST \"\" \"$wildcard\"");
    if(eregi('^'.$data['id'].' (no|bad)',$data['last'])){
      throw new Exception('IMAP::listing '."can't open list for $wildcard.<br/>\n".$data['last']);
    }
    $lines = preg_split("/\n/",$data['data']);
    $mboxes = array();
    foreach($lines as $line){
      if(eregi("^\* list \((.*)\) \".*\" (.*)",$line,$m))
        if(!eregi('\\NoSelect',$m[1])){
          $mb = chop($m[2]);
          if(eregi("\"(.*)\"",$mb,$m))
            $mb = $m[1];
          // special file cases
          if(!preg_match('/\.msf$/',$mb))
            $mboxes[] = $mb;
        }
    }
    return $mboxes;
  }
  // status of mailbox
  function status($mbox){
    $data = $this->exec("STATUS \"$mbox\" (MESSAGES RECENT UNSEEN)");
    if(preg_match('/^'.$data['id'].' (no|bad)/i',$data['last'])){
      throw new Exception("IMAP::status <b>$mbox</b> isn't a valid mbox file.");
      return null;
    }
    $status = array();
    if(preg_match('/\* .* \(messages ([0-9]*) recent ([0-9]*) unseen ([0-9]*)\)/i',$data['data'],$m)){
      $status['messages'] = $m[1];
      $status['recent'] = $m[2];
      $status['unseen'] = $m[3];
    }
    return $status;
  }
  // select mbox and get information
  function select($mbox){
    $data = $this->exec("SELECT \"$mbox\"");
    if(eregi('^'.$data['id'].' no',$data['last'])){
      throw new Exception('IMAP::select '."can't open $mbox.<br/>".$data['last']);
    }
    $num = 0;
    if(eregi("\* ([0-9]*) exists",$data['data'],$m)){
      $num = $m[1];
    }
    return $num;
  }
  // create new mbox
  function create($mbox){
    $data = $this->exec("CREATE \"$mbox\"");
    
    if(eregi('^'.$data['id'].' (no|bad)',$data['last'])){
      throw new Exception("IMAP::create : can't create ".$mbox."<br/>\n".
                          $data['last']);
    }
  }
  // delete mbox
  function delete($mbox){
    $data = $this->exec("DELETE \"$mbox\"");
    if(eregi('^'.$data['id'].' (no|bad)',$data['last'])){
      return false;
      // throw new Exception('IMAP::delete '."can't delete $mbox.<br/>\n".$data['last']);
    }
    return true;
  }
  // rename mbox
  function rename($mbox,$newname){
    $data = $this->exec("RENAME \"$mbox\" \"$newname\"");
    if(eregi('^'.$data['id'].' (no|bad)',$data['last'])){
      throw new Exception('IMAP::rename '."can't rename $mbox to $name.<br/>\n".$data['last']);
    }
  }
  // expunge
  function expunge($mbox){
    $data = $this->exec("EXPUNGE");
    if(eregi('^'.$data['id'].' (no|bad)',$data['last'])){
      throw new Exception('IMAP::expunge '."can't expunge $mbox.<br/>\n".$data['last']);
    }
  }
  // set flag
  function store_flag($mbox,$key,$flag,$set){
    $data = $this->exec("STORE $key ".($set?'+':'-')."FLAGS ($flag)");
    if(eregi('^'.$data['id'].' (no|bad)',$data['last'])){
      throw new Exception('IMAP::store_flag '."can't set flags to $mbox:$key.<br/>".$data['data']);
    }
  }
  function set_flag($mbox,$key,$flag){
    $this->select($mbox);
    $this->store_flag($mbox,$key,$flag,true);
  }
  // delete messages
  function delete_messages($mbox,$keys){
    if(!is_array($keys) && !is_numeric($keys))
      return;
    if(is_numeric($keys))
      $keys = array($keys);
    
    $this->select($mbox);
    foreach($keys as $k){
      $this->store_flag($mbox,$k,DELETED_FLAG,true);
    }
    $this->expunge($mbox);
    
  }
  // move messages
  function move_messages($mbox,$newmbox,$keys){
    if(!is_array($keys) && !is_numeric($keys))
      return;
    if(is_numeric($keys))
      $keys = array($keys);
    
    $this->select($mbox);
    foreach($keys as $k){
      $data = $this->exec("COPY $k \"$newmbox\"");
      if(eregi('^'.$data['id'].' (no|bad)',$data['last'])){
        throw new Exception("IMAP::move_messages can't move $mbox:$k to $newmbox.<br/>".$data['last']);
      }
      $this->store_flag($mbox,$k,DELETED_FLAG,true);
    }
    $this->expunge($mbox);
    
  }
  // copy messages
  function copy_messages($mbox,$newmbox,$keys){
    if(!is_array($keys) && !is_numeric($keys))
      return;
    if(is_numeric($keys))
      $keys = array($keys);
    
    $this->select($mbox);
    foreach($keys as $k){
      $this->store_flag($mbox,$k,DELETED_FLAG,false);
      $data = $this->exec("COPY $k \"$newmbox\"");
      if(eregi('^'.$data['id'].' (no|bad)',$data['last'])){
        throw new Exception("IMAP::copy_messages can't copy $mbox:$k to $newmbox.<br/>".$data['last']);
      }
    }
  }
  // append
  function append($mbox,$message){
    // $message = preg_replace("/([^\\r])\\n/","\\1\r\n",$message);
    $message = str_replace("\r",'',$message);
    $len = strlen($message);

    $id = $this->make_next_id();
    // write command
    fputs($this->sock,$id.' APPEND "'.$mbox.'" {'.$len.'}'."\n");

    // read feedback
    $s = fgets($this->sock,2048);
    if(eregi("^$id (no|bad)",$s)){
      throw new Exception('IMAP::append '."can't append to $mbox.<br/>".$s);
    }
    // write message
    $bytes = fputs($this->sock,$message);
    fputs($this->sock,"\n");    
    // read feedback
    while($s = fgets($this->sock,2048)){
      if(eregi("^$id ",$s))
        break;
    }
  }
  // fetch headers
  // optimized !!!
  function fetch_headers($from,$to=-1){
    if($to < 0) $to = $from;
    // next id
    $id = $this->make_next_id();
    // write comamnd
    fputs($this->sock,"$id FETCH $from:$to (FLAGS INTERNALDATE RFC822.SIZE BODY.PEEK[HEADER])\n");
    $s = fgets($this->sock,2048);
    if(eregi("^$id (no|bad)",$s)){
      throw new Exception("IMAP::fetch_headers can't fetch $from:$to.<br/>".$s);
    }
    $headers = array();
    $_header = ''; $header = false; $size = 0; $readen = 0;
    do{
      $chop = chop($s);
      if(strpos($chop,$id.' ')===0)
        break;
      else if(preg_match('/^\* ([0-9]*) fetch \(flags \((.*)\) internaldate "(.*)" rfc822\.size ([0-9]*) body\[header\] \{([0-9]*)\}/i',$chop,$m)){
        $_header = '';
        $header = array();
        $header['key'] = $m[1];
        $flags = $m[2];
        if(strpos($flags,"\\Seen")!==false)
          $header['seen'] = true;
        else
          $header['unseen'] = true;
        
        if(strpos($flags,"\\Recent")!==false)
          $header['recent'] = true;
        if(strpos($flags,"\\Deleted")!==false)
          $header['deleted'] = true;
        if(strpos($flags,"\\Answered")!==false)
          $header['answered'] = true;
        $header['flags'] =  split_quoted_string($flags);
        
        $header['date'] = $m[3];
        $header['size'] = $m[4];
        $size = $m[5];
        $readen = 0;
      }
      else if($readen < $size){
        $_header .= $s;
        $readen += strlen($s);
      }
      if($readen >= $size){
        // trim to size
        $_header = substr($_header,0,$size);
        $headers[] = array_merge($header,$this->parse_header($_header));
        $_header = '';
        $readen = 0;
      }

    }while($s = fgets($this->sock,2048));
    return $headers;
  }
  function get_mail($mbox,$key){
    $header = $this->get_header($mbox,$key);
    if(count($header) == 0)
      return array();
    // next id
    $id = $this->make_next_id();
    // write comamnd
    fputs($this->sock,"$id FETCH $key BODY[TEXT]\n");
    $s = fgets($this->sock,2048);
    if(eregi("^$id (no|bad)",$s)){
      throw new Exception("IMAP::get_mail can't fetch $mbox:$key body.<br/>".$s);
    }
    $size = false;
    if(eregi("^\* [0-9]* FETCH .* {([0-9]*)}",$s,$m)){
      $size = $m[1];
    }
    else if(!$size){
      print_obj(htmlspecialchars($s));
    }

    $_mail = '';
    $readen = 0;
    while($s = fgets($this->sock,2048)){
      $chop = chop($s);
      if(eregi("^$id ",$chop))
        break;
      else if($size === false || $readen < $size){
        $_mail .= $s;
        $readen += strlen($s);
      }
    }
    // delete unnecessary chairs , mostly ')' char at the last line
    if($size){
      $_mail = substr($_mail,0,$size);
    }
    return $this->parse_mail_body($header,$_mail);
  }
  function all_headers($mbox){
    $num = $this->select($mbox);
    if($num <= 0)
      return array();
    return $this->fetch_headers(1,$num);
  }
  function range_headers($mbox,$from,$count){
    $num = $this->select($mbox);
    if($num < 0) return array();
    print $from.','.($from+$count);
    return $this->fetch_headers($from,$from+$count);
  }
  function sorted_headers($mbox,$sort_order=SORT_BY_NUMBER,$from=-1,$count=-1){
    $num = $this->select($mbox);
    if($num <= 0) return array();
    if($from < 0 && $count < 0){
      $from = 1;
      $count = $num-1;
    }
    $func = false;
    if($sort_order == SORT_BY_DATE)
      $func = 'DATE';
    else if($sort_order == SORT_BY_SIZE)
      $func = 'SIZE';
    else if($sort_order == SORT_BY_SUBJECT)
      $func = 'REVERSE SUBJECT';
    else if($sort_order == SORT_BY_FROM)
      $func = 'REVERSE FROM';
    else if($sort_order == SORT_BY_TO)
      $func = 'TO';
    
    if($func){
      // print "SORT ($func) UTF-8 ALL\n";
      $data = $this->exec("SORT ($func) UTF-8 ALL");
      if(eregi('^'.$data['id'].' (no|bad)',$data['last']))
        throw new Exception("IMAP::sort sort failed<br/>".$data['last']);
      $keys = preg_split("/ /",trim(substr($data['data'],
                                           strpos($data['data'],'SORT')+4)));
      
      //print "$from $count <br/>";
      //print implode(" ",$keys)."<br/>";
      if($from > 0 && $count >= 0)
        $keys = array_slice($keys,$from-1,$count+1);
      $keys = array_reverse($keys);
      // print implode(" ",$keys)."<br/>";
      $headers = array();
      foreach($keys as $k){
        $tmp = $this->fetch_headers($k);
        $headers[] = array_pop($tmp);
      }
    }
    else{
      //print "$from $count <br/>";
      return array_reverse($this->fetch_headers($from,$from+$count));
    }
    return $headers;
  }
  function threads_build_tree(&$thread_syms){
    $res = array();
    while($val = current($thread_syms)){
      if($val == '('){
        next($thread_syms);
        $ret = $this->threads_build_tree($thread_syms);
        if(is_array($ret) && count($ret)==1)
          $res[] = $ret[0];
        else
          $res[] = $ret;
      }
      else if($val == ')'){
        next($thread_syms);
        break;
      }
      else{
        next($thread_syms);
        $res[] = $val;
      }
    }
    return $res;
  }
  function threads_tree($thread_list){
    $thread_temp = preg_split("//", $thread_list, -1, PREG_SPLIT_NO_EMPTY);
    $thread_temp_count = count($thread_temp);
    $thread_syms = array();
    for($i=0;$i<$thread_temp_count;$i++){
      if($thread_temp[$i] == '(' || $thread_temp[$i] == ')'){
        $thread_syms[] = $thread_temp[$i];
      }
      else if(preg_match('/[0-9]/',$thread_temp[$i])){
        $digit = '';
        do{
          $digit .= $thread_temp[$i];
          $i++;
        }while($i<$thread_temp_count && preg_match('/[0-9]/',$thread_temp[$i]));
        
        $thread_syms[] = $digit;
        $i--;
      }
    }
    reset($thread_syms);
    return $this->threads_build_tree($thread_syms);
  }
  function parse_threads($thread_list){
    $threads_tree = $this->threads_tree($thread_list);
    $thread_temp = preg_split("//", $thread_list, -1, PREG_SPLIT_NO_EMPTY);
    $char_count = count($thread_temp);
    $counter = 0;
    $thread_new = array();
    $k = 0;
    $thread_new[0] = "";
    for($i=0;$i<$char_count;$i++) {
      if ($thread_temp[$i] != ')' && $thread_temp[$i] != '(') {
        $thread_new[$k] .= $thread_temp[$i];
      }
      else if($thread_temp[$i] == '(') {
        // $thread_new[$k] .= $thread_temp[$i];
        $counter++;
      }
      else if($thread_temp[$i] == ')') {
        if ($counter > 1) {
          // $thread_new[$k] .= $thread_temp[$i];
          $thread_new[$k] .= ' ';
          $counter--;
        }
        else{
          // $thread_new[$k] .= $thread_temp[$i];
          $k++;
          $thread_new[$k] = "";
          $counter--;
        }
      }
    }
    $thread_new = array_reverse($thread_new);
    $thread_list = implode(" ", $thread_new);
    $thread_list = str_replace("(", " ", $thread_list);
    $thread_list = str_replace(")", " ", $thread_list);
    $thread_list = preg_split("/\s/", $thread_list, -1, PREG_SPLIT_NO_EMPTY);
    
    $threads = array();
    $threads_count = array();
    foreach($thread_new as $s){
      $s = trim($s);
      if($s != ''){
        $thread = array_reverse(preg_split('/\s/',$s,-1,PREG_SPLIT_NO_EMPTY));
        $threads[] = $thread;
        $threads_count[] = count($thread);
      }
    }
    // if(is_test_user())
      // print_obj($threads);
    return array('threads'=>$threads,'count'=>$threads_count,'tree'=>$threads_tree);
  }
  function get_threads($mbox){
    $num = $this->select($mbox);
    if($num > 0){
      // REFERENCES or ORDEREDSUBJECT
      $data = $this->exec("THREAD REFERENCES UTF-8 ALL");
      if(eregi('^'.$data['id'].' (no|bad)',$data['last']))
        throw new Exception("IMAP::thread thread failed<br/>".$data['last']);
      $thread_list = trim(preg_replace('/\* THREAD /','',$data['data']));
      return $this->parse_threads($thread_list);
    }
  }
  function get_header($mbox,$key){
    $num = $this->select($mbox);
    if($key > $num || $key < 1)
      return null;
    $headers = $this->fetch_headers($key);
    foreach($headers as $h)
      return $h;
  }
  function search_label($label,$boxes){
    $all_keys = array();
    if(is_array($boxes)){
      foreach($boxes as $mbox){
        $num = $this->select($mbox);
        if($num > 0){
          
          $data = $this->exec("SEARCH KEYWORD \"$label\"");
          if(eregi('^'.$data['id'].' (no|bad)',$data['last']))
            throw new Exception("IMAP::search search failed<br/>".$data['last']);
          $keys = preg_split("/ /",trim(substr($data['data'],
                                               strpos($data['data'],'SEARCH')+7)));
          $keys = array_diff(array_unique($keys),array(''));
          if(count($keys)>0){
            foreach($keys as $k){
              $k_headers = $this->fetch_headers($k);
              $all_keys[$mbox][] = array_pop($k_headers);
            }
          }
        }
      }
    }
    return  $all_keys;
  }
  function search($query,$type,$subject,$email,$body,$boxes){
    if(!eregi('and',$type) && !eregi('or',$type)) $type = 'AND';
    $query = trim($query);
    if($query == '')
      return false;
    
    $words = array();
    if(eregi('"',$query,$m)){
      $chars = preg_split('//',$query);$chars_count=count($chars);
      $words = array();
      $word = false;$in_quote=false;
      for($i=0;$i<$chars_count;$i++){
        if($chars[$i] == '"'){
          if($in_quote){
            $in_quote = false; $words[] = $word;$word = false;
          }
          else{
            if($word) $words[] = $word;
            $in_quote = true;
          }
          continue;
        }
        else if($in_quote) $word .= $chars[$i];
        else if($chars[$i] != ' ') $word .= $chars[$i];
        else if($word){
          $words[] = $word; $word = false;
        }
      }
      if($word) $words[] = $word;
    }
    else{
      $words = array_diff(array_unique(preg_split("/ /",$query)),array(''));
    }
    
    $all_keys = array();
    if(is_array($boxes)){
      foreach($boxes as $mbox){
        $num = $this->select($mbox);
        if($num > 0){
          $res_keys = false;
          foreach($words as $w){
            $keys = array();
            
            $w = str_replace('"','\"',trim($w));
            $data = $this->exec("SEARCH FROM \"$w\"");
            if(eregi('^'.$data['id'].' (no|bad)',$data['last']))
              throw new Exception("IMAP::search search failed<br/>".$data['last']);
            $keys = array_merge($keys,preg_split("/ /",trim(substr($data['data'],
                                                                   strpos($data['data'],'SEARCH')+7))));
            
            $data = $this->exec("SEARCH TO \"$w\"");
            if(eregi('^'.$data['id'].' (no|bad)',$data['last']))
              throw new Exception("IMAP::search search failed<br/>".$data['last']);
            $keys = array_merge($keys,preg_split("/ /",trim(substr($data['data'],
                                                                   strpos($data['data'],'SEARCH')+7))));
            
            $data = $this->exec("SEARCH CC \"$w\"");
            if(eregi('^'.$data['id'].' (no|bad)',$data['last']))
              throw new Exception("IMAP::search search failed<br/>".$data['last']);
            $keys = array_merge($keys,preg_split("/ /",trim(substr($data['data'],
                                                                   strpos($data['data'],'SEARCH')+7))));
            
            if($subject){
              $data = $this->exec("SEARCH SUBJECT \"$w\"");
              if(eregi('^'.$data['id'].' (no|bad)',$data['last']))
                throw new Exception("IMAP::search search failed<br/>".$data['last']);
              $keys = array_merge($keys,preg_split("/ /",trim(substr($data['data'],
                                                                     strpos($data['data'],'SEARCH')+7))));
            }
            
            if($body){
              $data = $this->exec("SEARCH BODY \"$w\"");
              if(eregi('^'.$data['id'].' (no|bad)',$data['last']))
                throw new Exception("IMAP::search search failed<br/>".$data['last']);
              $keys = array_merge($keys,preg_split("/ /",trim(substr($data['data'],
                                                                     strpos($data['data'],'SEARCH')+7))));
            }
            if(!$res_keys){
              $res_keys = $keys;
            }
            else if(eregi('and',$type))
              $res_keys = array_intersect($res_keys,$keys);
            else
              $res_keys = array_merge($res_keys,$keys);
          }
          
          $res_keys = array_diff(array_unique($res_keys),array(''));
          if(count($res_keys)>0){
            foreach($res_keys as $k){
              $tmp_hdrs = $this->fetch_headers($k);
              $all_keys[$mbox][] = array_pop($tmp_hdrs);
            }
          }
        }
      }
    }
    return  $all_keys;
  }
  // execute imap command
  function exec($command){
    // make id
    $id = $this->make_next_id();
    // write comamnd
    fputs($this->sock,"$id $command\n");
    // take output
    $data['id'] = $id;
    $data['data'] = '';
    while($s = fgets($this->sock,4096)){
      if(eregi("^$id ",$s)){
        $data['last'] = $s;
        break;
      }
      else
        $data['data'] .= $s;
    }
    return $data;
  }
  function make_next_id(){
    return sprintf('A%04d',($this->num++));
  }
}
///////////////////////////////////
// NNTP protocol implementation
//////////////////////////////////
class NNTP extends MailProtocol{
  function NNTP(){
    parent::MailProtocol(NNTPHOST,NNTPPORT);
    // read firts welcome line
    $s = fgets($this->sock,2048);
  }
  function listing($wildcard){
    fputs($this->sock,"LIST ACTIVE *".$wildcard."*\n");
    $s = fgets($this->sock,2048);
    if(eregi('^[45][0-9]{2} ',$s)){
      throw new Exception("NNTP::listing can't open list<br/>".$s);
    }
    $news = array();
    while($s = fgets($this->sock,2048)){
      $s = trim($s);
      if(eregi("^\.$",$s))
        break;
      else if(eregi('^([a-z0-9\.\-]*)',$s,$m))
        $news[] = trim($m[1]);
    }
    return $news;
  }
  function status($newsgroup){
    fputs($this->sock,"GROUP $newsgroup\n");
    $s = fgets($this->sock,2048);
    if(eregi('^[45][0-9]{2} ',$s)){
      throw new Exception("NNTP::status can't fetch #$newsgroup status<br/>".$s);
    }
    $status = array();
    if(eregi('[0-9]* ([0-9]*) ([0-9]*) ([0-9]*)',$s,$m)){
      $status['messages'] = $m[1];
      $status['unseen'] = 0;
    }
    return $status;
  }
  function all_headers($newsgroup){
    fputs($this->sock,"LISTGROUP $newsgroup\n");
    $s = fgets($this->sock,2048);
    if(eregi('^[45][0-9]{2} ',$s)){
      throw new Exception("NNTP::all_headers can't fetch #$newsgroup headers<br/>".$s);
    }
    $mnums = array();
    while($s = chop(fgets($this->sock,2048))){
      if(eregi("^\.$",$s))
        break;
      else if(eregi('([0-9]*)',$s,$m)){
        $mnums[] = $m[1];
      }
    }
    $headers = array();
    foreach($mnums as $n)
      $headers[] = $this->fetch_header($newsgroup,$n);
    return $headers;
  }
  function get_header($newsgroup,$key){
    $status = $this->status($newsgroup);
    if($status['messages'] > 0)
      return $this->fetch_header($newsgroup,$key);
    return array();
  }
  function fetch_header($newsgroup,$key){
    fputs($this->sock,"HEAD $key\n");
    $s = fgets($this->sock,2048);
    if(eregi('^[45][0-9]{2} ',$s)){
      throw new Exception("NNTP::listing can't fetch #$newsgroup:$key header<br/>".$s);
    }
    $_header = '';
    while($s = chop(fgets($this->sock,2048))){
      if(eregi("^\.$",$s))
        break;
      $_header .= "$s\n";
    }
    $header = $this->parse_header($_header);
    $header['key'] = $key;
    return $header;
  }
  function get_mail($newsgroup,$key){
    $header = $this->get_header($newsgroup,$key);
    if(count($header) == 0)
      return array();
    fputs($this->sock,"BODY $key\n");
    $s = fgets($this->sock,2048);
    if(eregi('^[45][0-9]{2} ',$s)){
      throw new Exception("NNTP::listing can't fetch #$newsgroup:$key body<br/>".$s);
    }
    $_mail = '';
    while($s = fgets($this->sock,2048)){
      if(eregi("^\.$",chop($s)))
        break;
      $_mail .= $s;
    }
    return $this->parse_mail_body($header,$_mail);
  }
}
////////////////////////////
// Abstract email sender
////////////////////////////
class Sender{
  var $if_successful;
  function Sender(){
    $this->if_successful = false;
  }
  function send($from,$to,$msg){}
}
// SMTP sender implementation
class SMTPSender extends Sender{
  function send($from,$tos,$msg){
    $this->if_successful = false;
    // replacing line ".\n" by " .\n"
    // $checked_msg = ereg_replace("([\r\n]+)\.([\r\n]+)","\\1 .\\2",$msg);
    $checked_msg = str_replace("\n\r.\n\r","\n\r.\n\r",$msg);
    $checked_msg = str_replace("\n\.\n","\n.\n",$msg);
    // open connection
    $smtpConnection = fsockopen(SMTPHOST,SMTPPORT,$errno,$errstr);
    if(!$smtpConnection){
      throw new Exception("SMTPSender::send : connection to [$SMTPHOST:$SMTPPORT] failed<br/>[$errno] $errstr");
    }
    $out = fgets($smtpConnection,1024);$res .= $out;
    // say hello
    fputs($smtpConnection,"HELO ".$GLOBALS['MAIL_USER_NAME']."\n");
    $out = fgets($smtpConnection,1024);$res .= $out;
    if(!eregi("^250",$out))
      return $res;
    // check server spam , how many lines your server sends ?
    /*
    $out = fgets($smtpConnection,1024);$res .= $out;
    $out = fgets($smtpConnection,1024);$res .= $out;
    $out = fgets($smtpConnection,1024);$res .= $out;
    $out = fgets($smtpConnection,1024);$res .= $out;
    $out = fgets($smtpConnection,1024);$res .= $out;
    $out = fgets($smtpConnection,1024);$res .= $out;
    $out = fgets($smtpConnection,1024);$res .= $out;
    $out = fgets($smtpConnection,1024);$res .= $out;
    $out = fgets($smtpConnection,1024);$res .= $out;
    $out = fgets($smtpConnection,1024);$res .= $out;
    */
    // from
    fputs($smtpConnection,"MAIL FROM: <$from>\n");
    $out = fgets($smtpConnection,1024);$res .= $out;
    if(!eregi("^250",$out))
      return $res;
    $one_ok = false;
    // tos
    foreach($tos as $to){
      fputs($smtpConnection,"RCPT TO: <$to>\n");
      $out = fgets($smtpConnection,1024);$res .= $out;
      if(eregi("^250",$out))
        $one_ok = true;
    }
    if(!$one_ok){
      return $res."No recipients.\n";
    }
    // data
    fputs($smtpConnection,"DATA\n");
    $out = fgets($smtpConnection,1024);$res .= $out;
    fputs($smtpConnection,$checked_msg."\n");
    // end of message
    fputs($smtpConnection,".\n");
    $out = fgets($smtpConnection,1024);$res .= $out;
    
    fputs($smtpConnection,"QUIT\n");
    fclose($smtpConnection);
    
    $this->if_successful = true;
    return $res;
  }
}

///////////////////////////////////////////////
// helper functions for build email message
///////////////////////////////////////////////
function create_envelope($subject,$to,$cc='',$bcc=''){
  $to = str_replace("\n",'',str_replace("\r",'',$to));
  $cc = str_replace("\n",'',str_replace("\r",'',$cc));
  $bcc = str_replace("\n",'',str_replace("\r",'',$bcc));
  // headers
  $envelope = array();
  // from e-mail
  $envelope['from'] =
     $_SESSION['preferences']->getName().' <'.get_current_user_email().'>';

  // HACK for keasar
  if($GLOBALS['MAIL_USER_NAME'] == 'keasar' && MAILSUFFIX == 'cs.bgu.ac.il')
    $envelope['from'] = $_SESSION['preferences']->getName().' <hide@address.com>';
  
  // recipients
  $pre_recipients = array();
  $recipients = array();
  // multi-mail delimeters
  $tmp = preg_split('/,/',$to);
  $to_num = count($tmp);
  
  $pre_recipients = array_merge($pre_recipients,$tmp);

  $tmp = preg_split('/,/',$cc);
  $cc_num = $to_num+count($tmp);
  $pre_recipients = array_merge($pre_recipients,$tmp);
  
  $tmp = preg_split('/,/',$bcc);
  $bcc_num = $cc_num+count($tmp);
  $pre_recipients = array_merge($pre_recipients,$tmp);
  
  reset($pre_recipients);
  $recipients = array();
  $recipients_full = array();
  $i = 0;
  foreach($pre_recipients as $pre_recipient){
    $pre_recipient = trim($pre_recipient);
    if($pre_recipient != ''){
      if(eregi('(.*)<(.+@.+)>',$pre_recipient,$match)){
        $recipients[] = chop($match[2]);
      }
      else if(!eregi('(.+)@(.+)',$pre_recipient)){
        /*
        $entry = $_SESSION['addressbook']->find($pre_recipient);
        if($entry){
          $recipients = array_merge($recipients,$entry->emails);
          $pre_recipient = $entry->get_emails();
        }
        else{
          $pre_recipient .= "@".MAILSUFFIX;
          $recipients[] = $pre_recipient;
        }
        */
        $pre_recipient .= "@".MAILSUFFIX;
        $recipients[] = $pre_recipient;
      }
      else
        $recipients[] = $pre_recipient;
      //print '<b>'.$pre_recipient.'</b><br/>';
      // header 
      if($i < $to_num-1)
        $envelope['to'] .= $pre_recipient.",";
      else if($i < $to_num)
        $envelope['to'] .= $pre_recipient;
      else if($i < $cc_num-1)
        $envelope['cc'] .= $pre_recipient.",";
      else if($i < $cc_num)
        $envelope['cc'] .= $pre_recipient;
      else if($i < $bcc_num-1)
        $envelope['bcc'] .= $pre_recipient.",";
      else if($i < $bcc_num)
        $envelope['bcc'] .= $pre_recipient;
      // full with name
      $recipients_full[] = $pre_recipient;
    }
    $i++;
  }
  $envelope['all-recipients'] = $recipients;
  $envelope['all-recipients-full'] = $recipients_full;
  if($subject != "")
    $envelope['subject'] = $subject;
  else
    $envelope['subject'] = 'none';
  // Custom headers definition
  $envelope['custom_headers'][] = 'Organization: '.ORGANIZATION;
  #$envelope['custom_headers'][] = 'Errors-To: '.ADMIN_EMAIL;
  $envelope['custom_headers'][] = 'X-Mailer: '.VERSION;
  
  return $envelope;
}

/**
 * Build mail from envelope(headers) and body.
 * Returns mail ready for delivery with \r\n newlines
 */
function build_mail($envelope,$body,$with_bcc=false){
  // body pre check
  $ctype = 'text/plain';
  $boundary = false;
  if(is_array($body)){
    $body_count = count($body);
    if($body_count == 1){
      $p = $body[0];
      $ctype = $p['type'];
      if($p['charset']) $ctype .= '; charset='.$p['charset'];
      $single = true;
    }
    else{
      if($body_count == 2 &&
         eregi('text/plain',$body[0]['type']) &&
         eregi('text/html',$body[1]['type']) &&
         $body[0]['disposition'] == '' &&
         $body[1]['disposition'] == '' &&
         $body[1]['multipart']){
        $ctype = "multipart/alternative;\n";
      }
      else if($body_count == 2 &&
              eregi('text/(plain|html)',$body[0]['type']) &&
              eregi('message/disposition-notification',$body[1]['type'])){
          $ctype = "multipart/report;report-type=disposition-notification;\n";
      }
      else if($envelope['encrypted']){
        $ctype = "multipart/encrypted;\n";
        $ctype .= "    protocol=\"application/pgp-encrypted\";\n";
      }
      else if($envelope['signed']){
        $ctype = "multipart/signed; micalg=pgp-sha1;\n";
        $ctype .= "    protocol=\"application/pgp-signature\";\n";
      }
      else{
        $ctype = "multipart/mixed;\n";
      }
      $boundary = sha1(print_r($body,true));
      $ctype .= '    boundary="'.$boundary.'"';
    }
  }
  // encode unicode subjects
  if(preg_match('/[\x7f-\xff]/',$envelope['subject'])){
    $envelope['subject'] = '=?'.$GLOBALS['DEFAULT_CHARSET'].'?B?'.base64_encode($envelope['subject']).'?=';
  }

  $res = '';
  // headers
  if($envelope['from'] != '')
    $res .= 'From: '.$envelope['from']."\n";
  if($envelope['to'] != '')
    $res .= 'To: '.$envelope['to']."\n";
  if($envelope['cc'] != '')
    $res .= 'Cc: '.$envelope['cc']."\n";
  if($with_bcc && $envelope['bcc'] != '')
    $res .= 'Bcc: '.$envelope['bcc']."\n";
  if($envelope['subject'] != '')
    $res .= 'Subject: '.$envelope['subject']."\n";
  
  if(array_not_empty($envelope))
    $res .= "MIME-Version: 1.0\n";
  
  $res .= 'Content-Type: '.$ctype."\n";
  
  if(is_array($envelope['custom_headers'])){
    foreach($envelope['custom_headers'] as $h){
      $res .= $h."\n";
    }
  }
  // header/body separator
  $res .= "\n";

  // body
  if(is_array($body)){
    if($single){
      $res .= $body[0]['contents.data'];
    }
    else{

      if($envelope['encrypted'])
        $res .= "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)\n";
      else if($envelope['signed'])
        $res .= "This is an OpenPGP/MIME signed message (RFC 2440 and 3156)\n";
      else
        $res .= "This is a multi-part message in MIME format.\n";
      
      foreach($body as $p){
        $res .= '--'.$boundary."\n";
        
        // composite part
        if($p['composite'] && array_not_empty($p['body'])){
           $res .= build_mail($p['envelope'],$p['body'],$with_bcc);
           $res .= "\n";
           continue;
        }
        
        if(isset($p['charset']))
          $res .= 'Content-Type: '.$p['type'].'; charset='.$p['charset']."\n";
        else
          $res .= 'Content-Type: '.$p['type']."\n";

        if(isset($p['disposition']))
          $res .= 'Content-Disposition: '.$p['disposition']."\n";

        if(isset($p['description']))
          $res .= 'Content-Description: '.$p['description']."\n";
        
        if($p['encoding'] == ENCBINARY)
          $res .= 'Content-Transfer-Encoding: base64'."\n";
        else if(isset($p['encoding']))
          $res .= 'Content-Transfer-Encoding: '.$p['encoding']."\n";
        
        // separator
        $res .= "\n";
        if($p['encoding'] == ENCBINARY)
          $res .= chunk_split(base64_encode($p['contents.data']));
        else{
          $res .= $p['contents.data'];
          // append \n at end if no
          if($len = strlen($p['contents.data']) && $p['contents.data'][$len-1] != "\n")
            $res .= "\n";
        }
      }
      $res .= '--'.$boundary."--\n";
    }
  }

  return fix_newlines($res);
}

?>
Return current item: CsWebmail