mime.php 41 KB

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