<?php
/*
This file is part of Thomas Bley's moregroupware webmail2/calendar2/admin2 module. (tb-mgw)

More information:
-----------------

Simple Groupware Solutions Thomas Bley
Fichtenstr. 23
Germany, 85640 Putzbrunn

Contact:
--------

info@simple-groupware.de

http://www.simple-groupware.de


Copyright 2002-2003 Thomas Bley
tb-mgw is distributed under the terms of the GNU General Public License 

tb-mgw is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

tb-mgw is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with tb-mgw; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

// Mimedecode
// Moregroupware.org


//
// +----------------------------------------------------------------------+
// | PHP Version 4                                                        |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license,      |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to          |
// | license@php.net so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Authors: Richard Heyes <richard@phpguru.org>                         |
// +----------------------------------------------------------------------+

require_once WM2_INC_DIR.'pear.php';

/**
*  +----------------------------- IMPORTANT ------------------------------+
*  | Usage of this class compared to native php extensions such as        |
*  | mailparse or imap, is slow and may be feature deficient. If available|
*  | you are STRONGLY recommended to use the php extensions.              |
*  +----------------------------------------------------------------------+
*
* Mime Decoding class
*
* This class will parse a raw mime email and return
* the structure. Returned structure is similar to
* that returned by imap_fetchstructure().
*
* USAGE: (assume $input is your raw email)
*
* $decode = new Mail_mimeDecode($input, "\r\n");
* $structure = $decode->decode();
* print_r($structure);
*
* Or statically:
*
* $params['input'] = $input;
* $structure = Mail_mimeDecode::decode($params);
* print_r($structure);
*
* TODO:
*  - Implement further content types, eg. multipart/parallel,
*    perhaps even message/partial.
*
* @author  Richard Heyes <richard@phpguru.org>
* @package Mail
*/

class Mail_mimeDecode extends PEAR
{

    /**
     * The raw email to decode
     * @var    string
     */
    var $_input;

    /**
     * The header part of the input
     * @var    string
     */
    var $_header;

    /**
     * The body part of the input
     * @var    string
     */
    var $_body;

    /**
     * If an error occurs, this is used to store the message
     * @var    string
     */
    var $_error;

    /**
     * Flag to determine whether to include bodies in the
     * returned object.
     * @var    boolean
     */
    var $_include_bodies;

    /**
     * Flag to determine whether to decode bodies
     * @var    boolean
     */
    var $_decode_bodies;

    /**
     * Flag to determine whether to decode headers
     * @var    boolean
     */
    var $_decode_headers;

    /**
     * Variable to hold the line end type.
     * @var    string
     */
    var $_crlf;

    /**
     * Constructor.
     *
     * Sets up the object, initialise the variables, and splits and
     * stores the header and body of the input.
     *
     * @param string The input to decode
     * @param string CRLF type to use (CRLF/LF/CR)
     * @access public
     */
    function Mail_mimeDecode($input, $crlf = "\r\n")
    {

/*		
		if (strpos(substr($input,0,512), "\r\n") === false) {
		  $this->_crlf = "\n";
		}
*/
        $this->_crlf = "\n";
	
        $pos = strpos($input, $this->_crlf . $this->_crlf);
        if ($pos === false) {
			$input = str_replace("\r","",$input); // Windows adds \r, don't ask me why ...
        	$pos = strpos($input, $this->_crlf . $this->_crlf);
	        if ($pos === false) {
              $this->_error = 'Could not split header and body';
              return false;
			}
        }

//        $this->_input          = $input;
        $this->_header         = substr($input, 0, $pos);

        $this->_body           = substr($input, $pos+(2*strlen($this->_crlf)));
		$input = "";
        $this->_decode_bodies  = false;
        $this->_include_bodies = true;
    }

    /**
     * Begins the decoding process. If called statically
     * it will create an object and call the decode() method
     * of it.
     *
     * @param array An array of various parameters that determine
     *              various things:
     *              include_bodies - Whether to include the body in the returned
     *                               object.
     *              decode_bodies  - Whether to decode the bodies
     *                               of the parts. (Transfer encoding)
     *              decode_headers - Whether to decode headers
     *              input          - If called statically, this will be treated
     *                               as the input
     *              crlf           - If called statically, this will be used as
     *                               the crlf value.
     * @return object Decoded results
     * @access public
     */
    function decode($params = null,$partno, $add_new_level, $is_related, $mypartno)
    {
        // Have we been called statically? If so, create an object and pass details to that.
        if (!isset($this) AND isset($params['input'])) {

            if (isset($params['crlf']))
                $obj = new Mail_mimeDecode($params['input'], $params['crlf']);
            else
                $obj = new Mail_mimeDecode($params['input']);
            $structure = $obj->decode($params,$partno, $add_new_level, $is_related, $mypartno);

        // Called statically but no input
        } elseif (!isset($this)) {
            return $this->raiseError('Called statically and no input given');

        // Called via an object
        } else {
            $this->_include_bodies = isset($params['include_bodies'])  ? $params['include_bodies']  : false;
            $this->_decode_bodies  = isset($params['decode_bodies'])   ? $params['decode_bodies']   : false;
            $this->_decode_headers = isset($params['decode_headers'])  ? $params['decode_headers']  : false;

            $structure = $this->_decode($this->_header, $this->_body,'',$partno, $add_new_level, $is_related, $mypartno);
            if ($structure === false) {
                $structure = $this->raiseError($this->_error);
            }
        }

        return $structure;
    }

