mime.php 42 KB

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