<?php
/**
* MailEngine class
* @author Brian Wong <bwsource@users.sourceforge.net>
* @version 04-12-05
* @package MailEngine
*
* Copyright (C) 2003 - 2007 MailZu
* License: GPL, see LICENSE
*/
/**
* Base directory of application
*/
@define('BASE_DIR', dirname(__FILE__) . '/..');
/**
* CmnFns class
*/
include_once('CmnFns.class.php');
/**
* Pear::DB
*/
if ($GLOBALS['conf']['app']['safeMode']) {
        ini_set('include_path', ( dirname(__FILE__) . '/pear/' . PATH_SEPARATOR . ini_get('include_path') ));
        include_once('pear/Mail/mimeDecode.php');
}
else {
        include_once('Mail/mimeDecode.php');
}

/**
* Include htmlfilter class
*/
include_once('lib/htmlfilter.php');


/**
* Provide all MIME functionality
*/

/**
* Get full MIME type
* $param The mime structure object
*/
function GetCtype($struct) {
        $ctype_p = strtolower(trim($struct->ctype_primary));
        $ctype_s = strtolower(trim($struct->ctype_secondary));
        $type = $ctype_p . '/' . $ctype_s;
        return $type;
}

/**
* Recursively parse MIME structure
* $param The mime structure object
*/
$filelist = array ();
$errors = array ();

function MsgParseBody($struct) {

        global $filelist;
        global $errors;
        $ctype_p = strtolower(trim($struct->ctype_primary));
        $ctype_s = strtolower(trim($struct->ctype_secondary));

        switch ($ctype_p) {
          case "multipart":
            switch ($ctype_s) {
              case "alternative":
                // Handle multipart/alternative parts
                $alt_entity = FindMultiAlt($struct->parts);
                // Ignore if we return false NEEDS WORK
                if ($alt_entity) MsgParseBody($alt_entity);
                break;
              case "related":
                // Handle multipart/related parts
                $rel_entities = FindMultiRel($struct);
                foreach ($rel_entities as $ent) {
                  MsgParseBody($ent);
                }
                break;
              default:
                // Probably multipart/mixed here
                // Recursively process nested mime entities
                if ( is_array($struct->parts) || is_object($struct->parts) ) {
                  foreach ($struct->parts as $cur_part) {
                    MsgParseBody($cur_part);
                  }
                } else {
                  $errors['Invalid or Corrupt MIME Detected.'] = true;
                }
                break;
            }
            break;

          case "text":
            // Do not display attached text types
            if ($attachment = $struct->d_parameters['filename'] or
                  $attachment = $struct->d_parameters['name']) {
                array_push($filelist, $attachment);
                break;
            }
            switch ($ctype_s) {
              // Plain text
              case "plain":
                MsgBodyPlainText($struct->body);
                break;
              // HTML text
              case "html":
                MsgBodyHtmlText($struct->body);
                break;
              // Text type we do not support
              default:
                $errors['Portions of text could not be displayed'] = true;
            }
            break;

          default:
            // Save the listed filename or notify the
            // reader that this mail is not displayed completely
            $attachment = $struct->d_parameters['filename'];
            $attachment ? array_push($filelist, $attachment) : $errors['Unsupported MIME objects present'] = true;

        }
}

/**
* Get the best MIME entity for multipart/alternative
* Adapted from SqurrelMail
* $param Array of MIME entities
* $return Single MIME entity
*/
function FindMultiAlt($parts) {
  $alt_pref = array ('text/plain', 'text/html');
  $best_view = 0;
  // Bad Headers sometimes have invalid MIME.... 
  if ( is_array($parts) || is_object($parts) ) {
    foreach ($parts as $cur_part) {
      $type = GetCtype($cur_part);
      if ($type == 'multipart/related') {
      $type = $cur_part->d_parameters['type'];
      // Mozilla bug. Mozilla does not provide the parameter type.
      if (!$type) $type = 'text/html';
      }
      $altCount = count($alt_pref);
      for ($j = $best_view; $j < $altCount; ++$j) {
        if (($alt_pref[$j] == $type) && ($j >= $best_view)) {
          $best_view = $j;
          $struct = $cur_part;
        }
      }
    }
    return $struct;
  } else {
    $errors['Invalid or Corrupt MIME Detected.'] = true;
  }
}

