0
0
mirror of https://github.com/ezyang/htmlpurifier.git synced 2025-02-02 18:10:01 +00:00

Release 2.1.2, merged in 1368 to HEAD.

git-svn-id: http://htmlpurifier.org/svnroot/htmlpurifier/branches/strict@1404 48356398-32a2-884e-a903-53898d9a118a
This commit is contained in:
Edward Z. Yang 2007-09-03 15:40:43 +00:00
parent 80c60bb9b5
commit b3f0e6c86c
72 changed files with 6233 additions and 1405 deletions

View File

@ -4,7 +4,7 @@
# Project related configuration options
#---------------------------------------------------------------------------
PROJECT_NAME = HTML Purifier
PROJECT_NUMBER = 2.1.1
PROJECT_NUMBER = 2.1.2
OUTPUT_DIRECTORY = "C:/Documents and Settings/Edward/My Documents/My Webs/htmlpurifier/docs/doxygen"
CREATE_SUBDIRS = NO
OUTPUT_LANGUAGE = English

28
NEWS
View File

@ -9,6 +9,34 @@ NEWS ( CHANGELOG and HISTORY ) HTMLPurifier
. Internal change
==========================
2.1.2, released 2007-09-03
! Implemented Object module for trusted users
! Implemented experimental HTML5 parsing mode using PH5P. To use, add
this to your code:
require_once 'HTMLPurifier/Lexer/PH5P.php';
$config->set('Core', 'LexerImpl', 'PH5P');
Note that this Lexer introduces some classes not in the HTMLPurifier
namespace. Also, this is PHP5 only.
! CSS property border-spacing implemented
- Fix non-visible parsing error in DirectLex with empty tags that have
slashes inside attribute values.
- Fix typo in CSS definition: border-collapse:seperate; was incorrectly
accepted as valid CSS. Usually non-visible, because this styling is the
default for tables in most browsers. Thanks Brett Zamir for pointing
this out.
- Fix validation errors in configuration form
- Hammer out a bunch of edge-case bugs in the standalone distribution
- Inclusion reflection removed from URISchemeRegistry; you must manually
include any new schema files you wish to use
- Numerous typo fixes in documentation thanks to Brett Zamir
. Unit test refactoring for one logical test per test function
. Config and context parameters in ComplexHarness deprecated: instead, edit
the $config and $context member variables
. HTML wrapper in DOMLex now takes DTD identifiers into account; doesn't
really make a difference, but is good for completeness sake
. merge-library.php script refactored for greater code reusability and
PHP4 compatibility
2.1.1, released 2007-08-04
- Fix show-stopper bug in %URI.MakeAbsolute functionality
- Fix PHP4 syntax error in standalone version

15
TODO
View File

@ -28,23 +28,22 @@ afraid to cast your vote for the next feature to be implemented!
- 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)
- Externalize inline CSS to promote clean HTML
2.4 release [It's All About Trust] (floating)
# Implement untrusted, dangerous elements/attributes
# Implement IDREF support (harder than it seems, since you cannot have
IDREFs to non-existent IDs)
# Frameset XHTML 1.0 and HTML 4.01 doctypes
3.0 release [Beyond HTML]
# Legit token based CSS parsing (will require revamping almost every
AttrDef class)
AttrDef class). Probably will use CSSTidy class
# More control over allowed CSS properties (maybe modularize it in the
same fashion!)
# Formatters for plaintext
- Smileys
- Standardize token armor for all areas of processing
- Fixes for Firefox's inability to handle COL alignment props (Bug 915)
- Automatically add non-breaking spaces to empty table cells when
empty-cells:show is applied to have compatibility with Internet Explorer
- Convert RTL/LTR override characters to <bdo> tags, or vice versa on demand.
Also, enable disabling of directionality
@ -63,25 +62,27 @@ Ongoing
- Complete basic smoketests
Unknown release (on a scratch-an-itch basis)
? Semi-lossy dumb alternate character encoding transfor
# CHMOD install script for PEAR installs
? Have 'lang' attribute be checked against official lists, achieved by
encoding all characters that have string entity equivalents
- Abstract ChildDef_BlockQuote to work with all elements that only
allow blocks in them, required or optional
- Reorganize Unit Tests
- Refactor loop tests: Lexer
- Reorganize configuration directives (Create more namespaces! Get messy!)
- Advanced URI filtering schemes (see docs/proposal-new-directives.txt)
- Implement lenient <ruby> child validation
- Explain how to use HTML Purifier in non-PHP languages / create
a simple command line stub (or complicated?)
- Fixes for Firefox's inability to handle COL alignment props (Bug 915)
- Automatically add non-breaking spaces to empty table cells when
empty-cells:show is applied to have compatibility with Internet Explorer
Requested
Wontfix
- Non-lossy smart alternate character encoding transformations (unless
patch provided)
- Pretty-printing HTML, users can use Tidy on the output on entire page
- Pretty-printing HTML: users can use Tidy on the output on entire page
- Native content compression, whitespace stripping (don't rely on Tidy, make
sure we don't remove from <pre> or related tags): use gzip if this is
really important

View File

@ -1 +1 @@
2.1.1
2.1.2

View File

@ -1,10 +1,8 @@
In version 2.1, HTML Purifier's URI validation and filtering handling
system has been revamped with a new, extensible URIFilter system. Also
notable features include preservation of emoticons in PHP5 with
%Core.AggressivelyFixLt, standalone and lite download versions,
transforming relative URIs to absolute URIs, Ruby in XHTML 1.1, a Phorum
mod, and UTF-8 font names. Notable bug-fixes include refinement of
the auto-paragraphing algorithm (no longer experimental), better XHTML
1.1 support and the removal of the contents of <style> elements. Version
2.1.1 amends a few bugs in some of newly introduced features, namely
running the standalone download version in PHP4 and %URI.MakeAbsolute.
Version 2.1.2 is a mix of experimental features and stability updates.
Among new features: an Object module for trusted users, support for the
CSS property 'border-spacing', and HTML 5 style parsing using PH5P.
Bug fixes ihave resolved a few obscure issues including border-collapse:seperate,
a DirectLex parsing error, broken HTML in printDefinition.php, and problems
with the experimental standalone distribution. Also, there were large
amounts of behind-the-scenes refactoring and the removal of URIScheme
inclusion reflection.

View File

@ -39,7 +39,7 @@ thead th {text-align:left;padding:0.1em;background-color:#EEE;}
<table cellspacing="0"><tbody>
<tr><td class="impl-yes">Implemented</td></tr>
<tr><td class="impl-partial">Partially implemented</td></tr>
<tr><td class="impl-no">Will not implement</td></tr>
<tr><td class="impl-no">Not priority to implement</td></tr>
<tr><td class="danger">Dangerous attribute/property</td></tr>
<tr><td class="css1">Present in CSS1</td></tr>
<tr><td class="feature">Feature, requires extra work</td></tr>
@ -118,6 +118,7 @@ thead th {text-align:left;padding:0.1em;background-color:#EEE;}
<tbody>
<tr><th colspan="2">Table</th></tr>
<tr class="impl-yes"><td>border-collapse</td><td>ENUM(collapse, seperate)</td></tr>
<tr class="impl-yes"><td>border-space</td><td>MULTIPLE</td></tr>
<tr class="impl-yes"><td>caption-side</td><td>ENUM(top, bottom)</td></tr>
<tr class="feature"><td>empty-cells</td><td>ENUM(show, hide), No IE support makes this useless,
possible fix with &amp;nbsp;? Unknown release milestone.</td></tr>

View File

@ -32,7 +32,7 @@
Before we even write any code, it is paramount to consider whether or
not the code we're writing is necessary or not. HTML Purifier, by default,
contains a large set of elements and attributes: large enough so that
<em>any</em> element or attribute in XHTML 1.0 (and its HTML variant)
<em>any</em> element or attribute in XHTML 1.0 or 1.1 (and its HTML variants)
that can be safely used by the general public is implemented.
</p>
@ -76,11 +76,12 @@
<h3>XHTML 1.1</h3>
<p>
We have not implemented the
As of HTMLPurifier 2.1.0, we have implemented the
<a href="http://www.w3.org/TR/2001/REC-ruby-20010531/">Ruby module</a>,
which defines a set of tags
for publishing short annotations for text, used mostly in Japanese
and Chinese school texts.
and Chinese school texts, but applicable for positioning any text (not
limited to translations) above or below other corresponding text.
</p>
<h3>XHTML 2.0</h3>
@ -492,10 +493,11 @@ $def =& $config->getHTMLDefinition(true);
<p>
The <code>(%flow;)*</code> indicates the allowed children of the
<code>li</code> tag: <code>li</code> allows any number of flow
elements as its children. In HTML Purifier, we'd write it like
<code>Flow</code> (here's where the content sets we were
discussing earlier come into play). There are three shorthand content models you
can specify:
elements as its children. (The <code>- O</code> allows the closing tag to be
omitted, though in XML this is not allowed.) In HTML Purifier,
we'd write it like <code>Flow</code> (here's where the content sets
we were discussing earlier come into play). There are three shorthand
content models you can specify:
</p>
<table class="table">
@ -668,12 +670,22 @@ $def =& $config->getHTMLDefinition(true);
Common is a combination of the above-mentioned collections.
</p>
<p class="aside">
Readers familiar with the modularization may have noticed that the Core
attribute collection differs from that specified by the <a
href="http://www.w3.org/TR/xhtml-modularization/abstract_modules.html#s_commonatts">abstract
modules of the XHTML Modularization 1.1</a>. We believe this section
to be in error, as <code>br</code> permits the use of the <code>style</code>
attribute even though it uses the <code>Core</code> collection, and
the DTD and XML Schemas supplied by W3C support our interpretation.
</p>
<h3>Attributes</h3>
<p>
If you didn't read the <a href="#addAttribute">previous section on
If you didn't read the <a href="#addAttribute">earlier section on
adding attributes</a>, read it now. The last parameter is simply
array of attribute names to attribute implementations, in the exact
an array of attribute names to attribute implementations, in the exact
same format as <code>addAttribute()</code>.
</p>

View File

@ -58,7 +58,7 @@ appear elsewhere on the document. The method is simple:</p>
<pre>$config->set('HTML', 'EnableAttrID', true);
$config->set('Attr', 'IDBlacklist' array(
'list', 'of', 'attributes', 'that', 'are', 'forbidden'
'list', 'of', 'attribute', 'values', 'that', 'are', 'forbidden'
));</pre>
<p>That being said, there are some notable drawbacks. First of all, you have to
@ -71,9 +71,9 @@ to possible standards-compliance issues.</p>
<p>Furthermore, this position becomes untenable when a single web page must hold
multiple portions of user-submitted content. Since there's obviously no way
to find out before-hand what IDs users will use, the blacklist is helpless.
And even since HTML Purifier validates each segment seperately, perhaps doing
And since HTML Purifier validates each segment separately, perhaps doing
so at different times, it would be extremely difficult to dynamically update
the blacklist inbetween runs.</p>
the blacklist in between runs.</p>
<p>Finally, simply destroying the ID is extremely un-userfriendly behavior: after
all, they might have simply specified a duplicate ID by accident.</p>

View File

@ -22,7 +22,7 @@ out:</p>
<p class="emphasis">This ain't HTML Tidy!</p>
<p>Rather, Tidy stands for a cool set of Tidy-inspired in HTML Purifier
<p>Rather, Tidy stands for a cool set of Tidy-inspired features in HTML Purifier
that allows users to submit deprecated elements and attributes and get
valid strict markup back. For example:</p>
@ -33,8 +33,8 @@ valid strict markup back. For example:</p>
<pre>&lt;div style=&quot;text-align:center;&quot;&gt;Centered&lt;/div&gt;</pre>
<p>...when this particular fix is run on the HTML. This tutorial will give
you down the lowdown of what exactly HTML Purifier will do when Tidy
is on, and how to fine tune this behavior. Once again, <strong>you do
you the lowdown of what exactly HTML Purifier will do when Tidy
is on, and how to fine-tune this behavior. Once again, <strong>you do
not need Tidy installed on your PHP to use these features!</strong></p>
<h2>What does it do?</h2>
@ -221,7 +221,7 @@ general syntax:</p>
<p>The lowdown is, quite frankly, HTML Purifier's default settings are
probably good enough. The next step is to bump the level up to heavy,
and if that still doesn't satisfy your appetite, do some fine tuning.
and if that still doesn't satisfy your appetite, do some fine-tuning.
Other than that, don't worry about it: this all works silently and
effectively in the background.</p>

View File

@ -96,7 +96,7 @@ which can be a rewarding (but difficult) task.</p>
<h2 id="findcharset">Finding the real encoding</h2>
<p>In the beginning, there was ASCII, and things were simple. But they
weren't good, for no one could write in Cryllic or Thai. So there
weren't good, for no one could write in Cyrillic or Thai. So there
exploded a proliferation of character encodings to remedy the problem
by extending the characters ASCII could express. This ridiculously
simplified version of the history of character encodings shows us that
@ -138,7 +138,7 @@ browser:</p>
<dd>View &gt; Encoding: bulleted item is unofficial name</dd>
</dl>
<p>Internet Explorer won't give you the mime (i.e. useful/real) name of the
<p>Internet Explorer won't give you the MIME (i.e. useful/real) name of the
character encoding, so you'll have to look it up using their description.
Some common ones:</p>
@ -216,6 +216,12 @@ if your <code>META</code> tag claims that either:</p>
<h2 id="fixcharset">Fixing the encoding</h2>
<p class="aside">The advice given here is for pages being served as
vanilla <code>text/html</code>. Different practices must be used
for <code>application/xml</code> or <code>application/xml+xhtml</code>, see
<a href="http://www.w3.org/TR/2002/NOTE-xhtml-media-types-20020430/">W3C's
document on XHTML media types</a> for more information.</p>
<p>If your <code>META</code> encoding and your real encoding match,
savvy! You can skip this section. If they don't...</p>
@ -302,7 +308,8 @@ languages</a>. The appropriate code is:</p>
<p>...replacing UTF-8 with whatever your embedded encoding is.
This code must come before any output, so be careful about
stray whitespace in your application.</p>
stray whitespace in your application (i.e., any whitespace before
output excluding whitespace within &lt;?php ?&gt; tags).</p>
<h4 id="fixcharset-server-phpini">PHP ini directive</h4>
@ -313,8 +320,8 @@ header call: <code><a href="http://php.net/ini.core#ini.default-charset">default
<p>...will also do the trick. If PHP is running as an Apache module (and
not as FastCGI, consult
<a href="http://php.net/phpinfo">phpinfo</a>() for details), you can even use htaccess do apply this property
globally:</p>
<a href="http://php.net/phpinfo">phpinfo</a>() for details), you can even use htaccess to apply this property
across many PHP files:</p>
<pre><a href="http://php.net/configuration.changes#configuration.changes.apache">php_value</a> default_charset &quot;UTF-8&quot;</pre>
@ -360,10 +367,11 @@ to send anything at all:</p>
<pre><a href="http://httpd.apache.org/docs/1.3/mod/core.html#adddefaultcharset">AddDefaultCharset</a> Off</pre>
<p>...making your <code>META</code> tags the sole source of
character encoding information. In these cases, it is
<em>especially</em> important to make sure you have valid <code>META</code>
tags on your pages and all the text before them is ASCII.</p>
<p>...making your internal charset declaration (usually the <code>META</code> tags)
the sole source of character encoding
information. In these cases, it is <em>especially</em> important to make
sure you have valid <code>META</code> tags on your pages and all the
text before them is ASCII.</p>
<blockquote class="aside"><p>These directives can also be
placed in httpd.conf file for Apache, but
@ -428,28 +436,30 @@ IIS to change character encodings, I'd be grateful.</p>
<p><code>META</code> tags are the most common source of embedded
encodings, but they can also come from somewhere else: XML
processing instructions. They look like:</p>
Declarations. They look like:</p>
<pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</pre>
<p>...and are most often found in XML documents (including XHTML).</p>
<p>For XHTML, this processing instruction theoretically
<p>For XHTML, this XML Declaration theoretically
overrides the <code>META</code> tag. In reality, this happens only when the
XHTML is actually served as legit XML and not HTML, which is almost always
never due to Internet Explorer's lack of support for
<code>application/xhtml+xml</code> (even though doing so is often
argued to be <a href="http://www.hixie.ch/advocacy/xhtml">good practice</a>).</p>
argued to be <a href="http://www.hixie.ch/advocacy/xhtml">good
practice</a> and is required by the XHTML 1.1 specification).</p>
<p>For XML, however, this processing instruction is extremely important.
<p>For XML, however, this XML Declaration is extremely important.
Since most webservers are not configured to send charsets for .xml files,
this is the only thing a parser has to go on. Furthermore, the default
for XML files is UTF-8, which often butts heads with more common
ISO-8859-1 encoding (you see this in garbled RSS feeds).</p>
<p>In short, if you use XHTML and have gone through the
trouble of adding the XML header, make sure it jives
with your <code>META</code> tags and HTTP headers.</p>
trouble of adding the XML Declaration, make sure it jives
with your <code>META</code> tags (which should only be present
if served in text/html) and HTTP headers.</p>
<h3 id="fixcharset-internals">Inside the process</h3>
@ -506,7 +516,7 @@ usage in one language sometimes requires the occasional special character
that, without surprise, is not available in your character set. Sometimes
developers get around this by adding support for multiple encodings: when
using Chinese, use Big5, when using Japanese, use Shift-JIS, when
using Greek, etc. Other times, they use character entities with great
using Greek, etc. Other times, they use character references with great
zeal.</p>
<p>UTF-8, however, obviates the need for any of these complicated
@ -520,14 +530,14 @@ you don't have to use those user-unfriendly entities.</p>
<p>Websites encoded in Latin-1 (ISO-8859-1) which ocassionally need
a special character outside of their scope often will use a character
entity to achieve the desired effect. For instance, &theta; can be
entity reference to achieve the desired effect. For instance, &theta; can be
written <code>&amp;theta;</code>, regardless of the character encoding's
support of Greek letters.</p>
<p>This works nicely for limited use of special characters, but
say you wanted this sentence of Chinese text: &#28608;&#20809;,
&#36889;&#20841;&#20491;&#23383;&#26159;&#29978;&#40636;&#24847;&#24605;.
The entity-ized version would look like this:</p>
The ampersand encoded version would look like this:</p>
<pre>&amp;#28608;&amp;#20809;, &amp;#36889;&amp;#20841;&amp;#20491;&amp;#23383;&amp;#26159;&amp;#29978;&amp;#40636;&amp;#24847;&amp;#24605;</pre>
@ -545,7 +555,7 @@ an application that originally used ISO-8859-1 but switched to UTF-8
when it became far to cumbersome to support foreign languages. Bots
will now actually go through articles and convert character entities
to their corresponding real characters for the sake of user-friendliness
and searcheability. See
and searchability. See
<a href="http://meta.wikimedia.org/wiki/Help:Special_characters">Meta's
page on special characters</a> for more details.
</p></blockquote>
@ -593,7 +603,7 @@ browser you're using, they might:</p>
<ul>
<li>Replace the unsupported characters with useless question marks,</li>
<li>Attempt to fix the characters (example: smart quotes to regular quotes),</li>
<li>Replace the character with a character entity, or</li>
<li>Replace the character with a character entity reference, or</li>
<li>Send it anyway as a different character encoding mixed in
with the original encoding (usually Windows-1252 rather than
iso-8859-1 or UTF-8 interspersed in 8-bit)</li>
@ -609,7 +619,7 @@ since UTF-8 supports every character.</p>
<h4 id="whyutf8-forms-multipart"><code>multipart/form-data</code></h4>
<p>Multipart form submission takes a way a lot of the ambiguity
<p>Multipart form submission takes away a lot of the ambiguity
that percent-encoding had: the server now can explicitly ask for
certain encodings, and the client can explicitly tell the server
during the form submission what encoding the fields are in.</p>
@ -622,9 +632,9 @@ Each method has deficiencies, especially the former.</p>
<p>If you tell the browser to send the form in the same encoding as
the page, you still have the trouble of what to do with characters
that are outside of the character encoding's range. The behavior, once
again, varies: Firefox 2.0 entity-izes them while Internet Explorer
7.0 mangles them beyond intelligibility. For serious internationalization purposes,
this is not an option.</p>
again, varies: Firefox 2.0 converts them to character entity references
while Internet Explorer 7.0 mangles them beyond intelligibility. For
serious internationalization purposes, this is not an option.</p>
<p>The other possibility is to set Accept-Encoding to UTF-8, which
begs the question: Why aren't you using UTF-8 for everything then?
@ -664,12 +674,12 @@ it up to the module iconv to do the dirty work.</p>
<p>This approach, however, is not perfect. iconv is blithely unaware
of HTML character entities. HTML Purifier, in order to
protect against sophisticated escaping schemes, normalizes all character
and numeric entities before processing the text. This leads to
and numeric entitie references before processing the text. This leads to
one important ramification:</p>
<p><strong>Any character that is not supported by the target character
set, regardless of whether or not it is in the form of a character
entity or a raw character, will be silently ignored.</strong></p>
entity reference or a raw character, will be silently ignored.</strong></p>
<p>Example of this principle at work: say you have <code>&amp;theta;</code>
in your HTML, but the output is in Latin-1 (which, understandably,
@ -678,7 +688,7 @@ set the encoding correctly using %Core.Encoding):</p>
<ul>
<li>The <code>Encoder</code> will transform the text from ISO 8859-1 to UTF-8
(note that theta is preserved since it doesn't actually use
(note that theta is preserved here since it doesn't actually use
any non-ASCII characters): <code>&amp;theta;</code></li>
<li>The <code>EntityParser</code> will transform all named and numeric
character entities to their corresponding raw UTF-8 equivalents:
@ -701,7 +711,7 @@ Purifier has provided a slightly more palatable workaround using
<li>The <code>EntityParser</code> transforms entities: <code>&theta;</code></li>
<li>HTML Purifier processes the code: <code>&theta;</code></li>
<li>The <code>Encoder</code> replaces all non-ASCII characters
with numeric entities: <code>&amp;#952;</code></li>
with numeric entity reference: <code>&amp;#952;</code></li>
<li>For good measure, <code>Encoder</code> transforms encoding back to
original (which is strictly unnecessary for 99% of encodings
out there): <code>&amp;#952;</code> (remember, it's all ASCII!)</li>
@ -711,19 +721,19 @@ Purifier has provided a slightly more palatable workaround using
the land of Unicode characters, and is totally unacceptable for Chinese
or Japanese texts. The even bigger kicker is that, supposing the
input encoding was actually ISO-8859-7, which <em>does</em> support
theta, the character would get entity-ized anyway! (The Encoder does
not discriminate).</p>
theta, the character would get converted into a character entity reference
anyway! (The Encoder does not discriminate).</p>
<p>The current functionality is about where HTML Purifier will be for
the rest of eternity. HTML Purifier could attempt to preserve the original
form of the entities so that they could be substituted back in, only the
form of the character references so that they could be substituted back in, only the
DOM extension kills them off irreversibly. HTML Purifier could also attempt
to be smart and only convert non-ASCII characters that weren't supported
by the target encoding, but that would require reimplementing iconv
with HTML awareness, something I will not do.</p>
<p>So there: either it's UTF-8 or crippled international support. Your pick! (and I'm
not being sarcastic here: some people could care less about other languages)</p>
not being sarcastic here: some people could care less about other languages).</p>
<h2 id="migrate">Migrate to UTF-8</h2>
@ -985,7 +995,7 @@ and yes, it is variable width. Other traits:</p>
in different ways. It is beyond the scope of this document to explain
what precisely these implications are. PHPWact provides
a very good <a href="http://www.phpwact.org/php/i18n/utf-8">reference document</a>
on what to expect from each functions, although coverage is spotty in
on what to expect from each function, although coverage is spotty in
some areas. Their more general notes on
<a href="http://www.phpwact.org/php/i18n/charsets">character sets</a>
are also worth looking at for information on UTF-8. Some rules of thumb
@ -999,7 +1009,7 @@ when dealing with Unicode text:</p>
<li>Think twice before using functions that:<ul>
<li>...count characters (strlen will return bytes, not characters;
str_split and word_wrap may corrupt)</li>
<li>...entity-ize things (UTF-8 doesn't need entities)</li>
<li>...convert characters to entity references (UTF-8 doesn't need entities)</li>
<li>...do very complex string processing (*printf)</li>
</ul></li>
</ul>

View File

@ -22,7 +22,7 @@
*/
/*
HTML Purifier 2.1.1 - Standards Compliant HTML Filtering
HTML Purifier 2.1.2 - Standards Compliant HTML Filtering
Copyright (C) 2006 Edward Z. Yang
This library is free software; you can redistribute it and/or
@ -77,7 +77,7 @@ This directive has been available since 2.0.0.
class HTMLPurifier
{
var $version = '2.1.1';
var $version = '2.1.2';
var $config;
var $filters;

View File

@ -6,6 +6,7 @@ require_once 'HTMLPurifier/URIScheme.php';
require_once 'HTMLPurifier/URISchemeRegistry.php';
require_once 'HTMLPurifier/AttrDef/URI/Host.php';
require_once 'HTMLPurifier/PercentEncoder.php';
require_once 'HTMLPurifier/AttrDef/URI/Email.php';
// special case filtering directives

View File

@ -1,6 +1,7 @@
<?php
require_once 'HTMLPurifier/AttrDef.php';
require_once 'HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php';
class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
{

View File

@ -44,6 +44,9 @@ class HTMLPurifier_AttrTypes
$this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang();
$this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color();
// unimplemented aliases
$this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
// number is really a positive integer (one or more digits)
// FIXME: ^^ not always, see start and value of list items
$this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true);

View File

@ -204,7 +204,7 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
$this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
$this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(array(
'collapse', 'seperate'));
'collapse', 'separate'));
$this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(array(
'top', 'bottom'));
@ -219,6 +219,8 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
new HTMLPurifier_AttrDef_CSS_Percentage()
));
$this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
// partial support
$this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(array('nowrap'));

View File

@ -42,7 +42,7 @@ class HTMLPurifier_Config
/**
* HTML Purifier's version
*/
var $version = '2.1.1';
var $version = '2.1.2';
/**
* Two-level associative array of configuration directives

View File

@ -330,7 +330,7 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
if (isset($this->info_content_sets['Block'][$block_wrapper])) {
$this->info_block_wrapper = $block_wrapper;
} else {
trigger_error('Cannot use non-block element as block wrapper.',
trigger_error('Cannot use non-block element as block wrapper',
E_USER_ERROR);
}
@ -340,7 +340,7 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
$this->info_parent = $parent;
$this->info_parent_def = $def;
} else {
trigger_error('Cannot use unrecognized element as parent.',
trigger_error('Cannot use unrecognized element as parent',
E_USER_ERROR);
$this->info_parent_def = $this->manager->getElement($this->info_parent, true);
}

View File

@ -0,0 +1,47 @@
<?php
require_once 'HTMLPurifier/HTMLModule.php';
/**
* XHTML 1.1 Object Module, defines elements for generic object inclusion
* @warning Users will commonly use <embed> to cater to legacy browsers: this
* module does not allow this sort of behavior
*/
class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule
{
var $name = 'Object';
function HTMLPurifier_HTMLModule_Object() {
$this->addElement('object', false, 'Inline', 'Optional: #PCDATA | Flow | param', 'Common',
array(
'archive' => 'URI',
'classid' => 'URI',
'codebase' => 'URI',
'codetype' => 'Text',
'data' => 'URI',
'declare' => 'Bool#declare',
'height' => 'Length',
'name' => 'CDATA',
'standby' => 'Text',
'tabindex' => 'Number',
'type' => 'ContentType',
'width' => 'Length'
)
);
$this->addElement('param', false, false, 'Empty', false,
array(
'id' => 'ID',
'name*' => 'Text',
'type' => 'Text',
'value' => 'Text',
'valuetype' => 'Enum#data,ref,object'
)
);
}
}

View File

@ -29,6 +29,7 @@ require_once 'HTMLPurifier/HTMLModule/Scripting.php';
require_once 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
require_once 'HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php';
require_once 'HTMLPurifier/HTMLModule/Ruby.php';
require_once 'HTMLPurifier/HTMLModule/Object.php';
// tidy modules
require_once 'HTMLPurifier/HTMLModule/Tidy.php';
@ -172,7 +173,7 @@ class HTMLPurifier_HTMLModuleManager
$common = array(
'CommonAttributes', 'Text', 'Hypertext', 'List',
'Presentation', 'Edit', 'Bdo', 'Tables', 'Image',
'StyleAttribute', 'Scripting'
'StyleAttribute', 'Scripting', 'Object'
);
$transitional = array('Legacy', 'Target');
$xml = array('XMLCommonAttributes');

View File

@ -189,6 +189,9 @@ class HTMLPurifier_Lexer
return new HTMLPurifier_Lexer_DOMLex();
case 'DirectLex':
return new HTMLPurifier_Lexer_DirectLex();
case 'PH5P':
// experimental Lexer that must be manually included
return new HTMLPurifier_Lexer_PH5P();
default:
trigger_error("Cannot instantiate unrecognized Lexer type " . htmlspecialchars($lexer), E_USER_ERROR);
}

View File

@ -53,14 +53,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
}
// preprocess html, essential for UTF-8
$html =
'<!DOCTYPE html '.
'PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'.
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'.
'<html><head>'.
'<meta http-equiv="Content-Type" content="text/html;'.
' charset=utf-8" />'.
'</head><body><div>'.$html.'</div></body></html>';
$html = $this->wrapHTML($html, $config, $context);
$doc = new DOMDocument();
$doc->encoding = 'UTF-8'; // theoretically, the above has this covered
@ -177,5 +170,25 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
return '<!--' . str_replace('&', '&amp;', $matches[1]) . $matches[2];
}
/**
* Wraps an HTML fragment in the necessary HTML
*/
function wrapHTML($html, $config, &$context) {
$def = $config->getDefinition('HTML');
$ret = '';
if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) {
$ret .= '<!DOCTYPE html ';
if (!empty($def->doctype->dtdPublic)) $ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" ';
if (!empty($def->doctype->dtdSystem)) $ret .= '"' . $def->doctype->dtdSystem . '" ';
$ret .= '>';
}
$ret .= '<html><head>';
$ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
$ret .= '</head><body><div>'.$html.'</div></body></html>';
return $ret;
}
}

