0
0
mirror of https://github.com/ezyang/htmlpurifier.git synced 2025-01-09 23:41:52 +00:00

Merge in r657-674, prompted by near release of 1.4.0.

git-svn-id: http://htmlpurifier.org/svnroot/htmlpurifier/branches/strict@675 48356398-32a2-884e-a903-53898d9a118a
This commit is contained in:
Edward Z. Yang 2007-01-21 16:07:36 +00:00
parent 37ea1673dd
commit 9a84e11f34
56 changed files with 1356 additions and 509 deletions

18
NEWS
View File

@ -11,12 +11,22 @@ NEWS ( CHANGELOG and HISTORY ) HTMLPurifier
1.4.0, unknown release date 1.4.0, unknown release date
! Implemented list-style-image, URIs now allowed in list-style ! Implemented list-style-image, URIs now allowed in list-style
! Implemented background-image, background-repeat and background-attachment ! Implemented background-image, background-repeat, background-attachment
CSS properties. background shorthand property HAS NOT been extended and background-position CSS properties. Shorthand property background
to allow these, and background-position IS NOT implemented yet. supports all of these properties.
! Configuration documentation looks nicer ! Configuration documentation looks nicer
! Added smoketest 'all.php', which loads all other smoketests via frames ! Added %Core.EscapeNonASCIICharacters to workaround loss of Unicode
characters while %Core.Encoding is set to a non-UTF-8 encoding.
! Support for configuration directive aliases added
! Config object can now be instantiated from ini files
! YouTube preservation code added to the core, with two lines of code
you can add it as a filter to your code. See smoketests/preserveYouTube.php
for sample code.
- Replaced version check with functionality check for DOM (thanks Stephen
Khoo)
. Added smoketest 'all.php', which loads all other smoketests via frames
. Implemented AttrDef_CSSURI for url(http://google.com) style declarations . Implemented AttrDef_CSSURI for url(http://google.com) style declarations
. Added convenient single test selector form on test runner
1.3.3, unknown release date, likely to be dropped 1.3.3, unknown release date, likely to be dropped
! Moved SLOW to docs/enduser-slow.html and added code examples ! Moved SLOW to docs/enduser-slow.html and added code examples

61
TODO
View File

@ -7,19 +7,14 @@ TODO List
? At-risk ? At-risk
========================== ==========================
1.4 release
# More extensive URI filtering schemes (see docs/proposal-new-directives.txt)
# Allow for background-image and list-style-image (intrinsically tied to above)
# Add hooks for custom behavior (for instance, YouTube preservation)
- Aggressive caching
? Rich set* methods and config file loaders for HTMLPurifier_Config
? Configuration profiles: sets of directives that get set with one func call
? ConfigSchema directive aliases (so we can rename some of them)
? URI validation routines tighter (see docs/dev-code-quality.html) (COMPLEX)
1.5 release 1.5 release
# Implement all non-essential attribute transforms
# URI validation routines tighter (see docs/dev-code-quality.html) (COMPLEX)
# Advanced URI filtering schemes (see docs/proposal-new-directives.txt)
# Error logging for filtering/cleanup procedures # Error logging for filtering/cleanup procedures
- Requires I18N facilities to be created first (COMPLEX) - Requires I18N facilities to be created first (COMPLEX)
? Configuration profiles: sets of directives that get set with one func call
- XSS-attempt detection
1.6 release 1.6 release
# Add pre-packaged "levels" of cleaning (custom behavior already done) # Add pre-packaged "levels" of cleaning (custom behavior already done)
@ -28,14 +23,30 @@ TODO List
specification of elements that, when detected as foreign, trigger removal specification of elements that, when detected as foreign, trigger removal
of children, although unbalanced tags could wreck havoc (or at least of children, although unbalanced tags could wreck havoc (or at least
delete the rest of the document)). delete the rest of the document)).
- Allow specifying global attributes on a tag-by-tag basis in
%HTML.AllowAttributes
? More user-friendly warnings when %HTML.Allow* attempts to specify a
tag or attribute that is not supported
- Parse TinyMCE whitelist into our %HTML.Allow* whitelists
1.7 release 1.7 release
# Additional support for poorly written HTML # Additional support for poorly written HTML
- Implement all non-essential attribute transforms (BIG!)
- Microsoft Word HTML cleaning (i.e. MsoNormal, but research essential!) - Microsoft Word HTML cleaning (i.e. MsoNormal, but research essential!)
- Friendly strict handling of <address> (block -> <br>) - Friendly strict handling of <address> (block -> <br>)
- Remove redundant tags, ex. <u><u>Underlined</u></u>. Implementation notes:
1. Analyzing which tags to remove duplicants
2. Ensure attributes are merged into the parent tag
3. Extend the tag exclusion system to specify whether or not the
contents should be dropped or not (currently, there's code that could do
something like this if it didn't drop the inner text too.)
- Remove <span> tags that don't do anything (no attributes)
- Remove empty inline tags<i></i>
- Append something to duplicate IDs so they're still usable (impl. note: the
dupe detector would also need to detect the suffix as well)
2.0 release 2.0 release
# Legit token based CSS parsing (will require revamping almost every
AttrDef class)
# Formatters for plaintext (COMPLEX) # Formatters for plaintext (COMPLEX)
- Auto-paragraphing (be sure to leverage fact that we know when things - Auto-paragraphing (be sure to leverage fact that we know when things
shouldn't be paragraphed, such as lists and tables). shouldn't be paragraphed, such as lists and tables).
@ -48,48 +59,32 @@ TODO List
- Hooks for adding custom processors to custom namespaced tags and - Hooks for adding custom processors to custom namespaced tags and
attributes, offer default implementation attributes, offer default implementation
- Lots of documentation and samples - Lots of documentation and samples
- Allow tags to be "armored", an internal flag that protects them
from validation and passes them out unharmed
- XHTML 1.1 support - XHTML 1.1 support
Ongoing Ongoing
- Lots of profiling, make it faster! - Lots of profiling, make it faster!
- Plugins for major CMSes (COMPLEX) - Plugins for major CMSes (COMPLEX)
- Drupal
- WordPress - WordPress
- eFiction - eFiction
- more! (look for ones that use WYSIWYGs) - more! (look for ones that use WYSIWYGs)
Unknown release (on a scratch-an-itch basis) Unknown release (on a scratch-an-itch basis)
- Upgrade SimpleTest testing code to newest versions
- Fixes for Firefox's inability to handle COL alignment props (Bug 915) - Fixes for Firefox's inability to handle COL alignment props (Bug 915)
- Automatically add non-breaking spaces to empty table cells when - Automatically add non-breaking spaces to empty table cells when
empty-cells:show is applied to have compatibility with Internet Explorer empty-cells:show is applied to have compatibility with Internet Explorer
- Convert RTL/LTR override characters to <bdo> tags, or vice versa on demand. - Convert RTL/LTR override characters to <bdo> tags, or vice versa on demand.
Also, enable disabling of directionality Also, enable disabling of directionality
- Append something to duplicate IDs so they're still usable (impl. note: the
dupe detector would also need to detect the suffix as well)
- Have 'lang' attribute be checked against official lists - Have 'lang' attribute be checked against official lists
? Semi-lossy dumb alternate character encoding transformations, achieved by
Encoding workarounds
- Non-lossy dumb alternate character encoding transformations, achieved by
numerically encoding all non-ASCII characters
- Semi-lossy dumb alternate character encoding transformations, achieved by
encoding all characters that have string entity equivalents encoding all characters that have string entity equivalents
Requested Requested
- Native content compression, whitespace stripping (don't rely on Tidy, make ? Native content compression, whitespace stripping (don't rely on Tidy, make
sure we don't remove from <pre> or related tags) sure we don't remove from <pre> or related tags)
- Win32 Phalanger C# binaries (?) ? Win32 Phalanger C# binaries
- Remove redundant tags, ex. <u><u>Underlined</u></u>. Implementation notes:
1. Analyzing which tags to remove duplicants
2. Ensure attributes are merged into the parent tag
3. Extend the tag exclusion system to specify whether or not the
contents should be dropped or not (currently, there's code that could do
something like this if it didn't drop the inner text too.)
- More user-friendly warnings when %HTML.Allow* attempts to specify a
tag or attribute that is not supported
- Allow specifying global attributes on a tag-by-tag basis in
%HTML.AllowAttributes
- Parse TinyMCE whitelist into our %HTML.Allow* whitelists
- XSS-attempt detection
Wontfix Wontfix
- Non-lossy smart alternate character encoding transformations (unless - Non-lossy smart alternate character encoding transformations (unless

View File

@ -99,6 +99,8 @@ foreach($schema->info as $namespace_name => $namespace_info) {
foreach ($namespace_info as $name => $info) { foreach ($namespace_info as $name => $info) {
if ($info->class == 'alias') continue;
$dom_directive = $dom_document->createElement('directive'); $dom_directive = $dom_document->createElement('directive');
$dom_namespace->appendChild($dom_directive); $dom_namespace->appendChild($dom_directive);

View File

@ -60,7 +60,7 @@ thead th {text-align:left;padding:0.1em;background-color:#EEE;}
<tbody> <tbody>
<tr><th colspan="2">Standard</th></tr> <tr><th colspan="2">Standard</th></tr>
<tr class="css1 impl-yes"><td>background-color</td><td>COMPOSITE(&lt;color&gt;, transparent)</td></tr> <tr class="css1 impl-yes"><td>background-color</td><td>COMPOSITE(&lt;color&gt;, transparent)</td></tr>
<tr class="css1 impl-partial"><td>background</td><td>SHORTHAND</td></tr> <tr class="css1 impl-yes"><td>background</td><td>SHORTHAND, currently alias for background-color</td></tr>
<tr class="css1 impl-yes"><td>border</td><td>SHORTHAND, MULTIPLE</td></tr> <tr class="css1 impl-yes"><td>border</td><td>SHORTHAND, MULTIPLE</td></tr>
<tr class="css1 impl-yes"><td>border-color</td><td>MULTIPLE</td></tr> <tr class="css1 impl-yes"><td>border-color</td><td>MULTIPLE</td></tr>
<tr class="css1 impl-yes"><td>border-style</td><td>MULTIPLE</td></tr> <tr class="css1 impl-yes"><td>border-style</td><td>MULTIPLE</td></tr>
@ -145,13 +145,13 @@ thead th {text-align:left;padding:0.1em;background-color:#EEE;}
<tr class="danger css1 impl-yes"><td>background-image</td><td>Dangerous, target milestone 1.3</td></tr> <tr class="danger css1 impl-yes"><td>background-image</td><td>Dangerous, target milestone 1.3</td></tr>
<tr class="css1 impl-yes"><td>background-attachment</td><td>ENUM(scroll, fixed), <tr class="css1 impl-yes"><td>background-attachment</td><td>ENUM(scroll, fixed),
Depends on background-image</td></tr> Depends on background-image</td></tr>
<tr class="css1"><td>background-position</td><td>Depends on background-image</td></tr> <tr class="css1 impl-yes"><td>background-position</td><td>Depends on background-image</td></tr>
<tr class="danger impl-no"><td>cursor</td><td>Dangerous but fluffy</td></tr> <tr class="danger impl-no"><td>cursor</td><td>Dangerous but fluffy</td></tr>
<tr class="danger css1"><td>display</td><td>ENUM(...), Dangerous but interesting; <tr class="danger css1"><td>display</td><td>ENUM(...), Dangerous but interesting;
will not implement list-item, run-in (Opera only) or table (no IE); will not implement list-item, run-in (Opera only) or table (no IE);
inline-block has incomplete IE6 support and requires -moz-inline-box inline-block has incomplete IE6 support and requires -moz-inline-box
for Mozilla. Unknown target milestone.</td></tr> for Mozilla. Unknown target milestone.</td></tr>
<tr><td class="css1">height</td><td>Interesting, why use it? Unknown target milestone.</td></tr> <tr class="css1"><td>height</td><td>Interesting, why use it? Unknown target milestone.</td></tr>
<tr class="danger css1 impl-yes"><td>list-style-image</td><td>Dangerous?</td></tr> <tr class="danger css1 impl-yes"><td>list-style-image</td><td>Dangerous?</td></tr>
<tr class="impl-no"><td>max-height</td><td rowspan="4">No IE 5/6</td></tr> <tr class="impl-no"><td>max-height</td><td rowspan="4">No IE 5/6</td></tr>
<tr class="impl-no"><td>min-height</td></tr> <tr class="impl-no"><td>min-height</td></tr>
@ -231,7 +231,7 @@ Mozilla on inside and needs -moz-outline, no IE support.</td></tr>
<tbody> <tbody>
<tr><th colspan="3">CSS</th></tr> <tr><th colspan="3">CSS</th></tr>
<tr class="impl-yes"><td>style</td><td>All</td><td>Not all properties may be implemented, parser is good though.</td></tr> <tr class="impl-yes"><td>style</td><td>All</td><td>Parser is reasonably functional. Status here doesn't count individual properties.</td></tr>
</tbody> </tbody>
<tbody> <tbody>
@ -266,13 +266,13 @@ Mozilla on inside and needs -moz-outline, no IE support.</td></tr>
<tr><td rowspan="5">align</td><td>CAPTION</td><td>Near-equiv style 'caption-side', drop left and right</td></tr> <tr><td rowspan="5">align</td><td>CAPTION</td><td>Near-equiv style 'caption-side', drop left and right</td></tr>
<tr><td>IMG</td><td rowspan="2">Margin-left and margin-right = auto or parent div</td></tr> <tr><td>IMG</td><td rowspan="2">Margin-left and margin-right = auto or parent div</td></tr>
<tr><td>TABLE</td></tr> <tr><td>TABLE</td></tr>
<tr><td>HR</td><td>Equivalent style 'text-align' (IE tested)</td></tr> <tr><td>HR</td><td>Near-equivalent style 'text-align' (Works for IE and Opera, but not Firefox). Also try <code>margin-right:auto; margin-left:0;</code> for left or <code>margin-right:0; margin-left:auto;</code> for right (optionally replacing 0 with the original margin for that side)</td></tr>
<tr class="impl-yes"><td>H1, H2, H3, H4, H5, H6, P</td><td>Equivalent style 'text-align'</td></tr> <tr class="impl-yes"><td>H1, H2, H3, H4, H5, H6, P</td><td>Equivalent style 'text-align'</td></tr>
<tr class="required impl-yes"><td>alt</td><td>IMG</td><td>Required, insert image filename if src is present or default invalid image text</td></tr> <tr class="required impl-yes"><td>alt</td><td>IMG</td><td>Required, insert image filename if src is present or default invalid image text</td></tr>
<tr><td rowspan="3">bgcolor</td><td>TABLE</td><td>Equivalent style 'background-color' (IE tested)</td></tr> <tr><td rowspan="3">bgcolor</td><td>TABLE</td><td>Equivalent style 'background-color'</td></tr>
<tr><td>TR</td><td>Equivalent style 'background-color' (IE tested)</td></tr> <tr><td>TR</td><td>Equivalent style 'background-color'</td></tr>
<tr><td>TD, TH</td><td>Equivalent style 'background-color'</td></tr> <tr><td>TD, TH</td><td>Equivalent style 'background-color'</td></tr>
<tr><td>border</td><td>IMG</td><td>Equivalent style 'border-width', only applies when link present</td></tr> <tr><td>border</td><td>IMG</td><td>Near equivalent style 'border-width', as it only applies when link present</td></tr>
<tr><td>clear</td><td>BR</td><td>Near-equiv style 'clear', transform 'all' into 'both'</td></tr> <tr><td>clear</td><td>BR</td><td>Near-equiv style 'clear', transform 'all' into 'both'</td></tr>
<tr class="impl-no"><td>compact</td><td>DL, OL, UL</td><td>Boolean, needs custom CSS class; rarely used anyway</td></tr> <tr class="impl-no"><td>compact</td><td>DL, OL, UL</td><td>Boolean, needs custom CSS class; rarely used anyway</td></tr>
<tr class="required impl-yes"><td>dir</td><td>BDO</td><td>Required, insert ltr (or configuration value) if none</td></tr> <tr class="required impl-yes"><td>dir</td><td>BDO</td><td>Required, insert ltr (or configuration value) if none</td></tr>

View File

@ -7,6 +7,7 @@ and it's up to you to provide it the proper information and proper context
to be effective. Things to remember: to be effective. Things to remember:
1. Character Encoding: UTF-8. 1. Character Encoding: UTF-8.
This segment will soon be obsoleted by enduser-utf8.html
Currently, the parser runs under the assumption that it is dealing Currently, the parser runs under the assumption that it is dealing
with UTF-8. Not ISO-8859-1 or Windows-1252, UTF-8. And definitely not "no with UTF-8. Not ISO-8859-1 or Windows-1252, UTF-8. And definitely not "no
character encoding explicitly stated" or UTF-7. If you're not using UTF-8 as character encoding explicitly stated" or UTF-7. If you're not using UTF-8 as
@ -27,6 +28,7 @@ this may be configurable in the future. Do you want standards compliance?
The doctype is a good place to start. The doctype is a good place to start.
3. IDs 3. IDs
This segment is obsoleted by enduser-id.html
They need to be unique, but without some knowledge of the They need to be unique, but without some knowledge of the
rest of the document, it's difficult to know what's unique. %Attr.IDBlacklist rest of the document, it's difficult to know what's unique. %Attr.IDBlacklist
needs to be set: we may want to consider disallowing IDs by default to needs to be set: we may want to consider disallowing IDs by default to

View File

@ -172,9 +172,10 @@ introduced after it has finished.</p>
<h2>Future plans</h2> <h2>Future plans</h2>
<p>It would probably be a good idea if this code was added to the core <p>This functionality is part of the core library, using the
library. Look out for the inclusion of this into the core as a decorator HTMLPurifier_Filter class to acheive the desired effect. Our implementation
or the like.</p> is slightly different, and this page will be updated to reflect that
once 1.4.0 is released.</p>
</body> </body>
</html> </html>

View File

@ -31,6 +31,9 @@ information for casual developers using HTML Purifier.</p>
<dt><a href="enduser-slow.html">Speeding up HTML Purifier</a></dt> <dt><a href="enduser-slow.html">Speeding up HTML Purifier</a></dt>
<dd>Explains how to speed up HTML Purifier through caching or inbound filtering.</dd> <dd>Explains how to speed up HTML Purifier through caching or inbound filtering.</dd>
<dt><a href="enduser-utf8.html">UTF-8</a></dt>
<dd>Describes the rationale for using UTF-8, the ramifications otherwise, and how to make the switch.</dd>
</dl> </dl>
<h2>Development</h2> <h2>Development</h2>

View File

@ -14,15 +14,15 @@ Since configuration is dependant on context, internal classes require a
configuration object to be passed as a parameter. (They also require a configuration object to be passed as a parameter. (They also require a
Context object). Context object).
In relation to HTMLDefinition and CSSDefinition, there is a special class In relation to HTMLDefinition and CSSDefinition, there could be a special class
of directives that influence the *construction* of the Definition object. of directives that influence the *construction* of the Definition object.
A standard call pattern would look like: A theoretical call pattern would look like:
1. Client calls Config->getHTMLDefinition() 1. Client calls Config->getHTMLDefinition()
2. Config calls HTMLDefinition->createNew(this) 2. Config calls HTMLDefinition->createNew(this)
3. HTMLDefinition constructs itself with base configuration 3. HTMLDefinition constructs itself with base configuration
4. HTMLDefinition calls Config->get('HTMLDefinition') 4. HTMLDefinition calls Config->get('HTML')
5. Config returns array of directives that later construction 5. Config returns array of directives
6. HTMLDefinition performs operations and changes specified by directives 6. HTMLDefinition performs operations and changes specified by directives
7. HTMLPurifier returns constructed definition 7. HTMLPurifier returns constructed definition
8. Config caches definition so it doesn't have to be generated again 8. Config caches definition so it doesn't have to be generated again
@ -33,3 +33,7 @@ custom copy, which OVERRIDES all directives. Only the base, vanilla copy
is the Singleton, the object actually interfaced with is a operated-upon is the Singleton, the object actually interfaced with is a operated-upon
clone of that object. Also, if an update to the directives would update clone of that object. Also, if an update to the directives would update
the definition, you'd have to force reconstruction. the definition, you'd have to force reconstruction.
In practice, the pulling directives from the config object are
solely need-based, and the flex points are littered throughout the
setup() function. Some sort of refactoring is likely in order.

View File

@ -15,7 +15,10 @@ and properties to allow. HTMLDefinition makes a big part of what HTMLPurifier
is. is.
The idea, then, is to setup fundamentally different set of definitions, which The idea, then, is to setup fundamentally different set of definitions, which
can further be customized using simpler configuration options. can further be customized using simpler configuration options. Alternatively,
they could be implemented as configuration profiles, which simply load
a set of recommended directives to acheive a desired affect (no simpler
config options though).
Here are some fuzzy levels you could set: Here are some fuzzy levels you could set:

View File

@ -4,8 +4,6 @@ Configuration Ideas
Here are some theoretical configuration ideas that we could implement some Here are some theoretical configuration ideas that we could implement some
time. Note the naming convention: %Namespace.Directive time. Note the naming convention: %Namespace.Directive
%Attr.IDPrefix - prefix all ids with this
%Attr.RewriteFragments - if there's %Attr.IDPrefix we may want to transparently %Attr.RewriteFragments - if there's %Attr.IDPrefix we may want to transparently
rewrite the URLs we parse too. However, we can only do it when it's a pure rewrite the URLs we parse too. However, we can only do it when it's a pure
anchor link, so it's not foolproof anchor link, so it's not foolproof

View File

@ -2,8 +2,8 @@
Is HTML Purifier Strict or Transitional? Is HTML Purifier Strict or Transitional?
A little bit of helpful guidance A little bit of helpful guidance
Despite the fact that HTML Purifier professes only to support transitional Despite the fact that HTML Purifier professes to support both transitional and
HTML, it rejects a lot of attributes and elements that are actually, indeed, strict HTML, it rejects a lot of attributes and elements that are actually, indeed,
valid. You can investigate progress.html to find out precisely what we valid. You can investigate progress.html to find out precisely what we
are doing to these *deprecated* attributes. are doing to these *deprecated* attributes.
@ -11,8 +11,8 @@ However, users have found that Strict HTML imposes some quite unreasonable
restrictions on certain things. The start and value attributes in ol and restrictions on certain things. The start and value attributes in ol and
li (respectively) perhaps are the most contested. There's is currently no li (respectively) perhaps are the most contested. There's is currently no
widely supported browser method short of JavaScript that can replace these widely supported browser method short of JavaScript that can replace these
two deprecated elements. HTML Purifier does not currently support them, but two deprecated elements. It behooves us to allow these deprecated
it might behoove us to do so while our output is still transitional. attributes when the output is transitional.
Fortunantely, that's the only real bugger case. The others have near-perfect Fortunantely, that's the only real bugger case. The others have near-perfect
CSS equivalents, and were presentational anyway. However, the other question CSS equivalents, and were presentational anyway. However, the other question
@ -32,5 +32,6 @@ these loose-only constructs in loose mode:
The changed child definitions as well as the ul.start li.value are the most The changed child definitions as well as the ul.start li.value are the most
compelling reasons why loose should be used. We may want offer disabling <u>, compelling reasons why loose should be used. We may want offer disabling <u>,
<strike> and <s> by themselves. <strike> and <s> by themselves. We may also want to offer no pre-emptive
deprecated conversions. This all must be unified.

View File

@ -67,6 +67,7 @@ class HTMLPurifier
var $version = '1.3.2'; var $version = '1.3.2';
var $config; var $config;
var $filters;
var $lexer, $strategy, $generator; var $lexer, $strategy, $generator;
@ -94,6 +95,14 @@ class HTMLPurifier
} }
/**
* Adds a filter to process the output. First come first serve
* @param $filter HTMLPurifier_Filter object
*/
function addFilter($filter) {
$this->filters[] = $filter;
}
/** /**
* Filters an HTML snippet/document to be XSS-free and standards-compliant. * Filters an HTML snippet/document to be XSS-free and standards-compliant.
* *
@ -111,6 +120,10 @@ class HTMLPurifier
$context = new HTMLPurifier_Context(); $context = new HTMLPurifier_Context();
$html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context); $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context);
for ($i = 0, $size = count($this->filters); $i < $size; $i++) {
$html = $this->filters[$i]->preFilter($html, $config, $context);
}
// purified HTML // purified HTML
$html = $html =
$this->generator->generateFromTokens( $this->generator->generateFromTokens(
@ -126,6 +139,10 @@ class HTMLPurifier
$config, $context $config, $context
); );
for ($i = $size - 1; $i >= 0; $i--) {
$html = $this->filters[$i]->postFilter($html, $config, $context);
}
$html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context); $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context);
$this->context =& $context; $this->context =& $context;
return $html; return $html;

View File

@ -0,0 +1,87 @@
<?php
require_once 'HTMLPurifier/AttrDef.php';
require_once 'HTMLPurifier/CSSDefinition.php';
/**
* Validates shorthand CSS property background.
* @warning Does not support url tokens that have internal spaces.
*/
class HTMLPurifier_AttrDef_Background extends HTMLPurifier_AttrDef
{
/**
* Local copy of component validators.
* @note See HTMLPurifier_AttrDef_Font::$info for a similar impl.
*/
var $info;
function HTMLPurifier_AttrDef_Background($config) {
$def = $config->getCSSDefinition();
$this->info['background-color'] = $def->info['background-color'];
$this->info['background-image'] = $def->info['background-image'];
$this->info['background-repeat'] = $def->info['background-repeat'];
$this->info['background-attachment'] = $def->info['background-attachment'];
$this->info['background-position'] = $def->info['background-position'];
}
function validate($string, $config, &$context) {
// regular pre-processing
$string = $this->parseCDATA($string);
if ($string === '') return false;
// assumes URI doesn't have spaces in it
$bits = explode(' ', strtolower($string)); // bits to process
$caught = array();
$caught['color'] = false;
$caught['image'] = false;
$caught['repeat'] = false;
$caught['attachment'] = false;
$caught['position'] = false;
$i = 0; // number of catches
$none = false;
foreach ($bits as $bit) {
if ($bit === '') continue;
foreach ($caught as $key => $status) {
if ($key != 'position') {
if ($status !== false) continue;
$r = $this->info['background-' . $key]->validate($bit, $config, $context);
} else {
$r = $bit;
}
if ($r === false) continue;
if ($key == 'position') {
if ($caught[$key] === false) $caught[$key] = '';
$caught[$key] .= $r . ' ';
} else {
$caught[$key] = $r;
}
$i++;
break;
}
}
if (!$i) return false;
if ($caught['position'] !== false) {
$caught['position'] = $this->info['background-position']->
validate($caught['position'], $config, $context);
}
$ret = array();
foreach ($caught as $value) {
if ($value === false) continue;
$ret[] = $value;
}
if (empty($ret)) return false;
return implode(' ', $ret);
}
}
?>

View File

@ -0,0 +1,130 @@
<?php
require_once 'HTMLPurifier/AttrDef.php';
require_once 'HTMLPurifier/AttrDef/CSSLength.php';
require_once 'HTMLPurifier/AttrDef/Percentage.php';
/* W3C says:
[ // adjective and number must be in correct order, even if
// you could switch them without introducing ambiguity.
// some browsers support that syntax
[
<percentage> | <length> | left | center | right
]
[
<percentage> | <length> | top | center | bottom
]?
] |
[ // this signifies that the vertical and horizontal adjectives
// can be arbitrarily ordered, however, there can only be two,
// one of each, or none at all
[
left | center | right
] ||
[
top | center | bottom
]
]
top, left = 0%
center, (none) = 50%
bottom, right = 100%
*/
/* QuirksMode says:
keyword + length/percentage must be ordered correctly, as per W3C
Internet Explorer and Opera, however, support arbitrary ordering. We
should fix it up.
Minor issue though, not strictly necessary.
*/
// control freaks may appreciate the ability to convert these to
// percentages or something, but it's not necessary
/**
* Validates the value of background-position.
*/
class HTMLPurifier_AttrDef_BackgroundPosition extends HTMLPurifier_AttrDef
{
var $length;
var $percentage;
function HTMLPurifier_AttrDef_BackgroundPosition() {
$this->length = new HTMLPurifier_AttrDef_CSSLength();
$this->percentage = new HTMLPurifier_AttrDef_Percentage();
}
function validate($string, $config, &$context) {
$string = $this->parseCDATA($string);
$bits = explode(' ', $string);
$keywords = array();
$keywords['h'] = false; // left, right
$keywords['v'] = false; // top, bottom
$keywords['c'] = false; // center
$measures = array();
$i = 0;
$lookup = array(
'top' => 'v',
'bottom' => 'v',
'left' => 'h',
'right' => 'h',
'center' => 'c'
);
foreach ($bits as $bit) {
if ($bit === '') continue;
// test for keyword
$lbit = ctype_lower($bit) ? $bit : strtolower($bit);
if (isset($lookup[$lbit])) {
$status = $lookup[$lbit];
$keywords[$status] = $lbit;
$i++;
}
// test for length
$r = $this->length->validate($bit, $config, &$context);
if ($r !== false) {
$measures[] = $r;
$i++;
}
// test for percentage
$r = $this->percentage->validate($bit, $config, &$context);
if ($r !== false) {
$measures[] = $r;
$i++;
}
}
if (!$i) return false; // no valid values were caught
$ret = array();
// first keyword
if ($keywords['h']) $ret[] = $keywords['h'];
elseif (count($measures)) $ret[] = array_shift($measures);
elseif ($keywords['c']) {
$ret[] = $keywords['c'];
$keywords['c'] = false; // prevent re-use: center = center center
}
if ($keywords['v']) $ret[] = $keywords['v'];
elseif (count($measures)) $ret[] = array_shift($measures);
elseif ($keywords['c']) $ret[] = $keywords['c'];
if (empty($ret)) return false;
return implode(' ', $ret);
}
}
?>

View File

@ -40,6 +40,7 @@ class HTMLPurifier_AttrDef_CSSLength extends HTMLPurifier_AttrDef
// we assume all units are two characters // we assume all units are two characters
$unit = substr($length, $strlen - 2); $unit = substr($length, $strlen - 2);
if (!ctype_lower($unit)) $unit = strtolower($unit);
$number = substr($length, 0, $strlen - 2); $number = substr($length, 0, $strlen - 2);
if (!isset($this->units[$unit])) return false; if (!isset($this->units[$unit])) return false;

View File

@ -53,6 +53,7 @@ class HTMLPurifier_AttrDef_ListStyle extends HTMLPurifier_AttrDef
} }
$caught[$key] = $r; $caught[$key] = $r;
$i++; $i++;
break;
} }
} }

