Permalink
Browse files

Merge branch 'w15_MDL-25826_m20_htmlpurifier' of git://github.com/sko…

…dak/moodle into MOODLE_20_STABLE
  • Loading branch information...
2 parents 465846f + 60aaf1d commit 8fa13d155d2b3bd51cbc3689b3c46faafaaf668b @stronk7 stronk7 committed Apr 11, 2011
Showing with 1,001 additions and 250 deletions.
  1. +3 −3 lib/htmlpurifier/HTMLPurifier.php
  2. +2 −0 lib/htmlpurifier/HTMLPurifier.safe-includes.php
  3. +138 −13 lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php
  4. +9 −0 lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/URI.php
  5. +6 −0 lib/htmlpurifier/HTMLPurifier/AttrDef/URI/Host.php
  6. +41 −0 lib/htmlpurifier/HTMLPurifier/AttrTransform/Nofollow.php
  7. +2 −1 lib/htmlpurifier/HTMLPurifier/AttrTransform/SafeParam.php
  8. +8 −2 lib/htmlpurifier/HTMLPurifier/Bootstrap.php
  9. +21 −0 lib/htmlpurifier/HTMLPurifier/CSSDefinition.php
  10. +181 −52 lib/htmlpurifier/HTMLPurifier/Config.php
  11. +7 −1 lib/htmlpurifier/HTMLPurifier/ConfigSchema.php
  12. BIN lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema.ser
  13. +12 −0 lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt
  14. +9 −0 lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt
  15. +11 −0 lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt
  16. +7 −0 lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt
  17. +1 −0 lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt
  18. +15 −0 lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt
  19. +11 −0 lib/htmlpurifier/HTMLPurifier/Definition.php
  20. +24 −11 lib/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer.php
  21. 0 lib/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/README
  22. +1 −1 lib/htmlpurifier/HTMLPurifier/EntityLookup/entities.ser
  23. +38 −13 lib/htmlpurifier/HTMLPurifier/Generator.php
  24. +19 −0 lib/htmlpurifier/HTMLPurifier/HTMLModule/Nofollow.php
  25. +1 −1 lib/htmlpurifier/HTMLPurifier/HTMLModule/SafeEmbed.php
  26. +0 −1 lib/htmlpurifier/HTMLPurifier/HTMLModule/SafeObject.php
  27. +4 −4 lib/htmlpurifier/HTMLPurifier/HTMLModuleManager.php
  28. +3 −3 lib/htmlpurifier/HTMLPurifier/Lexer.php
  29. +52 −22 lib/htmlpurifier/HTMLPurifier/Lexer/DOMLex.php
  30. +79 −22 lib/htmlpurifier/HTMLPurifier/Strategy/MakeWellFormed.php
  31. +9 −7 lib/htmlpurifier/HTMLPurifier/TagTransform/Font.php
  32. +2 −1 lib/htmlpurifier/HTMLPurifier/Token/Tag.php
  33. +62 −31 lib/htmlpurifier/HTMLPurifier/URI.php
  34. +54 −7 lib/htmlpurifier/HTMLPurifier/URIScheme.php
  35. +4 −1 lib/htmlpurifier/HTMLPurifier/URIScheme/data.php
  36. +8 −2 lib/htmlpurifier/HTMLPurifier/URIScheme/file.php
  37. +1 −2 lib/htmlpurifier/HTMLPurifier/URIScheme/ftp.php
  38. +1 −2 lib/htmlpurifier/HTMLPurifier/URIScheme/http.php
  39. +2 −2 lib/htmlpurifier/HTMLPurifier/URIScheme/mailto.php
  40. +2 −2 lib/htmlpurifier/HTMLPurifier/URIScheme/news.php
  41. +1 −2 lib/htmlpurifier/HTMLPurifier/URIScheme/nntp.php
  42. +2 −3 lib/htmlpurifier/readme_moodle.txt
  43. +133 −0 lib/simpletest/testpurifier.php
  44. +0 −25 lib/simpletest/testweblib.php
  45. +1 −1 lib/thirdpartylibs.xml
  46. +14 −12 lib/weblib.php