/**
* Get the list of related entities for multipart/related
* Adapted from SqurrelMail
* $param multipart/alternative structure
* @return List of MIME entities
*/
function FindMultiRel($struct) {
  $entities = array();
  $type = $struct->d_parameters['type'];
  // Mozilla bug. Mozilla does not provide the parameter type.
  if (!$type) $type = 'text/html';
  // Bad Headers sometimes have invalid MIME.... 
  if ( is_array($struct->parts) || is_object($struct->parts) ) {
    foreach ($struct->parts as $part) {
      if (GetCtype($part) == $type || GetCtype($part) == "multipart/alternative") {
        array_push($entities,$part);
      }
    }
  } else {
    $errors['Invalid or Corrupt MIME Detected.'] = true;
  }
  return $entities;
}

// Wrapper script for htmlfilter. Settings taken
// from SquirrelMail
function sanitizeHTML($body) {
    if (isset($_COOKIE['lang']) &&
        file_exists("img/".substr($_COOKIE['lang'],0,2).".blocked_img.png")) {
      $secremoveimg = "img/".substr($_COOKIE['lang'],0,2).".blocked_img.png";
    } else {
      $secremoveimg = "img/blocked_img.png";
    }
        $tag_list = Array(
                      false,
                      "object",
                      "meta",
                      "html",
                      "head",
                      "base",
                      "link",
                      "frame",
                      "iframe",
                      "plaintext",
                      "marquee"
                      );

        $rm_tags_with_content = Array(
                                  "script",
                                  "applet",
                                  "embed",
                                  "title",
                                  "frameset",
                                  "xml",
                                  "style"
                                  );

        $self_closing_tags =  Array(
                                "img",
                                "br",
                                "hr",
                                "input"
                                );

        $force_tag_closing = true;

        $rm_attnames = Array(
                         "/.*/" =>
                         Array(
                               "/target/i",
                               "/^on.*/i",
                               "/^dynsrc/i",
                               "/^data.*/i",
                               "/^lowsrc.*/i"
                               )
                         );

        $bad_attvals = Array(
        "/.*/" =>
            Array(
                "/^src|background/i" =>
                    Array(
                          Array(
                                "/^([\'\"])\s*\S+script\s*:.*([\'\"])/si",
                                "/^([\'\"])\s*mocha\s*:*.*([\'\"])/si",
                                "/^([\'\"])\s*about\s*:.*([\'\"])/si",
                                "/^([\'\"])\s*https*:.*([\'\"])/si",
                                "/^([\'\"])\s*cid*:.*([\'\"])/si"
                                ),
                          Array(
                                "\\1$secremoveimg\\2",
                                "\\1$secremoveimg\\2",
                                "\\1$secremoveimg\\2",
                                "\\1$secremoveimg\\2",
                                "\\1$secremoveimg\\2"
                                )
                        ),
                "/^href|action/i" =>
                    Array(
                          Array(
                                "/^([\'\"])\s*\S+script\s*:.*([\'\"])/si",
                                "/^([\'\"])\s*mocha\s*:*.*([\'\"])/si",
                                "/^([\'\"])\s*about\s*:.*([\'\"])/si"
                                ),
                          Array(
                                "\\1#\\1",
                                "\\1#\\1",
                                "\\1#\\1",
                                "\\1#\\1"
                                )
                        ),
                "/^style/i" =>
                    Array(
                          Array(
                                "/expression/i",
                                "/binding/i",
                                "/behaviou*r/i",
                                "/include-source/i",
                                "/url\s*\(\s*([\'\"])\s*\S+script\s*:.*([\'\"])\s*\)/si",
                                "/url\s*\(\s*([\'\"])\s*mocha\s*:.*([\'\"])\s*\)/si",
                                "/url\s*\(\s*([\'\"])\s*about\s*:.*([\'\"])\s*\)/si",
                                "/(.*)\s*:\s*url\s*\(\s*([\'\"]*)\s*\S+script\s*:.*([\'\"]*)\s*\)/si",
                                "/url\(([\'\"])\s*https*:.*([\'\"])\)/si"
                               ),
                          Array(
                                "idiocy",
                                "idiocy",
                                "idiocy",
                                "idiocy",
                                "url(\\1#\\1)",
                                "url(\\1#\\1)",
                                "url(\\1#\\1)",
                                "url(\\1#\\1)",
                                "url(\\1#\\1)",
                                "\\1:url(\\2#\\3)",
                                "url(\\1$secremoveimg\\1)"
                               )
                          )
                )
        );

        $add_attr_to_tag = Array(
        "/^a$/i" =>
            Array('target'=>'"_new"'
            )
        );

        $trusted_html = sanitize($body,
                  $tag_list,
                  $rm_tags_with_content,
                  $self_closing_tags,
                  $force_tag_closing,
                  $rm_attnames,
                  $bad_attvals,
                  $add_attr_to_tag
                  );

        return $trusted_html;
}