View File

@ -4,14 +4,13 @@ require_once 'HTMLPurifier/AttrDef.php';
require_once 'HTMLPurifier/AttrDef/Number.php'; require_once 'HTMLPurifier/AttrDef/Number.php';
/** /**
* Validates a Percentage as defined by the HTML spec. * Validates a Percentage as defined by the CSS spec.
* @note This also allows integer pixel values.
*/ */
class HTMLPurifier_AttrDef_Percentage extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_Percentage extends HTMLPurifier_AttrDef
{ {
/** /**
* Instance of HTMLPurifier_AttrDef_Number to defer pixel validation * Instance of HTMLPurifier_AttrDef_Number to defer number validation
*/ */
var $number_def; var $number_def;

View File

@ -12,6 +12,8 @@ require_once 'HTMLPurifier/AttrDef/Font.php';
require_once 'HTMLPurifier/AttrDef/Border.php'; require_once 'HTMLPurifier/AttrDef/Border.php';
require_once 'HTMLPurifier/AttrDef/ListStyle.php'; require_once 'HTMLPurifier/AttrDef/ListStyle.php';
require_once 'HTMLPurifier/AttrDef/CSSURI.php'; require_once 'HTMLPurifier/AttrDef/CSSURI.php';
require_once 'HTMLPurifier/AttrDef/BackgroundPosition.php';
require_once 'HTMLPurifier/AttrDef/Background.php';
/** /**
* Defines allowed CSS attributes and what their values are. * Defines allowed CSS attributes and what their values are.
@ -79,9 +81,7 @@ class HTMLPurifier_CSSDefinition
$this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum( $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
array('scroll', 'fixed') array('scroll', 'fixed')
); );
$this->info['background-position'] = new HTMLPurifier_AttrDef_BackgroundPosition();
// pending its own validator as a shorthand
$this->info['background'] =
$border_color = $border_color =
$this->info['border-top-color'] = $this->info['border-top-color'] =
@ -93,6 +93,8 @@ class HTMLPurifier_CSSDefinition
new HTMLPurifier_AttrDef_Color() new HTMLPurifier_AttrDef_Color()
)); ));
$this->info['background'] = new HTMLPurifier_AttrDef_Background($config);
$this->info['border-color'] = new HTMLPurifier_AttrDef_Multiple($border_color); $this->info['border-color'] = new HTMLPurifier_AttrDef_Multiple($border_color);
$border_width = $border_width =

View File

@ -48,14 +48,16 @@ class HTMLPurifier_Config
* Convenience constructor that creates a config object based on a mixed var * Convenience constructor that creates a config object based on a mixed var
* @static * @static
* @param mixed $config Variable that defines the state of the config * @param mixed $config Variable that defines the state of the config
* object. Can be: a HTMLPurifier_Config() object or * object. Can be: a HTMLPurifier_Config() object,
* an array of directives based on loadArray(). * an array of directives based on loadArray(),
* or a string filename of an ini file.
* @return Configured HTMLPurifier_Config object * @return Configured HTMLPurifier_Config object
*/ */
static function create($config) { static function create($config) {
if ($config instanceof HTMLPurifier_Config) return $config; if ($config instanceof HTMLPurifier_Config) return $config;
$ret = HTMLPurifier_Config::createDefault(); $ret = HTMLPurifier_Config::createDefault();
if (is_array($config)) $ret->loadArray($config); if (is_string($config)) $ret->loadIni($config);
elseif (is_array($config)) $ret->loadArray($config);
return $ret; return $ret;
} }
@ -75,12 +77,17 @@ class HTMLPurifier_Config
* @param $namespace String namespace * @param $namespace String namespace
* @param $key String key * @param $key String key
*/ */
function get($namespace, $key) { function get($namespace, $key, $from_alias = false) {
if (!isset($this->def->info[$namespace][$key])) { if (!isset($this->def->info[$namespace][$key])) {
trigger_error('Cannot retrieve value of undefined directive', trigger_error('Cannot retrieve value of undefined directive',
E_USER_WARNING); E_USER_WARNING);
return; return;
} }
if ($this->def->info[$namespace][$key]->class == 'alias') {
trigger_error('Cannot get value from aliased directive, use real name',
E_USER_ERROR);
return;
}
return $this->conf[$namespace][$key]; return $this->conf[$namespace][$key];
} }
@ -103,12 +110,22 @@ class HTMLPurifier_Config
* @param $key String key * @param $key String key
* @param $value Mixed value * @param $value Mixed value
*/ */
function set($namespace, $key, $value) { function set($namespace, $key, $value, $from_alias = false) {
if (!isset($this->def->info[$namespace][$key])) { if (!isset($this->def->info[$namespace][$key])) {
trigger_error('Cannot set undefined directive to value', trigger_error('Cannot set undefined directive to value',
E_USER_WARNING); E_USER_WARNING);
return; return;
} }
if ($this->def->info[$namespace][$key]->class == 'alias') {
if ($from_alias) {
trigger_error('Double-aliases not allowed, please fix '.
'ConfigSchema bug');
}
$this->set($this->def->info[$namespace][$key]->namespace,
$this->def->info[$namespace][$key]->name,
$value, true);
return;
}
$value = $this->def->validate( $value = $this->def->validate(
$value, $value,
$this->def->info[$namespace][$key]->type, $this->def->info[$namespace][$key]->type,
@ -178,6 +195,15 @@ class HTMLPurifier_Config
} }
} }
/**
* Loads configuration values from an ini file
* @param $filename Name of ini file
*/
function loadIni($filename) {
$array = parse_ini_file($filename, true);
$this->loadArray($array);
}
} }
?> ?>

