Overview

Packages

  • IMP

Classes

  • IMP
  • IMP_Ajax_Addresses
  • IMP_Ajax_Application
  • IMP_Ajax_Application_Compose
  • IMP_Ajax_Application_Handler_Common
  • IMP_Ajax_Application_Handler_ComposeAttach
  • IMP_Ajax_Application_Handler_Draft
  • IMP_Ajax_Application_Handler_Dynamic
  • IMP_Ajax_Application_Handler_ImageUnblock
  • IMP_Ajax_Application_Handler_Mboxtoggle
  • IMP_Ajax_Application_Handler_Passphrase
  • IMP_Ajax_Application_Handler_Remote
  • IMP_Ajax_Application_Handler_RemotePrefs
  • IMP_Ajax_Application_Handler_Search
  • IMP_Ajax_Application_Handler_Smartmobile
  • IMP_Ajax_Application_ListMessages
  • IMP_Ajax_Application_ShowMessage
  • IMP_Ajax_Application_Viewport
  • IMP_Ajax_Application_Viewport_Error
  • IMP_Ajax_Imple_ImportEncryptKey
  • IMP_Ajax_Imple_ItipRequest
  • IMP_Ajax_Imple_PassphraseDialog
  • IMP_Ajax_Imple_VcardImport
  • IMP_Ajax_Queue
  • IMP_Api
  • IMP_Application
  • IMP_Auth
  • IMP_Basic_Base
  • IMP_Basic_Compose
  • IMP_Basic_Contacts
  • IMP_Basic_Error
  • IMP_Basic_Folders
  • IMP_Basic_Listinfo
  • IMP_Basic_Mailbox
  • IMP_Basic_Message
  • IMP_Basic_Pgp
  • IMP_Basic_Saveimage
  • IMP_Basic_Search
  • IMP_Basic_Searchbasic
  • IMP_Basic_Smime
  • IMP_Basic_Thread
  • IMP_Block_Newmail
  • IMP_Block_Summary
  • IMP_Compose
  • IMP_Compose_Attachment
  • IMP_Compose_Attachment_Linked_Metadata
  • IMP_Compose_Attachment_Metadata
  • IMP_Compose_Attachment_Storage
  • IMP_Compose_Attachment_Storage_AutoDetermine
  • IMP_Compose_Attachment_Storage_Temp
  • IMP_Compose_Attachment_Storage_VfsLinked
  • IMP_Compose_Exception
  • IMP_Compose_Exception_Address
  • IMP_Compose_HtmlSignature
  • IMP_Compose_Link
  • IMP_Compose_LinkedAttachment
  • IMP_Compose_Ui
  • IMP_Compose_View
  • IMP_Contacts
  • IMP_Contacts_Avatar_Addressbook
  • IMP_Contacts_Avatar_Gravatar
  • IMP_Contacts_Avatar_Unknown
  • IMP_Contacts_Flag_Host
  • IMP_Contacts_Image
  • IMP_Contents
  • IMP_Contents_InlineOutput
  • IMP_Contents_View
  • IMP_Crypt_Pgp
  • IMP_Crypt_Smime
  • IMP_Dynamic_AddressList
  • IMP_Dynamic_Base
  • IMP_Dynamic_Compose
  • IMP_Dynamic_Compose_Common
  • IMP_Dynamic_Helper_Base
  • IMP_Dynamic_Mailbox
  • IMP_Dynamic_Message
  • IMP_Exception
  • IMP_Factory_AuthImap
  • IMP_Factory_Compose
  • IMP_Factory_ComposeAtc
  • IMP_Factory_Contacts
  • IMP_Factory_Contents
  • IMP_Factory_Flags
  • IMP_Factory_Ftree
  • IMP_Factory_Identity
  • IMP_Factory_Imap
  • IMP_Factory_Mail
  • IMP_Factory_MailAutoconfig
  • IMP_Factory_Mailbox
  • IMP_Factory_MailboxCache
  • IMP_Factory_MailboxList
  • IMP_Factory_Maillog
  • IMP_Factory_MimeViewer
  • IMP_Factory_Pgp
  • IMP_Factory_PrefsSort
  • IMP_Factory_Quota
  • IMP_Factory_Search
  • IMP_Factory_Sentmail
  • IMP_Factory_Smime
  • IMP_Factory_Spam
  • IMP_Filter
  • IMP_Flag_Base
  • IMP_Flag_Imap
  • IMP_Flag_Imap_Answered
  • IMP_Flag_Imap_Deleted
  • IMP_Flag_Imap_Draft
  • IMP_Flag_Imap_Flagged
  • IMP_Flag_Imap_Forwarded
  • IMP_Flag_Imap_Junk
  • IMP_Flag_Imap_NotJunk
  • IMP_Flag_Imap_Seen
  • IMP_Flag_System_Attachment
  • IMP_Flag_System_Encrypted
  • IMP_Flag_System_HighPriority
  • IMP_Flag_System_List
  • IMP_Flag_System_LowPriority
  • IMP_Flag_System_Match_Address
  • IMP_Flag_System_Match_Flag
  • IMP_Flag_System_Match_Header
  • IMP_Flag_System_Personal
  • IMP_Flag_System_Signed
  • IMP_Flag_System_Unseen
  • IMP_Flag_User
  • IMP_Flags
  • IMP_Ftree
  • IMP_Ftree_Account
  • IMP_Ftree_Account_Imap
  • IMP_Ftree_Account_Inboxonly
  • IMP_Ftree_Account_Remote
  • IMP_Ftree_Account_Vfolder
  • IMP_Ftree_Element
  • IMP_Ftree_Eltdiff
  • IMP_Ftree_Iterator
  • IMP_Ftree_Iterator_Ancestors
  • IMP_Ftree_IteratorFilter
  • IMP_Ftree_IteratorFilter_Children
  • IMP_Ftree_IteratorFilter_Containers
  • IMP_Ftree_IteratorFilter_Expanded
  • IMP_Ftree_IteratorFilter_Invisible
  • IMP_Ftree_IteratorFilter_Mailboxes
  • IMP_Ftree_IteratorFilter_Nonimap
  • IMP_Ftree_IteratorFilter_Polled
  • IMP_Ftree_IteratorFilter_Remote
  • IMP_Ftree_IteratorFilter_Special
  • IMP_Ftree_IteratorFilter_Subscribed
  • IMP_Ftree_IteratorFilter_Vfolder
  • IMP_Ftree_Prefs
  • IMP_Ftree_Prefs_Expanded
  • IMP_Ftree_Prefs_Poll
  • IMP_Ftree_Select
  • IMP_Images
  • IMP_Imap
  • IMP_Imap_Acl
  • IMP_Imap_Cache_Wrapper
  • IMP_Imap_Config
  • IMP_Imap_Exception
  • IMP_Imap_Password
  • IMP_Imap_PermanentFlags
  • IMP_Imap_Remote
  • IMP_Indices
  • IMP_Indices_Mailbox
  • IMP_LoginTasks_SystemTask_GarbageCollection
  • IMP_LoginTasks_SystemTask_Upgrade
  • IMP_LoginTasks_SystemTask_UpgradeAuth
  • IMP_LoginTasks_Task_Autocreate
  • IMP_LoginTasks_Task_DeleteAttachmentsMonthly
  • IMP_LoginTasks_Task_DeleteSentmailMonthly
  • IMP_LoginTasks_Task_FilterOnLogin
  • IMP_LoginTasks_Task_PurgeSentmail
  • IMP_LoginTasks_Task_PurgeSpam
  • IMP_LoginTasks_Task_PurgeTrash
  • IMP_LoginTasks_Task_RecoverDraft
  • IMP_LoginTasks_Task_RenameSentmailMonthly
  • IMP_Mailbox
  • IMP_Mailbox_List
  • IMP_Mailbox_List_Pop3
  • IMP_Mailbox_List_Thread
  • IMP_Mailbox_List_Virtual
  • IMP_Mailbox_SessionCache
  • IMP_Mailbox_Ui
  • IMP_Maillog
  • IMP_Maillog_Log_Base
  • IMP_Maillog_Log_Forward
  • IMP_Maillog_Log_Mdn
  • IMP_Maillog_Log_Redirect
  • IMP_Maillog_Log_Reply
  • IMP_Maillog_Log_Replyall
  • IMP_Maillog_Log_Replylist
  • IMP_Maillog_Message
  • IMP_Maillog_Storage_Base
  • IMP_Maillog_Storage_Composite
  • IMP_Maillog_Storage_History
  • IMP_Maillog_Storage_Mdnsent
  • IMP_Maillog_Storage_Null
  • IMP_Mbox_Generate
  • IMP_Mbox_Import
  • IMP_Mbox_Size
  • IMP_Message
  • IMP_Message_Date
  • IMP_Message_Ui
  • IMP_Mime_Headers
  • IMP_Mime_Status
  • IMP_Mime_Status_RenderIssue
  • IMP_Mime_Status_RenderIssue_Display
  • IMP_Mime_Viewer_Alternative
  • IMP_Mime_Viewer_Appledouble
  • IMP_Mime_Viewer_Audio
  • IMP_Mime_Viewer_Enriched
  • IMP_Mime_Viewer_Externalbody
  • IMP_Mime_Viewer_Html
  • IMP_Mime_Viewer_Images
  • IMP_Mime_Viewer_Itip
  • IMP_Mime_Viewer_Mdn
  • IMP_Mime_Viewer_Partial
  • IMP_Mime_Viewer_Pdf
  • IMP_Mime_Viewer_Pgp
  • IMP_Mime_Viewer_Plain
  • IMP_Mime_Viewer_Related
  • IMP_Mime_Viewer_Rfc822
  • IMP_Mime_Viewer_Smil
  • IMP_Mime_Viewer_Smime
  • IMP_Mime_Viewer_Status
  • IMP_Mime_Viewer_Vcard
  • IMP_Mime_Viewer_Video
  • IMP_Mime_Viewer_Zip
  • IMP_Minimal_Base
  • IMP_Minimal_Compose
  • IMP_Minimal_Error
  • IMP_Minimal_Folders
  • IMP_Minimal_Mailbox
  • IMP_Minimal_Message
  • IMP_Minimal_Messagepart
  • IMP_Minimal_Search
  • IMP_Notification_Event_Status
  • IMP_Notification_Handler_Decorator_ImapAlerts
  • IMP_Notification_Handler_Decorator_NewmailNotify
  • IMP_Perms
  • IMP_Prefs_AttribText
  • IMP_Prefs_Identity
  • IMP_Prefs_Sort
  • IMP_Prefs_Sort_FixedDate
  • IMP_Prefs_Sort_None
  • IMP_Prefs_Sort_Sortpref
  • IMP_Prefs_Sort_Sortpref_Locked
  • IMP_Prefs_Special_Acl
  • IMP_Prefs_Special_ComposeTemplates
  • IMP_Prefs_Special_Drafts
  • IMP_Prefs_Special_Encrypt
  • IMP_Prefs_Special_Flag
  • IMP_Prefs_Special_HtmlSignature
  • IMP_Prefs_Special_ImageReplacement
  • IMP_Prefs_Special_InitialPage
  • IMP_Prefs_Special_Mailto
  • IMP_Prefs_Special_NewmailSound
  • IMP_Prefs_Special_PgpPrivateKey
  • IMP_Prefs_Special_PgpPublicKey
  • IMP_Prefs_Special_Remote
  • IMP_Prefs_Special_Searches
  • IMP_Prefs_Special_Sentmail
  • IMP_Prefs_Special_SmimePrivateKey
  • IMP_Prefs_Special_SmimePublicKey
  • IMP_Prefs_Special_Sourceselect
  • IMP_Prefs_Special_Spam
  • IMP_Prefs_Special_SpecialMboxes
  • IMP_Prefs_Special_Trash
  • IMP_Quota
  • IMP_Quota_Hook
  • IMP_Quota_Imap
  • IMP_Quota_Null
  • IMP_Quota_Ui
  • IMP_Remote
  • IMP_Remote_Account
  • IMP_Script_Package_Autocomplete
  • IMP_Script_Package_ComposeBase
  • IMP_Script_Package_DynamicBase
  • IMP_Script_Package_Editor
  • IMP_Script_Package_Imp
  • IMP_Search
  • IMP_Search_Element
  • IMP_Search_Element_Attachment
  • IMP_Search_Element_Autogenerated
  • IMP_Search_Element_Bulk
  • IMP_Search_Element_Contacts
  • IMP_Search_Element_Daterange
  • IMP_Search_Element_Flag
  • IMP_Search_Element_Header
  • IMP_Search_Element_Mailinglist
  • IMP_Search_Element_Or
  • IMP_Search_Element_Personal
  • IMP_Search_Element_Recipient
  • IMP_Search_Element_Size
  • IMP_Search_Element_Text
  • IMP_Search_Element_Within
  • IMP_Search_Filter
  • IMP_Search_Filter_Attachment
  • IMP_Search_Filter_Autogenerated
  • IMP_Search_Filter_Builtin
  • IMP_Search_Filter_Bulk
  • IMP_Search_Filter_Contacts
  • IMP_Search_Filter_Mailinglist
  • IMP_Search_Filter_Personal
  • IMP_Search_IteratorFilter
  • IMP_Search_Query
  • IMP_Search_Ui
  • IMP_Search_Vfolder
  • IMP_Search_Vfolder_Builtin
  • IMP_Search_Vfolder_Vinbox
  • IMP_Search_Vfolder_Vtrash
  • IMP_Sentmail
  • IMP_Sentmail_Mongo
  • IMP_Sentmail_Null
  • IMP_Sentmail_Sql
  • IMP_Smartmobile
  • IMP_Spam
  • IMP_Spam_Email
  • IMP_Spam_Null
  • IMP_Spam_Program
  • IMP_Test
  • IMP_Tree_Flist
  • IMP_Tree_Jquerymobile
  • IMP_Tree_Simplehtml
  • IMP_View_Subinfo