    /**
     * Performs the decoding. Decodes the body string passed to it
     * If it finds certain content-types it will call itself in a
     * recursive fashion
     *
     * @param string Header section
     * @param string Body section
     * @return object Results of decoding process
     * @access private
     */
    function _decode($headers, $body, $default_ctype = 'text/plain',$partno, $add_new_level, $is_related, $mypartno)
    {
		global $MGW,$mystructure,$conn,$attaches3,$attachments2,$body_show,$plain,$body_plain,$msg_id,$emailbox,$forwarded,$replied,$mymailsize,$hashmap,$related,$msg_cache,$mime_types,$rfc,$gpg_signature,$gpg_decrypt,$notify;
        $return = new stdClass;

        $headers = $this->_parseHeaders($headers);

        foreach ($headers as $value) {
            if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
                $return->headers[strtolower($value['name'])]   = array($return->headers[strtolower($value['name'])]);
                $return->headers[strtolower($value['name'])][] = $value['value'];

            } elseif (isset($return->headers[strtolower($value['name'])])) {
                $return->headers[strtolower($value['name'])][] = $value['value'];

            } else {
                $return->headers[strtolower($value['name'])] = $value['value'];
            }
        }

		$return->ctype_full = "text/plain";

        reset($headers);

        while (list($key, $value) = each($headers)) {
            $headers[$key]['name'] = strtolower($headers[$key]['name']);
            switch ($headers[$key]['name']) {

                case 'content-type':
                    $content_type = $this->_parseHeaderValue($headers[$key]['value']);

					$content_type['value'] = strtolower($content_type['value']);

                    if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
                        $return->ctype_full   = strtolower($regs[1]."/".$regs[2]);
                        $return->ctype_primary   = strtolower($regs[1]);
                        $return->ctype_secondary = strtolower($regs[2]);
                    }

                    if (isset($content_type['other'])) {
                        while (list($p_name, $p_value) = each($content_type['other'])) {
                            $return->ctype_parameters[$p_name] = $p_value;
                        }
                    }
                    break;

                case 'content-disposition':
                    $content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
                    $return->disposition   = $content_disposition['value'];
                    if (isset($content_disposition['other'])) {
                        while (list($p_name, $p_value) = each($content_disposition['other'])) {
                            $return->d_parameters[$p_name] = $p_value;
                        }
                    }
                    break;

                case 'content-transfer-encoding':
                    $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
                    break;
            }
        }

 		if (!isset($return->headers['x-priority']) or !is_array($return->headers['x-priority']) or !count($return->headers['x-priority'])>0) {$priority = "";} else {$priority = htmlspecialchars($return->headers['x-priority'][0]);}
		if (!isset($return->headers['from'])) {$from = "";} else {$from = htmlspecialchars($return->headers['from']);}
		if (!isset($return->headers['subject'])) {$subject = "";} else {$subject = decode_mime_string(htmlspecialchars($return->headers['subject']));}
		if (!isset($return->headers['to'])) {$to = "";} else {$to = htmlspecialchars($return->headers['to']);}
		if (!isset($return->headers['bcc'])) {$bcc = "";} else {$bcc = htmlspecialchars($return->headers['bcc']);}
		if (!isset($return->headers['cc'])) {$cc = "";} else {$cc = htmlspecialchars($return->headers['cc']);}
		if (!isset($return->headers['date'])) {$date = "";} else {$date = htmlspecialchars($return->headers['date']);}
		if (!isset($return->headers['reply-to'])) {$replyto = "";} else {$replyto = htmlspecialchars($return->headers['reply-to']);}

		if (!isset($return->ctype_parameters['pagename'])) { $return->ctype_parameters['pagename'] = ""; } else { $return->ctype_parameters['pagename'] = htmlspecialchars($return->ctype_parameters['pagename']); }
		if (!isset($return->ctype_parameters['author'])) { $return->ctype_parameters['author'] = ""; } else { $return->ctype_parameters['author'] = htmlspecialchars($return->ctype_parameters['author']); }
		if (!isset($return->ctype_parameters['description'])) { $return->ctype_parameters['description'] = ""; } else { $return->ctype_parameters['description'] = htmlspecialchars(urldecode($return->ctype_parameters['description'])); }
		if (!isset($return->ctype_parameters['summary'])) { $return->ctype_parameters['summary'] = ""; } else { $return->ctype_parameters['summary'] = htmlspecialchars(urldecode($return->ctype_parameters['summary'])); }
		if (!isset($return->ctype_parameters['lastmodified']) or !is_numeric($return->ctype_parameters['lastmodified'])) { $return->ctype_parameters['lastmodified'] = ""; } else { $return->ctype_parameters['lastmodified'] = date("D, d M Y H:i:s O",$return->ctype_parameters['lastmodified']); }

		if (!isset($gpg_signature)) $gpg_signature = array();
		if (!isset($gpg_decrypt)) $gpg_decrypt = array();

		if (isset($return->disposition)) {$dispo = " (".strtolower($return->disposition).")";} else {$dispo = "";}
		if (isset($return->d_parameters["filename"])) {$dispo2 = " (".strtolower($return->d_parameters["filename"]).")";} else {$dispo2 = "";}

		$mystructure[] = $return->ctype_full.$dispo.$dispo2." ".$partno;
		
        if (isset($content_type)) {
            switch ($content_type['value']) {
				case 'application/pgp-signature':
                case 'text/plain':
                    if ($subject != "" and $date == "") {$date = date("D, d M Y H:i:s O");}
                    $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';

					$body2 = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body);
					$return->bodysize = strlen($body);

					if (isset($MGW->settings["wm2_gpg_use"]) and $MGW->settings["wm2_gpg_use"] and strpos(substr($body2,0,100),"BEGIN PGP MESSAGE")) {
					  if (file_exists($MGW->settings["wm2_gpg_prog"]) and file_exists($MGW->settings["wm2_gpg_pubring"]) and file_exists($MGW->settings["wm2_gpg_secring"])) {
					    
						$results = decrypt_gpg($body2);
						if ($results[0]) {
						  $gpg_decrypt[0] = "ok";
						  $gpg_decrypt[1] = $results[1];
						  $body2 = $results[2];
						} else {
						  $gpg_decrypt[0] = "bad";
						  $gpg_decrypt[1] = $results[1];
						}
					  } else {
					    echo "Could not find gpg/pubring/secring.";
					  }
					}

					if (isset($MGW->settings["wm2_gpg_use"]) and $MGW->settings["wm2_gpg_use"] and strpos(substr($body2,0,100),"BEGIN PGP SIGNED MESSAGE")) {
					  if (file_exists($MGW->settings["wm2_gpg_prog"]) and file_exists($MGW->settings["wm2_gpg_pubring"]) and file_exists($MGW->settings["wm2_gpg_secring"])) {
					    
						$results = verify_gpg($body2);
						if ($results[0]) {
						  $gpg_signature[0] = "ok";
						  $gpg_signature[1] = $results[1];
						  $body2 = $results[2];
						} else {
						  $gpg_signature[0] = "bad";
						  $gpg_signature[1] = $results[1];
						}
					  } else {
					    echo "Could not find gpg/pubring/secring.";
					  }
					}

					$return->body = "";
					$hasfilename = false;
					$thefilename = $partno;
					$isattachment = false;
					$isinline = false;
					$oversize = false;

					if (!isset($body_show)) {$body_show = array();}
					  
					if (isset($return->d_parameters["filename"]) and $return->d_parameters["filename"]!="") {
					  $thefilename = trim(decode_mime_string($return->d_parameters["filename"]));
					  $hasfilename = true;
					}
					if (isset($return->ctype_parameters) and isset($return->ctype_parameters["name"]) and $return->ctype_parameters["name"]) {
					  $thefilename = trim(decode_mime_string($return->ctype_parameters["name"]));
					  $hasfilename = true;
					}
					
					if (isset($content_disposition['value']) and $content_disposition['value']=="attachment") { $isattachment = true; }
					  elseif (isset($content_disposition['value']) and $content_disposition['value']=="inline") { $isinline = true; }
                      
					if ($hasfilename) {
					  $act_name = substr($body,0,strlen($body)-1);
                      if (file_exists($act_name)) { $return->bodysize = filesize($act_name); } else { $return->bodysize=0; }
					  $attaches3++;
					}
					if ($return->bodysize > 5000) { $oversize = true; }

					if (($isattachment or ($isinline and $hasfilename)) and !$oversize) {
					  if ($MGW->settings["wm2_enableinline"]) {
						if ($hasfilename) {
						  $fd = fopen ($act_name, "rb");
						  $return->body = fread ($fd, filesize ($act_name));
						  fclose ($fd);

						  $thesize = format_filesize(filesize($act_name));
 						  $body_show[count($body_show)][0] = "<br><b>".$thefilename."</b> (".$hashmap["maillist_size"].": ".$thesize.")<br>";
				          $body_show[count($body_show)-1][1] = modify_text2(htmlspecialchars(trim($return->body)));
						} else {
						  if ($return->body=="") {$this->_include_bodies ? $return->body = $body2 : null;}
				          $body_show[count($body_show)][0] = "text";
				          $body_show[count($body_show)-1][1] = modify_text(htmlspecialchars(trim($return->body)));
						}
					  }
					} elseif (!$hasfilename) {
					  $plain = true;
					  if ($return->body=="") {$this->_include_bodies ? $return->body = $body2 : null;}
					  $body_show[count($body_show)][0] = "text";
					  $body_show[count($body_show)-1][1] = modify_text(htmlspecialchars(trim($return->body)))."<br>";
					  $body_plain .= $return->body;
					}
					
					if (count($gpg_signature) > 0 or count($gpg_decrypt) > 0) {
					  $rfc = true;
					} else {
					  $rfc = false;
					}
                    break;

				case 'application/x-tikiwiki':
				
					require_once("wikilib.php");
					
                    if ($subject != "" and $date == "" and !$plain) {$date = date("D, d M Y H:i:s O");}
                    $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
					$return->bodysize = strlen($body);

					$return->body = "";

					if (!isset($body_show)) {$body_show = array();}

					if ($return->body=="") {$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;}
					
					$return->body = wiki_parse_data($return->body,$return->ctype_parameters['pagename']);
					
					
 					$body_show[count($body_show)][0] = "<br><b>".$return->ctype_parameters['pagename'].($return->ctype_parameters['author']==""?"":" / ".$return->ctype_parameters['author'])."</b> ".($return->ctype_parameters['description']==""?"":"(".$return->ctype_parameters['description'].")").($return->ctype_parameters['lastmodified']==""?"":" ".$return->ctype_parameters['lastmodified'])."<br><br>".($return->ctype_parameters['summary']==""?"":$return->ctype_parameters['summary']."<br><br>");
					$body_show[count($body_show)-1][1]  = modify_html($return->body)."<br>";

					if ($plain==false) {
				  	  $text = strip_tags(str_replace("<br>","#mybr#",trim($return->body)));
				  	  $text = preg_replace("/(\015\012)|(\015)|(\012)/","",$text);
				  	  $text = str_replace("#mybr#","\r\n",$text);
                      $text = my_decodeHTML($text);
				  	  $body_plain .= $text;
					}

                    break;
				
                case 'text/html':
				
                    if ($subject != "" and $date == "" and !$plain) {$date = date("D, d M Y H:i:s O");}
                    $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
					$return->bodysize = strlen($body);

					$return->body = "";
					$hasfilename = false;
					$thefilename = $partno;
					$isattachment = false;
					$isinline = false;
					$oversize = false;

					if (!isset($body_show)) {$body_show = array();}

					if (isset($return->d_parameters["filename"]) and $return->d_parameters["filename"]!="") {
					  $thefilename = trim(decode_mime_string($return->d_parameters["filename"]));
					  $hasfilename = true;
					}
					if (isset($content_disposition['value']) and $content_disposition['value']=="attachment") { $isattachment = true; }
					  elseif (isset($content_disposition['value']) and $content_disposition['value']=="inline") { $isinline = true; }

					if ($hasfilename) {
					  $act_name = substr($body,0,strlen($body)-1);
                      if (file_exists($act_name)) { $return->bodysize = filesize($act_name); } else { $return->bodysize=0; }
					  $attaches3++;
					}
					if ($return->bodysize > 15000) { $oversize = true; }

					if (($isattachment or ($isinline and $hasfilename))and !$oversize) {
					  if ($MGW->settings["wm2_enableinline"]) {
						if ($hasfilename) {
						  $fd = fopen ($act_name, "rb");
						  $return->body = fread ($fd, filesize ($act_name));
						  fclose ($fd);

						  $thesize = format_filesize(filesize($act_name));
 						  $body_show[count($body_show)][0] = "<br><b>".$thefilename."</b> (".$hashmap["maillist_size"].": ".$thesize.")<br>";
				          $body_show[count($body_show)-1][1] = modify_html(trim($return->body));
						} else {
						  if ($return->body=="") {$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;}
				          $body_show[count($body_show)][0] = "html";
				          $body_show[count($body_show)-1][1] = modify_html(trim($return->body));
						}
					  }
					} elseif (!$hasfilename) {
					  if ($return->body=="") {$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;}

				      if (isset($body_show[count($body_show)-1][0]) and $body_show[count($body_show)-1][0]=="text" and !$rfc) {
					    $body_show[count($body_show)-1][0]  = "html";
					    $body_show[count($body_show)-1][1]  = modify_html($return->body)."<br>";
  					  } else {
					    $body_show[count($body_show)][0]  = "html";
					    $body_show[count($body_show)-1][1]  = modify_html($return->body)."<br>";
					  }

					  if ($plain==false) {
				  		$text = strip_tags(str_replace("<br>","#mybr#",trim($return->body)));
				  		$text = preg_replace("/(\015\012)|(\015)|(\012)/","",$text);
				  		$text = str_replace("#mybr#","\r\n",$text);
                        $text = my_decodeHTML($text);
				  		$body_plain .= $text;
					  }
					}

                    $rfc = false;
                    break;

                case 'multipart/report': // RFC1892
                case 'multipart/signed': // PGP
                case 'multipart/mixed':
					
                    if(!isset($content_type['other']['boundary'])){
                        $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
                        return false;
                    }

                    $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';

					$prefix = ($partno != '1') ? $partno.'.' : '';
                    $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
                    for ($i = 0; $i < count($parts); $i++) {
                        list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
                        $part = $this->_decode($part_header, $part_body, $default_ctype,$prefix.($i+1), TRUE, FALSE, $mypartno);
                        if($part === false)
                            $part = $this->raiseError($this->_error);
                        $return->parts[] = $part;
                    }
                    break;

                case 'multipart/parallel':
                case 'multipart/digest':

                    if(!isset($content_type['other']['boundary'])){
                        $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
                        return false;
            		}

                    $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';

                    $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
                    for ($i = 0; $i < count($parts); $i++) {
                        list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);

						$part = $this->_decode($part_header, $part_body, $default_ctype,($i+1), TRUE, FALSE,$mypartno);
						if($part === false)
                            $part = $this->raiseError($this->_error);
                        $return->parts[] = $part;
        			}
                    break;


                case 'multipart/alternative':
                case 'multipart/alternate':

                    if(!isset($content_type['other']['boundary'])){
                        $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
                        return false;
                    }

                    $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';

					if ($add_new_level) {$partno = $partno.'.1';}
                    $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
                    for ($i = 0; $i < count($parts); $i++) {
                        list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
                        $part = $this->_decode($part_header, $part_body, $default_ctype, substr($partno, 0, -1).($i+1), TRUE, FALSE,$mypartno);
                        if($part === false)
                            $part = $this->raiseError($this->_error);
                        $return->parts[] = $part;
                    }
                    break;

                case 'multipart/related':
                    if(!isset($content_type['other']['boundary'])){
                        $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
                        return false;
                    }

                    $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';

					if ($add_new_level) {$partno = $partno.'.1';}
                    $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
                    for ($i = 0; $i < count($parts); $i++) {
                        list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
                        $part = $this->_decode($part_header, $part_body, $default_ctype, substr($partno, 0, -1).($i+1), TRUE, TRUE,$mypartno);
                        if($part === false)
                            $part = $this->raiseError($this->_error);
                        $return->parts[] = $part;
                    }
                    break;

                case 'message/rfc822':
                    $rfc = true;
                
				    if($add_new_level) {$partno = $partno.'.1';}
                    $obj = &new Mail_mimeDecode($body, $this->_crlf);
					$return->bodysize = strlen($body);
					// substr($partno, 0, -1).($i+1)
                    $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies,'decode_bodies'  => $this->_decode_bodies,'decode_headers' => $this->_decode_headers),$partno, TRUE, TRUE,$mypartno);
                    unset($obj);
                    break;

                default:
                    $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
	         	    $ctype = explode('/', $content_type['value']);

					$return->body = "";
					$hasfilename = false;
					$thefilename = $partno;
					$isattachment = false;
					$isinline = false;
					$oversize = false;

					if (!isset($body_show)) {$body_show = array();}

					if (isset($return->ctype_parameters["name"]) and $return->ctype_parameters["name"]!="") {
					  $thefilename = trim(decode_mime_string($return->ctype_parameters["name"]));
					  $hasfilename = true;
					}

					if (isset($return->d_parameters["filename"]) and $return->d_parameters["filename"]!="") {
					  $thefilename = trim(decode_mime_string($return->d_parameters["filename"]));
					  $hasfilename = true;
					}

					if ($content_type['value']=="text/calendar") {
					  $thefilename = "appointment.ics";
					  $hasfilename = true;
					}

					if ($hasfilename) {
					  $act_name = substr($body,0,strlen($body)-1);
                      if (file_exists($act_name)) { $return->bodysize = filesize($act_name); } else { $return->bodysize=0; }
					  $attaches3++;
					  if ($return->bodysize > 5000) { $oversize = true; }
					} else {$act_name = "";}
					
					if (isset($content_disposition['value']) and $content_disposition['value']=="attachment") { $isattachment = true; }
					  elseif (isset($content_disposition['value']) and $content_disposition['value']=="inline") { $isinline = true; }

					if ($is_related == TRUE){
					  if (isset($return->headers['content-id'])){
						$cid = trim($return->headers['content-id']);
						if(substr($cid,0,1) == '<') $cid = substr($cid, 1);
						if(substr($cid,-1) == '>') $cid = substr($cid,0,-1);
					  }else{
						$cid = 0;
					  }
                      if ($act_name == "") {$act_name = $body; }
				      $related[] = array('act_name'=>str_replace(ROOTPATH,ROOTURL,$act_name),'c_id'=>$cid,'filename'=>$thefilename);
					} else {
					  $attaches3++;
					}
					

					if ($isattachment or ($isinline and $hasfilename)) {
					  if ($MGW->settings["wm2_enableinline"]) {

						$ext = strtolower(substr($thefilename,-strpos(strrev($thefilename),'.')));
						if (isset($mime_types[$ext][0])) {
						  $inline = $mime_types[$ext][0];
						} else { $inline = ""; }

						if ($inline=="yes" or $inline=="img" or $inline=="office") {
					      if (!$oversize and $inline=="yes" and file_exists($act_name)) {
							if ($hasfilename) {
							  $fd = fopen ($act_name, "rb");
							  $return->body = fread ($fd, filesize ($act_name));
							  fclose ($fd);
	
							  $thesize = format_filesize(filesize($act_name));
 							  $body_show[count($body_show)][0] = "<br><b>".$thefilename."</b> (".$hashmap["maillist_size"].": ".$thesize.")<br>";
					          $body_show[count($body_show)-1][1] = modify_text2(htmlspecialchars(trim($return->body)));
							} else {
							  if ($return->body=="") {$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;}
				    	      $body_show[count($body_show)][0] = "text";
					          $body_show[count($body_show)-1][1] = modify_text(htmlspecialchars(trim($return->body)));
							}

							if ($return->body=="") {$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;}

						  } elseif ($inline=="office" and $hasfilename and file_exists($act_name) and isset($_SESSION["MGW"]->settings["wm2_unzip"]) and $_SESSION["MGW"]->settings["wm2_unzip"]!="") {
							  require_once("oo_xml/class.tx_rlmpofficeimport_xml.php");
							  if (file_exists($_SESSION["MGW"]->settings["wm2_unzip"])) {

							    if ($account == 0) { $act_name_xml = CACHE_FOLDER."attachments/".$dir_nr."/".$msg_no."-".$msg_cache."-".$partno."-content.xml"; }
							      else { $act_name_xml = CACHE_FOLDER."imap/attachments/".$user_id."-".$message_id."-".$partno."-content.xml"; }

								if (!file_exists($act_name_xml)) wm2_unzip($act_name,"content.xml",$act_name_xml);
								
							    $oo_xml = new tx_rlmpofficeimport_openoffice;
							    $output = $oo_xml->mainWriter($act_name_xml, "", $act_name);
								unset($oo_xml);
							  
							    $thesize = format_filesize(filesize($act_name));
 							    $body_show[count($body_show)][0] = "<br><b>".$thefilename."</b> (".$hashmap["maillist_size"].": ".$thesize.")<br><br>";
							    $body_show[count($body_show)-1][1]  = '<table border="0" cellpadding="5" cellspacing="5" style="border: 1 solid #333333; background-color: #FFFFFF"><tr><td>'.str_replace('onmyload','onload="return_width(this);"',modify_html(implode($output,"\n")))."<br></td></tr></table>";
							  } else {
							    die("Could not execute unzip.");
							  }
						  } elseif (!$is_related and $inline=="img") {
                            if (file_exists($act_name)) { $att_size = filesize($act_name); } else { $att_size=0; }
						    $thesize = format_filesize($att_size);
 						    $body_show[count($body_show)][0] = "<br><b>".$thefilename."</b> (".$hashmap["maillist_size"].": ".$thesize.")";

							// download.php?attfile='.rawurlencode($filename).'&c_type='.rawurlencode($c_type).'&msg_id='.rawurlencode($msg_no).'&partno='.rawurlencode($partno).'&disposition=inline
							// download.php?attfile='.rawurlencode($filename).'&c_type='.rawurlencode($c_type).'&msg_id='.rawurlencode($msg_no).'&partno='.rawurlencode($partno).'&disposition=inline&'.SID.'
						    $body_show[count($body_show)-1][1] = '
							  <table cellpadding="1" cellspacing="0" border="0" bgcolor="#4E4E4E"><tr><td>
							  <table bgcolor="#F3F3F3" border="0" cellpadding="0" cellspacing="0"><tr><td><a target="_blank" href="'.$act_name.'"><img src="'.$act_name.'" border="0" title="'.$thefilename.'" onload="return_width(this);"></a></td></tr></table></td></tr></table>';
						  }
						}
					  }
					} elseif (!$is_related and !$hasfilename) {
					  $plain = true;
					  if ($return->body=="") {$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;}
					  $body_show[count($body_show)][0]  = "text";
					  $body_show[count($body_show)-1][1]  = modify_text(htmlspecialchars(trim($return->body)))."<br>";
					}
                    break;
            }

        } else {
            $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
            $return->ctype_primary   = "text";
            $return->ctype_secondary = "plain";
			$return->bodysize = strlen($body);
		    $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
			$body_show[count($body_show)][0] = "text";
			$body_show[count($body_show)-1][1] = modify_text(htmlspecialchars(trim($return->body)))."<br>";
		    $body_plain .= $return->body;
            if ($date == "") {$date = date("D, d M Y H:i:s O");}
        }
		
		$return->body = "";
		if ($date!="") {
		  if ($partno!="0") {
		    $size = "";
			$structure = "";
		  } else {
		    if (isset($return->headers["disposition-notification-to"]) and $return->headers["disposition-notification-to"]!="") $notify = true;
		    $size = $mymailsize;
			$structure = $mystructure;
          }
		  $emailbox[] = array(
			$from,
			$subject,
			$date,
			$cc,
			$bcc,
			$to,
			$replyto,
			$size,
			$priority,
			$structure,
			$attaches3,
			$attachments2,
			$gpg_signature,
			$gpg_decrypt
		  );

		  $gpg_signature = array();
		  $gpg_decrypt = array();

		  $attachments2 = array();
		  $attaches3 = 0;
	    }


      return $return;
    }

    /**
     * Given the output of the above function, this will return an
     * array of references to the parts, indexed by mime number.
     *
     * @param  object $structure   The structure to go through
     * @param  string $mime_number Internal use only.
     * @return array               Mime numbers
     */
    function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '')
    {
        $return = array();
        if (!empty($structure->parts)) {
            if ($mime_number != '') {
                $structure->mime_id = $prepend . $mime_number;
                $return[$prepend . $mime_number] = &$structure;
            }
            for ($i = 0; $i < count($structure->parts); $i++) {

            
                if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
                    $prepend      = $prepend . $mime_number . '.';
                    $_mime_number = '';
                } else {
                    $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
                }

                $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
                foreach ($arr as $key => $val) {
                    $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
                }
            }
        } else {
            if ($mime_number == '') {
                $mime_number = '1';
            }
            $structure->mime_id = $prepend . $mime_number;
            $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
        }
        
        return $return;
    }

    /**
     * Given a string containing a header and body
     * section, this function will split them (at the first
     * blank line) and return them.
     *
     * @param string Input to split apart
     * @return array Contains header and body section
     * @access private
     */
    function _splitBodyHeader($input)
    {

        $pos = strpos($input, $this->_crlf . $this->_crlf);
        if ($pos === false) {
            $this->_error = 'Could not split header and body';
            return false;
        }

        $header = substr($input, 0, $pos);
        $body   = substr($input, $pos+(2*strlen($this->_crlf)));

        return array($header, $body);
    }

    /**
     * Parse headers given in $input and return
     * as assoc array.
     *
     * @param string Headers to parse
     * @return array Contains parsed headers
     * @access private
     */
    function _parseHeaders($input)
    {

        if ($input !== '') {
            // Unfold the input
            $input   = preg_replace('/' . $this->_crlf . "(\t| )/", ' ', $input);
            $headers = explode($this->_crlf, trim($input));

            foreach ($headers as $value) {
                $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
                $hdr_value = substr($value, $pos+1);
                if($hdr_value[0] == ' ')
                    $hdr_value = substr($hdr_value, 1);

                $return[] = array(
                                  'name'  => $hdr_name,
                                  'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
                                 );
            }
        } else {
            $return = array();
        }

        return $return;
    }

    /**
     * Function to parse a header value,
     * extract first part, and any secondary
     * parts (after ;) This function is not as
     * robust as it could be. Eg. header comments
     * in the wrong place will probably break it.
     *
     * @param string Header value to parse
     * @return array Contains parsed result
     * @access private
     */
    function _parseHeaderValue($input)
    {

        if (($pos = strpos($input, ';')) !== false) {

            $return['value'] = trim(substr($input, 0, $pos));
            $input = trim(substr($input, $pos+1));

            if (strlen($input) > 0) {

                // This splits on a semi-colon, if there's no preceeding backslash
                // Can't handle if it's in double quotes however. (Of course anyone
                // sending that needs a good slap).
                $parameters = preg_split('/\s*(?<!\\\\);\s*/i', $input);

                for ($i = 0; $i < count($parameters); $i++) {
                    $param_name  = substr($parameters[$i], 0, $pos = strpos($parameters[$i], '='));
                    $param_value = substr($parameters[$i], $pos + 1);
                    if ($param_value[0] == '"') {
                        $param_value = substr($param_value, 1, -1);
                    }
                    $return['other'][$param_name] = $param_value;
                    $return['other'][strtolower($param_name)] = $param_value;
                }
            }
        } else {
            $return['value'] = trim($input);
        }

        return $return;
    }

    /**
     * This function splits the input based
     * on the given boundary
     *
     * @param string Input to parse
     * @return array Contains array of resulting mime parts
     * @access private
     */
    function _boundarySplit($input, $boundary)
    {
        $tmp = explode('--'.$boundary, $input);
		$parts = array();
        for ($i=1; $i<count($tmp)-1; $i++) {
            $parts[] = $tmp[$i];
        }

        return $parts;
    }

    /**
     * Given a header, this function will decode it
     * according to RFC2047. Probably not *exactly*
     * conformant, but it does pass all the given
     * examples (in RFC2047).
     *
     * @param string Input header value to decode
     * @return string Decoded header value
     * @access private
     */
    function _decodeHeader($input)
    {
        // Remove white space between encoded-words
        $input = preg_replace('/(=\?[^?]+\?(Q|B)\?[^?]*\?=)( |' . "\t|" . $this->_crlf . ')+=\?/', '\1=?', $input);

        // For each encoded-word...
        while (preg_match('/(=\?([^?]+)\?(Q|B)\?([^?]*)\?=)/', $input, $matches)) {

            $encoded  = $matches[1];
            $charset  = $matches[2];
            $encoding = $matches[3];
            $text     = $matches[4];

            switch ($encoding) {
                case 'B':
                    $text = base64_decode($text);
                    break;

                case 'Q':
                    $text = str_replace('_', ' ', $text);
                    preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
                    foreach($matches[1] as $value)
                        $text = str_replace('='.$value, chr(hexdec($value)), $text);
                    break;
            }

            $input = str_replace($encoded, $text, $input);
        }

        return $input;
    }

    /**
     * Given a body string and an encoding type,
     * this function will decode and return it.
     *
     * @param  string Input body to decode
     * @param  string Encoding type to use.
     * @return string Decoded body
     * @access private
     */
    function _decodeBody($input, $encoding = '7bit')
    {
        switch (strtolower($encoding)) {
            case '7bit':
                return $input;
                break;

            case 'quoted-printable':
                return $this->_quotedPrintableDecode($input);
                break;

            case 'base64':
                return base64_decode($input);
                break;

            default:
                return $input;
        }
    }

    /**
     * Given a quoted-printable string, this
     * function will decode and return it.
     *
     * @param  string Input body to decode
     * @return string Decoded body
     * @access private
     */
    function _quotedPrintableDecode($input)
    {
        // Remove soft line breaks
        $input = preg_replace("/=\r?\n/", '', $input);

        // Replace encoded characters
        if (preg_match_all('/=[a-f0-9]{2}/i', $input, $matches)) {
            $matches = array_unique($matches[0]);
            foreach ($matches as $value) {
                $input = str_replace($value, chr(hexdec(substr($value,1))), $input);
            }
        }

        return $input;
    }

    /**
     * Checks the input for uuencoded files and returns
     * an array of them. Can be called statically, eg:
     *
     * $files =& Mail_mimeDecode::uudecode($some_text);
     *
     * It will check for the begin 666 ... end syntax
     * however and won't just blindly decode whatever you
     * pass it.
     *
     * @param  string Input body to look for attahcments in
     * @return array  Decoded bodies, filenames and permissions
     * @access public
     * @author Unknown
     */
    function &uudecode($input)
    {
        // Find all uuencoded sections
        preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);

        for ($j = 0; $j < count($matches[3]); $j++) {

            $str      = $matches[3][$j];
            $filename = $matches[2][$j];
            $fileperm = $matches[1][$j];

            $file = '';
            $str = preg_split("/\r?\n/", trim($str));
            $strlen = count($str);

            for ($i = 0; $i < $strlen; $i++) {
                $pos = 1;
                $d = 0;
                $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077);

                while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) {
                    $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
                    $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
                    $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
                    $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20);
                    $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));

                    $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));

                    $file .= chr(((($c2 - ' ') & 077) << 6) |  (($c3 - ' ') & 077));

                    $pos += 4;
                    $d += 3;
                }

                if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
                    $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
                    $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
                    $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
                    $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));

                    $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));

                    $pos += 3;
                    $d += 2;
                }

                if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
                    $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
                    $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
                    $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));

                }
            }
            $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
        }

        return $files;
    }

    /**
     * Returns a xml copy of the output of
     * Mail_mimeDecode::decode. Pass the output in as the
     * argument. This function can be called statically. Eg:
     *
     * $output = $obj->decode();
     * $xml    = Mail_mimeDecode::getXML($output);
     *
     * The DTD used for this should have been in the package. Or
     * alternatively you can get it from cvs, or here:
     * http://www.phpguru.org/xmail/xmail.dtd.
     *
     * @param  object Input to convert to xml. This should be the
     *                output of the Mail_mimeDecode::decode function
     * @return string XML version of input
     * @access public
     */
    function getXML($input)
    {
        $crlf    =  "\r\n";
        $output  = '<?xml version=\'1.0\'?>' . $crlf .
                   '<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf .
                   '<email>' . $crlf .
                   Mail_mimeDecode::_getXML($input) .
                   '</email>';

        return $output;
    }

    /**
     * Function that does the actual conversion to xml. Does a single
     * mimepart at a time.
     *
     * @param  object  Input to convert to xml. This is a mimepart object.
     *                 It may or may not contain subparts.
     * @param  integer Number of tabs to indent
     * @return string  XML version of input
     * @access private
     */
    function _getXML($input, $indent = 1)
    {
        $htab    =  "\t";
        $crlf    =  "\r\n";
        $output  =  '';
        $headers = @(array)$input->headers;

        foreach ($headers as $hdr_name => $hdr_value) {

            // Multiple headers with this name
            if (is_array($headers[$hdr_name])) {
                for ($i = 0; $i < count($hdr_value); $i++) {
                    $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
                }

            // Only one header of this sort
            } else {
                $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent);
            }
        }

        if (!empty($input->parts)) {
            for ($i = 0; $i < count($input->parts); $i++) {
                $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf .
                           Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) .
                           str_repeat($htab, $indent) . '</mimepart>' . $crlf;
            }
        } elseif (isset($input->body)) {
            $output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' .
                       $input->body . ']]></body>' . $crlf;
        }

        return $output;
    }

    /**
     * Helper function to _getXML(). Returns xml of a header.
     *
     * @param  string  Name of header
     * @param  string  Value of header
     * @param  integer Number of tabs to indent
     * @return string  XML version of input
     * @access private
     */
    function _getXML_helper($hdr_name, $hdr_value, $indent)
    {
        $htab   = "\t";
        $crlf   = "\r\n";
        $return = '';

        $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value);
        $new_hdr_name  = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));

        // Sort out any parameters
        if (!empty($new_hdr_value['other'])) {
            foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
                $params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf .
                            str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf .
                            str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf .
                            str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf;
            }

            $params = implode('', $params);
        } else {
            $params = '';
        }

        $return = str_repeat($htab, $indent) . '<header>' . $crlf .
                  str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf .
                  str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf .
                  $params .
                  str_repeat($htab, $indent) . '</header>' . $crlf;

        return $return;
    }

} // End of class
?>