View File

@ -106,6 +106,11 @@ class HTMLPurifier_ConfigSchema {
E_USER_ERROR); E_USER_ERROR);
return; return;
} }
if (empty($description)) {
trigger_error('Description must be non-empty',
E_USER_ERROR);
return;
}
if (isset($def->info[$namespace][$name])) { if (isset($def->info[$namespace][$name])) {
if ( if (
$def->info[$namespace][$name]->type !== $type || $def->info[$namespace][$name]->type !== $type ||
@ -161,6 +166,11 @@ class HTMLPurifier_ConfigSchema {
E_USER_ERROR); E_USER_ERROR);
return; return;
} }
if (empty($description)) {
trigger_error('Description must be non-empty',
E_USER_ERROR);
return;
}
$def->info[$namespace] = array(); $def->info[$namespace] = array();
$def->info_namespace[$namespace] = new HTMLPurifier_ConfigEntity_Namespace(); $def->info_namespace[$namespace] = new HTMLPurifier_ConfigEntity_Namespace();
$def->info_namespace[$namespace]->description = $description; $def->info_namespace[$namespace]->description = $description;
@ -216,12 +226,66 @@ class HTMLPurifier_ConfigSchema {
E_USER_ERROR); E_USER_ERROR);
return; return;
} }
if ($def->info[$namespace][$name]->allowed === true) { $directive =& $def->info[$namespace][$name];
$def->info[$namespace][$name]->allowed = array(); $type = $directive->type;
if ($type != 'string' && $type != 'istring') {
trigger_error('Cannot define allowed values for directive whose type is not string',
E_USER_ERROR);
return;
}
if ($directive->allowed === true) {
$directive->allowed = array();
} }
foreach ($allowed_values as $value) { foreach ($allowed_values as $value) {
$def->info[$namespace][$name]->allowed[$value] = true; $directive->allowed[$value] = true;
} }
if ($def->defaults[$namespace][$name] !== null &&
!isset($directive->allowed[$def->defaults[$namespace][$name]])) {
trigger_error('Default value must be in allowed range of variables',
E_USER_ERROR);
$directive->allowed = true; // undo undo!
return;
}
}
/**
* Defines a directive alias for backwards compatibility
* @static
* @param $namespace
* @param $name Directive that will be aliased
* @param $new_namespace
* @param $new_name Directive that the alias will be to
*/
static function defineAlias($namespace, $name, $new_namespace, $new_name) {
$def =& HTMLPurifier_ConfigSchema::instance();
if (!isset($def->info[$namespace])) {
trigger_error('Cannot define directive alias in undefined namespace',
E_USER_ERROR);
return;
}
if (!ctype_alnum($name)) {
trigger_error('Directive name must be alphanumeric',
E_USER_ERROR);
return;
}
if (isset($def->info[$namespace][$name])) {
trigger_error('Cannot define alias over directive',
E_USER_ERROR);
return;
}
if (!isset($def->info[$new_namespace][$new_name])) {
trigger_error('Cannot define alias to undefined directive',
E_USER_ERROR);
return;
}
if ($def->info[$new_namespace][$new_name]->class == 'alias') {
trigger_error('Cannot define alias to alias',
E_USER_ERROR);
return;
}
$def->info[$namespace][$name] =
new HTMLPurifier_ConfigEntity_DirectiveAlias(
$new_namespace, $new_name);
} }
/** /**
@ -318,13 +382,21 @@ class HTMLPurifier_ConfigSchema {
/** /**
* Base class for configuration entity * Base class for configuration entity
*/ */
class HTMLPurifier_ConfigEntity {} class HTMLPurifier_ConfigEntity {
var $class = false;
}
/** /**
* Structure object describing of a namespace * Structure object describing of a namespace
*/ */
class HTMLPurifier_ConfigEntity_Namespace extends HTMLPurifier_ConfigEntity { class HTMLPurifier_ConfigEntity_Namespace extends HTMLPurifier_ConfigEntity {
function HTMLPurifier_ConfigEntity_Namespace($description = null) {
$this->description = $description;
}
var $class = 'namespace';
/** /**
* String description of what kinds of directives go in this namespace. * String description of what kinds of directives go in this namespace.
*/ */
@ -339,15 +411,21 @@ class HTMLPurifier_ConfigEntity_Namespace extends HTMLPurifier_ConfigEntity {
class HTMLPurifier_ConfigEntity_Directive extends HTMLPurifier_ConfigEntity class HTMLPurifier_ConfigEntity_Directive extends HTMLPurifier_ConfigEntity
{ {
/** var $class = 'directive';
* Hash of value aliases, i.e. values that are equivalent.
*/
var $aliases = array();
/** function HTMLPurifier_ConfigEntity_Directive(
* Lookup table of allowed values of the element, bool true if all allowed. $type = null,
*/ $descriptions = null,
var $allowed = true; $allow_null = null,
$allowed = null,
$aliases = null
) {
if ( $type !== null) $this->type = $type;
if ($descriptions !== null) $this->descriptions = $descriptions;
if ( $allow_null !== null) $this->allow_null = $allow_null;
if ( $allowed !== null) $this->allowed = $allowed;
if ( $aliases !== null) $this->aliases = $aliases;
}
/** /**
* Allowed type of the directive. Values are: * Allowed type of the directive. Values are:
@ -364,16 +442,26 @@ class HTMLPurifier_ConfigEntity_Directive extends HTMLPurifier_ConfigEntity
var $type = 'mixed'; var $type = 'mixed';
/** /**
* Is null allowed? Has no affect for mixed type. * Plaintext descriptions of the configuration entity is. Organized by
* file and line number, so multiple descriptions are allowed.
*/
var $descriptions = array();
/**
* Is null allowed? Has no effect for mixed type.
* @bool * @bool
*/ */
var $allow_null = false; var $allow_null = false;
/** /**
* Plaintext descriptions of the configuration entity is. Organized by * Lookup table of allowed values of the element, bool true if all allowed.
* file and line number, so multiple descriptions are allowed.
*/ */
var $descriptions = array(); var $allowed = true;
/**
* Hash of value aliases, i.e. values that are equivalent.
*/
var $aliases = array();
/** /**
* Adds a description to the array * Adds a description to the array
@ -385,4 +473,26 @@ class HTMLPurifier_ConfigEntity_Directive extends HTMLPurifier_ConfigEntity
} }
/**
* Structure object describing a directive alias
*/
class HTMLPurifier_ConfigEntity_DirectiveAlias extends HTMLPurifier_ConfigEntity
{
var $class = 'alias';
/**
* Namespace being aliased to
*/
var $namespace;
/**
* Directive being aliased to
*/
var $name;
function HTMLPurifier_ConfigEntity_DirectiveAlias($namespace, $name) {
$this->namespace = $namespace;
$this->name = $name;
}
}
?> ?>

View File

@ -6,15 +6,29 @@ HTMLPurifier_ConfigSchema::define(
'Core', 'Encoding', 'utf-8', 'istring', 'Core', 'Encoding', 'utf-8', 'istring',
'If for some reason you are unable to convert all webpages to UTF-8, '. 'If for some reason you are unable to convert all webpages to UTF-8, '.
'you can use this directive as a stop-gap compatibility change to '. 'you can use this directive as a stop-gap compatibility change to '.
'let HTMLPurifier deal with non UTF-8 input. This technique has '. 'let HTML Purifier deal with non UTF-8 input. This technique has '.
'notable deficiencies: absolutely no characters outside of the selected '. 'notable deficiencies: absolutely no characters outside of the selected '.
'character encoding will be preserved, not even the ones that have '. 'character encoding will be preserved, not even the ones that have '.
'been ampersand escaped (this is due to a UTF-8 specific <em>feature</em> '. 'been ampersand escaped (this is due to a UTF-8 specific <em>feature</em> '.
'that automatically resolves all entities), making it pretty useless '. 'that automatically resolves all entities), making it pretty useless '.
'for anything except the most I18N-blind applications. This directive '. 'for anything except the most I18N-blind applications, although '.
'%Core.EscapeNonASCIICharacters offers fixes this trouble with '.
'another tradeoff. This directive '.
'only accepts ISO-8859-1 if iconv is not enabled.' 'only accepts ISO-8859-1 if iconv is not enabled.'
); );
HTMLPurifier_ConfigSchema::define(
'Core', 'EscapeNonASCIICharacters', false, 'bool',
'This directive overcomes a deficiency in %Core.Encoding by blindly '.
'converting all non-ASCII characters into decimal numeric entities before '.
'converting it to its native encoding. This means that even '.
'characters that can be expressed in the non-UTF-8 encoding will '.
'be entity-ized, which can be a real downer for encodings like Big5. '.
'It also assumes that the ASCII repetoire is available, although '.
'this is the case for almost all encodings. Anyway, use UTF-8! This '.
'directive has been available since 1.4.0.'
);
if ( !function_exists('iconv') ) { if ( !function_exists('iconv') ) {
// only encodings with native PHP support // only encodings with native PHP support
HTMLPurifier_ConfigSchema::defineAllowedValues( HTMLPurifier_ConfigSchema::defineAllowedValues(
@ -310,6 +324,7 @@ class HTMLPurifier_Encoder
} elseif ($encoding === 'iso-8859-1') { } elseif ($encoding === 'iso-8859-1') {
return @utf8_encode($str); return @utf8_encode($str);
} }
trigger_error('Encoding not supported', E_USER_ERROR);
} }
/** /**
@ -323,11 +338,63 @@ class HTMLPurifier_Encoder
if ($iconv === null) $iconv = function_exists('iconv'); if ($iconv === null) $iconv = function_exists('iconv');
$encoding = $config->get('Core', 'Encoding'); $encoding = $config->get('Core', 'Encoding');
if ($encoding === 'utf-8') return $str; if ($encoding === 'utf-8') return $str;
if ($config->get('Core', 'EscapeNonASCIICharacters')) {
$str = HTMLPurifier_Encoder::convertToASCIIDumbLossless($str);
}
if ($iconv && !$config->get('Test', 'ForceNoIconv')) { if ($iconv && !$config->get('Test', 'ForceNoIconv')) {
return @iconv('utf-8', $encoding . '//IGNORE', $str); return @iconv('utf-8', $encoding . '//IGNORE', $str);
} elseif ($encoding === 'iso-8859-1') { } elseif ($encoding === 'iso-8859-1') {
return @utf8_decode($str); return @utf8_decode($str);
} }
trigger_error('Encoding not supported', E_USER_ERROR);
}
/**
* Lossless (character-wise) conversion of HTML to ASCII
* @static
* @param $str UTF-8 string to be converted to ASCII
* @returns ASCII encoded string with non-ASCII character entity-ized
* @warning Adapted from MediaWiki, claiming fair use: this is a common
* algorithm. If you disagree with this license fudgery,
* implement it yourself.
* @note Uses decimal numeric entities since they are best supported.
* @note This is a DUMB function: it has no concept of keeping
* character entities that the projected character encoding
* can allow. We could possibly implement a smart version
* but that would require it to also know which Unicode
* codepoints the charset supported (not an easy task).
* @note Sort of with cleanUTF8() but it assumes that $str is
* well-formed UTF-8
*/
static function convertToASCIIDumbLossless($str) {
$bytesleft = 0;
$result = '';
$working = 0;
$len = strlen($str);
for( $i = 0; $i < $len; $i++ ) {
$bytevalue = ord( $str[$i] );
if( $bytevalue <= 0x7F ) { //0xxx xxxx
$result .= chr( $bytevalue );
$bytesleft = 0;
} elseif( $bytevalue <= 0xBF ) { //10xx xxxx
$working = $working << 6;
$working += ($bytevalue & 0x3F);
$bytesleft--;
if( $bytesleft <= 0 ) {
$result .= "&#" . $working . ";";
}
} elseif( $bytevalue <= 0xDF ) { //110x xxxx
$working = $bytevalue & 0x1F;
$bytesleft = 1;
} elseif( $bytevalue <= 0xEF ) { //1110 xxxx
$working = $bytevalue & 0x0F;
$bytesleft = 2;
} else { //1111 0xxx
$working = $bytevalue & 0x07;
$bytesleft = 3;
}
}
return $result;
} }

View File

@ -0,0 +1,39 @@
<?php
/**
* Represents a pre or post processing filter on HTML Purifier's output
*
* Sometimes, a little ad-hoc fixing of HTML has to be done before
* it gets sent through HTML Purifier: you can use filters to acheive
* this effect. For instance, YouTube videos can be preserved using
* this manner. You could have used a decorator for this task, but
* PHP's support for them is not terribly robust, so we're going
* to just loop through the filters.
*
* Filters should be exited first in, last out. If there are three filters,
* named 1, 2 and 3, the order of execution should go 1->preFilter,
* 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter,
* 1->postFilter.
*/
class HTMLPurifier_Filter
{
/**
* Name of the filter for identification purposes
*/
var $name;
/**
* Pre-processor function, handles HTML before HTML Purifier
*/
function preFilter($html, $config, &$context) {}
/**
* Post-processor function, handles HTML after HTML Purifier
*/
function postFilter($html, $config, &$context) {}
}
?>

View File

@ -0,0 +1,34 @@
<?php
require_once 'HTMLPurifier/Filter.php';
class HTMLPurifier_Filter_YouTube extends HTMLPurifier_Filter
{
var $name = 'YouTube preservation';
function preFilter($html, $config, &$context) {
$pre_regex = '#<object[^>]+>.+?'.
'http://www.youtube.com/v/([A-Za-z0-9]+).+?</object>#';
$pre_replace = '<span class="youtube-embed">\1</span>';
return preg_replace($pre_regex, $pre_replace, $html);
}
function postFilter($html, $config, &$context) {
$post_regex = '#<span class="youtube-embed">([A-Za-z0-9]+)</span>#';
$post_replace = '<object width="425" height="350" '.
'data="http://www.youtube.com/v/\1">'.
'<param name="movie" value="http://www.youtube.com/v/\1"></param>'.
'<param name="wmode" value="transparent"></param>'.
'<!--[if IE]>'.
'<embed src="http://www.youtube.com/v/\1"'.
'type="application/x-shockwave-flash"'.
'wmode="transparent" width="425" height="350" />'.
'<![endif]-->'.
'</object>';
return preg_replace($post_regex, $post_replace, $html);
}
}
?>

View File

@ -151,7 +151,7 @@ class HTMLPurifier_Lexer
$lexer = $prototype; $lexer = $prototype;
} }
if (empty($lexer)) { if (empty($lexer)) {
if (version_compare(PHP_VERSION, '5', '>=')) { if (class_exists('DOMDocument')) { // check for DOM support
require_once 'HTMLPurifier/Lexer/DOMLex.php'; require_once 'HTMLPurifier/Lexer/DOMLex.php';
$lexer = new HTMLPurifier_Lexer_DOMLex(); $lexer = new HTMLPurifier_Lexer_DOMLex();
} else { } else {

View File

@ -15,34 +15,13 @@ echo '<?xml version="1.0" encoding="UTF-8" ?>';
<h1>HTML Purifier Preserve YouTube Smoketest</h1> <h1>HTML Purifier Preserve YouTube Smoketest</h1>
<?php <?php
class HTMLPurifierX_PreserveYouTube extends HTMLPurifier
{
function purify($html, $config = null) {
$pre_regex = '#<object[^>]+>.+?'.
'http://www.youtube.com/v/([A-Za-z0-9]+).+?</object>#';
$pre_replace = '<span class="youtube-embed">\1</span>';
$html = preg_replace($pre_regex, $pre_replace, $html);
$html = parent::purify($html, $config);
$post_regex = '#<span class="youtube-embed">([A-Za-z0-9]+)</span>#';
$post_replace = '<object width="425" height="350" '.
'data="http://www.youtube.com/v/\1">'.
'<param name="movie" value="http://www.youtube.com/v/\1"></param>'.
'<param name="wmode" value="transparent"></param>'.
'<!--[if IE]>'.
'<embed src="http://www.youtube.com/v/\1"'.
'type="application/x-shockwave-flash"'.
'wmode="transparent" width="425" height="350" />'.
'<![endif]-->'.
'</object>';
$html = preg_replace($post_regex, $post_replace, $html);
return $html;
}
}
$string = '<object width="425" height="350"><param name="movie" value="http://www.youtube.com/v/JzqumbhfxRo"></param><param name="wmode" value="transparent"></param><embed src="http://www.youtube.com/v/JzqumbhfxRo" type="application/x-shockwave-flash" wmode="transparent" width="425" height="350"></embed></object>'; $string = '<object width="425" height="350"><param name="movie" value="http://www.youtube.com/v/JzqumbhfxRo"></param><param name="wmode" value="transparent"></param><embed src="http://www.youtube.com/v/JzqumbhfxRo" type="application/x-shockwave-flash" wmode="transparent" width="425" height="350"></embed></object>';
$regular_purifier = new HTMLPurifier(); $regular_purifier = new HTMLPurifier();
$youtube_purifier = new HTMLPurifierX_PreserveYouTube();
$youtube_purifier = new HTMLPurifier();
require_once 'HTMLPurifier/Filter/YouTube.php';
$youtube_purifier->addFilter(new HTMLPurifier_Filter_YouTube());
?> ?>
<h2>Unpurified</h2> <h2>Unpurified</h2>

View File

@ -0,0 +1,71 @@
<?php
require_once 'HTMLPurifier/AttrDefHarness.php';
require_once 'HTMLPurifier/AttrDef/BackgroundPosition.php';
class HTMLPurifier_AttrDef_BackgroundPositionTest extends HTMLPurifier_AttrDefHarness
{
function test() {
$this->def = new HTMLPurifier_AttrDef_BackgroundPosition();
// explicitly cited in spec
$this->assertDef('0% 0%');
$this->assertDef('100% 100%');
$this->assertDef('14% 84%');
$this->assertDef('2cm 1cm');
$this->assertDef('top');
$this->assertDef('left');
$this->assertDef('center');
$this->assertDef('right');
$this->assertDef('bottom');
$this->assertDef('left top');
$this->assertDef('center top');
$this->assertDef('right top');
$this->assertDef('left center');
$this->assertDef('right center');
$this->assertDef('left bottom');
$this->assertDef('center bottom');
$this->assertDef('right bottom');
// reordered due to internal impl details
$this->assertDef('top left', 'left top');
$this->assertDef('top center', 'center top');
$this->assertDef('top right', 'right top');
$this->assertDef('center left', 'left center');
$this->assertDef('center center', 'center'); // two centers collide
$this->assertDef('center right', 'right center');
$this->assertDef('bottom left', 'left bottom');
$this->assertDef('bottom center', 'center bottom');
$this->assertDef('bottom right', 'right bottom');
// more cases from the defined syntax
$this->assertDef('1.32in 4ex');
$this->assertDef('-14% -84.65%');
$this->assertDef('-1in -4ex');
$this->assertDef('-1pc 2.3%');
// keyword mixing
$this->assertDef('3em top');
$this->assertDef('left 50%');
// fixable keyword mixing
$this->assertDef('top 3em', '3em top');
$this->assertDef('50% left', 'left 50%');
// whitespace collapsing
$this->assertDef('3em top', '3em top');
$this->assertDef("left\n \t foo ", 'left');
// invalid uses (we're going to be strict on these)
$this->assertDef('foo bar', false);
$this->assertDef('left left', 'left');
$this->assertDef('left right top bottom center left', 'left bottom');
$this->assertDef('0fr 9%', '9%');
}
}
?>

View File

@ -0,0 +1,21 @@
<?php
require_once 'HTMLPurifier/AttrDefHarness.php';
require_once 'HTMLPurifier/AttrDef/Background.php';
class HTMLPurifier_AttrDef_BackgroundTest extends HTMLPurifier_AttrDefHarness
{
function test() {
$this->def = new HTMLPurifier_AttrDef_Background(HTMLPurifier_Config::createDefault());
$valid = '#333 url(chess.png) repeat fixed 50% top';
$this->assertDef($valid);
$this->assertDef('url("chess.png") #333 50% top repeat fixed', $valid);
}
}
?>

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'HTMLPurifier/AttrDef/Border.php'; require_once 'HTMLPurifier/AttrDef/Border.php';
require_once 'HTMLPurifier/AttrDef/PixelsTest.php';
class HTMLPurifier_AttrDef_BorderTest extends HTMLPurifier_AttrDef_PixelsTest class HTMLPurifier_AttrDef_BorderTest extends HTMLPurifier_AttrDef_PixelsTest
{ {

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'HTMLPurifier/AttrDef/CSSLength.php'; require_once 'HTMLPurifier/AttrDef/CSSLength.php';
require_once 'HTMLPurifier/AttrDefHarness.php';
class HTMLPurifier_AttrDef_CSSLengthTest extends HTMLPurifier_AttrDefHarness class HTMLPurifier_AttrDef_CSSLengthTest extends HTMLPurifier_AttrDefHarness
{ {
@ -21,6 +22,8 @@ class HTMLPurifier_AttrDef_CSSLengthTest extends HTMLPurifier_AttrDefHarness
$this->assertDef('3pt'); $this->assertDef('3pt');
$this->assertDef('3pc'); $this->assertDef('3pc');
$this->assertDef('3PX', '3px');
$this->assertDef('3', false); $this->assertDef('3', false);
$this->assertDef('3miles', false); $this->assertDef('3miles', false);

View File

@ -25,7 +25,7 @@ class HTMLPurifier_AttrDef_CSSTest extends HTMLPurifier_AttrDefHarness
$this->assertDef('text-transform:capitalize;'); $this->assertDef('text-transform:capitalize;');
$this->assertDef('background-color:rgb(0,0,255);'); $this->assertDef('background-color:rgb(0,0,255);');
$this->assertDef('background-color:transparent;'); $this->assertDef('background-color:transparent;');
$this->assertDef('background:#FF9;'); $this->assertDef('background:#333 url(chess.png) repeat fixed 50% top;');
$this->assertDef('color:#F00;'); $this->assertDef('color:#F00;');
$this->assertDef('border-top-color:#F00;'); $this->assertDef('border-top-color:#F00;');
$this->assertDef('border-color:#F00 #FF0;'); $this->assertDef('border-color:#F00 #FF0;');
@ -78,6 +78,7 @@ class HTMLPurifier_AttrDef_CSSTest extends HTMLPurifier_AttrDefHarness
$this->assertDef('background-image:none;'); $this->assertDef('background-image:none;');
$this->assertDef('background-repeat:repeat-y;'); $this->assertDef('background-repeat:repeat-y;');
$this->assertDef('background-attachment:fixed;'); $this->assertDef('background-attachment:fixed;');
$this->assertDef('background-position:left 90%;');
// duplicates // duplicates
$this->assertDef('text-align:right;text-align:left;', $this->assertDef('text-align:right;text-align:left;',

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'HTMLPurifier/AttrDef/Color.php'; require_once 'HTMLPurifier/AttrDef/Color.php';
require_once 'HTMLPurifier/AttrDefHarness.php';
class HTMLPurifier_AttrDef_ColorTest extends HTMLPurifier_AttrDefHarness class HTMLPurifier_AttrDef_ColorTest extends HTMLPurifier_AttrDefHarness
{ {

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'HTMLPurifier/AttrDef/Composite.php'; require_once 'HTMLPurifier/AttrDef/Composite.php';
require_once 'HTMLPurifier/AttrDefHarness.php';
class HTMLPurifier_AttrDef_Composite_Testable extends class HTMLPurifier_AttrDef_Composite_Testable extends
HTMLPurifier_AttrDef_Composite HTMLPurifier_AttrDef_Composite

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'HTMLPurifier/AttrDef/Email.php'; require_once 'HTMLPurifier/AttrDef/Email.php';
require_once 'HTMLPurifier/AttrDefHarness.php';
class HTMLPurifier_AttrDef_EmailHarness extends HTMLPurifier_AttrDefHarness class HTMLPurifier_AttrDef_EmailHarness extends HTMLPurifier_AttrDefHarness
{ {

View File

@ -66,13 +66,11 @@ class HTMLPurifier_AttrDef_IDTest extends HTMLPurifier_AttrDefHarness
$this->assertDef('user_story95_alas'); $this->assertDef('user_story95_alas');
$this->assertDef('user_alas', 'user_story95_user_alas'); // ! $this->assertDef('user_alas', 'user_story95_user_alas'); // !
// no effect when IDPrefix isn't set
$this->config->set('Attr', 'IDPrefix', ''); $this->config->set('Attr', 'IDPrefix', '');
$this->assertDef('amherst'); // no affect when IDPrefix isn't set $this->expectError('%Attr.IDPrefixLocal cannot be used unless '.
$this->assertError('%Attr.IDPrefixLocal cannot be used unless '.
'%Attr.IDPrefix is set'); '%Attr.IDPrefix is set');
// SimpleTest has a bug and throws a sprintf error $this->assertDef('amherst');
// $this->assertNoErrors();
$this->swallowErrors();
} }

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'HTMLPurifier/AttrDef/Integer.php'; require_once 'HTMLPurifier/AttrDef/Integer.php';
require_once 'HTMLPurifier/AttrDefHarness.php';
class HTMLPurifier_AttrDef_IntegerTest extends HTMLPurifier_AttrDefHarness class HTMLPurifier_AttrDef_IntegerTest extends HTMLPurifier_AttrDefHarness
{ {

View File

@ -1,7 +1,10 @@
<?php <?php
require_once 'HTMLPurifier/AttrDef/Integer.php';
require_once 'HTMLPurifier/AttrDef/Multiple.php'; require_once 'HTMLPurifier/AttrDef/Multiple.php';
require_once 'HTMLPurifier/AttrDefHarness.php';
// borrowed for the sakes of this test
require_once 'HTMLPurifier/AttrDef/Integer.php';
class HTMLPurifier_AttrDef_MultipleTest extends HTMLPurifier_AttrDefHarness class HTMLPurifier_AttrDef_MultipleTest extends HTMLPurifier_AttrDefHarness
{ {

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'HTMLPurifier/AttrDef/Number.php'; require_once 'HTMLPurifier/AttrDef/Number.php';
require_once 'HTMLPurifier/AttrDefHarness.php';
class HTMLPurifier_AttrDef_NumberTest extends HTMLPurifier_AttrDefHarness class HTMLPurifier_AttrDef_NumberTest extends HTMLPurifier_AttrDefHarness
{ {

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'HTMLPurifier/AttrDef/Percentage.php'; require_once 'HTMLPurifier/AttrDef/Percentage.php';
require_once 'HTMLPurifier/AttrDefHarness.php';
class HTMLPurifier_AttrDef_PercentageTest extends HTMLPurifier_AttrDefHarness class HTMLPurifier_AttrDef_PercentageTest extends HTMLPurifier_AttrDefHarness
{ {

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'HTMLPurifier/AttrTransform/BdoDir.php'; require_once 'HTMLPurifier/AttrTransform/BdoDir.php';
require_once 'HTMLPurifier/AttrTransformHarness.php';
class HTMLPurifier_AttrTransform_BdoDirTest extends HTMLPurifier_AttrTransformHarness class HTMLPurifier_AttrTransform_BdoDirTest extends HTMLPurifier_AttrTransformHarness
{ {

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'HTMLPurifier/AttrTransform/ImgRequired.php'; require_once 'HTMLPurifier/AttrTransform/ImgRequired.php';
require_once 'HTMLPurifier/AttrTransformHarness.php';
class HTMLPurifier_AttrTransform_ImgRequiredTest extends HTMLPurifier_AttrTransformHarness class HTMLPurifier_AttrTransform_ImgRequiredTest extends HTMLPurifier_AttrTransformHarness
{ {

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'HTMLPurifier/AttrTransform/TextAlign.php'; require_once 'HTMLPurifier/AttrTransform/TextAlign.php';
require_once 'HTMLPurifier/AttrTransformHarness.php';
class HTMLPurifier_AttrTransform_TextAlignTest extends HTMLPurifier_AttrTransformHarness class HTMLPurifier_AttrTransform_TextAlignTest extends HTMLPurifier_AttrTransformHarness
{ {

View File

@ -38,10 +38,9 @@ extends HTMLPurifier_ChildDefHarness
$this->assertResult('Needs wrap', '<div>Needs wrap</div>', $this->assertResult('Needs wrap', '<div>Needs wrap</div>',
array('HTML.BlockWrapper' => 'div')); array('HTML.BlockWrapper' => 'div'));
$this->expectError('Cannot use non-block element as block wrapper.');
$this->assertResult('Needs wrap', '<p>Needs wrap</p>', $this->assertResult('Needs wrap', '<p>Needs wrap</p>',
array('HTML.BlockWrapper' => 'dav')); array('HTML.BlockWrapper' => 'dav'));
$this->assertError('Cannot use non-block element as block wrapper.');
$this->assertNoErrors();
} }

View File

@ -2,10 +2,26 @@
require_once 'HTMLPurifier/ConfigSchema.php'; require_once 'HTMLPurifier/ConfigSchema.php';
if (!class_exists('CS')) {
class CS extends HTMLPurifier_ConfigSchema {}
}
class HTMLPurifier_ConfigSchemaTest extends UnitTestCase class HTMLPurifier_ConfigSchemaTest extends UnitTestCase
{ {
/**
* Munged name of current file.
*/
var $file;
/**
* Copy of the real ConfigSchema to revert to.
*/
var $old_copy; var $old_copy;
/**
* Copy of dummy ConfigSchema for testing purposes.
*/
var $our_copy; var $our_copy;
function setUp() { function setUp() {
@ -18,239 +34,214 @@ class HTMLPurifier_ConfigSchemaTest extends UnitTestCase
$this->old_copy = HTMLPurifier_ConfigSchema::instance(); $this->old_copy = HTMLPurifier_ConfigSchema::instance();
// put in our copy, and reassign to the REAL reference // put in our copy, and reassign to the REAL reference
$this->our_copy =& HTMLPurifier_ConfigSchema::instance($our_copy); $this->our_copy =& HTMLPurifier_ConfigSchema::instance($our_copy);
$this->file = $this->our_copy->mungeFilename(__FILE__);
} }
function tearDown() { function tearDown() {
// testing is done, restore the old copy // testing is done, restore the old copy
HTMLPurifier_ConfigSchema::instance($this->old_copy); HTMLPurifier_ConfigSchema::instance($this->old_copy);
tally_errors();
} }
function testNormal() { function test_defineNamespace() {
CS::defineNamespace('http', $d = 'This is an internet protocol.');
$file = $this->our_copy->mungeFilename(__FILE__);
// define a namespace
$description = 'Configuration that is always available.';
HTMLPurifier_ConfigSchema::defineNamespace(
'Core', $description
);
$this->assertIdentical($this->our_copy->defaults, array(
'Core' => array()
));
$this->assertIdentical($this->our_copy->info, array(
'Core' => array()
));
$namespace = new HTMLPurifier_ConfigEntity_Namespace();
$namespace->description = $description;
$this->assertIdentical($this->our_copy->info_namespace, array( $this->assertIdentical($this->our_copy->info_namespace, array(
'Core' => $namespace 'http' => new HTMLPurifier_ConfigEntity_Namespace($d)
)); ));
$this->expectError('Cannot redefine namespace');
CS::defineNamespace('http', 'It is used to serve webpages.');
$this->expectError('Namespace name must be alphanumeric');
CS::defineNamespace('ssh+http', 'This http is tunneled through SSH.');
// define a directive $this->expectError('Description must be non-empty');
$description = 'This is a description of the directive.'; CS::defineNamespace('ftp', null);
HTMLPurifier_ConfigSchema::define( }
'Core', 'Name', 'default value', 'string',
$description
); $line = __LINE__;
$this->assertIdentical($this->our_copy->defaults, array(
'Core' => array(
'Name' => 'default value'
)
));
$directive = new HTMLPurifier_ConfigEntity_Directive();
$directive->type = 'string';
$directive->addDescription($file, $line, $description);
$this->assertIdentical($this->our_copy->info, array(
'Core' => array(
'Name' => $directive
)
));
function test_define() {
CS::defineNamespace('Car', 'Automobiles, those gas-guzzlers!');
CS::define('Car', 'Seats', 5, 'int', $d = 'Standard issue.'); $l = __LINE__;
// define a directive in an undefined namespace $this->assertIdentical($this->our_copy->defaults['Car']['Seats'], 5);
HTMLPurifier_ConfigSchema::define( $this->assertIdentical($this->our_copy->info['Car']['Seats'],
'Extension', 'Name', false, 'bool', new HTMLPurifier_ConfigEntity_Directive('int',
'This is for an extension, but we have not defined its namespace!' array($this->file => array($l => $d))
);
$this->assertError('Cannot define directive for undefined namespace');
$this->assertNoErrors();
// redefine a value in a valid manner
$description = 'Alternative configuration definition';
HTMLPurifier_ConfigSchema::define(
'Core', 'Name', 'default value', 'string',
$description
); $line = __LINE__;
$this->assertNoErrors();
$directive->addDescription($file, $line, $description);
$this->assertIdentical($this->our_copy->info, array(
'Core' => array(
'Name' => $directive
)
));
// redefine a directive in an invalid manner
HTMLPurifier_ConfigSchema::define(
'Core', 'Name', 'different default', 'string',
'Inconsistent default or type, cannot redefine'
);
$this->assertError('Inconsistent default or type, cannot redefine');
$this->assertNoErrors();
// make an enumeration
HTMLPurifier_ConfigSchema::defineAllowedValues(
'Core', 'Name', array(
'Real Value',
'Real Value 2'
) )
); );
$directive->allowed = array(
'Real Value' => true,
'Real Value 2' => true
);
$this->assertIdentical($this->our_copy->info, array(
'Core' => array(
'Name' => $directive
)
));
CS::define('Car', 'Age', null, 'int/null', $d = 'Not always known.'); $l = __LINE__;
$this->assertIdentical($this->our_copy->defaults['Car']['Age'], null);
// redefinition of enumeration is cumulative $this->assertIdentical($this->our_copy->info['Car']['Age'],
HTMLPurifier_ConfigSchema::defineAllowedValues( new HTMLPurifier_ConfigEntity_Directive('int',
'Core', 'Name', array( array($this->file => array($l => $d)), true
'Real Value 3',
) )
); );
$directive->allowed['Real Value 3'] = true;
$this->assertIdentical($this->our_copy->info, array(
'Core' => array(
'Name' => $directive
)
));
$this->expectError('Cannot define directive for undefined namespace');
CS::define('Train', 'Cars', 10, 'int', 'Including the caboose.');
$this->expectError('Directive name must be alphanumeric');
CS::define('Car', 'Is it shiny?', true, 'bool', 'Indicates regular waxing.');
// cannot define enumeration for undefined directive $this->expectError('Invalid type for configuration directive');
HTMLPurifier_ConfigSchema::defineAllowedValues( CS::define('Car', 'Efficiency', 50, 'mpg', 'The higher the better.');
'Core', 'Foobar', array(
'Real Value 9', $this->expectError('Default value does not match directive type');
CS::define('Car', 'Producer', 'Ford', 'int', 'ID of the company that made the car.');
$this->expectError('Description must be non-empty');
CS::define('Car', 'ComplexAttribute', 'lawyers', 'istring', null);
}
function testRedefinition_define() {
CS::defineNamespace('Cat', 'Belongs to Schrodinger.');
CS::define('Cat', 'Dead', false, 'bool', $d1 = 'Well, is it?'); $l1 = __LINE__;
CS::define('Cat', 'Dead', false, 'bool', $d2 = 'It is difficult to say.'); $l2 = __LINE__;
$this->assertIdentical($this->our_copy->defaults['Cat']['Dead'], false);
$this->assertIdentical($this->our_copy->info['Cat']['Dead'],
new HTMLPurifier_ConfigEntity_Directive('bool',
array($this->file => array($l1 => $d1, $l2 => $d2))
) )
); );
$this->assertError('Cannot define allowed values for undefined directive');
$this->assertNoErrors();
$this->expectError('Inconsistent default or type, cannot redefine');
CS::define('Cat', 'Dead', true, 'bool', 'Quantum mechanics does not know.');
$this->expectError('Inconsistent default or type, cannot redefine');
CS::define('Cat', 'Dead', 'maybe', 'string', 'Perhaps if we look we will know.');
}
// test defining value aliases for an enumerated value function test_defineAllowedValues() {
HTMLPurifier_ConfigSchema::defineValueAliases( CS::defineNamespace('QuantumNumber', 'D');
'Core', 'Name', array( CS::define('QuantumNumber', 'Spin', 0.5, 'float',
'Aliased Value' => 'Real Value' 'Spin of particle. Fourth quantum number, represented by s.');
CS::define('QuantumNumber', 'Current', 's', 'string',
'Currently selected quantum number.');
CS::define('QuantumNumber', 'Difficulty', null, 'string/null', $d = 'How hard are the problems?'); $l = __LINE__;
CS::defineAllowedValues( // okay, since default is null
'QuantumNumber', 'Difficulty', array('easy', 'medium', 'hard')
);
$this->assertIdentical($this->our_copy->defaults['QuantumNumber']['Difficulty'], null);
$this->assertIdentical($this->our_copy->info['QuantumNumber']['Difficulty'],
new HTMLPurifier_ConfigEntity_Directive(
'string',
array($this->file => array($l => $d)),
true,
array(
'easy' => true,
'medium' => true,
'hard' => true
)
) )
); );
$directive->aliases['Aliased Value'] = 'Real Value';
$this->assertIdentical($this->our_copy->info, array(
'Core' => array(
'Name' => $directive
)
));
$this->expectError('Cannot define allowed values for undefined directive');
CS::defineAllowedValues(
'SpaceTime', 'Symmetry', array('time', 'spatial', 'projective')
);
$this->expectError('Cannot define allowed values for directive whose type is not string');
CS::defineAllowedValues(
'QuantumNumber', 'Spin', array(0.5, -0.5)
);
// redefine should be cumulative $this->expectError('Default value must be in allowed range of variables');
HTMLPurifier_ConfigSchema::defineValueAliases( CS::defineAllowedValues(
'Core', 'Name', array( 'QuantumNumber', 'Current', array('n', 'l', 'm') // forgot s!
'Aliased Value 2' => 'Real Value 2' );
}
function test_defineValueAliases() {
CS::defineNamespace('Abbrev', 'Stuff on abbreviations.');
CS::define('Abbrev', 'HTH', 'Happy to Help', 'string', $d = 'Three-letters'); $l = __LINE__;
CS::defineAllowedValues(
'Abbrev', 'HTH', array(
'Happy to Help',
'Hope that Helps',
'HAIL THE HAND!'
) )
); );
$directive->aliases['Aliased Value 2'] = 'Real Value 2'; CS::defineValueAliases(
$this->assertIdentical($this->our_copy->info, array( 'Abbrev', 'HTH', array(
'Core' => array( 'happy' => 'Happy to Help',
'Name' => $directive 'hope' => 'Hope that Helps'
)
));
// cannot create alias to not-allowed value
HTMLPurifier_ConfigSchema::defineValueAliases(
'Core', 'Name', array(
'Aliased Value 3' => 'Invalid Value'
) )
); );
$this->assertError('Cannot define alias to value that is not allowed'); CS::defineValueAliases( // delayed addition
$this->assertNoErrors(); 'Abbrev', 'HTH', array(
'hail' => 'HAIL THE HAND!'
// cannot create alias for already allowed value
HTMLPurifier_ConfigSchema::defineValueAliases(
'Core', 'Name', array(
'Real Value' => 'Real Value 2'
) )
); );
$this->assertError('Cannot define alias over allowed value');
$this->assertNoErrors();
$this->assertIdentical($this->our_copy->defaults['Abbrev']['HTH'], 'Happy to Help');
$this->assertIdentical($this->our_copy->info['Abbrev']['HTH'],
// define a directive with an invalid type new HTMLPurifier_ConfigEntity_Directive(
HTMLPurifier_ConfigSchema::define( 'string',
'Core', 'Foobar', false, 'omen', array($this->file => array($l => $d)),
'Omen is not a valid type, so we reject this.' false,
array(
'Happy to Help' => true,
'Hope that Helps' => true,
'HAIL THE HAND!' => true
),
array(
'happy' => 'Happy to Help',
'hope' => 'Hope that Helps',
'hail' => 'HAIL THE HAND!'
)
)
); );
$this->assertError('Invalid type for configuration directive'); $this->expectError('Cannot define alias to value that is not allowed');
$this->assertNoErrors(); CS::defineValueAliases(
'Abbrev', 'HTH', array(
'head' => 'Head to Head'
)
// define a directive with inconsistent type
HTMLPurifier_ConfigSchema::define(
'Core', 'Foobaz', 10, 'string',
'If we say string, we should mean it, not integer 10.'
); );
$this->assertError('Default value does not match directive type'); $this->expectError('Cannot define alias over allowed value');
$this->assertNoErrors(); CS::defineValueAliases(
'Abbrev', 'HTH', array(
'Hope that Helps' => 'Happy to Help'
)
// define a directive that allows null
HTMLPurifier_ConfigSchema::define(
'Core', 'Foobaz', null, 'string/null',
'Nulls are allowed if you add on /null, cool huh?'
); );
$this->assertNoErrors(); }
function testAlias() {
CS::defineNamespace('Home', 'Sweet home.');
CS::define('Home', 'Rug', 3, 'int', 'ID.');
CS::defineAlias('Home', 'Carpet', 'Home', 'Rug');
// define a directive with bad characters $this->assertTrue(!isset($this->our_copy->defaults['Home']['Carpet']));
HTMLPurifier_ConfigSchema::define( $this->assertIdentical($this->our_copy->info['Home']['Carpet'],
'Core', 'Core.Attr', 10, 'int', new HTMLPurifier_ConfigEntity_DirectiveAlias('Home', 'Rug')
'No periods! >:-('
); );
$this->assertError('Directive name must be alphanumeric'); $this->expectError('Cannot define directive alias in undefined namespace');
$this->assertNoErrors(); CS::defineAlias('Store', 'Rug', 'Home', 'Rug');
// define a namespace with bad characters $this->expectError('Directive name must be alphanumeric');
HTMLPurifier_ConfigSchema::defineNamespace( CS::defineAlias('Home', 'R.g', 'Home', 'Rug');
'Foobar&Gromit', $description
);
$this->assertError('Namespace name must be alphanumeric'); CS::define('Home', 'Rugger', 'Bob Max', 'string', 'Name of.');
$this->assertNoErrors(); $this->expectError('Cannot define alias over directive');
CS::defineAlias('Home', 'Rugger', 'Home', 'Rug');
$this->expectError('Cannot define alias to undefined directive');
CS::defineAlias('Home', 'Rug2', 'Home', 'Rugavan');
$this->expectError('Cannot define alias to alias');
CS::defineAlias('Home', 'Rug2', 'Home', 'Carpet');
} }
function assertValid($var, $type, $ret = null) { function assertValid($var, $type, $ret = null) {
@ -270,25 +261,32 @@ class HTMLPurifier_ConfigSchemaTest extends UnitTestCase
$this->assertValid('foobar', 'string'); $this->assertValid('foobar', 'string');
$this->assertValid('FOOBAR', 'istring', 'foobar'); $this->assertValid('FOOBAR', 'istring', 'foobar');
$this->assertValid(34, 'int'); $this->assertValid(34, 'int');
$this->assertValid(3.34, 'float'); $this->assertValid(3.34, 'float');
$this->assertValid(false, 'bool'); $this->assertValid(false, 'bool');
$this->assertValid(0, 'bool', false); $this->assertValid(0, 'bool', false);
$this->assertValid(1, 'bool', true); $this->assertValid(1, 'bool', true);
$this->assertInvalid(34, 'bool');
$this->assertInvalid(null, 'bool');
$this->assertValid(array('1', '2', '3'), 'list');
$this->assertValid(array('1' => true, '2' => true), 'lookup');
$this->assertValid(array('1', '2'), 'lookup', array('1' => true, '2' => true));
$this->assertValid(array('foo' => 'bar'), 'hash');
$this->assertInvalid(array(0 => 'moo'), 'hash');
$this->assertValid(array(1 => 'moo'), 'hash');
$this->assertValid(23, 'mixed');
$this->assertValid('foo,bar, cow', 'list', array('foo', 'bar', 'cow'));
$this->assertValid('foo,bar', 'lookup', array('foo' => true, 'bar' => true));
$this->assertValid('true', 'bool', true); $this->assertValid('true', 'bool', true);
$this->assertValid('false', 'bool', false); $this->assertValid('false', 'bool', false);
$this->assertValid('1', 'bool', true); $this->assertValid('1', 'bool', true);
$this->assertInvalid(34, 'bool');
$this->assertInvalid(null, 'bool');
$this->assertValid(array('1', '2', '3'), 'list');
$this->assertValid('foo,bar, cow', 'list', array('foo', 'bar', 'cow'));
$this->assertValid(array('1' => true, '2' => true), 'lookup');
$this->assertValid(array('1', '2'), 'lookup', array('1' => true, '2' => true));
$this->assertValid('foo,bar', 'lookup', array('foo' => true, 'bar' => true));
$this->assertValid(array('foo' => 'bar'), 'hash');
$this->assertValid(array(1 => 'moo'), 'hash');
$this->assertInvalid(array(0 => 'moo'), 'hash');
$this->assertValid(23, 'mixed');
} }
@ -318,12 +316,12 @@ class HTMLPurifier_ConfigSchemaTest extends UnitTestCase
function testMungeFilename() { function testMungeFilename() {
$this->assertMungeFilename( $this->assertMungeFilename(
'C:\\php\\libs\\htmlpurifier\\library\\HTMLPurifier\\AttrDef.php', 'C:\\php\\My Libraries\\htmlpurifier\\library\\HTMLPurifier\\AttrDef.php',
'HTMLPurifier/AttrDef.php' 'HTMLPurifier/AttrDef.php'
); );
$this->assertMungeFilename( $this->assertMungeFilename(
'C:\\php\\libs\\htmlpurifier\\library\\HTMLPurifier.php', 'C:\\php\\My Libraries\\htmlpurifier\\library\\HTMLPurifier.php',
'HTMLPurifier.php' 'HTMLPurifier.php'
); );

View File

@ -0,0 +1,2 @@
[Cake]
Sprinkles = 42

View File

@ -0,0 +1,4 @@
[Shortcut]
Copy = q
Cut = t
Paste = p

View File

@ -2,6 +2,10 @@
require_once 'HTMLPurifier/Config.php'; require_once 'HTMLPurifier/Config.php';
if (!class_exists('CS')) {
class CS extends HTMLPurifier_ConfigSchema {}
}
class HTMLPurifier_ConfigTest extends UnitTestCase class HTMLPurifier_ConfigTest extends UnitTestCase
{ {
@ -16,109 +20,199 @@ class HTMLPurifier_ConfigTest extends UnitTestCase
function tearDown() { function tearDown() {
HTMLPurifier_ConfigSchema::instance($this->old_copy); HTMLPurifier_ConfigSchema::instance($this->old_copy);
tally_errors();
} }
function test() { // test functionality based on ConfigSchema
HTMLPurifier_ConfigSchema::defineNamespace('Core', 'Corestuff'); function testNormal() {
HTMLPurifier_ConfigSchema::defineNamespace('Attr', 'Attributes'); CS::defineNamespace('Element', 'Chemical substances that cannot be further decomposed');
HTMLPurifier_ConfigSchema::defineNamespace('Extension', 'Extensible');
HTMLPurifier_ConfigSchema::define( CS::define('Element', 'Abbr', 'H', 'string', 'Abbreviation of element name.');
'Core', 'Key', false, 'bool', 'A boolean directive.' CS::define('Element', 'Name', 'hydrogen', 'istring', 'Full name of atoms.');
); CS::define('Element', 'Number', 1, 'int', 'Atomic number, is identity.');
HTMLPurifier_ConfigSchema::define( CS::define('Element', 'Mass', 1.00794, 'float', 'Atomic mass.');
'Attr', 'Key', 42, 'int', 'An integer directive.' CS::define('Element', 'Radioactive', false, 'bool', 'Does it have rapid decay?');
); CS::define('Element', 'Isotopes', array(1 => true, 2 => true, 3 => true), 'lookup',
HTMLPurifier_ConfigSchema::define( 'What numbers of neutrons for this element have been observed?');
'Extension', 'Pert', 'foo', 'string', 'A string directive.' CS::define('Element', 'Traits', array('nonmetallic', 'odorless', 'flammable'), 'list',
); 'What are general properties of the element?');
HTMLPurifier_ConfigSchema::define( CS::define('Element', 'IsotopeNames', array(1 => 'protium', 2 => 'deuterium', 3 => 'tritium'), 'hash',
'Core', 'Encoding', 'utf-8', 'istring', 'Case insensitivity!' 'Lookup hash of neutron counts to formal names.');
); CS::define('Element', 'Object', new stdClass(), 'mixed', 'Model representation.');
HTMLPurifier_ConfigSchema::define(
'Extension', 'CanBeNull', null, 'string/null', 'Null or string!'
);
HTMLPurifier_ConfigSchema::defineAllowedValues(
'Extension', 'Pert', array('foo', 'moo')
);
HTMLPurifier_ConfigSchema::defineValueAliases(
'Extension', 'Pert', array('cow' => 'moo')
);
HTMLPurifier_ConfigSchema::defineAllowedValues(
'Core', 'Encoding', array('utf-8', 'iso-8859-1')
);
$config = HTMLPurifier_Config::createDefault(); $config = HTMLPurifier_Config::createDefault();
// test default value retrieval // test default value retrieval
$this->assertIdentical($config->get('Core', 'Key'), false); $this->assertIdentical($config->get('Element', 'Abbr'), 'H');
$this->assertIdentical($config->get('Attr', 'Key'), 42); $this->assertIdentical($config->get('Element', 'Name'), 'hydrogen');
$this->assertIdentical($config->get('Extension', 'Pert'), 'foo'); $this->assertIdentical($config->get('Element', 'Number'), 1);
$this->assertIdentical($config->get('Element', 'Mass'), 1.00794);
$this->assertIdentical($config->get('Element', 'Radioactive'), false);
$this->assertIdentical($config->get('Element', 'Isotopes'), array(1 => true, 2 => true, 3 => true));
$this->assertIdentical($config->get('Element', 'Traits'), array('nonmetallic', 'odorless', 'flammable'));
$this->assertIdentical($config->get('Element', 'IsotopeNames'), array(1 => 'protium', 2 => 'deuterium', 3 => 'tritium'));
$this->assertIdentical($config->get('Element', 'Object'), new stdClass());
// set some values // test setting values
$config->set('Core', 'Key', true); $config->set('Element', 'Abbr', 'Pu');
$this->assertIdentical($config->get('Core', 'Key'), true); $config->set('Element', 'Name', 'PLUTONIUM'); // test decaps
$config->set('Element', 'Number', '94'); // test parsing
$config->set('Element', 'Mass', '244.'); // test parsing
$config->set('Element', 'Radioactive', true);
$config->set('Element', 'Isotopes', array(238, 239)); // test inversion
$config->set('Element', 'Traits', 'nuclear, heavy, actinide'); // test parsing
$config->set('Element', 'IsotopeNames', array(238 => 'Plutonium-238', 239 => 'Plutonium-239'));
$config->set('Element', 'Object', false); // unmodeled
// try to retrieve undefined value // test value retrieval
$config->get('Core', 'NotDefined'); $this->assertIdentical($config->get('Element', 'Abbr'), 'Pu');
$this->assertError('Cannot retrieve value of undefined directive'); $this->assertIdentical($config->get('Element', 'Name'), 'plutonium');
$this->assertNoErrors(); $this->assertIdentical($config->get('Element', 'Number'), 94);
$this->assertIdentical($config->get('Element', 'Mass'), 244.);
$this->assertIdentical($config->get('Element', 'Radioactive'), true);
$this->assertIdentical($config->get('Element', 'Isotopes'), array(238 => true, 239 => true));
$this->assertIdentical($config->get('Element', 'Traits'), array('nuclear', 'heavy', 'actinide'));
$this->assertIdentical($config->get('Element', 'IsotopeNames'), array(238 => 'Plutonium-238', 239 => 'Plutonium-239'));
$this->assertIdentical($config->get('Element', 'Object'), false);
// try to set undefined value // errors
$config->set('Foobar', 'Key', 'foobar');
$this->assertError('Cannot set undefined directive to value');
$this->assertNoErrors();
// try to set not allowed value $this->expectError('Cannot retrieve value of undefined directive');
$config->set('Extension', 'Pert', 'wizard'); $config->get('Element', 'Metal');
$this->assertError('Value not supported');
$this->assertNoErrors();
// try to set not allowed value $this->expectError('Cannot set undefined directive to value');
$config->set('Extension', 'Pert', 34); $config->set('Element', 'Metal', true);
$this->assertError('Value is of invalid type');
$this->assertNoErrors();
// set aliased value $this->expectError('Value is of invalid type');
$config->set('Extension', 'Pert', 'cow'); $config->set('Element', 'Radioactive', 'very');
$this->assertNoErrors();
$this->assertIdentical($config->get('Extension', 'Pert'), 'moo');
// case-insensitive attempt to set value that is allowed }
$config->set('Core', 'Encoding', 'ISO-8859-1');
$this->assertNoErrors();
$this->assertIdentical($config->get('Core', 'Encoding'), 'iso-8859-1');
// set null to directive that allows null function testEnumerated() {
$config->set('Extension', 'CanBeNull', null);
$this->assertNoErrors();
$this->assertIdentical($config->get('Extension', 'CanBeNull'), null);
$config->set('Extension', 'CanBeNull', 'foobar'); CS::defineNamespace('Instrument', 'Of the musical type.');
$this->assertNoErrors();
$this->assertIdentical($config->get('Extension', 'CanBeNull'), 'foobar');
// set null to directive that doesn't allow null // case sensitive
$config->set('Extension', 'Pert', null); CS::define('Instrument', 'Manufacturer', 'Yamaha', 'string', 'Who made it?');
$this->assertError('Value is of invalid type'); CS::defineAllowedValues('Instrument', 'Manufacturer', array(
$this->assertNoErrors(); 'Yamaha', 'Conn-Selmer', 'Vandoren', 'Laubin', 'Buffet', 'other'));
CS::defineValueAliases('Instrument', 'Manufacturer', array(
'Selmer' => 'Conn-Selmer'));
// case insensitive
CS::define('Instrument', 'Family', 'woodwind', 'istring', 'What family is it?');
CS::defineAllowedValues('Instrument', 'Family', array(
'brass', 'woodwind', 'percussion', 'string', 'keyboard', 'electronic'));
CS::defineValueAliases('Instrument', 'Family', array(
'synth' => 'electronic'));
$config = HTMLPurifier_Config::createDefault();
// case sensitive
$config->set('Instrument', 'Manufacturer', 'Vandoren');
$this->assertIdentical($config->get('Instrument', 'Manufacturer'), 'Vandoren');
$config->set('Instrument', 'Manufacturer', 'Selmer');
$this->assertIdentical($config->get('Instrument', 'Manufacturer'), 'Conn-Selmer');
$this->expectError('Value not supported');
$config->set('Instrument', 'Manufacturer', 'buffet');
// case insensitive
$config->set('Instrument', 'Family', 'brass');
$this->assertIdentical($config->get('Instrument', 'Family'), 'brass');
$config->set('Instrument', 'Family', 'PERCUSSION');
$this->assertIdentical($config->get('Instrument', 'Family'), 'percussion');
$config->set('Instrument', 'Family', 'synth');
$this->assertIdentical($config->get('Instrument', 'Family'), 'electronic');
$config->set('Instrument', 'Family', 'Synth');
$this->assertIdentical($config->get('Instrument', 'Family'), 'electronic');
}
function testNull() {
CS::defineNamespace('ReportCard', 'It is for grades.');
CS::define('ReportCard', 'English', null, 'string/null', 'Grade from English class.');
CS::define('ReportCard', 'Absences', 0, 'int', 'How many times missing from school?');
$config = HTMLPurifier_Config::createDefault();
$config->set('ReportCard', 'English', 'B-');
$this->assertIdentical($config->get('ReportCard', 'English'), 'B-');
$config->set('ReportCard', 'English', null); // not yet graded
$this->assertIdentical($config->get('ReportCard', 'English'), null);
// error
$this->expectError('Value is of invalid type');
$config->set('ReportCard', 'Absences', null);
}
function testAliases() {
HTMLPurifier_ConfigSchema::defineNamespace('Home', 'Sweet home.');
HTMLPurifier_ConfigSchema::define('Home', 'Rug', 3, 'int', 'ID.');
HTMLPurifier_ConfigSchema::defineAlias('Home', 'Carpet', 'Home', 'Rug');
$config = HTMLPurifier_Config::createDefault();
$this->assertEqual($config->get('Home', 'Rug'), 3);
$this->expectError('Cannot get value from aliased directive, use real name');
$config->get('Home', 'Carpet');
$config->set('Home', 'Carpet', 999);
$this->assertEqual($config->get('Home', 'Rug'), 999);
}
// test functionality based on method
function test_getBatch() {
CS::defineNamespace('Variables', 'Changing quantities in equation.');
CS::define('Variables', 'TangentialAcceleration', 'a_tan', 'string', 'In m/s^2');
CS::define('Variables', 'AngularAcceleration', 'alpha', 'string', 'In rad/s^2');
$config = HTMLPurifier_Config::createDefault();
// grab a namespace // grab a namespace
$config->set('Attr', 'Key', 0xBEEF);
$this->assertIdentical( $this->assertIdentical(
$config->getBatch('Attr'), $config->getBatch('Variables'),
array( array(
'Key' => 0xBEEF 'TangentialAcceleration' => 'a_tan',
'AngularAcceleration' => 'alpha'
) )
); );
// grab a non-existant namespace // grab a non-existant namespace
$config->getBatch('FurnishedGoods'); $this->expectError('Cannot retrieve undefined namespace');
$this->assertError('Cannot retrieve undefined namespace'); $config->getBatch('Constants');
$this->assertNoErrors();
}
function test_loadIni() {
CS::defineNamespace('Shortcut', 'Keyboard shortcuts for commands');
CS::define('Shortcut', 'Copy', 'c', 'istring', 'Copy text');
CS::define('Shortcut', 'Paste', 'v', 'istring', 'Paste clipboard');
CS::define('Shortcut', 'Cut', 'x', 'istring', 'Cut text');
$config = HTMLPurifier_Config::createDefault();
$config->loadIni(dirname(__FILE__) . '/ConfigTest-loadIni.ini');
$this->assertIdentical($config->get('Shortcut', 'Copy'), 'q');
$this->assertIdentical($config->get('Shortcut', 'Paste'), 'p');
$this->assertIdentical($config->get('Shortcut', 'Cut'), 't');
} }
@ -148,7 +242,7 @@ class HTMLPurifier_ConfigTest extends UnitTestCase
'Zoo', 'Others', array(), 'list', 'Other animals we have one of.' 'Zoo', 'Others', array(), 'list', 'Other animals we have one of.'
); );
$config_manual = HTMLPurifier_Config::createDefault(); $config_manual = HTMLPurifier_Config::createDefault();
$config_loadabbr = HTMLPurifier_Config::createDefault(); $config_loadabbr = HTMLPurifier_Config::createDefault();
$config_loadfull = HTMLPurifier_Config::createDefault(); $config_loadfull = HTMLPurifier_Config::createDefault();
@ -197,6 +291,10 @@ class HTMLPurifier_ConfigTest extends UnitTestCase
$created_config = HTMLPurifier_Config::create(array('Cake.Sprinkles' => 42)); $created_config = HTMLPurifier_Config::create(array('Cake.Sprinkles' => 42));
$this->assertEqual($config, $created_config); $this->assertEqual($config, $created_config);
// test loadIni
$created_config = HTMLPurifier_Config::create(dirname(__FILE__) . '/ConfigTest-create.ini');
$this->assertEqual($config, $created_config);
} }
} }

View File

@ -29,12 +29,13 @@ class HTMLPurifier_ContextTest extends UnitTestCase
$this->context->destroy('IDAccumulator'); $this->context->destroy('IDAccumulator');
$this->assertFalse($this->context->exists('IDAccumulator')); $this->assertFalse($this->context->exists('IDAccumulator'));
$this->expectError('Attempted to retrieve non-existent variable');
$accumulator_3 =& $this->context->get('IDAccumulator'); $accumulator_3 =& $this->context->get('IDAccumulator');
$this->assertError('Attempted to retrieve non-existent variable');
$this->assertNull($accumulator_3); $this->assertNull($accumulator_3);
$this->expectError('Attempted to destroy non-existent variable');
$this->context->destroy('IDAccumulator'); $this->context->destroy('IDAccumulator');
$this->assertError('Attempted to destroy non-existent variable');
} }
@ -42,15 +43,13 @@ class HTMLPurifier_ContextTest extends UnitTestCase
$var = true; $var = true;
$this->context->register('OnceOnly', $var); $this->context->register('OnceOnly', $var);
$this->assertNoErrors();
$this->expectError('Name collision, cannot re-register');
$this->context->register('OnceOnly', $var); $this->context->register('OnceOnly', $var);
$this->assertError('Name collision, cannot re-register');
// destroy it, now registration is okay // destroy it, now registration is okay
$this->context->destroy('OnceOnly'); $this->context->destroy('OnceOnly');
$this->context->register('OnceOnly', $var); $this->context->register('OnceOnly', $var);
$this->assertNoErrors();
} }

View File

@ -5,7 +5,7 @@ require_once 'HTMLPurifier/Encoder.php';
class HTMLPurifier_EncoderTest extends UnitTestCase class HTMLPurifier_EncoderTest extends UnitTestCase
{ {
var $Encoder; var $_entity_lookup;
function setUp() { function setUp() {
$this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
@ -60,6 +60,9 @@ class HTMLPurifier_EncoderTest extends UnitTestCase
$config = HTMLPurifier_Config::createDefault(); $config = HTMLPurifier_Config::createDefault();
$context = new HTMLPurifier_Context(); $context = new HTMLPurifier_Context();
// zhong-wen
$chinese = "\xE4\xB8\xAD\xE6\x96\x87 (Chinese)";
// UTF-8 means that we don't touch it // UTF-8 means that we don't touch it
$this->assertIdentical( $this->assertIdentical(
HTMLPurifier_Encoder::convertFromUTF8("\xC3\xB6", $config, $context), HTMLPurifier_Encoder::convertFromUTF8("\xC3\xB6", $config, $context),
@ -74,13 +77,55 @@ class HTMLPurifier_EncoderTest extends UnitTestCase
"\xF6" "\xF6"
); );
$config->set('Test', 'ForceNoIconv', true); if (function_exists('iconv')) {
// iconv has it's own way
$this->assertIdentical(
HTMLPurifier_Encoder::convertFromUTF8($chinese, $config, $context),
" (Chinese)"
);
}
// Plain PHP implementation has slightly different behavior
$config->set('Test', 'ForceNoIconv', true);
$this->assertIdentical( $this->assertIdentical(
HTMLPurifier_Encoder::convertFromUTF8("\xC3\xB6", $config, $context), HTMLPurifier_Encoder::convertFromUTF8("\xC3\xB6", $config, $context),
"\xF6" "\xF6"
); );
$this->assertIdentical(
HTMLPurifier_Encoder::convertFromUTF8($chinese, $config, $context),
"?? (Chinese)"
);
// Preserve the characters!
$config->set('Core', 'EscapeNonASCIICharacters', true);
$this->assertIdentical(
HTMLPurifier_Encoder::convertFromUTF8($chinese, $config, $context),
"&#20013;&#25991; (Chinese)"
);
}
function test_convertToASCIIDumbLossless() {
// Uppercase thorn letter
$this->assertIdentical(
HTMLPurifier_Encoder::convertToASCIIDumbLossless("\xC3\x9Eorn"),
"&#222;orn"
);
$this->assertIdentical(
HTMLPurifier_Encoder::convertToASCIIDumbLossless("an"),
"an"
);
// test up to four bytes
$this->assertIdentical(
HTMLPurifier_Encoder::convertToASCIIDumbLossless("\xF3\xA0\x80\xA0"),
"&#917536;"
);
} }
} }

View File

@ -0,0 +1,35 @@
<?php
class HTMLPurifier_SimpleTest_Reporter extends HTMLReporter
{
function paintHeader($test_name) {
parent::paintHeader($test_name);
$test_file = $GLOBALS['HTMLPurifierTest']['File'];
?>
<form action="" method="get" id="select">
<select name="f">
<option value="" style="font-weight:bold;"<?php if(!$test_file) {echo ' selected';} ?>>All Tests</option>
<?php foreach($GLOBALS['HTMLPurifierTest']['Files'] as $file) { ?>
<option value="<?php echo $file ?>"<?php
if ($test_file == $file) echo ' selected';
?>><?php echo $file ?></option>
<?php } ?>
</select>
<input type="submit" value="Go">
</form>
<?php
flush();
}
function _getCss() {
$css = parent::_getCss();
$css .= '
#select {position:absolute;top:0.2em;right:0.2em;}
';
return $css;
}
}
?>

View File

@ -91,11 +91,10 @@ class HTMLPurifier_Strategy_FixNestingTest extends HTMLPurifier_StrategyHarness
'<div>Reject</div>', 'Reject', array('HTML.Parent' => 'span') '<div>Reject</div>', 'Reject', array('HTML.Parent' => 'span')
); );
$this->expectError('Cannot use unrecognized element as parent.');
$this->assertResult( $this->assertResult(
'<div>Accept</div>', true, array('HTML.Parent' => 'script') '<div>Accept</div>', true, array('HTML.Parent' => 'script')
); );
$this->assertError('Cannot use unrecognized element as parent.');
$this->assertNoErrors();
} }

View File

@ -154,7 +154,7 @@ class HTMLPurifier_Strategy_ValidateAttributesTest extends
'<bdo dir="ltr">Invalid value!</bdo>' '<bdo dir="ltr">Invalid value!</bdo>'
); );
// comparison check for test 20 // see above, behavior is subtly different
$this->assertResult( $this->assertResult(
'<span dir="blahblah">Invalid value!</span>', '<span dir="blahblah">Invalid value!</span>',
'<span>Invalid value!</span>' '<span>Invalid value!</span>'

View File

@ -0,0 +1,11 @@
<?php
// since Mocks can't be called from within test files, we need to do
// a little jumping through hoops to generate them
function generate_mock_once($name) {
$mock_name = $name . 'Mock';
if (class_exists($mock_name)) return false;
Mock::generate($name, $mock_name);
}
?>

View File

@ -1,147 +1,82 @@
<?php <?php
// call one file using /?f=FileTest.php , see $test_files array for
// valid values
error_reporting(E_ALL | E_STRICT); error_reporting(E_ALL | E_STRICT);
define('HTMLPurifierTest', 1);
// wishlist: automated calling of this file from multiple PHP versions so we // wishlist: automated calling of this file from multiple PHP versions so we
// don't have to constantly switch around // don't have to constantly switch around
// configuration // default settings (protect against register_globals)
$GLOBALS['HTMLPurifierTest'] = array();
$GLOBALS['HTMLPurifierTest']['PEAR'] = false; // do PEAR tests $GLOBALS['HTMLPurifierTest']['PEAR'] = false; // do PEAR tests
$simpletest_location = 'simpletest/'; // reasonable guess
$simpletest_location = 'simpletest/'; // load SimpleTest
if (file_exists('../test-settings.php')) include_once '../test-settings.php'; @include '../test-settings.php'; // don't mind if it isn't there
require_once $simpletest_location . 'unit_tester.php'; require_once $simpletest_location . 'unit_tester.php';
require_once $simpletest_location . 'reporter.php'; require_once $simpletest_location . 'reporter.php';
require_once $simpletest_location . 'mock_objects.php'; require_once $simpletest_location . 'mock_objects.php';
require_once 'HTMLPurifier/SimpleTest/Reporter.php';
// configure PEAR if necessary // load Debugger
require_once 'Debugger.php';
// load convenience functions
require_once 'generate_mock_once.func.php';
require_once 'path2class.func.php';
require_once 'tally_errors.func.php'; // compat
// initialize PEAR (optional)
if ( is_string($GLOBALS['HTMLPurifierTest']['PEAR']) ) { if ( is_string($GLOBALS['HTMLPurifierTest']['PEAR']) ) {
// if PEAR is true, we assume that there's no need to
// add it to the path
set_include_path($GLOBALS['HTMLPurifierTest']['PEAR'] . PATH_SEPARATOR . set_include_path($GLOBALS['HTMLPurifierTest']['PEAR'] . PATH_SEPARATOR .
get_include_path()); get_include_path());
} }
// debugger // initialize and load HTML Purifier
require_once 'Debugger.php';
// emulates inserting a dir called HTMLPurifier into your class dir
set_include_path('../library' . PATH_SEPARATOR . get_include_path()); set_include_path('../library' . PATH_SEPARATOR . get_include_path());
// since Mocks can't be called from within test files, we need to do
// a little jumping through hoops to generate them
function generate_mock_once($name) {
$mock_name = $name . 'Mock';
if (class_exists($mock_name)) return false;
Mock::generate($name, $mock_name);
}
// this has to be defined before we do any includes of library files
require_once 'HTMLPurifier.php'; require_once 'HTMLPurifier.php';
// define callable test files // load tests
$test_files = array(); $test_files = array();
$test_files[] = 'ConfigTest.php'; require 'test_files.php'; // populates $test_files array
$test_files[] = 'ConfigSchemaTest.php'; sort($test_files); // for the SELECT
$test_files[] = 'LexerTest.php'; $GLOBALS['HTMLPurifierTest']['Files'] = $test_files; // for the reporter
$test_files[] = 'Lexer/DirectLexTest.php';
$test_files[] = 'TokenTest.php';
$test_files[] = 'ChildDef/RequiredTest.php';
$test_files[] = 'ChildDef/OptionalTest.php';
$test_files[] = 'ChildDef/ChameleonTest.php';
$test_files[] = 'ChildDef/CustomTest.php';
$test_files[] = 'ChildDef/TableTest.php';
$test_files[] = 'ChildDef/StrictBlockquoteTest.php';
$test_files[] = 'GeneratorTest.php';
$test_files[] = 'EntityLookupTest.php';
$test_files[] = 'Strategy/RemoveForeignElementsTest.php';
$test_files[] = 'Strategy/MakeWellFormedTest.php';
$test_files[] = 'Strategy/FixNestingTest.php';
$test_files[] = 'Strategy/CompositeTest.php';
$test_files[] = 'Strategy/CoreTest.php';
$test_files[] = 'Strategy/ValidateAttributesTest.php';
$test_files[] = 'AttrDefTest.php';
$test_files[] = 'AttrDef/EnumTest.php';
$test_files[] = 'AttrDef/IDTest.php';
$test_files[] = 'AttrDef/ClassTest.php';
$test_files[] = 'AttrDef/TextTest.php';
$test_files[] = 'AttrDef/LangTest.php';
$test_files[] = 'AttrDef/PixelsTest.php';
$test_files[] = 'AttrDef/LengthTest.php';
$test_files[] = 'AttrDef/URITest.php';
$test_files[] = 'AttrDef/CSSTest.php';
$test_files[] = 'AttrDef/CompositeTest.php';
$test_files[] = 'AttrDef/ColorTest.php';
$test_files[] = 'AttrDef/IntegerTest.php';
$test_files[] = 'AttrDef/NumberTest.php';
$test_files[] = 'AttrDef/CSSLengthTest.php';
$test_files[] = 'AttrDef/PercentageTest.php';
$test_files[] = 'AttrDef/MultipleTest.php';
$test_files[] = 'AttrDef/TextDecorationTest.php';
$test_files[] = 'AttrDef/FontFamilyTest.php';
$test_files[] = 'AttrDef/HostTest.php';
$test_files[] = 'AttrDef/IPv4Test.php';
$test_files[] = 'AttrDef/IPv6Test.php';
$test_files[] = 'AttrDef/FontTest.php';
$test_files[] = 'AttrDef/BorderTest.php';
$test_files[] = 'AttrDef/ListStyleTest.php';
$test_files[] = 'AttrDef/Email/SimpleCheckTest.php';
$test_files[] = 'AttrDef/CSSURITest.php';
$test_files[] = 'IDAccumulatorTest.php';
$test_files[] = 'TagTransformTest.php';
$test_files[] = 'AttrTransform/LangTest.php';
$test_files[] = 'AttrTransform/TextAlignTest.php';
$test_files[] = 'AttrTransform/BdoDirTest.php';
$test_files[] = 'AttrTransform/ImgRequiredTest.php';
$test_files[] = 'URISchemeRegistryTest.php';
$test_files[] = 'URISchemeTest.php';
$test_files[] = 'EncoderTest.php';
$test_files[] = 'EntityParserTest.php';
$test_files[] = 'Test.php';
$test_files[] = 'ContextTest.php';
$test_files[] = 'PercentEncoderTest.php';
if (version_compare(PHP_VERSION, '5', '>=')) {
$test_files[] = 'TokenFactoryTest.php';
}
$test_file_lookup = array_flip($test_files); $test_file_lookup = array_flip($test_files);
function htmlpurifier_path2class($path) { // determine test file
$temp = $path; if (isset($_GET['f']) && isset($test_file_lookup[$_GET['f']])) {
$temp = str_replace('./', '', $temp); // remove leading './' $GLOBALS['HTMLPurifierTest']['File'] = $_GET['f'];
$temp = str_replace('.\\', '', $temp); // remove leading '.\' } else {
$temp = str_replace('\\', '_', $temp); // normalize \ to _ $GLOBALS['HTMLPurifierTest']['File'] = false;
$temp = str_replace('/', '_', $temp); // normalize / to _
while(strpos($temp, '__') !== false) $temp = str_replace('__', '_', $temp);
$temp = str_replace('.php', '', $temp);
return $temp;
} }
// we can't use addTestFile because SimpleTest chokes on E_STRICT warnings // we can't use addTestFile because SimpleTest chokes on E_STRICT warnings
if ($test_file = $GLOBALS['HTMLPurifierTest']['File']) {
if (isset($_GET['file']) && isset($test_file_lookup[$_GET['file']])) { $test = new GroupTest($test_file . ' - HTML Purifier');
// execute only one test
$test_file = $_GET['file'];
$test = new GroupTest('HTML Purifier - ' . $test_file);
$path = 'HTMLPurifier/' . $test_file; $path = 'HTMLPurifier/' . $test_file;
require_once $path; require_once $path;
$test->addTestClass(htmlpurifier_path2class($path)); $test->addTestClass(path2class($path));
} else { } else {
$test = new GroupTest('HTML Purifier'); $test = new GroupTest('All Tests - HTML Purifier');
foreach ($test_files as $test_file) { foreach ($test_files as $test_file) {
$path = 'HTMLPurifier/' . $test_file; $path = 'HTMLPurifier/' . $test_file;
require_once $path; require_once $path;
$test->addTestClass(htmlpurifier_path2class($path)); $test->addTestClass(path2class($path));
} }
} }
if (SimpleReporter::inCli()) $reporter = new TextReporter(); if (SimpleReporter::inCli()) $reporter = new TextReporter();
else $reporter = new HTMLReporter('UTF-8'); else $reporter = new HTMLPurifier_SimpleTest_Reporter('UTF-8');
$test->run($reporter); $test->run($reporter);

14
tests/path2class.func.php Normal file
View File

@ -0,0 +1,14 @@
<?php
function path2class($path) {
$temp = $path;
$temp = str_replace('./', '', $temp); // remove leading './'
$temp = str_replace('.\\', '', $temp); // remove leading '.\'
$temp = str_replace('\\', '_', $temp); // normalize \ to _
$temp = str_replace('/', '_', $temp); // normalize / to _
while(strpos($temp, '__') !== false) $temp = str_replace('__', '_', $temp);
$temp = str_replace('.php', '', $temp);
return $temp;
}
?>

View File

@ -0,0 +1,18 @@
<?php
function tally_errors() {
// BRITTLE: relies on private code to work
$context = &SimpleTest::getContext();
$queue = &$context->get('SimpleErrorQueue');
if (!isset($queue->_expectation_queue)) return; // fut-compat
foreach ($queue->_expectation_queue as $e) {
if (count($e) != 2) return; // fut-compat
if (!isset($e[0])) return; // fut-compat
$e[0]->_dumper = new SimpleDumper();
$this->fail('Error expectation not fulfilled: ' .
$e[0]->testMessage(null));
}
$queue->_expectation_queue = array();
}
?>

72
tests/test_files.php Normal file
View File

@ -0,0 +1,72 @@
<?php
if (!defined('HTMLPurifierTest')) exit;
// define callable test files
$test_files[] = 'ConfigTest.php';
$test_files[] = 'ConfigSchemaTest.php';
$test_files[] = 'LexerTest.php';
$test_files[] = 'Lexer/DirectLexTest.php';
$test_files[] = 'TokenTest.php';
$test_files[] = 'ChildDef/RequiredTest.php';
$test_files[] = 'ChildDef/OptionalTest.php';
$test_files[] = 'ChildDef/ChameleonTest.php';
$test_files[] = 'ChildDef/CustomTest.php';
$test_files[] = 'ChildDef/TableTest.php';
$test_files[] = 'ChildDef/StrictBlockquoteTest.php';
$test_files[] = 'GeneratorTest.php';
$test_files[] = 'EntityLookupTest.php';
$test_files[] = 'Strategy/RemoveForeignElementsTest.php';
$test_files[] = 'Strategy/MakeWellFormedTest.php';
$test_files[] = 'Strategy/FixNestingTest.php';
$test_files[] = 'Strategy/CompositeTest.php';
$test_files[] = 'Strategy/CoreTest.php';
$test_files[] = 'Strategy/ValidateAttributesTest.php';
$test_files[] = 'AttrDefTest.php';
$test_files[] = 'AttrDef/EnumTest.php';
$test_files[] = 'AttrDef/IDTest.php';
$test_files[] = 'AttrDef/ClassTest.php';
$test_files[] = 'AttrDef/TextTest.php';
$test_files[] = 'AttrDef/LangTest.php';
$test_files[] = 'AttrDef/PixelsTest.php';
$test_files[] = 'AttrDef/LengthTest.php';
$test_files[] = 'AttrDef/URITest.php';
$test_files[] = 'AttrDef/CSSTest.php';
$test_files[] = 'AttrDef/CompositeTest.php';
$test_files[] = 'AttrDef/ColorTest.php';
$test_files[] = 'AttrDef/IntegerTest.php';
$test_files[] = 'AttrDef/NumberTest.php';
$test_files[] = 'AttrDef/CSSLengthTest.php';
$test_files[] = 'AttrDef/PercentageTest.php';
$test_files[] = 'AttrDef/MultipleTest.php';
$test_files[] = 'AttrDef/TextDecorationTest.php';
$test_files[] = 'AttrDef/FontFamilyTest.php';
$test_files[] = 'AttrDef/HostTest.php';
$test_files[] = 'AttrDef/IPv4Test.php';
$test_files[] = 'AttrDef/IPv6Test.php';
$test_files[] = 'AttrDef/FontTest.php';
$test_files[] = 'AttrDef/BorderTest.php';
$test_files[] = 'AttrDef/ListStyleTest.php';
$test_files[] = 'AttrDef/Email/SimpleCheckTest.php';
$test_files[] = 'AttrDef/CSSURITest.php';
$test_files[] = 'AttrDef/BackgroundPositionTest.php';
$test_files[] = 'AttrDef/BackgroundTest.php';
$test_files[] = 'IDAccumulatorTest.php';
$test_files[] = 'TagTransformTest.php';
$test_files[] = 'AttrTransform/LangTest.php';
$test_files[] = 'AttrTransform/TextAlignTest.php';
$test_files[] = 'AttrTransform/BdoDirTest.php';
$test_files[] = 'AttrTransform/ImgRequiredTest.php';
$test_files[] = 'URISchemeRegistryTest.php';
$test_files[] = 'URISchemeTest.php';
$test_files[] = 'EncoderTest.php';
$test_files[] = 'EntityParserTest.php';
$test_files[] = 'Test.php';
$test_files[] = 'ContextTest.php';
$test_files[] = 'PercentEncoderTest.php';
if (version_compare(PHP_VERSION, '5', '>=')) {
$test_files[] = 'TokenFactoryTest.php';
}
?>