@@ -19,7 +19,7 @@
*/
/*
- HTML Purifier 4.2.0 - Standards Compliant HTML Filtering
+ HTML Purifier 4.3.0 - Standards Compliant HTML Filtering
Copyright (C) 2006-2008 Edward Z. Yang
This library is free software; you can redistribute it and/or
@@ -55,10 +55,10 @@ class HTMLPurifier
{
/** Version of HTML Purifier */
- public $version = '4.2.0';
+ public $version = '4.3.0';
/** Constant with version of HTML Purifier */
- const VERSION = '4.2.0';
+ const VERSION = '4.3.0';
/** Global configuration object */
public $config;
@@ -119,6 +119,7 @@
require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/NameSync.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/Nofollow.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeEmbed.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php';
@@ -145,6 +146,7 @@
require_once $__dir . '/HTMLPurifier/HTMLModule/Legacy.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/List.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Name.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Nofollow.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Object.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php';
@@ -2,11 +2,43 @@
/**
* Validates a font family list according to CSS spec
- * @todo whitelisting allowed fonts would be nice
*/
class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
{
+ protected $mask = null;
+
+ public function __construct() {
+ $this->mask = '- ';
+ for ($c = 'a'; $c <= 'z'; $c++) $this->mask .= $c;
+ for ($c = 'A'; $c <= 'Z'; $c++) $this->mask .= $c;
+ for ($c = '0'; $c <= '9'; $c++) $this->mask .= $c; // cast-y, but should be fine
+ // special bytes used by UTF-8
+ for ($i = 0x80; $i <= 0xFF; $i++) {
+ // We don't bother excluding invalid bytes in this range,
+ // because the our restriction of well-formed UTF-8 will
+ // prevent these from ever occurring.
+ $this->mask .= chr($i);
+ }
+
+ /*
+ PHP's internal strcspn implementation is
+ O(length of string * length of mask), making it inefficient
+ for large masks. However, it's still faster than
+ preg_match 8)
+ for (p = s1;;) {
+ spanp = s2;
+ do {
+ if (*spanp == c || p == s1_end) {
+ return p - s1;
+ }
+ } while (spanp++ < (s2_end - 1));
+ c = *++p;
+ }
+ */
+ // possible optimization: invert the mask.
+ }
+
public function validate($string, $config, $context) {
static $generic_names = array(
'serif' => true,
@@ -15,6 +47,7 @@ public function validate($string, $config, $context) {
'fantasy' => true,
'cursive' => true
);
+ $allowed_fonts = $config->get('CSS.AllowedFonts');
// assume that no font names contain commas in them
$fonts = explode(',', $string);
@@ -24,7 +57,9 @@ public function validate($string, $config, $context) {
if ($font === '') continue;
// match a generic name
if (isset($generic_names[$font])) {
- $final .= $font . ', ';
+ if ($allowed_fonts === null || isset($allowed_fonts[$font])) {
+ $final .= $font . ', ';
+ }
continue;
}
// match a quoted name
@@ -40,6 +75,10 @@ public function validate($string, $config, $context) {
// $font is a pure representation of the font name
+ if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) {
+ continue;
+ }
+
if (ctype_alnum($font) && $font !== '') {
// very simple font, allow it in unharmed
$final .= $font . ', ';
@@ -50,17 +89,103 @@ public function validate($string, $config, $context) {
// shouldn't show up regardless
$font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font);
- // These ugly transforms don't pose a security
- // risk (as \\ and \" might). We could try to be clever and
- // use single-quote wrapping when there is a double quote
- // present, but I have choosen not to implement that.
- // (warning: this code relies on the selection of quotation
- // mark below)
- $font = str_replace('\\', '\\5C ', $font);
- $font = str_replace('"', '\\22 ', $font);
-
- // complicated font, requires quoting
- $final .= "\"$font\", "; // note that this will later get turned into &quot;
+ // Here, there are various classes of characters which need
+ // to be treated differently:
+ // - Alphanumeric characters are essentially safe. We
+ // handled these above.
+ // - Spaces require quoting, though most parsers will do
+ // the right thing if there aren't any characters that
+ // can be misinterpreted
+ // - Dashes rarely occur, but they fairly unproblematic
+ // for parsing/rendering purposes.
+ // The above characters cover the majority of Western font
+ // names.
+ // - Arbitrary Unicode characters not in ASCII. Because
+ // most parsers give little thought to Unicode, treatment
+ // of these codepoints is basically uniform, even for
+ // punctuation-like codepoints. These characters can
+ // show up in non-Western pages and are supported by most
+ // major browsers, for example: "MS 明朝" is a
+ // legitimate font-name
+ // <http://ja.wikipedia.org/wiki/MS_明朝>. See
+ // the CSS3 spec for more examples:
+ // <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png>
+ // You can see live samples of these on the Internet:
+ // <http://www.google.co.jp/search?q=font-family+MS+明朝|ゴシック>
+ // However, most of these fonts have ASCII equivalents:
+ // for example, 'MS Mincho', and it's considered
+ // professional to use ASCII font names instead of
+ // Unicode font names. Thanks Takeshi Terada for
+ // providing this information.
+ // The following characters, to my knowledge, have not been
+ // used to name font names.
+ // - Single quote. While theoretically you might find a
+ // font name that has a single quote in its name (serving
+ // as an apostrophe, e.g. Dave's Scribble), I haven't
+ // been able to find any actual examples of this.
+ // Internet Explorer's cssText translation (which I
+ // believe is invoked by innerHTML) normalizes any
+ // quoting to single quotes, and fails to escape single
+ // quotes. (Note that this is not IE's behavior for all
+ // CSS properties, just some sort of special casing for
+ // font-family). So a single quote *cannot* be used
+ // safely in the font-family context if there will be an
+ // innerHTML/cssText translation. Note that Firefox 3.x
+ // does this too.
+ // - Double quote. In IE, these get normalized to
+ // single-quotes, no matter what the encoding. (Fun
+ // fact, in IE8, the 'content' CSS property gained
+ // support, where they special cased to preserve encoded
+ // double quotes, but still translate unadorned double
+ // quotes into single quotes.) So, because their
+ // fixpoint behavior is identical to single quotes, they
+ // cannot be allowed either. Firefox 3.x displays
+ // single-quote style behavior.
+ // - Backslashes are reduced by one (so \\ -> \) every
+ // iteration, so they cannot be used safely. This shows
+ // up in IE7, IE8 and FF3
+ // - Semicolons, commas and backticks are handled properly.
+ // - The rest of the ASCII punctuation is handled properly.
+ // We haven't checked what browsers do to unadorned
+ // versions, but this is not important as long as the
+ // browser doesn't /remove/ surrounding quotes (as IE does
+ // for HTML).
+ //
+ // With these results in hand, we conclude that there are
+ // various levels of safety:
+ // - Paranoid: alphanumeric, spaces and dashes(?)
+ // - International: Paranoid + non-ASCII Unicode
+ // - Edgy: Everything except quotes, backslashes
+ // - NoJS: Standards compliance, e.g. sod IE. Note that
+ // with some judicious character escaping (since certain
+ // types of escaping doesn't work) this is theoretically
+ // OK as long as innerHTML/cssText is not called.
+ // We believe that international is a reasonable default
+ // (that we will implement now), and once we do more
+ // extensive research, we may feel comfortable with dropping
+ // it down to edgy.
+
+ // Edgy: alphanumeric, spaces, dashes and Unicode. Use of
+ // str(c)spn assumes that the string was already well formed
+ // Unicode (which of course it is).
+ if (strspn($font, $this->mask) !== strlen($font)) {
+ continue;
+ }
+
+ // Historical:
+ // In the absence of innerHTML/cssText, these ugly
+ // transforms don't pose a security risk (as \\ and \"
+ // might--these escapes are not supported by most browsers).
+ // We could try to be clever and use single-quote wrapping
+ // when there is a double quote present, but I have choosen
+ // not to implement that. (NOTE: you can reduce the amount
+ // of escapes by one depending on what quoting style you use)
+ // $font = str_replace('\\', '\\5C ', $font);
+ // $font = str_replace('"', '\\22 ', $font);
+ // $font = str_replace("'", '\\27 ', $font);
+
+ // font possibly with spaces, requires quoting
+ $final .= "'$font', ";
}
$final = rtrim($final, ', ');
if ($final === '') return false;
@@ -43,6 +43,15 @@ public function validate($uri_string, $config, $context) {
// extra sanity check; should have been done by URI
$result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result);
+ // suspicious characters are ()'; we're going to percent encode
+ // them for safety.
+ $result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result);
+
+ // there's an extra bug where ampersands lose their escaping on
+ // an innerHTML cycle, so a very unlucky query parameter could
+ // then change the meaning of the URL. Unfortunately, there's
+ // not much we can do about that...
+
return "url(\"$result\")";
}
@@ -23,6 +23,12 @@ public function __construct() {
public function validate($string, $config, $context) {
$length = strlen($string);
+ // empty hostname is OK; it's usually semantically equivalent:
+ // the default host as defined by a URI scheme is used:
+ //
+ // If the URI scheme defines a default for host, then that
+ // default applies when the host subcomponent is undefined
+ // or when the registered name is empty (zero length).
if ($string === '') return '';
if ($length > 1 && $string[0] === '[' && $string[$length-1] === ']') {
//IPv6
@@ -0,0 +1,41 @@
+<?php
+
+// must be called POST validation
+
+/**
+ * Adds rel="nofollow" to all outbound links. This transform is
+ * only attached if Attr.Nofollow is TRUE.
+ */
+class HTMLPurifier_AttrTransform_Nofollow extends HTMLPurifier_AttrTransform
+{
+ private $parser;
+
+ public function __construct() {
+ $this->parser = new HTMLPurifier_URIParser();
+ }
+
+ public function transform($attr, $config, $context) {
+
+ if (!isset($attr['href'])) {
+ return $attr;
+ }
+
+ // XXX Kind of inefficient
+ $url = $this->parser->parse($attr['href']);
+ $scheme = $url->getSchemeObj($config, $context);
+
+ if (!is_null($url->host) && $scheme !== false && $scheme->browsable) {
+ if (isset($attr['rel'])) {
+ $attr['rel'] .= ' nofollow';
+ } else {
+ $attr['rel'] = 'nofollow';
+ }
+ }
+
+ return $attr;
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
@@ -19,6 +19,7 @@ class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
public function __construct() {
$this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
+ $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent'));
}
public function transform($attr, $config, $context) {
@@ -41,7 +42,7 @@ public function transform($attr, $config, $context) {
}
break;
case 'wmode':
- $attr['value'] = 'window';
+ $attr['value'] = $this->wmode->validate($attr['value'], $config, $context);
break;
case 'movie':
case 'src':
@@ -37,7 +37,12 @@ class HTMLPurifier_Bootstrap
public static function autoload($class) {
$file = HTMLPurifier_Bootstrap::getPath($class);
if (!$file) return false;
- require HTMLPURIFIER_PREFIX . '/' . $file;
+ // Technically speaking, it should be ok and more efficient to
+ // just do 'require', but Antonio Parraga reports that with
+ // Zend extensions such as Zend debugger and APC, this invariant
+ // may be broken. Since we have efficient alternatives, pay
+ // the cost here and avoid the bug.
+ require_once HTMLPURIFIER_PREFIX . '/' . $file;
return true;
}
@@ -65,10 +70,11 @@ public static function registerAutoload() {
if ( ($funcs = spl_autoload_functions()) === false ) {
spl_autoload_register($autoload);
} elseif (function_exists('spl_autoload_unregister')) {
+ $buggy = version_compare(PHP_VERSION, '5.2.11', '<');
$compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
version_compare(PHP_VERSION, '5.1.0', '>=');
foreach ($funcs as $func) {
- if (is_array($func)) {
+ if ($buggy && is_array($func)) {
// :TRICKY: There are some compatibility issues and some
// places where we need to error out
$reflector = new ReflectionMethod($func[0], $func[1]);
@@ -219,6 +219,10 @@ protected function doSetup($config) {
$this->doSetupTricky($config);
}
+ if ($config->get('CSS.Trusted')) {
+ $this->doSetupTrusted($config);
+ }
+
$allow_important = $config->get('CSS.AllowImportant');
// wrap all attr-defs with decorator that handles !important
foreach ($this->info as $k => $v) {
@@ -260,6 +264,23 @@ protected function doSetupTricky($config) {
$this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
}
+ protected function doSetupTrusted($config) {
+ $this->info['position'] = new HTMLPurifier_AttrDef_Enum(array(
+ 'static', 'relative', 'absolute', 'fixed'
+ ));
+ $this->info['top'] =
+ $this->info['left'] =
+ $this->info['right'] =
+ $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+ new HTMLPurifier_AttrDef_CSS_Length(),
+ new HTMLPurifier_AttrDef_CSS_Percentage(),
+ new HTMLPurifier_AttrDef_Enum(array('auto')),
+ ));
+ $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+ new HTMLPurifier_AttrDef_Integer(),
+ new HTMLPurifier_AttrDef_Enum(array('auto')),
+ ));
+ }
/**
* Performs extra config-based processing. Based off of
Oops, something went wrong.

0 comments on commit 8fa13d1

Please sign in to comment.