mime.php 57 KB

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