1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
18: class IMP_Mime_Viewer_Html extends Horde_Mime_Viewer_Html
19: {
20: const CSS_BG_PREG = '/(background(?:-image)?:[^;\}]*(?:url\(["\']?))(.*?)((?:["\']?\)))/i';
21:
22: 23: 24: 25: 26:
27: protected $_imptmp = array();
28:
29: 30: 31: 32: 33:
34: protected $_capability = array(
35: 'full' => true,
36: 'info' => true,
37: 'inline' => true,
38: 'raw' => false
39: );
40:
41: 42: 43: 44: 45:
46: protected function _render()
47: {
48: return array(
49: $this->_mimepart->getMimeId() => $this->_IMPrender(false)
50: );
51: }
52:
53: 54: 55: 56: 57:
58: protected function _renderInline()
59: {
60: $data = $this->_IMPrender(true);
61:
62: if (!is_null($this->_imptmp)) {
63: $uid = strval(new Horde_Support_Randomid());
64:
65: Horde::addScriptFile('imp.js', 'imp');
66:
67: $data['js'] = array('IMP_JS.iframeInject("' . $uid . '", ' . Horde_Serialize::serialize($data['data'], Horde_Serialize::JSON, $this->_mimepart->getCharset()) . ')');
68: $data['data'] = '<div>' . _("Loading...") . '</div><iframe class="htmlMsgData" id="' . $uid . '" src="javascript:false" frameborder="0" style="display:none"></iframe>';
69: $data['type'] = 'text/html; charset=UTF-8';
70: }
71:
72: return array(
73: $this->_mimepart->getMimeId() => $data
74: );
75: }
76:
77: 78: 79: 80: 81:
82: protected function _renderInfo()
83: {
84: if ($this->canRender('inline') ||
85: ($this->_mimepart->getDisposition() == 'attachment')) {
86: return array();
87: }
88:
89: $status = new IMP_Mime_Status(array(
90: _("This message part contains HTML data, but inline HTML display is disabled."),
91: $this->getConfigParam('imp_contents')->linkViewJS($this->_mimepart, 'view_attach', _("View HTML data in new window.")),
92: $this->getConfigParam('imp_contents')->linkViewJS($this->_mimepart, 'view_attach', _("Convert HTML data to plain text and view in new window."), array('params' => array('convert_text' => 1)))
93: ));
94: $status->icon('mime/html.png');
95:
96: return array(
97: $this->_mimepart->getMimeId() => array(
98: 'data' => '',
99: 'status' => $status,
100: 'type' => 'text/html; charset=' . $this->getConfigParam('charset')
101: )
102: );
103: }
104:
105: 106: 107: 108: 109: 110: 111:
112: protected function _IMPrender($inline)
113: {
114: $data = $this->_mimepart->getContents();
115:
116: 117:
118: if ((IMP::getViewMode() == 'mimp') ||
119: (!$inline && Horde_Util::getFormData('convert_text'))) {
120: $this->_imptmp = null;
121: } else {
122: if ($inline) {
123: $imgview = new IMP_Ui_Imageview();
124: $blockimg = !$imgview->showInlineImage($this->getConfigParam('imp_contents'));
125: } else {
126: $blockimg = false;
127: }
128:
129: $this->_imptmp = array(
130: 'blockimg' => null,
131: 'cid' => null,
132: 'cid_used' => array(),
133: 'cssblock' => false,
134: 'img' => $blockimg,
135: 'imgblock' => false,
136: 'inline' => $inline,
137: 'style' => array(),
138: 'target' => strval(new Horde_Support_Randomid())
139: );
140:
141:
142: if ($this->_imptmp['img']) {
143: $this->_imptmp['blockimg'] = Horde::url(Horde_Themes::img('spacer_red.png'), true, array('append_session' => -1));
144: }
145:
146: 147:
148: $this->_imptmp['cid'] = array();
149: if (($related_part = $this->getConfigParam('imp_contents')->findMimeType($this->_mimepart->getMimeId(), 'multipart/related')) &&
150: ($related_cids = $related_part->getMetadata('related_cids'))) {
151: $cid_replace = array();
152:
153: foreach ($related_cids as $mime_id => $cid) {
154: if ($cid = trim($cid, '<>')) {
155: $cid_replace['cid:' . $cid] = $mime_id;
156: }
157: }
158:
159: if (!empty($cid_replace)) {
160: $this->_imptmp['cid'] = $cid_replace;
161: }
162: }
163: }
164:
165:
166: $data = $this->_cleanHTML($data, array(
167: 'noprefetch' => ($inline && (IMP::getViewMode() != 'mimp')),
168: 'phishing' => $inline
169: ));
170:
171: $status = array();
172: if ($this->_phishWarn) {
173: $status[] = new IMP_Mime_Status(array(
174: sprintf(_("%s: This message may not be from whom it claims to be. Beware of following any links in it or of providing the sender with any personal information."), _("Warning")),
175: _("The links that caused this warning have this background color:") . ' <span style="' . $this->_phishCss . '">' . _("EXAMPLE") . '</span>'
176: ));
177: }
178:
179: 180:
181: if (is_null($this->_imptmp)) {
182: $data = $this->_textFilter($data, 'Html2text', array(
183: 'wrap' => false
184: ));
185:
186:
187: return array(
188: 'data' => IMP::filterText($data),
189: 'type' => 'text/plain; charset=' . $this->getConfigParam('charset')
190: );
191: }
192:
193: if ($this->_imptmp['imgblock']) {
194: $tmp = new IMP_Mime_Status(array(
195: _("Images have been blocked in this message part."),
196: Horde::link('#', '', 'unblockImageLink') . _("Show Images?") . '</a>'
197: ));
198: $tmp->icon('mime/image.png');
199: $status[] = $tmp;
200: } elseif ($this->_imptmp['cssblock']) {
201: 202:
203: $tmp = new IMP_Mime_Status(array(
204: _("Message styling has been suppressed in this message part since the style data lives on a remote server."),
205: Horde::link('#', '', 'unblockImageLink') . _("Load Styling?") . '</a>'
206: ));
207: $tmp->icon('mime/image.png');
208: $status[] = $tmp;
209: }
210:
211: $filters = array();
212: if ($GLOBALS['prefs']->getValue('emoticons')) {
213: $filters['emoticons'] = array(
214: 'entities' => true
215: );
216: }
217:
218: if ($inline) {
219: $filters['emails'] = array(
220: 'callback' => array($this, 'emailsCallback')
221: );
222: }
223:
224: if (!empty($filters)) {
225: $data = $this->_textFilter($data, array_keys($filters), array_values($filters));
226: }
227:
228:
229: $data = IMP::filterText($data);
230:
231:
232: if (!empty($this->_imptmp['cid'])) {
233: $related_part->setMetadata('related_cids_used', $this->_imptmp['cid_used']);
234: }
235:
236: return array(
237: 'data' => $data,
238: 'status' => $status,
239: 'type' => $this->_mimepart->getType(true)
240: );
241: }
242:
243: 244: 245: 246: 247:
248: protected function _inAddressBook()
249: {
250: if (!$this->getConfigParam('imp_contents')) {
251: return false;
252: }
253:
254: $from = Horde_Mime_Address::bareAddress($this->getConfigParam('imp_contents')->getHeader()->getValue('from'));
255:
256: if ($GLOBALS['prefs']->getValue('html_image_addrbook') &&
257: $GLOBALS['registry']->hasMethod('contacts/getField')) {
258: $params = IMP::getAddressbookSearchParams();
259: try {
260: if ($GLOBALS['registry']->call('contacts/getField', array($from, '__key', $params['sources'], true, true))) {
261: return true;
262: }
263: } catch (Horde_Exception $e) {}
264: }
265:
266:
267: $safe_addrs = $this->getConfigParam('safe_addrs');
268: return (!empty($safe_addrs) && in_array($from, $safe_addrs));
269: }
270:
271: 272: 273: 274: 275: 276: 277: 278: 279:
280: public function emailsCallback($args, $extra)
281: {
282: return IMP::composeLink($args, $extra, true);
283: }
284:
285: 286:
287: protected function _node($doc, $node)
288: {
289: parent::_node($doc, $node);
290:
291: if ($doc == $node) {
292:
293: if ($this->_imptmp && !empty($this->_imptmp['style'])) {
294:
295: try {
296: $style = $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter(implode("\n", $this->_imptmp['style']), 'csstidy', array(
297: 'ob' => true,
298: 'preserve_css' => false
299: ));
300:
301: $blocked = array();
302: foreach ($style->import as $val) {
303: $blocked[] = '@import "' . $val . '";';
304: }
305: $style->import = array();
306:
307: foreach ($style->css as $key => $val) {
308: foreach ($val as $key2 => $val2) {
309: foreach ($val2 as $key3 => $val3) {
310: foreach ($val3['p'] as $key4 => $val4) {
311: if (preg_match('/^\s*url\(["\']?.*?["\']?\)/i', $val4)) {
312: $blocked[] = $key2 . '{' . $key3 . ':' . $val4 . ';}';
313: unset($style->css[$key][$key2][$key3]['p'][$key4]);
314: }
315: }
316: }
317: }
318: }
319:
320: $css_text = $style->print->plain();
321:
322: if ($css_text || !empty($blocked)) {
323:
324:
325: $head = $doc->getElementsByTagName('head');
326: if ($head->length) {
327: $headelt = $head->item(0);
328: } else {
329: $headelt = $doc->createElement('head');
330: $doc->appendChild($headelt);
331: }
332: }
333:
334: if ($css_text) {
335: $style_elt = $doc->createElement('style', $css_text);
336: $style_elt->setAttribute('type', 'text/css');
337: $headelt->appendChild($style_elt);
338: }
339:
340: 341: 342: 343:
344: if (!empty($blocked)) {
345: $block_elt = $doc->createElement('style', implode('', $blocked));
346: $block_elt->setAttribute('type', 'text/x-imp-cssblocked');
347: $headelt->appendChild($block_elt);
348: }
349: } catch (Horde_Exception $e) {}
350: }
351: }
352: }
353:
354: 355: 356: 357: 358: 359:
360: protected function _nodeCallback($doc, $node)
361: {
362: if (is_null($this->_imptmp)) {
363: return;
364: }
365:
366: if ($node instanceof DOMElement) {
367: $tag = Horde_String::lower($node->tagName);
368:
369: switch ($tag) {
370: case 'a':
371: case 'area':
372: 373:
374: if ($node->hasAttribute('href')) {
375: $url = parse_url($node->getAttribute('href'));
376: if (isset($url['scheme']) && ($url['scheme'] == 'mailto')) {
377: 378:
379: $node->setAttribute('href', IMP::composeLink($node->getAttribute('href'), array(), true));
380: $node->removeAttribute('target');
381: } elseif ($this->_imptmp['inline'] &&
382: isset($url['fragment']) &&
383: empty($url['path']) &&
384: $GLOBALS['browser']->isBrowser('mozilla')) {
385: 386:
387: $node->removeAttribute('href');
388: } elseif (!$node->hasAttribute('target') ||
389: Horde_String::lower($node->getAttribute('target')) == '_self') {
390: $node->setAttribute('target', $this->_imptmp['target']);
391: }
392: }
393: break;
394:
395: case 'img':
396: case 'input':
397: if ($this->_imptmp && $node->hasAttribute('src')) {
398: $val = $node->getAttribute('src');
399:
400:
401: if (($tag == 'img') &&
402: isset($this->_imptmp['cid'][$val])) {
403: $this->_imptmp['cid_used'][] = $this->_imptmp['cid'][$val];
404: $val = $this->getConfigParam('imp_contents')->urlView(null, 'view_attach', array('params' => array(
405: 'ctype' => 'image/*',
406: 'id' => $this->_imptmp['cid'][$val],
407: 'imp_img_view' => 'data'
408: )));
409: $node->setAttribute('src', $val);
410: }
411:
412:
413: if ($this->_imptmp['img']) {
414: $node->setAttribute('htmlimgblocked', $val);
415: $node->setAttribute('src', $this->_imptmp['blockimg']);
416: $this->_imptmp['imgblock'] = true;
417: }
418: }
419: break;
420:
421: case 'link':
422: 423: 424: 425:
426: $delete_link = true;
427:
428: switch (Horde_String::lower($node->getAttribute('type'))) {
429: case 'text/css':
430: if ($this->_imptmp && $node->hasAttribute('href')) {
431: $tmp = $node->getAttribute('href');
432:
433: if (isset($this->_imptmp['cid'][$tmp])) {
434: $this->_imptmp['style'][] = $this->getConfigParam('imp_contents')->getMIMEPart($this->_imptmp['cid'][$tmp])->getContents();
435: } else {
436: $node->setAttribute('htmlcssblocked', $node->getAttribute('href'));
437: $node->removeAttribute('href');
438: $this->_imptmp['cssblock'] = true;
439: $delete_link = false;
440: }
441: }
442: break;
443: }
444:
445: if ($delete_link &&
446: $node->hasAttribute('href') &&
447: $node->parentNode) {
448: $node->parentNode->removeChild($node);
449: }
450: break;
451:
452: case 'style':
453: switch (Horde_String::lower($node->getAttribute('type'))) {
454: case 'text/css':
455: if ($this->_imptmp) {
456: $this->_imptmp['style'][] = $node->nodeValue;
457: }
458: $node->parentNode->removeChild($node);
459: break;
460: }
461: break;
462:
463: case 'table':
464: 465: 466:
467: if ($this->_imptmp['inline'] &&
468: $node->hasAttribute('height') &&
469: ($node->getAttribute('height') == '100%')) {
470: $node->removeAttribute('height');
471: }
472:
473:
474:
475: case 'body':
476: case 'td':
477: if ($this->_imptmp &&
478: $node->hasAttribute('background')) {
479: $val = $node->getAttribute('background');
480:
481:
482: if (isset($this->_imptmp['cid'][$val])) {
483: $this->_imptmp['cid_used'][] = $this->_imptmp['cid'][$val];
484: $val = $this->getConfigParam('imp_contents')->urlView(null, 'view_attach', array('params' => array(
485: 'id' => $this->_imptmp['cid'][$val],
486: 'imp_img_view' => 'data'
487: )));
488: $node->setAttribute('background', $val);
489: }
490:
491:
492: if ($this->_imptmp['img']) {
493: $node->setAttribute('htmlimgblocked', $val);
494: $node->setAttribute('background', $this->_imptmp['blockimg']);
495: $this->_imptmp['imgblock'] = true;
496: }
497: }
498: break;
499: }
500:
501:
502: if (!is_null($this->_imptmp)) {
503: $remove = array();
504: foreach ($node->attributes as $val) {
505: 506:
507: if (stripos($val->value, 'mailto:') === 0) {
508: $remove[] = $val->name;
509: }
510: }
511:
512: foreach ($remove as $val) {
513: $node->removeAttribute($val);
514: }
515:
516: if ($node->hasAttribute('style')) {
517: if (strpos($node->getAttribute('style'), 'content:') !== false) {
518:
519: $node->removeAttribute('style');
520: } elseif ($this->_imptmp['img'] || $this->_imptmp['cid']) {
521: $this->_imptmp['node'] = $node;
522: $style = preg_replace_callback(self::CSS_BG_PREG, array($this, '_styleCallback'), $node->getAttribute('style'), -1, $matches);
523: if ($matches) {
524: $node->setAttribute('style', $style);
525: }
526: }
527: }
528: }
529: }
530: }
531:
532: 533: 534: 535: 536: 537: 538: 539:
540: protected function _styleCallback($matches)
541: {
542: if (isset($this->_imptmp['cid'][$matches[2]])) {
543: $replace = $this->getConfigParam('imp_contents')->urlView(null, 'view_attach', array('params' => array(
544: 'id' => $this->_imptmp['cid'][$matches[2]],
545: 'imp_img_view' => 'data'
546: )));
547: $this->_imptmp['cid_used'][] = $this->_imptmp['cid'][$matches[2]];
548: } else {
549: $this->_imptmp['node']->setAttribute('htmlimgblocked', $matches[2]);
550: $this->_imptmp['imgblock'] = true;
551: $replace = $this->_imptmp['blockimg'];
552: }
553: return $matches[1] . $replace . $matches[3];
554: }
555:
556: }
557: