1<?php
2/**
3* MailEngine class
4* @author Brian Wong <bwsource@users.sourceforge.net>
5* @version 04-12-05
6* @package MailEngine
7*
8* Copyright (C) 2003 - 2007 MailZu
9* License: GPL, see LICENSE
10*/
11/**
12* Base directory of application
13*/
14@define('BASE_DIR', dirname(__FILE__) . '/..');
15/**
16* CmnFns class
17*/
18include_once('CmnFns.class.php');
19/**
20* Pear::DB
21*/
22if ($GLOBALS['conf']['app']['safeMode']) {
23        ini_set('include_path', ( dirname(__FILE__) . '/pear/' . PATH_SEPARATOR . ini_get('include_path') ));
24        include_once('pear/Mail/mimeDecode.php');
25}
26else {
27        include_once('Mail/mimeDecode.php');
28}
29
30/**
31* Include htmlfilter class
32*/
33include_once('lib/htmlfilter.php');
34
35
36/**
37* Provide all MIME functionality
38*/
39
40/**
41* Get full MIME type
42* $param The mime structure object
43*/
44function GetCtype($struct) {
45        $ctype_p = strtolower(trim($struct->ctype_primary));
46        $ctype_s = strtolower(trim($struct->ctype_secondary));
47        $type = $ctype_p . '/' . $ctype_s;
48        return $type;
49}
50
51/**
52* Recursively parse MIME structure
53* $param The mime structure object
54*/
55$filelist = array ();
56$errors = array ();
57
58function MsgParseBody($struct) {
59
60        global $filelist;
61        global $errors;
62        $ctype_p = strtolower(trim($struct->ctype_primary));
63        $ctype_s = strtolower(trim($struct->ctype_secondary));
64
65        switch ($ctype_p) {
66          case "multipart":
67            switch ($ctype_s) {
68              case "alternative":
69                // Handle multipart/alternative parts
70                $alt_entity = FindMultiAlt($struct->parts);
71                // Ignore if we return false NEEDS WORK
72                if ($alt_entity) MsgParseBody($alt_entity);
73                break;
74              case "related":
75                // Handle multipart/related parts
76                $rel_entities = FindMultiRel($struct);
77                foreach ($rel_entities as $ent) {
78                  MsgParseBody($ent);
79                }
80                break;
81              default:
82                // Probably multipart/mixed here
83                // Recursively process nested mime entities
84                if ( is_array($struct->parts) || is_object($struct->parts) ) {
85                  foreach ($struct->parts as $cur_part) {
86                    MsgParseBody($cur_part);
87                  }
88                } else {
89                  $errors['Invalid or Corrupt MIME Detected.'] = true;
90                }
91                break;
92            }
93            break;
94
95          case "text":
96            // Do not display attached text types
97            if ( isset($struct->d_parameters['filename']) ) {
98                $attachment = $struct->d_parameters['filename'];
99            } elseif ( isset($struct->d_parameters['name']) ) {
100                $attachment = $struct->d_parameters['name'];
101            } else {
102                $attachment = NULL;
103            }
104            if ($attachment) {
105                array_push($filelist, $attachment);
106                break;
107            }
108            switch ($ctype_s) {
109              // Plain text
110              case "plain":
111                MsgBodyPlainText($struct->body);
112                break;
113              // HTML text
114              case "html":
115                MsgBodyHtmlText($struct->body);
116                break;
117              // Text type we do not support
118              default:
119                $errors['Portions of text could not be displayed'] = true;
120            }
121            break;
122
123          default:
124            // Save the listed filename or notify the
125            // reader that this mail is not displayed completely
126            if ( isset( $struct->d_parameters['filename'] )) {
127               $attachment = $struct->d_parameters['filename'];
128            } else { $attachment = NULL; }
129            $attachment ? array_push($filelist, $attachment) : $errors['Unsupported MIME objects present'] = true;
130
131        }
132}
133
134/**
135* Get the best MIME entity for multipart/alternative
136* Adapted from SqurrelMail
137* $param Array of MIME entities
138* $return Single MIME entity
139*/
140function FindMultiAlt($parts) {
141  $alt_pref = array ('text/plain', 'text/html');
142  $best_view = 0;
143  // Bad Headers sometimes have invalid MIME....
144  if ( is_array($parts) || is_object($parts) ) {
145    foreach ($parts as $cur_part) {
146      $type = GetCtype($cur_part);
147      if ($type == 'multipart/related') {
148        if ( isset( $cur_part->d_parameters['type'] )) { $type = $cur_part->d_parameters['type']; }
149        // Mozilla bug. Mozilla does not provide the parameter type.
150        if (!$type) $type = 'text/html';
151      }
152      $altCount = count($alt_pref);
153      for ($j = $best_view; $j < $altCount; ++$j) {
154        if (($alt_pref[$j] == $type) && ($j >= $best_view)) {
155          $best_view = $j;
156          $struct = $cur_part;
157        }
158      }
159    }
160    return $struct;
161  } else {
162    $errors['Invalid or Corrupt MIME Detected.'] = true;
163  }
164}
165
166/**
167* Get the list of related entities for multipart/related
168* Adapted from SqurrelMail
169* $param multipart/alternative structure
170* @return List of MIME entities
171*/
172function FindMultiRel($struct) {
173  $entities = array();
174  if ( isset( $cur_part->d_parameters['type'] )) { $type = $cur_part->d_parameters['type']; }
175  // Mozilla bug. Mozilla does not provide the parameter type.
176  if (!$type) $type = 'text/html';
177  // Bad Headers sometimes have invalid MIME....
178  if ( is_array($struct->parts) || is_object($struct->parts) ) {
179    foreach ($struct->parts as $part) {
180      if (GetCtype($part) == $type || GetCtype($part) == "multipart/alternative") {
181        array_push($entities,$part);
182      }
183    }
184  } else {
185    $errors['Invalid or Corrupt MIME Detected.'] = true;
186  }
187  return $entities;
188}
189
190// Wrapper script for htmlfilter. Settings taken
191// from SquirrelMail
192function sanitizeHTML($body) {
193    if (isset($_COOKIE['lang']) &&
194        file_exists("img/".substr($_COOKIE['lang'],0,2).".blocked_img.png")) {
195      $secremoveimg = "img/".substr($_COOKIE['lang'],0,2).".blocked_img.png";
196    } else {
197      $secremoveimg = "img/blocked_img.png";
198    }
199        $tag_list = Array(
200                      false,
201                      "object",
202                      "meta",
203                      "html",
204                      "head",
205                      "base",
206                      "link",
207                      "frame",
208                      "iframe",
209                      "plaintext",
210                      "marquee"
211                      );
212
213        $rm_tags_with_content = Array(
214                                  "script",
215                                  "applet",
216                                  "embed",
217                                  "title",
218                                  "frameset",
219                                  "xml",
220                                  "style"
221                                  );
222
223        $self_closing_tags =  Array(
224                                "img",
225                                "br",
226                                "hr",
227                                "input"
228                                );
229
230        $force_tag_closing = true;
231
232        $rm_attnames = Array(
233                         "/.*/" =>
234                         Array(
235                               "/target/i",
236                               "/^on.*/i",
237                               "/^dynsrc/i",
238                               "/^data.*/i",
239                               "/^lowsrc.*/i"
240                               )
241                         );
242
243        $bad_attvals = Array(
244        "/.*/" =>
245            Array(
246                "/^src|background/i" =>
247                    Array(
248                          Array(
249                                "/^([\'\"])\s*\S+script\s*:.*([\'\"])/si",
250                                "/^([\'\"])\s*mocha\s*:*.*([\'\"])/si",
251                                "/^([\'\"])\s*about\s*:.*([\'\"])/si",
252                                "/^([\'\"])\s*https*:.*([\'\"])/si",
253                                "/^([\'\"])\s*cid*:.*([\'\"])/si"
254                                ),
255                          Array(
256                                "\\1$secremoveimg\\2",
257                                "\\1$secremoveimg\\2",
258                                "\\1$secremoveimg\\2",
259                                "\\1$secremoveimg\\2",
260                                "\\1$secremoveimg\\2"
261                                )
262                        ),
263                "/^href|action/i" =>
264                    Array(
265                          Array(
266                                "/^([\'\"])\s*\S+script\s*:.*([\'\"])/si",
267                                "/^([\'\"])\s*mocha\s*:*.*([\'\"])/si",
268                                "/^([\'\"])\s*about\s*:.*([\'\"])/si"
269                                ),
270                          Array(
271                                "\\1#\\1",
272                                "\\1#\\1",
273                                "\\1#\\1",
274                                "\\1#\\1"
275                                )
276                        ),
277                "/^style/i" =>
278                    Array(
279                          Array(
280                                "/expression/i",
281                                "/binding/i",
282                                "/behaviou*r/i",
283                                "/include-source/i",
284                                "/url\s*\(\s*([\'\"])\s*\S+script\s*:.*([\'\"])\s*\)/si",
285                                "/url\s*\(\s*([\'\"])\s*mocha\s*:.*([\'\"])\s*\)/si",
286                                "/url\s*\(\s*([\'\"])\s*about\s*:.*([\'\"])\s*\)/si",
287                                "/(.*)\s*:\s*url\s*\(\s*([\'\"]*)\s*\S+script\s*:.*([\'\"]*)\s*\)/si",
288                                "/url\(([\'\"])\s*https*:.*([\'\"])\)/si"
289                               ),
290                          Array(
291                                "idiocy",
292                                "idiocy",
293                                "idiocy",
294                                "idiocy",
295                                "url(\\1#\\1)",
296                                "url(\\1#\\1)",
297                                "url(\\1#\\1)",
298                                "url(\\1#\\1)",
299                                "url(\\1#\\1)",
300                                "\\1:url(\\2#\\3)",
301                                "url(\\1$secremoveimg\\1)"
302                               )
303                          )
304                )
305        );
306
307        $add_attr_to_tag = Array(
308        "/^a$/i" =>
309            Array('target'=>'"_new"'
310            )
311        );
312
313        $trusted_html = sanitize($body,
314                  $tag_list,
315                  $rm_tags_with_content,
316                  $self_closing_tags,
317                  $force_tag_closing,
318                  $rm_attnames,
319                  $bad_attvals,
320                  $add_attr_to_tag
321                  );
322
323        return $trusted_html;
324}
325