mime.php 54 KB

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