View File

@ -237,7 +237,7 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
// trailing slash. Remember, we could have a tag like <br>, so
// any later token processing scripts must convert improperly
// classified EmptyTags from StartTags.
$is_self_closing= (strpos($segment,'/') === $strlen_segment-1);
$is_self_closing= (strrpos($segment,'/') === $strlen_segment-1);
if ($is_self_closing) {
$strlen_segment--;
$segment = substr($segment, 0, $strlen_segment);

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,9 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
/**
* Whether or not to compress directive names, clipping them off
* after a certain amount of letters
* after a certain amount of letters. False to disable or integer letters
* before clipping.
* @protected
*/
var $compress = false;
@ -41,11 +43,13 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
$this->docURL = $doc_url;
$this->name = $name;
$this->compress = $compress;
// initialize sub-printers
$this->fields['default'] = new HTMLPurifier_Printer_ConfigForm_default();
$this->fields['bool'] = new HTMLPurifier_Printer_ConfigForm_bool();
}
/**
* Sets default column and row size for textareas in sub-printers
* @param $cols Integer columns of textarea, null to use default
* @param $rows Integer rows of textarea, null to use default
*/
@ -55,15 +59,14 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
}
/**
* Retrieves styling, in case the directory it's in is not publically
* available
* Retrieves styling, in case it is not accessible by webserver
*/
function getCSS() {
return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.css');
}
/**
* Retrieves JavaScript, in case directory is not public
* Retrieves JavaScript, in case it is not accessible by webserver
*/
function getJavaScript() {
return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js');
@ -97,14 +100,14 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
$ret .= $this->renderNamespace($ns, $directives);
}
if ($render_controls) {
$ret .= $this->start('tfoot');
$ret .= $this->start('tbody');
$ret .= $this->start('tr');
$ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls'));
$ret .= $this->elementEmpty('input', array('type' => 'Submit', 'value' => 'Submit'));
$ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit'));
$ret .= '[<a href="?">Reset</a>]';
$ret .= $this->end('td');
$ret .= $this->end('tr');
$ret .= $this->end('tfoot');
$ret .= $this->end('tbody');
}
$ret .= $this->end('table');
return $ret;

View File

