mime.php 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  1. <?php
  2. /**
  3. * mime.php
  4. *
  5. * Copyright (c) 1999-2001 The SquirrelMail Development Team
  6. * Licensed under the GNU GPL. For full terms see the file COPYING.
  7. *
  8. * This contains the functions necessary to detect and decode MIME
  9. * messages.
  10. *
  11. * $Id$
  12. */
  13. /*****************************************************************/
  14. /*** THIS FILE NEEDS TO HAVE ITS FORMATTING FIXED!!! ***/
  15. /*** PLEASE DO SO AND REMOVE THIS COMMENT SECTION. ***/
  16. /*** + Base level indent should begin at left margin, as ***/
  17. /*** the require_once below. ***/
  18. /*** + All identation should consist of four space blocks ***/
  19. /*** + Tab characters are evil. ***/
  20. /*** + all comments should use "slash-star ... star-slash" ***/
  21. /*** style -- no pound characters, no slash-slash style ***/
  22. /*** + FLOW CONTROL STATEMENTS (if, while, etc) SHOULD ***/
  23. /*** ALWAYS USE { AND } CHARACTERS!!! ***/
  24. /*** + Please use ' instead of ", when possible. Note " ***/
  25. /*** should always be used in _( ) function calls. ***/
  26. /*** Thank you for your help making the SM code more readable. ***/
  27. /*****************************************************************/
  28. require_once('../functions/imap.php');
  29. require_once('../functions/attachment_common.php');
  30. /** Setting up the objects that have the structure for the message **/
  31. class msg_header {
  32. /** msg_header contains generic variables for values that **/
  33. /** could be in a header. **/
  34. var $type0 = '', $type1 = '', $boundary = '', $charset = '';
  35. var $encoding = '', $size = 0, $to = array(), $from = '', $date = '';
  36. var $cc = array(), $bcc = array(), $reply_to = '', $subject = '';
  37. var $id = 0, $mailbox = '', $description = '', $filename = '';
  38. var $entity_id = 0, $message_id = 0, $name = '';
  39. // var $priority = "";
  40. }
  41. class message {
  42. /** message is the object that contains messages. It is a recursive
  43. object in that through the $entities variable, it can contain
  44. more objects of type message. See documentation in mime.txt for
  45. a better description of how this works.
  46. **/
  47. var $header = '';
  48. var $entities = array();
  49. function addEntity ($msg) {
  50. $this->entities[] = $msg;
  51. }
  52. }
  53. /* --------------------------------------------------------------------------------- */
  54. /* MIME DECODING */
  55. /* --------------------------------------------------------------------------------- */
  56. // This function gets the structure of a message and stores it in the "message" class.
  57. // It will return this object for use with all relevant header information and
  58. // fully parsed into the standard "message" object format.
  59. function mime_structure ($imap_stream, $header) {
  60. sqimap_messages_flag ($imap_stream, $header->id, $header->id, 'Seen');
  61. $ssid = sqimap_session_id();
  62. $lsid = strlen( $ssid );
  63. $id = $header->id;
  64. fputs ($imap_stream, "$ssid FETCH $id BODYSTRUCTURE\r\n");
  65. //
  66. // This should use sqimap_read_data instead of reading it itself
  67. //
  68. $read = fgets ($imap_stream, 10000);
  69. $bodystructure = '';
  70. while( substr($read, 0, $lsid) <> $ssid &&
  71. !feof( $imap_stream ) ) {
  72. $bodystructure .= $read;
  73. $read = fgets ($imap_stream, 10000);
  74. }
  75. $read = $bodystructure;
  76. // isolate the body structure and remove beginning and end parenthesis
  77. $read = trim(substr ($read, strpos(strtolower($read), 'bodystructure') + 13));
  78. $read = trim(substr ($read, 0, -1));
  79. $end = mime_match_parenthesis(0, $read);
  80. while ($end == strlen($read)-1) {
  81. $read = trim(substr ($read, 0, -1));
  82. $read = trim(substr ($read, 1));
  83. $end = mime_match_parenthesis(0, $read);
  84. }
  85. $msg = mime_parse_structure ($read, 0);
  86. $msg->header = $header;
  87. return $msg;
  88. }
  89. // this starts the parsing of a particular structure. It is called recursively,
  90. // so it can be passed different structures. It returns an object of type
  91. // $message.
  92. // First, it checks to see if it is a multipart message. If it is, then it
  93. // handles that as it sees is necessary. If it is just a regular entity,
  94. // then it parses it and adds the necessary header information (by calling out
  95. // to mime_get_elements()
  96. function mime_parse_structure ($structure, $ent_id) {
  97. $msg = new message();
  98. if ($structure{0} == '(') {
  99. $ent_id = mime_new_element_level($ent_id);
  100. $start = $end = -1;
  101. do {
  102. $start = $end+1;
  103. $end = mime_match_parenthesis ($start, $structure);
  104. $element = substr($structure, $start+1, ($end - $start)-1);
  105. $ent_id = mime_increment_id ($ent_id);
  106. $newmsg = mime_parse_structure ($element, $ent_id);
  107. $msg->addEntity ($newmsg);
  108. } while ($structure{$end+1} == '(');
  109. } else {
  110. // parse the elements
  111. $msg = mime_get_element ($structure, $msg, $ent_id);
  112. }
  113. return $msg;
  114. }
  115. // Increments the element ID. An element id can look like any of
  116. // the following: 1, 1.2, 4.3.2.4.1, etc. This function increments
  117. // the last number of the element id, changing 1.2 to 1.3.
  118. function mime_increment_id ($id) {
  119. if (strpos($id, ".")) {
  120. $first = substr($id, 0, strrpos($id, "."));
  121. $last = substr($id, strrpos($id, ".")+1);
  122. $last++;
  123. $new = $first . "." .$last;
  124. } else {
  125. $new = $id + 1;
  126. }
  127. return $new;
  128. }
  129. // See comment for mime_increment_id().
  130. // This adds another level on to the entity_id changing 1.3 to 1.3.0
  131. // NOTE: 1.3.0 is not a valid element ID. It MUST be incremented
  132. // before it can be used. I left it this way so as not to have
  133. // to make a special case if it is the first entity_id. It
  134. // always increments it, and that works fine.
  135. function mime_new_element_level ($id) {
  136. if (!$id) {
  137. $id = 0;
  138. } else {
  139. $id = $id . '.0';
  140. }
  141. return( $id );
  142. }
  143. function mime_get_element (&$structure, $msg, $ent_id) {
  144. $elem_num = 1;
  145. $msg->header = new msg_header();
  146. $msg->header->entity_id = $ent_id;
  147. $properties = array();
  148. while (strlen($structure) > 0) {
  149. $structure = trim($structure);
  150. $char = $structure{0};
  151. if (strtolower(substr($structure, 0, 3)) == 'nil') {
  152. $text = '';
  153. $structure = substr($structure, 3);
  154. } else if ($char == '"') {
  155. // loop through until we find the matching quote, and return that as a string
  156. $pos = 1;
  157. $text = '';
  158. while ( ($char = $structure{$pos} ) <> '"' && $pos < strlen($structure)) {
  159. $text .= $char;
  160. $pos++;
  161. }
  162. $structure = substr($structure, strlen($text) + 2);
  163. } else if ($char == '(') {
  164. // comment me
  165. $end = mime_match_parenthesis (0, $structure);
  166. $sub = substr($structure, 1, $end-1);
  167. $properties = mime_get_props($properties, $sub);
  168. $structure = substr($structure, strlen($sub) + 2);
  169. } else {
  170. // loop through until we find a space or an end parenthesis
  171. $pos = 0;
  172. $char = $structure{$pos};
  173. $text = '';
  174. while ($char != ' ' && $char != ')' && $pos < strlen($structure)) {
  175. $text .= $char;
  176. $pos++;
  177. $char = $structure{$pos};
  178. }
  179. $structure = substr($structure, strlen($text));
  180. }
  181. // This is where all the text parts get put into the header
  182. switch ($elem_num) {
  183. case 1:
  184. $msg->header->type0 = strtolower($text);
  185. break;
  186. case 2:
  187. $msg->header->type1 = strtolower($text);
  188. break;
  189. case 4: // Id
  190. // Invisimail enclose images with <>
  191. $msg->header->id = str_replace( '<', '', str_replace( '>', '', $text ) );
  192. break;
  193. case 5:
  194. $msg->header->description = $text;
  195. break;
  196. case 6:
  197. $msg->header->encoding = strtolower($text);
  198. break;
  199. case 7:
  200. $msg->header->size = $text;
  201. break;
  202. default:
  203. if ($msg->header->type0 == 'text' && $elem_num == 8) {
  204. // This is a plain text message, so lets get the number of lines
  205. // that it contains.
  206. $msg->header->num_lines = $text;
  207. } else if ($msg->header->type0 == 'message' && $msg->header->type1 == 'rfc822' && $elem_num == 8) {
  208. // This is an encapsulated message, so lets start all over again and
  209. // parse this message adding it on to the existing one.
  210. $structure = trim($structure);
  211. if ( $structure{0} == '(' ) {
  212. $e = mime_match_parenthesis (0, $structure);
  213. $structure = substr($structure, 0, $e);
  214. $structure = substr($structure, 1);
  215. $m = mime_parse_structure($structure, $msg->header->entity_id);
  216. // the following conditional is there to correct a bug that wasn't
  217. // incrementing the entity IDs correctly because of the special case
  218. // that message/rfc822 is. This fixes it fine.
  219. if (substr($structure, 1, 1) != '(')
  220. $m->header->entity_id = mime_increment_id(mime_new_element_level($ent_id));
  221. // Now we'll go through and reformat the results.
  222. if ($m->entities) {
  223. for ($i=0; $i < count($m->entities); $i++) {
  224. $msg->addEntity($m->entities[$i]);
  225. }
  226. } else {
  227. $msg->addEntity($m);
  228. }
  229. $structure = "";
  230. }
  231. }
  232. break;
  233. }
  234. $elem_num++;
  235. $text = "";
  236. }
  237. // loop through the additional properties and put those in the various headers
  238. if ($msg->header->type0 != 'message') {
  239. for ($i=0; $i < count($properties); $i++) {
  240. $msg->header->{$properties[$i]['name']} = $properties[$i]['value'];
  241. }
  242. }
  243. return $msg;
  244. }
  245. // I did most of the MIME stuff yesterday (June 20, 2000), but I couldn't
  246. // figure out how to do this part, so I decided to go to bed. I woke up
  247. // in the morning and had a flash of insight. I went to the white-board
  248. // and scribbled it out, then spent a bit programming it, and this is the
  249. // result. Nothing complicated, but I think my brain was fried yesterday.
  250. // Funny how that happens some times.
  251. //
  252. // This gets properties in a nested parenthesisized list. For example,
  253. // this would get passed something like: ("attachment" ("filename" "luke.tar.gz"))
  254. // This returns an array called $props with all paired up properties.
  255. // It ignores the "attachment" for now, maybe that should change later
  256. // down the road. In this case, what is returned is:
  257. // $props[0]["name"] = "filename";
  258. // $props[0]["value"] = "luke.tar.gz";
  259. function mime_get_props ($props, $structure) {
  260. while (strlen($structure) > 0) {
  261. $structure = trim($structure);
  262. $char = $structure{0};
  263. if ($char == '"') {
  264. $pos = 1;
  265. $tmp = '';
  266. while ( ( $char = $structure{$pos} ) != '"' &&
  267. $pos < strlen($structure)) {
  268. $tmp .= $char;
  269. $pos++;
  270. }
  271. $structure = trim(substr($structure, strlen($tmp) + 2));
  272. $char = $structure{0};
  273. if ($char == '"') {
  274. $pos = 1;
  275. $value = '';
  276. while ( ( $char = $structure{$pos} ) != '"' &&
  277. $pos < strlen($structure) ) {
  278. $value .= $char;
  279. $pos++;
  280. }
  281. $structure = trim(substr($structure, strlen($tmp) + 2));
  282. $k = count($props);
  283. $props[$k]['name'] = strtolower($tmp);
  284. $props[$k]['value'] = $value;
  285. } else if ($char == '(') {
  286. $end = mime_match_parenthesis (0, $structure);
  287. $sub = substr($structure, 1, $end-1);
  288. if (! isset($props))
  289. $props = array();
  290. $props = mime_get_props($props, $sub);
  291. $structure = substr($structure, strlen($sub) + 2);
  292. }
  293. return $props;
  294. } else if ($char == '(') {
  295. $end = mime_match_parenthesis (0, $structure);
  296. $sub = substr($structure, 1, $end-1);
  297. $props = mime_get_props($props, $sub);
  298. $structure = substr($structure, strlen($sub) + 2);
  299. return $props;
  300. } else {
  301. return $props;
  302. }
  303. }
  304. }
  305. // Matches parenthesis. It will return the position of the matching
  306. // parenthesis in $structure. For instance, if $structure was:
  307. // ("text" "plain" ("val1name", "1") nil ... )
  308. // x x
  309. // then this would return 42 to match up those two.
  310. function mime_match_parenthesis ($pos, $structure) {
  311. $j = strlen( $structure );
  312. // ignore all extra characters
  313. // If inside of a string, skip string -- Boundary IDs and other
  314. // things can have ) in them.
  315. if( $structure{$pos} != '(' )
  316. return( $j );
  317. while( $pos < $j ) {
  318. $pos++;
  319. if ($structure{$pos} == ')') {
  320. return $pos;
  321. } elseif ($structure{$pos} == '"') {
  322. $pos++;
  323. while( $structure{$pos} != '"' &&
  324. $pos < $j ) {
  325. if (substr($structure, $pos, 2) == '\\"')
  326. $pos++;
  327. elseif (substr($structure, $pos, 2) == '\\\\')
  328. $pos++;
  329. $pos++;
  330. }
  331. } elseif ( $structure{$pos} == '(' ) {
  332. $pos = mime_match_parenthesis ($pos, $structure);
  333. }
  334. }
  335. echo "Error decoding mime structure. Report this as a bug!<br>\n";
  336. return( $pos );
  337. }
  338. function mime_fetch_body ($imap_stream, $id, $ent_id ) {
  339. // do a bit of error correction. If we couldn't find the entity id, just guess
  340. // that it is the first one. That is usually the case anyway.
  341. if (!$ent_id)
  342. $ent_id = 1;
  343. $sid = sqimap_session_id();
  344. fputs ($imap_stream, "$sid FETCH $id BODY[$ent_id]\r\n");
  345. $data = sqimap_read_data ($imap_stream, $sid, true, $response, $message);
  346. $topline = array_shift($data);
  347. while (! ereg('\\* [0-9]+ FETCH ', $topline) && $data)
  348. $topline = array_shift($data);
  349. $wholemessage = implode('', $data);
  350. if (ereg('\\{([^\\}]*)\\}', $topline, $regs)) {
  351. $ret = substr( $wholemessage, 0, $regs[1] );
  352. /*
  353. There is some information in the content info header that could be important
  354. in order to parse html messages. Let's get them here.
  355. */
  356. if( $ret{0} == '<' ) {
  357. fputs ($imap_stream, "$sid FETCH $id BODY[$ent_id.MIME]\r\n");
  358. $data = sqimap_read_data ($imap_stream, $sid, true, $response, $message);
  359. $base = '';
  360. $k = 10;
  361. foreach( $data as $d ) {
  362. if( substr( $d, 0, 13 ) == 'Content-Base:' ) {
  363. $j = strlen( $d );
  364. $i = 13;
  365. $base = '';
  366. while( $i < $j &&
  367. ( !isNoSep( $d{$i} ) || $d{$i} == '"' ) )
  368. $i++;
  369. while( $i < $j ) {
  370. if( isNoSep( $d{$i} ) )
  371. $base .= $d{$i};
  372. $i++;
  373. }
  374. $k = 0;
  375. } elseif( $k == 1 && !isnosep( $d{0} ) ) {
  376. $base .= substr( $d, 1 );
  377. }
  378. $k++;
  379. }
  380. if( $base <> '' )
  381. $ret = "<base href=\"$base\">" . $ret;
  382. }
  383. } else if (ereg('"([^"]*)"', $topline, $regs)) {
  384. $ret = $regs[1];
  385. } else {
  386. global $where, $what, $mailbox, $passed_id, $startMessage;
  387. $par = "mailbox=".urlencode($mailbox)."&passed_id=$passed_id";
  388. if (isset($where) && isset($what)) {
  389. $par .= "&where=".urlencode($where)."&what=".urlencode($what);
  390. } else {
  391. $par .= "&startMessage=$startMessage&show_more=0";
  392. }
  393. $par .= '&response='.urlencode($response).'&message='.urlencode($message).
  394. '&topline='.urlencode($topline);
  395. echo '<b><font color=$color[2]>Body retrieval error. The reason for this is most probably that<BR> ' .
  396. 'the message is malformed. Please help us making future versions<BR> ' .
  397. "better by submitting this message to the developers knowledgebase!<BR>\n" .
  398. "<A HREF=\"../src/retrievalerror.php?$par\">Submit message</A><BR>" .
  399. "<tt>Response: $response<BR>" .
  400. "Message: $message<BR>" .
  401. "FETCH line: $topline<BR></tt></font></b>";
  402. fputs ($imap_stream, "$sid FETCH $passed_id BODY[]\r\n");
  403. $data = sqimap_read_data ($imap_stream, $sid, true, $response, $message);
  404. array_shift($data);
  405. $wholemessage = implode('', $data);
  406. $ret = "---------------\n$wholemessage";
  407. }
  408. return( $ret );
  409. }
  410. function mime_print_body_lines ($imap_stream, $id, $ent_id, $encoding) {
  411. // do a bit of error correction. If we couldn't find the entity id, just guess
  412. // that it is the first one. That is usually the case anyway.
  413. if (!$ent_id) $ent_id = 1;
  414. $sid = sqimap_session_id();
  415. // Don't kill the connection if the browser is over a dialup
  416. // and it would take over 30 seconds to download it.
  417. set_time_limit(0);
  418. fputs ($imap_stream, "$sid FETCH $id BODY[$ent_id]\r\n");
  419. $cnt = 0;
  420. $continue = true;
  421. $read = fgets ($imap_stream,4096);
  422. // This could be bad -- if the section has sqimap_session_id() . ' OK'
  423. // or similar, it will kill the download.
  424. while (!ereg("^".$sid." (OK|BAD|NO)(.*)$", $read, $regs)) {
  425. if (trim($read) == ')==') {
  426. $read1 = $read;
  427. $read = fgets ($imap_stream,4096);
  428. if (ereg("^".$sid." (OK|BAD|NO)(.*)$", $read, $regs)) {
  429. return;
  430. } else {
  431. echo decodeBody($read1, $encoding) .
  432. decodeBody($read, $encoding);
  433. }
  434. } else if ($cnt) {
  435. echo decodeBody($read, $encoding);
  436. }
  437. $read = fgets ($imap_stream,4096);
  438. $cnt++;
  439. }
  440. }
  441. /* -[ END MIME DECODING ]----------------------------------------------------------- */
  442. /** This is the first function called. It decides if this is a multipart
  443. message or if it should be handled as a single entity
  444. **/
  445. function decodeMime ($imap_stream, &$header) {
  446. global $username, $key, $imapServerAddress, $imapPort;
  447. return mime_structure ($imap_stream, $header);
  448. }
  449. // This is here for debugging purposese. It will print out a list
  450. // of all the entity IDs that are in the $message object.
  451. /*
  452. function listEntities ($message) {
  453. if ($message) {
  454. if ($message->header->entity_id)
  455. echo "<tt>" . $message->header->entity_id . ' : ' . $message->header->type0 . '/' . $message->header->type1 . '<br>';
  456. for ($i = 0; $message->entities[$i]; $i++) {
  457. $msg = listEntities($message->entities[$i], $ent_id);
  458. if ($msg)
  459. return $msg;
  460. }
  461. }
  462. }
  463. */
  464. // returns a $message object for a particular entity id
  465. function getEntity ($message, $ent_id) {
  466. if ($message) {
  467. if ($message->header->entity_id == $ent_id && strlen($ent_id) == strlen($message->header->entity_id)) {
  468. return $message;
  469. } else {
  470. for ($i = 0; isset($message->entities[$i]); $i++) {
  471. $msg = getEntity ($message->entities[$i], $ent_id);
  472. if ($msg)
  473. return $msg;
  474. }
  475. }
  476. }
  477. }
  478. // figures out what entity to display and returns the $message object
  479. // for that entity.
  480. function findDisplayEntity ($message, $textOnly = 1) {
  481. global $show_html_default;
  482. $entity = 0;
  483. if ($message) {
  484. if ( $message->header->type0 == 'multipart' &&
  485. ( $message->header->type1 == 'alternative' ||
  486. $message->header->type1 == 'related' ) &&
  487. $show_html_default && ! $textOnly ) {
  488. $entity = findDisplayEntityHTML($message);
  489. }
  490. // Show text/plain or text/html -- the first one we find.
  491. if ( $entity == 0 &&
  492. $message->header->type0 == 'text' &&
  493. ( $message->header->type1 == 'plain' ||
  494. $message->header->type1 == 'html' ) &&
  495. isset($message->header->entity_id) ) {
  496. $entity = $message->header->entity_id;
  497. }
  498. $i = 0;
  499. while ($entity == 0 && isset($message->entities[$i]) ) {
  500. $entity = findDisplayEntity($message->entities[$i], $textOnly);
  501. $i++;
  502. }
  503. }
  504. return( $entity );
  505. }
  506. // Shows the HTML version
  507. function findDisplayEntityHTML ($message) {
  508. if ($message->header->type0 == 'text' &&
  509. $message->header->type1 == 'html' &&
  510. isset($message->header->entity_id))
  511. return $message->header->entity_id;
  512. for ($i = 0; isset($message->entities[$i]); $i ++) {
  513. $entity = findDisplayEntityHTML($message->entities[$i]);
  514. if ($entity != 0)
  515. return $entity;
  516. }
  517. return 0;
  518. }
  519. /** This returns a parsed string called $body. That string can then
  520. be displayed as the actual message in the HTML. It contains
  521. everything needed, including HTML Tags, Attachments at the
  522. bottom, etc.
  523. **/
  524. function formatBody($imap_stream, $message, $color, $wrap_at) {
  525. // this if statement checks for the entity to show as the
  526. // primary message. To add more of them, just put them in the
  527. // order that is their priority.
  528. global $startMessage, $username, $key, $imapServerAddress, $imapPort,
  529. $show_html_default;
  530. $id = $message->header->id;
  531. $urlmailbox = urlencode($message->header->mailbox);
  532. // Get the right entity and redefine message to be this entity
  533. // Pass the 0 to mean that we want the 'best' viewable one
  534. $ent_num = findDisplayEntity ($message, 0);
  535. $body_message = getEntity($message, $ent_num);
  536. if (($body_message->header->type0 == 'text') ||
  537. ($body_message->header->type0 == 'rfc822')) {
  538. $body = mime_fetch_body ($imap_stream, $id, $ent_num);
  539. $body = decodeBody($body, $body_message->header->encoding);
  540. $hookResults = do_hook("message_body", $body);
  541. $body = $hookResults[1];
  542. // If there are other types that shouldn't be formatted, add
  543. // them here
  544. if ($body_message->header->type1 == 'html') {
  545. if( $show_html_default <> 1 ) {
  546. $body = strip_tags( $body );
  547. translateText($body, $wrap_at, $body_message->header->charset);
  548. } else {
  549. $body = MagicHTML( $body, $id );
  550. }
  551. } else {
  552. translateText($body, $wrap_at, $body_message->header->charset);
  553. }
  554. $body .= "<SMALL><CENTER><A HREF=\"../src/download.php?absolute_dl=true&passed_id=$id&passed_ent_id=$ent_num&mailbox=$urlmailbox&showHeaders=1\">". _("Download this as a file") ."</A></CENTER><BR></SMALL>";
  555. /** Display the ATTACHMENTS: message if there's more than one part **/
  556. $body .= "</TD></TR></TABLE>";
  557. if (isset($message->entities[0])) {
  558. $body .= formatAttachments ($message, $ent_num, $message->header->mailbox, $id);
  559. }
  560. $body .= "</TD></TR></TABLE>";
  561. } else {
  562. $body = formatAttachments ($message, -1, $message->header->mailbox, $id);
  563. }
  564. return( $body );
  565. }
  566. // A recursive function that returns a list of attachments with links
  567. // to where to download these attachments
  568. function formatAttachments ($message, $ent_id, $mailbox, $id) {
  569. global $where, $what;
  570. global $startMessage, $color;
  571. static $ShownHTML = 0;
  572. $body = "";
  573. if ($ShownHTML == 0) {
  574. $ShownHTML = 1;
  575. $body .= "<TABLE WIDTH=100% CELLSPACING=0 CELLPADDING=2 BORDER=0 BGCOLOR=\"$color[0]\"><TR>\n" .
  576. "<TH ALIGN=\"left\" BGCOLOR=\"$color[9]\"><B>\n" .
  577. _("Attachments") . ':' .
  578. "</B></TH></TR><TR><TD>\n" .
  579. "<TABLE CELLSPACING=0 CELLPADDING=1 BORDER=0>\n" .
  580. formatAttachments ($message, $ent_id, $mailbox, $id) .
  581. "</TABLE></TD></TR></TABLE>";
  582. return( $body );
  583. }
  584. if ($message) {
  585. if (!$message->entities) {
  586. $type0 = strtolower($message->header->type0);
  587. $type1 = strtolower($message->header->type1);
  588. $name = decodeHeader($message->header->name);
  589. if ($message->header->entity_id != $ent_id) {
  590. $filename = decodeHeader($message->header->filename);
  591. if (trim($filename) == '') {
  592. if (trim($name) == '') {
  593. if( trim( $message->header->id ) == '' )
  594. $display_filename = 'untitled-[' . $message->header->entity_id . ']' ;
  595. else
  596. $display_filename = 'cid: ' . $message->header->id;
  597. // $display_filename = 'untitled-[' . $message->header->entity_id . ']' ;
  598. } else {
  599. $display_filename = $name;
  600. $filename = $name;
  601. }
  602. } else {
  603. $display_filename = $filename;
  604. }
  605. $urlMailbox = urlencode($mailbox);
  606. $ent = urlencode($message->header->entity_id);
  607. $DefaultLink =
  608. "../src/download.php?startMessage=$startMessage&passed_id=$id&mailbox=$urlMailbox&passed_ent_id=$ent";
  609. if ($where && $what)
  610. $DefaultLink .= '&where=' . urlencode($where) . '&what=' . urlencode($what);
  611. $Links['download link']['text'] = _("download");
  612. $Links['download link']['href'] =
  613. "../src/download.php?absolute_dl=true&passed_id=$id&mailbox=$urlMailbox&passed_ent_id=$ent";
  614. $ImageURL = '';
  615. $HookResults = do_hook("attachment $type0/$type1", $Links,
  616. $startMessage, $id, $urlMailbox, $ent, $DefaultLink,
  617. $display_filename, $where, $what);
  618. $Links = $HookResults[1];
  619. $DefaultLink = $HookResults[6];
  620. $body .= '<TR><TD>&nbsp;&nbsp;</TD><TD>' .
  621. "<A HREF=\"$DefaultLink\">$display_filename</A>&nbsp;</TD>" .
  622. '<TD><SMALL><b>' . show_readable_size($message->header->size) .
  623. '</b>&nbsp;&nbsp;</small></TD>' .
  624. "<TD><SMALL>[ $type0/$type1 ]&nbsp;</SMALL></TD>" .
  625. '<TD><SMALL>';
  626. if ($message->header->description)
  627. $body .= '<b>' . htmlspecialchars($message->header->description) . '</b>';
  628. $body .= '</SMALL></TD><TD><SMALL>&nbsp;';
  629. $SkipSpaces = 1;
  630. foreach ($Links as $Val) {
  631. if ($SkipSpaces) {
  632. $SkipSpaces = 0;
  633. } else {
  634. $body .= '&nbsp;&nbsp;|&nbsp;&nbsp;';
  635. }
  636. $body .= '<a href="' . $Val['href'] . '">' . $Val['text'] . '</a>';
  637. }
  638. unset($Links);
  639. $body .= "</SMALL></TD></TR>\n";
  640. }
  641. } else {
  642. for ($i = 0; $i < count($message->entities); $i++) {
  643. $body .= formatAttachments ($message->entities[$i], $ent_id, $mailbox, $id);
  644. }
  645. }
  646. return( $body );
  647. }
  648. }
  649. /** this function decodes the body depending on the encoding type. **/
  650. function decodeBody($body, $encoding) {
  651. $body = str_replace("\r\n", "\n", $body);
  652. $encoding = strtolower($encoding);
  653. global $show_html_default;
  654. if ($encoding == 'quoted-printable') {
  655. $body = quoted_printable_decode($body);
  656. while (ereg("=\n", $body))
  657. $body = ereg_replace ("=\n", "", $body);
  658. } else if ($encoding == 'base64') {
  659. $body = base64_decode($body);
  660. }
  661. // All other encodings are returned raw.
  662. return $body;
  663. }
  664. // This functions decode strings that is encoded according to
  665. // RFC1522 (MIME Part Two: Message Header Extensions for Non-ASCII Text).
  666. function decodeHeader ($string) {
  667. if (eregi('=\\?([^?]+)\\?(q|b)\\?([^?]+)\\?=',
  668. $string, $res)) {
  669. if (ucfirst($res[2]) == "B") {
  670. $replace = base64_decode($res[3]);
  671. } else {
  672. $replace = ereg_replace("_", " ", $res[3]);
  673. // Convert lowercase Quoted Printable to uppercase for
  674. // quoted_printable_decode to understand it.
  675. while (ereg("(=(([0-9][abcdef])|([abcdef][0-9])|([abcdef][abcdef])))", $replace, $res)) {
  676. $replace = str_replace($res[1], strtoupper($res[1]), $replace);
  677. }
  678. $replace = quoted_printable_decode($replace);
  679. }
  680. $replace = charset_decode ($res[1], $replace);
  681. // Remove the name of the character set.
  682. $string = eregi_replace ('=\\?([^?]+)\\?(q|b)\\?([^?]+)\\?=',
  683. $replace, $string);
  684. // In case there should be more encoding in the string: recurse
  685. return (decodeHeader($string));
  686. } else
  687. return ($string);
  688. }
  689. // Encode a string according to RFC 1522 for use in headers if it
  690. // contains 8-bit characters or anything that looks like it should
  691. // be encoded.
  692. function encodeHeader ($string) {
  693. global $default_charset;
  694. // Encode only if the string contains 8-bit characters or =?
  695. $j = strlen( $string );
  696. $l = FALSE; // Must be encoded ?
  697. $ret = '';
  698. for( $i=0; $i < $j; ++$i) {
  699. switch( $string{$i} ) {
  700. case '=':
  701. $ret .= '=3D';
  702. break;
  703. case '?':
  704. $l = TRUE;
  705. $ret .= '=3F';
  706. break;
  707. case '_':
  708. $ret .= '=5F';
  709. break;
  710. case ' ':
  711. $ret .= '_';
  712. break;
  713. default:
  714. $k = ord( $string{$i} );
  715. if( $k > 126 ) {
  716. $ret .= sprintf("=%02X", $k);
  717. $l = TRUE;
  718. } else
  719. $ret .= $string{$i};
  720. }
  721. }
  722. if( $l )
  723. $string = "=?$default_charset?Q?$ret?=";
  724. return( $string );
  725. }
  726. /*
  727. Strips dangerous tags from html messages.
  728. */
  729. function MagicHTML( $body, $id ) {
  730. global $message, $PHP_SELF, $HTTP_SERVER_VARS;
  731. $j = strlen( $body ); // Legnth of the HTML
  732. $ret = ''; // Returned string
  733. $bgcolor = '#ffffff'; // Background style color (defaults to white)
  734. $textcolor = '#000000'; // Foreground style color (defaults to black)
  735. $leftmargin = ''; // Left margin style
  736. $title = ''; // HTML title if any
  737. $i = 0;
  738. while( $i < $j ) {
  739. if( $body{$i} == '<' ) {
  740. $pos = $i + 1;
  741. $tag = '';
  742. while ($body{$pos} == ' ' || $body{$pos} == "\t" ||
  743. $body{$pos} == "\n")
  744. $pos ++;
  745. while (strlen($tag) < 4 && $body{$pos} != ' ' &&
  746. $body{$pos} != "\t" && $body{$pos} != "\n") {
  747. $tag .= $body{$pos};
  748. $pos ++;
  749. }
  750. switch( strtoupper( $tag ) ) {
  751. // Strips the entire tag and contents
  752. case 'APPL':
  753. case 'EMBB':
  754. case 'FRAM':
  755. case 'SCRI':
  756. case 'OBJE':
  757. $etg = '/' . $tag;
  758. while( $body{$i+1}.$body{$i+2}.$body{$i+3}.$body{$i+4}.$body{$i+5} <> $etg &&
  759. $i < $j ) $i++;
  760. while( $i < $j && $body{++$i} <> '>' );
  761. // $ret .= "<!-- $tag removed -->";
  762. break;
  763. // Substitute Title
  764. case 'TITL':
  765. $i += 5;
  766. while( $body{$i} <> '>' && // </title>
  767. $i < $j )
  768. $i++;
  769. $i++;
  770. $title = '';
  771. while( $body{$i} <> '<' && // </title>
  772. $i < $j ) {
  773. $title .= $body{$i};
  774. $i++;
  775. }
  776. $i += 7;
  777. break;
  778. // Destroy these tags
  779. case 'HTML':
  780. case 'HEAD':
  781. case '/HTM':
  782. case '/HEA':
  783. case '!DOC':
  784. case 'META':
  785. case 'DIV ':
  786. case '/DIV':
  787. case '!-- ':
  788. $i += 4;
  789. while( $body{$i} <> '>' &&
  790. $i < $j )
  791. $i++;
  792. // $i++;
  793. break;
  794. case 'STYL':
  795. $i += 5;
  796. while( $body{$i} <> '>' && // </title>
  797. $i < $j )
  798. $i++;
  799. $i++;
  800. // We parse the style to look for interesting stuff
  801. $styleblk = '';
  802. while( $body{$i} <> '>' &&
  803. $i < $j ) {
  804. // First we get the name of the style
  805. $style = '';
  806. while( $body{$i} <> '>' &&
  807. $body{$i} <> '<' &&
  808. $body{$i} <> '{' &&
  809. $i < $j ) {
  810. if( isnoSep( $body{$i} ) )
  811. $style .= $body{$i};
  812. $i++;
  813. }
  814. stripComments( $i, $j, $body );
  815. $style = strtoupper( trim( $style ) );
  816. if( $style == 'BODY' ) {
  817. // Next we look into the definitions of the body style
  818. while( $body{$i} <> '>' &&
  819. $body{$i} <> '}' &&
  820. $i < $j ) {
  821. // We look for the background color if any.
  822. if( substr( $body, $i, 17 ) == 'BACKGROUND-COLOR:' ) {
  823. $i += 17;
  824. $bgcolor = getStyleData( $i, $j, $body );
  825. } elseif ( substr( $body, $i, 12 ) == 'MARGIN-LEFT:' ) {
  826. $i += 12;
  827. $leftmargin = getStyleData( $i, $j, $body );
  828. }
  829. $i++;
  830. }
  831. } else {
  832. // Other style are mantained
  833. $styleblk .= "$style ";
  834. while( $body{$i} <> '>' &&
  835. $body{$i} <> '<' &&
  836. $body{$i} <> '}' &&
  837. $i < $j ) {
  838. $styleblk .= $body{$i};
  839. $i++;
  840. }
  841. $styleblk .= $body{$i};
  842. }
  843. stripComments( $i, $j, $body );
  844. if( $body{$i} <> '>' )
  845. $i++;
  846. }
  847. if( $styleblk <> '' )
  848. $ret .= "<style>$styleblk";
  849. break;
  850. case 'BODY':
  851. if( $title <> '' )
  852. $ret .= '<b>' . _("Title:") . " </b>$title<br>\n";
  853. $ret .= "<TABLE";
  854. $i += 5;
  855. if (! isset($base))
  856. $base = '';
  857. $ret .= stripEvent( $i, $j, $body, $id, $base );
  858. $ret .= " bgcolor=$bgcolor width=\"100%\"><tr>";
  859. if( $leftmargin <> '' )
  860. $ret .= "<td width=$leftmargin>&nbsp;</td>";
  861. $ret .= '<td>';
  862. if (strtolower($bgcolor) == 'ffffff' ||
  863. strtolower($bgcolor) == '#ffffff')
  864. $ret .= '<font color=#000000>';
  865. break;
  866. case 'BASE':
  867. $i += 5;
  868. $base = '';
  869. while( !isNoSep( $body{$i} ) &&
  870. $i < $j )
  871. $i++;
  872. if( strcasecmp( substr( $base, 0, 4 ), 'href' ) ) {
  873. $i += 5;
  874. while( !isNoSep( $body{$i} ) &&
  875. $i < $j )
  876. $i++;
  877. while( $body{$i} <> '>' &&
  878. $i < $j ) {
  879. if( $body{$i} <> '"' )
  880. $base .= $body{$i};
  881. $i++;
  882. }
  883. // Debuging $ret .= "<!-- base == $base -->";
  884. if( strcasecmp( substr( $base, 0, 4 ), 'file' ) <> 0 )
  885. $ret .= "\n<BASE HREF=\"$base\">\n";
  886. }
  887. break;
  888. case '/BOD':
  889. $ret .= '</font></td></tr></TABLE>';
  890. $i += 6;
  891. break;
  892. default:
  893. // Following tags can contain some event handler, lets search it
  894. stripComments( $i, $j, $body );
  895. if (! isset($base))
  896. $base = '';
  897. $ret .= stripEvent( $i, $j, $body, $id, $base ) . '>';
  898. // $ret .= "<!-- $tag detected -->";
  899. }
  900. } else {
  901. $ret .= $body{$i};
  902. }
  903. $i++;
  904. }
  905. return( "\n\n<!-- HTML Output ahead -->\n" .
  906. $ret .
  907. "\n<!-- END of HTML Output --><base href=\"".
  908. $HTTP_SERVER_VARS["SERVER_NAME"] . substr( $PHP_SELF, 0, strlen( $PHP_SELF ) - 13 ) .
  909. "\">\n\n" );
  910. }
  911. function isNoSep( $char ) {
  912. switch( $char ) {
  913. case ' ':
  914. case "\n":
  915. case "\t":
  916. case "\r":
  917. case '>':
  918. case '"':
  919. return( FALSE );
  920. break;
  921. default:
  922. return( TRUE );
  923. }
  924. }
  925. /*
  926. The following function is usefull to remove extra data that can cause
  927. html not to display properly. Especialy with MS stuff.
  928. */
  929. function stripComments( &$i, $j, &$body ) {
  930. while( $body{$i}.$body{$i+1}.$body{$i+2}.$body{$i+3} == '<!--' &&
  931. $i < $j ) {
  932. $i += 5;
  933. while( $body{$i-2}.$body{$i-1}.$body{$i} <> '-->' &&
  934. $i < $j )
  935. $i++;
  936. $i++;
  937. }
  938. return;
  939. }
  940. /* Gets the style data of a specific style */
  941. function getStyleData( &$i, $j, &$body ) {
  942. // We skip spaces
  943. while( $body{$i} <> '>' && !isNoSep( $body{$i} ) &&
  944. $i < $j ) {
  945. $i++;
  946. }
  947. // And get the color
  948. $ret = '';
  949. while( isNoSep( $body{$i} ) &&
  950. $i < $j ) {
  951. $ret .= $body{$i};
  952. $i++;
  953. }
  954. return( $ret );
  955. }
  956. /*
  957. Private function for strip_dangerous_tag. Look for event based coded and "remove" it
  958. change on with no (onload -> noload)
  959. */
  960. function stripEvent( &$i, $j, &$body, $id, $base ) {
  961. global $message;
  962. $ret = '';
  963. while( $body{$i} <> '>' &&
  964. $i < $j ) {
  965. $etg = strtolower($body{$i}.$body{$i+1}.$body{$i+2});
  966. switch( $etg ) {
  967. case '../':
  968. // Retrolinks are not allowed without a base because they mess with SM security
  969. if( $base == '' ) {
  970. $i += 2;
  971. } else {
  972. $ret .= '.';
  973. }
  974. break;
  975. case 'cid':
  976. // Internal link
  977. $k = $i-1;
  978. if( $body{$i+3} == ':') {
  979. $i +=4;
  980. $name = '';
  981. while( isNoSep( $body{$i} ) &&
  982. $i < $j )
  983. $name .= $body{$i++};
  984. if( $name <> '' ) {
  985. $ret .= "../src/download.php?absolute_dl=true&passed_id=$id&mailbox=" .
  986. urlencode( $message->header->mailbox ) .
  987. "&passed_ent_id=" . find_ent_id( $name, $message );
  988. if( $body{$k} == '"' )
  989. $ret .= '" ';
  990. else
  991. $ret .= ' ';
  992. }
  993. if( $body{$i} == '>' )
  994. $i -= 1;
  995. }
  996. break;
  997. case ' on':
  998. case "\non":
  999. case "\ron":
  1000. case "\ton":
  1001. $ret .= ' no';
  1002. $i += 2;
  1003. break;
  1004. case 'pt:':
  1005. if( strcasecmp( $body{$i-4}.$body{$i-3}.$body{$i-2}.$body{$i-1}.$body{$i}.$body{$i+1}.$body{$i+2}, 'script:') == 0 ) {
  1006. $ret .= '_no/';
  1007. } else {
  1008. $ret .= $etg;
  1009. }
  1010. $i += 2;
  1011. break;
  1012. default:
  1013. $ret .= $body{$i};
  1014. }
  1015. $i++;
  1016. }
  1017. return( $ret );
  1018. }
  1019. /* This function trys to locate the entity_id of a specific mime element */
  1020. function find_ent_id( $id, $message ) {
  1021. $ret = '';
  1022. for ($i=0; $ret == '' && $i < count($message->entities); $i++) {
  1023. if( $message->entities[$i]->header->entity_id == '' ) {
  1024. $ret = find_ent_id( $id, $message->entities[$i] );
  1025. } else {
  1026. if( strcasecmp( $message->entities[$i]->header->id, $id ) == 0 )
  1027. $ret = $message->entities[$i]->header->entity_id;
  1028. }
  1029. }
  1030. return( $ret );
  1031. }
  1032. ?>