mime.php 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623
  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(SM_PATH . 'functions/imap.php');
  14. require_once(SM_PATH . 'functions/attachment_common.php');
  15. /* --------------------------------------------------------------------------------- */
  16. /* MIME DECODING */
  17. /* --------------------------------------------------------------------------------- */
  18. /* This function gets the structure of a message and stores it in the "message" class.
  19. * It will return this object for use with all relevant header information and
  20. * fully parsed into the standard "message" object format.
  21. */
  22. function mime_structure ($bodystructure, $flags=array()) {
  23. /* Isolate the body structure and remove beginning and end parenthesis. */
  24. $read = trim(substr ($bodystructure, strpos(strtolower($bodystructure), 'bodystructure') + 13));
  25. $read = trim(substr ($read, 0, -1));
  26. $msg =& new Message();
  27. $res = $msg->parseStructure($read);
  28. $msg = $res[0];
  29. if (!is_object($msg)) {
  30. include_once(SM_PATH . 'functions/display_messages.php');
  31. global $color, $mailbox;
  32. displayPageHeader( $color, urldecode($mailbox) );
  33. echo "<BODY TEXT=\"$color[8]\" BGCOLOR=\"$color[4]\" LINK=\"$color[7]\" VLINK=\"$color[7]\" ALINK=\"$color[7]\">\n\n" .
  34. '<CENTER>';
  35. $errormessage = _("Squirrelmail could not decode the bodystructure of the message");
  36. $errormessage .= '<BR>'._("the provided bodystructure by your imap-server").':<BR><BR>';
  37. $errormessage .= '<table><tr><td>' . htmlspecialchars($read) . '</td></tr></table>';
  38. plain_error_message( $errormessage, $color );
  39. echo '</body></html>';
  40. exit;
  41. }
  42. $msg->setEnt('0');
  43. if (count($flags)) {
  44. foreach ($flags as $flag) {
  45. $char = strtoupper($flag{1});
  46. switch ($char) {
  47. case 'S':
  48. if (strtolower($flag) == '\\seen') {
  49. $msg->is_seen = true;
  50. }
  51. break;
  52. case 'A':
  53. if (strtolower($flag) == '\\answered') {
  54. $msg->is_answered = true;
  55. }
  56. break;
  57. case 'D':
  58. if (strtolower($flag) == '\\deleted') {
  59. $msg->is_deleted = true;
  60. }
  61. break;
  62. case 'F':
  63. if (strtolower($flag) == '\\flagged') {
  64. $msg->is_flagged = true;
  65. }
  66. break;
  67. case 'M':
  68. if (strtolower($flag) == '$mdnsent') {
  69. $msg->is_mdnsent = true;
  70. }
  71. break;
  72. default:
  73. break;
  74. }
  75. }
  76. }
  77. // listEntities($msg);
  78. return $msg;
  79. }
  80. /* This starts the parsing of a particular structure. It is called recursively,
  81. * so it can be passed different structures. It returns an object of type
  82. * $message.
  83. * First, it checks to see if it is a multipart message. If it is, then it
  84. * handles that as it sees is necessary. If it is just a regular entity,
  85. * then it parses it and adds the necessary header information (by calling out
  86. * to mime_get_elements()
  87. */
  88. function mime_fetch_body($imap_stream, $id, $ent_id) {
  89. global $uid_support;
  90. /* Do a bit of error correction. If we couldn't find the entity id, just guess
  91. * that it is the first one. That is usually the case anyway.
  92. */
  93. if (!$ent_id) {
  94. $ent_id = 1;
  95. }
  96. $cmd = "FETCH $id BODY[$ent_id]";
  97. $data = sqimap_run_command ($imap_stream, $cmd, true, $response, $message, $uid_support);
  98. do {
  99. $topline = trim(array_shift($data));
  100. } while($topline && ($topline[0] == '*') && !preg_match('/\* [0-9]+ FETCH.*/i', $topline)) ;
  101. $wholemessage = implode('', $data);
  102. if (ereg('\\{([^\\}]*)\\}', $topline, $regs)) {
  103. $ret = substr($wholemessage, 0, $regs[1]);
  104. /* There is some information in the content info header that could be important
  105. * in order to parse html messages. Let's get them here.
  106. */
  107. if ($ret{0} == '<') {
  108. $data = sqimap_run_command ($imap_stream, "FETCH $id BODY[$ent_id.MIME]", true, $response, $message, $uid_support);
  109. }
  110. } else if (ereg('"([^"]*)"', $topline, $regs)) {
  111. $ret = $regs[1];
  112. } else {
  113. global $where, $what, $mailbox, $passed_id, $startMessage;
  114. $par = 'mailbox=' . urlencode($mailbox) . '&amp;passed_id=' . $passed_id;
  115. if (isset($where) && isset($what)) {
  116. $par .= '&amp;where=' . urlencode($where) . '&amp;what=' . urlencode($what);
  117. } else {
  118. $par .= '&amp;startMessage=' . $startMessage . '&amp;show_more=0';
  119. }
  120. $par .= '&amp;response=' . urlencode($response) .
  121. '&amp;message=' . urlencode($message) .
  122. '&amp;topline=' . urlencode($topline);
  123. echo '<tt><br>' .
  124. '<table width="80%"><tr>' .
  125. '<tr><td colspan=2>' .
  126. _("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!") .
  127. " <A HREF=\"../src/retrievalerror.php?$par\"><br>" .
  128. _("Submit message") . '</A><BR>&nbsp;' .
  129. '</td></tr>' .
  130. '<td><b>' . _("Command:") . "</td><td>$cmd</td></tr>" .
  131. '<td><b>' . _("Response:") . "</td><td>$response</td></tr>" .
  132. '<td><b>' . _("Message:") . "</td><td>$message</td></tr>" .
  133. '<td><b>' . _("FETCH line:") . "</td><td>$topline</td></tr>" .
  134. "</table><BR></tt></font><hr>";
  135. $data = sqimap_run_command ($imap_stream, "FETCH $passed_id BODY[]", true, $response, $message, $uid_support);
  136. array_shift($data);
  137. $wholemessage = implode('', $data);
  138. $ret = $wholemessage;
  139. }
  140. return $ret;
  141. }
  142. function mime_print_body_lines ($imap_stream, $id, $ent_id, $encoding) {
  143. global $uid_support;
  144. /* Do a bit of error correction. If we couldn't find the entity id, just guess
  145. * that it is the first one. That is usually the case anyway.
  146. */
  147. if (!$ent_id) {
  148. $ent_id = 1;
  149. }
  150. $sid = sqimap_session_id($uid_support);
  151. /* Don't kill the connection if the browser is over a dialup
  152. * and it would take over 30 seconds to download it.
  153. * Don´t call set_time_limit in safe mode.
  154. */
  155. if (!ini_get('safe_mode')) {
  156. set_time_limit(0);
  157. }
  158. if ($uid_support) {
  159. $sid_s = substr($sid,0,strpos($sid, ' '));
  160. } else {
  161. $sid_s = $sid;
  162. }
  163. $body = mime_fetch_body ($imap_stream, $id, $ent_id);
  164. echo decodeBody($body, $encoding);
  165. return;
  166. /*
  167. fputs ($imap_stream, "$sid FETCH $id BODY[$ent_id]\r\n");
  168. $cnt = 0;
  169. $continue = true;
  170. $read = fgets ($imap_stream,8192);
  171. // This could be bad -- if the section has sqimap_session_id() . ' OK'
  172. // or similar, it will kill the download.
  173. while (!ereg("^".$sid_s." (OK|BAD|NO)(.*)$", $read, $regs)) {
  174. if (trim($read) == ')==') {
  175. $read1 = $read;
  176. $read = fgets ($imap_stream,4096);
  177. if (ereg("^".$sid." (OK|BAD|NO)(.*)$", $read, $regs)) {
  178. return;
  179. } else {
  180. echo decodeBody($read1, $encoding) .
  181. decodeBody($read, $encoding);
  182. }
  183. } else if ($cnt) {
  184. echo decodeBody($read, $encoding);
  185. }
  186. $read = fgets ($imap_stream,4096);
  187. $cnt++;
  188. // break;
  189. }
  190. */
  191. }
  192. /* -[ END MIME DECODING ]----------------------------------------------------------- */
  193. /* This is here for debugging purposes. It will print out a list
  194. * of all the entity IDs that are in the $message object.
  195. */
  196. function listEntities ($message) {
  197. if ($message) {
  198. echo "<tt>" . $message->entity_id . ' : ' . $message->type0 . '/' . $message->type1 . ' parent = '. $message->parent->entity_id. '<br>';
  199. for ($i = 0; isset($message->entities[$i]); $i++) {
  200. echo "$i : ";
  201. $msg = listEntities($message->entities[$i]);
  202. if ($msg) {
  203. echo "return: ";
  204. return $msg;
  205. }
  206. }
  207. }
  208. }
  209. function getPriorityStr($priority) {
  210. $priority_level = substr($priority,0,1);
  211. switch($priority_level) {
  212. /* Check for a higher then normal priority. */
  213. case '1':
  214. case '2':
  215. $priority_string = _("High");
  216. break;
  217. /* Check for a lower then normal priority. */
  218. case '4':
  219. case '5':
  220. $priority_string = _("Low");
  221. break;
  222. /* Check for a normal priority. */
  223. case '3':
  224. default:
  225. $priority_level = '3';
  226. $priority_string = _("Normal");
  227. break;
  228. }
  229. return $priority_string;
  230. }
  231. /* returns a $message object for a particular entity id */
  232. function getEntity ($message, $ent_id) {
  233. return $message->getEntity($ent_id);
  234. }
  235. /* translateText
  236. * Extracted from strings.php 23/03/2002
  237. */
  238. function translateText(&$body, $wrap_at, $charset) {
  239. global $where, $what; /* from searching */
  240. global $color; /* color theme */
  241. require_once(SM_PATH . 'functions/url_parser.php');
  242. $body_ary = explode("\n", $body);
  243. for ($i=0; $i < count($body_ary); $i++) {
  244. $line = $body_ary[$i];
  245. if (strlen($line) - 2 >= $wrap_at) {
  246. sqWordWrap($line, $wrap_at);
  247. }
  248. $line = charset_decode($charset, $line);
  249. $line = str_replace("\t", ' ', $line);
  250. parseUrl ($line);
  251. $quotes = 0;
  252. $pos = 0;
  253. $j = strlen($line);
  254. while ($pos < $j) {
  255. if ($line[$pos] == ' ') {
  256. $pos++;
  257. } else if (strpos($line, '&gt;', $pos) === $pos) {
  258. $pos += 4;
  259. $quotes++;
  260. } else {
  261. break;
  262. }
  263. }
  264. if ($quotes > 1) {
  265. if (!isset($color[14])) {
  266. $color[14] = '#FF0000';
  267. }
  268. $line = '<FONT COLOR="' . $color[14] . '">' . $line . '</FONT>';
  269. } elseif ($quotes) {
  270. if (!isset($color[13])) {
  271. $color[13] = '#800000';
  272. }
  273. $line = '<FONT COLOR="' . $color[13] . '">' . $line . '</FONT>';
  274. }
  275. $body_ary[$i] = $line;
  276. }
  277. $body = '<pre>' . implode("\n", $body_ary) . '</pre>';
  278. }
  279. /* This returns a parsed string called $body. That string can then
  280. * be displayed as the actual message in the HTML. It contains
  281. * everything needed, including HTML Tags, Attachments at the
  282. * bottom, etc.
  283. */
  284. function formatBody($imap_stream, $message, $color, $wrap_at, $ent_num, $id, $mailbox='INBOX') {
  285. /* This if statement checks for the entity to show as the
  286. * primary message. To add more of them, just put them in the
  287. * order that is their priority.
  288. */
  289. global $startMessage, $username, $key, $imapServerAddress, $imapPort,
  290. $show_html_default, $has_unsafe_images, $view_unsafe_images, $sort;
  291. $has_unsafe_images= 0;
  292. $body = '';
  293. $urlmailbox = urlencode($mailbox);
  294. $body_message = getEntity($message, $ent_num);
  295. if (($body_message->header->type0 == 'text') ||
  296. ($body_message->header->type0 == 'rfc822')) {
  297. $body = mime_fetch_body ($imap_stream, $id, $ent_num);
  298. $body = decodeBody($body, $body_message->header->encoding);
  299. $hookResults = do_hook("message_body", $body);
  300. $body = $hookResults[1];
  301. /* If there are other types that shouldn't be formatted, add
  302. * them here.
  303. */
  304. if ($body_message->header->type1 == 'html') {
  305. if ($show_html_default <> 1) {
  306. $entity_conv = array('&nbsp;' => ' ',
  307. '<p>' => "\n",
  308. '<br>' => "\n",
  309. '<P>' => "\n",
  310. '<BR>' => "\n",
  311. '&gt;' => '>',
  312. '&lt;' => '<');
  313. $body = strtr($body, $entity_conv);
  314. $body = strip_tags($body);
  315. $body = trim($body);
  316. translateText($body, $wrap_at,
  317. $body_message->header->getParameter('charset'));
  318. } else {
  319. $body = magicHTML($body, $id, $message, $mailbox);
  320. }
  321. } else {
  322. translateText($body, $wrap_at,
  323. $body_message->header->getParameter('charset'));
  324. }
  325. if ($has_unsafe_images) {
  326. if ($view_unsafe_images) {
  327. $untext = '">' . _("Hide Unsafe Images");
  328. } else {
  329. $untext = '&amp;view_unsafe_images=1">' . _("View Unsafe Images");
  330. }
  331. $body .= '<center><small><a href="read_body.php?passed_id=' . $id .
  332. '&amp;passed_ent_id=' . $message->entity_id . '&amp;mailbox=' . $urlmailbox .
  333. '&amp;sort=' . $sort . '&amp;startMessage=' . $startMessage . '&amp;show_more=0' .
  334. $untext . '</a></small></center><br>' . "\n";
  335. }
  336. }
  337. return $body;
  338. }
  339. function formatAttachments($message, $exclude_id, $mailbox, $id) {
  340. global $where, $what, $startMessage, $color;
  341. static $ShownHTML = 0;
  342. $att_ar = $message->getAttachments($exclude_id);
  343. if (!count($att_ar)) return '';
  344. $attachments = '';
  345. $urlMailbox = urlencode($mailbox);
  346. foreach ($att_ar as $att) {
  347. $ent = urldecode($att->entity_id);
  348. $header = $att->header;
  349. $type0 = strtolower($header->type0);
  350. $type1 = strtolower($header->type1);
  351. $name = '';
  352. $links['download link']['text'] = _("download");
  353. $links['download link']['href'] =
  354. "../src/download.php?absolute_dl=true&amp;passed_id=$id&amp;mailbox=$urlMailbox&amp;ent_id=$ent";
  355. $ImageURL = '';
  356. if ($type0 =='message' && $type1 == 'rfc822') {
  357. $default_page = '../src/read_body.php';
  358. $rfc822_header = $att->rfc822_header;
  359. $filename = decodeHeader($rfc822_header->subject);
  360. $from_o = $rfc822_header->from;
  361. if (is_object($from_o)) {
  362. $from_name = $from_o->getAddress(false);
  363. } else {
  364. $from_name = _("Unknown sender");
  365. }
  366. $from_name = decodeHeader(htmlspecialchars($from_name));
  367. $description = $from_name;
  368. } else {
  369. $default_page = '../src/download.php';
  370. if (is_object($header->disposition)) {
  371. $filename = decodeHeader($header->disposition->getProperty('filename'));
  372. if (trim($filename) == '') {
  373. $name = decodeHeader($header->disposition->getProperty('name'));
  374. if (trim($name) == '') {
  375. if (trim( $header->id ) == '') {
  376. $filename = 'untitled-[' . $ent . ']' ;
  377. } else {
  378. $filename = 'cid: ' . $header->id;
  379. }
  380. } else {
  381. $filename = $name;
  382. }
  383. }
  384. } else {
  385. if (trim( $header->id ) == '') {
  386. $filename = 'untitled-[' . $ent . ']' ;
  387. } else {
  388. $filename = 'cid: ' . $header->id;
  389. }
  390. }
  391. if ($header->description) {
  392. $description = htmlspecialchars($header->description);
  393. } else {
  394. $description = '';
  395. }
  396. }
  397. $display_filename = $filename;
  398. if (isset($passed_ent_id)) {
  399. $passed_ent_id_link = '&amp;passed_ent_id='.$passed_ent_id;
  400. } else {
  401. $passed_ent_id_link = '';
  402. }
  403. $defaultlink = $default_page . "?startMessage=$startMessage"
  404. . "&amp;passed_id=$id&amp;mailbox=$urlMailbox"
  405. . '&amp;ent_id='.$ent.$passed_ent_id_link;
  406. if ($where && $what) {
  407. $defaultlink = '&amp;where='. urlencode($where).'&amp;what='.urlencode($what);
  408. }
  409. /* This executes the attachment hook with a specific MIME-type.
  410. * If that doesn't have results, it tries if there's a rule
  411. * for a more generic type.
  412. */
  413. $hookresults = do_hook("attachment $type0/$type1", $links,
  414. $startMessage, $id, $urlMailbox, $ent, $defaultlink,
  415. $display_filename, $where, $what);
  416. if(count($hookresults[1]) <= 1) {
  417. $hookresults = do_hook("attachment $type0/*", $links,
  418. $startMessage, $id, $urlMailbox, $ent, $defaultlink,
  419. $display_filename, $where, $what);
  420. }
  421. $links = $hookresults[1];
  422. $defaultlink = $hookresults[6];
  423. $attachments .= '<TR><TD>' .
  424. "<A HREF=\"$defaultlink\">$display_filename</A>&nbsp;</TD>" .
  425. '<TD><SMALL><b>' . show_readable_size($header->size) .
  426. '</b>&nbsp;&nbsp;</small></TD>' .
  427. "<TD><SMALL>[ $type0/$type1 ]&nbsp;</SMALL></TD>" .
  428. '<TD><SMALL>';
  429. $attachments .= '<b>' . $description . '</b>';
  430. $attachments .= '</SMALL></TD><TD><SMALL>&nbsp;';
  431. $skipspaces = 1;
  432. foreach ($links as $val) {
  433. if ($skipspaces) {
  434. $skipspaces = 0;
  435. } else {
  436. $attachments .= '&nbsp;&nbsp;|&nbsp;&nbsp;';
  437. }
  438. $attachments .= '<a href="' . $val['href'] . '">' . $val['text'] . '</a>';
  439. }
  440. unset($links);
  441. $attachments .= "</TD></TR>\n";
  442. }
  443. return $attachments;
  444. }
  445. /* This function decodes the body depending on the encoding type. */
  446. function decodeBody($body, $encoding) {
  447. global $languages, $squirrelmail_language;
  448. global $show_html_default;
  449. $body = str_replace("\r\n", "\n", $body);
  450. $encoding = strtolower($encoding);
  451. if ($encoding == 'quoted-printable' ||
  452. $encoding == 'quoted_printable') {
  453. $body = quoted_printable_decode($body);
  454. while (ereg("=\n", $body)) {
  455. $body = ereg_replace ("=\n", '', $body);
  456. }
  457. } else if ($encoding == 'base64') {
  458. $body = base64_decode($body);
  459. }
  460. if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
  461. function_exists($languages[$squirrelmail_language]['XTRA_CODE'])) {
  462. $body = $languages[$squirrelmail_language]['XTRA_CODE']('decode', $body);
  463. }
  464. // All other encodings are returned raw.
  465. return $body;
  466. }
  467. /*
  468. * This functions decode strings that is encoded according to
  469. * RFC1522 (MIME Part Two: Message Header Extensions for Non-ASCII Text).
  470. * Patched by Christian Schmidt <christian@ostenfeld.dk> 23/03/2002
  471. */
  472. function decodeHeader ($string, $utfencode=true) {
  473. global $languages, $squirrelmail_language;
  474. if (is_array($string)) {
  475. $string = implode("\n", $string);
  476. }
  477. if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
  478. function_exists($languages[$squirrelmail_language]['XTRA_CODE'])) {
  479. $string = $languages[$squirrelmail_language]['XTRA_CODE']('decodeheader', $string);
  480. }
  481. $i = 0;
  482. while (preg_match('/^(.{' . $i . '})(.*)=\?([^?]*)\?(Q|B)\?([^?]*)\?=/Ui',
  483. $string, $res)) {
  484. $prefix = $res[1];
  485. /* Ignore white-space between consecutive encoded-words. */
  486. if (strspn($res[2], " \t") != strlen($res[2])) {
  487. $prefix .= $res[2];
  488. }
  489. if (ucfirst($res[4]) == 'B') {
  490. $replace = base64_decode($res[5]);
  491. } else {
  492. $replace = str_replace('_', ' ', $res[5]);
  493. $replace = preg_replace('/=([0-9a-f]{2})/ie', 'chr(hexdec("\1"))',
  494. $replace);
  495. /* Only encode into entities by default. Some places
  496. * don't need the encoding, like the compose form.
  497. */
  498. if ($utfencode) {
  499. $replace = charset_decode($res[3], $replace);
  500. }
  501. }
  502. $string = $prefix . $replace . substr($string, strlen($res[0]));
  503. $i = strlen($prefix) + strlen($replace);
  504. }
  505. return $string;
  506. }
  507. /*
  508. * Encode a string according to RFC 1522 for use in headers if it
  509. * contains 8-bit characters or anything that looks like it should
  510. * be encoded.
  511. */
  512. function encodeHeader ($string) {
  513. global $default_charset, $languages, $squirrelmail_language;
  514. if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
  515. function_exists($languages[$squirrelmail_language]['XTRA_CODE'])) {
  516. return $languages[$squirrelmail_language]['XTRA_CODE']('encodeheader', $string);
  517. }
  518. // Encode only if the string contains 8-bit characters or =?
  519. $j = strlen($string);
  520. $l = strstr($string, '=?'); // Must be encoded ?
  521. $ret = '';
  522. for($i = 0; $i < $j; ++$i) {
  523. switch($string{$i}) {
  524. case '=':
  525. $ret .= '=3D';
  526. break;
  527. case '?':
  528. $ret .= '=3F';
  529. break;
  530. case '_':
  531. $ret .= '=5F';
  532. break;
  533. case ' ':
  534. $ret .= '_';
  535. break;
  536. default:
  537. $k = ord($string{$i});
  538. if ($k > 126) {
  539. $ret .= sprintf("=%02X", $k);
  540. $l = TRUE;
  541. } else {
  542. $ret .= $string{$i};
  543. }
  544. break;
  545. }
  546. }
  547. if ($l) {
  548. $string = "=?$default_charset?Q?$ret?=";
  549. }
  550. return $string;
  551. }
  552. /* This function trys to locate the entity_id of a specific mime element */
  553. function find_ent_id($id, $message) {
  554. for ($i = 0, $ret = ''; $ret == '' && $i < count($message->entities); $i++) {
  555. if ($message->entities[$i]->header->type0 == 'multipart') {
  556. $ret = find_ent_id($id, $message->entities[$i]);
  557. } else {
  558. if (strcasecmp($message->entities[$i]->header->id, $id) == 0) {
  559. if (sq_check_save_extension($message->entities[$i])) {
  560. return $message->entities[$i]->entity_id;
  561. }
  562. }
  563. }
  564. }
  565. return $ret;
  566. }
  567. function sq_check_save_extension($message) {
  568. $filename = $message->getFilename();
  569. $ext = substr($filename, strrpos($filename,'.')+1);
  570. $save_extensions = array('jpg','jpeg','gif','png','bmp');
  571. return in_array($ext, $save_extensions);
  572. }
  573. /**
  574. ** HTMLFILTER ROUTINES
  575. */
  576. /**
  577. * This function returns the final tag out of the tag name, an array
  578. * of attributes, and the type of the tag. This function is called by
  579. * sq_sanitize internally.
  580. *
  581. * @param $tagname the name of the tag.
  582. * @param $attary the array of attributes and their values
  583. * @param $tagtype The type of the tag (see in comments).
  584. * @return a string with the final tag representation.
  585. */
  586. function sq_tagprint($tagname, $attary, $tagtype){
  587. $me = 'sq_tagprint';
  588. if ($tagtype == 2){
  589. $fulltag = '</' . $tagname . '>';
  590. } else {
  591. $fulltag = '<' . $tagname;
  592. if (is_array($attary) && sizeof($attary)){
  593. $atts = Array();
  594. while (list($attname, $attvalue) = each($attary)){
  595. array_push($atts, "$attname=$attvalue");
  596. }
  597. $fulltag .= ' ' . join(" ", $atts);
  598. }
  599. if ($tagtype == 3){
  600. $fulltag .= ' /';
  601. }
  602. $fulltag .= '>';
  603. }
  604. return $fulltag;
  605. }
  606. /**
  607. * A small helper function to use with array_walk. Modifies a by-ref
  608. * value and makes it lowercase.
  609. *
  610. * @param $val a value passed by-ref.
  611. * @return void since it modifies a by-ref value.
  612. */
  613. function sq_casenormalize(&$val){
  614. $val = strtolower($val);
  615. }
  616. /**
  617. * This function skips any whitespace from the current position within
  618. * a string and to the next non-whitespace value.
  619. *
  620. * @param $body the string
  621. * @param $offset the offset within the string where we should start
  622. * looking for the next non-whitespace character.
  623. * @return the location within the $body where the next
  624. * non-whitespace char is located.
  625. */
  626. function sq_skipspace($body, $offset){
  627. $me = 'sq_skipspace';
  628. preg_match('/^(\s*)/s', substr($body, $offset), $matches);
  629. if (sizeof($matches{1})){
  630. $count = strlen($matches{1});
  631. $offset += $count;
  632. }
  633. return $offset;
  634. }
  635. /**
  636. * This function looks for the next character within a string. It's
  637. * really just a glorified "strpos", except it catches if failures
  638. * nicely.
  639. *
  640. * @param $body The string to look for needle in.
  641. * @param $offset Start looking from this position.
  642. * @param $needle The character/string to look for.
  643. * @return location of the next occurance of the needle, or
  644. * strlen($body) if needle wasn't found.
  645. */
  646. function sq_findnxstr($body, $offset, $needle){
  647. $me = 'sq_findnxstr';
  648. $pos = strpos($body, $needle, $offset);
  649. if ($pos === FALSE){
  650. $pos = strlen($body);
  651. }
  652. return $pos;
  653. }
  654. /**
  655. * This function takes a PCRE-style regexp and tries to match it
  656. * within the string.
  657. *
  658. * @param $body The string to look for needle in.
  659. * @param $offset Start looking from here.
  660. * @param $reg A PCRE-style regex to match.
  661. * @return Returns a false if no matches found, or an array
  662. * with the following members:
  663. * - integer with the location of the match within $body
  664. * - string with whatever content between offset and the match
  665. * - string with whatever it is we matched
  666. */
  667. function sq_findnxreg($body, $offset, $reg){
  668. $me = 'sq_findnxreg';
  669. $matches = Array();
  670. $retarr = Array();
  671. preg_match("%^(.*?)($reg)%s", substr($body, $offset), $matches);
  672. if (!$matches{0}){
  673. $retarr = false;
  674. } else {
  675. $retarr{0} = $offset + strlen($matches{1});
  676. $retarr{1} = $matches{1};
  677. $retarr{2} = $matches{2};
  678. }
  679. return $retarr;
  680. }
  681. /**
  682. * This function looks for the next tag.
  683. *
  684. * @param $body String where to look for the next tag.
  685. * @param $offset Start looking from here.
  686. * @return false if no more tags exist in the body, or
  687. * an array with the following members:
  688. * - string with the name of the tag
  689. * - array with attributes and their values
  690. * - integer with tag type (1, 2, or 3)
  691. * - integer where the tag starts (starting "<")
  692. * - integer where the tag ends (ending ">")
  693. * first three members will be false, if the tag is invalid.
  694. */
  695. function sq_getnxtag($body, $offset){
  696. $me = 'sq_getnxtag';
  697. if ($offset > strlen($body)){
  698. return false;
  699. }
  700. $lt = sq_findnxstr($body, $offset, "<");
  701. if ($lt == strlen($body)){
  702. return false;
  703. }
  704. /**
  705. * We are here:
  706. * blah blah <tag attribute="value">
  707. * \---------^
  708. */
  709. $pos = sq_skipspace($body, $lt+1);
  710. if ($pos >= strlen($body)){
  711. return Array(false, false, false, $lt, strlen($body));
  712. }
  713. /**
  714. * There are 3 kinds of tags:
  715. * 1. Opening tag, e.g.:
  716. * <a href="blah">
  717. * 2. Closing tag, e.g.:
  718. * </a>
  719. * 3. XHTML-style content-less tag, e.g.:
  720. * <img src="blah"/>
  721. */
  722. $tagtype = false;
  723. switch (substr($body, $pos, 1)){
  724. case '/':
  725. $tagtype = 2;
  726. $pos++;
  727. break;
  728. case '!':
  729. /**
  730. * A comment or an SGML declaration.
  731. */
  732. if (substr($body, $pos+1, 2) == "--"){
  733. $gt = strpos($body, "-->", $pos);
  734. if ($gt === false){
  735. $gt = strlen($body);
  736. } else {
  737. $gt += 2;
  738. }
  739. return Array(false, false, false, $lt, $gt);
  740. } else {
  741. $gt = sq_findnxstr($body, $pos, ">");
  742. return Array(false, false, false, $lt, $gt);
  743. }
  744. break;
  745. default:
  746. /**
  747. * Assume tagtype 1 for now. If it's type 3, we'll switch values
  748. * later.
  749. */
  750. $tagtype = 1;
  751. break;
  752. }
  753. $tag_start = $pos;
  754. $tagname = '';
  755. /**
  756. * Look for next [\W-_], which will indicate the end of the tag name.
  757. */
  758. $regary = sq_findnxreg($body, $pos, "[^\w\-_]");
  759. if ($regary == false){
  760. return Array(false, false, false, $lt, strlen($body));
  761. }
  762. list($pos, $tagname, $match) = $regary;
  763. $tagname = strtolower($tagname);
  764. /**
  765. * $match can be either of these:
  766. * '>' indicating the end of the tag entirely.
  767. * '\s' indicating the end of the tag name.
  768. * '/' indicating that this is type-3 xhtml tag.
  769. *
  770. * Whatever else we find there indicates an invalid tag.
  771. */
  772. switch ($match){
  773. case '/':
  774. /**
  775. * This is an xhtml-style tag with a closing / at the
  776. * end, like so: <img src="blah"/>. Check if it's followed
  777. * by the closing bracket. If not, then this tag is invalid
  778. */
  779. if (substr($body, $pos, 2) == "/>"){
  780. $pos++;
  781. $tagtype = 3;
  782. } else {
  783. $gt = sq_findnxstr($body, $pos, ">");
  784. $retary = Array(false, false, false, $lt, $gt);
  785. return $retary;
  786. }
  787. case '>':
  788. return Array($tagname, false, $tagtype, $lt, $pos);
  789. break;
  790. default:
  791. /**
  792. * Check if it's whitespace
  793. */
  794. if (!preg_match('/\s/', $match)){
  795. /**
  796. * This is an invalid tag! Look for the next closing ">".
  797. */
  798. $gt = sq_findnxstr($body, $offset, ">");
  799. return Array(false, false, false, $lt, $gt);
  800. }
  801. break;
  802. }
  803. /**
  804. * At this point we're here:
  805. * <tagname attribute='blah'>
  806. * \-------^
  807. *
  808. * At this point we loop in order to find all attributes.
  809. */
  810. $attname = '';
  811. $atttype = false;
  812. $attary = Array();
  813. while ($pos <= strlen($body)){
  814. $pos = sq_skipspace($body, $pos);
  815. if ($pos == strlen($body)){
  816. /**
  817. * Non-closed tag.
  818. */
  819. return Array(false, false, false, $lt, $pos);
  820. }
  821. /**
  822. * See if we arrived at a ">" or "/>", which means that we reached
  823. * the end of the tag.
  824. */
  825. $matches = Array();
  826. if (preg_match("%^(\s*)(>|/>)%s", substr($body, $pos), $matches)) {
  827. /**
  828. * Yep. So we did.
  829. */
  830. $pos += strlen($matches{1});
  831. if ($matches{2} == "/>"){
  832. $tagtype = 3;
  833. $pos++;
  834. }
  835. return Array($tagname, $attary, $tagtype, $lt, $pos);
  836. }
  837. /**
  838. * There are several types of attributes, with optional
  839. * [:space:] between members.
  840. * Type 1:
  841. * attrname[:space:]=[:space:]'CDATA'
  842. * Type 2:
  843. * attrname[:space:]=[:space:]"CDATA"
  844. * Type 3:
  845. * attr[:space:]=[:space:]CDATA
  846. * Type 4:
  847. * attrname
  848. *
  849. * We leave types 1 and 2 the same, type 3 we check for
  850. * '"' and convert to "&quot" if needed, then wrap in
  851. * double quotes. Type 4 we convert into:
  852. * attrname="yes".
  853. */
  854. $regary = sq_findnxreg($body, $pos, "[^\w\-_]");
  855. if ($regary == false){
  856. /**
  857. * Looks like body ended before the end of tag.
  858. */
  859. return Array(false, false, false, $lt, strlen($body));
  860. }
  861. list($pos, $attname, $match) = $regary;
  862. $attname = strtolower($attname);
  863. /**
  864. * We arrived at the end of attribute name. Several things possible
  865. * here:
  866. * '>' means the end of the tag and this is attribute type 4
  867. * '/' if followed by '>' means the same thing as above
  868. * '\s' means a lot of things -- look what it's followed by.
  869. * anything else means the attribute is invalid.
  870. */
  871. switch($match){
  872. case '/':
  873. /**
  874. * This is an xhtml-style tag with a closing / at the
  875. * end, like so: <img src="blah"/>. Check if it's followed
  876. * by the closing bracket. If not, then this tag is invalid
  877. */
  878. if (substr($body, $pos, 2) == "/>"){
  879. $pos++;
  880. $tagtype = 3;
  881. } else {
  882. $gt = sq_findnxstr($body, $pos, ">");
  883. $retary = Array(false, false, false, $lt, $gt);
  884. return $retary;
  885. }
  886. case '>':
  887. $attary{$attname} = '"yes"';
  888. return Array($tagname, $attary, $tagtype, $lt, $pos);
  889. break;
  890. default:
  891. /**
  892. * Skip whitespace and see what we arrive at.
  893. */
  894. $pos = sq_skipspace($body, $pos);
  895. $char = substr($body, $pos, 1);
  896. /**
  897. * Two things are valid here:
  898. * '=' means this is attribute type 1 2 or 3.
  899. * \w means this was attribute type 4.
  900. * anything else we ignore and re-loop. End of tag and
  901. * invalid stuff will be caught by our checks at the beginning
  902. * of the loop.
  903. */
  904. if ($char == "="){
  905. $pos++;
  906. $pos = sq_skipspace($body, $pos);
  907. /**
  908. * Here are 3 possibilities:
  909. * "'" attribute type 1
  910. * '"' attribute type 2
  911. * everything else is the content of tag type 3
  912. */
  913. $quot = substr($body, $pos, 1);
  914. if ($quot == "'"){
  915. $regary = sq_findnxreg($body, $pos+1, "\'");
  916. if ($regary == false){
  917. return Array(false, false, false, $lt, strlen($body));
  918. }
  919. list($pos, $attval, $match) = $regary;
  920. $pos++;
  921. $attary{$attname} = "'" . $attval . "'";
  922. } else if ($quot == '"'){
  923. $regary = sq_findnxreg($body, $pos+1, '\"');
  924. if ($regary == false){
  925. return Array(false, false, false, $lt, strlen($body));
  926. }
  927. list($pos, $attval, $match) = $regary;
  928. $pos++;
  929. $attary{$attname} = '"' . $attval . '"';
  930. } else {
  931. /**
  932. * These are hateful. Look for \s, or >.
  933. */
  934. $regary = sq_findnxreg($body, $pos, "[\s>]");
  935. if ($regary == false){
  936. return Array(false, false, false, $lt, strlen($body));
  937. }
  938. list($pos, $attval, $match) = $regary;
  939. /**
  940. * If it's ">" it will be caught at the top.
  941. */
  942. $attval = preg_replace("/\"/s", "&quot;", $attval);
  943. $attary{$attname} = '"' . $attval . '"';
  944. }
  945. } else if (preg_match("|[\w/>]|", $char)) {
  946. /**
  947. * That was attribute type 4.
  948. */
  949. $attary{$attname} = '"yes"';
  950. } else {
  951. /**
  952. * An illegal character. Find next '>' and return.
  953. */
  954. $gt = sq_findnxstr($body, $pos, ">");
  955. return Array(false, false, false, $lt, $gt);
  956. }
  957. break;
  958. }
  959. }
  960. /**
  961. * The fact that we got here indicates that the tag end was never
  962. * found. Return invalid tag indication so it gets stripped.
  963. */
  964. return Array(false, false, false, $lt, strlen($body));
  965. }
  966. /**
  967. * This function checks attribute values for entity-encoded values
  968. * and returns them translated into 8-bit strings so we can run
  969. * checks on them.
  970. *
  971. * @param $attvalue A string to run entity check against.
  972. * @return Translated value.
  973. */
  974. function sq_deent($attvalue){
  975. $me = 'sq_deent';
  976. /**
  977. * See if we have to run the checks first. All entities must start
  978. * with "&".
  979. */
  980. if (strpos($attvalue, "&") === false){
  981. return $attvalue;
  982. }
  983. /**
  984. * Check named entities first.
  985. */
  986. $trans = get_html_translation_table(HTML_ENTITIES);
  987. /**
  988. * Leave &quot; in, as it can mess us up.
  989. */
  990. $trans = array_flip($trans);
  991. unset($trans{"&quot;"});
  992. while (list($ent, $val) = each($trans)){
  993. $attvalue = preg_replace("/$ent*(\W)/si", "$val\\1", $attvalue);
  994. }
  995. /**
  996. * Now translate numbered entities from 1 to 255 if needed.
  997. */
  998. if (strpos($attvalue, "#") !== false){
  999. $omit = Array(34, 39);
  1000. for ($asc=1; $asc<256; $asc++){
  1001. if (!in_array($asc, $omit)){
  1002. $chr = chr($asc);
  1003. $attvalue = preg_replace("/\&#0*$asc;*(\D)/si", "$chr\\1",
  1004. $attvalue);
  1005. $attvalue = preg_replace("/\&#x0*".dechex($asc).";*(\W)/si",
  1006. "$chr\\1", $attvalue);
  1007. }
  1008. }
  1009. }
  1010. return $attvalue;
  1011. }
  1012. /**
  1013. * This function runs various checks against the attributes.
  1014. *
  1015. * @param $tagname String with the name of the tag.
  1016. * @param $attary Array with all tag attributes.
  1017. * @param $rm_attnames See description for sq_sanitize
  1018. * @param $bad_attvals See description for sq_sanitize
  1019. * @param $add_attr_to_tag See description for sq_sanitize
  1020. * @param $message message object
  1021. * @param $id message id
  1022. * @return Array with modified attributes.
  1023. */
  1024. function sq_fixatts($tagname,
  1025. $attary,
  1026. $rm_attnames,
  1027. $bad_attvals,
  1028. $add_attr_to_tag,
  1029. $message,
  1030. $id,
  1031. $mailbox
  1032. ){
  1033. $me = 'sq_fixatts';
  1034. while (list($attname, $attvalue) = each($attary)){
  1035. /**
  1036. * See if this attribute should be removed.
  1037. */
  1038. foreach ($rm_attnames as $matchtag=>$matchattrs){
  1039. if (preg_match($matchtag, $tagname)){
  1040. foreach ($matchattrs as $matchattr){
  1041. if (preg_match($matchattr, $attname)){
  1042. unset($attary{$attname});
  1043. continue;
  1044. }
  1045. }
  1046. }
  1047. }
  1048. /**
  1049. * Remove any entities.
  1050. */
  1051. $attvalue = sq_deent($attvalue);
  1052. /**
  1053. * Now let's run checks on the attvalues.
  1054. * I don't expect anyone to comprehend this. If you do,
  1055. * get in touch with me so I can drive to where you live and
  1056. * shake your hand personally. :)
  1057. */
  1058. foreach ($bad_attvals as $matchtag=>$matchattrs){
  1059. if (preg_match($matchtag, $tagname)){
  1060. foreach ($matchattrs as $matchattr=>$valary){
  1061. if (preg_match($matchattr, $attname)){
  1062. /**
  1063. * There are two arrays in valary.
  1064. * First is matches.
  1065. * Second one is replacements
  1066. */
  1067. list($valmatch, $valrepl) = $valary;
  1068. $newvalue =
  1069. preg_replace($valmatch, $valrepl, $attvalue);
  1070. if ($newvalue != $attvalue){
  1071. $attary{$attname} = $newvalue;
  1072. }
  1073. }
  1074. }
  1075. }
  1076. }
  1077. /**
  1078. * Turn cid: urls into http-friendly ones.
  1079. */
  1080. if (preg_match("/^[\'\"]\s*cid:/si", $attvalue)){
  1081. $attary{$attname} = sq_cid2http($message, $id, $attvalue, $mailbox);
  1082. }
  1083. }
  1084. /**
  1085. * See if we need to append any attributes to this tag.
  1086. */
  1087. foreach ($add_attr_to_tag as $matchtag=>$addattary){
  1088. if (preg_match($matchtag, $tagname)){
  1089. $attary = array_merge($attary, $addattary);
  1090. }
  1091. }
  1092. return $attary;
  1093. }
  1094. /**
  1095. * This function edits the style definition to make them friendly and
  1096. * usable in squirrelmail.
  1097. *
  1098. * @param $message the message object
  1099. * @param $id the message id
  1100. * @param $content a string with whatever is between <style> and </style>
  1101. * @return a string with edited content.
  1102. */
  1103. function sq_fixstyle($message, $id, $content){
  1104. global $view_unsafe_images;
  1105. $me = 'sq_fixstyle';
  1106. /**
  1107. * First look for general BODY style declaration, which would be
  1108. * like so:
  1109. * body {background: blah-blah}
  1110. * and change it to .bodyclass so we can just assign it to a <div>
  1111. */
  1112. $content = preg_replace("|body(\s*\{.*?\})|si", ".bodyclass\\1", $content);
  1113. $secremoveimg = '../images/' . _("sec_remove_eng.png");
  1114. /**
  1115. * Fix url('blah') declarations.
  1116. */
  1117. $content = preg_replace("|url\(([\'\"])\s*\S+script\s*:.*?([\'\"])\)|si",
  1118. "url(\\1$secremoveimg\\2)", $content);
  1119. /**
  1120. * Fix url('https*://.*) declarations but only if $view_unsafe_images
  1121. * is false.
  1122. */
  1123. if (!$view_unsafe_images){
  1124. $content = preg_replace("|url\(([\'\"])\s*https*:.*?([\'\"])\)|si",
  1125. "url(\\1$secremoveimg\\2)", $content);
  1126. }
  1127. /**
  1128. * Fix urls that refer to cid:
  1129. */
  1130. while (preg_match("|url\(([\'\"]\s*cid:.*?[\'\"])\)|si", $content,
  1131. $matches)){
  1132. $cidurl = $matches{1};
  1133. $httpurl = sq_cid2http($message, $id, $cidurl);
  1134. $content = preg_replace("|url\($cidurl\)|si",
  1135. "url($httpurl)", $content);
  1136. }
  1137. /**
  1138. * Fix stupid css declarations which lead to vulnerabilities
  1139. * in IE.
  1140. */
  1141. $match = Array('/expression/si',
  1142. '/behaviou*r/si',
  1143. '/binding/si');
  1144. $replace = Array('idiocy', 'idiocy', 'idiocy');
  1145. $content = preg_replace($match, $replace, $content);
  1146. return $content;
  1147. }
  1148. /**
  1149. * This function converts cid: url's into the ones that can be viewed in
  1150. * the browser.
  1151. *
  1152. * @param $message the message object
  1153. * @param $id the message id
  1154. * @param $cidurl the cid: url.
  1155. * @return a string with a http-friendly url
  1156. */
  1157. function sq_cid2http($message, $id, $cidurl, $mailbox){
  1158. /**
  1159. * Get rid of quotes.
  1160. */
  1161. $quotchar = substr($cidurl, 0, 1);
  1162. $cidurl = str_replace($quotchar, "", $cidurl);
  1163. $cidurl = substr(trim($cidurl), 4);
  1164. $linkurl = find_ent_id($cidurl, $message);
  1165. /* in case of non-save cid links $httpurl should be replaced by a sort of
  1166. unsave link image */
  1167. $httpurl = '';
  1168. if ($linkurl) {
  1169. $httpurl = $quotchar . '../src/download.php?absolute_dl=true&amp;' .
  1170. "passed_id=$id&amp;mailbox=" . urlencode($mailbox) .
  1171. '&amp;ent_id=' . $linkurl . $quotchar;
  1172. }
  1173. return $httpurl;
  1174. }
  1175. /**
  1176. * This function changes the <body> tag into a <div> tag since we
  1177. * can't really have a body-within-body.
  1178. *
  1179. * @param $attary an array of attributes and values of <body>
  1180. * @return a modified array of attributes to be set for <div>
  1181. */
  1182. function sq_body2div($attary){
  1183. $me = 'sq_body2div';
  1184. $divattary = Array('class' => "'bodyclass'");
  1185. $bgcolor = '#ffffff';
  1186. $text = '#000000';
  1187. $styledef = '';
  1188. if (is_array($attary) && sizeof($attary) > 0){
  1189. foreach ($attary as $attname=>$attvalue){
  1190. $quotchar = substr($attvalue, 0, 1);
  1191. $attvalue = str_replace($quotchar, "", $attvalue);
  1192. switch ($attname){
  1193. case 'background':
  1194. $styledef .= "background-image: url('$attvalue'); ";
  1195. break;
  1196. case 'bgcolor':
  1197. $styledef .= "background-color: $attvalue; ";
  1198. break;
  1199. case 'text':
  1200. $styledef .= "color: $attvalue; ";
  1201. break;
  1202. }
  1203. }
  1204. if (strlen($styledef) > 0){
  1205. $divattary{"style"} = "\"$styledef\"";
  1206. }
  1207. }
  1208. return $divattary;
  1209. }
  1210. /**
  1211. * This is the main function and the one you should actually be calling.
  1212. * There are several variables you should be aware of an which need
  1213. * special description.
  1214. *
  1215. * Since the description is quite lengthy, see it here:
  1216. * http://www.mricon.com/html/phpfilter.html
  1217. *
  1218. * @param $body the string with HTML you wish to filter
  1219. * @param $tag_list see description above
  1220. * @param $rm_tags_with_content see description above
  1221. * @param $self_closing_tags see description above
  1222. * @param $force_tag_closing see description above
  1223. * @param $rm_attnames see description above
  1224. * @param $bad_attvals see description above
  1225. * @param $add_attr_to_tag see description above
  1226. * @param $message message object
  1227. * @param $id message id
  1228. * @return sanitized html safe to show on your pages.
  1229. */
  1230. function sq_sanitize($body,
  1231. $tag_list,
  1232. $rm_tags_with_content,
  1233. $self_closing_tags,
  1234. $force_tag_closing,
  1235. $rm_attnames,
  1236. $bad_attvals,
  1237. $add_attr_to_tag,
  1238. $message,
  1239. $id,
  1240. $mailbox
  1241. ){
  1242. $me = 'sq_sanitize';
  1243. /**
  1244. * Normalize rm_tags and rm_tags_with_content.
  1245. */
  1246. @array_walk($rm_tags, 'sq_casenormalize');
  1247. @array_walk($rm_tags_with_content, 'sq_casenormalize');
  1248. @array_walk($self_closing_tags, 'sq_casenormalize');
  1249. /**
  1250. * See if tag_list is of tags to remove or tags to allow.
  1251. * false means remove these tags
  1252. * true means allow these tags
  1253. */
  1254. $rm_tags = array_shift($tag_list);
  1255. $curpos = 0;
  1256. $open_tags = Array();
  1257. $trusted = "<!-- begin sanitized html -->\n";
  1258. $skip_content = false;
  1259. /**
  1260. * Take care of netscape's stupid javascript entities like
  1261. * &{alert('boo')};
  1262. */
  1263. $body = preg_replace("/&(\{.*?\};)/si", "&amp;\\1", $body);
  1264. while (($curtag=sq_getnxtag($body, $curpos)) != FALSE){
  1265. list($tagname, $attary, $tagtype, $lt, $gt) = $curtag;
  1266. $free_content = substr($body, $curpos, $lt-$curpos);
  1267. /**
  1268. * Take care of <style>
  1269. */
  1270. if ($tagname == "style" && $tagtype == 2){
  1271. /**
  1272. * This is a closing </style>. Edit the
  1273. * content before we apply it.
  1274. */
  1275. $free_content = sq_fixstyle($message, $id, $free_content);
  1276. }
  1277. if ($skip_content == false){
  1278. $trusted .= $free_content;
  1279. }
  1280. if ($tagname != FALSE){
  1281. if ($tagtype == 2){
  1282. if ($skip_content == $tagname){
  1283. /**
  1284. * Got to the end of tag we needed to remove.
  1285. */
  1286. $tagname = false;
  1287. $skip_content = false;
  1288. } else {
  1289. if ($skip_content == false){
  1290. if ($tagname == "body"){
  1291. $tagname = "div";
  1292. } else {
  1293. if (isset($open_tags{$tagname}) &&
  1294. $open_tags{$tagname} > 0){
  1295. $open_tags{$tagname}--;
  1296. } else {
  1297. $tagname = false;
  1298. }
  1299. }
  1300. }
  1301. }
  1302. } else {
  1303. /**
  1304. * $rm_tags_with_content
  1305. */
  1306. if ($skip_content == false){
  1307. /**
  1308. * See if this is a self-closing type and change
  1309. * tagtype appropriately.
  1310. */
  1311. if ($tagtype == 1
  1312. && in_array($tagname, $self_closing_tags)){
  1313. $tagtype=3;
  1314. }
  1315. /**
  1316. * See if we should skip this tag and any content
  1317. * inside it.
  1318. */
  1319. if ($tagtype == 1 &&
  1320. in_array($tagname, $rm_tags_with_content)){
  1321. $skip_content = $tagname;
  1322. } else {
  1323. if (($rm_tags == false
  1324. && in_array($tagname, $tag_list)) ||
  1325. ($rm_tags == true &&
  1326. !in_array($tagname, $tag_list))){
  1327. $tagname = false;
  1328. } else {
  1329. if ($tagtype == 1){
  1330. if (isset($open_tags{$tagname})){
  1331. $open_tags{$tagname}++;
  1332. } else {
  1333. $open_tags{$tagname}=1;
  1334. }
  1335. }
  1336. /**
  1337. * This is where we run other checks.
  1338. */
  1339. if (is_array($attary) && sizeof($attary) > 0){
  1340. $attary = sq_fixatts($tagname,
  1341. $attary,
  1342. $rm_attnames,
  1343. $bad_attvals,
  1344. $add_attr_to_tag,
  1345. $message,
  1346. $id,
  1347. $mailbox
  1348. );
  1349. }
  1350. /**
  1351. * Convert body into div.
  1352. */
  1353. if ($tagname == "body"){
  1354. $tagname = "div";
  1355. $attary = sq_body2div($attary, $message, $id);
  1356. }
  1357. }
  1358. }
  1359. }
  1360. }
  1361. if ($tagname != false && $skip_content == false){
  1362. $trusted .= sq_tagprint($tagname, $attary, $tagtype);
  1363. }
  1364. }
  1365. $curpos = $gt+1;
  1366. }
  1367. $trusted .= substr($body, $curpos, strlen($body)-$curpos);
  1368. if ($force_tag_closing == true){
  1369. foreach ($open_tags as $tagname=>$opentimes){
  1370. while ($opentimes > 0){
  1371. $trusted .= '</' . $tagname . '>';
  1372. $opentimes--;
  1373. }
  1374. }
  1375. $trusted .= "\n";
  1376. }
  1377. $trusted .= "<!-- end sanitized html -->\n";
  1378. return $trusted;
  1379. }
  1380. /**
  1381. * This is a wrapper function to call html sanitizing routines.
  1382. *
  1383. * @param $body the body of the message
  1384. * @param $id the id of the message
  1385. * @return a string with html safe to display in the browser.
  1386. */
  1387. function magicHTML($body, $id, $message, $mailbox = 'INBOX'){
  1388. global $attachment_common_show_images, $view_unsafe_images,
  1389. $has_unsafe_images;
  1390. /**
  1391. * Don't display attached images in HTML mode.
  1392. */
  1393. $attachment_common_show_images = false;
  1394. $tag_list = Array(
  1395. false,
  1396. "object",
  1397. "meta",
  1398. "html",
  1399. "head",
  1400. "base",
  1401. "link",
  1402. "frame",
  1403. "iframe"
  1404. );
  1405. $rm_tags_with_content = Array(
  1406. "script",
  1407. "applet",
  1408. "embed",
  1409. "title"
  1410. );
  1411. $self_closing_tags = Array(
  1412. "img",
  1413. "br",
  1414. "hr",
  1415. "input"
  1416. );
  1417. $force_tag_closing = false;
  1418. $rm_attnames = Array(
  1419. "/.*/" =>
  1420. Array(
  1421. "/target/si",
  1422. "/^on.*/si",
  1423. "/^dynsrc/si",
  1424. "/^data.*/si"
  1425. )
  1426. );
  1427. $secremoveimg = "../images/" . _("sec_remove_eng.png");
  1428. $bad_attvals = Array(
  1429. "/.*/" =>
  1430. Array(
  1431. "/^src|background/i" =>
  1432. Array(
  1433. Array(
  1434. "|^([\'\"])\s*\.\./.*([\'\"])|si",
  1435. "/^([\'\"])\s*\S+script\s*:.*([\'\"])/si",
  1436. "/^([\'\"])\s*mocha\s*:*.*([\'\"])/si",
  1437. "/^([\'\"])\s*about\s*:.*([\'\"])/si"
  1438. ),
  1439. Array(
  1440. "\\1$secremoveimg\\2",
  1441. "\\1$secremoveimg\\2",
  1442. "\\1$secremoveimg\\2",
  1443. "\\1$secremoveimg\\2"
  1444. )
  1445. ),
  1446. "/^href|action/i" =>
  1447. Array(
  1448. Array(
  1449. "|^([\'\"])\s*\.\./.*([\'\"])|si",
  1450. "/^([\'\"])\s*\S+script\s*:.*([\'\"])/si",
  1451. "/^([\'\"])\s*mocha\s*:*.*([\'\"])/si",
  1452. "/^([\'\"])\s*about\s*:.*([\'\"])/si"
  1453. ),
  1454. Array(
  1455. "\\1#\\2",
  1456. "\\1#\\2",
  1457. "\\1#\\2",
  1458. "\\1#\\2"
  1459. )
  1460. ),
  1461. "/^style/si" =>
  1462. Array(
  1463. Array(
  1464. "/expression/si",
  1465. "/binding/si",
  1466. "/behaviou*r/si",
  1467. "|url\(([\'\"])\s*\.\./.*([\'\"])\)|si",
  1468. "/url\(([\'\"])\s*\S+script\s*:.*([\'\"])\)/si",
  1469. "/url\(([\'\"])\s*mocha\s*:.*([\'\"])\)/si",
  1470. "/url\(([\'\"])\s*about\s*:.*([\'\"])\)/si"
  1471. ),
  1472. Array(
  1473. "idiocy",
  1474. "idiocy",
  1475. "idiocy",
  1476. "url(\\1#\\2)",
  1477. "url(\\1#\\2)",
  1478. "url(\\1#\\2)",
  1479. "url(\\1#\\2)"
  1480. )
  1481. )
  1482. )
  1483. );
  1484. if (!$view_unsafe_images){
  1485. /**
  1486. * Remove any references to http/https if view_unsafe_images set
  1487. * to false.
  1488. */
  1489. array_push($bad_attvals{'/.*/'}{'/^src|background/i'}[0],
  1490. '/^([\'\"])\s*https*:.*([\'\"])/si');
  1491. array_push($bad_attvals{'/.*/'}{'/^src|background/i'}[1],
  1492. "\\1$secremoveimg\\2");
  1493. array_push($bad_attvals{'/.*/'}{'/^style/si'}[0],
  1494. '/url\(([\'\"])\s*https*:.*([\'\"])\)/si');
  1495. array_push($bad_attvals{'/.*/'}{'/^style/si'}[1],
  1496. "url(\\1$secremoveimg\\2)");
  1497. }
  1498. $add_attr_to_tag = Array(
  1499. "/^a$/si" => Array('target'=>'"_new"')
  1500. );
  1501. $trusted = sq_sanitize($body,
  1502. $tag_list,
  1503. $rm_tags_with_content,
  1504. $self_closing_tags,
  1505. $force_tag_closing,
  1506. $rm_attnames,
  1507. $bad_attvals,
  1508. $add_attr_to_tag,
  1509. $message,
  1510. $id,
  1511. $mailbox
  1512. );
  1513. if (preg_match("|$secremoveimg|si", $trusted)){
  1514. $has_unsafe_images = true;
  1515. }
  1516. return $trusted;
  1517. }
  1518. ?>