diff --git a/lib/htmlpurifier/HTMLPurifier.php b/lib/htmlpurifier/HTMLPurifier.php index c6041bc1130..38a78e8da84 100644 --- a/lib/htmlpurifier/HTMLPurifier.php +++ b/lib/htmlpurifier/HTMLPurifier.php @@ -19,7 +19,7 @@ */ /* - HTML Purifier 4.7.0 - Standards Compliant HTML Filtering + HTML Purifier 4.8.0 - Standards Compliant HTML Filtering Copyright (C) 2006-2008 Edward Z. Yang This library is free software; you can redistribute it and/or @@ -58,12 +58,12 @@ class HTMLPurifier * Version of HTML Purifier. * @type string */ - public $version = '4.7.0'; + public $version = '4.8.0'; /** * Constant with version of HTML Purifier. */ - const VERSION = '4.7.0'; + const VERSION = '4.8.0'; /** * Global configuration object. @@ -104,7 +104,7 @@ class HTMLPurifier /** * Initializes the purifier. * - * @param HTMLPurifier_Config $config Optional HTMLPurifier_Config object + * @param HTMLPurifier_Config|mixed $config Optional HTMLPurifier_Config object * for all instances of the purifier, if omitted, a default * configuration is supplied (which can be overridden on a * per-use basis). diff --git a/lib/htmlpurifier/HTMLPurifier.safe-includes.php b/lib/htmlpurifier/HTMLPurifier.safe-includes.php index 9dea6d1ed55..fe587c7863e 100644 --- a/lib/htmlpurifier/HTMLPurifier.safe-includes.php +++ b/lib/htmlpurifier/HTMLPurifier.safe-includes.php @@ -131,6 +131,7 @@ require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/TargetBlank.php'; +require_once $__dir . '/HTMLPurifier/AttrTransform/TargetNoreferrer.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Textarea.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php'; @@ -169,6 +170,7 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/StyleAttribute.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Target.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/TargetBlank.php'; +require_once $__dir . '/HTMLPurifier/HTMLModule/TargetNoreferrer.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Text.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/XMLCommonAttributes.php'; @@ -219,5 +221,6 @@ require_once $__dir . '/HTMLPurifier/URIScheme/https.php'; require_once $__dir . '/HTMLPurifier/URIScheme/mailto.php'; require_once $__dir . '/HTMLPurifier/URIScheme/news.php'; require_once $__dir . '/HTMLPurifier/URIScheme/nntp.php'; +require_once $__dir . '/HTMLPurifier/URIScheme/tel.php'; require_once $__dir . '/HTMLPurifier/VarParser/Flexible.php'; require_once $__dir . '/HTMLPurifier/VarParser/Native.php'; diff --git a/lib/htmlpurifier/HTMLPurifier/Arborize.php b/lib/htmlpurifier/HTMLPurifier/Arborize.php index 9e6617be5de..d2e9d22a207 100644 --- a/lib/htmlpurifier/HTMLPurifier/Arborize.php +++ b/lib/htmlpurifier/HTMLPurifier/Arborize.php @@ -19,8 +19,8 @@ class HTMLPurifier_Arborize if ($token instanceof HTMLPurifier_Token_End) { $token->start = null; // [MUT] $r = array_pop($stack); - assert($r->name === $token->name); - assert(empty($token->attr)); + //assert($r->name === $token->name); + //assert(empty($token->attr)); $r->endCol = $token->col; $r->endLine = $token->line; $r->endArmor = $token->armor; @@ -32,7 +32,7 @@ class HTMLPurifier_Arborize $stack[] = $node; } } - assert(count($stack) == 1); + //assert(count($stack) == 1); return $stack[0]; } diff --git a/lib/htmlpurifier/HTMLPurifier/AttrCollections.php b/lib/htmlpurifier/HTMLPurifier/AttrCollections.php index 4f6c2e39a2e..c7b17cf144d 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrCollections.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrCollections.php @@ -21,6 +21,11 @@ class HTMLPurifier_AttrCollections * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members */ public function __construct($attr_types, $modules) + { + $this->doConstruct($attr_types, $modules); + } + + public function doConstruct($attr_types, $modules) { // load extensions from the modules foreach ($modules as $module) { diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS.php index 02c1641fb2a..2b977ca38e8 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS.php @@ -25,6 +25,7 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef $css = $this->parseCDATA($css); $definition = $config->getCSSDefinition(); + $allow_duplicates = $config->get("CSS.AllowDuplicates"); // we're going to break the spec and explode by semicolons. // This is because semicolon rarely appears in escaped form @@ -34,6 +35,7 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef $declarations = explode(';', $css); $propvalues = array(); + $new_declarations = ''; /** * Name of the current CSS property being validated. @@ -83,7 +85,11 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef if ($result === false) { continue; } - $propvalues[$property] = $result; + if ($allow_duplicates) { + $new_declarations .= "$property:$result;"; + } else { + $propvalues[$property] = $result; + } } $context->destroy('CurrentCSSProperty'); @@ -92,7 +98,6 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef // slightly inefficient, but it's the only way of getting rid of // duplicates. Perhaps config to optimize it, but not now. - $new_declarations = ''; foreach ($propvalues as $prop => $value) { $new_declarations .= "$prop:$value;"; } diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/URI.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/URI.php index f9434230e21..6617acace59 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/URI.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/URI.php @@ -33,6 +33,9 @@ class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI return false; } $uri_string = substr($uri_string, 4); + if (strlen($uri_string) == 0) { + return false; + } $new_length = strlen($uri_string) - 1; if ($uri_string[$new_length] != ')') { return false; diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/HTML/ID.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/HTML/ID.php index 3d86efb44c3..4ba45610feb 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/HTML/ID.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/HTML/ID.php @@ -72,18 +72,26 @@ class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef // we purposely avoid using regex, hopefully this is faster - if (ctype_alpha($id)) { - $result = true; - } else { - if (!ctype_alpha(@$id[0])) { + if ($config->get('Attr.ID.HTML5') === true) { + if (preg_match('/[\t\n\x0b\x0c ]/', $id)) { return false; } - // primitive style of regexps, I suppose - $trim = trim( - $id, - 'A..Za..z0..9:-._' - ); - $result = ($trim === ''); + } else { + if (ctype_alpha($id)) { + // OK + } else { + if (!ctype_alpha(@$id[0])) { + return false; + } + // primitive style of regexps, I suppose + $trim = trim( + $id, + 'A..Za..z0..9:-._' + ); + if ($trim !== '') { + return false; + } + } } $regexp = $config->get('Attr.IDBlacklistRegexp'); @@ -91,14 +99,14 @@ class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef return false; } - if (!$this->selector && $result) { + if (!$this->selector) { $id_accumulator->add($id); } // if no change was made to the ID, return the result // else, return the new id if stripping whitespace made it // valid, or return false. - return $result ? $id : false; + return $id; } } diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/URI/Host.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/URI/Host.php index e7df800b1eb..151f7aff756 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/URI/Host.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/URI/Host.php @@ -76,24 +76,33 @@ class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef // fairly well supported. $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : ''; + // Based off of RFC 1738, but amended so that + // as per RFC 3696, the top label need only not be all numeric. // The productions describing this are: $a = '[a-z]'; // alpha $an = '[a-z0-9]'; // alphanum $and = "[a-z0-9-$underscore]"; // alphanum | "-" // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum - $domainlabel = "$an($and*$an)?"; - // toplabel = alpha | alpha *( alphanum | "-" ) alphanum - $toplabel = "$a($and*$an)?"; + $domainlabel = "$an(?:$and*$an)?"; + // AMENDED as per RFC 3696 + // toplabel = alphanum | alphanum *( alphanum | "-" ) alphanum + // side condition: not all numeric + $toplabel = "$an(?:$and*$an)?"; // hostname = *( domainlabel "." ) toplabel [ "." ] - if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) { - return $string; + if (preg_match("/^(?:$domainlabel\.)*($toplabel)\.?$/i", $string, $matches)) { + if (!ctype_digit($matches[1])) { + return $string; + } } + // PHP 5.3 and later support this functionality natively + if (function_exists('idn_to_ascii')) { + return idn_to_ascii($string); + // If we have Net_IDNA2 support, we can support IRIs by // punycoding them. (This is the most portable thing to do, // since otherwise we have to assume browsers support - - if ($config->get('Core.EnableIDNA')) { + } elseif ($config->get('Core.EnableIDNA')) { $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true)); // we need to encode each period separately $parts = explode('.', $string); diff --git a/lib/htmlpurifier/HTMLPurifier/AttrTransform/ImgRequired.php b/lib/htmlpurifier/HTMLPurifier/AttrTransform/ImgRequired.php index 7df6cb3e1b4..235ebb34b60 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrTransform/ImgRequired.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrTransform/ImgRequired.php @@ -32,8 +32,7 @@ class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform if ($src) { $alt = $config->get('Attr.DefaultImageAlt'); if ($alt === null) { - // truncate if the alt is too long - $attr['alt'] = substr(basename($attr['src']), 0, 40); + $attr['alt'] = basename($attr['src']); } else { $attr['alt'] = $alt; } diff --git a/lib/htmlpurifier/HTMLPurifier/AttrTransform/TargetNoreferrer.php b/lib/htmlpurifier/HTMLPurifier/AttrTransform/TargetNoreferrer.php new file mode 100644 index 00000000000..587dc2e079a --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/AttrTransform/TargetNoreferrer.php @@ -0,0 +1,37 @@ +get('CSS.MaxImgLength'); + $this->info['min-width'] = + $this->info['max-width'] = + $this->info['min-height'] = + $this->info['max-height'] = $this->info['width'] = $this->info['height'] = $max === null ? @@ -370,6 +374,19 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition ); $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid')); + $border_radius = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative + new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative + )); + + $this->info['border-top-left-radius'] = + $this->info['border-top-right-radius'] = + $this->info['border-bottom-right-radius'] = + $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2); + // TODO: support SLASH syntax + $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4); + } /** diff --git a/lib/htmlpurifier/HTMLPurifier/ChildDef/List.php b/lib/htmlpurifier/HTMLPurifier/ChildDef/List.php index 891b9f6f5bb..5a53a4b4940 100644 --- a/lib/htmlpurifier/HTMLPurifier/ChildDef/List.php +++ b/lib/htmlpurifier/HTMLPurifier/ChildDef/List.php @@ -38,6 +38,12 @@ class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef return false; } + // if li is not allowed, delete parent node + if (!isset($config->getHTMLDefinition()->info['li'])) { + trigger_error("Cannot allow ul/ol without allowing li", E_USER_WARNING); + return false; + } + // the new set of children $result = array(); diff --git a/lib/htmlpurifier/HTMLPurifier/ChildDef/Table.php b/lib/htmlpurifier/HTMLPurifier/ChildDef/Table.php index 3e4a0f21824..cb6b3e6cdc2 100644 --- a/lib/htmlpurifier/HTMLPurifier/ChildDef/Table.php +++ b/lib/htmlpurifier/HTMLPurifier/ChildDef/Table.php @@ -203,7 +203,7 @@ class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef $current_tr_tbody->children[] = $node; break; case '#PCDATA': - assert($node->is_whitespace); + //assert($node->is_whitespace); if ($current_tr_tbody === null) { $ret[] = $node; } else { diff --git a/lib/htmlpurifier/HTMLPurifier/Config.php b/lib/htmlpurifier/HTMLPurifier/Config.php index 2b2db0c2647..7b9dcf0ec50 100644 --- a/lib/htmlpurifier/HTMLPurifier/Config.php +++ b/lib/htmlpurifier/HTMLPurifier/Config.php @@ -21,7 +21,7 @@ class HTMLPurifier_Config * HTML Purifier's version * @type string */ - public $version = '4.7.0'; + public $version = '4.8.0'; /** * Whether or not to automatically finalize diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema.ser b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema.ser index 1e6ccd22755..0a7a406e132 100644 Binary files a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema.ser and b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema.ser differ diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt new file mode 100644 index 00000000000..735d4b7a10f --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt @@ -0,0 +1,10 @@ +Attr.ID.HTML5 +TYPE: bool/null +DEFAULT: null +VERSION: 4.8.0 +--DESCRIPTION-- +In HTML5, restrictions on the format of the id attribute have been significantly +relaxed, such that any string is valid so long as it contains no spaces and +is at least one character. In lieu of a general HTML5 compatibility flag, +set this configuration directive to true to use the relaxed rules. +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt new file mode 100644 index 00000000000..4d054b1f071 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt @@ -0,0 +1,11 @@ +CSS.AllowDuplicates +TYPE: bool +DEFAULT: false +VERSION: 4.8.0 +--DESCRIPTION-- +

+ By default, HTML Purifier removes duplicate CSS properties, + like color:red; color:blue. If this is set to + true, duplicate properties are allowed. +

+--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt index b2b83d9ab6a..2e0cc81044f 100644 --- a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt @@ -1,5 +1,5 @@ Cache.SerializerPermissions -TYPE: int +TYPE: int/null VERSION: 4.3.0 DEFAULT: 0755 --DESCRIPTION-- @@ -8,4 +8,9 @@ DEFAULT: 0755 Directory permissions of the files and directories created inside the DefinitionCache/Serializer or other custom serializer path.

+

+ In HTML Purifier 4.8.0, this also supports NULL, + which means that no chmod'ing or directory creation shall + occur. +

--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt new file mode 100644 index 00000000000..cb5a0b0e5e1 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt @@ -0,0 +1,9 @@ +HTML.TargetNoreferrer +TYPE: bool +VERSION: 4.8.0 +DEFAULT: TRUE +--DESCRIPTION-- +If enabled, noreferrer rel attributes are added to links which have +a target attribute associated with them. This prevents malicious +destinations from overwriting the original window. +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt index 666635a5ff0..eb97307e200 100644 --- a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt @@ -8,6 +8,7 @@ array ( 'ftp' => true, 'nntp' => true, 'news' => true, + 'tel' => true, ) --DESCRIPTION-- Whitelist that defines the schemes that a URI is allowed to have. This diff --git a/lib/htmlpurifier/HTMLPurifier/DefinitionCache.php b/lib/htmlpurifier/HTMLPurifier/DefinitionCache.php index 67bb5b1e69a..9aa8ff354ff 100644 --- a/lib/htmlpurifier/HTMLPurifier/DefinitionCache.php +++ b/lib/htmlpurifier/HTMLPurifier/DefinitionCache.php @@ -118,7 +118,7 @@ abstract class HTMLPurifier_DefinitionCache /** * Clears all expired (older version or revision) objects from cache - * @note Be carefuly implementing this method as flush. Flush must + * @note Be careful implementing this method as flush. Flush must * not interfere with other Definition types, and cleanup() * should not be repeatedly called by userland code. * @param HTMLPurifier_Config $config diff --git a/lib/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer.php b/lib/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer.php index ce268d91b42..7fb106f3f11 100644 --- a/lib/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer.php +++ b/lib/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer.php @@ -97,6 +97,12 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac } $dir = $this->generateDirectoryPath($config); $dh = opendir($dir); + // Apparently, on some versions of PHP, readdir will return + // an empty string if you pass an invalid argument to readdir. + // So you need this test. See #49. + if (false === $dh) { + return false; + } while (false !== ($filename = readdir($dh))) { if (empty($filename)) { continue; @@ -106,6 +112,7 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac } unlink($dir . '/' . $filename); } + return true; } /** @@ -119,6 +126,10 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac } $dir = $this->generateDirectoryPath($config); $dh = opendir($dir); + // See #49 (and above). + if (false === $dh) { + return false; + } while (false !== ($filename = readdir($dh))) { if (empty($filename)) { continue; @@ -131,6 +142,7 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac unlink($dir . '/' . $filename); } } + return true; } /** @@ -186,11 +198,9 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac if ($result !== false) { // set permissions of the new file (no execute) $chmod = $config->get('Cache.SerializerPermissions'); - if (!$chmod) { - $chmod = 0644; // invalid config or simpletest + if ($chmod !== null) { + chmod($file, $chmod & 0666); } - $chmod = $chmod & 0666; - chmod($file, $chmod); } return $result; } @@ -204,8 +214,10 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac { $directory = $this->generateDirectoryPath($config); $chmod = $config->get('Cache.SerializerPermissions'); - if (!$chmod) { - $chmod = 0755; // invalid config or simpletest + if ($chmod === null) { + // TODO: This races + if (is_dir($directory)) return true; + return mkdir($directory); } if (!is_dir($directory)) { $base = $this->generateBaseDirectoryPath($config); @@ -219,15 +231,16 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac } elseif (!$this->_testPermissions($base, $chmod)) { return false; } - mkdir($directory, $chmod); - if (!$this->_testPermissions($directory, $chmod)) { + if (!mkdir($directory, $chmod)) { trigger_error( - 'Base directory ' . $base . ' does not exist, - please create or change using %Cache.SerializerPath', + 'Could not create directory ' . $directory . '', E_USER_WARNING ); return false; } + if (!$this->_testPermissions($directory, $chmod)) { + return false; + } } elseif (!$this->_testPermissions($directory, $chmod)) { return false; } @@ -256,7 +269,7 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac ); return false; } - if (function_exists('posix_getuid')) { + if (function_exists('posix_getuid') && $chmod !== null) { // POSIX system, we can give more specific advice if (fileowner($dir) === posix_getuid()) { // we can chmod it ourselves diff --git a/lib/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/README b/lib/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/README old mode 100644 new mode 100755 diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/TargetNoreferrer.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/TargetNoreferrer.php new file mode 100644 index 00000000000..32484d60195 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/TargetNoreferrer.php @@ -0,0 +1,21 @@ +addBlankElement('a'); + $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetNoreferrer(); + } +} diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModuleManager.php b/lib/htmlpurifier/HTMLPurifier/HTMLModuleManager.php index f3a17cb03b3..2546c043c46 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModuleManager.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModuleManager.php @@ -271,6 +271,11 @@ class HTMLPurifier_HTMLModuleManager if ($config->get('HTML.TargetBlank')) { $modules[] = 'TargetBlank'; } + // NB: HTML.TargetNoreferrer must be AFTER HTML.TargetBlank + // so that its post-attr-transform gets run afterwards. + if ($config->get('HTML.TargetNoreferrer')) { + $modules[] = 'TargetNoreferrer'; + } // merge in custom modules $modules = array_merge($modules, $this->userModules); diff --git a/lib/htmlpurifier/HTMLPurifier/Injector/Linkify.php b/lib/htmlpurifier/HTMLPurifier/Injector/Linkify.php index 069708c2509..74f83eaa7d1 100644 --- a/lib/htmlpurifier/HTMLPurifier/Injector/Linkify.php +++ b/lib/htmlpurifier/HTMLPurifier/Injector/Linkify.php @@ -31,9 +31,14 @@ class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector return; } - // there is/are URL(s). Let's split the string: - // Note: this regex is extremely permissive - $bits = preg_split('#((?:https?|ftp)://[^\s\'",<>()]+)#Su', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE); + // there is/are URL(s). Let's split the string. + // We use this regex: + // https://gist.github.com/gruber/249502 + // but with @cscott's backtracking fix and also + // the Unicode characters un-Unicodified. + $bits = preg_split( + '/\\b((?:[a-z][\\w\\-]+:(?:\\/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}\\/)(?:[^\\s()<>]|\\((?:[^\\s()<>]|(?:\\([^\\s()<>]+\\)))*\\))+(?:\\((?:[^\\s()<>]|(?:\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:\'".,<>?\x{00ab}\x{00bb}\x{201c}\x{201d}\x{2018}\x{2019}]))/iu', + $token->data, -1, PREG_SPLIT_DELIM_CAPTURE); $token = array(); diff --git a/lib/htmlpurifier/HTMLPurifier/Injector/RemoveEmpty.php b/lib/htmlpurifier/HTMLPurifier/Injector/RemoveEmpty.php index 01353ff1d50..0ebc477c68e 100644 --- a/lib/htmlpurifier/HTMLPurifier/Injector/RemoveEmpty.php +++ b/lib/htmlpurifier/HTMLPurifier/Injector/RemoveEmpty.php @@ -46,6 +46,12 @@ class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp'); $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions'); $this->exclude = $config->get('AutoFormat.RemoveEmpty.Predicate'); + foreach ($this->exclude as $key => $attrs) { + if (!is_array($attrs)) { + // HACK, see HTMLPurifier/Printer/ConfigForm.php + $this->exclude[$key] = explode(';', $attrs); + } + } $this->attrValidator = new HTMLPurifier_AttrValidator(); } diff --git a/lib/htmlpurifier/HTMLPurifier/Injector/SafeObject.php b/lib/htmlpurifier/HTMLPurifier/Injector/SafeObject.php index 3d17e07af21..317f7864dd4 100644 --- a/lib/htmlpurifier/HTMLPurifier/Injector/SafeObject.php +++ b/lib/htmlpurifier/HTMLPurifier/Injector/SafeObject.php @@ -36,6 +36,7 @@ class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector ); /** + * These are all lower-case keys. * @type array */ protected $allowedParam = array( @@ -43,7 +44,7 @@ class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector 'movie' => true, 'flashvars' => true, 'src' => true, - 'allowFullScreen' => true, // if omitted, assume to be 'false' + 'allowfullscreen' => true, // if omitted, assume to be 'false' ); /** @@ -93,9 +94,11 @@ class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector $token->attr['name'] === $this->addParam[$n]) { // keep token, and add to param stack $this->paramStack[$i][$n] = true; - } elseif (isset($this->allowedParam[$n])) { + } elseif (isset($this->allowedParam[strtolower($n)])) { // keep token, don't do anything to it // (could possibly check for duplicates here) + // Note: In principle, parameters should be case sensitive. + // But it seems they are not really; so accept any case. } else { $token = false; } diff --git a/lib/htmlpurifier/HTMLPurifier/Lexer.php b/lib/htmlpurifier/HTMLPurifier/Lexer.php index 43732621dc9..44c5c659da8 100644 --- a/lib/htmlpurifier/HTMLPurifier/Lexer.php +++ b/lib/htmlpurifier/HTMLPurifier/Lexer.php @@ -345,12 +345,17 @@ class HTMLPurifier_Lexer public function extractBody($html) { $matches = array(); - $result = preg_match('!]*>(.*)!is', $html, $matches); + $result = preg_match('|(.*?)]*>(.*)|is', $html, $matches); if ($result) { - return $matches[1]; - } else { - return $html; + // Make sure it's not in a comment + $comment_start = strrpos($matches[1], ''); + if ($comment_start === false || + ($comment_end !== false && $comment_end > $comment_start)) { + return $matches[2]; + } } + return $html; } } diff --git a/lib/htmlpurifier/HTMLPurifier/Printer/ConfigForm.php b/lib/htmlpurifier/HTMLPurifier/Printer/ConfigForm.php index 36100ce7384..65a77790413 100644 --- a/lib/htmlpurifier/HTMLPurifier/Printer/ConfigForm.php +++ b/lib/htmlpurifier/HTMLPurifier/Printer/ConfigForm.php @@ -327,6 +327,10 @@ class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer case HTMLPurifier_VarParser::HASH: $nvalue = ''; foreach ($value as $i => $v) { + if (is_array($v)) { + // HACK + $v = implode(";", $v); + } $nvalue .= "$i:$v" . PHP_EOL; } $value = $nvalue; diff --git a/lib/htmlpurifier/HTMLPurifier/URIScheme/data.php b/lib/htmlpurifier/HTMLPurifier/URIScheme/data.php index 6ebca498481..41c49d5533f 100644 --- a/lib/htmlpurifier/HTMLPurifier/URIScheme/data.php +++ b/lib/htmlpurifier/HTMLPurifier/URIScheme/data.php @@ -79,9 +79,18 @@ class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme } else { $raw_data = $data; } + if ( strlen($raw_data) < 12 ) { + // error; exif_imagetype throws exception with small files, + // and this likely indicates a corrupt URI/failed parse anyway + return false; + } // XXX probably want to refactor this into a general mechanism // for filtering arbitrary content types - $file = tempnam("/tmp", ""); + if (function_exists('sys_get_temp_dir')) { + $file = tempnam(sys_get_temp_dir(), ""); + } else { + $file = tempnam("/tmp", ""); + } file_put_contents($file, $raw_data); if (function_exists('exif_imagetype')) { $image_code = exif_imagetype($file); diff --git a/lib/htmlpurifier/HTMLPurifier/URIScheme/tel.php b/lib/htmlpurifier/HTMLPurifier/URIScheme/tel.php new file mode 100644 index 00000000000..8cd1933527b --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/URIScheme/tel.php @@ -0,0 +1,46 @@ +userinfo = null; + $uri->host = null; + $uri->port = null; + + // Delete all non-numeric characters, non-x characters + // from phone number, EXCEPT for a leading plus sign. + $uri->path = preg_replace('/(?!^\+)[^\dx]/', '', + // Normalize e(x)tension to lower-case + str_replace('X', 'x', $uri->path)); + + return true; + } +} + +// vim: et sw=4 sts=4