public function testIsData() { $this->assertFalse(Horde_Url_Data::isData(new Horde_Url())); $this->assertFalse(Horde_Url_Data::isData('foo')); $this->assertFalse(Horde_Url_Data::isData(333)); $this->assertFalse(Horde_Url_Data::isData(array(new Horde_Url_Data()))); $this->assertTrue(Horde_Url_Data::isData(new Horde_Url_Data())); $this->assertTrue(Horde_Url_Data::isData('data:text/plain,Foo')); }
/** * Constructor. * * @param string $sig HTML signature data. * * @throws IMP_Exception */ public function __construct($sig) { global $conf, $injector; /* Scrub HTML. */ $this->dom = $injector->getInstance('Horde_Core_Factory_TextFilter')->filter($sig, 'Xss', array('charset' => 'UTF-8', 'return_dom' => true, 'strip_style_attributes' => false)); $img_limit = intval($conf['compose']['htmlsig_img_size']); $xpath = new DOMXPath($this->dom->dom); foreach ($xpath->query('//*[@src]') as $node) { $src = $node->getAttribute('src'); if (Horde_Url_Data::isData($src)) { if (strcasecmp($node->tagName, 'IMG') === 0) { $data_url = new Horde_Url_Data($src); if ($img_limit && ($img_limit -= strlen($data_url->data)) < 0) { throw new IMP_Exception(_("The total size of your HTML signature image data has exceeded the maximum allowed.")); } $node->setAttribute(self::HTMLSIG_ATTR, 1); } else { /* Don't allow any other non-image data URLs. */ $node->removeAttribute('src'); } } } }
/** */ protected function _node($doc, $node) { parent::_node($doc, $node); if (empty($this->_imptmp) || !$node instanceof DOMElement) { if ($node instanceof DOMText && $node->length > 1) { /* Filter bad language. */ $text = IMP::filterText($node->data); if ($node->data != $text) { $node->replaceData(0, $node->length, $text); } } return; } $tag = Horde_String::lower($node->tagName); /* Remove 'height' styles from HTML messages, because it can break * sizing of IFRAME. */ foreach ($node->attributes as $key => $val) { if ($key == 'style') { /* Do simplistic style parsing here. */ $parts = array_filter(explode(';', $val->value)); foreach ($parts as $k2 => $v2) { if (preg_match("/^\\s*height:\\s*/i", $v2)) { unset($parts[$k2]); } } $val->value = implode(';', $parts); } } switch ($tag) { case 'a': case 'area': /* Convert links to open in new windows. Ignore mailto: links and * links that already have a target. */ if ($node->hasAttribute('href')) { $url = parse_url($node->getAttribute('href')); if (isset($url['scheme']) && $url['scheme'] == 'mailto') { /* We don't include HordePopup in IFRAME, so need to use * 'simple' links. */ $clink = new IMP_Compose_Link($node->getAttribute('href')); $node->setAttribute('href', $clink->link(true)); $node->removeAttribute('target'); } elseif (!empty($this->_imptmp['inline']) && isset($url['fragment']) && empty($url['path']) && $GLOBALS['browser']->isBrowser('mozilla')) { /* See Bug #8695: internal anchors are broken in * Mozilla. */ $node->removeAttribute('href'); } elseif (empty($url)) { /* Empty URL - remove href/target so the link is not * clickable. */ $node->removeAttribute('href'); $node->removeAttribute('target'); } else { $node->setAttribute('target', strval(new Horde_Support_Randomid())); } } break; case 'body': $style = $node->hasAttribute('style') ? rtrim($node->getAttribute('style'), ';') . ';' : ''; $node->setAttribute('style', $style . 'width:auto !important'); break; case 'img': case 'input': if ($node->hasAttribute('src')) { $val = $node->getAttribute('src'); /* Multipart/related. */ if ($tag == 'img' && ($id = $this->_cidSearch($val))) { $val = $this->getConfigParam('imp_contents')->urlView(null, 'view_attach', array('params' => array('ctype' => 'image/*', 'id' => $id, 'imp_img_view' => 'data'))); } /* Block images.*/ if ($this->_imgBlock()) { if (Horde_Url_Data::isData($val)) { $url = new Horde_Url_Data($val); } else { /* Check for relative URLs. These won't be loaded and * will cause unnecessary 404 hits to the local web * server. */ $parsed_url = parse_url($val); if (isset($parsed_url['host'])) { $url = new Horde_Url($val); $url->setScheme(); } else { $url = null; } } if ($url) { $node->setAttribute(self::IMGBLOCK, $url); $node->setAttribute('src', $this->_imgBlockImg()); $this->_imptmp['imgblock'] = true; } else { $node->parentNode->removeChild($node); $this->_imptmp['imgbroken'] = true; } } else { $node->removeAttribute('src'); $node->setAttribute('data-src', $val); } } /* IMG only */ if ($tag == 'img' && $this->_imgBlock() && $node->hasAttribute('srcset')) { $node->setAttribute(self::SRCSETBLOCK, $node->getAttribute('srcset')); $node->setAttribute('srcset', ''); $this->_imptmp['imgblock'] = true; } break; case 'link': /* Block all link tags that reference foreign URLs, other than * CSS. There's no inherently wrong with linking to a foreign * CSS file other than privacy concerns. Therefore, block * linking until requested by the user. */ $delete_link = true; switch (Horde_String::lower($node->getAttribute('type'))) { case 'text/css': if ($node->hasAttribute('href')) { $tmp = $node->getAttribute('href'); if ($id = $this->_cidSearch($tmp, false)) { $this->_imptmp['style'][] = $this->getConfigParam('imp_contents')->getMIMEPart($id)->getContents(); } elseif ($this->_imgBlock()) { $node->setAttribute(self::CSSBLOCK, $node->getAttribute('href')); $node->removeAttribute('href'); $this->_imptmp['cssblock'] = true; $delete_link = false; } } break; } if ($delete_link && $node->hasAttribute('href') && $node->parentNode) { $node->parentNode->removeChild($node); } break; case 'style': switch (Horde_String::lower($node->getAttribute('type'))) { case 'text/css': $this->_imptmp['style'][] = str_replace(array('<!--', '-->'), '', $node->nodeValue); $node->parentNode->removeChild($node); break; } break; case 'table': /* If displaying inline (in IFRAME), tables with 100% height seems * to confuse many browsers re: the IFRAME internal height. */ if (!empty($this->_imptmp['inline']) && $node->hasAttribute('height') && $node->getAttribute('height') == '100%') { $node->removeAttribute('height'); } // Fall-through // Fall-through case 'body': case 'td': if ($node->hasAttribute('background')) { $val = $node->getAttribute('background'); /* Multipart/related. */ if ($id = $this->_cidSearch($val)) { $val = $this->getConfigParam('imp_contents')->urlView(null, 'view_attach', array('params' => array('id' => $id, 'imp_img_view' => 'data'))); $node->setAttribute('background', $val); } /* Block images.*/ if ($this->_imgBlock()) { $node->setAttribute(self::IMGBLOCK, $val); $node->setAttribute('background', $this->_imgBlockImg()); $this->_imptmp['imgblock'] = true; } } break; } $remove = array(); foreach ($node->attributes as $val) { /* Catch random mailto: strings in attributes that will cause * problems with e-mail linking. */ if (stripos($val->value, 'mailto:') === 0) { $remove[] = $val->name; } } foreach ($remove as $val) { $node->removeAttribute($val); } if ($node->hasAttribute('style')) { if (strpos($node->getAttribute('style'), 'content:') !== false) { // TODO: Figure out way to unblock? $node->removeAttribute('style'); } elseif (!empty($this->_imptmp['cid']) || $this->_imgBlock()) { $this->_imptmp['node'] = $node; $style = preg_replace_callback(self::CSS_BG_PREG, array($this, '_styleCallback'), $node->getAttribute('style'), -1, $matches); if ($matches) { $node->setAttribute('style', $style); } } } }
/** */ protected function _minify($data) { $out = ''; foreach ($data as $uri => $file) { if (!is_readable($file)) { $this->_opts['logger']->log(sprintf('Could not open CSS file %s.', $file), Horde_Log::ERR); continue; } $css = file_get_contents($file); try { $parser = new Horde_Css_Parser($css); } catch (Exception $e) { /* If the CSS is broken, log error and output as-is. */ $this->_opts['logger']->log($e, Horde_Log::ERR); $out .= $css; continue; } if (!empty($this->_opts['import'])) { foreach ($parser->doc->getContents() as $val) { if ($val instanceof Sabberworm\CSS\Property\Import) { $res = call_user_func($this->_opts['import'], dirname($uri) . '/' . $val->getLocation()->getURL()->getString()); $out .= $this->_minify(array($res[0] => $res[1])); $parser->doc->remove($val); } } } $url = array(); foreach ($parser->doc->getAllRuleSets() as $val) { foreach (array_merge($val->getRules('background-'), $val->getRules('src')) as $val2) { $item = $val2->getValue(); if ($item instanceof Sabberworm\CSS\Value\URL) { $url[] = $item; } elseif ($item instanceof Sabberworm\CSS\Value\RuleValueList) { foreach ($item->getListComponents() as $val3) { if ($val3 instanceof Sabberworm\CSS\Value\URL) { $url[] = $val3; } elseif ($val3 instanceof Sabberworm\CSS\Value\RuleValueList) { foreach ($val3->getListComponents() as $val4) { if ($val4 instanceof Sabberworm\CSS\Value\URL) { $url[] = $val4; } } } } } } } foreach ($url as $val) { $url_ob = $val->getURL(); $url_str = ltrim($url_ob->getString()); if (stripos($url_str, 'http') !== 0 && !Horde_Url_Data::isData($url_str)) { $url_str = dirname($uri) . '/' . $url_str; if (!empty($this->_opts['dataurl'])) { $url_str = call_user_func($this->_opts['dataurl'], $url_str); } } $url_ob->setString($url_str); } $out .= $parser->compress(); } return $out; }
/** * Clean outgoing HTML (remove unexpected data URLs). * * @param Horde_Domhtml $html The HTML data. */ protected function _cleanHtmlOutput(Horde_Domhtml $html) { global $registry; $xpath = new DOMXPath($html->dom); foreach ($xpath->query('//*[@src]') as $node) { $src = $node->getAttribute('src'); /* Check for attempts to sneak data URL information into the * output. */ if (Horde_Url_Data::isData($src)) { if (IMP_Compose_HtmlSignature::isSigImage($node, true)) { /* This is HTML signature image data. Convert to an * attachment. */ $sig_img = new Horde_Url_Data($src); if ($sig_img->data) { $data_part = new Horde_Mime_Part(); $data_part->setContents($sig_img->data); $data_part->setType($sig_img->type); try { $this->addRelatedAttachment($this->addAttachmentFromPart($data_part), $node, 'src'); } catch (IMP_Compose_Exception $e) { // Remove image on error. } } } $node->removeAttribute('src'); } elseif (strcasecmp($node->tagName, 'IMG') === 0) { /* Check for smileys. They live in the JS directory, under * the base ckeditor directory, so search for that and replace * with the filesystem information if found (Request * #13051). Need to ignore other image links that may have * been explicitly added by the user. */ $js_path = strval(Horde::url($registry->get('jsuri', 'horde'), true)); if (stripos($src, $js_path . '/ckeditor') === 0) { $file = str_replace($js_path, $registry->get('jsfs', 'horde'), $src); if (is_readable($file)) { $data_part = new Horde_Mime_Part(); $data_part->setContents(file_get_contents($file)); $data_part->setName(basename($file)); try { $this->addRelatedAttachment($this->addAttachmentFromPart($data_part), $node, 'src'); } catch (IMP_Compose_Exception $e) { // Keep existing data on error. } } } } } }