Interfaces

  • IMP_Compose_Attachment_Linked
  • IMP_Contacts_Avatar_Backend
  • IMP_Contacts_Flag_Backend
  • IMP_Spam_Base
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Copyright 1999-2014 Horde LLC (http://www.horde.org/)
  4:  *
  5:  * See the enclosed file COPYING for license information (GPL). If you
  6:  * did not receive this file, see http://www.horde.org/licenses/gpl.
  7:  *
  8:  * @category  Horde
  9:  * @copyright 1999-2014 Horde LLC
 10:  * @license   http://www.horde.org/licenses/gpl GPL
 11:  * @package   IMP
 12:  */
 13: 
 14: /**
 15:  * Renderer of HTML data, attempting to remove malicious code.
 16:  *
 17:  * @author    Anil Madhavapeddy <anil@recoil.org>
 18:  * @author    Jon Parise <jon@horde.org>
 19:  * @author    Michael Slusarz <slusarz@horde.org>
 20:  * @category  Horde
 21:  * @copyright 1999-2014 Horde LLC
 22:  * @license   http://www.horde.org/licenses/gpl GPL
 23:  * @package   IMP
 24:  */
 25: class IMP_Mime_Viewer_Html extends Horde_Mime_Viewer_Html
 26: {
 27:     /** CSS background regex. */
 28:     const CSS_BG_PREG = '/(background(?:-image)?:[^;\}]*(?:url\(["\']?))(.*?)((?:["\']?\)))/i';
 29: 
 30:     /** Blocked attributes. */
 31:     const CSSBLOCK = 'htmlcssblocked';
 32:     const IMGBLOCK = 'htmlimgblocked';
 33:     const SRCSETBLOCK = 'htmlimgblocked_srcset';
 34: 
 35:     /**
 36:      * Temp array for storing data when parsing the HTML document.
 37:      *
 38:      * @var array
 39:      */
 40:     protected $_imptmp = array();
 41: 
 42:     /**
 43:      * This driver's display capabilities.
 44:      *
 45:      * @var array
 46:      */
 47:     protected $_capability = array(
 48:         'full' => true,
 49:         'info' => true,
 50:         'inline' => true,
 51:         'raw' => false
 52:     );
 53: 
 54:     /**
 55:      * Return the full rendered version of the Horde_Mime_Part object.
 56:      *
 57:      * @return array  See parent::render().
 58:      */
 59:     protected function _render()
 60:     {
 61:         return array(
 62:             $this->_mimepart->getMimeId() => $this->_IMPrender(false)
 63:         );
 64:     }
 65: 
 66:     /**
 67:      * Return the rendered inline version of the Horde_Mime_Part object.
 68:      *
 69:      * @return array  See parent::render().
 70:      */
 71:     protected function _renderInline()
 72:     {
 73:         global $page_output, $registry;
 74: 
 75:         $data = $this->_IMPrender(true);
 76: 
 77:         switch ($view = $registry->getView()) {
 78:         case $registry::VIEW_MINIMAL:
 79:             $data['status'] = new IMP_Mime_Status(array(
 80:                 _("This message part contains HTML data, but this data can not be displayed inline."),
 81:                 $this->getConfigParam('imp_contents')->linkView($this->_mimepart, 'view_attach', _("View HTML data in new window."))
 82:             ));
 83:             break;
 84: 
 85:         default:
 86:             $uid = strval(new Horde_Support_Randomid());
 87: 
 88:             $page_output->addScriptPackage('IMP_Script_Package_Imp');
 89: 
 90:             $data['metadata'] = array(array('html', $uid, $data['data']));
 91:             $data['data'] = '<div>' . _("Loading...") . '</div><iframe class="htmlMsgData" id="' . $uid . '" src="javascript:false" frameborder="0" style="display:none;height:auto;"></iframe>';
 92:             $data['type'] = 'text/html; charset=UTF-8';
 93:             break;
 94:         }
 95: 
 96:         return array(
 97:             $this->_mimepart->getMimeId() => $data
 98:         );
 99:     }
100: 
101:     /**
102:      * Return the rendered information about the Horde_Mime_Part object.
103:      *
104:      * @return array  See parent::render().
105:      */
106:     protected function _renderInfo()
107:     {
108:         if ($this->canRender('inline') ||
109:             ($this->_mimepart->getDisposition() == 'attachment')) {
110:             return array();
111:         }
112: 
113:         $status = new IMP_Mime_Status(array(
114:             _("This message part contains HTML data, but inline HTML display is disabled."),
115:             $this->getConfigParam('imp_contents')->linkViewJS($this->_mimepart, 'view_attach', _("View HTML data in new window.")),
116:             $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)))
117:         ));
118:         $status->icon('mime/html.png');
119: 
120:         return array(
121:             $this->_mimepart->getMimeId() => array(
122:                 'data' => '',
123:                 'status' => $status,
124:                 'type' => 'text/html; charset=' . $this->getConfigParam('charset')
125:             )
126:         );
127:     }
128: 
129:     /**
130:      * Render out the currently set contents.
131:      *
132:      * @param boolean $inline  Are we viewing inline?
133:      *
134:      * @return array  Two elements: html and status.
135:      */
136:     protected function _IMPrender($inline)
137:     {
138:         global $injector, $registry;
139: 
140:         $data = $this->_mimepart->getContents();
141:         $view = $registry->getView();
142: 
143:         $contents = $this->getConfigParam('imp_contents');
144:         $convert_text = ($view == $registry::VIEW_MINIMAL) ||
145:                         $injector->getInstance('Horde_Variables')->convert_text;
146: 
147:         /* Don't do IMP DOM processing if in mimp mode or converting to
148:          * text. */
149:         $this->_imptmp = array();
150:         if ($inline && !$convert_text) {
151:             $this->_imptmp += array(
152:                 'cid' => null,
153:                 'cid_used' => array(),
154:                 'cssblock' => false,
155:                 'cssbroken' => false,
156:                 'imgblock' => false,
157:                 'imgbroken' => false,
158:                 'inline' => $inline,
159:                 'style' => array()
160:             );
161:         }
162: 
163:         /* Search for inlined data that we can display (multipart/related
164:          * parts) - see RFC 2392. */
165:         if ($related_part = $contents->findMimeType($this->_mimepart->getMimeId(), 'multipart/related')) {
166:             $this->_imptmp['cid'] = $related_part->getMetadata('related_ob');
167:         }
168: 
169:         /* Sanitize the HTML. */
170:         $data = $this->_cleanHTML($data, array(
171:             'noprefetch' => ($inline && ($view != Horde_Registry::VIEW_MINIMAL)),
172:             'phishing' => $inline
173:         ));
174: 
175:         if (!empty($this->_imptmp['style'])) {
176:             $this->_processDomDocument($data->dom);
177:         }
178: 
179:         if ($inline) {
180:             $charset = 'UTF-8';
181:             $data = $data->returnHtml(array(
182:                 'charset' => $charset,
183:                 'metacharset' => true
184:             ));
185:         } else {
186:             $charset = $this->_mimepart->getCharset();
187:             $data = $data->returnHtml();
188:         }
189: 
190:         $status = array();
191:         if ($this->_phishWarn) {
192:             $status[] = new IMP_Mime_Status(array(
193:                 sprintf(_("%s: This message may not be from whom it claims to be."), _("WARNING")),
194:                 _("Beware of following any links in it or of providing the sender with any personal information."),
195:                 _("The links that caused this warning have this background color:") . ' <span style="' . $this->_phishCss . '">' . _("EXAMPLE LINK") . '</span>'
196:             ));
197:         }
198: 
199:         /* We are done processing if in mimp mode, or we are converting to
200:          * text. */
201:         if ($convert_text) {
202:             $data = $this->_textFilter($data, 'Html2text', array(
203:                 'wrap' => false
204:             ));
205: 
206:             // Filter bad language.
207:             return array(
208:                 'data' => IMP::filterText($data),
209:                 'type' => 'text/plain; charset=' . $charset
210:             );
211:         }
212: 
213:         if ($inline) {
214:             switch ($view) {
215:             case $registry::VIEW_SMARTMOBILE:
216:                 if ($this->_imptmp['imgblock']) {
217:                     $tmp_txt = _("Show images...");
218:                 } elseif ($this->_imptmp['cssblock']) {
219:                     $tmp_txt = _("Load message styling...");
220:                 } else {
221:                     $tmp_txt = null;
222:                 }
223: 
224:                 if (!is_null($tmp_txt)) {
225:                     $tmp = new IMP_Mime_Status(array(
226:                         '<a href="#unblock-image" data-role="button" data-theme="e">' . $tmp_txt . '</a>'
227:                     ));
228:                     $tmp->views = array($view);
229:                     $status[] = $tmp;
230:                 }
231:                 break;
232: 
233:             default:
234:                 $class = 'unblockImageLink';
235:                 if (!$injector->getInstance('IMP_Prefs_Special_ImageReplacement')->canAddToSafeAddrList() ||
236:                     $injector->getInstance('IMP_Identity')->hasAddress($contents->getHeader()->getOb('from'))) {
237:                     $class .= ' noUnblockImageAdd';
238:                 }
239: 
240:                 $link = $text = null;
241:                 if ($this->_imptmp['imgblock']) {
242:                     $text = _("Images have been blocked in this message part.");
243:                     $link = _("Show Images?");
244:                 } elseif ($this->_imptmp['cssblock']) {
245:                     $text = _("Message styling has been suppressed in this message part since the style data lives on a remote server.");
246:                     $link = _("Load Styling?");
247:                 }
248: 
249:                 if (!is_null($link)) {
250:                     $tmp = new IMP_Mime_Status(array(
251:                         $text,
252:                         Horde::link('#', '', $class, '', '', '', '', array(
253:                             'muid' => strval($contents->getIndicesOb())
254:                         )) . $link . '</a>'
255:                     ));
256:                     $tmp->icon('mime/image.png');
257:                     $status[] = $tmp;
258:                 }
259: 
260:                 if ($this->_imptmp['cssbroken']) {
261:                     $tmp = new IMP_Mime_Status_RenderIssue(array(
262:                         _("This message contains corrupt styling data so the message contents may not appear correctly below."),
263:                         $contents->linkViewJS($this->_mimepart, 'view_attach', _("Click to view HTML data in new window; it is possible this will allow you to view the message correctly."))
264:                     ));
265:                     $tmp->icon('mime/image.png');
266:                     $status[] = $tmp;
267:                 }
268: 
269:                 if ($this->_imptmp['imgbroken']) {
270:                     $tmp = new IMP_Mime_Status_RenderIssue(array(
271:                         _("This message contains images that cannot be loaded.")
272:                     ));
273:                     $tmp->icon('mime/image.png');
274:                     $status[] = $tmp;
275:                 }
276:                 break;
277:             }
278:         }
279: 
280:         /* Add used CID information. */
281:         if ($inline && !empty($this->_imptmp['cid'])) {
282:             $related_part->setMetadata('related_cids_used', $this->_imptmp['cid_used']);
283:         }
284: 
285:         return array(
286:             'data' => $data,
287:             'status' => $status,
288:             'type' => 'text/html; charset=' . $charset
289:         );
290:     }
291: 
292:     /**
293:      */
294:     protected function _node($doc, $node)
295:     {
296:         parent::_node($doc, $node);
297: 
298:         if (empty($this->_imptmp) || !($node instanceof DOMElement)) {
299:             if (($node instanceof DOMText) && ($node->length > 1)) {
300:                 /* Filter bad language. */
301:                 $text = IMP::filterText($node->data);
302:                 if ($node->data != $text) {
303:                     $node->replaceData(0, $node->length, $text);
304:                 }
305:             }
306:             return;
307:         }
308: 
309:         $tag = Horde_String::lower($node->tagName);
310: 
311:         /* Remove 'height' styles from HTML messages, because it can break
312:          * sizing of IFRAME. */
313:         foreach ($node->attributes as $key => $val) {
314:             if ($key == 'style') {
315:                 /* Do simplistic style parsing here. */
316:                 $parts = array_filter(explode(';', $val->value));
317:                 foreach ($parts as $k2 => $v2) {
318:                     if (preg_match("/^\s*height:\s*/i", $v2)) {
319:                         unset($parts[$k2]);
320:                     }
321:                 }
322:                 $val->value = implode(';', $parts);
323:             }
324:         }
325: 
326:         switch ($tag) {
327:         case 'a':
328:         case 'area':
329:             /* Convert links to open in new windows. Ignore mailto: links and
330:              * links that already have a target. */
331:             if ($node->hasAttribute('href')) {
332:                 $url = parse_url($node->getAttribute('href'));
333:                 if (isset($url['scheme']) && ($url['scheme'] == 'mailto')) {
334:                     /* We don't include HordePopup in IFRAME, so need to use
335:                      * 'simple' links. */
336:                     $clink = new IMP_Compose_Link($node->getAttribute('href'));
337:                     $node->setAttribute('href', $clink->link(true));
338:                     $node->removeAttribute('target');
339:                 } elseif (!empty($this->_imptmp['inline']) &&
340:                           isset($url['fragment']) &&
341:                           empty($url['path']) &&
342:                           $GLOBALS['browser']->isBrowser('mozilla')) {
343:                     /* See Bug #8695: internal anchors are broken in
344:                      * Mozilla. */
345:                     $node->removeAttribute('href');
346:                 } elseif (empty($url)) {
347:                     /* Empty URL - remove href/target so the link is not
348:                      * clickable. */
349:                     $node->removeAttribute('href');
350:                     $node->removeAttribute('target');
351:                 } else {
352:                     $node->setAttribute('target', strval(new Horde_Support_Randomid()));
353:                 }
354:             }
355:             break;
356: 
357:         case 'body':
358:             $style = $node->hasAttribute('style')
359:                 ? (rtrim($node->getAttribute('style'), ';') . ';')
360:                 : '';
361:             $node->setAttribute('style', $style . 'width:auto !important');
362:             break;
363: 
364:         case 'img':
365:         case 'input':
366:             if ($node->hasAttribute('src')) {
367:                 $val = $node->getAttribute('src');
368: 
369:                 /* Multipart/related. */
370:                 if (($tag == 'img') && ($id = $this->_cidSearch($val))) {
371:                     $val = $this->getConfigParam('imp_contents')->urlView(null, 'view_attach', array('params' => array(
372:                         'ctype' => 'image/*',
373:                         'id' => $id,
374:                         'imp_img_view' => 'data'
375:                     )));
376:                 }
377: 
378:                 /* Block images.*/
379:                 if ($this->_imgBlock()) {
380:                     if (Horde_Url_Data::isData($val)) {
381:                         $url = new Horde_Url_Data($val);
382:                     } else {
383:                         /* Check for relative URLs. These won't be loaded and
384:                          * will cause unnecessary 404 hits to the local web
385:                          * server. */
386:                         $parsed_url = parse_url($val);
387:                         if (isset($parsed_url['host'])) {
388:                             $url = new Horde_Url($val);
389:                             $url->setScheme();
390:                         } else {
391:                             $url = null;
392:                         }
393:                     }
394: 
395:                     if ($url) {
396:                         $node->setAttribute(self::IMGBLOCK, $url);
397:                         $node->setAttribute('src', $this->_imgBlockImg());
398:                         $this->_imptmp['imgblock'] = true;
399:                     } else {
400:                         $node->parentNode->removeChild($node);
401:                         $this->_imptmp['imgbroken'] = true;
402:                     }
403:                 } else {
404:                     $node->removeAttribute('src');
405:                     $node->setAttribute('data-src', $val);
406:                 }
407:             }
408: 
409:             /* IMG only */
410:             if (($tag == 'img') &&
411:                 $this->_imgBlock() &&
412:                 $node->hasAttribute('srcset')) {
413:                 $node->setAttribute(self::SRCSETBLOCK, $node->getAttribute('srcset'));
414:                 $node->setAttribute('srcset', '');
415:                 $this->_imptmp['imgblock'] = true;
416:             }
417:             break;
418: 
419:         case 'link':
420:             /* Block all link tags that reference foreign URLs, other than
421:              * CSS. There's no inherently wrong with linking to a foreign
422:              * CSS file other than privacy concerns. Therefore, block
423:              * linking until requested by the user. */
424:             $delete_link = true;
425: 
426:             switch (Horde_String::lower($node->getAttribute('type'))) {
427:             case 'text/css':
428:                 if ($node->hasAttribute('href')) {
429:                     $tmp = $node->getAttribute('href');
430: 
431:                     if ($id = $this->_cidSearch($tmp, false)) {
432:                         $this->_imptmp['style'][] = $this->getConfigParam('imp_contents')->getMIMEPart($id)->getContents();
433:                     } elseif ($this->_imgBlock()) {
434:                         $node->setAttribute(self::CSSBLOCK, $node->getAttribute('href'));
435:                         $node->removeAttribute('href');
436:                         $this->_imptmp['cssblock'] = true;
437:                         $delete_link = false;
438:                     }
439:                 }
440:                 break;
441:             }
442: 
443:             if ($delete_link &&
444:                 $node->hasAttribute('href') &&
445:                 $node->parentNode) {
446:                 $node->parentNode->removeChild($node);
447:             }
448:             break;
449: 
450:         case 'style':
451:             switch (Horde_String::lower($node->getAttribute('type'))) {
452:             case 'text/css':
453:                 $this->_imptmp['style'][] = str_replace(
454:                     array('<!--', '-->'),
455:                     '',
456:                     $node->nodeValue
457:                 );
458:                 $node->parentNode->removeChild($node);
459:                 break;
460:             }
461:             break;
462: 
463:         case 'table':
464:             /* If displaying inline (in IFRAME), tables with 100% height seems
465:              * to confuse many browsers re: the IFRAME internal height. */
466:             if (!empty($this->_imptmp['inline']) &&
467:                 $node->hasAttribute('height') &&
468:                 ($node->getAttribute('height') == '100%')) {
469:                 $node->removeAttribute('height');
470:             }
471: 
472:             // Fall-through
473: 
474:         case 'body':
475:         case 'td':
476:             if ($node->hasAttribute('background')) {
477:                 $val = $node->getAttribute('background');
478: 
479:                 /* Multipart/related. */
480:                 if ($id = $this->_cidSearch($val)) {
481:                     $val = $this->getConfigParam('imp_contents')->urlView(null, 'view_attach', array('params' => array(
482:                         'id' => $id,
483:                         'imp_img_view' => 'data'
484:                     )));
485:                     $node->setAttribute('background', $val);
486:                 }
487: 
488:                 /* Block images.*/
489:                 if ($this->_imgBlock()) {
490:                     $node->setAttribute(self::IMGBLOCK, $val);
491:                     $node->setAttribute('background', $this->_imgBlockImg());
492:                     $this->_imptmp['imgblock'] = true;
493:                 }
494:             }
495:             break;
496:         }
497: 
498:         $remove = array();
499:         foreach ($node->attributes as $val) {
500:             /* Catch random mailto: strings in attributes that will cause
501:              * problems with e-mail linking. */
502:             if (stripos($val->value, 'mailto:') === 0) {
503:                 $remove[] = $val->name;
504:             }
505:         }
506: 
507:         foreach ($remove as $val) {
508:             $node->removeAttribute($val);
509:         }
510: 
511:         if ($node->hasAttribute('style')) {
512:             if (strpos($node->getAttribute('style'), 'content:') !== false) {
513:                 // TODO: Figure out way to unblock?
514:                 $node->removeAttribute('style');
515:             } elseif (!empty($this->_imptmp['cid']) || $this->_imgBlock()) {
516:                 $this->_imptmp['node'] = $node;
517:                 $style = preg_replace_callback(self::CSS_BG_PREG, array($this, '_styleCallback'), $node->getAttribute('style'), -1, $matches);
518:                 if ($matches) {
519:                     $node->setAttribute('style', $style);
520:                 }
521:             }
522:         }
523:     }
524: 
525:     /**
526:      */
527:     protected function _processDomDocument($doc)
528:     {
529:         try {
530:             $css = new Horde_Css_Parser(implode("\n", $this->_imptmp['style']));
531:         } catch (Exception $e) {
532:             /* If your CSS sucks and we can't parse it, tough. Ignore it
533:              * and inform the user. */
534:             $this->_imptmp['cssbroken'] = true;
535:             return;
536:         }
537:         $blocked = clone $css;
538: 
539:         /* Go through and remove questionable rules from styles first. */
540:         $css_text = $this->_parseCss($css, false);
541: 
542:         /* Now go through blocked object and do the opposite: only keep the
543:          * questionable rules. */
544:         $blocked_text = $this->_parseCss($blocked, true);
545: 
546:         if (strlen($css_text) || strlen($blocked_text)) {
547:             /* Gets the HEAD element or creates one if it doesn't exist. */
548:             $head = $doc->getElementsByTagName('head');
549:             if ($head->length) {
550:                 $headelt = $head->item(0);
551:             } else {
552:                 $headelt = $doc->createElement('head');
553:                 $doc->appendChild($headelt);
554:             }
555:         } else {
556:             $headelt = $doc->createElement('head');
557:             $doc->appendChild($headelt);
558:         }
559: 
560:         if (strlen($css_text)) {
561:             $style_elt = $doc->createElement('style');
562:             $style_elt->appendChild(new DOMText($css_text));
563:             $style_elt->setAttribute('type', 'text/css');
564:             $headelt->appendChild($style_elt);
565:         }
566: 
567:         /* Store all the blocked CSS in a bogus style element in the HTML
568:          * output - then we simply need to change the type attribute to
569:          * text/css, and the browser should load the definitions on-demand. */
570:         if (strlen($blocked_text)) {
571:             $block_elt = $doc->createElement('style');
572:             $block_elt->appendChild(new DOMText($blocked_text));
573:             $block_elt->setAttribute('type', 'text/x-imp-cssblocked');
574:             $headelt->appendChild($block_elt);
575:         }
576:     }
577: 
578:     /**
579:      */
580:     protected function _parseCss($css, $blocked)
581:     {
582:         foreach ($css->doc->getContents() as $val) {
583:             if ($val instanceof Sabberworm\CSS\RuleSet\RuleSet) {
584:                 foreach ($val->getRules() as $val2) {
585:                     $item = $val2->getValue();
586: 
587:                     if ($item instanceof Sabberworm\CSS\Value\URL) {
588:                         if (!$blocked) {
589:                             $val->removeRule($val2);
590:                         }
591:                     } elseif ($item instanceof Sabberworm\CSS\Value\RuleValueList) {
592:                         $components = $item->getListComponents();
593:                         foreach ($components as $key3 => $val3) {
594:                             if ($val3 instanceof Sabberworm\CSS\Value\URL) {
595:                                 if (!$blocked) {
596:                                     unset($components[$key3]);
597:                                 }
598:                             } elseif ($blocked) {
599:                                 unset($components[$key3]);
600:                             }
601:                         }
602:                         $item->setListComponents($components);
603:                     } elseif ($blocked) {
604:                         $val->removeRule($val2);
605:                     }
606:                 }
607:             } elseif ($val instanceof Sabberworm\CSS\Property\Import) {
608:                 if (!$blocked) {
609:                     $css->doc->remove($val);
610:                 }
611:             } elseif ($blocked) {
612:                 $css->doc->remove($val);
613:             }
614:         }
615: 
616:         return $css->compress();
617:     }
618: 
619:     /**
620:      * preg_replace_callback() callback for style/background matching of
621:      * images.
622:      *
623:      * @param array $matches  The list of matches.
624:      *
625:      * @return string  The replacement image string.
626:      */
627:     protected function _styleCallback($matches)
628:     {
629:         if ($id = $this->_cidSearch($matches[2])) {
630:             $replace = $this->getConfigParam('imp_contents')->urlView(null, 'view_attach', array('params' => array(
631:                 'id' => $id,
632:                 'imp_img_view' => 'data'
633:             )));
634:         } else {
635:             $this->_imptmp['node']->setAttribute(self::IMGBLOCK, $matches[2]);
636:             $this->_imptmp['imgblock'] = true;
637:             $replace = $this->_imgBlockImg();
638:         }
639:         return $matches[1] . $replace . $matches[3];
640:     }
641: 
642:     /**
643:      * Search for a CID in a related part.
644:      *
645:      * @param string $cid    The CID to query.
646:      * @param boolean $save  Save as a CID used?
647:      *
648:      * @return string  The MIME ID of the part, or null if not found.
649:      */
650:     protected function _cidSearch($cid, $save = true)
651:     {
652:         if (empty($this->_imptmp['cid']) ||
653:             (strpos($cid, 'cid:') !== 0) ||
654:             !($id = $this->_imptmp['cid']->cidSearch(substr($cid, 4)))) {
655:             return null;
656:         }
657: 
658:         if ($save) {
659:             $this->_imptmp['cid_used'][] = $id;
660:         }
661: 
662:         return $id;
663:     }
664: 
665:     /**
666:      * Are we blocking images?
667:      *
668:      * @return boolean  True if blocking images.
669:      */
670:     protected function _imgBlock()
671:     {
672:         global $injector;
673: 
674:         /* Done on demand, since we potentially save a contacts API call if
675:          * not needed/used in a message. */
676:         if (!isset($this->_imptmp['img'])) {
677:             $this->_imptmp['img'] =
678:                 ($this->_imptmp['inline'] &&
679:                 !$injector->getInstance('IMP_Images')->showInlineImage($this->getConfigParam('imp_contents')));
680:         }
681: 
682:         return $this->_imptmp['img'];
683:     }
684: 
685:     /**
686:      * The HTML image source to use for blocked images.
687:      *
688:      * @return string  The HTML image source.
689:      */
690:     protected function _imgBlockImg()
691:     {
692:         if (!isset($this->_imptmp['blockimg'])) {
693:             $this->_imptmp['blockimg'] = strval(Horde_Themes::img('spacer_red.png'));
694:         }
695: 
696:         return $this->_imptmp['blockimg'];
697:     }
698: 
699: }
700: 
API documentation generated by ApiGen