Fix XSS problem with unsanitized style tags in messages [CVE-2011-2023]
[squirrelmail.git] / functions / mime.php
CommitLineData
59177427 1<?php
2ba13803 2
35586184 3/**
8bd0068d 4 * mime.php
5 *
8bd0068d 6 * This contains the functions necessary to detect and decode MIME
7 * messages.
8 *
ae5dddc0 9 * @copyright 1999-2011 The SquirrelMail Project Team
4b4abf93 10 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
8bd0068d 11 * @version $Id$
12 * @package squirrelmail
13 */
b74ba498 14
202bcbcc 15/**
16 * dependency information
17 functions dependency
18 mime_structure
19 class/mime/Message.class.php
20 Message::parseStructure
21 functions/page_header.php
22 displayPageHeader
23 functions/display_messages.php
24 plain_error_message
25 mime_fetch_body
26 functions/imap_general.php
27 sqimap_run_command
28 mime_print_body_lines
29
30
31
32functions/imap.php
33functions/attachment_common.php
34functions/display_messages.php
35
36magicHtml => url_parser
37translateText => url_parser
38
39*/
40
8beafbbc 41
7c7b74b3 42/* -------------------------------------------------------------------------- */
43/* MIME DECODING */
44/* -------------------------------------------------------------------------- */
b74ba498 45
d6c32258 46/**
8bd0068d 47 * Get the MIME structure
48 *
49 * This function gets the structure of a message and stores it in the "message" class.
50 * It will return this object for use with all relevant header information and
51 * fully parsed into the standard "message" object format.
52 */
a4a70693 53function mime_structure ($bodystructure, $flags=array()) {
c9d78ab4 54
3d8371be 55 /* Isolate the body structure and remove beginning and end parenthesis. */
a4a70693 56 $read = trim(substr ($bodystructure, strpos(strtolower($bodystructure), 'bodystructure') + 13));
451f74a2 57 $read = trim(substr ($read, 0, -1));
22efa9fb 58 $i = 0;
59 $msg = Message::parseStructure($read,$i);
2b665f28 60
9de42168 61 if (!is_object($msg)) {
3d8371be 62 global $color, $mailbox;
a48eba8f 63 displayPageHeader( $color, $mailbox );
5e8de8b6 64 $errormessage = _("SquirrelMail could not decode the bodystructure of the message");
8bd0068d 65 $errormessage .= '<br />'._("The bodystructure provided by your IMAP server:").'<br /><br />';
472e7acb 66 $errormessage .= '<pre>' . htmlspecialchars($read) . '</pre>';
ce8c6f42 67 plain_error_message( $errormessage );
3d8371be 68 echo '</body></html>';
9de42168 69 exit;
70 }
a4a70693 71 if (count($flags)) {
7a9e9c89 72 foreach ($flags as $flag) {
e1115979 73//FIXME: please document why it is we have to check the first char of the flag but we then go ahead and do a full string comparison anyway. Is this a speed enhancement? If not, let's keep it simple and just compare the full string and forget the switch block.
7a9e9c89 74 $char = strtoupper($flag{1});
75 switch ($char) {
3d8371be 76 case 'S':
77 if (strtolower($flag) == '\\seen') {
78 $msg->is_seen = true;
79 }
80 break;
81 case 'A':
82 if (strtolower($flag) == '\\answered') {
83 $msg->is_answered = true;
84 }
85 break;
86 case 'D':
87 if (strtolower($flag) == '\\deleted') {
88 $msg->is_deleted = true;
89 }
90 break;
91 case 'F':
92 if (strtolower($flag) == '\\flagged') {
93 $msg->is_flagged = true;
94 }
1a753239 95 else if (strtolower($flag) == '$forwarded') {
96 $msg->is_forwarded = true;
97 }
3d8371be 98 break;
99 case 'M':
100 if (strtolower($flag) == '$mdnsent') {
101 $msg->is_mdnsent = true;
102 }
103 break;
104 default:
105 break;
7a9e9c89 106 }
107 }
451f74a2 108 }
7a9e9c89 109 // listEntities($msg);
3d8371be 110 return $msg;
451f74a2 111}
b74ba498 112
22efa9fb 113
114
3d8371be 115/* This starts the parsing of a particular structure. It is called recursively,
8bd0068d 116 * so it can be passed different structures. It returns an object of type
117 * $message.
118 * First, it checks to see if it is a multipart message. If it is, then it
119 * handles that as it sees is necessary. If it is just a regular entity,
120 * then it parses it and adds the necessary header information (by calling out
121 * to mime_get_elements()
122 */
451f74a2 123
4d592352 124function mime_fetch_body($imap_stream, $id, $ent_id=1, $fetch_size=0) {
3d8371be 125 /* Do a bit of error correction. If we couldn't find the entity id, just guess
8bd0068d 126 * that it is the first one. That is usually the case anyway.
127 */
7c7b74b3 128
09a4bde3 129 if (!$ent_id) {
08b7f7cc 130 $cmd = "FETCH $id BODY[]";
1035e159 131 } else {
08b7f7cc 132 $cmd = "FETCH $id BODY[$ent_id]";
09a4bde3 133 }
3d8371be 134
4d592352 135 if ($fetch_size!=0) $cmd .= "<0.$fetch_size>";
da2415c1 136
6201339c 137 $data = sqimap_run_command ($imap_stream, $cmd, true, $response, $message, TRUE);
77b88425 138 do {
3d8371be 139 $topline = trim(array_shift($data));
140 } while($topline && ($topline[0] == '*') && !preg_match('/\* [0-9]+ FETCH.*/i', $topline)) ;
a4a70693 141
451f74a2 142 $wholemessage = implode('', $data);
b7910e12 143 if (preg_match('/\{([^\}]*)\}/', $topline, $regs)) {
3d8371be 144 $ret = substr($wholemessage, 0, $regs[1]);
145 /* There is some information in the content info header that could be important
8bd0068d 146 * in order to parse html messages. Let's get them here.
147 */
0600bdf1 148// if ($ret{0} == '<') {
6201339c 149// $data = sqimap_run_command ($imap_stream, "FETCH $id BODY[$ent_id.MIME]", true, $response, $message, TRUE);
0600bdf1 150// }
b7910e12 151 } else if (preg_match('/"([^"]*)"/', $topline, $regs)) {
451f74a2 152 $ret = $regs[1];
06bcb9c3 153 } else if ((stristr($topline, 'nil') !== false) && (empty($wholemessage))) {
154 $ret = $wholemessage;
451f74a2 155 } else {
156 global $where, $what, $mailbox, $passed_id, $startMessage;
3d8371be 157 $par = 'mailbox=' . urlencode($mailbox) . '&amp;passed_id=' . $passed_id;
451f74a2 158 if (isset($where) && isset($what)) {
3d8371be 159 $par .= '&amp;where=' . urlencode($where) . '&amp;what=' . urlencode($what);
a3daaaf3 160 } else {
3d8371be 161 $par .= '&amp;startMessage=' . $startMessage . '&amp;show_more=0';
451f74a2 162 }
e5ea9327 163 $par .= '&amp;response=' . urlencode($response) .
8bd0068d 164 '&amp;message=' . urlencode($message) .
165 '&amp;topline=' . urlencode($topline);
a019eeb8 166
8bd0068d 167 echo '<tt><br />' .
02474e43 168 '<table width="80%"><tr>' .
169 '<tr><td colspan="2">' .
170 _("Body retrieval error. The reason for this is most probably that the message is malformed.") .
171 '</td></tr>' .
172 '<tr><td><b>' . _("Command:") . "</td><td>$cmd</td></tr>" .
173 '<tr><td><b>' . _("Response:") . "</td><td>$response</td></tr>" .
174 '<tr><td><b>' . _("Message:") . "</td><td>$message</td></tr>" .
175 '<tr><td><b>' . _("FETCH line:") . "</td><td>$topline</td></tr>" .
176 "</table><br /></tt></font><hr />";
346817d4 177
6201339c 178 $data = sqimap_run_command ($imap_stream, "FETCH $passed_id BODY[]", true, $response, $message, TRUE);
451f74a2 179 array_shift($data);
180 $wholemessage = implode('', $data);
a019eeb8 181
346817d4 182 $ret = $wholemessage;
a3daaaf3 183 }
3d8371be 184 return $ret;
451f74a2 185}
d4467150 186
fa26ab45 187function mime_print_body_lines ($imap_stream, $id, $ent_id=1, $encoding, $rStream='php://stdout', $force_crlf='') {
1035e159 188
3d8371be 189 /* Don't kill the connection if the browser is over a dialup
8bd0068d 190 * and it would take over 30 seconds to download it.
191 * Don't call set_time_limit in safe mode.
192 */
b7206e1d 193
3d8371be 194 if (!ini_get('safe_mode')) {
b7206e1d 195 set_time_limit(0);
196 }
7c7b74b3 197 /* in case of base64 encoded attachments, do not buffer them.
8bd0068d 198 Instead, echo the decoded attachment directly to screen */
7c7b74b3 199 if (strtolower($encoding) == 'base64') {
200 if (!$ent_id) {
7591f143 201 $query = "FETCH $id BODY[]";
7c7b74b3 202 } else {
7591f143 203 $query = "FETCH $id BODY[$ent_id]";
7c7b74b3 204 }
7591f143 205 sqimap_run_command($imap_stream,$query,true,$response,$message,TRUE,'sqimap_base64_decode',$rStream,true);
1d142b8d 206 } else {
7591f143 207 $body = mime_fetch_body ($imap_stream, $id, $ent_id);
208 if (is_resource($rStream)) {
fa26ab45 209 fputs($rStream,decodeBody($body, $encoding, $force_crlf));
7591f143 210 } else {
fa26ab45 211 echo decodeBody($body, $encoding, $force_crlf);
7591f143 212 }
1d142b8d 213 }
346817d4 214
da2415c1 215 /*
8bd0068d 216 TODO, use the same method for quoted printable.
217 However, I assume that quoted printable attachments aren't that large
218 so the performancegain / memory usage drop will be minimal.
219 If we decide to add that then we need to adapt sqimap_fread because
220 we need to split te result on \n and fread doesn't stop at \n. That
221 means we also should provide $results from sqimap_fread (by ref) to
222 te function and set $no_return to false. The $filter function for
223 quoted printable should handle unsetting of $results.
224 */
da2415c1 225 /*
8bd0068d 226 TODO 2: find out how we write to the output stream php://stdout. fwrite
227 doesn't work because 'php://stdout isn't a stream.
228 */
7c7b74b3 229
5d9c6f73 230 return;
451f74a2 231}
beb9e459 232
451f74a2 233/* -[ END MIME DECODING ]----------------------------------------------------------- */
d4467150 234
3d8371be 235/* This is here for debugging purposes. It will print out a list
8bd0068d 236 * of all the entity IDs that are in the $message object.
237 */
451f74a2 238function listEntities ($message) {
3d8371be 239 if ($message) {
3c621ba1 240 echo "<tt>" . $message->entity_id . ' : ' . $message->type0 . '/' . $message->type1 . ' parent = '. $message->parent->entity_id. '<br />';
3d8371be 241 for ($i = 0; isset($message->entities[$i]); $i++) {
242 echo "$i : ";
243 $msg = listEntities($message->entities[$i]);
244
245 if ($msg) {
246 echo "return: ";
247 return $msg;
248 }
249 }
a4a70693 250 }
451f74a2 251}
f0c4dc12 252
f792c641 253function getPriorityStr($priority) {
3d8371be 254 $priority_level = substr($priority,0,1);
255
256 switch($priority_level) {
257 /* Check for a higher then normal priority. */
258 case '1':
259 case '2':
260 $priority_string = _("High");
261 break;
262
263 /* Check for a lower then normal priority. */
264 case '4':
265 case '5':
266 $priority_string = _("Low");
267 break;
268
269 /* Check for a normal priority. */
270 case '3':
271 default:
272 $priority_level = '3';
273 $priority_string = _("Normal");
274 break;
275
276 }
277 return $priority_string;
f792c641 278}
279
451f74a2 280/* returns a $message object for a particular entity id */
281function getEntity ($message, $ent_id) {
a4a70693 282 return $message->getEntity($ent_id);
451f74a2 283}
8beafbbc 284
3d8371be 285/* translateText
8bd0068d 286 * Extracted from strings.php 23/03/2002
287 */
da4c66e8 288
289function translateText(&$body, $wrap_at, $charset) {
3d8371be 290 global $where, $what; /* from searching */
291 global $color; /* color theme */
da4c66e8 292
202bcbcc 293 // require_once(SM_PATH . 'functions/url_parser.php');
da4c66e8 294
295 $body_ary = explode("\n", $body);
da4c66e8 296 for ($i=0; $i < count($body_ary); $i++) {
4c1f4be3 297 $line = rtrim($body_ary[$i],"\r");
298
da4c66e8 299 if (strlen($line) - 2 >= $wrap_at) {
c7aff938 300 sqWordWrap($line, $wrap_at, $charset);
da4c66e8 301 }
302 $line = charset_decode($charset, $line);
303 $line = str_replace("\t", ' ', $line);
304
305 parseUrl ($line);
306
3d8371be 307 $quotes = 0;
da4c66e8 308 $pos = 0;
3d8371be 309 $j = strlen($line);
da4c66e8 310
3d8371be 311 while ($pos < $j) {
da4c66e8 312 if ($line[$pos] == ' ') {
3d8371be 313 $pos++;
da4c66e8 314 } else if (strpos($line, '&gt;', $pos) === $pos) {
315 $pos += 4;
3d8371be 316 $quotes++;
da4c66e8 317 } else {
318 break;
319 }
320 }
3d8371be 321
83c94382 322 if ($quotes % 2) {
d0814c02 323 $line = '<span class="quote1">' . $line . '</span>';
4c25967c 324 } elseif ($quotes) {
d0814c02 325 $line = '<span class="quote2">' . $line . '</span>';
da4c66e8 326 }
3d8371be 327
da4c66e8 328 $body_ary[$i] = $line;
329 }
330 $body = '<pre>' . implode("\n", $body_ary) . '</pre>';
331}
332
a2bfcbce 333/**
da1b55ad 334 * This returns a parsed string called $body. That string can then
335 * be displayed as the actual message in the HTML. It contains
336 * everything needed, including HTML Tags, Attachments at the
337 * bottom, etc.
f8a1ed5a 338 *
da1b55ad 339 * Since 1.2.0 function uses message_body hook.
340 * Till 1.3.0 function included output of formatAttachments().
341 *
342 * @param resource $imap_stream imap connection resource
343 * @param object $message squirrelmail message object
344 * @param array $color squirrelmail color theme array
345 * @param integer $wrap_at number of characters per line
346 * @param string $ent_num (since 1.3.0) message part id
347 * @param integer $id (since 1.3.0) message id
348 * @param string $mailbox (since 1.3.0) imap folder name
da1b55ad 349 * @return string html formated message text
350 */
a540f994 351function formatBody($imap_stream, $message, $color, $wrap_at, $ent_num, $id, $mailbox='INBOX') {
3d8371be 352 /* This if statement checks for the entity to show as the
8bd0068d 353 * primary message. To add more of them, just put them in the
354 * order that is their priority.
355 */
ce68b76b 356 global $startMessage, $languages, $squirrelmail_language,
40a34e57 357 $show_html_default, $sort, $has_unsafe_images, $passed_ent_id,
14c85e39 358 $use_iframe, $iframe_height, $download_and_unsafe_link,
955bfc8f 359 $download_href, $unsafe_image_toggle_href, $unsafe_image_toggle_text,
551c7b53 360 $oTemplate, $nbsp;
2c25d36a 361
362 // workaround for not updated config.php
363 if (! isset($use_iframe)) $use_iframe = false;
77bfbd2e 364
d31f73f1 365 // If there's no "view_unsafe_images" variable in the URL, turn unsafe
366 // images off by default.
afbf184c 367 sqgetGlobalVar('view_unsafe_images', $view_unsafe_images, SQ_GET, FALSE);
d03c24f4 368
cc34b00d 369 $body = '';
23bcec6f 370 $urlmailbox = urlencode($mailbox);
451f74a2 371 $body_message = getEntity($message, $ent_num);
372 if (($body_message->header->type0 == 'text') ||
8bd0068d 373 ($body_message->header->type0 == 'rfc822')) {
3d8371be 374 $body = mime_fetch_body ($imap_stream, $id, $ent_num);
451f74a2 375 $body = decodeBody($body, $body_message->header->encoding);
e842b215 376
377 if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
8bd0068d 378 function_exists($languages[$squirrelmail_language]['XTRA_CODE'] . '_decode')) {
e842b215 379 if (mb_detect_encoding($body) != 'ASCII') {
33a55f5a 380 $body = call_user_func($languages[$squirrelmail_language]['XTRA_CODE'] . '_decode',$body);
e842b215 381 }
382 }
d849b570 383
384 /* As of 1.5.2, $body is passed (and modified) by reference */
385 do_hook('message_body', $body);
23bcec6f 386
3d8371be 387 /* If there are other types that shouldn't be formatted, add
8bd0068d 388 * them here.
389 */
3d8371be 390
451f74a2 391 if ($body_message->header->type1 == 'html') {
3d8371be 392 if ($show_html_default <> 1) {
85015544 393 $entity_conv = array('&nbsp;' => ' ',
8bd0068d 394 '<p>' => "\n",
395 '<P>' => "\n",
396 '<br>' => "\n",
397 '<BR>' => "\n",
398 '<br />' => "\n",
399 '<BR />' => "\n",
400 '&gt;' => '>',
401 '&lt;' => '<');
85015544 402 $body = strtr($body, $entity_conv);
3d8371be 403 $body = strip_tags($body);
85015544 404 $body = trim($body);
405 translateText($body, $wrap_at,
8bd0068d 406 $body_message->header->getParameter('charset'));
a540f994 407 } elseif ($use_iframe) {
84410f31 408 /**
409 * If we don't add html message between iframe tags,
410 * we must detect unsafe images and modify $has_unsafe_images.
f8a1ed5a 411 */
758a7889 412 $html_body = magicHTML($body, $id, $message, $mailbox);
b6c52e61 413 // Convert character set in order to display html mails in different character set
414 $html_body = charset_decode($body_message->header->getParameter('charset'),$html_body,false,true);
84410f31 415
2c25d36a 416 // creating iframe url
417 $iframeurl=sqm_baseuri().'src/view_html.php?'
f8a1ed5a 418 . 'mailbox=' . $urlmailbox
2c25d36a 419 . '&amp;passed_id=' . $id
420 . '&amp;ent_id=' . $ent_num
421 . '&amp;view_unsafe_images=' . (int) $view_unsafe_images;
422
79d58d4c 423 global $oTemplate;
424 $oTemplate->assign('iframe_url', $iframeurl);
51554708 425 $oTemplate->assign('iframe_height', $iframe_height);
79d58d4c 426 $oTemplate->assign('html_body', $html_body);
2b665f28 427
79d58d4c 428 $body = $oTemplate->fetch('read_html_iframe.tpl');
a3daaaf3 429 } else {
2c25d36a 430 // old way of html rendering
b6c52e61 431 /**
758a7889 432 * convert character set. charset_decode does not remove html special chars
b6c52e61 433 * applied by magicHTML functions and does not sanitize them second time if
758a7889 434 * fourth argument is true.
435 */
567dc524 436 $charset = $body_message->header->getParameter('charset');
437 if (!empty($charset)) {
438 $body = charset_decode($charset,$body,false,true);
439 }
440 $body = magicHTML($body, $id, $message, $mailbox);
a3daaaf3 441 }
451f74a2 442 } else {
3d8371be 443 translateText($body, $wrap_at,
8bd0068d 444 $body_message->header->getParameter('charset'));
451f74a2 445 }
a2bfcbce 446
9ebace19 447 /*
448 * Previously the links for downloading and unsafe images were printed
449 * under the mail. By putting the links in a global variable we can
450 * print it in the toolbar where it belongs. Since the original code was
451 * in this place it's left here. It might be possible to move it to some
452 * other place if that makes sense. The possibility to do so has not
453 * been evaluated yet.
454 */
455
456 // Initialize the global variable to an empty string.
457 // FIXME: To have $download_and_unsafe_link as a global variable might not be needed since the use of separate variables ($download_href, $unsafe_image_toggle_href, and $unsafe_image_toggle_text) for the templates was introduced.
40a34e57 458 $download_and_unsafe_link = '';
459
9ebace19 460 // Prepare and build a link for downloading the mail.
83cf04bd 461 $link = 'passed_id=' . $id . '&amp;ent_id='.$ent_num.
8bd0068d 462 '&amp;mailbox=' . $urlmailbox .'&amp;sort=' . $sort .
463 '&amp;startMessage=' . $startMessage . '&amp;show_more=0';
08b7f7cc 464 if (isset($passed_ent_id)) {
465 $link .= '&amp;passed_ent_id='.$passed_ent_id;
466 }
14c85e39 467 $download_href = SM_PATH . 'src/download.php?absolute_dl=true&amp;' . $link;
9ebace19 468
469 // Always add the link for downloading the mail as a file to the global
470 // variable.
955bfc8f 471 $download_and_unsafe_link .= "$nbsp|$nbsp"
472 . create_hyperlink($download_href, _("Download this as a file"));
9ebace19 473
474 // Find out the right text to use in the link depending on the
475 // circumstances. If the unsafe images are displayed the link should
476 // hide them, if they aren't displayed the link should only appear if
477 // the mail really contains unsafe images.
7aad7b77 478 if ($view_unsafe_images) {
23f617b8 479 $text = _("Hide Unsafe Images");
7aad7b77 480 } else {
08b7f7cc 481 if (isset($has_unsafe_images) && $has_unsafe_images) {
482 $link .= '&amp;view_unsafe_images=1';
483 $text = _("View Unsafe Images");
484 } else {
485 $text = '';
486 }
3d8371be 487 }
9ebace19 488
489 // Only create a link for unsafe images if there's need for one. If so:
490 // add it to the global variable.
83cf04bd 491 if($text != '') {
14c85e39 492 $unsafe_image_toggle_href = SM_PATH . 'src/read_body.php?'.$link;
493 $unsafe_image_toggle_text = $text;
955bfc8f 494 $download_and_unsafe_link .= "$nbsp|$nbsp"
495 . create_hyperlink($unsafe_image_toggle_href, $text);
83cf04bd 496 }
3d8371be 497 }
498 return $body;
451f74a2 499}
b74ba498 500
da1b55ad 501/**
a540f994 502 * Generate attachments array for passing to templates.
2b665f28 503 *
d67f519a 504 * @since 1.5.2
da1b55ad 505 * @param object $message SquirrelMail message object
506 * @param array $exclude_id message parts that are not attachments.
507 * @param string $mailbox mailbox name
508 * @param integer $id message id
da1b55ad 509 */
d67f519a 510function buildAttachmentArray($message, $exclude_id, $mailbox, $id) {
511 global $where, $what, $startMessage, $color, $passed_ent_id, $base_uri;
451f74a2 512
23bcec6f 513 $att_ar = $message->getAttachments($exclude_id);
23bcec6f 514 $urlMailbox = urlencode($mailbox);
515
d67f519a 516 $attachments = array();
23bcec6f 517 foreach ($att_ar as $att) {
fdc9d9b5 518 $ent = $att->entity_id;
2e25760a 519 $header = $att->header;
f0c4dc12 520 $type0 = strtolower($header->type0);
521 $type1 = strtolower($header->type1);
2e25760a 522 $name = '';
d0187bd6 523 $links = array();
21dab2dc 524 $links['download link']['text'] = _("Download");
202bcbcc 525 $links['download link']['href'] = $base_uri .
8bd0068d 526 "src/download.php?absolute_dl=true&amp;passed_id=$id&amp;mailbox=$urlMailbox&amp;ent_id=$ent";
2b665f28 527
2e25760a 528 if ($type0 =='message' && $type1 == 'rfc822') {
202bcbcc 529 $default_page = $base_uri . 'src/read_body.php';
2e25760a 530 $rfc822_header = $att->rfc822_header;
098ea084 531 $filename = $rfc822_header->subject;
6cc08d8b 532 if (trim( $filename ) == '') {
533 $filename = 'untitled-[' . $ent . ']' ;
08b7f7cc 534 }
2e25760a 535 $from_o = $rfc822_header->from;
536 if (is_object($from_o)) {
04ea844e 537 $from_name = decodeHeader($from_o->getAddress(false));
7e697748 538 } elseif (is_array($from_o) && count($from_o) && is_object($from_o[0])) {
539 // something weird happens when a digest message is opened and you return to the digest
540 // now the from object is part of an array. Probably the parseHeader call overwrites the info
541 // retrieved from the bodystructure in a different way. We need to fix this later.
542 // possible starting point, do not fetch header we already have and inspect how
543 // the rfc822_header object behaves.
544 $from_name = decodeHeader($from_o[0]->getAddress(false));
2e25760a 545 } else {
546 $from_name = _("Unknown sender");
f0c4dc12 547 }
d0187bd6 548 $description = _("From").': '.$from_name;
23bcec6f 549 } else {
202bcbcc 550 $default_page = $base_uri . 'src/download.php';
02474e43 551 $filename = $att->getFilename();
f810c0b2 552 if ($header->description) {
098ea084 553 $description = decodeHeader($header->description);
f810c0b2 554 } else {
3d8371be 555 $description = '';
556 }
2e25760a 557 }
558
559 $display_filename = $filename;
3d8371be 560 if (isset($passed_ent_id)) {
561 $passed_ent_id_link = '&amp;passed_ent_id='.$passed_ent_id;
562 } else {
563 $passed_ent_id_link = '';
564 }
565 $defaultlink = $default_page . "?startMessage=$startMessage"
8bd0068d 566 . "&amp;passed_id=$id&amp;mailbox=$urlMailbox"
567 . '&amp;ent_id='.$ent.$passed_ent_id_link;
2e25760a 568 if ($where && $what) {
8bd0068d 569 $defaultlink .= '&amp;where='. urlencode($where).'&amp;what='.urlencode($what);
2e25760a 570 }
7e2ff844 571 // IE does make use of mime content sniffing. Forcing a download
572 // prohibit execution of XSS inside an application/octet-stream attachment
573 if ($type0 == 'application' && $type1 == 'octet-stream') {
574 $defaultlink .= '&amp;absolute_dl=true';
575 }
2b665f28 576
3d8371be 577 /* This executes the attachment hook with a specific MIME-type.
53901c7b 578 * It also allows plugins to run if there's a rule for a more
579 * generic type. Finally, a hook for ALL attachment types is
580 * run as well.
8bd0068d 581 */
8dca4d22 582 // First remember the default link.
583 $defaultlink_orig = $defaultlink;
584
d849b570 585 /* The API for this hook has changed as of 1.5.2 so that all plugin
586 arguments are passed in an array instead of each their own plugin
587 argument, and arguments are passed by reference, so instead of
588 returning any changes, changes should simply be made to the original
589 arguments themselves. */
53901c7b 590 $temp = array(&$links, &$startMessage, &$id, &$urlMailbox, &$ent,
9c3b2d22 591 &$defaultlink, &$display_filename, &$where, &$what);
592 do_hook("attachment $type0/$type1", $temp);
53901c7b 593 /* The API for this hook has changed as of 1.5.2 so that all plugin
594 arguments are passed in an array instead of each their own plugin
595 argument, and arguments are passed by reference, so instead of
596 returning any changes, changes should simply be made to the original
597 arguments themselves. */
598 $temp = array(&$links, &$startMessage, &$id, &$urlMailbox, &$ent,
599 &$defaultlink, &$display_filename, &$where, &$what);
600 // Do not let a generic plugin change the default link if a more
601 // specialized one already did it...
602 if ($defaultlink != $defaultlink_orig) {
603 $dummy = '';
604 $temp[5] = &$dummy;
2e25760a 605 }
53901c7b 606 do_hook("attachment $type0/*", $temp);
d849b570 607 /* The API for this hook has changed as of 1.5.2 so that all plugin
608 arguments are passed in an array instead of each their own plugin
609 argument, and arguments are passed by reference, so instead of
610 returning any changes, changes should simply be made to the original
611 arguments themselves. */
9c3b2d22 612 $temp = array(&$links, &$startMessage, &$id, &$urlMailbox, &$ent,
613 &$defaultlink, &$display_filename, &$where, &$what);
8dca4d22 614 // Do not let a generic plugin change the default link if a more
615 // specialized one already did it...
616 if ($defaultlink != $defaultlink_orig) {
617 $dummy = '';
618 $temp[5] = &$dummy;
619 }
9c3b2d22 620 do_hook("attachment */*", $temp);
77b88425 621
d67f519a 622 $this_attachment = array();
623 $this_attachment['Name'] = decodeHeader($display_filename);
624 $this_attachment['Description'] = $description;
625 $this_attachment['DefaultHREF'] = $defaultlink;
626 $this_attachment['DownloadHREF'] = $links['download link']['href'];
627 $this_attachment['ViewHREF'] = isset($links['attachment_common']) ? $links['attachment_common']['href'] : '';
628 $this_attachment['Size'] = $header->size;
629 $this_attachment['ContentType'] = htmlspecialchars($type0 .'/'. $type1);
630 $this_attachment['OtherLinks'] = array();
3d8371be 631 foreach ($links as $val) {
d0187bd6 632 if ($val['text']==_("Download") || $val['text'] == _("View"))
633 continue;
634 if (empty($val['text']) && empty($val['extra']))
635 continue;
2b665f28 636
d67f519a 637 $temp = array();
638 $temp['HREF'] = $val['href'];
639 $temp['Text'] = (empty($val['text']) ? '' : $val['text']) . (empty($val['extra']) ? '' : $val['extra']);
640 $this_attachment['OtherLinks'][] = $temp;
2e25760a 641 }
d67f519a 642 $attachments[] = $this_attachment;
2b665f28 643
3d8371be 644 unset($links);
2e25760a 645 }
2b665f28 646
d67f519a 647 return $attachments;
648}
649
650/**
651 * Displays attachment links and information
652 *
653 * Since 1.3.0 function is not included in formatBody() call.
654 *
655 * Since 1.0.2 uses attachment $type0/$type1 hook.
656 * Since 1.2.5 uses attachment $type0/* hook.
657 * Since 1.5.0 uses attachments_bottom hook.
658 * Since 1.5.2 uses templates and does *not* return a value.
659 *
660 * @param object $message SquirrelMail message object
661 * @param array $exclude_id message parts that are not attachments.
662 * @param string $mailbox mailbox name
663 * @param integer $id message id
664 */
665function formatAttachments($message, $exclude_id, $mailbox, $id) {
666 global $oTemplate;
2b665f28 667
d67f519a 668 $attach = buildAttachmentArray($message, $exclude_id, $mailbox, $id);
d0187bd6 669
670 $oTemplate->assign('attachments', $attach);
671 $oTemplate->display('read_attachments.tpl');
451f74a2 672}
b74ba498 673
7c7b74b3 674function sqimap_base64_decode(&$string) {
7c0ec1d8 675
b17a8968 676 // Base64 encoded data goes in pairs of 4 bytes. To achieve on the
7c0ec1d8 677 // fly decoding (to reduce memory usage) you have to check if the
678 // data has incomplete pairs
679
b17a8968 680 // Remove the noise in order to check if the 4 bytes pairs are complete
7c0ec1d8 681 $string = str_replace(array("\r\n","\n", "\r", " "),array('','','',''),$string);
682
42ce44f8 683 $sStringRem = '';
7c0ec1d8 684 $iMod = strlen($string) % 4;
685 if ($iMod) {
686 $sStringRem = substr($string,-$iMod);
b17a8968 687 // Check if $sStringRem contains padding characters
7c0ec1d8 688 if (substr($sStringRem,-1) != '=') {
689 $string = substr($string,0,-$iMod);
690 } else {
691 $sStringRem = '';
692 }
693 }
7c7b74b3 694 $string = base64_decode($string);
7c0ec1d8 695 return $sStringRem;
7c7b74b3 696}
697
fdf7cef1 698/**
fa26ab45 699 * Decodes encoded string (usually message body)
700 *
701 * This function decodes a string (usually the message body)
702 * depending on the encoding type. Currently quoted-printable
703 * and base64 encodings are supported.
704 *
705 * The decode_body hook was added to this function in 1.4.2/1.5.0.
706 * The $force_crlf parameter was added in 1.5.2.
707 *
708 * @param string $string The encoded string
709 * @param string $encoding used encoding
710 * @param string $force_crlf Whether or not to force CRLF or LF
711 * line endings (or to leave as is).
712 * If given as "LF", line endings will
713 * all be converted to LF; if "CRLF",
714 * line endings will all be converted
715 * to CRLF. If given as an empty value,
9785376e 716 * the global $force_crlf_default will
fa26ab45 717 * be consulted (it can be specified in
718 * config/config_local.php). Otherwise,
719 * any other value will cause the string
720 * to be left alone. Note that this will
721 * be overridden to "LF" if not using at
722 * least PHP version 4.3.0. (OPTIONAL;
723 * default is empty - consult global
724 * default value)
725 *
726 * @return string The decoded string
fdf7cef1 727 *
fdf7cef1 728 * @since 1.0
fa26ab45 729 *
fdf7cef1 730 */
fa26ab45 731function decodeBody($string, $encoding, $force_crlf='') {
732
733 global $force_crlf_default;
734 if (empty($force_crlf)) $force_crlf = $force_crlf_default;
735 $force_crlf = strtoupper($force_crlf);
736
737 // must force line endings to LF due to broken
738 // quoted_printable_decode() in PHP versions
739 // before 4.3.0 (see below)
740 //
741 if (!check_php_version(4, 3, 0) || $force_crlf == 'LF')
742 $string = str_replace("\r\n", "\n", $string);
743 else if ($force_crlf == 'CRLF')
744 $string = str_replace("\n", "\r\n", $string);
83be314a 745
b583c3e8 746 $encoding = strtolower($encoding);
3d8371be 747
d849b570 748 $encoding_handler = do_hook('decode_body', $encoding);
5166f86a 749
750
fa26ab45 751 // plugins get first shot at decoding the string
5166f86a 752 //
753 if (!empty($encoding_handler) && function_exists($encoding_handler)) {
fa26ab45 754 $string = $encoding_handler('decode', $string);
5166f86a 755
fdf7cef1 756 } elseif ($encoding == 'quoted-printable' ||
8bd0068d 757 $encoding == 'quoted_printable') {
fa26ab45 758
759 // quoted_printable_decode() function is broken in older
760 // php versions. Text with \r\n decoding was fixed only
761 // in php 4.3.0. Minimal code requirement is PHP 4.0.4+
762 // and the above call to: str_replace("\r\n", "\n", $string);
763 //
764 $string = quoted_printable_decode($string);
765
fdf7cef1 766 } elseif ($encoding == 'base64') {
fa26ab45 767 $string = base64_decode($string);
b583c3e8 768 }
3d8371be 769
b583c3e8 770 // All other encodings are returned raw.
fa26ab45 771 return $string;
451f74a2 772}
773
9f7f68c3 774/**
8bd0068d 775 * Decodes headers
776 *
e1115979 777 * This function decodes strings that are encoded according to
8bd0068d 778 * RFC1522 (MIME Part Two: Message Header Extensions for Non-ASCII Text).
779 * Patched by Christian Schmidt <christian@ostenfeld.dk> 23/03/2002
780 *
781 * @param string $string header string that has to be made readable
782 * @param boolean $utfencode change message in order to be readable on user's charset. defaults to true
bcae324b 783 * @param boolean $htmlsafe preserve spaces and sanitize html special characters. defaults to true
8bd0068d 784 * @param boolean $decide decide if string can be utfencoded. defaults to false
785 * @return string decoded header string
786 */
bcae324b 787function decodeHeader ($string, $utfencode=true,$htmlsafe=true,$decide=false) {
d6f584fc 788 global $languages, $squirrelmail_language,$default_charset;
79e07c7e 789 if (is_array($string)) {
790 $string = implode("\n", $string);
791 }
da2415c1 792
10dec454 793 if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
8bd0068d 794 function_exists($languages[$squirrelmail_language]['XTRA_CODE'] . '_decodeheader')) {
33a55f5a 795 $string = call_user_func($languages[$squirrelmail_language]['XTRA_CODE'] . '_decodeheader', $string);
08b7f7cc 796 // Do we need to return at this point?
797 // return $string;
83be314a 798 }
79e07c7e 799 $i = 0;
08b7f7cc 800 $iLastMatch = -2;
db65b6b0 801 $encoded = true;
0a06275a 802
098ea084 803 $aString = explode(' ',$string);
08b7f7cc 804 $ret = '';
098ea084 805 foreach ($aString as $chunk) {
358a78a1 806 if ($encoded && $chunk === '') {
08b7f7cc 807 continue;
358a78a1 808 } elseif ($chunk === '') {
08b7f7cc 809 $ret .= ' ';
810 continue;
811 }
098ea084 812 $encoded = false;
08b7f7cc 813 /* if encoded words are not separated by a linear-space-white we still catch them */
814 $j = $i-1;
7e6ca3e8 815
08b7f7cc 816 while ($match = preg_match('/^(.*)=\?([^?]*)\?(Q|B)\?([^?]*)\?=(.*)$/Ui',$chunk,$res)) {
817 /* if the last chunk isn't an encoded string then put back the space, otherwise don't */
818 if ($iLastMatch !== $j) {
bcae324b 819 if ($htmlsafe) {
9f7f68c3 820 $ret .= '&#32;';
08b7f7cc 821 } else {
822 $ret .= ' ';
823 }
824 }
825 $iLastMatch = $i;
826 $j = $i;
bcae324b 827 if ($htmlsafe) {
cb718de0 828 $ret .= htmlspecialchars($res[1]);
829 } else {
830 $ret .= $res[1];
831 }
098ea084 832 $encoding = ucfirst($res[3]);
d6f584fc 833
834 /* decide about valid decoding */
835 if ($decide && is_conversion_safe($res[2])) {
8bd0068d 836 $utfencode=true;
837 $can_be_encoded=true;
d6f584fc 838 } else {
8bd0068d 839 $can_be_encoded=false;
d6f584fc 840 }
098ea084 841 switch ($encoding)
842 {
8bd0068d 843 case 'B':
844 $replace = base64_decode($res[4]);
845 if ($utfencode) {
846 if ($can_be_encoded) {
847 /* convert string to different charset,
848 * if functions asks for it (usually in compose)
849 */
bcae324b 850 $ret .= charset_convert($res[2],$replace,$default_charset,$htmlsafe);
8bd0068d 851 } else {
852 // convert string to html codes in order to display it
853 $ret .= charset_decode($res[2],$replace);
854 }
fab65ca9 855 } else {
bcae324b 856 if ($htmlsafe) {
8bd0068d 857 $replace = htmlspecialchars($replace);
858 }
859 $ret.= $replace;
fab65ca9 860 }
8bd0068d 861 break;
862 case 'Q':
863 $replace = str_replace('_', ' ', $res[4]);
864 $replace = preg_replace('/=([0-9a-f]{2})/ie', 'chr(hexdec("\1"))',
865 $replace);
866 if ($utfencode) {
867 if ($can_be_encoded) {
868 /* convert string to different charset,
869 * if functions asks for it (usually in compose)
870 */
bcae324b 871 $replace = charset_convert($res[2], $replace,$default_charset,$htmlsafe);
8bd0068d 872 } else {
873 // convert string to html codes in order to display it
874 $replace = charset_decode($res[2], $replace);
875 }
876 } else {
bcae324b 877 if ($htmlsafe) {
8bd0068d 878 $replace = htmlspecialchars($replace);
879 }
098ea084 880 }
8bd0068d 881 $ret .= $replace;
882 break;
883 default:
884 break;
79e07c7e 885 }
098ea084 886 $chunk = $res[5];
887 $encoded = true;
08b7f7cc 888 }
889 if (!$encoded) {
bcae324b 890 if ($htmlsafe) {
9f7f68c3 891 $ret .= '&#32;';
08b7f7cc 892 } else {
893 $ret .= ' ';
da2415c1 894 }
08b7f7cc 895 }
dc3d13a7 896
bcae324b 897 if (!$encoded && $htmlsafe) {
dc3d13a7 898 $ret .= htmlspecialchars($chunk);
899 } else {
900 $ret .= $chunk;
901 }
098ea084 902 ++$i;
903 }
fd81e884 904 /* remove the first added space */
905 if ($ret) {
bcae324b 906 if ($htmlsafe) {
9f7f68c3 907 $ret = substr($ret,5);
fd81e884 908 } else {
909 $ret = substr($ret,1);
910 }
911 }
da2415c1 912
08b7f7cc 913 return $ret;
451f74a2 914}
915
9f7f68c3 916/**
a24cf710 917 * Encodes header
8bd0068d 918 *
a24cf710 919 * Function uses XTRA_CODE _encodeheader function, if such function exists.
a24cf710 920 *
758a7889 921 * Function uses Q encoding by default and encodes a string according to RFC
922 * 1522 for use in headers if it contains 8-bit characters or anything that
a24cf710 923 * looks like it should be encoded.
8bd0068d 924 *
758a7889 925 * Function switches to B encoding and encodeHeaderBase64() function, if
926 * string is 8bit and multibyte character set supported by mbstring extension
927 * is used. It can cause E_USER_NOTICE errors, if interface is used with
f270a6eb 928 * multibyte character set unsupported by mbstring extension.
929 *
8bd0068d 930 * @param string $string header string, that has to be encoded
931 * @return string quoted-printable encoded string
f270a6eb 932 * @todo make $mb_charsets system wide constant
8bd0068d 933 */
451f74a2 934function encodeHeader ($string) {
6fbd125b 935 global $default_charset, $languages, $squirrelmail_language;
83be314a 936
10dec454 937 if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
8bd0068d 938 function_exists($languages[$squirrelmail_language]['XTRA_CODE'] . '_encodeheader')) {
33a55f5a 939 return call_user_func($languages[$squirrelmail_language]['XTRA_CODE'] . '_encodeheader', $string);
83be314a 940 }
793cc001 941
a24cf710 942 // Use B encoding for multibyte charsets
f270a6eb 943 $mb_charsets = array('utf-8','big5','gb2313','euc-kr');
944 if (in_array($default_charset,$mb_charsets) &&
945 in_array($default_charset,sq_mb_list_encodings()) &&
946 sq_is8bit($string)) {
947 return encodeHeaderBase64($string,$default_charset);
948 } elseif (in_array($default_charset,$mb_charsets) &&
949 sq_is8bit($string) &&
950 ! in_array($default_charset,sq_mb_list_encodings())) {
951 // Add E_USER_NOTICE error here (can cause 'Cannot add header information' warning in compose.php)
952 // trigger_error('encodeHeader: Multibyte character set unsupported by mbstring extension.',E_USER_NOTICE);
953 }
a24cf710 954
451f74a2 955 // Encode only if the string contains 8-bit characters or =?
3d8371be 956 $j = strlen($string);
098ea084 957 $max_l = 75 - strlen($default_charset) - 7;
958 $aRet = array();
451f74a2 959 $ret = '';
c96c32f4 960 $iEncStart = $enc_init = false;
0d53f0f9 961 $cur_l = $iOffset = 0;
3d8371be 962 for($i = 0; $i < $j; ++$i) {
c96c32f4 963 switch($string{$i})
964 {
6b76cffa 965 case '"':
8bd0068d 966 case '=':
967 case '<':
968 case '>':
969 case ',':
970 case '?':
971 case '_':
972 if ($iEncStart === false) {
973 $iEncStart = $i;
974 }
975 $cur_l+=3;
976 if ($cur_l > ($max_l-2)) {
977 /* if there is an stringpart that doesn't need encoding, add it */
08b7f7cc 978 $aRet[] = substr($string,$iOffset,$iEncStart-$iOffset);
c96c32f4 979 $aRet[] = "=?$default_charset?Q?$ret?=";
980 $iOffset = $i;
981 $cur_l = 0;
982 $ret = '';
983 $iEncStart = false;
08b7f7cc 984 } else {
8bd0068d 985 $ret .= sprintf("=%02X",ord($string{$i}));
c96c32f4 986 }
8bd0068d 987 break;
988 case '(':
989 case ')':
990 if ($iEncStart !== false) {
08b7f7cc 991 $aRet[] = substr($string,$iOffset,$iEncStart-$iOffset);
8bd0068d 992 $aRet[] = "=?$default_charset?Q?$ret?=";
c96c32f4 993 $iOffset = $i;
8bd0068d 994 $cur_l = 0;
995 $ret = '';
996 $iEncStart = false;
c96c32f4 997 }
8bd0068d 998 break;
999 case ' ':
c96c32f4 1000 if ($iEncStart !== false) {
098ea084 1001 $cur_l++;
1002 if ($cur_l > $max_l) {
08b7f7cc 1003 $aRet[] = substr($string,$iOffset,$iEncStart-$iOffset);
c96c32f4 1004 $aRet[] = "=?$default_charset?Q?$ret?=";
c96c32f4 1005 $iOffset = $i;
1006 $cur_l = 0;
098ea084 1007 $ret = '';
8bd0068d 1008 $iEncStart = false;
08b7f7cc 1009 } else {
8bd0068d 1010 $ret .= '_';
c96c32f4 1011 }
3d8371be 1012 }
8bd0068d 1013 break;
1014 default:
1015 $k = ord($string{$i});
1016 if ($k > 126) {
1017 if ($iEncStart === false) {
1018 // do not start encoding in the middle of a string, also take the rest of the word.
1019 $sLeadString = substr($string,0,$i);
1020 $aLeadString = explode(' ',$sLeadString);
1021 $sToBeEncoded = array_pop($aLeadString);
1022 $iEncStart = $i - strlen($sToBeEncoded);
1023 $ret .= $sToBeEncoded;
1024 $cur_l += strlen($sToBeEncoded);
1025 }
1026 $cur_l += 3;
1027 /* first we add the encoded string that reached it's max size */
1028 if ($cur_l > ($max_l-2)) {
1029 $aRet[] = substr($string,$iOffset,$iEncStart-$iOffset);
1030 $aRet[] = "=?$default_charset?Q?$ret?= "; /* the next part is also encoded => separate by space */
1031 $cur_l = 3;
1032 $ret = '';
1033 $iOffset = $i;
1034 $iEncStart = $i;
1035 }
1036 $enc_init = true;
1037 $ret .= sprintf("=%02X", $k);
1038 } else {
1039 if ($iEncStart !== false) {
1040 $cur_l++;
1041 if ($cur_l > $max_l) {
1042 $aRet[] = substr($string,$iOffset,$iEncStart-$iOffset);
1043 $aRet[] = "=?$default_charset?Q?$ret?=";
1044 $iEncStart = false;
1045 $iOffset = $i;
1046 $cur_l = 0;
1047 $ret = '';
1048 } else {
1049 $ret .= $string{$i};
1050 }
1051 }
1052 }
1053 break;
f7b3ba37 1054 }
451f74a2 1055 }
793cc001 1056
c96c32f4 1057 if ($enc_init) {
1058 if ($iEncStart !== false) {
1059 $aRet[] = substr($string,$iOffset,$iEncStart-$iOffset);
1060 $aRet[] = "=?$default_charset?Q?$ret?=";
1061 } else {
1062 $aRet[] = substr($string,$iOffset);
1063 }
1064 $string = implode('',$aRet);
451f74a2 1065 }
3d8371be 1066 return $string;
451f74a2 1067}
b74ba498 1068
f270a6eb 1069/**
1070 * Encodes string according to rfc2047 B encoding header formating rules
1071 *
758a7889 1072 * It is recommended way to encode headers with character sets that store
f270a6eb 1073 * symbols in more than one byte.
1074 *
1075 * Function requires mbstring support. If required mbstring functions are missing,
1076 * function returns false and sets E_USER_WARNING level error message.
1077 *
758a7889 1078 * Minimal requirements - php 4.0.6 with mbstring extension. Please note,
1079 * that mbstring functions will generate E_WARNING errors, if unsupported
f270a6eb 1080 * character set is used. mb_encode_mimeheader function provided by php
1081 * mbstring extension is not used in order to get better control of header
1082 * encoding.
1083 *
758a7889 1084 * Used php code functions - function_exists(), trigger_error(), strlen()
1085 * (is used with charset names and base64 strings). Used php mbstring
f270a6eb 1086 * functions - mb_strlen and mb_substr.
1087 *
758a7889 1088 * Related documents: rfc 2045 (BASE64 encoding), rfc 2047 (mime header
f270a6eb 1089 * encoding), rfc 2822 (header folding)
1090 *
1091 * @param string $string header string that must be encoded
758a7889 1092 * @param string $charset character set. Must be supported by mbstring extension.
f270a6eb 1093 * Use sq_mb_list_encodings() to detect supported charsets.
1094 * @return string string encoded according to rfc2047 B encoding formating rules
1095 * @since 1.5.1
1096 * @todo First header line can be wrapped to $iMaxLength - $HeaderFieldLength - 1
1097 * @todo Do we want to control max length of header?
1098 * @todo Do we want to control EOL (end-of-line) marker?
1099 * @todo Do we want to translate error message?
1100 */
1101function encodeHeaderBase64($string,$charset) {
1102 /**
1103 * Check mbstring function requirements.
1104 */
1105 if (! function_exists('mb_strlen') ||
1106 ! function_exists('mb_substr')) {
1107 // set E_USER_WARNING
1108 trigger_error('encodeHeaderBase64: Required mbstring functions are missing.',E_USER_WARNING);
1109 // return false
1110 return false;
1111 }
1112
1113 // initial return array
1114 $aRet = array();
1115
1116 /**
1117 * header length = 75 symbols max (same as in encodeHeader)
1118 * remove $charset length
1119 * remove =? ? ?= (5 chars)
1120 * remove 2 more chars (\r\n ?)
1121 */
1122 $iMaxLength = 75 - strlen($charset) - 7;
1123
1124 // set first character position
1125 $iStartCharNum = 0;
1126
1127 // loop through all characters. count characters and not bytes.
1128 for ($iCharNum=1; $iCharNum<=mb_strlen($string,$charset); $iCharNum++) {
1129 // encode string from starting character to current character.
1130 $encoded_string = base64_encode(mb_substr($string,$iStartCharNum,$iCharNum-$iStartCharNum,$charset));
1131
1132 // Check encoded string length
1133 if(strlen($encoded_string)>$iMaxLength) {
1134 // if string exceeds max length, reduce number of encoded characters and add encoded string part to array
1135 $aRet[] = base64_encode(mb_substr($string,$iStartCharNum,$iCharNum-$iStartCharNum-1,$charset));
1136
1137 // set new starting character
1138 $iStartCharNum = $iCharNum-1;
1139
1140 // encode last char (in case it is last character in string)
1141 $encoded_string = base64_encode(mb_substr($string,$iStartCharNum,$iCharNum-$iStartCharNum,$charset));
1142 } // if string is shorter than max length - add next character
1143 }
1144
1145 // add last encoded string to array
1146 $aRet[] = $encoded_string;
1147
1148 // set initial return string
1149 $sRet = '';
1150
1151 // loop through encoded strings
1152 foreach($aRet as $string) {
1153 // TODO: Do we want to control EOL (end-of-line) marker
1154 if ($sRet!='') $sRet.= " ";
1155
1156 // add header tags and encoded string to return string
1157 $sRet.= '=?'.$charset.'?B?'.$string.'?=';
1158 }
1159
1160 return $sRet;
1161}
1162
691a2d25 1163/* This function trys to locate the entity_id of a specific mime element */
3d8371be 1164function find_ent_id($id, $message) {
a171b359 1165 for ($i = 0, $ret = ''; $ret == '' && $i < count($message->entities); $i++) {
1166 if ($message->entities[$i]->header->type0 == 'multipart') {
3d8371be 1167 $ret = find_ent_id($id, $message->entities[$i]);
451f74a2 1168 } else {
3d8371be 1169 if (strcasecmp($message->entities[$i]->header->id, $id) == 0) {
d8cffbab 1170// if (sq_check_save_extension($message->entities[$i])) {
8bd0068d 1171 return $message->entities[$i]->entity_id;
da2415c1 1172// }
c8f5f606 1173 } elseif (!empty($message->entities[$i]->header->parameters['name'])) {
1174 /**
1175 * This is part of a fix for Outlook Express 6.x generating
1176 * cid URLs without creating content-id headers
1177 * @@JA - 20050207
1178 */
1179 if (strcasecmp($message->entities[$i]->header->parameters['name'], $id) == 0) {
1180 return $message->entities[$i]->entity_id;
1181 }
3d8371be 1182 }
a3daaaf3 1183 }
451f74a2 1184 }
3d8371be 1185 return $ret;
451f74a2 1186}
a3daaaf3 1187
e5e9381a 1188function sq_check_save_extension($message) {
1189 $filename = $message->getFilename();
1190 $ext = substr($filename, strrpos($filename,'.')+1);
1191 $save_extensions = array('jpg','jpeg','gif','png','bmp');
3d8371be 1192 return in_array($ext, $save_extensions);
e5e9381a 1193}
1194
1195
691a2d25 1196/**
8bd0068d 1197 ** HTMLFILTER ROUTINES
1198 */
451f74a2 1199
2dd879b8 1200/**
0493ed11 1201 * This function checks attribute values for entity-encoded values
1202 * and returns them translated into 8-bit strings so we can run
1203 * checks on them.
8bd0068d 1204 *
0493ed11 1205 * @param $attvalue A string to run entity check against.
1206 * @return Nothing, modifies a reference value.
8bd0068d 1207 */
0493ed11 1208function sq_defang(&$attvalue){
1209 $me = 'sq_defang';
2dd879b8 1210 /**
0493ed11 1211 * Skip this if there aren't ampersands or backslashes.
8bd0068d 1212 */
0493ed11 1213 if (strpos($attvalue, '&') === false
1214 && strpos($attvalue, '\\') === false){
1215 return;
2dd879b8 1216 }
0493ed11 1217 $m = false;
2b665f28 1218 // before deent, translate the dangerous unicode characters and ... to safe values
1219 // otherwise the regular expressions do not match.
1220
1221
1222
0493ed11 1223 do {
1224 $m = false;
1225 $m = $m || sq_deent($attvalue, '/\&#0*(\d+);*/s');
1226 $m = $m || sq_deent($attvalue, '/\&#x0*((\d|[a-f])+);*/si', true);
1227 $m = $m || sq_deent($attvalue, '/\\\\(\d+)/s', true);
1228 } while ($m == true);
1229 $attvalue = stripslashes($attvalue);
2dd879b8 1230}
1231
1232/**
8bd0068d 1233 * Kill any tabs, newlines, or carriage returns. Our friends the
1234 * makers of the browser with 95% market value decided that it'd
1235 * be funny to make "java[tab]script" be just as good as "javascript".
1236 *
1237 * @param attvalue The attribute value before extraneous spaces removed.
0493ed11 1238 * @return attvalue Nothing, modifies a reference value.
8bd0068d 1239 */
0493ed11 1240function sq_unspace(&$attvalue){
1241 $me = 'sq_unspace';
1242 if (strcspn($attvalue, "\t\r\n\0 ") != strlen($attvalue)){
1243 $attvalue = str_replace(Array("\t", "\r", "\n", "\0", " "),
1244 Array('', '', '', '', ''), $attvalue);
2dd879b8 1245 }
2dd879b8 1246}
1247
2b665f28 1248/**
88de4926 1249 * Translate all dangerous Unicode or Shift_JIS characters which are accepted by
2b665f28 1250 * IE as regular characters.
1251 *
1252 * @param attvalue The attribute value before dangerous characters are translated.
1253 * @return attvalue Nothing, modifies a reference value.
1254 * @author Marc Groot Koerkamp.
1255 */
1256function sq_fixIE_idiocy(&$attvalue) {
1257 // remove NUL
1258 $attvalue = str_replace("\0", "", $attvalue);
1259 // remove comments
1260 $attvalue = preg_replace("/(\/\*.*?\*\/)/","",$attvalue);
1261
88de4926 1262 // IE has the evil habit of accepting every possible value for the attribute expression.
1263 // The table below contains characters which are parsed by IE if they are used in the "expression"
2b665f28 1264 // attribute value.
1265 $aDangerousCharsReplacementTable = array(
1266 array('&#x029F;', '&#0671;' ,/* L UNICODE IPA Extension */
1267 '&#x0280;', '&#0640;' ,/* R UNICODE IPA Extension */
1268 '&#x0274;', '&#0628;' ,/* N UNICODE IPA Extension */
567dc524 1269 '&#xFF25;', '&#65317;' ,/* Unicode FULLWIDTH LATIN CAPITAL LETTER E */
1270 '&#xFF45;', '&#65349;' ,/* Unicode FULLWIDTH LATIN SMALL LETTER E */
2b665f28 1271 '&#xFF38;', '&#65336;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER X */
1272 '&#xFF58;', '&#65368;',/* Unicode FULLWIDTH LATIN SMALL LETTER X */
1273 '&#xFF30;', '&#65328;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER P */
1274 '&#xFF50;', '&#65360;',/* Unicode FULLWIDTH LATIN SMALL LETTER P */
1275 '&#xFF32;', '&#65330;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER R */
1276 '&#xFF52;', '&#65362;',/* Unicode FULLWIDTH LATIN SMALL LETTER R */
1277 '&#xFF33;', '&#65331;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER S */
1278 '&#xFF53;', '&#65363;',/* Unicode FULLWIDTH LATIN SMALL LETTER S */
1279 '&#xFF29;', '&#65321;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER I */
1280 '&#xFF49;', '&#65353;',/* Unicode FULLWIDTH LATIN SMALL LETTER I */
1281 '&#xFF2F;', '&#65327;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER O */
1282 '&#xFF4F;', '&#65359;',/* Unicode FULLWIDTH LATIN SMALL LETTER O */
1283 '&#xFF2E;', '&#65326;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER N */
1284 '&#xFF4E;', '&#65358;',/* Unicode FULLWIDTH LATIN SMALL LETTER N */
1285 '&#xFF2C;', '&#65324;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER L */
1286 '&#xFF4C;', '&#65356;',/* Unicode FULLWIDTH LATIN SMALL LETTER L */
1287 '&#xFF35;', '&#65333;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER U */
1288 '&#xFF55;', '&#65365;',/* Unicode FULLWIDTH LATIN SMALL LETTER U */
1289 '&#x207F;', '&#8319;' ,/* Unicode SUPERSCRIPT LATIN SMALL LETTER N */
567dc524 1290 "\xEF\xBC\xA5", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER E */ // in unicode this is some Chinese char range
1291 "\xEF\xBD\x85", /* Shift JIS FULLWIDTH LATIN SMALL LETTER E */
1292 "\xEF\xBC\xB8", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER X */
1293 "\xEF\xBD\x98", /* Shift JIS FULLWIDTH LATIN SMALL LETTER X */
1294 "\xEF\xBC\xB0", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER P */
1295 "\xEF\xBD\x90", /* Shift JIS FULLWIDTH LATIN SMALL LETTER P */
1296 "\xEF\xBC\xB2", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER R */
1297 "\xEF\xBD\x92", /* Shift JIS FULLWIDTH LATIN SMALL LETTER R */
1298 "\xEF\xBC\xB3", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER S */
1299 "\xEF\xBD\x93", /* Shift JIS FULLWIDTH LATIN SMALL LETTER S */
1300 "\xEF\xBC\xA9", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER I */
1301 "\xEF\xBD\x89", /* Shift JIS FULLWIDTH LATIN SMALL LETTER I */
1302 "\xEF\xBC\xAF", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER O */
1303 "\xEF\xBD\x8F", /* Shift JIS FULLWIDTH LATIN SMALL LETTER O */
1304 "\xEF\xBC\xAE", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER N */
1305 "\xEF\xBD\x8E", /* Shift JIS FULLWIDTH LATIN SMALL LETTER N */
1306 "\xEF\xBC\xAC", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER L */
1307 "\xEF\xBD\x8C", /* Shift JIS FULLWIDTH LATIN SMALL LETTER L */
1308 "\xEF\xBC\xB5", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER U */
1309 "\xEF\xBD\x95", /* Shift JIS FULLWIDTH LATIN SMALL LETTER U */
1310 "\xE2\x81\xBF", /* Shift JIS FULLWIDTH SUPERSCRIPT N */
1311 "\xCA\x9F", /* L UNICODE IPA Extension */
1312 "\xCA\x80", /* R UNICODE IPA Extension */
1313 "\xC9\xB4"), /* N UNICODE IPA Extension */
2b665f28 1314 array('l', 'l', 'r','r','n','n',
567dc524 1315 'E','E','e','e','X','X','x','x','P','P','p','p','R','R','r','r','S','S','s','s','I','I',
1316 'i','i','O','O','o','o','N','N','n','n','L','L','l','l','U','U','u','u','n','n',
1317 'E','e','X','x','P','p','R','r','S','s','I','i','O','o','N','n','L','l','U','u','n','l','r','n'));
2b665f28 1318 $attvalue = str_replace($aDangerousCharsReplacementTable[0],$aDangerousCharsReplacementTable[1],$attvalue);
1319
88de4926 1320 // Escapes are useful for special characters like "{}[]()'&. In other cases they are
1321 // used for XSS.
2b665f28 1322 $attvalue = preg_replace("/(\\\\)([a-zA-Z]{1})/",'$2',$attvalue);
1323}
1324
691a2d25 1325/**
8bd0068d 1326 * This function returns the final tag out of the tag name, an array
1327 * of attributes, and the type of the tag. This function is called by
1328 * sq_sanitize internally.
1329 *
1330 * @param $tagname the name of the tag.
1331 * @param $attary the array of attributes and their values
1332 * @param $tagtype The type of the tag (see in comments).
1333 * @return a string with the final tag representation.
1334 */
691a2d25 1335function sq_tagprint($tagname, $attary, $tagtype){
b583c3e8 1336 $me = 'sq_tagprint';
3d8371be 1337
691a2d25 1338 if ($tagtype == 2){
1339 $fulltag = '</' . $tagname . '>';
1340 } else {
1341 $fulltag = '<' . $tagname;
1342 if (is_array($attary) && sizeof($attary)){
1343 $atts = Array();
1344 while (list($attname, $attvalue) = each($attary)){
1345 array_push($atts, "$attname=$attvalue");
1346 }
1347 $fulltag .= ' ' . join(" ", $atts);
1348 }
1349 if ($tagtype == 3){
b583c3e8 1350 $fulltag .= ' /';
691a2d25 1351 }
b583c3e8 1352 $fulltag .= '>';
451f74a2 1353 }
691a2d25 1354 return $fulltag;
451f74a2 1355}
a3daaaf3 1356
691a2d25 1357/**
8bd0068d 1358 * A small helper function to use with array_walk. Modifies a by-ref
1359 * value and makes it lowercase.
1360 *
1361 * @param $val a value passed by-ref.
1362 * @return void since it modifies a by-ref value.
1363 */
691a2d25 1364function sq_casenormalize(&$val){
1365 $val = strtolower($val);
1366}
451f74a2 1367
691a2d25 1368/**
8bd0068d 1369 * This function skips any whitespace from the current position within
1370 * a string and to the next non-whitespace value.
1371 *
1372 * @param $body the string
1373 * @param $offset the offset within the string where we should start
1374 * looking for the next non-whitespace character.
1375 * @return the location within the $body where the next
1376 * non-whitespace char is located.
1377 */
691a2d25 1378function sq_skipspace($body, $offset){
b583c3e8 1379 $me = 'sq_skipspace';
3d8371be 1380 preg_match('/^(\s*)/s', substr($body, $offset), $matches);
691a2d25 1381 if (sizeof($matches{1})){
1382 $count = strlen($matches{1});
1383 $offset += $count;
451f74a2 1384 }
691a2d25 1385 return $offset;
451f74a2 1386}
a3daaaf3 1387
691a2d25 1388/**
8bd0068d 1389 * This function looks for the next character within a string. It's
1390 * really just a glorified "strpos", except it catches if failures
1391 * nicely.
1392 *
1393 * @param $body The string to look for needle in.
1394 * @param $offset Start looking from this position.
1395 * @param $needle The character/string to look for.
1396 * @return location of the next occurance of the needle, or
1397 * strlen($body) if needle wasn't found.
1398 */
691a2d25 1399function sq_findnxstr($body, $offset, $needle){
3d8371be 1400 $me = 'sq_findnxstr';
691a2d25 1401 $pos = strpos($body, $needle, $offset);
1402 if ($pos === FALSE){
1403 $pos = strlen($body);
451f74a2 1404 }
691a2d25 1405 return $pos;
451f74a2 1406}
a3daaaf3 1407
691a2d25 1408/**
8bd0068d 1409 * This function takes a PCRE-style regexp and tries to match it
1410 * within the string.
1411 *
1412 * @param $body The string to look for needle in.
1413 * @param $offset Start looking from here.
1414 * @param $reg A PCRE-style regex to match.
1415 * @return Returns a false if no matches found, or an array
1416 * with the following members:
1417 * - integer with the location of the match within $body
1418 * - string with whatever content between offset and the match
1419 * - string with whatever it is we matched
1420 */
691a2d25 1421function sq_findnxreg($body, $offset, $reg){
b583c3e8 1422 $me = 'sq_findnxreg';
691a2d25 1423 $matches = Array();
1424 $retarr = Array();
7d06541f 1425 preg_match("%^(.*?)($reg)%si", substr($body, $offset), $matches);
1426 if (!isset($matches{0}) || !$matches{0}){
691a2d25 1427 $retarr = false;
1428 } else {
1429 $retarr{0} = $offset + strlen($matches{1});
1430 $retarr{1} = $matches{1};
1431 $retarr{2} = $matches{2};
1432 }
1433 return $retarr;
1434}
a3daaaf3 1435
691a2d25 1436/**
8bd0068d 1437 * This function looks for the next tag.
1438 *
1439 * @param $body String where to look for the next tag.
1440 * @param $offset Start looking from here.
1441 * @return false if no more tags exist in the body, or
1442 * an array with the following members:
1443 * - string with the name of the tag
1444 * - array with attributes and their values
1445 * - integer with tag type (1, 2, or 3)
1446 * - integer where the tag starts (starting "<")
1447 * - integer where the tag ends (ending ">")
1448 * first three members will be false, if the tag is invalid.
1449 */
691a2d25 1450function sq_getnxtag($body, $offset){
b583c3e8 1451 $me = 'sq_getnxtag';
691a2d25 1452 if ($offset > strlen($body)){
1453 return false;
1454 }
1455 $lt = sq_findnxstr($body, $offset, "<");
1456 if ($lt == strlen($body)){
1457 return false;
1458 }
1459 /**
8bd0068d 1460 * We are here:
1461 * blah blah <tag attribute="value">
1462 * \---------^
1463 */
691a2d25 1464 $pos = sq_skipspace($body, $lt+1);
1465 if ($pos >= strlen($body)){
1466 return Array(false, false, false, $lt, strlen($body));
1467 }
1468 /**
8bd0068d 1469 * There are 3 kinds of tags:
1470 * 1. Opening tag, e.g.:
1471 * <a href="blah">
1472 * 2. Closing tag, e.g.:
1473 * </a>
1474 * 3. XHTML-style content-less tag, e.g.:
1475 * <img src="blah" />
1476 */
691a2d25 1477 $tagtype = false;
1478 switch (substr($body, $pos, 1)){
3d8371be 1479 case '/':
1480 $tagtype = 2;
1481 $pos++;
1482 break;
1483 case '!':
1484 /**
8bd0068d 1485 * A comment or an SGML declaration.
1486 */
3d8371be 1487 if (substr($body, $pos+1, 2) == "--"){
1488 $gt = strpos($body, "-->", $pos);
1489 if ($gt === false){
1490 $gt = strlen($body);
1491 } else {
1492 $gt += 2;
1493 }
1494 return Array(false, false, false, $lt, $gt);
bb8d0799 1495 } else {
3d8371be 1496 $gt = sq_findnxstr($body, $pos, ">");
1497 return Array(false, false, false, $lt, $gt);
1498 }
1499 break;
1500 default:
1501 /**
8bd0068d 1502 * Assume tagtype 1 for now. If it's type 3, we'll switch values
1503 * later.
1504 */
3d8371be 1505 $tagtype = 1;
1506 break;
691a2d25 1507 }
a3daaaf3 1508
0493ed11 1509 $tag_start = $pos;
691a2d25 1510 $tagname = '';
1511 /**
8bd0068d 1512 * Look for next [\W-_], which will indicate the end of the tag name.
1513 */
691a2d25 1514 $regary = sq_findnxreg($body, $pos, "[^\w\-_]");
1515 if ($regary == false){
1516 return Array(false, false, false, $lt, strlen($body));
1517 }
1518 list($pos, $tagname, $match) = $regary;
1519 $tagname = strtolower($tagname);
1520
1521 /**
8bd0068d 1522 * $match can be either of these:
1523 * '>' indicating the end of the tag entirely.
1524 * '\s' indicating the end of the tag name.
1525 * '/' indicating that this is type-3 xhtml tag.
1526 *
1527 * Whatever else we find there indicates an invalid tag.
1528 */
691a2d25 1529 switch ($match){
3d8371be 1530 case '/':
691a2d25 1531 /**
8bd0068d 1532 * This is an xhtml-style tag with a closing / at the
1533 * end, like so: <img src="blah" />. Check if it's followed
1534 * by the closing bracket. If not, then this tag is invalid
1535 */
3d8371be 1536 if (substr($body, $pos, 2) == "/>"){
1537 $pos++;
1538 $tagtype = 3;
1539 } else {
1540 $gt = sq_findnxstr($body, $pos, ">");
1541 $retary = Array(false, false, false, $lt, $gt);
1542 return $retary;
1543 }
1544 case '>':
1545 return Array($tagname, false, $tagtype, $lt, $pos);
1546 break;
1547 default:
1548 /**
8bd0068d 1549 * Check if it's whitespace
1550 */
3d8371be 1551 if (!preg_match('/\s/', $match)){
1552 /**
8bd0068d 1553 * This is an invalid tag! Look for the next closing ">".
1554 */
7d06541f 1555 $gt = sq_findnxstr($body, $lt, ">");
3d8371be 1556 return Array(false, false, false, $lt, $gt);
1557 }
1558 break;
691a2d25 1559 }
3d8371be 1560
691a2d25 1561 /**
8bd0068d 1562 * At this point we're here:
1563 * <tagname attribute='blah'>
1564 * \-------^
1565 *
1566 * At this point we loop in order to find all attributes.
1567 */
691a2d25 1568 $attname = '';
0493ed11 1569 $atttype = false;
691a2d25 1570 $attary = Array();
1571
1572 while ($pos <= strlen($body)){
1573 $pos = sq_skipspace($body, $pos);
1574 if ($pos == strlen($body)){
1575 /**
8bd0068d 1576 * Non-closed tag.
1577 */
691a2d25 1578 return Array(false, false, false, $lt, $pos);
1579 }
1580 /**
8bd0068d 1581 * See if we arrived at a ">" or "/>", which means that we reached
1582 * the end of the tag.
1583 */
691a2d25 1584 $matches = Array();
164800ad 1585 if (preg_match("%^(\s*)(>|/>)%s", substr($body, $pos), $matches)) {
c828931c 1586 /**
8bd0068d 1587 * Yep. So we did.
1588 */
c828931c 1589 $pos += strlen($matches{1});
1590 if ($matches{2} == "/>"){
1591 $tagtype = 3;
1592 $pos++;
1593 }
1594 return Array($tagname, $attary, $tagtype, $lt, $pos);
1595 }
a3daaaf3 1596
cca46357 1597 /**
8bd0068d 1598 * There are several types of attributes, with optional
1599 * [:space:] between members.
1600 * Type 1:
1601 * attrname[:space:]=[:space:]'CDATA'
1602 * Type 2:
1603 * attrname[:space:]=[:space:]"CDATA"
1604 * Type 3:
1605 * attr[:space:]=[:space:]CDATA
1606 * Type 4:
1607 * attrname
1608 *
1609 * We leave types 1 and 2 the same, type 3 we check for
1610 * '"' and convert to "&quot" if needed, then wrap in
1611 * double quotes. Type 4 we convert into:
1612 * attrname="yes".
1613 */
3f7c623f 1614 $regary = sq_findnxreg($body, $pos, "[^:\w\-_]");
691a2d25 1615 if ($regary == false){
1616 /**
8bd0068d 1617 * Looks like body ended before the end of tag.
1618 */
691a2d25 1619 return Array(false, false, false, $lt, strlen($body));
cca46357 1620 }
691a2d25 1621 list($pos, $attname, $match) = $regary;
1622 $attname = strtolower($attname);
1623 /**
8bd0068d 1624 * We arrived at the end of attribute name. Several things possible
1625 * here:
1626 * '>' means the end of the tag and this is attribute type 4
1627 * '/' if followed by '>' means the same thing as above
1628 * '\s' means a lot of things -- look what it's followed by.
1629 * anything else means the attribute is invalid.
1630 */
691a2d25 1631 switch($match){
3d8371be 1632 case '/':
691a2d25 1633 /**
8bd0068d 1634 * This is an xhtml-style tag with a closing / at the
1635 * end, like so: <img src="blah" />. Check if it's followed
1636 * by the closing bracket. If not, then this tag is invalid
1637 */
3d8371be 1638 if (substr($body, $pos, 2) == "/>"){
691a2d25 1639 $pos++;
3d8371be 1640 $tagtype = 3;
691a2d25 1641 } else {
3d8371be 1642 $gt = sq_findnxstr($body, $pos, ">");
1643 $retary = Array(false, false, false, $lt, $gt);
1644 return $retary;
1645 }
1646 case '>':
1647 $attary{$attname} = '"yes"';
1648 return Array($tagname, $attary, $tagtype, $lt, $pos);
1649 break;
1650 default:
1651 /**
8bd0068d 1652 * Skip whitespace and see what we arrive at.
1653 */
3d8371be 1654 $pos = sq_skipspace($body, $pos);
1655 $char = substr($body, $pos, 1);
1656 /**
8bd0068d 1657 * Two things are valid here:
1658 * '=' means this is attribute type 1 2 or 3.
1659 * \w means this was attribute type 4.
1660 * anything else we ignore and re-loop. End of tag and
1661 * invalid stuff will be caught by our checks at the beginning
1662 * of the loop.
1663 */
3d8371be 1664 if ($char == "="){
1665 $pos++;
1666 $pos = sq_skipspace($body, $pos);
691a2d25 1667 /**
8bd0068d 1668 * Here are 3 possibilities:
1669 * "'" attribute type 1
1670 * '"' attribute type 2
1671 * everything else is the content of tag type 3
1672 */
3d8371be 1673 $quot = substr($body, $pos, 1);
1674 if ($quot == "'"){
1675 $regary = sq_findnxreg($body, $pos+1, "\'");
1676 if ($regary == false){
1677 return Array(false, false, false, $lt, strlen($body));
1678 }
1679 list($pos, $attval, $match) = $regary;
1680 $pos++;
1681 $attary{$attname} = "'" . $attval . "'";
1682 } else if ($quot == '"'){
1683 $regary = sq_findnxreg($body, $pos+1, '\"');
1684 if ($regary == false){
1685 return Array(false, false, false, $lt, strlen($body));
1686 }
1687 list($pos, $attval, $match) = $regary;
1688 $pos++;
1689 $attary{$attname} = '"' . $attval . '"';
1690 } else {
1691 /**
8bd0068d 1692 * These are hateful. Look for \s, or >.
1693 */
3d8371be 1694 $regary = sq_findnxreg($body, $pos, "[\s>]");
1695 if ($regary == false){
1696 return Array(false, false, false, $lt, strlen($body));
1697 }
1698 list($pos, $attval, $match) = $regary;
1699 /**
8bd0068d 1700 * If it's ">" it will be caught at the top.
1701 */
3d8371be 1702 $attval = preg_replace("/\"/s", "&quot;", $attval);
1703 $attary{$attname} = '"' . $attval . '"';
7e235a1a 1704 }
3d8371be 1705 } else if (preg_match("|[\w/>]|", $char)) {
691a2d25 1706 /**
8bd0068d 1707 * That was attribute type 4.
1708 */
3d8371be 1709 $attary{$attname} = '"yes"';
1710 } else {
1711 /**
8bd0068d 1712 * An illegal character. Find next '>' and return.
1713 */
3d8371be 1714 $gt = sq_findnxstr($body, $pos, ">");
1715 return Array(false, false, false, $lt, $gt);
451f74a2 1716 }
3d8371be 1717 break;
691a2d25 1718 }
1719 }
1720 /**
8bd0068d 1721 * The fact that we got here indicates that the tag end was never
1722 * found. Return invalid tag indication so it gets stripped.
1723 */
691a2d25 1724 return Array(false, false, false, $lt, strlen($body));
1725}
1726
1727/**
0493ed11 1728 * Translates entities into literal values so they can be checked.
8bd0068d 1729 *
0493ed11 1730 * @param $attvalue the by-ref value to check.
1731 * @param $regex the regular expression to check against.
1732 * @param $hex whether the entites are hexadecimal.
1733 * @return True or False depending on whether there were matches.
8bd0068d 1734 */
0493ed11 1735function sq_deent(&$attvalue, $regex, $hex=false){
b583c3e8 1736 $me = 'sq_deent';
0493ed11 1737 $ret_match = false;
2b665f28 1738 // remove comments
1739 //$attvalue = preg_replace("/(\/\*.*\*\/)/","",$attvalue);
0493ed11 1740 preg_match_all($regex, $attvalue, $matches);
1741 if (is_array($matches) && sizeof($matches[0]) > 0){
1742 $repl = Array();
1743 for ($i = 0; $i < sizeof($matches[0]); $i++){
1744 $numval = $matches[1][$i];
1745 if ($hex){
1746 $numval = hexdec($numval);
a3daaaf3 1747 }
0493ed11 1748 $repl{$matches[0][$i]} = chr($numval);
691a2d25 1749 }
0493ed11 1750 $attvalue = strtr($attvalue, $repl);
1751 return true;
1752 } else {
1753 return false;
691a2d25 1754 }
691a2d25 1755}
1756
1757/**
8bd0068d 1758 * This function runs various checks against the attributes.
1759 *
1760 * @param $tagname String with the name of the tag.
1761 * @param $attary Array with all tag attributes.
1762 * @param $rm_attnames See description for sq_sanitize
1763 * @param $bad_attvals See description for sq_sanitize
1764 * @param $add_attr_to_tag See description for sq_sanitize
1765 * @param $message message object
1766 * @param $id message id
1767 * @return Array with modified attributes.
1768 */
da2415c1 1769function sq_fixatts($tagname,
1770 $attary,
691a2d25 1771 $rm_attnames,
1772 $bad_attvals,
1773 $add_attr_to_tag,
1774 $message,
b3af12ef 1775 $id,
3d8371be 1776 $mailbox
691a2d25 1777 ){
b583c3e8 1778 $me = 'sq_fixatts';
691a2d25 1779 while (list($attname, $attvalue) = each($attary)){
1780 /**
8bd0068d 1781 * See if this attribute should be removed.
1782 */
691a2d25 1783 foreach ($rm_attnames as $matchtag=>$matchattrs){
1784 if (preg_match($matchtag, $tagname)){
1785 foreach ($matchattrs as $matchattr){
1786 if (preg_match($matchattr, $attname)){
1787 unset($attary{$attname});
1788 continue;
1789 }
451f74a2 1790 }
451f74a2 1791 }
691a2d25 1792 }
2b665f28 1793 /**
1794 * Workaround for IE quirks
1795 */
1796 sq_fixIE_idiocy($attvalue);
1797
691a2d25 1798 /**
8bd0068d 1799 * Remove any backslashes, entities, and extraneous whitespace.
1800 */
2b665f28 1801
1802 $oldattvalue = $attvalue;
0493ed11 1803 sq_defang($attvalue);
2b665f28 1804 if ($attname == 'style' && $attvalue !== $oldattvalue) {
1805 // entities are used in the attribute value. In 99% of the cases it's there as XSS
1806 // i.e.<div style="{ left:exp&#x0280;essio&#x0274;( alert('XSS') ) }">
1807 $attvalue = "idiocy";
1808 $attary{$attname} = $attvalue;
1809 }
0493ed11 1810 sq_unspace($attvalue);
af861a34 1811
691a2d25 1812 /**
8bd0068d 1813 * Now let's run checks on the attvalues.
1814 * I don't expect anyone to comprehend this. If you do,
1815 * get in touch with me so I can drive to where you live and
1816 * shake your hand personally. :)
1817 */
691a2d25 1818 foreach ($bad_attvals as $matchtag=>$matchattrs){
1819 if (preg_match($matchtag, $tagname)){
1820 foreach ($matchattrs as $matchattr=>$valary){
1821 if (preg_match($matchattr, $attname)){
1822 /**
8bd0068d 1823 * There are two arrays in valary.
1824 * First is matches.
1825 * Second one is replacements
1826 */
691a2d25 1827 list($valmatch, $valrepl) = $valary;
da2415c1 1828 $newvalue =
691a2d25 1829 preg_replace($valmatch, $valrepl, $attvalue);
1830 if ($newvalue != $attvalue){
1831 $attary{$attname} = $newvalue;
567dc524 1832 $attvalue = $newvalue;
691a2d25 1833 }
1834 }
1835 }
451f74a2 1836 }
a3daaaf3 1837 }
567dc524 1838 if ($attname == 'style') {
1839 if (preg_match('/[\0-\37\200-\377]+/',$attvalue)) {
1840 // 8bit and control characters in style attribute values can be used for XSS, remove them
1841 $attary{$attname} = '"disallowed character"';
1842 }
1843 preg_match_all("/url\s*\((.+)\)/si",$attvalue,$aMatch);
1844 if (count($aMatch)) {
1845 foreach($aMatch[1] as $sMatch) {
1846 // url value
1847 $urlvalue = $sMatch;
1848 sq_fix_url($attname, $urlvalue, $message, $id, $mailbox,"'");
1849 $attary{$attname} = str_replace($sMatch,$urlvalue,$attvalue);
1850 }
1851 }
691a2d25 1852 }
ff940ebc 1853 /**
567dc524 1854 * Use white list based filtering on attributes which can contain url's
ff940ebc 1855 */
567dc524 1856 else if ($attname == 'href' || $attname == 'src' || $attname == 'background') {
1857 sq_fix_url($attname, $attvalue, $message, $id, $mailbox);
1858 $attary{$attname} = $attvalue;
ff940ebc 1859 }
a3daaaf3 1860 }
691a2d25 1861 /**
8bd0068d 1862 * See if we need to append any attributes to this tag.
1863 */
691a2d25 1864 foreach ($add_attr_to_tag as $matchtag=>$addattary){
1865 if (preg_match($matchtag, $tagname)){
1866 $attary = array_merge($attary, $addattary);
1867 }
1868 }
1869 return $attary;
451f74a2 1870}
a3daaaf3 1871
567dc524 1872/**
1873 * This function filters url's
1874 *
1875 * @param $attvalue String with attribute value to filter
1876 * @param $message message object
1877 * @param $id message id
1878 * @param $mailbox mailbox
1879 * @param $sQuote quoting characters around url's
1880 */
1881function sq_fix_url($attname, &$attvalue, $message, $id, $mailbox,$sQuote = '"') {
1882 $attvalue = trim($attvalue);
1883 if ($attvalue && ($attvalue[0] =='"'|| $attvalue[0] == "'")) {
1884 // remove the double quotes
1885 $sQuote = $attvalue[0];
1886 $attvalue = trim(substr($attvalue,1,-1));
1887 }
1888
d31f73f1 1889 // If there's no "view_unsafe_images" variable in the URL, turn unsafe
1890 // images off by default.
afbf184c 1891 sqgetGlobalVar('view_unsafe_images', $view_unsafe_images, SQ_GET, FALSE);
d31f73f1 1892
567dc524 1893 $secremoveimg = '../images/' . _("sec_remove_eng.png");
1894
1895 /**
1896 * Replace empty src tags with the blank image. src is only used
1897 * for frames, images, and image inputs. Doing a replace should
1898 * not affect them working as should be, however it will stop
1899 * IE from being kicked off when src for img tags are not set
1900 */
1901 if ($attvalue == '') {
1902 $attvalue = '"' . SM_PATH . 'images/blank.png"';
1903 } else {
1904 // first, disallow 8 bit characters and control characters
1905 if (preg_match('/[\0-\37\200-\377]+/',$attvalue)) {
1906 switch ($attname) {
1907 case 'href':
1908 $attvalue = $sQuote . 'http://invalid-stuff-detected.example.com' . $sQuote;
1909 break;
1910 default:
1911 $attvalue = $sQuote . SM_PATH . 'images/blank.png'. $sQuote;
1912 break;
1913 }
1914 } else {
1915 $aUrl = parse_url($attvalue);
1916 if (isset($aUrl['scheme'])) {
1917 switch(strtolower($aUrl['scheme'])) {
d75e755b 1918 case 'mailto':
567dc524 1919 case 'http':
1920 case 'https':
1921 case 'ftp':
1922 if ($attname != 'href') {
1923 if ($view_unsafe_images == false) {
1924 $attvalue = $sQuote . $secremoveimg . $sQuote;
1925 } else {
1926 if (isset($aUrl['path'])) {
8bbbc757 1927
1928 // No one has been able to show that image URIs
1929 // can be exploited, so for now, no restrictions
1930 // are made at all. If this proves to be a problem,
1931 // the commented-out code below can be of help.
1932 // (One consideration is that I see nothing in this
1933 // function that specifically says that we will
1934 // only ever arrive here when inspecting an image
1935 // tag, although that does seem to be the end
1936 // result - e.g., <script src="..."> where malicious
1937 // image URIs are in fact a problem are already
1938 // filtered out elsewhere.
1939 /* ---------------------------------
567dc524 1940 // validate image extension.
1941 $ext = strtolower(substr($aUrl['path'],strrpos($aUrl['path'],'.')));
1942 if (!in_array($ext,array('.jpeg','.jpg','xjpeg','.gif','.bmp','.jpe','.png','.xbm'))) {
8bbbc757 1943 // If URI is to something other than
1944 // a regular image file, get the contents
1945 // and try to see if it is an image.
1946 // Don't use Fileinfo (finfo_file()) because
1947 // we'd need to make the admin configure the
1948 // location of the magic.mime file (FIXME: add finfo_file() support later?)
1949 //
1950 $mime_type = '';
1951 if (function_exists('mime_content_type')
1952 && ($FILE = @fopen($attvalue, 'rb', FALSE))) {
1953
1954 // fetch file
1955 //
1956 $file_contents = '';
1957 while (!feof($FILE)) {
1958 $file_contents .= fread($FILE, 8192);
1959 }
1960 fclose($FILE);
1961
1962 // store file locally
1963 //
1964 global $attachment_dir, $username;
1965 $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
1966 $localfilename = GenerateRandomString(32, '', 7);
1967 $full_localfilename = "$hashed_attachment_dir/$localfilename";
1968 while (file_exists($full_localfilename)) {
1969 $localfilename = GenerateRandomString(32, '', 7);
1970 $full_localfilename = "$hashed_attachment_dir/$localfilename";
1971 }
1972 $FILE = fopen("$hashed_attachment_dir/$localfilename", 'wb');
1973 fwrite($FILE, $file_contents);
1974 fclose($FILE);
1975
1976 // get mime type and remove file
1977 //
1978 $mime_type = mime_content_type("$hashed_attachment_dir/$localfilename");
1979 unlink("$hashed_attachment_dir/$localfilename");
1980 }
1981 // debug: echo "$attvalue FILE TYPE IS $mime_type<HR>";
1982 if (substr(strtolower($mime_type), 0, 5) != 'image') {
1983 $attvalue = $sQuote . SM_PATH . 'images/blank.png'. $sQuote;
1984 }
567dc524 1985 }
8bbbc757 1986 --------------------------------- */
567dc524 1987 } else {
1988 $attvalue = $sQuote . SM_PATH . 'images/blank.png'. $sQuote;
1989 }
1990 }
cf088f55 1991 } else {
1992 $attvalue = $sQuote . $attvalue . $sQuote;
567dc524 1993 }
1994 break;
1995 case 'outbind':
1996 /**
1997 * "Hack" fix for Outlook using propriatary outbind:// protocol in img tags.
1998 * One day MS might actually make it match something useful, for now, falling
1999 * back to using cid2http, so we can grab the blank.png.
2000 */
cf088f55 2001 $attvalue = $sQuote . sq_cid2http($message, $id, $attvalue, $mailbox) . $sQuote;
567dc524 2002 break;
2003 case 'cid':
2004 /**
2005 * Turn cid: urls into http-friendly ones.
2006 */
cf088f55 2007 $attvalue = $sQuote . sq_cid2http($message, $id, $attvalue, $mailbox) . $sQuote;
567dc524 2008 break;
2009 default:
2010 $attvalue = $sQuote . SM_PATH . 'images/blank.png' . $sQuote;
2011 break;
2012 }
2013 } else {
2014 if (!(isset($aUrl['path']) && $aUrl['path'] == $secremoveimg)) {
2015 // parse_url did not lead to satisfying result
2016 $attvalue = $sQuote . SM_PATH . 'images/blank.png' . $sQuote;
2017 }
2018 }
2019 }
2020 }
2021}
2022
691a2d25 2023/**
8bd0068d 2024 * This function edits the style definition to make them friendly and
2025 * usable in SquirrelMail.
2026 *
2027 * @param $message the message object
2028 * @param $id the message id
2029 * @param $content a string with whatever is between <style> and </style>
2030 * @param $mailbox the message mailbox
2031 * @return a string with edited content.
2032 */
e60a299a 2033function sq_fixstyle($body, $pos, $message, $id, $mailbox){
b583c3e8 2034 $me = 'sq_fixstyle';
7e2ff844 2035 // workaround for </style> in between comments
2036 $iCurrentPos = $pos;
2037 $content = '';
2038 $sToken = '';
2039 $bSucces = false;
2040 $bEndTag = false;
2041 for ($i=$pos,$iCount=strlen($body);$i<$iCount;++$i) {
2042 $char = $body{$i};
2043 switch ($char) {
2044 case '<':
10c0caeb 2045 $sToken = $char;
7e2ff844 2046 break;
2047 case '/':
2048 if ($sToken == '<') {
2049 $sToken .= $char;
2050 $bEndTag = true;
2051 } else {
2052 $content .= $char;
2053 }
2054 break;
2055 case '>':
2056 if ($bEndTag) {
2057 $sToken .= $char;
2058 if (preg_match('/\<\/\s*style\s*\>/i',$sToken,$aMatch)) {
2059 $newpos = $i + 1;
2060 $bSucces = true;
2061 break 2;
2062 } else {
2063 $content .= $sToken;
2064 }
2065 $bEndTag = false;
2066 } else {
2067 $content .= $char;
2068 }
2069 break;
2070 case '!':
2071 if ($sToken == '<') {
2072 // possible comment
2073 if (isset($body{$i+2}) && substr($body,$i,3) == '!--') {
2074 $i = strpos($body,'-->',$i+3);
2b665f28 2075 if ($i === false) { // no end comment
2076 $i = strlen($body);
2077 }
7e2ff844 2078 $sToken = '';
2079 }
2080 } else {
2081 $content .= $char;
2082 }
2083 break;
2084 default:
2085 if ($bEndTag) {
2086 $sToken .= $char;
2087 } else {
2088 $content .= $char;
2089 }
2090 break;
2091 }
2092 }
2093 if ($bSucces == FALSE){
7d06541f 2094 return array(FALSE, strlen($body));
2095 }
7e2ff844 2096
2b665f28 2097
2098
691a2d25 2099 /**
9ebace19 2100 * First look for general BODY style declaration, which would be
2101 * like so:
2102 * body {background: blah-blah}
2103 * and change it to .bodyclass so we can just assign it to a <div>
2104 */
691a2d25 2105 $content = preg_replace("|body(\s*\{.*?\})|si", ".bodyclass\\1", $content);
3d8371be 2106 $secremoveimg = '../images/' . _("sec_remove_eng.png");
691a2d25 2107 /**
0493ed11 2108 * Fix url('blah') declarations.
2109 */
2110 // $content = preg_replace("|url\s*\(\s*([\'\"])\s*\S+script\s*:.*?([\'\"])\s*\)|si",
2111 // "url(\\1$secremoveimg\\2)", $content);
2b665f28 2112
567dc524 2113 // first check for 8bit sequences and disallowed control characters
2114 if (preg_match('/[\16-\37\200-\377]+/',$content)) {
2115 $content = '<!-- style block removed by html filter due to presence of 8bit characters -->';
2116 return array($content, $newpos);
2117 }
2118
2b665f28 2119 // IE Sucks hard. We have a special function for it.
2120 sq_fixIE_idiocy($content);
2121
2122 // remove @import line
2123 $content = preg_replace("/^\s*(@import.*)$/mi","\n<!-- @import rules forbidden -->\n",$content);
2124
5b4884be 2125 // translate ur\l and variations (IE parses that)
2b665f28 2126 // TODO check if the sq_fixIE_idiocy function already handles this.
5b4884be 2127 $content = preg_replace("/(\\\\)?u(\\\\)?r(\\\\)?l(\\\\)?/i", 'url', $content);
567dc524 2128 preg_match_all("/url\s*\((.+)\)/si",$content,$aMatch);
2129 if (count($aMatch)) {
2130 $aValue = $aReplace = array();
2131 foreach($aMatch[1] as $sMatch) {
2132 // url value
2133 $urlvalue = $sMatch;
2134 sq_fix_url('style',$urlvalue, $message, $id, $mailbox,"'");
2135 $aValue[] = $sMatch;
2136 $aReplace[] = $urlvalue;
0493ed11 2137 }
567dc524 2138 $content = str_replace($aValue,$aReplace,$content);
691a2d25 2139 }
567dc524 2140
9ebace19 2141 /**
2142 * Remove any backslashes, entities, and extraneous whitespace.
2143 */
0493ed11 2144 $contentTemp = $content;
2145 sq_defang($contentTemp);
2146 sq_unspace($contentTemp);
a3daaaf3 2147
691a2d25 2148 /**
8bd0068d 2149 * Fix stupid css declarations which lead to vulnerabilities
2150 * in IE.
39352565 2151 *
2152 * Also remove "position" attribute, as it can easily be set
2153 * to "fixed" or "absolute" with "left" and "top" attributes
2154 * of zero, taking over the whole content frame. It can also
2155 * be set to relative and move itself anywhere it wants to,
2156 * displaying content in areas it shouldn't be allowed to touch.
8bd0068d 2157 */
5db90261 2158 $match = Array('/\/\*.*\*\//',
2159 '/expression/i',
0493ed11 2160 '/behaviou*r/i',
2161 '/binding/i',
2b665f28 2162 '/include-source/i',
2163 '/javascript/i',
39352565 2164 '/script/i',
2165 '/position/i');
2166 $replace = Array('','idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', '');
0493ed11 2167 $contentNew = preg_replace($match, $replace, $contentTemp);
2168 if ($contentNew !== $contentTemp) {
2169 // insecure css declarations are used. From now on we don't care
2170 // anymore if the css is destroyed by sq_deent, sq_unspace or sq_unbackslash
2171 $content = $contentNew;
2172 }
7d06541f 2173 return array($content, $newpos);
691a2d25 2174}
a3daaaf3 2175
0493ed11 2176
691a2d25 2177/**
8bd0068d 2178 * This function converts cid: url's into the ones that can be viewed in
2179 * the browser.
2180 *
2181 * @param $message the message object
2182 * @param $id the message id
2183 * @param $cidurl the cid: url.
2184 * @param $mailbox the message mailbox
2185 * @return a string with a http-friendly url
2186 */
b3af12ef 2187function sq_cid2http($message, $id, $cidurl, $mailbox){
691a2d25 2188 /**
8bd0068d 2189 * Get rid of quotes.
2190 */
691a2d25 2191 $quotchar = substr($cidurl, 0, 1);
2dd879b8 2192 if ($quotchar == '"' || $quotchar == "'"){
2193 $cidurl = str_replace($quotchar, "", $cidurl);
2194 } else {
2195 $quotchar = '';
2196 }
691a2d25 2197 $cidurl = substr(trim($cidurl), 4);
0493ed11 2198
2199 $match_str = '/\{.*?\}\//';
2200 $str_rep = '';
2201 $cidurl = preg_replace($match_str, $str_rep, $cidurl);
2202
e5e9381a 2203 $linkurl = find_ent_id($cidurl, $message);
9ebace19 2204 /* in case of non-safe cid links $httpurl should be replaced by a sort of
2205 unsafe link image */
e5e9381a 2206 $httpurl = '';
c8f5f606 2207
8bd0068d 2208 /**
2209 * This is part of a fix for Outlook Express 6.x generating
2210 * cid URLs without creating content-id headers. These images are
2211 * not part of the multipart/related html mail. The html contains
2212 * <img src="cid:{some_id}/image_filename.ext"> references to
2213 * attached images with as goal to render them inline although
2214 * the attachment disposition property is not inline.
2215 */
c8f5f606 2216
2217 if (empty($linkurl)) {
2218 if (preg_match('/{.*}\//', $cidurl)) {
2219 $cidurl = preg_replace('/{.*}\//','', $cidurl);
2220 if (!empty($cidurl)) {
2221 $linkurl = find_ent_id($cidurl, $message);
2222 }
2223 }
2224 }
f8a1ed5a 2225
c8f5f606 2226 if (!empty($linkurl)) {
fa26ab45 2227 $httpurl = $quotchar . sqm_baseuri() . 'src/download.php?absolute_dl=true&amp;' .
8bd0068d 2228 "passed_id=$id&amp;mailbox=" . urlencode($mailbox) .
2229 '&amp;ent_id=' . $linkurl . $quotchar;
c8f5f606 2230 } else {
2231 /**
2232 * If we couldn't generate a proper img url, drop in a blank image
2233 * instead of sending back empty, otherwise it causes unusual behaviour
2234 */
bc017c1d 2235 $httpurl = $quotchar . SM_PATH . 'images/blank.png' . $quotchar;
e5e9381a 2236 }
f8a1ed5a 2237
691a2d25 2238 return $httpurl;
2239}
2240
2241/**
8bd0068d 2242 * This function changes the <body> tag into a <div> tag since we
2243 * can't really have a body-within-body.
2244 *
2245 * @param $attary an array of attributes and values of <body>
2246 * @param $mailbox mailbox we're currently reading (for cid2http)
2247 * @param $message current message (for cid2http)
2248 * @param $id current message id (for cid2http)
2249 * @return a modified array of attributes to be set for <div>
2250 */
2dd879b8 2251function sq_body2div($attary, $mailbox, $message, $id){
b583c3e8 2252 $me = 'sq_body2div';
3d8371be 2253 $divattary = Array('class' => "'bodyclass'");
b583c3e8 2254 $text = '#000000';
c189a963 2255 $has_bgc_stl = $has_txt_stl = false;
b583c3e8 2256 $styledef = '';
691a2d25 2257 if (is_array($attary) && sizeof($attary) > 0){
2258 foreach ($attary as $attname=>$attvalue){
2259 $quotchar = substr($attvalue, 0, 1);
2260 $attvalue = str_replace($quotchar, "", $attvalue);
2261 switch ($attname){
3d8371be 2262 case 'background':
8bd0068d 2263 $attvalue = sq_cid2http($message, $id, $attvalue, $mailbox);
3d8371be 2264 $styledef .= "background-image: url('$attvalue'); ";
2265 break;
2266 case 'bgcolor':
c189a963 2267 $has_bgc_stl = true;
3d8371be 2268 $styledef .= "background-color: $attvalue; ";
2269 break;
2270 case 'text':
c189a963 2271 $has_txt_stl = true;
3d8371be 2272 $styledef .= "color: $attvalue; ";
2273 break;
691a2d25 2274 }
a3daaaf3 2275 }
c189a963 2276 // Outlook defines a white bgcolor and no text color. This can lead to
2277 // white text on a white bg with certain themes.
2278 if ($has_bgc_stl && !$has_txt_stl) {
2279 $styledef .= "color: $text; ";
2280 }
691a2d25 2281 if (strlen($styledef) > 0){
2282 $divattary{"style"} = "\"$styledef\"";
2283 }
2284 }
2285 return $divattary;
2286}
a3daaaf3 2287
691a2d25 2288/**
8bd0068d 2289 * This is the main function and the one you should actually be calling.
2290 * There are several variables you should be aware of an which need
2291 * special description.
2292 *
2293 * Since the description is quite lengthy, see it here:
2294 * http://linux.duke.edu/projects/mini/htmlfilter/
2295 *
2296 * @param $body the string with HTML you wish to filter
2297 * @param $tag_list see description above
2298 * @param $rm_tags_with_content see description above
2299 * @param $self_closing_tags see description above
2300 * @param $force_tag_closing see description above
2301 * @param $rm_attnames see description above
2302 * @param $bad_attvals see description above
2303 * @param $add_attr_to_tag see description above
2304 * @param $message message object
2305 * @param $id message id
2306 * @return sanitized html safe to show on your pages.
2307 */
da2415c1 2308function sq_sanitize($body,
8bd0068d 2309 $tag_list,
2310 $rm_tags_with_content,
2311 $self_closing_tags,
2312 $force_tag_closing,
2313 $rm_attnames,
2314 $bad_attvals,
2315 $add_attr_to_tag,
2316 $message,
2317 $id,
2318 $mailbox
2319 ){
b583c3e8 2320 $me = 'sq_sanitize';
7d06541f 2321 $rm_tags = array_shift($tag_list);
691a2d25 2322 /**
8bd0068d 2323 * Normalize rm_tags and rm_tags_with_content.
2324 */
7d06541f 2325 @array_walk($tag_list, 'sq_casenormalize');
691a2d25 2326 @array_walk($rm_tags_with_content, 'sq_casenormalize');
2327 @array_walk($self_closing_tags, 'sq_casenormalize');
2328 /**
8bd0068d 2329 * See if tag_list is of tags to remove or tags to allow.
2330 * false means remove these tags
2331 * true means allow these tags
2332 */
691a2d25 2333 $curpos = 0;
2334 $open_tags = Array();
2dd879b8 2335 $trusted = "\n<!-- begin sanitized html -->\n";
691a2d25 2336 $skip_content = false;
bb8d0799 2337 /**
8bd0068d 2338 * Take care of netscape's stupid javascript entities like
2339 * &{alert('boo')};
2340 */
bb8d0799 2341 $body = preg_replace("/&(\{.*?\};)/si", "&amp;\\1", $body);
691a2d25 2342
7d06541f 2343 while (($curtag = sq_getnxtag($body, $curpos)) != FALSE){
691a2d25 2344 list($tagname, $attary, $tagtype, $lt, $gt) = $curtag;
2345 $free_content = substr($body, $curpos, $lt-$curpos);
2346 /**
8bd0068d 2347 * Take care of <style>
2348 */
7d06541f 2349 if ($tagname == "style" && $tagtype == 1){
da2415c1 2350 list($free_content, $curpos) =
e60a299a 2351 sq_fixstyle($body, $gt+1, $message, $id, $mailbox);
7d06541f 2352 if ($free_content != FALSE){
bb40a9c1 2353 $attary = sq_fixatts($tagname,
2354 $attary,
2355 $rm_attnames,
2356 $bad_attvals,
2357 $add_attr_to_tag,
2358 $message,
2359 $id,
2360 $mailbox
2361 );
7d06541f 2362 $trusted .= sq_tagprint($tagname, $attary, $tagtype);
2363 $trusted .= $free_content;
2364 $trusted .= sq_tagprint($tagname, false, 2);
2365 }
2366 continue;
691a2d25 2367 }
2368 if ($skip_content == false){
2369 $trusted .= $free_content;
691a2d25 2370 }
2371 if ($tagname != FALSE){
2372 if ($tagtype == 2){
2373 if ($skip_content == $tagname){
2374 /**
8bd0068d 2375 * Got to the end of tag we needed to remove.
2376 */
691a2d25 2377 $tagname = false;
2378 $skip_content = false;
2379 } else {
2380 if ($skip_content == false){
c828931c 2381 if ($tagname == "body"){
2382 $tagname = "div";
2dd879b8 2383 }
da2415c1 2384 if (isset($open_tags{$tagname}) &&
8bd0068d 2385 $open_tags{$tagname} > 0){
2dd879b8 2386 $open_tags{$tagname}--;
691a2d25 2387 } else {
2dd879b8 2388 $tagname = false;
691a2d25 2389 }
691a2d25 2390 }
2391 }
2392 } else {
2393 /**
8bd0068d 2394 * $rm_tags_with_content
2395 */
691a2d25 2396 if ($skip_content == false){
2397 /**
8bd0068d 2398 * See if this is a self-closing type and change
2399 * tagtype appropriately.
2400 */
691a2d25 2401 if ($tagtype == 1
8bd0068d 2402 && in_array($tagname, $self_closing_tags)){
2dd879b8 2403 $tagtype = 3;
691a2d25 2404 }
2405 /**
8bd0068d 2406 * See if we should skip this tag and any content
2407 * inside it.
2408 */
691a2d25 2409 if ($tagtype == 1 &&
8bd0068d 2410 in_array($tagname, $rm_tags_with_content)){
691a2d25 2411 $skip_content = $tagname;
2412 } else {
da2415c1 2413 if (($rm_tags == false
8bd0068d 2414 && in_array($tagname, $tag_list)) ||
2415 ($rm_tags == true &&
2416 !in_array($tagname, $tag_list))){
691a2d25 2417 $tagname = false;
2418 } else {
2dd879b8 2419 /**
8bd0068d 2420 * Convert body into div.
2421 */
2dd879b8 2422 if ($tagname == "body"){
2423 $tagname = "div";
da2415c1 2424 $attary = sq_body2div($attary, $mailbox,
8bd0068d 2425 $message, $id);
2dd879b8 2426 }
691a2d25 2427 if ($tagtype == 1){
2428 if (isset($open_tags{$tagname})){
2429 $open_tags{$tagname}++;
2430 } else {
2431 $open_tags{$tagname}=1;
2432 }
2433 }
2434 /**
8bd0068d 2435 * This is where we run other checks.
2436 */
691a2d25 2437 if (is_array($attary) && sizeof($attary) > 0){
2438 $attary = sq_fixatts($tagname,
8bd0068d 2439 $attary,
2440 $rm_attnames,
2441 $bad_attvals,
2442 $add_attr_to_tag,
2443 $message,
2444 $id,
2445 $mailbox
2446 );
691a2d25 2447 }
2448 }
2449 }
691a2d25 2450 }
2451 }
2452 if ($tagname != false && $skip_content == false){
2453 $trusted .= sq_tagprint($tagname, $attary, $tagtype);
2454 }
691a2d25 2455 }
2456 $curpos = $gt+1;
a3daaaf3 2457 }
691a2d25 2458 $trusted .= substr($body, $curpos, strlen($body)-$curpos);
2459 if ($force_tag_closing == true){
2460 foreach ($open_tags as $tagname=>$opentimes){
2461 while ($opentimes > 0){
2462 $trusted .= '</' . $tagname . '>';
2463 $opentimes--;
2464 }
2465 }
2466 $trusted .= "\n";
2467 }
2468 $trusted .= "<!-- end sanitized html -->\n";
2469 return $trusted;
2470}
451f74a2 2471
691a2d25 2472/**
8bd0068d 2473 * This is a wrapper function to call html sanitizing routines.
2474 *
2475 * @param $body the body of the message
2476 * @param $id the id of the message
c189a963 2477
2478 * @param $message
2479 * @param $mailbox
2480 * @param boolean $take_mailto_links When TRUE, converts mailto: links
2481 * into internal SM compose links
2482 * (optional; default = TRUE)
8bd0068d 2483 * @return a string with html safe to display in the browser.
2484 */
c189a963 2485function magicHTML($body, $id, $message, $mailbox = 'INBOX', $take_mailto_links =true) {
2486
202bcbcc 2487 // require_once(SM_PATH . 'functions/url_parser.php'); // for $MailTo_PReg_Match
c189a963 2488
691a2d25 2489 global $attachment_common_show_images, $view_unsafe_images,
8bd0068d 2490 $has_unsafe_images;
691a2d25 2491 /**
8bd0068d 2492 * Don't display attached images in HTML mode.
2b665f28 2493 *
d0187bd6 2494 * SB: why?
8bd0068d 2495 */
691a2d25 2496 $attachment_common_show_images = false;
2497 $tag_list = Array(
8bd0068d 2498 false,
2499 "object",
2500 "meta",
2501 "html",
2502 "head",
2503 "base",
2504 "link",
2505 "frame",
2506 "iframe",
2507 "plaintext",
2508 "marquee"
2509 );
691a2d25 2510
2511 $rm_tags_with_content = Array(
8bd0068d 2512 "script",
2513 "applet",
2514 "embed",
2515 "title",
2516 "frameset",
0493ed11 2517 "xmp",
8bd0068d 2518 "xml"
2519 );
691a2d25 2520
2521 $self_closing_tags = Array(
8bd0068d 2522 "img",
2523 "br",
2524 "hr",
2525 "input",
2526 "outbind"
2527 );
691a2d25 2528
2dd879b8 2529 $force_tag_closing = true;
691a2d25 2530
2531 $rm_attnames = Array(
8bd0068d 2532 "/.*/" =>
2533 Array(
2534 "/target/i",
2535 "/^on.*/i",
2536 "/^dynsrc/i",
2537 "/^data.*/i",
2538 "/^lowsrc.*/i"
2539 )
2540 );
691a2d25 2541
2542 $secremoveimg = "../images/" . _("sec_remove_eng.png");
2543 $bad_attvals = Array(
8bd0068d 2544 "/.*/" =>
691a2d25 2545 Array(
0a6ec9b5 2546 "/^src|background/i" =>
8bd0068d 2547 Array(
691a2d25 2548 Array(
8bd0068d 2549 "/^([\'\"])\s*\S+script\s*:.*([\'\"])/si",
2550 "/^([\'\"])\s*mocha\s*:*.*([\'\"])/si",
2551 "/^([\'\"])\s*about\s*:.*([\'\"])/si"
691a2d25 2552 ),
8bd0068d 2553 Array(
2554 "\\1$secremoveimg\\2",
2555 "\\1$secremoveimg\\2",
2556 "\\1$secremoveimg\\2",
8bd0068d 2557 )
2558 ),
0a6ec9b5 2559 "/^href|action/i" =>
8bd0068d 2560 Array(
0a6ec9b5 2561 Array(
8bd0068d 2562 "/^([\'\"])\s*\S+script\s*:.*([\'\"])/si",
2563 "/^([\'\"])\s*mocha\s*:*.*([\'\"])/si",
2564 "/^([\'\"])\s*about\s*:.*([\'\"])/si"
0a6ec9b5 2565 ),
691a2d25 2566 Array(
8bd0068d 2567 "\\1#\\1",
2568 "\\1#\\1",
2569 "\\1#\\1"
02474e43 2570 )
8bd0068d 2571 ),
2572 "/^style/i" =>
2573 Array(
2574 Array(
5db90261 2575 "/\/\*.*\*\//",
8bd0068d 2576 "/expression/i",
2577 "/binding/i",
2578 "/behaviou*r/i",
2579 "/include-source/i",
39352565 2580
2581 // position:relative can also be exploited
2582 // to put content outside of email body area
2583 // and position:fixed is similarly exploitable
2584 // as position:absolute, so we'll remove it
2585 // altogether....
2586 //
2587 // Does this screw up legitimate HTML messages?
2588 // If so, the only fix I see is to allow position
2589 // attributes (any values? I think we still have
2590 // to block static and fixed) only if $use_iframe
2591 // is enabled (1.5.0+)
2592 //
2593 // was: "/position\s*:\s*absolute/i",
2594 //
2595 "/position\s*:/i",
2596
1d935bc2 2597 "/(\\\\)?u(\\\\)?r(\\\\)?l(\\\\)?/i",
8bd0068d 2598 "/url\s*\(\s*([\'\"])\s*\S+script\s*:.*([\'\"])\s*\)/si",
2599 "/url\s*\(\s*([\'\"])\s*mocha\s*:.*([\'\"])\s*\)/si",
2600 "/url\s*\(\s*([\'\"])\s*about\s*:.*([\'\"])\s*\)/si",
39352565 2601 "/(.*)\s*:\s*url\s*\(\s*([\'\"]*)\s*\S+script\s*:.*([\'\"]*)\s*\)/si",
8bd0068d 2602 ),
2603 Array(
5db90261 2604 "",
8bd0068d 2605 "idiocy",
2606 "idiocy",
2607 "idiocy",
2608 "idiocy",
567dc524 2609 "idiocy",
5b4884be 2610 "url",
8bd0068d 2611 "url(\\1#\\1)",
2612 "url(\\1#\\1)",
2613 "url(\\1#\\1)",
8bd0068d 2614 "\\1:url(\\2#\\3)"
2615 )
691a2d25 2616 )
8bd0068d 2617 )
691a2d25 2618 );
9ebace19 2619
d31f73f1 2620 // If there's no "view_unsafe_images" variable in the URL, turn unsafe
2621 // images off by default.
afbf184c 2622 sqgetGlobalVar('view_unsafe_images', $view_unsafe_images, SQ_GET, FALSE);
9ebace19 2623
691a2d25 2624 if (!$view_unsafe_images){
2625 /**
8bd0068d 2626 * Remove any references to http/https if view_unsafe_images set
2627 * to false.
2628 */
02474e43 2629 array_push($bad_attvals{'/.*/'}{'/^src|background/i'}[0],
8bd0068d 2630 '/^([\'\"])\s*https*:.*([\'\"])/si');
02474e43 2631 array_push($bad_attvals{'/.*/'}{'/^src|background/i'}[1],
8bd0068d 2632 "\\1$secremoveimg\\1");
02474e43 2633 array_push($bad_attvals{'/.*/'}{'/^style/i'}[0],
7ef0b415 2634 '/url\([\'\"]?https?:[^\)]*[\'\"]?\)/si');
02474e43 2635 array_push($bad_attvals{'/.*/'}{'/^style/i'}[1],
8bd0068d 2636 "url(\\1$secremoveimg\\1)");
691a2d25 2637 }
451f74a2 2638
691a2d25 2639 $add_attr_to_tag = Array(
8bd0068d 2640 "/^a$/i" =>
c25f2fbb 2641 Array('target'=>'"_blank"',
02474e43 2642 'title'=>'"'._("This external link will open in a new window").'"'
8bd0068d 2643 )
2644 );
da2415c1 2645 $trusted = sq_sanitize($body,
8bd0068d 2646 $tag_list,
2647 $rm_tags_with_content,
2648 $self_closing_tags,
2649 $force_tag_closing,
2650 $rm_attnames,
2651 $bad_attvals,
2652 $add_attr_to_tag,
2653 $message,
2654 $id,
2655 $mailbox
2656 );
567dc524 2657 if (strpos($trusted,$secremoveimg)){
691a2d25 2658 $has_unsafe_images = true;
da2415c1 2659 }
c189a963 2660
2661 // we want to parse mailto's in HTML output, change to SM compose links
2662 // this is a modified version of code from url_parser.php... but Marc is
2663 // right: we need a better filtering implementation; adding this randomly
2664 // here is not a great solution
2665 //
2666 if ($take_mailto_links) {
2667 // parseUrl($trusted); // this even parses URLs inside of tags... too aggressive
2668 global $MailTo_PReg_Match;
202bcbcc 2669 $MailTo_PReg_Match = '/mailto:' . substr($MailTo_PReg_Match, 1) ;
c189a963 2670 if ((preg_match_all($MailTo_PReg_Match, $trusted, $regs)) && ($regs[0][0] != '')) {
2671 foreach ($regs[0] as $i => $mailto_before) {
2672 $mailto_params = $regs[10][$i];
2673 // get rid of any tailing quote since we have to add send_to to the end
2674 //
2675 if (substr($mailto_before, strlen($mailto_before) - 1) == '"')
2676 $mailto_before = substr($mailto_before, 0, strlen($mailto_before) - 1);
2677 if (substr($mailto_params, strlen($mailto_params) - 1) == '"')
2678 $mailto_params = substr($mailto_params, 0, strlen($mailto_params) - 1);
2679
2680 if ($regs[1][$i]) { //if there is an email addr before '?', we need to merge it with the params
2681 $to = 'to=' . $regs[1][$i];
2682 if (strpos($mailto_params, 'to=') > -1) //already a 'to='
2683 $mailto_params = str_replace('to=', $to . '%2C%20', $mailto_params);
2684 else {
2685 if ($mailto_params) //already some params, append to them
2686 $mailto_params .= '&amp;' . $to;
2687 else
2688 $mailto_params .= '?' . $to;
2689 }
2690 }
2691
2692 $url_str = preg_replace(array('/to=/i', '/(?<!b)cc=/i', '/bcc=/i'), array('send_to=', 'send_to_cc=', 'send_to_bcc='), $mailto_params);
2693
2694 // we'll already have target=_blank, no need to allow comp_in_new
2695 // here (which would be a lot more work anyway)
2696 //
2697 global $compose_new_win;
2698 $temp_comp_in_new = $compose_new_win;
2699 $compose_new_win = 0;
2700 $comp_uri = makeComposeLink('src/compose.php' . $url_str, $mailto_before);
2701 $compose_new_win = $temp_comp_in_new;
2702
2703 // remove <a href=" and anything after the next quote (we only
2704 // need the uri, not the link HTML) in compose uri
2705 //
2706 $comp_uri = substr($comp_uri, 9);
2707 $comp_uri = substr($comp_uri, 0, strpos($comp_uri, '"', 1));
2708 $trusted = str_replace($mailto_before, $comp_uri, $trusted);
2709 }
2710 }
2711 }
2712
691a2d25 2713 return $trusted;
451f74a2 2714}
a4a70693 2715
da2415c1 2716/**
8bd0068d 2717 * function SendDownloadHeaders - send file to the browser
2718 *
2719 * Original Source: SM core src/download.php
2720 * moved here to make it available to other code, and separate
2721 * front end from back end functionality.
2722 *
2723 * @param string $type0 first half of mime type
2724 * @param string $type1 second half of mime type
2725 * @param string $filename filename to tell the browser for downloaded file
2726 * @param boolean $force whether to force the download dialog to pop
2727 * @param optional integer $filesize send the Content-Header and length to the browser
2728 * @return void
2729 */
02474e43 2730function SendDownloadHeaders($type0, $type1, $filename, $force, $filesize=0) {
2731 global $languages, $squirrelmail_language;
cfffd60b 2732 $isIE = $isIE6plus = false;
02474e43 2733
2734 sqgetGlobalVar('HTTP_USER_AGENT', $HTTP_USER_AGENT, SQ_SERVER);
2735
2736 if (strstr($HTTP_USER_AGENT, 'compatible; MSIE ') !== false &&
8bd0068d 2737 strstr($HTTP_USER_AGENT, 'Opera') === false) {
cfffd60b 2738 $isIE = true;
02474e43 2739 }
2740
cfffd60b 2741 if (preg_match('/compatible; MSIE ([0-9]+)/', $HTTP_USER_AGENT, $match) &&
2742 ((int)$match[1]) >= 6 && strstr($HTTP_USER_AGENT, 'Opera') === false) {
2743 $isIE6plus = true;
02474e43 2744 }
2745
2746 if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
8bd0068d 2747 function_exists($languages[$squirrelmail_language]['XTRA_CODE'] . '_downloadfilename')) {
02474e43 2748 $filename =
8bd0068d 2749 call_user_func($languages[$squirrelmail_language]['XTRA_CODE'] . '_downloadfilename', $filename, $HTTP_USER_AGENT);
02474e43 2750 } else {
1a4bc4a6 2751 $filename = preg_replace('/[\\\\\/:*?"<>|;]/', '_', str_replace('&nbsp;', ' ', $filename));
02474e43 2752 }
2753
2754 // A Pox on Microsoft and it's Internet Explorer!
2755 //
2756 // IE has lots of bugs with file downloads.
2757 // It also has problems with SSL. Both of these cause problems
2758 // for us in this function.
2759 //
2760 // See this article on Cache Control headers and SSL
2761 // http://support.microsoft.com/default.aspx?scid=kb;en-us;323308
2762 //
2763 // The best thing you can do for IE is to upgrade to the latest
2764 // version
2765 //set all the Cache Control Headers for IE
2766 if ($isIE) {
2767 $filename=rawurlencode($filename);
2768 header ("Pragma: public");
8bd0068d 2769 header ("Cache-Control: no-store, max-age=0, no-cache, must-revalidate"); // HTTP/1.1
02474e43 2770 header ("Cache-Control: post-check=0, pre-check=0", false);
8bd0068d 2771 header ("Cache-Control: private");
02474e43 2772
2773 //set the inline header for IE, we'll add the attachment header later if we need it
2774 header ("Content-Disposition: inline; filename=$filename");
2775 }
2776
2777 if (!$force) {
2778 // Try to show in browser window
2779 header ("Content-Disposition: inline; filename=\"$filename\"");
2780 header ("Content-Type: $type0/$type1; name=\"$filename\"");
2781 } else {
2782 // Try to pop up the "save as" box
2783
2784 // IE makes this hard. It pops up 2 save boxes, or none.
2785 // http://support.microsoft.com/support/kb/articles/Q238/5/88.ASP
2786 // http://support.microsoft.com/default.aspx?scid=kb;EN-US;260519
2787 // But, according to Microsoft, it is "RFC compliant but doesn't
2788 // take into account some deviations that allowed within the
2789 // specification." Doesn't that mean RFC non-compliant?
2790 // http://support.microsoft.com/support/kb/articles/Q258/4/52.ASP
2791
2792 // all browsers need the application/octet-stream header for this
2793 header ("Content-Type: application/octet-stream; name=\"$filename\"");
2794
2795 // http://support.microsoft.com/support/kb/articles/Q182/3/15.asp
2796 // Do not have quotes around filename, but that applied to
2797 // "attachment"... does it apply to inline too?
2798 header ("Content-Disposition: attachment; filename=\"$filename\"");
2799
cfffd60b 2800 if ($isIE && !$isIE6plus) {
02474e43 2801 // This combination seems to work mostly. IE 5.5 SP 1 has
2802 // known issues (see the Microsoft Knowledge Base)
2803
2804 // This works for most types, but doesn't work with Word files
2805 header ("Content-Type: application/download; name=\"$filename\"");
7e2ff844 2806 header ("Content-Type: application/force-download; name=\"$filename\"");
02474e43 2807 // These are spares, just in case. :-)
2808 //header("Content-Type: $type0/$type1; name=\"$filename\"");
2809 //header("Content-Type: application/x-msdownload; name=\"$filename\"");
2810 //header("Content-Type: application/octet-stream; name=\"$filename\"");
7e2ff844 2811 } else if ($isIE) {
2812 // This is to prevent IE for MIME sniffing and auto open a file in IE
2813 header ("Content-Type: application/force-download; name=\"$filename\"");
02474e43 2814 } else {
2815 // another application/octet-stream forces download for Netscape
2816 header ("Content-Type: application/octet-stream; name=\"$filename\"");
2817 }
2818 }
2819
2820 //send the content-length header if the calling function provides it
2821 if ($filesize > 0) {
2822 header("Content-Length: $filesize");
2823 }
07c49f57 2824
8d863f64 2825} // end fn SendDownloadHeaders