mime.php 43 KB

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