diff --git a/NEWS b/NEWS index 2313d9e0..dc8f6787 100644 --- a/NEWS +++ b/NEWS @@ -12,14 +12,19 @@ NEWS ( CHANGELOG and HISTORY ) HTMLPurifier 3.1.1, unknown release date ! More robust imagecrash protection with height/width CSS with %CSS.MaxImgLength, and height/width HTML with %HTML.MaxImgLength. -! %URI.SecureMunge for secure URI munging (as opposed to %URI.Munge). Be sure - to set %URI.SecureMungeSecretKey when using this directive. Thanks Chris - for sponsoring this feature. +! %URI.SecureMunge for secure URI munging (as opposed to %URI.Munge). Thanks Chris + for sponsoring this feature. Check out the corresponding documentation + for details. (Att Nightly testers: The API for this feature changed before + the general release. Namely, rename your directives %URI.SecureMungeSecretKey => + %URI.MungeSecretKey and and %URI.SecureMunge => %URI.Munge) ! Implemented post URI filtering. Set member variable $post to true to set a URIFilter as such. ! Allow modules to define injectors via $info_injector. Injectors are automatically disabled if injector's needed elements are not found. ! Support for "safe" objects added, use %HTML.SafeObject and %HTML.SafeEmbed. +! Added substitutions for %e, %n, %a and %p in %URI.Munge (in order, + embedded, tag name, attribute name, CSS property name). See %URI.Munge + for more details. - Disable percent height/width attributes for img - AttrValidator operations are now atomic; updates to attributes are not manifest in token until end of operations. This prevents naughty internal @@ -67,6 +72,8 @@ NEWS ( CHANGELOG and HISTORY ) HTMLPurifier . URIFilter->prepare can return false in order to abort loading of the filter . Factory for AttrDef_URI implemented, URI#embedded to indicate URI that embeds an external resource. +. %URI.Munge functionality factored out into a post-filter class. +. Added CurrentCSSProperty context variable during CSS validation 3.1.0, released 2008-05-18 # Unnecessary references to objects (vestiges of PHP4) removed from method diff --git a/configdoc/usage.xml b/configdoc/usage.xml index 4e1e6fde..9b8fc319 100644 --- a/configdoc/usage.xml +++ b/configdoc/usage.xml @@ -236,11 +236,6 @@ 28 - - - 77 - - 12 @@ -387,14 +382,14 @@ 8 - - - 9 + + + 14 - - - 10 + + + 15 diff --git a/library/HTMLPurifier.includes.php b/library/HTMLPurifier.includes.php index 61fabc5b..5ba2615e 100644 --- a/library/HTMLPurifier.includes.php +++ b/library/HTMLPurifier.includes.php @@ -184,7 +184,7 @@ require 'HTMLPurifier/URIFilter/DisableExternal.php'; require 'HTMLPurifier/URIFilter/DisableExternalResources.php'; require 'HTMLPurifier/URIFilter/HostBlacklist.php'; require 'HTMLPurifier/URIFilter/MakeAbsolute.php'; -require 'HTMLPurifier/URIFilter/SecureMunge.php'; +require 'HTMLPurifier/URIFilter/Munge.php'; require 'HTMLPurifier/URIScheme/ftp.php'; require 'HTMLPurifier/URIScheme/http.php'; require 'HTMLPurifier/URIScheme/https.php'; diff --git a/library/HTMLPurifier.safe-includes.php b/library/HTMLPurifier.safe-includes.php index bb43c0c4..42acd76b 100644 --- a/library/HTMLPurifier.safe-includes.php +++ b/library/HTMLPurifier.safe-includes.php @@ -178,7 +178,7 @@ require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternal.php'; require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternalResources.php'; require_once $__dir . '/HTMLPurifier/URIFilter/HostBlacklist.php'; require_once $__dir . '/HTMLPurifier/URIFilter/MakeAbsolute.php'; -require_once $__dir . '/HTMLPurifier/URIFilter/SecureMunge.php'; +require_once $__dir . '/HTMLPurifier/URIFilter/Munge.php'; require_once $__dir . '/HTMLPurifier/URIScheme/ftp.php'; require_once $__dir . '/HTMLPurifier/URIScheme/http.php'; require_once $__dir . '/HTMLPurifier/URIScheme/https.php'; diff --git a/library/HTMLPurifier/AttrDef/CSS.php b/library/HTMLPurifier/AttrDef/CSS.php index 128158e1..53afaf08 100644 --- a/library/HTMLPurifier/AttrDef/CSS.php +++ b/library/HTMLPurifier/AttrDef/CSS.php @@ -29,6 +29,12 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef $declarations = explode(';', $css); $propvalues = array(); + /** + * Name of the current CSS property being validated. + */ + $property = false; + $context->register('CurrentCSSProperty', $property); + foreach ($declarations as $declaration) { if (!$declaration) continue; if (!strpos($declaration, ':')) continue; @@ -61,6 +67,8 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef $propvalues[$property] = $result; } + $context->destroy('CurrentCSSProperty'); + // procedure does not write the new CSS simultaneously, so it's // slightly inefficient, but it's the only way of getting rid of // duplicates. Perhaps config to optimize it, but not now. diff --git a/library/HTMLPurifier/AttrDef/URI.php b/library/HTMLPurifier/AttrDef/URI.php index f63edcef..b814e9ee 100644 --- a/library/HTMLPurifier/AttrDef/URI.php +++ b/library/HTMLPurifier/AttrDef/URI.php @@ -68,18 +68,7 @@ class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef if (!$ok) return false; // back to string - $result = $uri->toString(); - - // munge entire URI if necessary - if ( - !is_null($uri->host) && // indicator for authority - !empty($scheme_obj->browsable) && - !is_null($munge = $config->get('URI', 'Munge')) - ) { - $result = str_replace('%s', rawurlencode($result), $munge); - } - - return $result; + return $uri->toString(); } diff --git a/library/HTMLPurifier/ConfigSchema/schema.ser b/library/HTMLPurifier/ConfigSchema/schema.ser index 63db7787..1af702b7 100644 --- a/library/HTMLPurifier/ConfigSchema/schema.ser +++ b/library/HTMLPurifier/ConfigSchema/schema.ser @@ -1 +1 @@ -O:25:"HTMLPurifier_ConfigSchema":2:{s:8:"defaults";a:12:{s:4:"Attr";a:11:{s:19:"AllowedFrameTargets";a:0:{}s:10:"AllowedRel";a:0:{}s:10:"AllowedRev";a:0:{}s:19:"DefaultInvalidImage";s:0:"";s:22:"DefaultInvalidImageAlt";s:13:"Invalid image";s:14:"DefaultTextDir";s:3:"ltr";s:8:"EnableID";b:0;s:11:"IDBlacklist";a:0:{}s:17:"IDBlacklistRegexp";N;s:8:"IDPrefix";s:0:"";s:13:"IDPrefixLocal";s:0:"";}s:10:"AutoFormat";a:4:{s:13:"AutoParagraph";b:0;s:6:"Custom";a:0:{}s:7:"Linkify";b:0;s:15:"PurifierLinkify";b:0;}s:15:"AutoFormatParam";a:1:{s:21:"PurifierLinkifyDocURL";s:3:"#%s";}s:3:"CSS";a:6:{s:14:"AllowImportant";b:0;s:11:"AllowTricky";b:0;s:17:"AllowedProperties";N;s:13:"DefinitionRev";i:1;s:12:"MaxImgLength";s:6:"1200px";s:11:"Proprietary";b:0;}s:5:"Cache";a:2:{s:14:"DefinitionImpl";s:10:"Serializer";s:14:"SerializerPath";N;}s:4:"Core";a:15:{s:17:"AggressivelyFixLt";b:0;s:13:"CollectErrors";b:0;s:13:"ColorKeywords";a:17:{s:6:"maroon";s:7:"#800000";s:3:"red";s:7:"#FF0000";s:6:"orange";s:7:"#FFA500";s:6:"yellow";s:7:"#FFFF00";s:5:"olive";s:7:"#808000";s:6:"purple";s:7:"#800080";s:7:"fuchsia";s:7:"#FF00FF";s:5:"white";s:7:"#FFFFFF";s:4:"lime";s:7:"#00FF00";s:5:"green";s:7:"#008000";s:4:"navy";s:7:"#000080";s:4:"blue";s:7:"#0000FF";s:4:"aqua";s:7:"#00FFFF";s:4:"teal";s:7:"#008080";s:5:"black";s:7:"#000000";s:6:"silver";s:7:"#C0C0C0";s:4:"gray";s:7:"#808080";}s:25:"ConvertDocumentToFragment";b:1;s:31:"DirectLexLineNumberSyncInterval";i:0;s:8:"Encoding";s:5:"utf-8";s:21:"EscapeInvalidChildren";b:0;s:17:"EscapeInvalidTags";b:0;s:24:"EscapeNonASCIICharacters";b:0;s:14:"HiddenElements";a:2:{s:6:"script";b:1;s:5:"style";b:1;}s:8:"Language";s:2:"en";s:9:"LexerImpl";N;s:19:"MaintainLineNumbers";N;s:16:"RemoveInvalidImg";b:1;s:20:"RemoveScriptContents";N;}s:6:"Filter";a:3:{s:6:"Custom";a:0:{}s:18:"ExtractStyleBlocks";b:0;s:7:"YouTube";b:0;}s:11:"FilterParam";a:3:{s:26:"ExtractStyleBlocksEscaping";b:1;s:23:"ExtractStyleBlocksScope";N;s:26:"ExtractStyleBlocksTidyImpl";N;}s:4:"HTML";a:23:{s:7:"Allowed";N;s:17:"AllowedAttributes";N;s:15:"AllowedElements";N;s:14:"AllowedModules";N;s:12:"BlockWrapper";s:1:"p";s:11:"CoreModules";a:7:{s:9:"Structure";b:1;s:4:"Text";b:1;s:9:"Hypertext";b:1;s:4:"List";b:1;s:22:"NonXMLCommonAttributes";b:1;s:19:"XMLCommonAttributes";b:1;s:16:"CommonAttributes";b:1;}s:13:"CustomDoctype";N;s:12:"DefinitionID";N;s:13:"DefinitionRev";i:1;s:7:"Doctype";N;s:19:"ForbiddenAttributes";a:0:{}s:17:"ForbiddenElements";a:0:{}s:12:"MaxImgLength";i:1200;s:6:"Parent";s:3:"div";s:11:"Proprietary";b:0;s:9:"SafeEmbed";b:0;s:10:"SafeObject";b:0;s:6:"Strict";b:0;s:7:"TidyAdd";a:0:{}s:9:"TidyLevel";s:6:"medium";s:10:"TidyRemove";a:0:{}s:7:"Trusted";b:0;s:5:"XHTML";b:1;}s:6:"Output";a:3:{s:21:"CommentScriptContents";b:1;s:7:"Newline";N;s:10:"TidyFormat";b:0;}s:4:"Test";a:1:{s:12:"ForceNoIconv";b:0;}s:3:"URI";a:16:{s:14:"AllowedSchemes";a:6:{s:4:"http";b:1;s:5:"https";b:1;s:6:"mailto";b:1;s:3:"ftp";b:1;s:4:"nntp";b:1;s:4:"news";b:1;}s:4:"Base";N;s:13:"DefaultScheme";s:4:"http";s:12:"DefinitionID";N;s:13:"DefinitionRev";i:1;s:7:"Disable";b:0;s:15:"DisableExternal";b:0;s:24:"DisableExternalResources";b:0;s:16:"DisableResources";b:0;s:4:"Host";N;s:13:"HostBlacklist";a:0:{}s:12:"MakeAbsolute";b:0;s:5:"Munge";N;s:22:"OverrideAllowedSchemes";b:1;s:11:"SecureMunge";N;s:20:"SecureMungeSecretKey";N;}}s:4:"info";a:12:{s:4:"Attr";a:12:{s:19:"AllowedFrameTargets";i:8;s:10:"AllowedRel";i:8;s:10:"AllowedRev";i:8;s:19:"DefaultInvalidImage";i:1;s:22:"DefaultInvalidImageAlt";i:1;s:14:"DefaultTextDir";O:8:"stdClass":2:{s:4:"type";i:1;s:7:"allowed";a:2:{s:3:"ltr";b:1;s:3:"rtl";b:1;}}s:8:"EnableID";i:7;s:11:"IDBlacklist";i:9;s:17:"IDBlacklistRegexp";i:-1;s:8:"IDPrefix";i:1;s:13:"IDPrefixLocal";i:1;s:10:"DisableURI";O:8:"stdClass":3:{s:9:"namespace";s:3:"URI";s:4:"name";s:7:"Disable";s:7:"isAlias";b:1;}}s:10:"AutoFormat";a:4:{s:13:"AutoParagraph";i:7;s:6:"Custom";i:9;s:7:"Linkify";i:7;s:15:"PurifierLinkify";i:7;}s:15:"AutoFormatParam";a:1:{s:21:"PurifierLinkifyDocURL";i:1;}s:3:"CSS";a:6:{s:14:"AllowImportant";i:7;s:11:"AllowTricky";i:7;s:17:"AllowedProperties";i:-8;s:13:"DefinitionRev";i:5;s:12:"MaxImgLength";i:-1;s:11:"Proprietary";i:7;}s:5:"Cache";a:2:{s:14:"DefinitionImpl";i:-1;s:14:"SerializerPath";i:-1;}s:4:"Core";a:20:{s:15:"DefinitionCache";O:8:"stdClass":3:{s:9:"namespace";s:5:"Cache";s:4:"name";s:14:"DefinitionImpl";s:7:"isAlias";b:1;}s:17:"AggressivelyFixLt";i:7;s:13:"CollectErrors";i:7;s:13:"ColorKeywords";i:10;s:25:"ConvertDocumentToFragment";i:7;s:19:"AcceptFullDocuments";O:8:"stdClass":3:{s:9:"namespace";s:4:"Core";s:4:"name";s:25:"ConvertDocumentToFragment";s:7:"isAlias";b:1;}s:31:"DirectLexLineNumberSyncInterval";i:5;s:8:"Encoding";i:2;s:21:"EscapeInvalidChildren";i:7;s:17:"EscapeInvalidTags";i:7;s:24:"EscapeNonASCIICharacters";i:7;s:14:"HiddenElements";i:8;s:8:"Language";i:1;s:9:"LexerImpl";i:-11;s:19:"MaintainLineNumbers";i:-7;s:16:"RemoveInvalidImg";i:7;s:20:"RemoveScriptContents";i:-7;s:5:"XHTML";O:8:"stdClass":3:{s:9:"namespace";s:4:"HTML";s:4:"name";s:5:"XHTML";s:7:"isAlias";b:1;}s:21:"CommentScriptContents";O:8:"stdClass":3:{s:9:"namespace";s:6:"Output";s:4:"name";s:21:"CommentScriptContents";s:7:"isAlias";b:1;}s:10:"TidyFormat";O:8:"stdClass":3:{s:9:"namespace";s:6:"Output";s:4:"name";s:10:"TidyFormat";s:7:"isAlias";b:1;}}s:6:"Filter";a:5:{s:6:"Custom";i:9;s:18:"ExtractStyleBlocks";i:7;s:7:"YouTube";i:7;s:26:"ExtractStyleBlocksEscaping";O:8:"stdClass":3:{s:9:"namespace";s:11:"FilterParam";s:4:"name";s:26:"ExtractStyleBlocksEscaping";s:7:"isAlias";b:1;}s:23:"ExtractStyleBlocksScope";O:8:"stdClass":3:{s:9:"namespace";s:11:"FilterParam";s:4:"name";s:23:"ExtractStyleBlocksScope";s:7:"isAlias";b:1;}}s:11:"FilterParam";a:3:{s:26:"ExtractStyleBlocksEscaping";i:7;s:23:"ExtractStyleBlocksScope";i:-1;s:26:"ExtractStyleBlocksTidyImpl";i:-11;}s:4:"HTML";a:24:{s:12:"EnableAttrID";O:8:"stdClass":3:{s:9:"namespace";s:4:"Attr";s:4:"name";s:8:"EnableID";s:7:"isAlias";b:1;}s:7:"Allowed";i:-4;s:17:"AllowedAttributes";i:-8;s:15:"AllowedElements";i:-8;s:14:"AllowedModules";i:-8;s:12:"BlockWrapper";i:1;s:11:"CoreModules";i:8;s:13:"CustomDoctype";i:-1;s:12:"DefinitionID";i:-1;s:13:"DefinitionRev";i:5;s:7:"Doctype";O:8:"stdClass":3:{s:4:"type";i:1;s:10:"allow_null";b:1;s:7:"allowed";a:5:{s:22:"HTML 4.01 Transitional";b:1;s:16:"HTML 4.01 Strict";b:1;s:22:"XHTML 1.0 Transitional";b:1;s:16:"XHTML 1.0 Strict";b:1;s:9:"XHTML 1.1";b:1;}}s:19:"ForbiddenAttributes";i:8;s:17:"ForbiddenElements";i:8;s:12:"MaxImgLength";i:-5;s:6:"Parent";i:1;s:11:"Proprietary";i:7;s:9:"SafeEmbed";i:7;s:10:"SafeObject";i:7;s:6:"Strict";i:7;s:7:"TidyAdd";i:8;s:9:"TidyLevel";O:8:"stdClass":2:{s:4:"type";i:1;s:7:"allowed";a:4:{s:4:"none";b:1;s:5:"light";b:1;s:6:"medium";b:1;s:5:"heavy";b:1;}}s:10:"TidyRemove";i:8;s:7:"Trusted";i:7;s:5:"XHTML";i:7;}s:6:"Output";a:3:{s:21:"CommentScriptContents";i:7;s:7:"Newline";i:-1;s:10:"TidyFormat";i:7;}s:4:"Test";a:1:{s:12:"ForceNoIconv";i:7;}s:3:"URI";a:16:{s:14:"AllowedSchemes";i:8;s:4:"Base";i:-1;s:13:"DefaultScheme";i:1;s:12:"DefinitionID";i:-1;s:13:"DefinitionRev";i:5;s:7:"Disable";i:7;s:15:"DisableExternal";i:7;s:24:"DisableExternalResources";i:7;s:16:"DisableResources";i:7;s:4:"Host";i:-1;s:13:"HostBlacklist";i:9;s:12:"MakeAbsolute";i:7;s:5:"Munge";i:-1;s:22:"OverrideAllowedSchemes";i:7;s:11:"SecureMunge";i:-1;s:20:"SecureMungeSecretKey";i:-1;}}} \ No newline at end of file +O:25:"HTMLPurifier_ConfigSchema":2:{s:8:"defaults";a:12:{s:4:"Attr";a:11:{s:19:"AllowedFrameTargets";a:0:{}s:10:"AllowedRel";a:0:{}s:10:"AllowedRev";a:0:{}s:19:"DefaultInvalidImage";s:0:"";s:22:"DefaultInvalidImageAlt";s:13:"Invalid image";s:14:"DefaultTextDir";s:3:"ltr";s:8:"EnableID";b:0;s:11:"IDBlacklist";a:0:{}s:17:"IDBlacklistRegexp";N;s:8:"IDPrefix";s:0:"";s:13:"IDPrefixLocal";s:0:"";}s:10:"AutoFormat";a:4:{s:13:"AutoParagraph";b:0;s:6:"Custom";a:0:{}s:7:"Linkify";b:0;s:15:"PurifierLinkify";b:0;}s:15:"AutoFormatParam";a:1:{s:21:"PurifierLinkifyDocURL";s:3:"#%s";}s:3:"CSS";a:6:{s:14:"AllowImportant";b:0;s:11:"AllowTricky";b:0;s:17:"AllowedProperties";N;s:13:"DefinitionRev";i:1;s:12:"MaxImgLength";s:6:"1200px";s:11:"Proprietary";b:0;}s:5:"Cache";a:2:{s:14:"DefinitionImpl";s:10:"Serializer";s:14:"SerializerPath";N;}s:4:"Core";a:15:{s:17:"AggressivelyFixLt";b:0;s:13:"CollectErrors";b:0;s:13:"ColorKeywords";a:17:{s:6:"maroon";s:7:"#800000";s:3:"red";s:7:"#FF0000";s:6:"orange";s:7:"#FFA500";s:6:"yellow";s:7:"#FFFF00";s:5:"olive";s:7:"#808000";s:6:"purple";s:7:"#800080";s:7:"fuchsia";s:7:"#FF00FF";s:5:"white";s:7:"#FFFFFF";s:4:"lime";s:7:"#00FF00";s:5:"green";s:7:"#008000";s:4:"navy";s:7:"#000080";s:4:"blue";s:7:"#0000FF";s:4:"aqua";s:7:"#00FFFF";s:4:"teal";s:7:"#008080";s:5:"black";s:7:"#000000";s:6:"silver";s:7:"#C0C0C0";s:4:"gray";s:7:"#808080";}s:25:"ConvertDocumentToFragment";b:1;s:31:"DirectLexLineNumberSyncInterval";i:0;s:8:"Encoding";s:5:"utf-8";s:21:"EscapeInvalidChildren";b:0;s:17:"EscapeInvalidTags";b:0;s:24:"EscapeNonASCIICharacters";b:0;s:14:"HiddenElements";a:2:{s:6:"script";b:1;s:5:"style";b:1;}s:8:"Language";s:2:"en";s:9:"LexerImpl";N;s:19:"MaintainLineNumbers";N;s:16:"RemoveInvalidImg";b:1;s:20:"RemoveScriptContents";N;}s:6:"Filter";a:3:{s:6:"Custom";a:0:{}s:18:"ExtractStyleBlocks";b:0;s:7:"YouTube";b:0;}s:11:"FilterParam";a:3:{s:26:"ExtractStyleBlocksEscaping";b:1;s:23:"ExtractStyleBlocksScope";N;s:26:"ExtractStyleBlocksTidyImpl";N;}s:4:"HTML";a:23:{s:7:"Allowed";N;s:17:"AllowedAttributes";N;s:15:"AllowedElements";N;s:14:"AllowedModules";N;s:12:"BlockWrapper";s:1:"p";s:11:"CoreModules";a:7:{s:9:"Structure";b:1;s:4:"Text";b:1;s:9:"Hypertext";b:1;s:4:"List";b:1;s:22:"NonXMLCommonAttributes";b:1;s:19:"XMLCommonAttributes";b:1;s:16:"CommonAttributes";b:1;}s:13:"CustomDoctype";N;s:12:"DefinitionID";N;s:13:"DefinitionRev";i:1;s:7:"Doctype";N;s:19:"ForbiddenAttributes";a:0:{}s:17:"ForbiddenElements";a:0:{}s:12:"MaxImgLength";i:1200;s:6:"Parent";s:3:"div";s:11:"Proprietary";b:0;s:9:"SafeEmbed";b:0;s:10:"SafeObject";b:0;s:6:"Strict";b:0;s:7:"TidyAdd";a:0:{}s:9:"TidyLevel";s:6:"medium";s:10:"TidyRemove";a:0:{}s:7:"Trusted";b:0;s:5:"XHTML";b:1;}s:6:"Output";a:3:{s:21:"CommentScriptContents";b:1;s:7:"Newline";N;s:10:"TidyFormat";b:0;}s:4:"Test";a:1:{s:12:"ForceNoIconv";b:0;}s:3:"URI";a:16:{s:14:"AllowedSchemes";a:6:{s:4:"http";b:1;s:5:"https";b:1;s:6:"mailto";b:1;s:3:"ftp";b:1;s:4:"nntp";b:1;s:4:"news";b:1;}s:4:"Base";N;s:13:"DefaultScheme";s:4:"http";s:12:"DefinitionID";N;s:13:"DefinitionRev";i:1;s:7:"Disable";b:0;s:15:"DisableExternal";b:0;s:24:"DisableExternalResources";b:0;s:16:"DisableResources";b:0;s:4:"Host";N;s:13:"HostBlacklist";a:0:{}s:12:"MakeAbsolute";b:0;s:5:"Munge";N;s:14:"MungeResources";b:0;s:14:"MungeSecretKey";N;s:22:"OverrideAllowedSchemes";b:1;}}s:4:"info";a:12:{s:4:"Attr";a:12:{s:19:"AllowedFrameTargets";i:8;s:10:"AllowedRel";i:8;s:10:"AllowedRev";i:8;s:19:"DefaultInvalidImage";i:1;s:22:"DefaultInvalidImageAlt";i:1;s:14:"DefaultTextDir";O:8:"stdClass":2:{s:4:"type";i:1;s:7:"allowed";a:2:{s:3:"ltr";b:1;s:3:"rtl";b:1;}}s:8:"EnableID";i:7;s:11:"IDBlacklist";i:9;s:17:"IDBlacklistRegexp";i:-1;s:8:"IDPrefix";i:1;s:13:"IDPrefixLocal";i:1;s:10:"DisableURI";O:8:"stdClass":3:{s:9:"namespace";s:3:"URI";s:4:"name";s:7:"Disable";s:7:"isAlias";b:1;}}s:10:"AutoFormat";a:4:{s:13:"AutoParagraph";i:7;s:6:"Custom";i:9;s:7:"Linkify";i:7;s:15:"PurifierLinkify";i:7;}s:15:"AutoFormatParam";a:1:{s:21:"PurifierLinkifyDocURL";i:1;}s:3:"CSS";a:6:{s:14:"AllowImportant";i:7;s:11:"AllowTricky";i:7;s:17:"AllowedProperties";i:-8;s:13:"DefinitionRev";i:5;s:12:"MaxImgLength";i:-1;s:11:"Proprietary";i:7;}s:5:"Cache";a:2:{s:14:"DefinitionImpl";i:-1;s:14:"SerializerPath";i:-1;}s:4:"Core";a:20:{s:15:"DefinitionCache";O:8:"stdClass":3:{s:9:"namespace";s:5:"Cache";s:4:"name";s:14:"DefinitionImpl";s:7:"isAlias";b:1;}s:17:"AggressivelyFixLt";i:7;s:13:"CollectErrors";i:7;s:13:"ColorKeywords";i:10;s:25:"ConvertDocumentToFragment";i:7;s:19:"AcceptFullDocuments";O:8:"stdClass":3:{s:9:"namespace";s:4:"Core";s:4:"name";s:25:"ConvertDocumentToFragment";s:7:"isAlias";b:1;}s:31:"DirectLexLineNumberSyncInterval";i:5;s:8:"Encoding";i:2;s:21:"EscapeInvalidChildren";i:7;s:17:"EscapeInvalidTags";i:7;s:24:"EscapeNonASCIICharacters";i:7;s:14:"HiddenElements";i:8;s:8:"Language";i:1;s:9:"LexerImpl";i:-11;s:19:"MaintainLineNumbers";i:-7;s:16:"RemoveInvalidImg";i:7;s:20:"RemoveScriptContents";i:-7;s:5:"XHTML";O:8:"stdClass":3:{s:9:"namespace";s:4:"HTML";s:4:"name";s:5:"XHTML";s:7:"isAlias";b:1;}s:21:"CommentScriptContents";O:8:"stdClass":3:{s:9:"namespace";s:6:"Output";s:4:"name";s:21:"CommentScriptContents";s:7:"isAlias";b:1;}s:10:"TidyFormat";O:8:"stdClass":3:{s:9:"namespace";s:6:"Output";s:4:"name";s:10:"TidyFormat";s:7:"isAlias";b:1;}}s:6:"Filter";a:5:{s:6:"Custom";i:9;s:18:"ExtractStyleBlocks";i:7;s:7:"YouTube";i:7;s:26:"ExtractStyleBlocksEscaping";O:8:"stdClass":3:{s:9:"namespace";s:11:"FilterParam";s:4:"name";s:26:"ExtractStyleBlocksEscaping";s:7:"isAlias";b:1;}s:23:"ExtractStyleBlocksScope";O:8:"stdClass":3:{s:9:"namespace";s:11:"FilterParam";s:4:"name";s:23:"ExtractStyleBlocksScope";s:7:"isAlias";b:1;}}s:11:"FilterParam";a:3:{s:26:"ExtractStyleBlocksEscaping";i:7;s:23:"ExtractStyleBlocksScope";i:-1;s:26:"ExtractStyleBlocksTidyImpl";i:-11;}s:4:"HTML";a:24:{s:12:"EnableAttrID";O:8:"stdClass":3:{s:9:"namespace";s:4:"Attr";s:4:"name";s:8:"EnableID";s:7:"isAlias";b:1;}s:7:"Allowed";i:-4;s:17:"AllowedAttributes";i:-8;s:15:"AllowedElements";i:-8;s:14:"AllowedModules";i:-8;s:12:"BlockWrapper";i:1;s:11:"CoreModules";i:8;s:13:"CustomDoctype";i:-1;s:12:"DefinitionID";i:-1;s:13:"DefinitionRev";i:5;s:7:"Doctype";O:8:"stdClass":3:{s:4:"type";i:1;s:10:"allow_null";b:1;s:7:"allowed";a:5:{s:22:"HTML 4.01 Transitional";b:1;s:16:"HTML 4.01 Strict";b:1;s:22:"XHTML 1.0 Transitional";b:1;s:16:"XHTML 1.0 Strict";b:1;s:9:"XHTML 1.1";b:1;}}s:19:"ForbiddenAttributes";i:8;s:17:"ForbiddenElements";i:8;s:12:"MaxImgLength";i:-5;s:6:"Parent";i:1;s:11:"Proprietary";i:7;s:9:"SafeEmbed";i:7;s:10:"SafeObject";i:7;s:6:"Strict";i:7;s:7:"TidyAdd";i:8;s:9:"TidyLevel";O:8:"stdClass":2:{s:4:"type";i:1;s:7:"allowed";a:4:{s:4:"none";b:1;s:5:"light";b:1;s:6:"medium";b:1;s:5:"heavy";b:1;}}s:10:"TidyRemove";i:8;s:7:"Trusted";i:7;s:5:"XHTML";i:7;}s:6:"Output";a:3:{s:21:"CommentScriptContents";i:7;s:7:"Newline";i:-1;s:10:"TidyFormat";i:7;}s:4:"Test";a:1:{s:12:"ForceNoIconv";i:7;}s:3:"URI";a:16:{s:14:"AllowedSchemes";i:8;s:4:"Base";i:-1;s:13:"DefaultScheme";i:1;s:12:"DefinitionID";i:-1;s:13:"DefinitionRev";i:5;s:7:"Disable";i:7;s:15:"DisableExternal";i:7;s:24:"DisableExternalResources";i:7;s:16:"DisableResources";i:7;s:4:"Host";i:-1;s:13:"HostBlacklist";i:9;s:12:"MakeAbsolute";i:7;s:5:"Munge";i:-1;s:14:"MungeResources";i:7;s:14:"MungeSecretKey";i:-1;s:22:"OverrideAllowedSchemes";i:7;}}} \ No newline at end of file diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt index b1207aab..c711c11b 100644 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt +++ b/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt @@ -6,7 +6,7 @@ DEFAULT: NULL