@ -102,6 +102,7 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer
$ret .= $this->element('td', $this->listifyTagLookup($lookup));
$ret .= $this->end('tr');
}
$ret .= $this->end('table');
return $ret;
}
@ -179,7 +180,8 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer
$def->validateChildren(array(), $this->config, $context);
}
$elements = $def->elements;
} elseif ($def->type == 'chameleon') {
}
if ($def->type == 'chameleon') {
$attr['rowspan'] = 2;
} elseif ($def->type == 'empty') {
$elements = array();

View File

@ -1,5 +1,12 @@
<?php
require_once 'HTMLPurifier/URIScheme/http.php';
require_once 'HTMLPurifier/URIScheme/https.php';
require_once 'HTMLPurifier/URIScheme/mailto.php';
require_once 'HTMLPurifier/URIScheme/ftp.php';
require_once 'HTMLPurifier/URIScheme/nntp.php';
require_once 'HTMLPurifier/URIScheme/news.php';
HTMLPurifier_ConfigSchema::define(
'URI', 'AllowedSchemes', array(
'http' => true, // "Hypertext Transfer Protocol", nuf' said
@ -7,7 +14,6 @@ HTMLPurifier_ConfigSchema::define(
// quite useful, but not necessary
'mailto' => true,// Email
'ftp' => true, // "File Transfer Protocol"
'irc' => true, // "Internet Relay Chat", usually needs another app
// for Usenet, these two are similar, but distinct
'nntp' => true, // individual Netnews articles
'news' => true // newsgroup or individual Netnews articles
@ -54,12 +60,6 @@ class HTMLPurifier_URISchemeRegistry
*/
var $schemes = array();
/**
* Directory where scheme objects can be found
* @private
*/
var $_scheme_dir = null;
/**
* Retrieves a scheme validator object
* @param $scheme String scheme name like http or mailto
@ -79,21 +79,16 @@ class HTMLPurifier_URISchemeRegistry
}
if (isset($this->schemes[$scheme])) return $this->schemes[$scheme];
if (empty($this->_dir)) $this->_dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/URIScheme/';
if (!isset($allowed_schemes[$scheme])) return $null;
// this bit of reflection is not very efficient, and a bit
// hacky too
$class = 'HTMLPurifier_URIScheme_' . $scheme;
if (!class_exists($class)) include_once $this->_dir . $scheme . '.php';
if (!class_exists($class)) return $null;
$this->schemes[$scheme] = new $class();
return $this->schemes[$scheme];
}
/**
* Registers a custom scheme to the cache.
* Registers a custom scheme to the cache, bypassing reflection.
* @param $scheme Scheme name
* @param $scheme_obj HTMLPurifier_URIScheme object
*/

45
maintenance/PH5P.patch Normal file
View File

@ -0,0 +1,45 @@
--- old.php 2007-08-19 14:42:33.640625000 -0400
+++ new.php 2007-08-19 14:41:51.609375000 -0400
@@ -211,7 +211,10 @@
// If nothing is returned, emit a U+0026 AMPERSAND character token.
// Otherwise, emit the character token that was returned.
$char = (!$entity) ? '&' : $entity;
- $this->emitToken($char);
+ $this->emitToken(array(
+ 'type' => self::CHARACTR,
+ 'data' => $char
+ ));
// Finally, switch to the data state.
$this->state = 'data';
@@ -708,7 +711,7 @@
} elseif($char === '&') {
/* U+0026 AMPERSAND (&)
Switch to the entity in attribute value state. */
- $this->entityInAttributeValueState('non');
+ $this->entityInAttributeValueState();
} elseif($char === '>') {
/* U+003E GREATER-THAN SIGN (>)
@@ -738,7 +741,8 @@
? '&'
: $entity;
- $this->emitToken($char);
+ $last = count($this->token['attr']) - 1;
+ $this->token['attr'][$last]['value'] .= $char;
}
private function bogusCommentState() {
@@ -1066,6 +1070,11 @@
$this->char++;
if(in_array($id, $this->entities)) {
+ if ($e_name[$c-1] !== ';') {
+ if ($c < $len && $e_name[$c] == ';') {
+ $this->char++; // consume extra semicolon
+ }
+ }
$entity = $id;
break;
}

View File

@ -1,5 +1,7 @@
<?php
require_once 'compat-function-file-put-contents.php';
function assertCli() {
if (php_sapi_name() != 'cli' && !getenv('PHP_IS_CLI')) {
echo 'Script cannot be called from web-browser (if you are calling via cli,
@ -7,3 +9,135 @@ set environment variable PHP_IS_CLI to work around this).';
exit;
}
}
/**
* Filesystem tools not provided by default; can recursively create, copy
* and delete folders. Some template methods are provided for extensibility.
* @note This class must be instantiated to be used, although it does
* not maintain state.
*/
class FSTools
{
/**
* Recursively creates a directory
* @param string $folder Name of folder to create
* @note Adapted from the PHP manual comment 76612
*/
function mkdir($folder) {
$folders = preg_split("#[\\\\/]#", $folder);
$base = '';
for($i = 0, $c = count($folders); $i < $c; $i++) {
if(empty($folders[$i])) {
if (!$i) {
// special case for root level
$base .= DIRECTORY_SEPARATOR;
}
continue;
}
$base .= $folders[$i];
if(!is_dir($base)){
mkdir($base);
}
$base .= DIRECTORY_SEPARATOR;
}
}
/**
* Copy a file, or recursively copy a folder and its contents; modified
* so that copied files, if PHP, have includes removed
*
* @author Aidan Lister <aidan@php.net>
* @version 1.0.1-modified
* @link http://aidanlister.com/repos/v/function.copyr.php
* @param string $source Source path
* @param string $dest Destination path
* @return bool Returns TRUE on success, FALSE on failure
*/
function copyr($source, $dest) {
// Simple copy for a file
if (is_file($source)) {
return $this->copy($source, $dest);
}
// Make destination directory
if (!is_dir($dest)) {
mkdir($dest);
}
// Loop through the folder
$dir = dir($source);
while (false !== $entry = $dir->read()) {
// Skip pointers
if ($entry == '.' || $entry == '..') {
continue;
}
if (!$this->copyable($entry)) {
continue;
}
// Deep copy directories
if ($dest !== "$source/$entry") {
$this->copyr("$source/$entry", "$dest/$entry");
}
}
// Clean up
$dir->close();
return true;
}
/**
* Stub for PHP's built-in copy function, can be used to overload
* functionality
*/
function copy($source, $dest) {
return copy($source, $dest);
}
/**
* Overloadable function that tests a filename for copyability. By
* default, everything should be copied; you can restrict things to
* ignore hidden files, unreadable files, etc.
*/
function copyable($file) {
return true;
}
/**
* Delete a file, or a folder and its contents
*
* @author Aidan Lister <aidan@php.net>
* @version 1.0.3
* @link http://aidanlister.com/repos/v/function.rmdirr.php
* @param string $dirname Directory to delete
* @return bool Returns TRUE on success, FALSE on failure
*/
function rmdirr($dirname)
{
// Sanity check
if (!file_exists($dirname)) {
return false;
}
// Simple delete for a file
if (is_file($dirname) || is_link($dirname)) {
return unlink($dirname);
}
// Loop through the folder
$dir = dir($dirname);
while (false !== $entry = $dir->read()) {
// Skip pointers
if ($entry == '.' || $entry == '..') {
continue;
}
// Recurse
$this->rmdirr($dirname . DIRECTORY_SEPARATOR . $entry);
}
// Clean up
$dir->close();
return rmdir($dirname);
}
}

View File

@ -0,0 +1,107 @@
<?php
// $Id: file_put_contents.php,v 1.27 2007/04/17 10:09:56 arpad Exp $
if (!defined('FILE_USE_INCLUDE_PATH')) {
define('FILE_USE_INCLUDE_PATH', 1);
}
if (!defined('LOCK_EX')) {
define('LOCK_EX', 2);
}
if (!defined('FILE_APPEND')) {
define('FILE_APPEND', 8);
}
/**
* Replace file_put_contents()
*
* @category PHP
* @package PHP_Compat
* @license LGPL - http://www.gnu.org/licenses/lgpl.html
* @copyright 2004-2007 Aidan Lister <aidan@php.net>, Arpad Ray <arpad@php.net>
* @link http://php.net/function.file_put_contents
* @author Aidan Lister <aidan@php.net>
* @version $Revision: 1.27 $
* @internal resource_context is not supported
* @since PHP 5
* @require PHP 4.0.0 (user_error)
*/
function php_compat_file_put_contents($filename, $content, $flags = null, $resource_context = null)
{
// If $content is an array, convert it to a string
if (is_array($content)) {
$content = implode('', $content);
}
// If we don't have a string, throw an error
if (!is_scalar($content)) {
user_error('file_put_contents() The 2nd parameter should be either a string or an array',
E_USER_WARNING);
return false;
}
// Get the length of data to write
$length = strlen($content);
// Check what mode we are using
$mode = ($flags & FILE_APPEND) ?
'a' :
'wb';
// Check if we're using the include path
$use_inc_path = ($flags & FILE_USE_INCLUDE_PATH) ?
true :
false;
// Open the file for writing
if (($fh = @fopen($filename, $mode, $use_inc_path)) === false) {
user_error('file_put_contents() failed to open stream: Permission denied',
E_USER_WARNING);
return false;
}
// Attempt to get an exclusive lock
$use_lock = ($flags & LOCK_EX) ? true : false ;
if ($use_lock === true) {
if (!flock($fh, LOCK_EX)) {
return false;
}
}
// Write to the file
$bytes = 0;
if (($bytes = @fwrite($fh, $content)) === false) {
$errormsg = sprintf('file_put_contents() Failed to write %d bytes to %s',
$length,
$filename);
user_error($errormsg, E_USER_WARNING);
return false;
}
// Close the handle
@fclose($fh);
// Check all the data was written
if ($bytes != $length) {
$errormsg = sprintf('file_put_contents() Only %d of %d bytes written, possibly out of free disk space.',
$bytes,
$length);
user_error($errormsg, E_USER_WARNING);
return false;
}
// Return length
return $bytes;
}
// Define
if (!function_exists('file_put_contents')) {
function file_put_contents($filename, $content, $flags = null, $resource_context = null)
{
return php_compat_file_put_contents($filename, $content, $flags, $resource_context);
}
}

View File

@ -6,20 +6,38 @@ assertCli();
/**
* Compiles all of HTML Purifier's library files into one big file
* named HTMLPurifier.standalone.php. Operates recursively, and will
* barf if there are conditional includes.
*
* Details: also creates blank "include" files in the test/blank directory
* in order to simulate require_once's inside the test files.
* named HTMLPurifier.standalone.php.
*/
/**
* Global array that tracks already loaded includes
* Global hash that tracks already loaded includes
*/
$GLOBALS['loaded'] = array('HTMLPurifier.php' => true);
/**
* @param $text Text to replace includes from
* Custom FSTools for this script that overloads some behavior
* @warning The overloading of copy() is not necessarily global for
* this script. Watch out!
*/
class MergeLibraryFSTools extends FSTools
{
function copyable($entry) {
// Skip hidden files
if ($entry[0] == '.') {
return false;
}
return true;
}
function copy($source, $dest) {
copy_and_remove_includes($source, $dest);
}
}
$FS = new MergeLibraryFSTools();
/**
* Replaces the includes inside PHP source code with the corresponding
* source.
* @param string $text PHP source code to replace includes from
*/
function replace_includes($text) {
return preg_replace_callback(
@ -32,6 +50,8 @@ function replace_includes($text) {
/**
* Removes leading PHP tags from included files. Assumes that there is
* no trailing tag.
* @note This is safe for files that have internal <?php
* @param string $text Text to have leading PHP tag from
*/
function remove_php_tags($text) {
return substr($text, 5);
@ -40,125 +60,48 @@ function remove_php_tags($text) {
/**
* Creates an appropriate blank file, recursively generating directories
* if necessary
* @param string $file Filename to create blank for
*/
function create_blank($file) {
global $FS;
$dir = dirname($file);
$base = realpath('../tests/blanks/') . DIRECTORY_SEPARATOR ;
if ($dir != '.') mkdir_deep($base . $dir);
if ($dir != '.') {
$FS->mkdir($base . $dir);
}
file_put_contents($base . $file, '');
}
/**
* Recursively creates a directory
* @note Adapted from the PHP manual comment 76612
* Copies the contents of a directory to the standalone directory
* @param string $dir Directory to copy
*/
function mkdir_deep($folder) {
$folders = preg_split("#[\\\\/]#", $folder);
$base = '';
for($i = 0, $c = count($folders); $i < $c; $i++) {
if(empty($folders[$i])) {
if (!$i) {
// special case for root level
$base .= DIRECTORY_SEPARATOR;
}
continue;
}
$base .= $folders[$i];
if(!is_dir($base)){
mkdir($base);
}
$base .= DIRECTORY_SEPARATOR;
}
function make_dir_standalone($dir) {
global $FS;
return $FS->copyr($dir, 'standalone/' . $dir);
}
/**
* Copy a file, or recursively copy a folder and its contents
*
* @author Aidan Lister <aidan@php.net>
* @version 1.0.1
* @link http://aidanlister.com/repos/v/function.copyr.php
* @param string $source Source path
* @param string $dest Destination path
* @return bool Returns TRUE on success, FALSE on failure
* Copies the contents of a file to the standalone directory
* @param string $file File to copy
*/
function copyr($source, $dest) {
// Simple copy for a file
if (is_file($source)) {
return copy($source, $dest);
}
// Make destination directory
if (!is_dir($dest)) {
mkdir($dest);
}
// Loop through the folder
$dir = dir($source);
while (false !== $entry = $dir->read()) {
// Skip pointers
if ($entry == '.' || $entry == '..') {
continue;
}
// Skip hidden files
if ($entry[0] == '.') {
continue;
}
// Deep copy directories
if ($dest !== "$source/$entry") {
copyr("$source/$entry", "$dest/$entry");
}
}
// Clean up
$dir->close();
function make_file_standalone($file) {
global $FS;
$FS->mkdir('standalone/' . dirname($file));
copy_and_remove_includes($file, 'standalone/' . $file);
return true;
}
/**
* Delete a file, or a folder and its contents
*
* @author Aidan Lister <aidan@php.net>
* @version 1.0.3
* @link http://aidanlister.com/repos/v/function.rmdirr.php
* @param string $dirname Directory to delete
* @return bool Returns TRUE on success, FALSE on failure
* Copies a file to another location recursively, if it is a PHP file
* remove includes
* @param string $file Original file
* @param string $sfile New location of file
*/
function rmdirr($dirname)
{
// Sanity check
if (!file_exists($dirname)) {
return false;
}
// Simple delete for a file
if (is_file($dirname) || is_link($dirname)) {
return unlink($dirname);
}
// Loop through the folder
$dir = dir($dirname);
while (false !== $entry = $dir->read()) {
// Skip pointers
if ($entry == '.' || $entry == '..') {
continue;
}
// Recurse
rmdirr($dirname . DIRECTORY_SEPARATOR . $entry);
}
// Clean up
$dir->close();
return rmdir($dirname);
}
/**
* Copies the contents of a directory to the standalone directory
*/
function make_dir_standalone($dir) {
return copyr($dir, 'standalone/' . $dir);
}
function make_file_standalone($file) {
mkdir_deep('standalone/' . dirname($file));
return copy($file, 'standalone/' . $file);
function copy_and_remove_includes($file, $sfile) {
$contents = file_get_contents($file);
if (strrchr($file, '.') === '.php') $contents = replace_includes($contents);
return file_put_contents($sfile, $contents);
}
/**
@ -167,8 +110,14 @@ function make_file_standalone($file) {
*/
function replace_includes_callback($matches) {
$file = $matches[1];
// PHP 5 only file
if ($file == 'HTMLPurifier/Lexer/DOMLex.php') {
$preserve = array(
// PHP 5 only
'HTMLPurifier/Lexer/DOMLex.php' => 1,
'HTMLPurifier/Printer.php' => 1,
// PEAR (external)
'XML/HTMLSax3.php' => 1
);
if (isset($preserve[$file])) {
return $matches[0];
}
if (isset($GLOBALS['loaded'][$file])) return '';
@ -192,16 +141,22 @@ file_put_contents('HTMLPurifier.standalone.php', $contents);
echo ' done!' . PHP_EOL;
echo 'Creating standalone directory...';
rmdirr('standalone'); // ensure a clean copy
mkdir_deep('standalone/HTMLPurifier/DefinitionCache/Serializer');
make_dir_standalone('HTMLPurifier/EntityLookup');
make_dir_standalone('HTMLPurifier/Language');
make_file_standalone('HTMLPurifier/Printer/ConfigForm.js');
make_file_standalone('HTMLPurifier/Printer/ConfigForm.css');
make_dir_standalone('HTMLPurifier/URIScheme');
// PHP 5 only file
mkdir_deep('standalone/HTMLPurifier/Lexer');
make_file_standalone('HTMLPurifier/Lexer/DOMLex.php');
make_file_standalone('HTMLPurifier/TokenFactory.php');
echo ' done!' . PHP_EOL;
$FS->rmdirr('standalone'); // ensure a clean copy
// data files
$FS->mkdir('standalone/HTMLPurifier/DefinitionCache/Serializer');
make_dir_standalone('HTMLPurifier/EntityLookup');
// non-standard inclusion setup
make_dir_standalone('HTMLPurifier/Language');
// optional components
make_file_standalone('HTMLPurifier/Printer.php');
make_dir_standalone('HTMLPurifier/Printer');
make_dir_standalone('HTMLPurifier/Filter');
make_file_standalone('HTMLPurifier/Lexer/PEARSax3.php');
// PHP 5 only files
make_file_standalone('HTMLPurifier/Lexer/DOMLex.php');
make_file_standalone('HTMLPurifier/Lexer/PH5P.php');
echo ' done!' . PHP_EOL;

View File

@ -31,7 +31,7 @@ while (false !== ($filename = readdir($dh))) {
if ($filename == 'all.php') continue;
if ($filename == 'testSchema.php') continue;
?>
<iframe src="<?php echo escapeHTML($filename); ?>"></iframe>
<iframe src="<?php echo escapeHTML($filename); if (isset($_GET['standalone'])) {echo '?standalone';} ?>"></iframe>
<?php
}

View File

@ -2,7 +2,11 @@
header('Content-type: text/html; charset=UTF-8');
require_once '../library/HTMLPurifier.auto.php';
if (!isset($_GET['standalone'])) {
require_once '../library/HTMLPurifier.auto.php';
} else {
require_once '../library/HTMLPurifier.standalone.php';
}
error_reporting(E_ALL | E_STRICT);
function escapeHTML($string) {

View File

@ -54,14 +54,14 @@ function isInScopes($array = array()) {
}
/**#@-*/
function printTokens($tokens, $index) {
function printTokens($tokens, $index = null) {
$string = '<pre>';
$generator = new HTMLPurifier_Generator();
foreach ($tokens as $i => $token) {
if ($index == $i) $string .= '[<strong>';
if ($index === $i) $string .= '[<strong>';
$string .= "<sup>$i</sup>";
$string .= $generator->escape($generator->generateFromToken($token));
if ($index == $i) $string .= '</strong>]';
if ($index === $i) $string .= '</strong>]';
}
$string .= '</pre>';
echo $string;

View File

@ -67,6 +67,7 @@ class HTMLPurifier_AttrDef_CSSTest extends HTMLPurifier_AttrDefHarness
$this->assertDef('border:1px solid #000;');
$this->assertDef('border-bottom:2em double #FF00FA;');
$this->assertDef('border-collapse:collapse;');
$this->assertDef('border-collapse:separate;');
$this->assertDef('caption-side:top;');
$this->assertDef('vertical-align:middle;');
$this->assertDef('vertical-align:12px;');
@ -79,6 +80,8 @@ class HTMLPurifier_AttrDef_CSSTest extends HTMLPurifier_AttrDefHarness
$this->assertDef('background-repeat:repeat-y;');
$this->assertDef('background-attachment:fixed;');
$this->assertDef('background-position:left 90%;');
$this->assertDef('border-spacing:1em;');
$this->assertDef('border-spacing:1em 2em;');
// duplicates
$this->assertDef('text-align:right;text-align:left;',

View File

@ -11,18 +11,19 @@ class HTMLPurifier_AttrTransform_BdoDirTest extends HTMLPurifier_AttrTransformHa
$this->obj = new HTMLPurifier_AttrTransform_BdoDir();
}
function test() {
function testAddDefaultDir() {
$this->assertResult( array(), array('dir' => 'ltr') );
// leave existing dir alone
}
function testPreserveExistingDir() {
$this->assertResult( array('dir' => 'rtl') );
// use a different default
}
function testAlternateDefault() {
$this->config->set('Attr', 'DefaultTextDir', 'rtl');
$this->assertResult(
array(),
array('dir' => 'rtl'),
array('Attr.DefaultTextDir' => 'rtl')
array('dir' => 'rtl')
);
}

View File

@ -3,6 +3,10 @@
require_once 'HTMLPurifier/AttrTransform/BgColor.php';
require_once 'HTMLPurifier/AttrTransformHarness.php';
// we currently rely on the CSS validator to fix any problems.
// This means that this transform, strictly speaking, supports
// a superset of the functionality.
class HTMLPurifier_AttrTransform_BgColorTest extends HTMLPurifier_AttrTransformHarness
{
@ -11,31 +15,31 @@ class HTMLPurifier_AttrTransform_BgColorTest extends HTMLPurifier_AttrTransformH
$this->obj = new HTMLPurifier_AttrTransform_BgColor();
}
function test() {
function testEmptyInput() {
$this->assertResult( array() );
// we currently rely on the CSS validator to fix any problems.
// This means that this transform, strictly speaking, supports
// a superset of the functionality.
}
function testBasicTransform() {
$this->assertResult(
array('bgcolor' => '#000000'),
array('style' => 'background-color:#000000;')
);
}
function testPrependNewCSS() {
$this->assertResult(
array('bgcolor' => '#000000', 'style' => 'font-weight:bold'),
array('style' => 'background-color:#000000;font-weight:bold')
);
}
function testLenientTreatmentOfInvalidInput() {
// this may change when we natively support the datatype and
// validate its contents before forwarding it on
$this->assertResult(
array('bgcolor' => '#F00'),
array('style' => 'background-color:#F00;')
);
}
}

View File

@ -11,27 +11,29 @@ class HTMLPurifier_AttrTransform_BoolToCSSTest extends HTMLPurifier_AttrTransfor
$this->obj = new HTMLPurifier_AttrTransform_BoolToCSS('foo', 'bar:3in;');
}
function test() {
function testEmptyInput() {
$this->assertResult( array() );
}
function testBasicTransform() {
$this->assertResult(
array('foo' => 'foo'),
array('style' => 'bar:3in;')
);
// boolean attribute just has to be set: we don't care about
// anything else
}
function testIgnoreValueOfBooleanAttribute() {
$this->assertResult(
array('foo' => 'no'),
array('style' => 'bar:3in;')
);
}
function testPrependCSS() {
$this->assertResult(
array('foo' => 'foo', 'style' => 'background-color:#F00;'),
array('style' => 'bar:3in;background-color:#F00;')
);
}
}

View File

@ -12,27 +12,29 @@ class HTMLPurifier_AttrTransform_BorderTest extends HTMLPurifier_AttrTransformHa
$this->obj = new HTMLPurifier_AttrTransform_Border();
}
function test() {
function testEmptyInput() {
$this->assertResult( array() );
}
function testBasicTransform() {
$this->assertResult(
array('border' => '1'),
array('style' => 'border:1px solid;')
);
// once again, no validation done here, we expect CSS validator
// to catch it
}
function testLenientTreatmentOfInvalidInput() {
$this->assertResult(
array('border' => '10%'),
array('style' => 'border:10%px solid;')
);
}
function testPrependNewCSS() {
$this->assertResult(
array('border' => '23', 'style' => 'font-weight:bold;'),
array('style' => 'border:23px solid;font-weight:bold;')
);
}
}

View File

@ -6,38 +6,44 @@ require_once 'HTMLPurifier/AttrTransformHarness.php';
class HTMLPurifier_AttrTransform_EnumToCSSTest extends HTMLPurifier_AttrTransformHarness
{
function testRegular() {
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
'left' => 'text-align:left;',
'right' => 'text-align:right;'
));
// leave empty arrays alone
}
function testEmptyInput() {
$this->assertResult( array() );
// leave arrays without interesting stuff alone
}
function testPreserveArraysWithoutInterestingAttributes() {
$this->assertResult( array('style' => 'font-weight:bold;') );
// test each of the conversions
}
function testConvertAlignLeft() {
$this->assertResult(
array('align' => 'left'),
array('style' => 'text-align:left;')
);
}
function testConvertAlignRight() {
$this->assertResult(
array('align' => 'right'),
array('style' => 'text-align:right;')
);
// drop garbage value
}
function testRemoveInvalidAlign() {
$this->assertResult(
array('align' => 'invalid'),
array()
);
// test CSS munging
}
function testPrependNewCSS() {
$this->assertResult(
array('align' => 'left', 'style' => 'font-weight:bold;'),
array('style' => 'text-align:left;font-weight:bold;')
@ -46,31 +52,23 @@ class HTMLPurifier_AttrTransform_EnumToCSSTest extends HTMLPurifier_AttrTransfor
}
function testCaseInsensitive() {
$this->obj = new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
'right' => 'text-align:right;'
));
// test case insensitivity
$this->assertResult(
array('align' => 'RIGHT'),
array('style' => 'text-align:right;')
);
}
function testCaseSensitive() {
$this->obj = new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
'right' => 'text-align:right;'
), true);
// test case insensitivity
$this->assertResult(
array('align' => 'RIGHT'),
array()
);
}
}

View File

@ -11,39 +11,37 @@ class HTMLPurifier_AttrTransform_ImgRequiredTest extends HTMLPurifier_AttrTransf
$this->obj = new HTMLPurifier_AttrTransform_ImgRequired();
}
function test() {
function testAddMissingAttr() {
$this->config->set('Core', 'RemoveInvalidImg', false);
$this->assertResult(
array(),
array('src' => '', 'alt' => 'Invalid image'),
array(
'Core.RemoveInvalidImg' => false
)
array('src' => '', 'alt' => 'Invalid image')
);
}
function testAlternateDefaults() {
$this->config->set('Attr', 'DefaultInvalidImage', 'blank.png');
$this->config->set('Attr', 'DefaultInvalidImageAlt', 'Pawned!');
$this->config->set('Core', 'RemoveInvalidImg', false);
$this->assertResult(
array(),
array('src' => 'blank.png', 'alt' => 'Pawned!'),
array(
'Attr.DefaultInvalidImage' => 'blank.png',
'Attr.DefaultInvalidImageAlt' => 'Pawned!',
'Core.RemoveInvalidImg' => false
)
array('src' => 'blank.png', 'alt' => 'Pawned!')
);
}
function testGenerateAlt() {
$this->assertResult(
array('src' => '/path/to/foobar.png'),
array('src' => '/path/to/foobar.png', 'alt' => 'foobar.png')
);
}
function testAddDefaultSrc() {
$this->config->set('Core', 'RemoveInvalidImg', false);
$this->assertResult(
array('alt' => 'intrigue'),
array('alt' => 'intrigue', 'src' => ''),
array(
'Core.RemoveInvalidImg' => false
)
array('alt' => 'intrigue', 'src' => '')
);
}
}

View File

@ -9,33 +9,35 @@ class HTMLPurifier_AttrTransform_ImgSpaceTest extends HTMLPurifier_AttrTransform
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_AttrTransform_ImgSpace('vspace');
}
function testVertical() {
$this->obj = new HTMLPurifier_AttrTransform_ImgSpace('vspace');
function testEmptyInput() {
$this->assertResult( array() );
}
function testVerticalBasicUsage() {
$this->assertResult(
array('vspace' => '1'),
array('style' => 'margin-top:1px;margin-bottom:1px;')
);
// no validation done here, we expect CSS validator to catch it
}
function testLenientHandlingOfInvalidInput() {
$this->assertResult(
array('vspace' => '10%'),
array('style' => 'margin-top:10%px;margin-bottom:10%px;')
);
}
function testPrependNewCSS() {
$this->assertResult(
array('vspace' => '23', 'style' => 'font-weight:bold;'),
array('style' => 'margin-top:23px;margin-bottom:23px;font-weight:bold;')
);
}
function testHorizontal() {
function testHorizontalBasicUsage() {
$this->obj = new HTMLPurifier_AttrTransform_ImgSpace('hspace');
$this->assertResult(
array('hspace' => '1'),
@ -43,7 +45,7 @@ class HTMLPurifier_AttrTransform_ImgSpaceTest extends HTMLPurifier_AttrTransform
);
}
function testInvalid() {
function testInvalidConstructionParameter() {
$this->expectError('ispace is not valid space attribute');
$this->obj = new HTMLPurifier_AttrTransform_ImgSpace('ispace');
$this->assertResult(

View File

@ -13,35 +13,36 @@ class HTMLPurifier_AttrTransform_LangTest
$this->obj = new HTMLPurifier_AttrTransform_Lang();
}
function test() {
// leave non-lang'ed elements alone
$this->assertResult(array(), true);
// copy lang to xml:lang
function testEmptyInput() {
$this->assertResult(array());
}
function testCopyLangToXMLLang() {
$this->assertResult(
array('lang' => 'en'),
array('lang' => 'en', 'xml:lang' => 'en')
);
// preserve attributes
}
function testPreserveAttributes() {
$this->assertResult(
array('src' => 'vert.png', 'lang' => 'fr'),
array('src' => 'vert.png', 'lang' => 'fr', 'xml:lang' => 'fr')
);
// copy xml:lang to lang
}
function testCopyXMLLangToLang() {
$this->assertResult(
array('xml:lang' => 'en'),
array('xml:lang' => 'en', 'lang' => 'en')
);
// both set, override lang with xml:lang
}
function testXMLLangOverridesLang() {
$this->assertResult(
array('lang' => 'fr', 'xml:lang' => 'de'),
array('lang' => 'de', 'xml:lang' => 'de')
);
}
}

View File

@ -11,21 +11,32 @@ class HTMLPurifier_AttrTransform_LengthTest extends HTMLPurifier_AttrTransformHa
$this->obj = new HTMLPurifier_AttrTransform_Length('width');
}
function test() {
function testEmptyInput() {
$this->assertResult( array() );
}
function testTransformPixel() {
$this->assertResult(
array('width' => '10'),
array('style' => 'width:10px;')
);
}
function testTransformPercentage() {
$this->assertResult(
array('width' => '10%'),
array('style' => 'width:10%;')
);
}
function testPrependNewCSS() {
$this->assertResult(
array('width' => '10%', 'style' => 'font-weight:bold'),
array('style' => 'width:10%;font-weight:bold')
);
// this behavior might change
}
function testLenientTreatmentOfInvalidInput() {
$this->assertResult(
array('width' => 'asdf'),
array('style' => 'width:asdf;')

View File

@ -11,12 +11,18 @@ class HTMLPurifier_AttrTransform_NameTest extends HTMLPurifier_AttrTransformHarn
$this->obj = new HTMLPurifier_AttrTransform_Name();
}
function test() {
function testEmpty() {
$this->assertResult( array() );
}
function testTransformNameToID() {
$this->assertResult(
array('name' => 'free'),
array('id' => 'free')
);
}
function testExistingIDOverridesName() {
$this->assertResult(
array('name' => 'tryit', 'id' => 'tobad'),
array('id' => 'tobad')

View File

@ -6,6 +6,7 @@ class HTMLPurifier_AttrTransformHarness extends HTMLPurifier_ComplexHarness
{
function setUp() {
parent::setUp();
$this->func = 'transform';
}

View File

@ -35,7 +35,7 @@ class HTMLPurifier_AttrValidator_ErrorsTest extends HTMLPurifier_ErrorsHarness
$this->invoke($token);
}
// to lazy to check for global post and global pre
// too lazy to check for global post and global pre
function testAttributeRemoved() {
$this->expectErrorCollection(E_ERROR, 'AttrValidator: Attribute removed');

View File

@ -6,28 +6,36 @@ require_once 'HTMLPurifier/ChildDef/Chameleon.php';
class HTMLPurifier_ChildDef_ChameleonTest extends HTMLPurifier_ChildDefHarness
{
function test() {
var $isInline;
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_ChildDef_Chameleon(
'b | i', // allowed only when in inline context
'b | i | div' // allowed only when in block context
);
$this->context->register('IsInline', $this->isInline);
}
function testInlineAlwaysAllowed() {
$this->isInline = true;
$this->assertResult(
'<b>Allowed.</b>', true,
array(), array('IsInline' => true)
'<b>Allowed.</b>'
);
}
function testBlockNotAllowedInInline() {
$this->isInline = true;
$this->assertResult(
'<div>Not allowed.</div>', '',
array(), array('IsInline' => true)
'<div>Not allowed.</div>', ''
);
}
function testBlockAllowedInNonInline() {
$this->isInline = false;
$this->assertResult(
'<div>Allowed.</div>', true,
array(), array('IsInline' => false)
'<div>Allowed.</div>'
);
}
}

View File

@ -6,13 +6,17 @@ require_once 'HTMLPurifier/ChildDef/Optional.php';
class HTMLPurifier_ChildDef_OptionalTest extends HTMLPurifier_ChildDefHarness
{
function test() {
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_ChildDef_Optional('b | i');
}
function testBasicUsage() {
$this->assertResult('<b>Bold text</b><img />', '<b>Bold text</b>');
}
function testRemoveForbiddenText() {
$this->assertResult('Not allowed text', '');
}
}

View File

@ -6,8 +6,7 @@ require_once 'HTMLPurifier/ChildDef/Required.php';
class HTMLPurifier_ChildDef_RequiredTest extends HTMLPurifier_ChildDefHarness
{
function testParsing() {
function testPrepareString() {
$def = new HTMLPurifier_ChildDef_Required('foobar | bang |gizmo');
$this->assertIdentical($def->elements,
array(
@ -15,51 +14,61 @@ class HTMLPurifier_ChildDef_RequiredTest extends HTMLPurifier_ChildDefHarness
,'bang' => true
,'gizmo' => true
));
}
function testPrepareArray() {
$def = new HTMLPurifier_ChildDef_Required(array('href', 'src'));
$this->assertIdentical($def->elements,
array(
'href' => true
,'src' => true
));
}
function testPCDATAForbidden() {
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_ChildDef_Required('dt | dd');
}
function testEmptyInput() {
$this->assertResult('', false);
}
function testRemoveIllegalTagsAndElements() {
$this->assertResult(
'<dt>Term</dt>Text in an illegal location'.
'<dd>Definition</dd><b>Illegal tag</b>',
'<dt>Term</dt><dd>Definition</dd>');
$this->assertResult('How do you do!', false);
}
function testIgnoreWhitespace() {
// whitespace shouldn't trigger it
$this->assertResult("\n<dd>Definition</dd> ");
}
function testPreserveWhitespaceAfterRemoval() {
$this->assertResult(
'<dd>Definition</dd> <b></b> ',
'<dd>Definition</dd> '
);
}
function testDeleteNodeIfOnlyWhitespace() {
$this->assertResult("\t ", false);
}
function testPCDATAAllowed() {
$this->obj = new HTMLPurifier_ChildDef_Required('#PCDATA | b');
$this->assertResult('<b>Bold text</b><img />', '<b>Bold text</b>');
// with child escaping on
$this->assertResult('Out <b>Bold text</b><img />', 'Out <b>Bold text</b>');
}
function testPCDATAAllowedWithEscaping() {
$this->obj = new HTMLPurifier_ChildDef_Required('#PCDATA | b');
$this->config->set('Core', 'EscapeInvalidChildren', true);
$this->assertResult(
'<b>Bold text</b><img />',
'<b>Bold text</b>&lt;img /&gt;',
array(
'Core.EscapeInvalidChildren' => true
)
'Out <b>Bold text</b><img />',
'Out <b>Bold text</b>&lt;img /&gt;'
);
}

View File

@ -7,48 +7,77 @@ class HTMLPurifier_ChildDef_StrictBlockquoteTest
extends HTMLPurifier_ChildDefHarness
{
function test() {
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_ChildDef_StrictBlockquote('div | p');
// assuming default wrap is p
}
function testEmptyInput() {
$this->assertResult('');
}
function testPreserveValidP() {
$this->assertResult('<p>Valid</p>');
}
function testPreserveValidDiv() {
$this->assertResult('<div>Still valid</div>');
}
function testWrapTextWithP() {
$this->assertResult('Needs wrap', '<p>Needs wrap</p>');
}
function testNoWrapForWhitespaceOrValidElements() {
$this->assertResult('<p>Do not wrap</p> <p>Whitespace</p>');
}
function testWrapTextNextToValidElements() {
$this->assertResult(
'Wrap'. '<p>Do not wrap</p>',
'<p>Wrap</p><p>Do not wrap</p>'
);
}
function testWrapInlineElements() {
$this->assertResult(
'<p>Do not</p>'.'<b>Wrap</b>',
'<p>Do not</p><p><b>Wrap</b></p>'
);
}
function testWrapAndRemoveInvalidTags() {
$this->assertResult(
'<li>Not allowed</li>Paragraph.<p>Hmm.</p>',
'<p>Not allowedParagraph.</p><p>Hmm.</p>'
);
}
function testWrapComplicatedSring() {
$this->assertResult(
$var = 'He said<br />perhaps<br />we should <b>nuke</b> them.',
"<p>$var</p>"
);
}
function testWrapAndRemoveInvalidTagsComplex() {
$this->assertResult(
'<foo>Bar</foo><bas /><b>People</b>Conniving.'. '<p>Fools!</p>',
'<p>Bar'. '<b>People</b>Conniving.</p><p>Fools!</p>'
);
$this->assertResult('Needs wrap', '<div>Needs wrap</div>',
array('HTML.BlockWrapper' => 'div'));
}
function testAlternateWrapper() {
$this->config->set('HTML', 'BlockWrapper', 'div');
$this->assertResult('Needs wrap', '<div>Needs wrap</div>');
}
function testError() {
$this->expectError('Cannot use non-block element as block wrapper');
$this->obj = new HTMLPurifier_ChildDef_StrictBlockquote('div | p');
$this->assertResult('Needs wrap', '<p>Needs wrap</p>',
array('HTML.BlockWrapper' => 'dav'));
$this->swallowErrors();
$this->config->set('HTML', 'BlockWrapper', 'dav');
$this->assertResult('Needs wrap', '<p>Needs wrap</p>');
}
}

View File

@ -3,46 +3,58 @@
require_once 'HTMLPurifier/ChildDefHarness.php';
require_once 'HTMLPurifier/ChildDef/Table.php';
// we're using empty tags to compact the tests: under real circumstances
// there would be contents in them
class HTMLPurifier_ChildDef_TableTest extends HTMLPurifier_ChildDefHarness
{
function test() {
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_ChildDef_Table();
}
function testEmptyInput() {
$this->assertResult('', false);
// we're using empty tags to compact the tests: under real circumstances
// there would be contents in them
}
function testSingleRow() {
$this->assertResult('<tr />');
}
function testComplexContents() {
$this->assertResult('<caption /><col /><thead /><tfoot /><tbody>'.
'<tr><td>asdf</td></tr></tbody>');
$this->assertResult('<col /><col /><col /><tr />');
// mixed up order
}
function testReorderContents() {
$this->assertResult(
'<col /><colgroup /><tbody /><tfoot /><thead /><tr>1</tr><caption /><tr />',
'<caption /><col /><colgroup /><thead /><tfoot /><tbody /><tr>1</tr><tr />');
// duplicates of singles
// - first caption serves
// - trailing tfoots/theads get turned into tbodys
}
function testDuplicateProcessing() {
$this->assertResult(
'<caption>1</caption><caption /><tbody /><tbody /><tfoot>1</tfoot><tfoot />',
'<caption>1</caption><tfoot>1</tfoot><tbody /><tbody /><tbody />'
);
// errant text dropped (until bubbling is implemented)
}
function testRemoveText() {
$this->assertResult('foo', false);
// whitespace sticks to the previous element, last whitespace is
// stationary
$this->assertResult("\n <tr />\n <tr />\n ", true, array('Output.Newline' => "\n"));
}
function testStickyWhitespaceOnTr() {
$this->config->set('Output', 'Newline', "\n");
$this->assertResult("\n <tr />\n <tr />\n ");
}
function testStickyWhitespaceOnTSection() {
$this->config->set('Output', 'Newline', "\n");
$this->assertResult(
"\n\t<tbody />\n\t\t<tfoot />\n\t\t\t",
"\n\t\t<tfoot />\n\t<tbody />\n\t\t\t",
array('Output.Newline' => "\n")
"\n\t\t<tfoot />\n\t<tbody />\n\t\t\t"
);
}

View File

@ -7,6 +7,7 @@ class HTMLPurifier_ChildDefHarness extends HTMLPurifier_ComplexHarness
{
function setUp() {
parent::setUp();
$this->obj = null;
$this->func = 'validateChildren';
$this->to_tokens = true;

View File

@ -67,41 +67,20 @@ class HTMLPurifier_ComplexHarness extends HTMLPurifier_Harness
* @param $context_array Context array in form of Key => Value or an actual
* context object.
*/
function assertResult($input, $expect = true,
$config_array = array(), $context_array = array()
) {
// setup config
if ($this->config) {
$config = HTMLPurifier_Config::create($this->config);
$config->autoFinalize = false;
$config->loadArray($config_array);
} else {
$config = HTMLPurifier_Config::create($config_array);
}
// setup context object. Note that we are operating on a copy of it!
// When necessary, extend the test harness to allow post-tests
// on the context object
if (empty($this->context)) {
$context = new HTMLPurifier_Context();
$context->loadArray($context_array);
} else {
$context =& $this->context;
}
function assertResult($input, $expect = true) {
if ($this->to_tokens && is_string($input)) {
// $func may cause $input to change, so "clone" another copy
// to sacrifice
$input = $this->lexer->tokenizeHTML($s = $input, $config, $context);
$input_c = $this->lexer->tokenizeHTML($s, $config, $context);
$input = $this->tokenize($temp = $input);
$input_c = $this->tokenize($temp);
} else {
$input_c = $input;
}
// call the function
$func = $this->func;
$result = $this->obj->$func($input_c, $config, $context);
$result = $this->obj->$func($input_c, $this->config, $this->context);
// test a bool result
if (is_bool($result)) {
@ -112,11 +91,9 @@ class HTMLPurifier_ComplexHarness extends HTMLPurifier_Harness
}
if ($this->to_html) {
$result = $this->generator->
generateFromTokens($result, $config, $context);
$result = $this->generate($result);
if (is_array($expect)) {
$expect = $this->generator->
generateFromTokens($expect, $config, $context);
$expect = $this->generate($expect);
}
}
@ -124,6 +101,20 @@ class HTMLPurifier_ComplexHarness extends HTMLPurifier_Harness
}
/**
* Tokenize HTML into tokens, uses member variables for common variables
*/
function tokenize($html) {
return $this->lexer->tokenizeHTML($html, $this->config, $this->context);
}
/**
* Generate textual HTML from tokens
*/
function generate($tokens) {
return $this->generator->generateFromTokens($tokens, $this->config, $this->context);
}
}

View File

@ -17,7 +17,7 @@ class HTMLPurifier_EntityLookupTest extends HTMLPurifier_Harness
// special char
$this->assertIdentical('"', $lookup->table['quot']);
$this->assertIdentical('“', $lookup->table['ldquo']);
$this->assertIdentical('<', $lookup->table['lt']); //expressed strangely
$this->assertIdentical('<', $lookup->table['lt']); // expressed strangely in source file
// symbol char
$this->assertIdentical('θ', $lookup->table['theta']);

View File

@ -0,0 +1,39 @@
<?php
require_once 'HTMLPurifier/HTMLModuleHarness.php';
class HTMLPurifier_HTMLModule_ObjectTest extends HTMLPurifier_HTMLModuleHarness
{
function setUp() {
parent::setUp();
$this->config->set('HTML', 'Trusted', true);
}
function testDefaultRemoval() {
$this->config->set('HTML', 'Trusted', false);
$this->assertResult(
'<object></object>', ''
);
}
function testMinimal() {
$this->assertResult('<object></object>');
}
function testStandardUseCase() {
$this->assertResult(
'<object type="video/x-ms-wmv" data="http://domain.com/video.wmv" width="320" height="256">
<param name="src" value="http://domain.com/video.wmv" />
<param name="autostart" value="false" />
<param name="controller" value="true" />
<param name="pluginurl" value="http://www.microsoft.com/Windows/MediaPlayer/" />
<a href="http://www.microsoft.com/Windows/MediaPlayer/">Windows Media player required</a>
</object>'
);
}
// more test-cases?
}

View File

@ -5,47 +5,51 @@ require_once 'HTMLPurifier/HTMLModuleHarness.php';
class HTMLPurifier_HTMLModule_ScriptingTest extends HTMLPurifier_HTMLModuleHarness
{
function test() {
// default (remove everything)
function setUp() {
parent::setUp();
$this->config->set('HTML', 'Trusted', true);
$this->config->set('Core', 'CommentScriptContents', false);
}
function testDefaultRemoval() {
$this->config->set('HTML', 'Trusted', false);
$this->assertResult(
'<script type="text/javascript">foo();</script>', ''
);
// enabled
}
function testPreserve() {
$this->assertResult(
'<script type="text/javascript">foo();</script>', true,
array('HTML.Trusted' => true)
'<script type="text/javascript">foo();</script>'
);
// CDATA
}
function testCDATAEnclosure() {
$this->assertResult(
'//<![CDATA[
'<script type="text/javascript">//<![CDATA[
alert("<This is compatible with XHTML>");
//]]> ', true,
array('HTML.Trusted' => true)
//]]></script>'
);
// max
}
function testAllAttributes() {
$this->assertResult(
'<script
defer="defer"
src="test.js"
type="text/javascript"
>PCDATA</script>', true,
array('HTML.Trusted' => true, 'Core.CommentScriptContents' => false)
>PCDATA</script>'
);
// unsupported
}
function testUnsupportedAttributes() {
$this->assertResult(
'<script
type="text/javascript"
charset="utf-8"
>PCDATA</script>',
'<script type="text/javascript">PCDATA</script>',
array('HTML.Trusted' => true, 'Core.CommentScriptContents' => false)
'<script type="text/javascript">PCDATA</script>'
);
}
}

View File

@ -8,29 +8,35 @@ class HTMLPurifier_Injector_AutoParagraphTest extends HTMLPurifier_InjectorHarne
function setup() {
parent::setup();
$this->config = array('AutoFormat.AutoParagraph' => true);
$this->config->set('AutoFormat', 'AutoParagraph', true);
}
function test() {
function testSingleParagraph() {
$this->assertResult(
'Foobar',
'<p>Foobar</p>'
);
}
function testSingleMultiLineParagraph() {
$this->assertResult(
'Par 1
Par 1 still',
'<p>Par 1
Par 1 still</p>'
);
}
function testTwoParagraphs() {
$this->assertResult(
'Par1
Par2',
'<p>Par1</p><p>Par2</p>'
);
}
function testTwoParagraphsWithLotsOfSpace() {
$this->assertResult(
'Par1
@ -39,15 +45,18 @@ Par2',
Par2',
'<p>Par1</p><p>Par2</p>'
);
}
function testTwoParagraphsWithInlineElements() {
$this->assertResult(
'<b>Par1</b>
<i>Par2</i>',
'<p><b>Par1</b></p><p><i>Par2</i></p>'
);
}
function testSingleParagraphThatLooksLikeTwo() {
$this->assertResult(
'<b>Par1
@ -56,29 +65,40 @@ Par2</b>',
Par2</b></p>'
);
}
function testAddParagraphAdjacentToParagraph() {
$this->assertResult(
'Par1<p>Par2</p>',
'<p>Par1</p><p>Par2</p>'
);
}
function testParagraphUnclosedInlineElement() {
$this->assertResult(
'<b>Par1',
'<p><b>Par1</b></p>'
);
}
function testPreservePreTags() {
$this->assertResult(
'<pre>Par1
Par1</pre>'
);
}
function testIgnoreTrailingWhitespace() {
$this->assertResult(
'Par1
',
'<p>Par1</p>'
);
}
function testDoNotParagraphBlockElements() {
$this->assertResult(
'Par1
@ -87,19 +107,25 @@ Par1</pre>'
Par3',
'<p>Par1</p><div>Par2</div><p>Par3</p>'
);
}
function testParagraphTextAndInlineNodes() {
$this->assertResult(
'Par<b>1</b>',
'<p>Par<b>1</b></p>'
);
}
function testIgnoreLeadingWhitespace() {
$this->assertResult(
'
Par',
'<p>Par</p>'
);
}
function testIgnoreSurroundingWhitespace() {
$this->assertResult(
'
@ -108,69 +134,90 @@ Par
',
'<p>Par</p>'
);
}
function testParagraphInsideBlockNode() {
$this->assertResult(
'<div>Par1
Par2</div>',
'<div><p>Par1</p><p>Par2</p></div>'
);
}
function testParagraphInlineNodeInsideBlockNode() {
$this->assertResult(
'<div><b>Par1</b>
Par2</div>',
'<div><p><b>Par1</b></p><p>Par2</p></div>'
);
}
function testNoParagraphWhenOnlyOneInsideBlockNode() {
$this->assertResult('<div>Par1</div>');
}
function testParagraphTwoInlineNodesInsideBlockNode() {
$this->assertResult(
'<div><b>Par1</b>
<i>Par2</i></div>',
'<div><p><b>Par1</b></p><p><i>Par2</i></p></div>'
);
}
function testPreserveInlineNodesInPreTag() {
$this->assertResult(
'<pre><b>Par1</b>
<i>Par2</i></pre>',
true
<i>Par2</i></pre>'
);
}
function testSplitUpInternalsOfPTagInBlockNode() {
$this->assertResult(
'<div><p>Foo
Bar</p></div>',
'<div><p>Foo</p><p>Bar</p></div>'
);
}
function testSplitUpInlineNodesInPTagInBlockNode() {
$this->assertResult(
'<div><p><b>Foo</b>
<i>Bar</i></p></div>',
'<div><p><b>Foo</b></p><p><i>Bar</i></p></div>'
);
}
function testNoParagraphSingleInlineNodeInBlockNode() {
$this->assertResult(
'<div><b>Foo</b></div>',
'<div><b>Foo</b></div>'
);
}
function testParagraphInBlockquote() {
$this->assertResult(
'<blockquote>Par1
Par2</blockquote>',
'<blockquote><p>Par1</p><p>Par2</p></blockquote>'
);
}
function testNoParagraphBetweenListItem() {
$this->assertResult(
'<ul><li>Foo</li>
<li>Bar</li></ul>', true
<li>Bar</li></ul>'
);
}
function testParagraphSingleElementWithSurroundingSpace() {
$this->assertResult(
'<div>
@ -179,7 +226,9 @@ Bar
</div>',
'<div><p>Bar</p></div>'
);
}
function testIgnoreExtraSpaceWithLeadingInlineNode() {
$this->assertResult(
'<b>Par1</b>a
@ -188,99 +237,124 @@ Bar
Par2',
'<p><b>Par1</b>a</p><p>Par2</p>'
);
}
function testAbsorbExtraEndingPTag() {
$this->assertResult(
'Par1
Par2</p>',
'<p>Par1</p><p>Par2</p>'
);
}
function testAbsorbExtraEndingDivTag() {
$this->assertResult(
'Par1
Par2</div>',
'<p>Par1</p><p>Par2</p>'
);
}
function testDoNotParagraphSingleSurroundingSpaceInBlockNode() {
$this->assertResult(
'<div>
Par1
</div>', true
</div>'
);
}
function testBlockNodeTextDelimeterInBlockNode() {
$this->assertResult(
'<div>Par1
<div>Par2</div></div>',
'<div><p>Par1</p><div>Par2</div></div>'
);
}
function testBlockNodeTextDelimeterWithoutDoublespaceInBlockNode() {
$this->assertResult(
'<div>Par1
<div>Par2</div></div>',
'<div><p>Par1
</p><div>Par2</div></div>'
);
}
function testBlockNodeTextDelimeterWithoutDoublespace() {
$this->assertResult(
'Par1
<div>Par2</div>',
'<p>Par1
</p><div>Par2</div>'
);
}
function testTwoParagraphsOfTextAndInlineNode() {
$this->assertResult(
'Par1
<b>Par2</b>',
'<p>Par1</p><p><b>Par2</b></p>'
);
}
function testLeadingInlineNodeParagraph() {
$this->assertResult(
'<img /> Foo',
'<p><img /> Foo</p>'
);
}
function testTrailingInlineNodeParagraph() {
$this->assertResult(
'<li>Foo <a>bar</a></li>'
);
}
function testTwoInlineNodeParagraph() {
$this->assertResult(
'<li><b>baz</b><a>bar</a></li>'
);
}
function testNoParagraphTrailingBlockNodeInBlockNode() {
$this->assertResult(
'<div><div>asdf</div><b>asdf</b></div>'
);
}
function testParagraphTrailingBlockNodeWithDoublespaceInBlockNode() {
$this->assertResult(
'<div><div>asdf</div>
<b>asdf</b></div>',
'<div><div>asdf</div><p><b>asdf</b></p></div>'
);
}
function testParagraphTwoInlineNodesAndWhitespaceNode() {
$this->assertResult(
'<b>One</b> <i>Two</i>',
'<p><b>One</b> <i>Two</i></p>'
);
}
function testInlineRootNode() {
function testNoParagraphWithInlineRootNode() {
$this->config->set('HTML', 'Parent', 'span');
$this->assertResult(
'Par
Par2',
true,
array('AutoFormat.AutoParagraph' => true, 'HTML.Parent' => 'span')
Par2'
);
}
function testNeeded() {
function testErrorNeeded() {
$this->config->set('HTML', 'Allowed', 'b');
$this->expectError('Cannot enable AutoParagraph injector because p is not allowed');
$this->assertResult('<b>foobar</b>', true, array('AutoFormat.AutoParagraph' => true, 'HTML.Allowed' => 'b'));
$this->assertResult('<b>foobar</b>');
}
}

View File

@ -8,35 +8,40 @@ class HTMLPurifier_Injector_LinkifyTest extends HTMLPurifier_InjectorHarness
function setup() {
parent::setup();
$this->config = array('AutoFormat.Linkify' => true);
$this->config->set('AutoFormat', 'Linkify', true);
}
function testLinkify() {
function testLinkifyURLInRootNode() {
$this->assertResult(
'http://example.com',
'<a href="http://example.com">http://example.com</a>'
);
}
function testLinkifyURLInInlineNode() {
$this->assertResult(
'<b>http://example.com</b>',
'<b><a href="http://example.com">http://example.com</a></b>'
);
}
function testBasicUsageCase() {
$this->assertResult(
'This URL http://example.com is what you need',
'This URL <a href="http://example.com">http://example.com</a> is what you need'
);
}
function testIgnoreURLInATag() {
$this->assertResult(
'<a>http://example.com/</a>'
);
}
function testNeeded() {
$this->config->set('HTML', 'Allowed', 'b');
$this->expectError('Cannot enable Linkify injector because a is not allowed');
$this->assertResult('http://example.com/', true, array('AutoFormat.Linkify' => true, 'HTML.Allowed' => 'b'));
$this->assertResult('http://example.com/');
}
}

View File

@ -8,39 +8,53 @@ class HTMLPurifier_Injector_PurifierLinkifyTest extends HTMLPurifier_InjectorHar
function setup() {
parent::setup();
$this->config = array(
'AutoFormat.PurifierLinkify' => true,
'AutoFormatParam.PurifierLinkifyDocURL' => '#%s'
);
$this->config->set('AutoFormat', 'PurifierLinkify', true);
$this->config->set('AutoFormatParam', 'PurifierLinkifyDocURL', '#%s');
}
function testLinkify() {
function testNoTriggerCharacer() {
$this->assertResult('Foobar');
}
function testTriggerCharacterInIrrelevantContext() {
$this->assertResult('20% off!');
}
function testPreserveNamespace() {
$this->assertResult('%Core namespace (not recognized)');
}
function testLinkifyBasic() {
$this->assertResult(
'%Namespace.Directive',
'<a href="#Namespace.Directive">%Namespace.Directive</a>'
);
}
function testLinkifyWithAdjacentTextNodes() {
$this->assertResult(
'This %Namespace.Directive thing',
'This <a href="#Namespace.Directive">%Namespace.Directive</a> thing'
);
}
function testLinkifyInBlock() {
$this->assertResult(
'<div>This %Namespace.Directive thing</div>',
'<div>This <a href="#Namespace.Directive">%Namespace.Directive</a> thing</div>'
);
}
function testPreserveInATag() {
$this->assertResult(
'<a>%Namespace.Directive</a>'
);
}
function testNeeded() {
$this->config->set('HTML', 'Allowed', 'b');
$this->expectError('Cannot enable PurifierLinkify injector because a is not allowed');
$this->assertResult('%Namespace.Directive', true, array('AutoFormat.PurifierLinkify' => true, 'HTML.Allowed' => 'b'));
$this->assertResult('%Namespace.Directive');
}
}

View File

@ -5,70 +5,98 @@ require_once 'HTMLPurifier/Lexer/DirectLex.php';
class HTMLPurifier_LexerTest extends HTMLPurifier_Harness
{
var $Lexer;
var $DirectLex, $PEARSax3, $DOMLex;
var $_entity_lookup;
var $_has_pear = false;
var $_has_dom = false;
function setUp() {
$this->Lexer = new HTMLPurifier_Lexer();
$this->DirectLex = new HTMLPurifier_Lexer_DirectLex();
if ( $GLOBALS['HTMLPurifierTest']['PEAR'] &&
((error_reporting() & E_STRICT) != E_STRICT)
function HTMLPurifier_LexerTest() {
parent::HTMLPurifier_Harness();
// E_STRICT = 2048, int used for PHP4 compat: this check disables
// PEAR if PHP 5 strict mode is on, since the class is not strict safe
if (
$GLOBALS['HTMLPurifierTest']['PEAR'] &&
((error_reporting() & 2048) != 2048) // ought to be a better way
) {
$this->_has_pear = true;
require_once 'HTMLPurifier/Lexer/PEARSax3.php';
$this->PEARSax3 = new HTMLPurifier_Lexer_PEARSax3();
$this->_has_pear = true;
}
$this->_has_dom = version_compare(PHP_VERSION, '5', '>=');
if ($this->_has_dom) {
require_once 'HTMLPurifier/Lexer/DOMLex.php';
$this->DOMLex = new HTMLPurifier_Lexer_DOMLex();
if ($GLOBALS['HTMLPurifierTest']['PH5P']) {
require_once 'HTMLPurifier/Lexer/PH5P.php';
}
$this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
}
// HTMLPurifier_Lexer::create() --------------------------------------------
function test_create() {
$config = HTMLPurifier_Config::create(array('Core.MaintainLineNumbers' => true));
$lexer = HTMLPurifier_Lexer::create($config);
$this->config->set('Core', 'MaintainLineNumbers', true);
$lexer = HTMLPurifier_Lexer::create($this->config);
$this->assertIsA($lexer, 'HTMLPurifier_Lexer_DirectLex');
}
// HTMLPurifier_Lexer->parseData() -----------------------------------------
function assertParseData($input, $expect = true) {
if ($expect === true) $expect = $input;
$lexer = new HTMLPurifier_Lexer();
$this->assertIdentical($expect, $lexer->parseData($input));
}
function test_parseData_plainText() {
$this->assertParseData('asdf');
}
function test_parseData_ampersandEntity() {
$this->assertParseData('&amp;', '&');
}
function test_parseData_quotEntity() {
$this->assertParseData('&quot;', '"');
}
function test_parseData_aposNumericEntity() {
$this->assertParseData('&#039;', "'");
}
function test_parseData_aposCompactNumericEntity() {
$this->assertParseData('&#39;', "'");
}
function test_parseData_adjacentAmpersandEntities() {
$this->assertParseData('&amp;&amp;&amp;', '&&&');
}
function test_parseData_trailingUnescapedAmpersand() {
$this->assertParseData('&amp;&', '&&');
}
function test_parseData_internalUnescapedAmpersand() {
$this->assertParseData('Procter & Gamble');
}
function test_parseData_improperEntityFaultToleranceTest() {
$this->assertParseData('&#x2D;');
}
// HTMLPurifier_Lexer->extractBody() ---------------------------------------
function assertExtractBody($text, $extract = true) {
$result = $this->Lexer->extractBody($text);
$lexer = new HTMLPurifier_Lexer();
$result = $lexer->extractBody($text);
if ($extract === true) $extract = $text;
$this->assertIdentical($extract, $result);
}
function test_parseData() {
$HP =& $this->Lexer;
$this->assertIdentical('asdf', $HP->parseData('asdf'));
$this->assertIdentical('&', $HP->parseData('&amp;'));
$this->assertIdentical('"', $HP->parseData('&quot;'));
$this->assertIdentical("'", $HP->parseData('&#039;'));
$this->assertIdentical("'", $HP->parseData('&#39;'));
$this->assertIdentical('&&&', $HP->parseData('&amp;&amp;&amp;'));
$this->assertIdentical('&&', $HP->parseData('&amp;&')); // [INVALID]
$this->assertIdentical('Procter & Gamble',
$HP->parseData('Procter & Gamble')); // [INVALID]
// This is not special, thus not converted. Test of fault tolerance,
// realistically speaking, this should never happen
$this->assertIdentical('&#x2D;', $HP->parseData('&#x2D;'));
function test_extractBody_noBodyTags() {
$this->assertExtractBody('<b>Bold</b>');
}
function test_extractBody() {
$this->assertExtractBody('<b>Bold</b>');
function test_extractBody_lowercaseBodyTags() {
$this->assertExtractBody('<html><body><b>Bold</b></body></html>', '<b>Bold</b>');
}
function test_extractBody_uppercaseBodyTags() {
$this->assertExtractBody('<HTML><BODY><B>Bold</B></BODY></HTML>', '<B>Bold</B>');
}
function test_extractBody_realisticUseCase() {
$this->assertExtractBody(
'<?xml version="1.0"
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
@ -96,303 +124,404 @@ class HTMLPurifier_LexerTest extends HTMLPurifier_Harness
</div>
</form>
');
$this->assertExtractBody('<html><body bgcolor="#F00"><b>Bold</b></body></html>', '<b>Bold</b>');
$this->assertExtractBody('<body>asdf'); // not closed, don't accept
}
function test_tokenizeHTML() {
$input = array();
$expect = array();
$sax_expect = array();
$config = array();
$input[0] = '';
$expect[0] = array();
$input[1] = 'This is regular text.';
$expect[1] = array(
new HTMLPurifier_Token_Text('This is regular text.')
);
$input[2] = 'This is <b>bold</b> text';
$expect[2] = array(
new HTMLPurifier_Token_Text('This is ')
,new HTMLPurifier_Token_Start('b', array())
,new HTMLPurifier_Token_Text('bold')
,new HTMLPurifier_Token_End('b')
,new HTMLPurifier_Token_Text(' text')
);
$input[3] = '<DIV>Totally rad dude. <b>asdf</b></div>';
$expect[3] = array(
new HTMLPurifier_Token_Start('DIV', array())
,new HTMLPurifier_Token_Text('Totally rad dude. ')
,new HTMLPurifier_Token_Start('b', array())
,new HTMLPurifier_Token_Text('asdf')
,new HTMLPurifier_Token_End('b')
,new HTMLPurifier_Token_End('div')
);
// [XML-INVALID]
$input[4] = '<asdf></asdf><d></d><poOloka><poolasdf><ds></asdf></ASDF>';
$expect[4] = array(
new HTMLPurifier_Token_Start('asdf')
,new HTMLPurifier_Token_End('asdf')
,new HTMLPurifier_Token_Start('d')
,new HTMLPurifier_Token_End('d')
,new HTMLPurifier_Token_Start('poOloka')
,new HTMLPurifier_Token_Start('poolasdf')
,new HTMLPurifier_Token_Start('ds')
,new HTMLPurifier_Token_End('asdf')
,new HTMLPurifier_Token_End('ASDF')
);
// DOM is different because it condenses empty tags into REAL empty ones
// as well as makes it well-formed
$dom_expect[4] = array(
new HTMLPurifier_Token_Empty('asdf')
,new HTMLPurifier_Token_Empty('d')
,new HTMLPurifier_Token_Start('pooloka')
,new HTMLPurifier_Token_Start('poolasdf')
,new HTMLPurifier_Token_Empty('ds')
,new HTMLPurifier_Token_End('poolasdf')
,new HTMLPurifier_Token_End('pooloka')
);
$input[5] = '<a'."\t".'href="foobar.php"'."\n".'title="foo!">Link to <b id="asdf">foobar</b></a>';
$expect[5] = array(
new HTMLPurifier_Token_Start('a',array('href'=>'foobar.php','title'=>'foo!'))
,new HTMLPurifier_Token_Text('Link to ')
,new HTMLPurifier_Token_Start('b',array('id'=>'asdf'))
,new HTMLPurifier_Token_Text('foobar')
,new HTMLPurifier_Token_End('b')
,new HTMLPurifier_Token_End('a')
);
$input[6] = '<br />';
$expect[6] = array(
new HTMLPurifier_Token_Empty('br')
);
// [SGML-INVALID] [RECOVERABLE]
$input[7] = '<!-- Comment --> <!-- not so well formed --->';
$expect[7] = array(
new HTMLPurifier_Token_Comment(' Comment ')
,new HTMLPurifier_Token_Text(' ')
,new HTMLPurifier_Token_Comment(' not so well formed -')
);
$sax_expect[7] = false; // we need to figure out proper comment output
// [SGML-INVALID]
$input[8] = '<a href=""';
$expect[8] = array(
new HTMLPurifier_Token_Text('<a href=""')
);
// SAX parses it into a tag
$sax_expect[8] = array(
new HTMLPurifier_Token_Start('a', array('href'=>''))
);
// DOM parses it into an empty tag
$dom_expect[8] = array(
new HTMLPurifier_Token_Empty('a', array('href'=>''))
);
$input[9] = '&lt;b&gt;';
$expect[9] = array(
new HTMLPurifier_Token_Text('<b>')
);
$sax_expect[9] = array(
new HTMLPurifier_Token_Text('<')
,new HTMLPurifier_Token_Text('b')
,new HTMLPurifier_Token_Text('>')
);
// note that SAX can clump text nodes together. We won't be
// too picky though
// [SGML-INVALID]
$input[10] = '<a "=>';
// We barf on this, aim for no attributes
$expect[10] = array(
new HTMLPurifier_Token_Start('a', array('"' => ''))
);
// DOM correctly has no attributes, but also closes the tag
$dom_expect[10] = array(
new HTMLPurifier_Token_Empty('a')
);
// SAX barfs on this
$sax_expect[10] = array(
new HTMLPurifier_Token_Start('a', array('"' => ''))
);
// [INVALID] [RECOVERABLE]
$input[11] = '"';
$expect[11] = array( new HTMLPurifier_Token_Text('"') );
// compare with this valid one:
$input[12] = '&quot;';
$expect[12] = array( new HTMLPurifier_Token_Text('"') );
$sax_expect[12] = false; // choked!
// CDATA sections!
$input[13] = '<![CDATA[You <b>can&#39;t</b> get me!]]>';
$expect[13] = array( new HTMLPurifier_Token_Text(
'You <b>can&#39;t</b> get me!' // raw
) );
$sax_expect[13] = array( // SAX has a seperate call for each entity
new HTMLPurifier_Token_Text('You '),
new HTMLPurifier_Token_Text('<'),
new HTMLPurifier_Token_Text('b'),
new HTMLPurifier_Token_Text('>'),
new HTMLPurifier_Token_Text('can'),
new HTMLPurifier_Token_Text('&'),
new HTMLPurifier_Token_Text('#39;t'),
new HTMLPurifier_Token_Text('<'),
new HTMLPurifier_Token_Text('/b'),
new HTMLPurifier_Token_Text('>'),
new HTMLPurifier_Token_Text(' get me!')
);
$char_theta = $this->_entity_lookup->table['theta'];
$char_rarr = $this->_entity_lookup->table['rarr'];
// test entity replacement
$input[14] = '&theta;';
$expect[14] = array( new HTMLPurifier_Token_Text($char_theta) );
// test that entities aren't replaced in CDATA sections
$input[15] = '&theta; <![CDATA[&rarr;]]>';
$expect[15] = array( new HTMLPurifier_Token_Text($char_theta . ' &rarr;') );
$sax_expect[15] = array(
new HTMLPurifier_Token_Text($char_theta . ' '),
new HTMLPurifier_Token_Text('&'),
new HTMLPurifier_Token_Text('rarr;')
);
// test entity resolution in attributes
$input[16] = '<a href="index.php?title=foo&amp;id=bar">Link</a>';
$expect[16] = array(
new HTMLPurifier_Token_Start('a',array('href' => 'index.php?title=foo&id=bar'))
,new HTMLPurifier_Token_Text('Link')
,new HTMLPurifier_Token_End('a')
);
// test that UTF-8 is preserved
$char_hearts = $this->_entity_lookup->table['hearts'];
$input[17] = $char_hearts;
$expect[17] = array( new HTMLPurifier_Token_Text($char_hearts) );
// test weird characters in attributes
$input[18] = '<br test="x &lt; 6" />';
$expect[18] = array( new HTMLPurifier_Token_Empty('br', array('test' => 'x < 6')) );
// test emoticon protection
$input[19] = '<b>Whoa! <3 That\'s not good >.></b>';
$expect[19] = array(
new HTMLPurifier_Token_Start('b'),
new HTMLPurifier_Token_Text('Whoa! '),
new HTMLPurifier_Token_Text('<3 That\'s not good >'),
new HTMLPurifier_Token_Text('.>'),
new HTMLPurifier_Token_End('b'),
);
$dom_expect[19] = array(
new HTMLPurifier_Token_Start('b'),
new HTMLPurifier_Token_Text('Whoa! <3 That\'s not good >.>'),
new HTMLPurifier_Token_End('b'),
);
$sax_expect[19] = false; // SAX drops the < character
$config[19] = HTMLPurifier_Config::create(array('Core.AggressivelyFixLt' => true));
// test comment parsing with funky characters inside
$input[20] = '<!-- This >< comment --><br />';
$expect[20] = array(
new HTMLPurifier_Token_Comment(' This >< comment '),
new HTMLPurifier_Token_Empty('br')
);
$sax_expect[20] = false;
$config[20] = HTMLPurifier_Config::create(array('Core.AggressivelyFixLt' => true));
// test comment parsing of missing end
$input[21] = '<!-- This >< comment';
$expect[21] = array(
new HTMLPurifier_Token_Comment(' This >< comment')
);
$sax_expect[21] = false;
$dom_expect[21] = false;
$config[21] = HTMLPurifier_Config::create(array('Core.AggressivelyFixLt' => true));
// test CDATA tags
$input[22] = '<script>alert("<foo>");</script>';
$expect[22] = array(
new HTMLPurifier_Token_Start('script')
,new HTMLPurifier_Token_Text('alert("<foo>");')
,new HTMLPurifier_Token_End('script')
);
$config[22] = HTMLPurifier_Config::create(array('HTML.Trusted' => true));
$sax_expect[22] = false;
// test escaping
$input[23] = '<!-- This comment < &lt; & -->';
$expect[23] = array(
new HTMLPurifier_Token_Comment(' This comment < &lt; & ') );
$sax_expect[23] = false; $config[23] =
HTMLPurifier_Config::create(array('Core.AggressivelyFixLt' =>
true));
// more DirectLex edge-cases
$input[24] = '<a href="><>">';
$expect[24] = array(
new HTMLPurifier_Token_Start('a', array('href' => '')),
new HTMLPurifier_Token_Text('<">')
);
$sax_expect[24] = false;
$dom_expect[24] = array(
new HTMLPurifier_Token_Empty('a', array('href' => '><>'))
);
$default_config = HTMLPurifier_Config::createDefault();
$default_context = new HTMLPurifier_Context();
foreach($input as $i => $discard) {
if (!isset($config[$i])) $config[$i] = $default_config;
$result = $this->DirectLex->tokenizeHTML($input[$i], $config[$i], $default_context);
$this->assertIdentical($expect[$i], $result, 'DirectLexTest '.$i.': %s');
paintIf($result, $expect[$i] != $result);
if ($this->_has_pear) {
// assert unless I say otherwise
$sax_result = $this->PEARSax3->tokenizeHTML($input[$i], $config[$i], $default_context);
if (!isset($sax_expect[$i])) {
// by default, assert with normal result
$this->assertIdentical($expect[$i], $sax_result, 'PEARSax3Test '.$i.': %s');
paintIf($sax_result, $expect[$i] != $sax_result);
} elseif ($sax_expect[$i] === false) {
// assertions were turned off, optionally dump
// paintIf($sax_expect, $i == NUMBER);
} else {
// match with a custom SAX result array
$this->assertIdentical($sax_expect[$i], $sax_result, 'PEARSax3Test (custom) '.$i.': %s');
paintIf($sax_result, $sax_expect[$i] != $sax_result);
}
}
if ($this->_has_dom) {
$dom_result = $this->DOMLex->tokenizeHTML($input[$i], $config[$i], $default_context);
// same structure as SAX
if (!isset($dom_expect[$i])) {
$this->assertIdentical($expect[$i], $dom_result, 'DOMLexTest '.$i.': %s');
paintIf($dom_result, $expect[$i] != $dom_result);
} elseif ($dom_expect[$i] === false) {
// paintIf($dom_result, $i == NUMBER);
} else {
$this->assertIdentical($dom_expect[$i], $dom_result, 'DOMLexTest (custom) '.$i.': %s');
paintIf($dom_result, $dom_expect[$i] != $dom_result);
}
}
}
function test_extractBody_bodyWithAttributes() {
$this->assertExtractBody('<html><body bgcolor="#F00"><b>Bold</b></body></html>', '<b>Bold</b>');
}
function test_extractBody_preserveUnclosedBody() {
$this->assertExtractBody('<body>asdf'); // not closed, don't accept
}
// HTMLPurifier_Lexer->tokenizeHTML() --------------------------------------
function assertTokenization($input, $expect, $alt_expect = array()) {
$lexers = array();
$lexers['DirectLex'] = new HTMLPurifier_Lexer_DirectLex();
if ($this->_has_pear) $lexers['PEARSax3'] = new HTMLPurifier_Lexer_PEARSax3();
if (version_compare(PHP_VERSION, "5", ">=") && class_exists('DOMDocument')) {
$lexers['DOMLex'] = new HTMLPurifier_Lexer_DOMLex();
$lexers['PH5P'] = new HTMLPurifier_Lexer_PH5P();
}
foreach ($lexers as $name => $lexer) {
$result = $lexer->tokenizeHTML($input, $this->config, $this->context);
if (isset($alt_expect[$name])) {
if ($alt_expect[$name] === false) continue;
$t_expect = $alt_expect[$name];
$this->assertIdentical($result, $alt_expect[$name], "$name: %s");
} else {
$t_expect = $expect;
$this->assertIdentical($result, $expect, "$name: %s");
}
if ($t_expect != $result) {
printTokens($result);
//var_dump($result);
}
}
}
function test_tokenizeHTML_emptyInput() {
$this->assertTokenization('', array());
}
function test_tokenizeHTML_plainText() {
$this->assertTokenization(
'This is regular text.',
array(
new HTMLPurifier_Token_Text('This is regular text.')
)
);
}
function test_tokenizeHTML_textAndTags() {
$this->assertTokenization(
'This is <b>bold</b> text',
array(
new HTMLPurifier_Token_Text('This is '),
new HTMLPurifier_Token_Start('b', array()),
new HTMLPurifier_Token_Text('bold'),
new HTMLPurifier_Token_End('b'),
new HTMLPurifier_Token_Text(' text'),
)
);
}
function test_tokenizeHTML_normalizeCase() {
$this->assertTokenization(
'<DIV>Totally rad dude. <b>asdf</b></div>',
array(
new HTMLPurifier_Token_Start('DIV', array()),
new HTMLPurifier_Token_Text('Totally rad dude. '),
new HTMLPurifier_Token_Start('b', array()),
new HTMLPurifier_Token_Text('asdf'),
new HTMLPurifier_Token_End('b'),
new HTMLPurifier_Token_End('div'),
)
);
}
function test_tokenizeHTML_notWellFormed() {
$this->assertTokenization(
'<asdf></asdf><d></d><poOloka><poolasdf><ds></asdf></ASDF>',
array(
new HTMLPurifier_Token_Start('asdf'),
new HTMLPurifier_Token_End('asdf'),
new HTMLPurifier_Token_Start('d'),
new HTMLPurifier_Token_End('d'),
new HTMLPurifier_Token_Start('poOloka'),
new HTMLPurifier_Token_Start('poolasdf'),
new HTMLPurifier_Token_Start('ds'),
new HTMLPurifier_Token_End('asdf'),
new HTMLPurifier_Token_End('ASDF'),
),
array(
'DOMLex' => $alt = array(
new HTMLPurifier_Token_Empty('asdf'),
new HTMLPurifier_Token_Empty('d'),
new HTMLPurifier_Token_Start('pooloka'),
new HTMLPurifier_Token_Start('poolasdf'),
new HTMLPurifier_Token_Empty('ds'),
new HTMLPurifier_Token_End('poolasdf'),
new HTMLPurifier_Token_End('pooloka'),
),
'PH5P' => $alt,
)
);
}
function test_tokenizeHTML_whitespaceInTag() {
$this->assertTokenization(
'<a'."\t".'href="foobar.php"'."\n".'title="foo!">Link to <b id="asdf">foobar</b></a>',
array(
new HTMLPurifier_Token_Start('a',array('href'=>'foobar.php','title'=>'foo!')),
new HTMLPurifier_Token_Text('Link to '),
new HTMLPurifier_Token_Start('b',array('id'=>'asdf')),
new HTMLPurifier_Token_Text('foobar'),
new HTMLPurifier_Token_End('b'),
new HTMLPurifier_Token_End('a'),
)
);
}
function test_tokenizeHTML_emptyTag() {
$this->assertTokenization(
'<br />',
array( new HTMLPurifier_Token_Empty('br') )
);
}
function test_tokenizeHTML_comment() {
$this->assertTokenization(
'<!-- Comment -->',
array( new HTMLPurifier_Token_Comment(' Comment ') ),
array(
'PEARSax3' => array( new HTMLPurifier_Token_Comment('-- Comment --') ),
)
);
}
function test_tokenizeHTML_malformedComment() {
$this->assertTokenization(
'<!-- not so well formed --->',
array( new HTMLPurifier_Token_Comment(' not so well formed -') ),
array(
'PEARSax3' => array( new HTMLPurifier_Token_Comment('-- not so well formed ---') ),
)
);
}
function test_tokenizeHTML_unterminatedTag() {
$this->assertTokenization(
'<a href=""',
array( new HTMLPurifier_Token_Text('<a href=""') ),
array(
// I like our behavior better, but it's non-standard
'DOMLex' => array( new HTMLPurifier_Token_Empty('a', array('href'=>'')) ),
'PEARSax3' => array( new HTMLPurifier_Token_Start('a', array('href'=>'')) ),
'PH5P' => false, // total barfing, grabs scaffolding too
)
);
}
function test_tokenizeHTML_specialEntities() {
$this->assertTokenization(
'&lt;b&gt;',
array(
new HTMLPurifier_Token_Text('<b>')
),
array(
// some parsers will separate entities out
'PEARSax3' => $split = array(
new HTMLPurifier_Token_Text('<'),
new HTMLPurifier_Token_Text('b'),
new HTMLPurifier_Token_Text('>'),
),
'PH5P' => $split,
)
);
}
function test_tokenizeHTML_earlyQuote() {
$this->assertTokenization(
'<a "=>',
array( new HTMLPurifier_Token_Empty('a') ),
array(
// we barf on this input
'DirectLex' => $tokens = array(
new HTMLPurifier_Token_Start('a', array('"' => ''))
),
'PEARSax3' => $tokens,
'PH5P' => array(
new HTMLPurifier_Token_Empty('a', array('"' => ''))
),
)
);
}
function test_tokenizeHTML_unescapedQuote() {
$this->assertTokenization(
'"',
array( new HTMLPurifier_Token_Text('"') )
);
}
function test_tokenizeHTML_escapedQuote() {
$this->assertTokenization(
'&quot;',
array( new HTMLPurifier_Token_Text('"') ),
array(
'PEARSax3' => false, // PEAR barfs on this
)
);
}
function test_tokenizeHTML_cdata() {
$this->assertTokenization(
'<![CDATA[You <b>can&#39;t</b> get me!]]>',
array( new HTMLPurifier_Token_Text('You <b>can&#39;t</b> get me!') ),
array(
// PEAR splits up all of the CDATA
'PEARSax3' => $split = array(
new HTMLPurifier_Token_Text('You '),
new HTMLPurifier_Token_Text('<'),
new HTMLPurifier_Token_Text('b'),
new HTMLPurifier_Token_Text('>'),
new HTMLPurifier_Token_Text('can'),
new HTMLPurifier_Token_Text('&'),
new HTMLPurifier_Token_Text('#39;t'),
new HTMLPurifier_Token_Text('<'),
new HTMLPurifier_Token_Text('/b'),
new HTMLPurifier_Token_Text('>'),
new HTMLPurifier_Token_Text(' get me!'),
),
'PH5P' => $split,
)
);
}
function test_tokenizeHTML_characterEntity() {
$this->assertTokenization(
'&theta;',
array( new HTMLPurifier_Token_Text("\xCE\xB8") )
);
}
function test_tokenizeHTML_characterEntityInCDATA() {
$this->assertTokenization(
'<![CDATA[&rarr;]]>',
array( new HTMLPurifier_Token_Text("&rarr;") ),
array(
'PEARSax3' => $split = array(
new HTMLPurifier_Token_Text('&'),
new HTMLPurifier_Token_Text('rarr;'),
),
'PH5P' => $split,
)
);
}
function test_tokenizeHTML_entityInAttribute() {
$this->assertTokenization(
'<a href="index.php?title=foo&amp;id=bar">Link</a>',
array(
new HTMLPurifier_Token_Start('a',array('href' => 'index.php?title=foo&id=bar')),
new HTMLPurifier_Token_Text('Link'),
new HTMLPurifier_Token_End('a'),
)
);
}
function test_tokenizeHTML_preserveUTF8() {
$this->assertTokenization(
"\xCE\xB8",
array( new HTMLPurifier_Token_Text("\xCE\xB8") )
);
}
function test_tokenizeHTML_specialEntityInAttribute() {
$this->assertTokenization(
'<br test="x &lt; 6" />',
array( new HTMLPurifier_Token_Empty('br', array('test' => 'x < 6')) )
);
}
function test_tokenizeHTML_emoticonProtection() {
$this->config->set('Core', 'AggressivelyFixLt', true);
$this->assertTokenization(
'<b>Whoa! <3 That\'s not good >.></b>',
array(
new HTMLPurifier_Token_Start('b'),
new HTMLPurifier_Token_Text('Whoa! '),
new HTMLPurifier_Token_Text('<3 That\'s not good >'),
new HTMLPurifier_Token_Text('.>'),
new HTMLPurifier_Token_End('b')
),
array(
// text is absorbed together
'DOMLex' => array(
new HTMLPurifier_Token_Start('b'),
new HTMLPurifier_Token_Text('Whoa! <3 That\'s not good >.>'),
new HTMLPurifier_Token_End('b'),
),
'PEARSax3' => false, // totally mangled
'PH5P' => array( // interesting grouping
new HTMLPurifier_Token_Start('b'),
new HTMLPurifier_Token_Text('Whoa! '),
new HTMLPurifier_Token_Text('<'),
new HTMLPurifier_Token_Text('3 That\'s not good >.>'),
new HTMLPurifier_Token_End('b'),
),
)
);
}
function test_tokenizeHTML_commentWithFunkyChars() {
$this->assertTokenization(
'<!-- This >< comment --><br />',
array(
new HTMLPurifier_Token_Comment(' This >< comment '),
new HTMLPurifier_Token_Empty('br'),
),
array(
'PEARSax3' => false,
)
);
}
function test_tokenizeHTML_unterminatedComment() {
$this->assertTokenization(
'<!-- This >< comment',
array( new HTMLPurifier_Token_Comment(' This >< comment') ),
array(
'DOMLex' => false,
'PEARSax3' => false,
'PH5P' => false,
)
);
}
function test_tokenizeHTML_scriptCDATAContents() {
$this->config->set('HTML', 'Trusted', true);
$this->assertTokenization(
'Foo: <script>alert("<foo>");</script>',
array(
new HTMLPurifier_Token_Text('Foo: '),
new HTMLPurifier_Token_Start('script'),
new HTMLPurifier_Token_Text('alert("<foo>");'),
new HTMLPurifier_Token_End('script'),
),
array(
'PEARSax3' => false,
// PH5P, for some reason, bubbles the script to <head>
'PH5P' => false,
)
);
}
function test_tokenizeHTML_entitiesInComment() {
$this->config->set('Core', 'AggressivelyFixLt', true);
$this->assertTokenization(
'<!-- This comment < &lt; & -->',
array( new HTMLPurifier_Token_Comment(' This comment < &lt; & ') ),
array(
'PEARSax3' => false
)
);
}
function test_tokenizeHTML_attributeWithSpecialCharacters() {
$this->assertTokenization(
'<a href="><>">',
array( new HTMLPurifier_Token_Empty('a', array('href' => '><>')) ),
array(
'DirectLex' => array(
new HTMLPurifier_Token_Start('a', array('href' => '')),
new HTMLPurifier_Token_Text('<">'),
),
'PEARSax3' => false,
)
);
}
function test_tokenizeHTML_emptyTagWithSlashInAttribute() {
$this->assertTokenization(
'<param name="src" value="http://example.com/video.wmv" />',
array( new HTMLPurifier_Token_Empty('param', array('name' => 'src', 'value' => 'http://example.com/video.wmv')) )
);
}
/*
function test_tokenizeHTML_() {
$this->assertTokenization(
,
array(
)
);
}
*/
}

View File

@ -16,6 +16,7 @@ class HTMLPurifier_SimpleTest_Reporter extends HTMLReporter
?>><?php echo $file ?></option>
<?php } ?>
</select>
<input type="checkbox" name="standalone" title="Standalone version?" <?php if(isset($_GET['standalone'])) {echo 'checked="checked" ';} ?>/>
<input type="submit" value="Go">
</form>
<?php

View File

@ -11,26 +11,36 @@ class HTMLPurifier_Strategy_CoreTest extends HTMLPurifier_StrategyHarness
$this->obj = new HTMLPurifier_Strategy_Core();
}
function test() {
function testBlankInput() {
$this->assertResult('');
}
function testMakeWellFormed() {
$this->assertResult(
'<b>Make well formed.',
'<b>Make well formed.</b>'
);
}
function testFixNesting() {
$this->assertResult(
'<b><div>Fix nesting.</div></b>',
'<b></b><div>Fix nesting.</div>'
);
}
function testRemoveForeignElements() {
$this->assertResult(
'<asdf>Foreign element removal.</asdf>',
'Foreign element removal.'
);
}
function testFirstThree() {
$this->assertResult(
'<foo><b><div>All three.</div></b>',
'<b></b><div>All three.</div>'
);
}
}

View File

@ -11,79 +11,81 @@ class HTMLPurifier_Strategy_FixNestingTest extends HTMLPurifier_StrategyHarness
$this->obj = new HTMLPurifier_Strategy_FixNesting();
}
function testBlockAndInlineIntegration() {
// legal inline
function testPreserveInlineInRoot() {
$this->assertResult('<b>Bold text</b>');
// legal inline and block (default parent element is FLOW)
}
function testPreserveInlineAndBlockInRoot() {
$this->assertResult('<a href="about:blank">Blank</a><div>Block</div>');
// illegal block in inline
}
function testRemoveBlockInInline() {
$this->assertResult(
'<b><div>Illegal div.</div></b>',
'<b>Illegal div.</b>'
);
// same test with different configuration (fragile)
$this->assertResult(
'<b><div>Illegal div.</div></b>',
'<b>&lt;div&gt;Illegal div.&lt;/div&gt;</b>',
array('Core.EscapeInvalidChildren' => true)
);
}
function testNodeRemovalIntegration() {
// test of empty set that's required, resulting in removal of node
function testEscapeBlockInInline() {
$this->config->set('Core', 'EscapeInvalidChildren', true);
$this->assertResult(
'<b><div>Illegal div.</div></b>',
'<b>&lt;div&gt;Illegal div.&lt;/div&gt;</b>'
);
}
function testRemoveNodeWithMissingRequiredElements() {
$this->assertResult('<ul></ul>', '');
// test illegal text which gets removed
}
function testRemoveIllegalPCDATA() {
$this->assertResult(
'<ul>Illegal text<li>Legal item</li></ul>',
'<ul><li>Legal item</li></ul>'
);
}
function testTableIntegration() {
// test custom table definition
$this->assertResult(
'<table><tr><td>Cell 1</td></tr></table>'
);
function testCustomTableDefinition() {
$this->assertResult('<table><tr><td>Cell 1</td></tr></table>');
}
function testRemoveEmptyTable() {
$this->assertResult('<table></table>', '');
}
function testChameleonIntegration() {
// block in inline ins not allowed
function testChameleonRemoveBlockInNodeInInline() {
$this->assertResult(
'<span><ins><div>Not allowed!</div></ins></span>',
'<span><ins>Not allowed!</ins></span>'
);
// test block element that has inline content
}
function testChameleonRemoveBlockInBlockNodeWithInlineContent() {
$this->assertResult(
'<h1><ins><div>Not allowed!</div></ins></h1>',
'<h1><ins>Not allowed!</ins></h1>'
);
// stacked ins/del
}
function testNestedChameleonRemoveBlockInNodeWithInlineContent() {
$this->assertResult(
'<h1><ins><del><div>Not allowed!</div></del></ins></h1>',
'<h1><ins><del>Not allowed!</del></ins></h1>'
);
}
function testNestedChameleonPreserveBlockInBlock() {
$this->assertResult(
'<div><ins><del><div>Allowed!</div></del></ins></div>'
);
}
function testChameleonEscapeInvalidBlockInInline() {
$this->config->set('Core', 'EscapeInvalidChildren', true);
$this->assertResult( // alt config
'<span><ins><div>Not allowed!</div></ins></span>',
'<span><ins>&lt;div&gt;Not allowed!&lt;/div&gt;</ins></span>',
array('Core.EscapeInvalidChildren' => true)
'<span><ins>&lt;div&gt;Not allowed!&lt;/div&gt;</ins></span>'
);
}
function testExclusionsIntegration() {
@ -93,41 +95,37 @@ class HTMLPurifier_Strategy_FixNestingTest extends HTMLPurifier_StrategyHarness
'<a><span></span></a>'
);
}
function testCustomParentIntegration() {
// test inline parent
$this->assertResult(
'<b>Bold</b>', true, array('HTML.Parent' => 'span')
);
$this->assertResult(
'<div>Reject</div>', 'Reject', array('HTML.Parent' => 'span')
);
}
function testError() {
// test fallback to div
$this->expectError('Cannot use unrecognized element as parent.');
$this->assertResult(
'<div>Accept</div>', true, array('HTML.Parent' => 'obviously-impossible')
);
$this->swallowErrors();
function testPreserveInlineNodeInInlineRootNode() {
$this->config->set('HTML', 'Parent', 'span');
$this->assertResult('<b>Bold</b>');
}
function testDoubleCheckIntegration() {
// breaks without the redundant checking code
function testRemoveBlockNodeInInlineRootNode() {
$this->config->set('HTML', 'Parent', 'span');
$this->assertResult('<div>Reject</div>', 'Reject');
}
function testInvalidParentError() {
// test fallback to div
$this->config->set('HTML', 'Parent', 'obviously-impossible');
$this->expectError('Cannot use unrecognized element as parent');
$this->assertResult('<div>Accept</div>');
}
function testCascadingRemovalOfNodesMissingRequiredChildren() {
$this->assertResult('<table><tr></tr></table>', '');
// special case, prevents scrolling one back to find parent
}
function testCascadingRemovalSpecialCaseCannotScrollOneBack() {
$this->assertResult('<table><tr></tr><tr></tr></table>', '');
// cascading rollbacks
$this->assertResult(
'<table><tbody><tr></tr><tr></tr></tbody><tr></tr><tr></tr></table>',
''
);
// rollbacks twice
}
function testLotsOfCascadingRemovalOfNodes() {
$this->assertResult('<table><tbody><tr></tr><tr></tr></tbody><tr></tr><tr></tr></table>', '');
}
function testAdjacentRemovalOfNodeMissingRequiredChildren() {
$this->assertResult('<table></table><table></table>', '');
}

View File

@ -9,113 +9,77 @@ class HTMLPurifier_Strategy_MakeWellFormedTest extends HTMLPurifier_StrategyHarn
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_Strategy_MakeWellFormed();
$this->config = array();
}
function testNormalIntegration() {
function testEmptyInput() {
$this->assertResult('');
}
function testWellFormedInput() {
$this->assertResult('This is <b>bold text</b>.');
}
function testUnclosedTagIntegration() {
function testUnclosedTagTerminatedByDocumentEnd() {
$this->assertResult(
'<b>Unclosed tag, gasp!',
'<b>Unclosed tag, gasp!</b>'
);
}
function testUnclosedTagTerminatedByParentNodeEnd() {
$this->assertResult(
'<b><i>Bold and italic?</b>',
'<b><i>Bold and italic?</i></b>'
);
}
function testRemoveStrayClosingTag() {
$this->assertResult(
'Unused end tags... recycle!</b>',
'Unused end tags... recycle!'
);
}
function testEmptyTagDetectionIntegration() {
function testConvertStartToEmpty() {
$this->assertResult(
'<br style="clear:both;">',
'<br style="clear:both;" />'
);
}
function testConvertEmptyToStart() {
$this->assertResult(
'<div style="clear:both;" />',
'<div style="clear:both;"></div>'
);
}
function testAutoClose() {
// paragraph
function testAutoCloseParagraph() {
$this->assertResult(
'<p>Paragraph 1<p>Paragraph 2',
'<p>Paragraph 1</p><p>Paragraph 2</p>'
);
}
function testAutoCloseParagraphInsideDiv() {
$this->assertResult(
'<div><p>Paragraphs<p>In<p>A<p>Div</div>',
'<div><p>Paragraphs</p><p>In</p><p>A</p><p>Div</p></div>'
);
// list
}
function testAutoCloseListItem() {
$this->assertResult(
'<ol><li>Item 1<li>Item 2</ol>',
'<ol><li>Item 1</li><li>Item 2</li></ol>'
);
// colgroup
}
function testAutoCloseColgroup() {
$this->assertResult(
'<table><colgroup><col /><tr></tr></table>',
'<table><colgroup><col /></colgroup><tr></tr></table>'
);
}
function testMultipleInjectors() {
$this->config = array('AutoFormat.AutoParagraph' => true, 'AutoFormat.Linkify' => true);
$this->assertResult(
'Foobar',
'<p>Foobar</p>'
);
$this->assertResult(
'http://example.com',
'<p><a href="http://example.com">http://example.com</a></p>'
);
$this->assertResult(
'<b>http://example.com</b>',
'<p><b><a href="http://example.com">http://example.com</a></b></p>'
);
$this->assertResult(
'<b>http://example.com',
'<p><b><a href="http://example.com">http://example.com</a></b></p>'
);
$this->assertResult(
'http://example.com
http://dev.example.com',
'<p><a href="http://example.com">http://example.com</a></p><p><a href="http://dev.example.com">http://dev.example.com</a></p>'
);
$this->assertResult(
'http://example.com <div>http://example.com</div>',
'<p><a href="http://example.com">http://example.com</a> </p><div><a href="http://example.com">http://example.com</a></div>'
);
$this->assertResult(
'This URL http://example.com is what you need',
'<p>This URL <a href="http://example.com">http://example.com</a> is what you need</p>'
);
}
}

View File

@ -0,0 +1,65 @@
<?php
require_once 'HTMLPurifier/StrategyHarness.php';
require_once 'HTMLPurifier/Strategy/MakeWellFormed.php';
class HTMLPurifier_Strategy_MakeWellFormed_InjectorTest extends HTMLPurifier_StrategyHarness
{
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_Strategy_MakeWellFormed();
$this->config->set('AutoFormat', 'AutoParagraph', true);
$this->config->set('AutoFormat', 'Linkify', true);
}
function testOnlyAutoParagraph() {
$this->assertResult(
'Foobar',
'<p>Foobar</p>'
);
}
function testParagraphWrappingOnlyLink() {
$this->assertResult(
'http://example.com',
'<p><a href="http://example.com">http://example.com</a></p>'
);
}
function testParagraphWrappingNodeContainingLink() {
$this->assertResult(
'<b>http://example.com</b>',
'<p><b><a href="http://example.com">http://example.com</a></b></p>'
);
}
function testParagraphWrappingPoorlyFormedNodeContainingLink() {
$this->assertResult(
'<b>http://example.com',
'<p><b><a href="http://example.com">http://example.com</a></b></p>'
);
}
function testTwoParagraphsContainingOnlyOneLink() {
$this->assertResult(
"http://example.com\n\nhttp://dev.example.com",
'<p><a href="http://example.com">http://example.com</a></p><p><a href="http://dev.example.com">http://dev.example.com</a></p>'
);
}
function testParagraphNextToDivWithLinks() {
$this->assertResult(
'http://example.com <div>http://example.com</div>',
'<p><a href="http://example.com">http://example.com</a> </p><div><a href="http://example.com">http://example.com</a></div>'
);
}
function testRealisticLinkInSentence() {
$this->assertResult(
'This URL http://example.com is what you need',
'<p>This URL <a href="http://example.com">http://example.com</a> is what you need</p>'
);
}
}

View File

@ -3,8 +3,7 @@
require_once 'HTMLPurifier/StrategyHarness.php';
require_once 'HTMLPurifier/Strategy/RemoveForeignElements.php';
class HTMLPurifier_Strategy_RemoveForeignElementsTest
extends HTMLPurifier_StrategyHarness
class HTMLPurifier_Strategy_RemoveForeignElementsTest extends HTMLPurifier_StrategyHarness
{
function setUp() {
@ -12,96 +11,75 @@ class HTMLPurifier_Strategy_RemoveForeignElementsTest
$this->obj = new HTMLPurifier_Strategy_RemoveForeignElements();
}
function test() {
$this->config = array('HTML.Doctype' => 'XHTML 1.0 Strict');
function testBlankInput() {
$this->assertResult('');
}
function testPreserveRecognizedElements() {
$this->assertResult('This is <b>bold text</b>.');
}
function testRemoveForeignElements() {
$this->assertResult(
'<asdf>Bling</asdf><d href="bang">Bong</d><foobar />',
'BlingBong'
);
}
function testRemoveScriptAndContents() {
$this->assertResult(
'<script>alert();</script>',
''
);
}
function testRemoveStyleAndContents() {
$this->assertResult(
'<style>.foo {blink;}</style>',
''
);
}
function testRemoveOnlyScriptTagsLegacy() {
$this->config->set('Core', 'RemoveScriptContents', false);
$this->assertResult(
'<script>alert();</script>',
'alert();',
array('Core.RemoveScriptContents' => false)
'alert();'
);
}
function testRemoveOnlyScriptTags() {
$this->config->set('Core', 'HiddenElements', array());
$this->assertResult(
'<script>alert();</script>',
'alert();',
array('Core.HiddenElements' => array())
'alert();'
);
$this->assertResult(
'<menu><li>Item 1</li></menu>',
'<ul><li>Item 1</li></ul>'
);
// test center transform
$this->assertResult(
'<center>Look I am Centered!</center>',
'<div style="text-align:center;">Look I am Centered!</div>'
);
// test font transform
$this->assertResult(
'<font color="red" face="Arial" size="6">Big Warning!</font>',
'<span style="color:red;font-family:Arial;font-size:xx-large;">Big'.
' Warning!</span>'
);
// test removal of invalid img tag
$this->assertResult(
'<img />',
''
);
// test preservation of valid img tag
}
function testRemoveInvalidImg() {
$this->assertResult('<img />', '');
}
function testPreserveValidImg() {
$this->assertResult('<img src="foobar.gif" alt="foobar.gif" />');
// test preservation of invalid img tag when removal is disabled
$this->assertResult(
'<img />',
true,
array(
'Core.RemoveInvalidImg' => false
)
);
// test transform to unallowed element
$this->assertResult(
'<font color="red" face="Arial" size="6">Big Warning!</font>',
'Big Warning!',
array('HTML.Allowed' => 'div')
);
// text-ify commented script contents ( the trailing comment gets
// removed during generation )
}
function testPreserveInvalidImgWhenRemovalIsDisabled() {
$this->config->set('Core', 'RemoveInvalidImg', false);
$this->assertResult('<img />');
}
function testTextifyCommentedScriptContents() {
$this->config->set('HTML', 'Trusted', true);
$this->config->set('Output', 'CommentScriptContents', false); // simplify output
$this->assertResult(
'<script type="text/javascript"><!--
alert(<b>bold</b>);
// --></script>',
'<script type="text/javascript">
alert(&lt;b&gt;bold&lt;/b&gt;);
// </script>',
array('HTML.Trusted' => true, 'Output.CommentScriptContents' => false)
// </script>'
);
}
}

View File

@ -0,0 +1,46 @@
<?php
require_once 'HTMLPurifier/StrategyHarness.php';
require_once 'HTMLPurifier/Strategy/RemoveForeignElements.php';
class HTMLPurifier_Strategy_RemoveForeignElements_TidyTest
extends HTMLPurifier_StrategyHarness
{
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_Strategy_RemoveForeignElements();
$this->config->set('HTML', 'TidyLevel', 'heavy');
}
function testCenterTransform() {
$this->assertResult(
'<center>Look I am Centered!</center>',
'<div style="text-align:center;">Look I am Centered!</div>'
);
}
function testFontTransform() {
$this->assertResult(
'<font color="red" face="Arial" size="6">Big Warning!</font>',
'<span style="color:red;font-family:Arial;font-size:xx-large;">Big'.
' Warning!</span>'
);
}
function testTransformToForbiddenElement() {
$this->config->set('HTML', 'Allowed', 'div');
$this->assertResult(
'<font color="red" face="Arial" size="6">Big Warning!</font>',
'Big Warning!'
);
}
function testMenuTransform() {
$this->assertResult(
'<menu><li>Item 1</li></menu>',
'<ul><li>Item 1</li></ul>'
);
}
}

View File

@ -1,6 +1,5 @@
<?php
require_once('HTMLPurifier/Config.php');
require_once('HTMLPurifier/StrategyHarness.php');
require_once('HTMLPurifier/Strategy/ValidateAttributes.php');
@ -11,126 +10,99 @@ class HTMLPurifier_Strategy_ValidateAttributesTest extends
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_Strategy_ValidateAttributes();
$this->config = array('HTML.Doctype' => 'XHTML 1.0 Strict');
}
function testEmpty() {
function testEmptyInput() {
$this->assertResult('');
}
function testIDs() {
function testRemoveIDByDefault() {
$this->assertResult(
'<div id="valid">Kill the ID.</div>',
'<div>Kill the ID.</div>'
);
$this->assertResult('<div id="valid">Preserve the ID.</div>', true,
array('HTML.EnableAttrID' => true));
$this->assertResult(
'<div id="0invalid">Kill the ID.</div>',
'<div>Kill the ID.</div>',
array('HTML.EnableAttrID' => true)
);
// test id accumulator
$this->assertResult(
'<div id="valid">Valid</div><div id="valid">Invalid</div>',
'<div id="valid">Valid</div><div>Invalid</div>',
array('HTML.EnableAttrID' => true)
);
}
function testRemoveInvalidDir() {
$this->assertResult(
'<span dir="up-to-down">Bad dir.</span>',
'<span>Bad dir.</span>'
);
// test attribute key case sensitivity
$this->assertResult(
'<div ID="valid">Convert ID to lowercase.</div>',
'<div id="valid">Convert ID to lowercase.</div>',
array('HTML.EnableAttrID' => true)
);
// test simple attribute substitution
$this->assertResult(
'<div id=" valid ">Trim whitespace.</div>',
'<div id="valid">Trim whitespace.</div>',
array('HTML.EnableAttrID' => true)
);
// test configuration id blacklist
$this->assertResult(
'<div id="invalid">Invalid</div>',
'<div>Invalid</div>',
array(
'Attr.IDBlacklist' => array('invalid'),
'HTML.EnableAttrID' => true
)
);
// name rewritten as id
$this->assertResult(
'<a name="foobar" />',
'<a id="foobar" />',
array('HTML.EnableAttrID' => true)
);
}
function testClasses() {
function testPreserveValidClass() {
$this->assertResult('<div class="valid">Valid</div>');
}
function testSelectivelyRemoveInvalidClasses() {
$this->assertResult(
'<div class="valid 0invalid">Keep valid.</div>',
'<div class="valid">Keep valid.</div>'
);
}
function testTitle() {
function testPreserveTitle() {
$this->assertResult(
'<acronym title="PHP: Hypertext Preprocessor">PHP</acronym>'
);
}
function testLang() {
function testAddXMLLang() {
$this->assertResult(
'<span lang="fr">La soupe.</span>',
'<span lang="fr" xml:lang="fr">La soupe.</span>'
);
// test only xml:lang for XHTML 1.1
}
function testOnlyXMLLangInXHTML11() {
$this->config->set('HTML', 'Doctype', 'XHTML 1.1');
$this->assertResult(
'<b lang="en">asdf</b>',
'<b xml:lang="en">asdf</b>', array('HTML.Doctype' => 'XHTML 1.1')
'<b xml:lang="en">asdf</b>'
);
}
function testAlign() {
$this->assertResult(
'<h1 align="center">Centered Headline</h1>',
'<h1 style="text-align:center;">Centered Headline</h1>'
);
$this->assertResult(
'<h1 align="right">Right-aligned Headline</h1>',
'<h1 style="text-align:right;">Right-aligned Headline</h1>'
);
$this->assertResult(
'<h1 align="left">Left-aligned Headline</h1>',
'<h1 style="text-align:left;">Left-aligned Headline</h1>'
);
$this->assertResult(
'<p align="justify">Justified Paragraph</p>',
'<p style="text-align:justify;">Justified Paragraph</p>'
);
$this->assertResult(
'<h1 align="invalid">Invalid Headline</h1>',
'<h1>Invalid Headline</h1>'
);
function testBasicURI() {
$this->assertResult('<a href="http://www.google.com/">Google</a>');
}
function testTable() {
function testInvalidURI() {
$this->assertResult(
'<a href="javascript:badstuff();">Google</a>',
'<a>Google</a>'
);
}
function testBdoAddMissingDir() {
$this->assertResult(
'<bdo>Go left.</bdo>',
'<bdo dir="ltr">Go left.</bdo>'
);
}
function testBdoReplaceInvalidDirWithDefault() {
$this->assertResult(
'<bdo dir="blahblah">Invalid value!</bdo>',
'<bdo dir="ltr">Invalid value!</bdo>'
);
}
function testBdoAlternateDefaultDir() {
$this->config->set('Attr', 'DefaultTextDir', 'rtl');
$this->assertResult(
'<bdo>Go right.</bdo>',
'<bdo dir="rtl">Go right.</bdo>'
);
}
function testRemoveDirWhenNotRequired() {
$this->assertResult(
'<span dir="blahblah">Invalid value!</span>',
'<span>Invalid value!</span>'
);
}
function testTableAttributes() {
$this->assertResult(
'<table frame="above" rules="rows" summary="A test table" border="2" cellpadding="5%" cellspacing="3" width="100%">
<col align="right" width="4*" />
@ -148,293 +120,64 @@ class HTMLPurifier_Strategy_ValidateAttributesTest extends
</tr>
</table>'
);
// test col.span is non-zero
}
function testColSpanIsNonZero() {
$this->assertResult(
'<col span="0" />',
'<col />'
);
// lengths
$this->assertResult(
'<td width="5%" height="10" /><th width="10" height="5%" /><hr width="10" height="10" />',
'<td style="width:5%;height:10px;" /><th style="width:10px;height:5%;" /><hr style="width:10px;" />'
);
// td boolean transformation
$this->assertResult(
'<td nowrap />',
'<td style="white-space:nowrap;" />'
);
// caption align transformation
$this->assertResult(
'<caption align="left" />',
'<caption style="text-align:left;" />'
);
$this->assertResult(
'<caption align="right" />',
'<caption style="text-align:right;" />'
);
$this->assertResult(
'<caption align="top" />',
'<caption style="caption-side:top;" />'
);
$this->assertResult(
'<caption align="bottom" />',
'<caption style="caption-side:bottom;" />'
);
$this->assertResult(
'<caption align="nonsense" />',
'<caption />'
);
// align transformation
$this->assertResult(
'<table align="left" />',
'<table style="float:left;" />'
);
$this->assertResult(
'<table align="center" />',
'<table style="margin-left:auto;margin-right:auto;" />'
);
$this->assertResult(
'<table align="right" />',
'<table style="float:right;" />'
);
$this->assertResult(
'<table align="top" />',
'<table />'
);
}
function testURI() {
$this->assertResult('<a href="http://www.google.com/">Google</a>');
// test invalid URI
$this->assertResult(
'<a href="javascript:badstuff();">Google</a>',
'<a>Google</a>'
);
}
function testImg() {
function testImgAddDefaults() {
$this->config->set('Core', 'RemoveInvalidImg', false);
$this->assertResult(
'<img />',
'<img src="" alt="Invalid image" />',
array('Core.RemoveInvalidImg' => false)
'<img src="" alt="Invalid image" />'
);
}
function testImgGenerateAlt() {
$this->assertResult(
'<img src="foobar.jpg" />',
'<img src="foobar.jpg" alt="foobar.jpg" />'
);
}
function testImgAddDefaultSrc() {
$this->config->set('Core', 'RemoveInvalidImg', false);
$this->assertResult(
'<img alt="pretty picture" />',
'<img alt="pretty picture" src="" />',
array('Core.RemoveInvalidImg' => false)
'<img alt="pretty picture" src="" />'
);
// mailto in image is not allowed
}
function testImgRemoveNonRetrievableProtocol() {
$this->config->set('Core', 'RemoveInvalidImg', false);
$this->assertResult(
'<img src="mailto:foo@example.com" />',
'<img alt="mailto:foo@example.com" src="" />',
array('Core.RemoveInvalidImg' => false)
);
// align transformation
$this->assertResult(
'<img src="foobar.jpg" alt="foobar" align="left" />',
'<img src="foobar.jpg" alt="foobar" style="float:left;" />'
);
$this->assertResult(
'<img src="foobar.jpg" alt="foobar" align="right" />',
'<img src="foobar.jpg" alt="foobar" style="float:right;" />'
);
$this->assertResult(
'<img src="foobar.jpg" alt="foobar" align="bottom" />',
'<img src="foobar.jpg" alt="foobar" style="vertical-align:baseline;" />'
);
$this->assertResult(
'<img src="foobar.jpg" alt="foobar" align="middle" />',
'<img src="foobar.jpg" alt="foobar" style="vertical-align:middle;" />'
);
$this->assertResult(
'<img src="foobar.jpg" alt="foobar" align="top" />',
'<img src="foobar.jpg" alt="foobar" style="vertical-align:top;" />'
);
$this->assertResult(
'<img src="foobar.jpg" alt="foobar" align="outerspace" />',
'<img src="foobar.jpg" alt="foobar" />'
);
}
function testBdo() {
// test required attributes for bdo
$this->assertResult(
'<bdo>Go left.</bdo>',
'<bdo dir="ltr">Go left.</bdo>'
);
$this->assertResult(
'<bdo dir="blahblah">Invalid value!</bdo>',
'<bdo dir="ltr">Invalid value!</bdo>'
'<img alt="mailto:foo@example.com" src="" />'
);
}
function testDir() {
// see testBdo, behavior is subtly different
$this->assertResult(
'<span dir="blahblah">Invalid value!</span>',
'<span>Invalid value!</span>'
);
function testPreserveRel() {
$this->config->set('Attr', 'AllowedRel', 'nofollow');
$this->assertResult('<a href="foo" rel="nofollow" />');
}
function testLinks() {
// link types
$this->assertResult(
'<a href="foo" rel="nofollow" />',
true,
array('Attr.AllowedRel' => 'nofollow')
);
// link targets
$this->assertResult(
'<a href="foo" target="_top" />',
true,
array('Attr.AllowedFrameTargets' => '_top',
'HTML.Doctype' => 'XHTML 1.0 Transitional')
);
function testPreserveTarget() {
$this->config->set('Attr', 'AllowedFrameTargets', '_top');
$this->config->set('HTML', 'Doctype', 'XHTML 1.0 Transitional');
$this->assertResult('<a href="foo" target="_top" />');
}
function testRemoveTargetWhenNotSupported() {
$this->config->set('HTML', 'Doctype', 'XHTML 1.0 Strict');
$this->config->set('Attr', 'AllowedFrameTargets', '_top');
$this->assertResult(
'<a href="foo" target="_top" />',
'<a href="foo" />'
);
$this->assertResult(
'<a href="foo" target="_top" />',
'<a href="foo" />',
array('Attr.AllowedFrameTargets' => '_top', 'HTML.Strict' => true)
);
}
function testBorder() {
// border
$this->assertResult(
'<img src="foo" alt="foo" hspace="1" vspace="3" />',
'<img src="foo" alt="foo" style="margin-top:3px;margin-bottom:3px;margin-left:1px;margin-right:1px;" />',
array('Attr.AllowedRel' => 'nofollow')
);
}
function testHr() {
$this->assertResult(
'<hr size="3" />',
'<hr style="height:3px;" />'
);
$this->assertResult(
'<hr noshade />',
'<hr style="color:#808080;background-color:#808080;border:0;" />'
);
// align transformation
$this->assertResult(
'<hr align="left" />',
'<hr style="margin-left:0;margin-right:auto;text-align:left;" />'
);
$this->assertResult(
'<hr align="center" />',
'<hr style="margin-left:auto;margin-right:auto;text-align:center;" />'
);
$this->assertResult(
'<hr align="right" />',
'<hr style="margin-left:auto;margin-right:0;text-align:right;" />'
);
$this->assertResult(
'<hr align="bottom" />',
'<hr />'
);
}
function testBr() {
// br clear transformation
$this->assertResult(
'<br clear="left" />',
'<br style="clear:left;" />'
);
$this->assertResult(
'<br clear="right" />',
'<br style="clear:right;" />'
);
$this->assertResult( // test both?
'<br clear="all" />',
'<br style="clear:both;" />'
);
$this->assertResult(
'<br clear="none" />',
'<br style="clear:none;" />'
);
$this->assertResult(
'<br clear="foo" />',
'<br />'
);
}
function testListTypeTransform() {
// ul
$this->assertResult(
'<ul type="disc" />',
'<ul style="list-style-type:disc;" />'
);
$this->assertResult(
'<ul type="square" />',
'<ul style="list-style-type:square;" />'
);
$this->assertResult(
'<ul type="circle" />',
'<ul style="list-style-type:circle;" />'
);
$this->assertResult( // case insensitive
'<ul type="CIRCLE" />',
'<ul style="list-style-type:circle;" />'
);
$this->assertResult(
'<ul type="a" />',
'<ul />'
);
// ol
$this->assertResult(
'<ol type="1" />',
'<ol style="list-style-type:decimal;" />'
);
$this->assertResult(
'<ol type="i" />',
'<ol style="list-style-type:lower-roman;" />'
);
$this->assertResult(
'<ol type="I" />',
'<ol style="list-style-type:upper-roman;" />'
);
$this->assertResult(
'<ol type="a" />',
'<ol style="list-style-type:lower-alpha;" />'
);
$this->assertResult(
'<ol type="A" />',
'<ol style="list-style-type:upper-alpha;" />'
);
$this->assertResult(
'<ol type="disc" />',
'<ol />'
);
// li
$this->assertResult(
'<li type="circle" />',
'<li style="list-style-type:circle;" />'
);
$this->assertResult(
'<li type="A" />',
'<li style="list-style-type:upper-alpha;" />'
);
$this->assertResult( // case sensitive
'<li type="CIRCLE" />',
'<li />'
);
}
}

View File

@ -0,0 +1,65 @@
<?php
require_once('HTMLPurifier/StrategyHarness.php');
require_once('HTMLPurifier/Strategy/ValidateAttributes.php');
class HTMLPurifier_Strategy_ValidateAttributes_IDTest extends HTMLPurifier_StrategyHarness
{
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_Strategy_ValidateAttributes();
$this->config->set('HTML', 'EnableAttrID', true);
}
function testPreserveIDWhenEnabled() {
$this->assertResult('<div id="valid">Preserve the ID.</div>');
}
function testRemoveInvalidID() {
$this->assertResult(
'<div id="0invalid">Kill the ID.</div>',
'<div>Kill the ID.</div>'
);
}
function testRemoveDuplicateID() {
$this->assertResult(
'<div id="valid">Valid</div><div id="valid">Invalid</div>',
'<div id="valid">Valid</div><div>Invalid</div>'
);
}
function testAttributeKeyCaseInsensitivity() {
$this->assertResult(
'<div ID="valid">Convert ID to lowercase.</div>',
'<div id="valid">Convert ID to lowercase.</div>'
);
}
function testTrimWhitespace() {
$this->assertResult(
'<div id=" valid ">Trim whitespace.</div>',
'<div id="valid">Trim whitespace.</div>'
);
}
function testIDBlacklist() {
$this->config->set('Attr', 'IDBlacklist', array('invalid'));
$this->assertResult(
'<div id="invalid">Invalid</div>',
'<div>Invalid</div>'
);
}
function testNameConvertedToID() {
$this->config->set('HTML', 'TidyLevel', 'heavy');
$this->assertResult(
'<a name="foobar" />',
'<a id="foobar" />'
);
}
}

View File

@ -0,0 +1,353 @@
<?php
require_once('HTMLPurifier/StrategyHarness.php');
require_once('HTMLPurifier/Strategy/ValidateAttributes.php');
class HTMLPurifier_Strategy_ValidateAttributes_TidyTest extends HTMLPurifier_StrategyHarness
{
function setUp() {
parent::setUp();
$this->obj = new HTMLPurifier_Strategy_ValidateAttributes();
$this->config->set('HTML', 'TidyLevel', 'heavy');
}
function testConvertCenterAlign() {
$this->assertResult(
'<h1 align="center">Centered Headline</h1>',
'<h1 style="text-align:center;">Centered Headline</h1>'
);
}
function testConvertRightAlign() {
$this->assertResult(
'<h1 align="right">Right-aligned Headline</h1>',
'<h1 style="text-align:right;">Right-aligned Headline</h1>'
);
}
function testConvertLeftAlign() {
$this->assertResult(
'<h1 align="left">Left-aligned Headline</h1>',
'<h1 style="text-align:left;">Left-aligned Headline</h1>'
);
}
function testConvertJustifyAlign() {
$this->assertResult(
'<p align="justify">Justified Paragraph</p>',
'<p style="text-align:justify;">Justified Paragraph</p>'
);
}
function testRemoveInvalidAlign() {
$this->assertResult(
'<h1 align="invalid">Invalid Headline</h1>',
'<h1>Invalid Headline</h1>'
);
}
function testConvertTableLengths() {
$this->assertResult(
'<td width="5%" height="10" /><th width="10" height="5%" /><hr width="10" height="10" />',
'<td style="width:5%;height:10px;" /><th style="width:10px;height:5%;" /><hr style="width:10px;" />'
);
}
function testTdConvertNowrap() {
$this->assertResult(
'<td nowrap />',
'<td style="white-space:nowrap;" />'
);
}
function testCaptionConvertAlignLeft() {
$this->assertResult(
'<caption align="left" />',
'<caption style="text-align:left;" />'
);
}
function testCaptionConvertAlignRight() {
$this->assertResult(
'<caption align="right" />',
'<caption style="text-align:right;" />'
);
}
function testCaptionConvertAlignTop() {
$this->assertResult(
'<caption align="top" />',
'<caption style="caption-side:top;" />'
);
}
function testCaptionConvertAlignBottom() {
$this->assertResult(
'<caption align="bottom" />',
'<caption style="caption-side:bottom;" />'
);
}
function testCaptionRemoveInvalidAlign() {
$this->assertResult(
'<caption align="nonsense" />',
'<caption />'
);
}
function testTableConvertAlignLeft() {
$this->assertResult(
'<table align="left" />',
'<table style="float:left;" />'
);
}
function testTableConvertAlignCenter() {
$this->assertResult(
'<table align="center" />',
'<table style="margin-left:auto;margin-right:auto;" />'
);
}
function testTableConvertAlignRight() {
$this->assertResult(
'<table align="right" />',
'<table style="float:right;" />'
);
}
function testTableRemoveInvalidAlign() {
$this->assertResult(
'<table align="top" />',
'<table />'
);
}
function testImgConvertAlignLeft() {
$this->assertResult(
'<img src="foobar.jpg" alt="foobar" align="left" />',
'<img src="foobar.jpg" alt="foobar" style="float:left;" />'
);
}
function testImgConvertAlignRight() {
$this->assertResult(
'<img src="foobar.jpg" alt="foobar" align="right" />',
'<img src="foobar.jpg" alt="foobar" style="float:right;" />'
);
}
function testImgConvertAlignBottom() {
$this->assertResult(
'<img src="foobar.jpg" alt="foobar" align="bottom" />',
'<img src="foobar.jpg" alt="foobar" style="vertical-align:baseline;" />'
);
}
function testImgConvertAlignMiddle() {
$this->assertResult(
'<img src="foobar.jpg" alt="foobar" align="middle" />',
'<img src="foobar.jpg" alt="foobar" style="vertical-align:middle;" />'
);
}
function testImgConvertAlignTop() {
$this->assertResult(
'<img src="foobar.jpg" alt="foobar" align="top" />',
'<img src="foobar.jpg" alt="foobar" style="vertical-align:top;" />'
);
}
function testImgRemoveInvalidAlign() {
$this->assertResult(
'<img src="foobar.jpg" alt="foobar" align="outerspace" />',
'<img src="foobar.jpg" alt="foobar" />'
);
}
function testBorderConvertHVSpace() {
$this->assertResult(
'<img src="foo" alt="foo" hspace="1" vspace="3" />',
'<img src="foo" alt="foo" style="margin-top:3px;margin-bottom:3px;margin-left:1px;margin-right:1px;" />'
);
}
function testHrConvertSize() {
$this->assertResult(
'<hr size="3" />',
'<hr style="height:3px;" />'
);
}
function testHrConvertNoshade() {
$this->assertResult(
'<hr noshade />',
'<hr style="color:#808080;background-color:#808080;border:0;" />'
);
}
function testHrConvertAlignLeft() {
$this->assertResult(
'<hr align="left" />',
'<hr style="margin-left:0;margin-right:auto;text-align:left;" />'
);
}
function testHrConvertAlignCenter() {
$this->assertResult(
'<hr align="center" />',
'<hr style="margin-left:auto;margin-right:auto;text-align:center;" />'
);
}
function testHrConvertAlignRight() {
$this->assertResult(
'<hr align="right" />',
'<hr style="margin-left:auto;margin-right:0;text-align:right;" />'
);
}
function testHrRemoveInvalidAlign() {
$this->assertResult(
'<hr align="bottom" />',
'<hr />'
);
}
function testBrConvertClearLeft() {
$this->assertResult(
'<br clear="left" />',
'<br style="clear:left;" />'
);
}
function testBrConvertClearRight() {
$this->assertResult(
'<br clear="right" />',
'<br style="clear:right;" />'
);
}
function testBrConvertClearAll() {
$this->assertResult(
'<br clear="all" />',
'<br style="clear:both;" />'
);
}
function testBrConvertClearNone() {
$this->assertResult(
'<br clear="none" />',
'<br style="clear:none;" />'
);
}
function testBrRemoveInvalidClear() {
$this->assertResult(
'<br clear="foo" />',
'<br />'
);
}
function testUlConvertTypeDisc() {
$this->assertResult(
'<ul type="disc" />',
'<ul style="list-style-type:disc;" />'
);
}
function testUlConvertTypeSquare() {
$this->assertResult(
'<ul type="square" />',
'<ul style="list-style-type:square;" />'
);
}
function testUlConvertTypeCircle() {
$this->assertResult(
'<ul type="circle" />',
'<ul style="list-style-type:circle;" />'
);
}
function testUlConvertTypeCaseInsensitive() {
$this->assertResult(
'<ul type="CIRCLE" />',
'<ul style="list-style-type:circle;" />'
);
}
function testUlRemoveInvalidType() {
$this->assertResult(
'<ul type="a" />',
'<ul />'
);
}
function testOlConvertType1() {
$this->assertResult(
'<ol type="1" />',
'<ol style="list-style-type:decimal;" />'
);
}
function testOlConvertTypeLowerI() {
$this->assertResult(
'<ol type="i" />',
'<ol style="list-style-type:lower-roman;" />'
);
}
function testOlConvertTypeUpperI() {
$this->assertResult(
'<ol type="I" />',
'<ol style="list-style-type:upper-roman;" />'
);
}
function testOlConvertTypeLowerA() {
$this->assertResult(
'<ol type="a" />',
'<ol style="list-style-type:lower-alpha;" />'
);
}
function testOlConvertTypeUpperA() {
$this->assertResult(
'<ol type="A" />',
'<ol style="list-style-type:upper-alpha;" />'
);
}
function testOlRemoveInvalidType() {
$this->assertResult(
'<ol type="disc" />',
'<ol />'
);
}
function testLiConvertTypeCircle() {
$this->assertResult(
'<li type="circle" />',
'<li style="list-style-type:circle;" />'
);
}
function testLiConvertTypeA() {
$this->assertResult(
'<li type="A" />',
'<li style="list-style-type:upper-alpha;" />'
);
}
function testLiConvertTypeCaseSensitive() {
$this->assertResult(
'<li type="CIRCLE" />',
'<li />'
);
}
}

View File

@ -5,7 +5,7 @@
error_reporting(E_ALL | E_STRICT);
define('HTMLPurifierTest', 1);
define('HTMLPURIFIER_SCHEMA_STRICT', true);
define('HTMLPURIFIER_SCHEMA_STRICT', true); // validate schemas
// wishlist: automated calling of this file from multiple PHP versions so we
// don't have to constantly switch around
@ -13,10 +13,11 @@ define('HTMLPURIFIER_SCHEMA_STRICT', true);
// default settings (protect against register_globals)
$GLOBALS['HTMLPurifierTest'] = array();
$GLOBALS['HTMLPurifierTest']['PEAR'] = false; // do PEAR tests
$GLOBALS['HTMLPurifierTest']['PH5P'] = version_compare(PHP_VERSION, "5", ">=") && class_exists('DOMDocument');
$simpletest_location = 'simpletest/'; // reasonable guess
// load SimpleTest
@include '../test-settings.php'; // don't mind if it isn't there
if (file_exists('../test-settings.php')) include '../test-settings.php';
require_once $simpletest_location . 'unit_tester.php';
require_once $simpletest_location . 'reporter.php';
require_once $simpletest_location . 'mock_objects.php';
@ -79,7 +80,6 @@ if ($test_file = $GLOBALS['HTMLPurifierTest']['File']) {
} else {
$test = new GroupTest('All Tests');
foreach ($test_files as $test_file) {
require_once $test_file;
$test->addTestClass(path2class($test_file));

View File

@ -79,6 +79,7 @@ $test_files[] = 'HTMLPurifier/GeneratorTest.php';
$test_files[] = 'HTMLPurifier/HTMLDefinitionTest.php';
$test_files[] = 'HTMLPurifier/HTMLModuleManagerTest.php';
$test_files[] = 'HTMLPurifier/HTMLModuleTest.php';
$test_files[] = 'HTMLPurifier/HTMLModule/ObjectTest.php';
$test_files[] = 'HTMLPurifier/HTMLModule/RubyTest.php';
$test_files[] = 'HTMLPurifier/HTMLModule/ScriptingTest.php';
$test_files[] = 'HTMLPurifier/HTMLModule/TidyTest.php';
@ -98,9 +99,13 @@ $test_files[] = 'HTMLPurifier/Strategy/FixNestingTest.php';
$test_files[] = 'HTMLPurifier/Strategy/FixNesting_ErrorsTest.php';
$test_files[] = 'HTMLPurifier/Strategy/MakeWellFormedTest.php';
$test_files[] = 'HTMLPurifier/Strategy/MakeWellFormed_ErrorsTest.php';
$test_files[] = 'HTMLPurifier/Strategy/MakeWellFormed_InjectorTest.php';
$test_files[] = 'HTMLPurifier/Strategy/RemoveForeignElementsTest.php';
$test_files[] = 'HTMLPurifier/Strategy/RemoveForeignElements_ErrorsTest.php';
$test_files[] = 'HTMLPurifier/Strategy/RemoveForeignElements_TidyTest.php';
$test_files[] = 'HTMLPurifier/Strategy/ValidateAttributesTest.php';
$test_files[] = 'HTMLPurifier/Strategy/ValidateAttributes_IDTest.php';
$test_files[] = 'HTMLPurifier/Strategy/ValidateAttributes_TidyTest.php';
$test_files[] = 'HTMLPurifier/TagTransformTest.php';
$test_files[] = 'HTMLPurifier/TokenTest.php';
$test_files[] = 'HTMLPurifier/URIDefinitionTest.php';