Munges all browsable (usually http, https and ftp) - absolute URI's into another URI, usually a URI redirection service. + absolute URIs into another URI, usually a URI redirection service. This directive accepts a URI, formatted with a %s where the url-encoded original URI should be inserted (sample: http://www.google.com/url?q=%s). @@ -19,13 +19,58 @@ DEFAULT: NULL Prevent PageRank leaks, while being fairly transparent to users (you may also want to add some client side JavaScript to override the text in the statusbar). Notice: - Many security experts believe that this form of protection does -not deter spam-bots. + Many security experts believe that this form of protection does not deter spam-bots.

  • Redirect users to a splash page telling them they are leaving your - website. While this is poor usability practice, it is often -mandated + website. While this is poor usability practice, it is often mandated in corporate environments.
  • +

    + You may want to also use %URI.MungeSecretKey along with this directive + in order to enforce what URIs your redirector script allows. Open + redirector scripts can be a security risk and negatively affect the + reputation of your domain name. +

    +

    + Starting with HTML Purifier 3.1.1, there is also these substitutions: +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyDescriptionExample <a href="">
    %r1 - The URI embeds a resource
    (blank) - The URI is merely a link
    %nThe name of the tag this URI came froma
    %mThe name of the attribute this URI came fromhref
    %pThe name of the CSS property this URI came from, or blank if irrelevant
    +

    + Admittedly, these letters are somewhat arbitrary; the only stipulation + was that they couldn't be a through f. r is for resource (I would have preferred + e, but you take what you can get), n is for name, m + was picked because it came after n (and I couldn't use a), p is for + property. +

    diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt new file mode 100644 index 00000000..cf4e2272 --- /dev/null +++ b/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt @@ -0,0 +1,12 @@ +URI.MungeResources +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

    + If true, any URI munging directives like %URI.Munge or %URI.SecureMunge + will also apply to embedded resources, such as <img src="">. + Be careful enabling this directive if you have a redirector script + that does not use the Location HTTP header; all of your images + and other embedded resources will break. + diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt new file mode 100644 index 00000000..a2f5a02c --- /dev/null +++ b/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt @@ -0,0 +1,29 @@ +URI.MungeSecretKey +TYPE: string/null +VERSION: 3.1.1 +DEFAULT: NULL +--DESCRIPTION-- +

    + This directive enables secure checksum generation along with %URI.Munge. + It should be set to a secure key that is not shared with anyone else. + The checksum can be placed in the URI using %t. Use of this checksum + affords an additional level of protection by allowing a redirector + to check if a URI has passed through HTML Purifier with this line: +

    + +
    $checksum === sha1($secret_key . ':' . $url)
    + +

    + If the output is TRUE, the redirector script should accept the URI. +

    + +

    + Please note that it would still be possible for an attacker to procure + secure hashes en-mass by abusing your website's Preview feature or the + like, but this service affords an additional level of protection + that should be combined with website blacklisting. +

    + +

    + Remember this has no effect if %URI.Munge is not on. +

    diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.SecureMunge.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.SecureMunge.txt deleted file mode 100644 index 68860ae4..00000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.SecureMunge.txt +++ /dev/null @@ -1,33 +0,0 @@ -URI.SecureMunge -TYPE: string/null -VERSION: 3.1.1 -DEFAULT: NULL ---DESCRIPTION-- -

    - Like %URI.Munge, this directive munges browsable external resources - into another URI redirection service. %URI.SecureMunge accepts a URI - with a %s located where the original URI should be substituted in, - and %t located where the secure checksum should be provided. - However, this directive affords - an additional level of protection by generating a secure checksum from - the URI as well as a secret key provided by %URI.SecureMungeSecretKey. - Any redirector script can check this key by using: -

    - -
    $checksum === sha1($secret_key . ':' . $url)
    - -

    - If the output is TRUE, the redirector script should accept the URI. -

    - -

    - Please note that it would still be possible for an attacker to procure - secure hashes en-mass by abusing your website's Preview feature or the - like, but this service affords an additional level of protection - that should be combined with website blacklisting. -

    - -

    - This is a post-filter. This filter may conflict with other - post-filters that deal with external links. -

    \ No newline at end of file diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.SecureMungeSecretKey.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.SecureMungeSecretKey.txt deleted file mode 100644 index 05244e56..00000000 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.SecureMungeSecretKey.txt +++ /dev/null @@ -1,11 +0,0 @@ -URI.SecureMungeSecretKey -TYPE: string/null -VERSION: 3.1.1 -DEFAULT: NULL ---DESCRIPTION-- -

    - This is the secret key used in conjunction with %URI.SecureMunge. Your - redirector script needs to know about this key, and no one else should - know about this key. Please see the above - directive for more details. -

    \ No newline at end of file diff --git a/library/HTMLPurifier/URIDefinition.php b/library/HTMLPurifier/URIDefinition.php index 57f4ae58..6977338a 100644 --- a/library/HTMLPurifier/URIDefinition.php +++ b/library/HTMLPurifier/URIDefinition.php @@ -28,7 +28,7 @@ class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources()); $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist()); $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute()); - $this->registerFilter(new HTMLPurifier_URIFilter_SecureMunge()); + $this->registerFilter(new HTMLPurifier_URIFilter_Munge()); } public function registerFilter($filter) { diff --git a/library/HTMLPurifier/URIFilter/Munge.php b/library/HTMLPurifier/URIFilter/Munge.php new file mode 100644 index 00000000..ad6578e6 --- /dev/null +++ b/library/HTMLPurifier/URIFilter/Munge.php @@ -0,0 +1,48 @@ +target = $config->get('URI', $this->name); + $this->parser = new HTMLPurifier_URIParser(); + $this->doEmbed = $config->get('URI', 'MungeResources'); + $this->secretKey = $config->get('URI', 'MungeSecretKey'); + return true; + } + public function filter(&$uri, $config, $context) { + if ($context->get('EmbeddedURI', true) && !$this->doEmbed) return true; + + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) return true; // ignore unknown schemes, maybe another postfilter did it + if (is_null($uri->host) || empty($scheme_obj->browsable)) { + return true; + } + + $this->makeReplace($uri, $config, $context); + $this->replace = array_map('rawurlencode', $this->replace); + + $new_uri = strtr($this->target, $this->replace); + $uri = $this->parser->parse($new_uri); // overwrite + return true; + } + + protected function makeReplace($uri, $config, $context) { + $string = $uri->toString(); + // always available + $this->replace['%s'] = $string; + $this->replace['%r'] = $context->get('EmbeddedURI', true); + $token = $context->get('CurrentToken', true); + $this->replace['%n'] = $token ? $token->name : null; + $this->replace['%m'] = $context->get('CurrentAttr', true); + $this->replace['%p'] = $context->get('CurrentCSSProperty', true); + // not always available + if ($this->secretKey) $this->replace['%t'] = sha1($this->secretKey . ':' . $string); + } + +} diff --git a/library/HTMLPurifier/URIFilter/SecureMunge.php b/library/HTMLPurifier/URIFilter/SecureMunge.php deleted file mode 100644 index f131ad4e..00000000 --- a/library/HTMLPurifier/URIFilter/SecureMunge.php +++ /dev/null @@ -1,33 +0,0 @@ -target = $config->get('URI', 'SecureMunge'); - $this->secretKey = $config->get('URI', 'SecureMungeSecretKey'); - $this->parser = new HTMLPurifier_URIParser(); - if (!$this->secretKey) { - trigger_error('URI.SecureMunge is being ignored due to lack of value for URI.SecureMungeSecretKey', E_USER_WARNING); - return false; - } - return true; - } - public function filter(&$uri, $config, $context) { - if (!$this->target || !$this->secretKey) return true; - if ($context->get('EmbeddedURI', true)) return true; // abort for embedded URIs - $scheme_obj = $uri->getSchemeObj($config, $context); - if (!$scheme_obj) return true; // ignore unknown schemes, maybe another postfilter did it - if (is_null($uri->host) || empty($scheme_obj->browsable)) { - return true; - } - $string = $uri->toString(); - $checksum = sha1($this->secretKey . ':' . $string); - $new_uri = str_replace('%s', rawurlencode($string), $this->target); - $new_uri = str_replace('%t', $checksum, $new_uri); - $uri = $this->parser->parse($new_uri); // overwrite - return true; - } -} diff --git a/tests/HTMLPurifier/URIFilter/MungeTest.php b/tests/HTMLPurifier/URIFilter/MungeTest.php new file mode 100644 index 00000000..81fb93fc --- /dev/null +++ b/tests/HTMLPurifier/URIFilter/MungeTest.php @@ -0,0 +1,115 @@ +filter = new HTMLPurifier_URIFilter_Munge(); + } + + protected function setMunge($uri = 'http://www.google.com/url?q=%s') { + $this->config->set('URI', 'Munge', $uri); + } + + protected function setSecureMunge($key = 'secret') { + $this->setMunge('/redirect.php?url=%s&checksum=%t'); + $this->config->set('URI', 'MungeSecretKey', $key); + } + + function testMunge() { + $this->setMunge(); + $this->assertFiltering( + 'http://www.example.com/', + 'http://www.google.com/url?q=http%3A%2F%2Fwww.example.com%2F' + ); + } + + function testMungeReplaceTagName() { + $this->setMunge('/r?tagname=%n&url=%s'); + $token = new HTMLPurifier_Token_Start('a'); + $this->context->register('CurrentToken', $token); + $this->assertFiltering('http://google.com', '/r?tagname=a&url=http%3A%2F%2Fgoogle.com'); + } + + function testMungeReplaceAttribute() { + $this->setMunge('/r?attr=%m&url=%s'); + $attr = 'href'; + $this->context->register('CurrentAttr', $attr); + $this->assertFiltering('http://google.com', '/r?attr=href&url=http%3A%2F%2Fgoogle.com'); + } + + function testMungeReplaceResource() { + $this->setMunge('/r?embeds=%r&url=%s'); + $embeds = false; + $this->context->register('EmbeddedURI', $embeds); + $this->assertFiltering('http://google.com', '/r?embeds=&url=http%3A%2F%2Fgoogle.com'); + } + + function testMungeReplaceCSSProperty() { + $this->setMunge('/r?property=%p&url=%s'); + $property = 'background'; + $this->context->register('CurrentCSSProperty', $property); + $this->assertFiltering('http://google.com', '/r?property=background&url=http%3A%2F%2Fgoogle.com'); + } + + function testIgnoreEmbedded() { + $this->setMunge(); + $embeds = true; + $this->context->register('EmbeddedURI', $embeds); + $this->assertFiltering('http://example.com'); + } + + function testProcessEmbedded() { + $this->setMunge(); + $this->config->set('URI', 'MungeResources', true); + $embeds = true; + $this->context->register('EmbeddedURI', $embeds); + $this->assertFiltering('http://www.example.com/', 'http://www.google.com/url?q=http%3A%2F%2Fwww.example.com%2F'); + } + + function testPreserveRelative() { + $this->setMunge(); + $this->assertFiltering('index.html'); + } + + function testMungeIgnoreUnknownSchemes() { + $this->setMunge(); + $this->assertFiltering('javascript:foobar();', true); + } + + function testSecureMungePreserve() { + $this->setSecureMunge(); + $this->assertFiltering('/local'); + } + + function testSecureMungePreserveEmbedded() { + $this->setSecureMunge(); + $embedded = true; + $this->context->register('EmbeddedURI', $embedded); + $this->assertFiltering('http://google.com'); + } + + function testSecureMungeStandard() { + $this->setSecureMunge(); + $this->assertFiltering('http://google.com', '/redirect.php?url=http%3A%2F%2Fgoogle.com&checksum=0072e2f817fd2844825def74e54443debecf0892'); + } + + function testSecureMungeIgnoreUnknownSchemes() { + // This should be integration tested as well to be false + $this->setSecureMunge(); + $this->assertFiltering('javascript:', true); + } + + function testSecureMungeIgnoreUnbrowsableSchemes() { + $this->setSecureMunge(); + $this->assertFiltering('news:', true); + } + + function testSecureMungeToDirectory() { + $this->setSecureMunge(); + $this->setMunge('/links/%s/%t'); + $this->assertFiltering('http://google.com', '/links/http%3A%2F%2Fgoogle.com/0072e2f817fd2844825def74e54443debecf0892'); + } + +} diff --git a/tests/HTMLPurifier/URIFilter/SecureMungeTest.php b/tests/HTMLPurifier/URIFilter/SecureMungeTest.php deleted file mode 100644 index 4a71ce04..00000000 --- a/tests/HTMLPurifier/URIFilter/SecureMungeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -filter = new HTMLPurifier_URIFilter_SecureMunge(); - $this->setSecureMunge(); - $this->setSecretKey(); - } - - function setSecureMunge($uri = '/redirect.php?url=%s&checksum=%t') { - $this->config->set('URI', 'SecureMunge', $uri); - } - - function setSecretKey($key = 'secret') { - $this->config->set('URI', 'SecureMungeSecretKey', $key); - } - - function testPreserve() { - $this->assertFiltering('/local'); - } - - function testPreserveEmbedded() { - $embedded = true; - $this->context->register('EmbeddedURI', $embedded); - $this->assertFiltering('http://google.com'); - } - - function testStandardMunge() { - $this->assertFiltering('http://google.com', '/redirect.php?url=http%3A%2F%2Fgoogle.com&checksum=0072e2f817fd2844825def74e54443debecf0892'); - } - - function testIgnoreUnknownSchemes() { - // This should be integration tested as well to be false - $this->assertFiltering('javascript:', true); - } - - function testIgnoreUnbrowsableSchemes() { - $this->assertFiltering('news:', true); - } - - function testMungeToDirectory() { - $this->setSecureMunge('/links/%s/%t'); - $this->assertFiltering('http://google.com', '/links/http%3A%2F%2Fgoogle.com/0072e2f817fd2844825def74e54443debecf0892'); - } - - function testErrorNoSecretKey() { - $this->setSecretKey(null); - $this->expectError('URI.SecureMunge is being ignored due to lack of value for URI.SecureMungeSecretKey'); - $this->assertFiltering('http://google.com'); - } - -} diff --git a/tests/HTMLPurifierTest.php b/tests/HTMLPurifierTest.php index 63cccae8..4f6c19a3 100644 --- a/tests/HTMLPurifierTest.php +++ b/tests/HTMLPurifierTest.php @@ -186,8 +186,8 @@ alert(""); } function test_secureMunge() { - $this->config->set('URI', 'SecureMunge', '/redirect.php?url=%s&check=%t'); - $this->config->set('URI', 'SecureMungeSecretKey', 'foo'); + $this->config->set('URI', 'Munge', '/redirect.php?url=%s&check=%t'); + $this->config->set('URI', 'MungeSecretKey', 'foo'); $this->assertPurification( 'foolocal', 'foolocal' @@ -206,13 +206,25 @@ alert(""); function test_safeObjectAndEmbedWithSecureMunge() { $this->config->set('HTML', 'SafeObject', true); $this->config->set('HTML', 'SafeEmbed', true); - $this->config->set('URI', 'SecureMunge', '/redirect.php?url=%s&check=%t'); - $this->config->set('URI', 'SecureMungeSecretKey', 'foo'); + $this->config->set('URI', 'Munge', '/redirect.php?url=%s&check=%t'); + $this->config->set('URI', 'MungeSecretKey', 'foo'); $this->assertPurification( '', '' ); } + function test_mungeWithExtraParams() { + $this->config->set('URI', 'Munge', '/redirect?s=%s&t=%t&r=%r&n=%n&m=%m&p=%p'); + $this->config->set('URI', 'MungeSecretKey', 'foo'); + $this->config->set('URI', 'MungeResources', true); + $this->assertPurification( + 'Linkexample.com', + 'Link'. + 'example.com' + ); + } + }