mirror of
https://github.com/ezyang/htmlpurifier.git
synced 2025-08-03 20:58:11 +02:00
Compare commits
3 Commits
v2.1.0-str
...
v2.1.2-str
Author | SHA1 | Date | |
---|---|---|---|
|
b3f0e6c86c | ||
|
80c60bb9b5 | ||
|
503e76081b |
2
Doxyfile
2
Doxyfile
@@ -4,7 +4,7 @@
|
||||
# Project related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
PROJECT_NAME = HTML Purifier
|
||||
PROJECT_NUMBER = 2.1.0
|
||||
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
|
||||
|
90
NEWS
90
NEWS
@@ -9,6 +9,96 @@ 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
|
||||
. Add prefix directory to include path for standalone, this prevents
|
||||
other installations from clobbering the standalone's URI schemes
|
||||
. Single test methods can be invoked by prefixing with __only
|
||||
|
||||
2.1.0, released 2007-08-02
|
||||
# flush-htmldefinition-cache.php superseded in favor of a generic
|
||||
flush-definition-cache.php script, you can clear a specific cache
|
||||
by passing its name as a parameter to the script
|
||||
! Phorum mod implemented for HTML Purifier
|
||||
! With %Core.AggressivelyFixLt, <3 and similar emoticons no longer
|
||||
trigger HTML removal in PHP5 (DOMLex). This directive is not necessary
|
||||
for PHP4 (DirectLex).
|
||||
! Standalone file now available, which greatly reduces the amount of
|
||||
includes (although there are still a few files that reside in the
|
||||
standalone folder)
|
||||
! Relative URIs can now be transformed into their absolute equivalents
|
||||
using %URI.Base and %URI.MakeAbsolute
|
||||
! Ruby implemented for XHTML 1.1
|
||||
! You can now define custom URI filtering behavior, see enduser-uri-filter.html
|
||||
for more details
|
||||
! UTF-8 font names now supported in CSS
|
||||
- AutoFormatters emit friendly error messages if tags or attributes they
|
||||
need are not allowed
|
||||
- ConfigForm's compactification of directive names is now configurable
|
||||
- AutoParagraph autoformatter algorithm refined after field-testing
|
||||
- XHTML 1.1 now applies XHTML 1.0 Strict cleanup routines, namely
|
||||
blockquote wrapping
|
||||
- Contents of <style> tags removed by default when tags are removed
|
||||
. HTMLPurifier_Config->getSerial() implemented, this is extremely useful
|
||||
for output cache invalidation
|
||||
. ConfigForm printer now can retrieve CSS and JS files as strings, in
|
||||
case HTML Purifier's directory is not publically accessible
|
||||
. Introduce new text/itext configuration directive values: these represent
|
||||
longer strings that would be more appropriately edited with a textarea
|
||||
. Allow newlines to act as separators for lists, hashes, lookups and
|
||||
%HTML.Allowed
|
||||
. ConfigForm generates textareas instead of text inputs for lists, hashes,
|
||||
lookups, text and itext fields
|
||||
. Hidden element content removal genericized: %Core.HiddenElements can
|
||||
be used to customize this behavior, by default <script> and <style> are
|
||||
hidden
|
||||
. Added HTMLPURIFIER_PREFIX constant, should be used instead of dirname(__FILE__)
|
||||
. Custom ChildDef added to default include list
|
||||
. URIScheme reflection improved: will not attempt to include file if class
|
||||
already exists. May clobber autoload, so I need to keep an eye on it
|
||||
. ConfigSchema heavily optimized, will only collect information and validate
|
||||
definitions when HTMLPURIFIER_SCHEMA_STRICT is true.
|
||||
. AttrDef_URI unit tests and implementation refactored
|
||||
. benchmarks/ directory now protected from public view with .htaccess file;
|
||||
run the tests via command line
|
||||
. URI scheme is munged off if there is no authority and the scheme is the
|
||||
default one
|
||||
. All unit tests inherit from HTMLPurifier_Harness, not UnitTestCase
|
||||
. Interface for URIScheme changed
|
||||
. Generic URI object to hold components of URI added, most systems involved
|
||||
in URI validation have been migrated to use it
|
||||
. Custom filtering for URIs factored out to URIDefinition interface for
|
||||
maximum extensibility
|
||||
|
||||
2.0.1, released 2007-06-27
|
||||
! Tag auto-closing now based on a ChildDef heuristic rather than a
|
||||
manually set auto_close array; some behavior may change
|
||||
|
36
TODO
36
TODO
@@ -6,14 +6,9 @@ TODO List
|
||||
? Maybe I'll Do It
|
||||
==========================
|
||||
|
||||
2.1 release [Refactor, refactor!]
|
||||
# URI validation routines tighter (see docs/dev-code-quality.html) (COMPLEX)
|
||||
# Advanced URI filtering schemes (see docs/proposal-new-directives.txt)
|
||||
# Ruby support
|
||||
- Configuration profiles: predefined directives set with one func call
|
||||
- Implement IDREF support (harder than it seems, since you cannot have
|
||||
IDREFs to non-existent IDs)
|
||||
- Allow non-ASCII characters in font names
|
||||
If no interest is expressed for a feature that may required a considerable
|
||||
amount of effort to implement, it may get endlessly delayed. Do not be
|
||||
afraid to cast your vote for the next feature to be implemented!
|
||||
|
||||
2.2 release [Error'ed]
|
||||
# Error logging for filtering/cleanup procedures
|
||||
@@ -33,21 +28,22 @@ TODO List
|
||||
- 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
|
||||
|
||||
@@ -60,31 +56,33 @@ TODO List
|
||||
Ongoing
|
||||
- Lots of profiling, make it faster!
|
||||
- Plugins for major CMSes (COMPLEX)
|
||||
- WordPress (mostly written, needs beta-testing)
|
||||
- phpBB
|
||||
- Phorum
|
||||
- eFiction
|
||||
- more! (look for ones that use WYSIWYGs)
|
||||
- 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
|
||||
- Explain how to use HTML Purifier in non-PHP languages / create
|
||||
a simple command line stub
|
||||
- Abstract ChildDef_BlockQuote to work with all elements that only
|
||||
allow blocks in them, required or optional
|
||||
- Reorganize Unit Tests
|
||||
- Refactor loop tests (esp. AttrDef_URI)
|
||||
- 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
|
||||
|
16
WHATSNEW
16
WHATSNEW
@@ -1,8 +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.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.
|
||||
|
1
benchmarks/.htaccess
Normal file
1
benchmarks/.htaccess
Normal file
@@ -0,0 +1 @@
|
||||
Deny from all
|
12
benchmarks/Trace.php
Normal file
12
benchmarks/Trace.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
ini_set('xdebug.trace_format', 1);
|
||||
ini_set('xdebug.show_mem_delta', true);
|
||||
|
||||
xdebug_start_trace(dirname(__FILE__) . '/Trace');
|
||||
require_once '../library/HTMLPurifier.auto.php';
|
||||
|
||||
$purifier = new HTMLPurifier();
|
||||
|
||||
$data = $purifier->purify(file_get_contents('samples/Lexer/4.html'));
|
||||
xdebug_stop_trace();
|
@@ -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 &nbsp;? Unknown release milestone.</td></tr>
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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>
|
||||
|
@@ -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><div style="text-align:center;">Centered</div></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>
|
||||
|
||||
|
@@ -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 > 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>
|
||||
|
||||
@@ -231,7 +237,7 @@ of your real encoding.</p>
|
||||
why the character encoding should be explicitly stated. When the
|
||||
browser isn't told what the character encoding of a text is, it
|
||||
has to guess: and sometimes the guess is wrong. Hackers can manipulate
|
||||
this guess in order to slip XSS pass filters and then fool the
|
||||
this guess in order to slip XSS past filters and then fool the
|
||||
browser into executing it as active code. A great example of this
|
||||
is the <a href="http://shiflett.org/archive/177">Google UTF-7
|
||||
exploit</a>.</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 <?php ?> 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 "UTF-8"</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><?xml version="1.0" encoding="UTF-8"?></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, θ can be
|
||||
entity reference to achieve the desired effect. For instance, θ can be
|
||||
written <code>&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: 激光,
|
||||
這兩個字是甚麼意思.
|
||||
The entity-ized version would look like this:</p>
|
||||
The ampersand encoded version would look like this:</p>
|
||||
|
||||
<pre>&#28608;&#20809;, &#36889;&#20841;&#20491;&#23383;&#26159;&#29978;&#40636;&#24847;&#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>
|
||||
@@ -567,10 +577,11 @@ which may be used by POST, and is required when you want to upload
|
||||
files.</p>
|
||||
|
||||
<p>The following is a summarization of notes from
|
||||
<a href="http://ppewww.physics.gla.ac.uk/~flavell/charset/form-i18n.html">
|
||||
<a href="http://web.archive.org/web/20060427015200/ppewww.ph.gla.ac.uk/~flavell/charset/form-i18n.html">
|
||||
<code>FORM</code> submission and i18n</a>. That document contains lots
|
||||
of useful information, but is written in a rambly manner, so
|
||||
here I try to get right to the point.</p>
|
||||
here I try to get right to the point. (Note: the original has
|
||||
disappeared off the web, so I am linking to the Web Archive copy.)</p>
|
||||
|
||||
<h4 id="whyutf8-forms-urlencoded"><code>application/x-www-form-urlencoded</code></h4>
|
||||
|
||||
@@ -592,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>
|
||||
@@ -608,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>
|
||||
@@ -621,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?
|
||||
@@ -663,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>&theta;</code>
|
||||
in your HTML, but the output is in Latin-1 (which, understandably,
|
||||
@@ -677,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>&theta;</code></li>
|
||||
<li>The <code>EntityParser</code> will transform all named and numeric
|
||||
character entities to their corresponding raw UTF-8 equivalents:
|
||||
@@ -700,7 +711,7 @@ Purifier has provided a slightly more palatable workaround using
|
||||
<li>The <code>EntityParser</code> transforms entities: <code>θ</code></li>
|
||||
<li>HTML Purifier processes the code: <code>θ</code></li>
|
||||
<li>The <code>Encoder</code> replaces all non-ASCII characters
|
||||
with numeric entities: <code>&#952;</code></li>
|
||||
with numeric entity reference: <code>&#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>&#952;</code> (remember, it's all ASCII!)</li>
|
||||
@@ -710,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>
|
||||
|
||||
@@ -984,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
|
||||
@@ -998,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>
|
||||
|
28
docs/ref-css-length.txt
Normal file
28
docs/ref-css-length.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
CSS Length Reference
|
||||
To bound, or not to bound, that is the question
|
||||
|
||||
It's quite a reasonable request, really, and it's already been implemented
|
||||
for HTML. That is, length bounding. It makes little sense to let users
|
||||
define text blocks that have a font-size of 63,360 inches (that's a mile,
|
||||
by the way) or a width of forty-fold the parent container.
|
||||
|
||||
But it's a little more complicated then that. There are multiple units
|
||||
one can use, and we have to a little unit conversion to get things working.
|
||||
Here's what we have:
|
||||
|
||||
Absolute:
|
||||
1 in ~= 2.54 cm
|
||||
1 cm = 10 mm
|
||||
1 pt = 1/72 in
|
||||
1 pc = 12 pt
|
||||
|
||||
Relative:
|
||||
1 em ~= 10.0667 px
|
||||
1 ex ~= 0.5 em, though Mozilla Firefox says 1 ex = 6px
|
||||
1 px ~= 1 pt
|
||||
|
||||
Watch out: font-sizes can also be nested to get successively larger
|
||||
(although I do not relish having to keep track of context font-sizes,
|
||||
this may be necessary, especially for some of the more advanced features
|
||||
for preventing things like white on white).
|
@@ -22,7 +22,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
HTML Purifier 2.0.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
|
||||
@@ -40,6 +40,9 @@
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
// constants are slow, but we'll make one exception
|
||||
define('HTMLPURIFIER_PREFIX', dirname(__FILE__));
|
||||
|
||||
// almost every class has an undocumented dependency to these, so make sure
|
||||
// they get included
|
||||
require_once 'HTMLPurifier/ConfigSchema.php'; // important
|
||||
@@ -74,7 +77,7 @@ This directive has been available since 2.0.0.
|
||||
class HTMLPurifier
|
||||
{
|
||||
|
||||
var $version = '2.0.1';
|
||||
var $version = '2.1.2';
|
||||
|
||||
var $config;
|
||||
var $filters;
|
||||
@@ -196,13 +199,13 @@ class HTMLPurifier
|
||||
/**
|
||||
* Singleton for enforcing just one HTML Purifier in your system
|
||||
*/
|
||||
function &getInstance($prototype = null) {
|
||||
static function &getInstance($prototype = null) {
|
||||
static $htmlpurifier;
|
||||
if (!$htmlpurifier || $prototype) {
|
||||
if (is_a($prototype, 'HTMLPurifier')) {
|
||||
if ($prototype instanceof HTMLPurifier) {
|
||||
$htmlpurifier = $prototype;
|
||||
} elseif ($prototype) {
|
||||
$htmlpurifier = new HTMLPurifier(HTMLPurifier_Config::create($prototype));
|
||||
$htmlpurifier = new HTMLPurifier($prototype);
|
||||
} else {
|
||||
$htmlpurifier = new HTMLPurifier();
|
||||
}
|
||||
|
@@ -38,19 +38,24 @@ class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
|
||||
$quote = $font[0];
|
||||
if ($font[$length - 1] !== $quote) continue;
|
||||
$font = substr($font, 1, $length - 2);
|
||||
// double-backslash processing is buggy
|
||||
$font = str_replace("\\$quote", $quote, $font); // de-escape quote
|
||||
$font = str_replace("\\\n", "\n", $font); // de-escape newlines
|
||||
}
|
||||
// process font
|
||||
// $font is a pure representation of the font name
|
||||
|
||||
if (ctype_alnum($font)) {
|
||||
// very simple font, allow it in unharmed
|
||||
$final .= $font . ', ';
|
||||
continue;
|
||||
}
|
||||
$nospace = str_replace(array(' ', '.', '!'), '', $font);
|
||||
if (ctype_alnum($nospace)) {
|
||||
// font with spaces in it
|
||||
$final .= "'$font', ";
|
||||
continue;
|
||||
}
|
||||
|
||||
// complicated font, requires quoting
|
||||
|
||||
// armor single quotes and new lines
|
||||
$font = str_replace("'", "\\'", $font);
|
||||
$font = str_replace("\n", "\\\n", $font);
|
||||
$final .= "'$font', ";
|
||||
}
|
||||
$final = rtrim($final, ', ');
|
||||
if ($final === '') return false;
|
||||
|
@@ -15,7 +15,7 @@ class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
|
||||
{
|
||||
|
||||
function HTMLPurifier_AttrDef_CSS_URI() {
|
||||
$this->HTMLPurifier_AttrDef_URI(true); // always embedded
|
||||
parent::HTMLPurifier_AttrDef_URI(true); // always embedded
|
||||
}
|
||||
|
||||
function validate($uri_string, $config, &$context) {
|
||||
|
@@ -1,90 +1,66 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/URIParser.php';
|
||||
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
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'DefaultScheme', 'http', 'string',
|
||||
'Defines through what scheme the output will be served, in order to '.
|
||||
'select the proper object validator when no scheme information is present.'
|
||||
);
|
||||
'URI', 'Munge', null, 'string/null', '
|
||||
<p>
|
||||
Munges all browsable (usually http, https and ftp)
|
||||
absolute URI\'s into another URI, usually a URI redirection service.
|
||||
This directive accepts a URI, formatted with a <code>%s</code> where
|
||||
the url-encoded original URI should be inserted (sample:
|
||||
<code>http://www.google.com/url?q=%s</code>).
|
||||
</p>
|
||||
<p>
|
||||
Uses for this directive:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Prevent PageRank leaks, while being fairly transparent
|
||||
to users (you may also want to add some client side JavaScript to
|
||||
override the text in the statusbar). <strong>Notice</strong>:
|
||||
Many security experts believe that this form of protection does not deter spam-bots.
|
||||
</li>
|
||||
<li>
|
||||
Redirect users to a splash page telling them they are leaving your
|
||||
website. While this is poor usability practice, it is often mandated
|
||||
in corporate environments.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
This directive has been available since 1.3.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
// disabling directives
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'Host', null, 'string/null',
|
||||
'Defines the domain name of the server, so we can determine whether or '.
|
||||
'an absolute URI is from your website or not. Not strictly necessary, '.
|
||||
'as users should be using relative URIs to reference resources on your '.
|
||||
'website. It will, however, let you use absolute URIs to link to '.
|
||||
'subdomains of the domain you post here: i.e. example.com will allow '.
|
||||
'sub.example.com. However, higher up domains will still be excluded: '.
|
||||
'if you set %URI.Host to sub.example.com, example.com will be blocked. '.
|
||||
'This directive has been available since 1.2.0.'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'DisableExternal', false, 'bool',
|
||||
'Disables links to external websites. This is a highly effective '.
|
||||
'anti-spam and anti-pagerank-leech measure, but comes at a hefty price: no'.
|
||||
'links or images outside of your domain will be allowed. Non-linkified '.
|
||||
'URIs will still be preserved. If you want to be able to link to '.
|
||||
'subdomains or use absolute URIs, specify %URI.Host for your website. '.
|
||||
'This directive has been available since 1.2.0.'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'DisableExternalResources', false, 'bool',
|
||||
'Disables the embedding of external resources, preventing users from '.
|
||||
'embedding things like images from other hosts. This prevents '.
|
||||
'access tracking (good for email viewers), bandwidth leeching, '.
|
||||
'cross-site request forging, goatse.cx posting, and '.
|
||||
'other nasties, but also results in '.
|
||||
'a loss of end-user functionality (they can\'t directly post a pic '.
|
||||
'they posted from Flickr anymore). Use it if you don\'t have a '.
|
||||
'robust user-content moderation team. This directive has been '.
|
||||
'available since 1.3.0.'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'DisableResources', false, 'bool',
|
||||
'Disables embedding resources, essentially meaning no pictures. You can '.
|
||||
'still link to them though. See %URI.DisableExternalResources for why '.
|
||||
'this might be a good idea. This directive has been available since 1.3.0.'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'Munge', null, 'string/null',
|
||||
'Munges all browsable (usually http, https and ftp) URI\'s into some URL '.
|
||||
'redirection service. Pass this directive a URI, with %s inserted where '.
|
||||
'the url-encoded original URI should be inserted (sample: '.
|
||||
'<code>http://www.google.com/url?q=%s</code>). '.
|
||||
'This prevents PageRank leaks, while being as transparent as possible '.
|
||||
'to users (you may also want to add some client side JavaScript to '.
|
||||
'override the text in the statusbar). Warning: many security experts '.
|
||||
'believe that this form of protection does not deter spam-bots. '.
|
||||
'You can also use this directive to redirect users to a splash page '.
|
||||
'telling them they are leaving your website. '.
|
||||
'This directive has been available since 1.3.0.'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'HostBlacklist', array(), 'list',
|
||||
'List of strings that are forbidden in the host of any URI. Use it to '.
|
||||
'kill domain names of spam, etc. Note that it will catch anything in '.
|
||||
'the domain, so <tt>moo.com</tt> will catch <tt>moo.com.example.com</tt>. '.
|
||||
'This directive has been available since 1.3.0.'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'Disable', false, 'bool',
|
||||
'Disables all URIs in all forms. Not sure why you\'d want to do that '.
|
||||
'(after all, the Internet\'s founded on the notion of a hyperlink). '.
|
||||
'This directive has been available since 1.3.0.'
|
||||
);
|
||||
'URI', 'Disable', false, 'bool', '
|
||||
<p>
|
||||
Disables all URIs in all forms. Not sure why you\'d want to do that
|
||||
(after all, the Internet\'s founded on the notion of a hyperlink).
|
||||
This directive has been available since 1.3.0.
|
||||
</p>
|
||||
');
|
||||
HTMLPurifier_ConfigSchema::defineAlias('Attr', 'DisableURI', 'URI', 'Disable');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'DisableResources', false, 'bool', '
|
||||
<p>
|
||||
Disables embedding resources, essentially meaning no pictures. You can
|
||||
still link to them though. See %URI.DisableExternalResources for why
|
||||
this might be a good idea. This directive has been available since 1.3.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
/**
|
||||
* Validates a URI as defined by RFC 3986.
|
||||
* @note Scheme-specific mechanics deferred to HTMLPurifier_URIScheme
|
||||
@@ -92,214 +68,83 @@ HTMLPurifier_ConfigSchema::defineAlias('Attr', 'DisableURI', 'URI', 'Disable');
|
||||
class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
var $host;
|
||||
var $embeds_resource;
|
||||
var $parser, $percentEncoder;
|
||||
var $embedsResource;
|
||||
|
||||
/**
|
||||
* @param $embeds_resource_resource Does the URI here result in an extra HTTP request?
|
||||
*/
|
||||
function HTMLPurifier_AttrDef_URI($embeds_resource = false) {
|
||||
$this->host = new HTMLPurifier_AttrDef_URI_Host();
|
||||
$this->embeds_resource = (bool) $embeds_resource;
|
||||
$this->parser = new HTMLPurifier_URIParser();
|
||||
$this->percentEncoder = new HTMLPurifier_PercentEncoder();
|
||||
$this->embedsResource = (bool) $embeds_resource;
|
||||
}
|
||||
|
||||
function validate($uri, $config, &$context) {
|
||||
|
||||
static $PercentEncoder = null;
|
||||
if ($PercentEncoder === null) $PercentEncoder = new HTMLPurifier_PercentEncoder();
|
||||
|
||||
// We'll write stack-based parsers later, for now, use regexps to
|
||||
// get things working as fast as possible (irony)
|
||||
|
||||
if ($config->get('URI', 'Disable')) return false;
|
||||
|
||||
// parse as CDATA
|
||||
// initial operations
|
||||
$uri = $this->parseCDATA($uri);
|
||||
$uri = $this->percentEncoder->normalize($uri);
|
||||
|
||||
// fix up percent-encoding
|
||||
$uri = $PercentEncoder->normalize($uri);
|
||||
// parse the URI
|
||||
$uri = $this->parser->parse($uri);
|
||||
if ($uri === false) return false;
|
||||
|
||||
// while it would be nice to use parse_url(), that's specifically
|
||||
// for HTTP and thus won't work for our generic URI parsing
|
||||
// add embedded flag to context for validators
|
||||
$context->register('EmbeddedURI', $this->embedsResource);
|
||||
|
||||
// according to the RFC... (but this cuts corners, i.e. non-validating)
|
||||
$r_URI = '!'.
|
||||
'(([^:/?#<>\'"]+):)?'. // 2. Scheme
|
||||
'(//([^/?#<>\'"]*))?'. // 4. Authority
|
||||
'([^?#<>\'"]*)'. // 5. Path
|
||||
'(\?([^#<>\'"]*))?'. // 7. Query
|
||||
'(#([^<>\'"]*))?'. // 8. Fragment
|
||||
'!';
|
||||
|
||||
$matches = array();
|
||||
$result = preg_match($r_URI, $uri, $matches);
|
||||
|
||||
if (!$result) return false; // invalid URI
|
||||
|
||||
// seperate out parts
|
||||
$scheme = !empty($matches[1]) ? $matches[2] : null;
|
||||
$authority = !empty($matches[3]) ? $matches[4] : null;
|
||||
$path = $matches[5]; // always present, can be empty
|
||||
$query = !empty($matches[6]) ? $matches[7] : null;
|
||||
$fragment = !empty($matches[8]) ? $matches[9] : null;
|
||||
|
||||
|
||||
|
||||
$registry =& HTMLPurifier_URISchemeRegistry::instance();
|
||||
if ($scheme !== null) {
|
||||
// no need to validate the scheme's fmt since we do that when we
|
||||
// retrieve the specific scheme object from the registry
|
||||
$scheme = ctype_lower($scheme) ? $scheme : strtolower($scheme);
|
||||
$scheme_obj = $registry->getScheme($scheme, $config, $context);
|
||||
if (!$scheme_obj) return false; // invalid scheme, clean it out
|
||||
} else {
|
||||
$scheme_obj = $registry->getScheme(
|
||||
$config->get('URI', 'DefaultScheme'), $config, $context
|
||||
);
|
||||
}
|
||||
|
||||
// something funky weird happened in the registry, abort!
|
||||
if (!$scheme_obj) {
|
||||
trigger_error(
|
||||
'Default scheme object "' . $config->get('URI', 'DefaultScheme') . '" was not readable',
|
||||
E_USER_WARNING
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// the URI we're processing embeds_resource a resource in the page, but the URI
|
||||
// it references cannot be located
|
||||
if ($this->embeds_resource && !$scheme_obj->browsable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if ($authority !== null) {
|
||||
$ok = false;
|
||||
do {
|
||||
|
||||
// remove URI if it's absolute and we disabled externals or
|
||||
// if it's absolute and embedded and we disabled external resources
|
||||
unset($our_host);
|
||||
if (
|
||||
$config->get('URI', 'DisableExternal') ||
|
||||
(
|
||||
$config->get('URI', 'DisableExternalResources') &&
|
||||
$this->embeds_resource
|
||||
)
|
||||
) {
|
||||
$our_host = $config->get('URI', 'Host');
|
||||
if ($our_host === null) return false;
|
||||
// generic validation
|
||||
$result = $uri->validate($config, $context);
|
||||
if (!$result) break;
|
||||
|
||||
// chained validation
|
||||
$uri_def =& $config->getDefinition('URI');
|
||||
$result = $uri_def->filter($uri, $config, $context);
|
||||
if (!$result) break;
|
||||
|
||||
// scheme-specific validation
|
||||
$scheme_obj = $uri->getSchemeObj($config, $context);
|
||||
if (!$scheme_obj) break;
|
||||
if ($this->embedsResource && !$scheme_obj->browsable) break;
|
||||
$result = $scheme_obj->validate($uri, $config, $context);
|
||||
if (!$result) break;
|
||||
|
||||
// survived gauntlet
|
||||
$ok = true;
|
||||
|
||||
} while (false);
|
||||
|
||||
$context->destroy('EmbeddedURI');
|
||||
if (!$ok) return false;
|
||||
|
||||
// munge scheme off if necessary (this must be last)
|
||||
if (!is_null($uri->scheme) && is_null($uri->host)) {
|
||||
if ($uri_def->defaultScheme == $uri->scheme) {
|
||||
$uri->scheme = null;
|
||||
}
|
||||
|
||||
$HEXDIG = '[A-Fa-f0-9]';
|
||||
$unreserved = 'A-Za-z0-9-._~'; // make sure you wrap with []
|
||||
$sub_delims = '!$&\'()'; // needs []
|
||||
$pct_encoded = "%$HEXDIG$HEXDIG";
|
||||
$r_userinfo = "(?:[$unreserved$sub_delims:]|$pct_encoded)*";
|
||||
$r_authority = "/^(($r_userinfo)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";
|
||||
$matches = array();
|
||||
preg_match($r_authority, $authority, $matches);
|
||||
// overloads regexp!
|
||||
$userinfo = !empty($matches[1]) ? $matches[2] : null;
|
||||
$host = !empty($matches[3]) ? $matches[3] : null;
|
||||
$port = !empty($matches[4]) ? $matches[5] : null;
|
||||
|
||||
// validate port
|
||||
if ($port !== null) {
|
||||
$port = (int) $port;
|
||||
if ($port < 1 || $port > 65535) $port = null;
|
||||
}
|
||||
|
||||
$host = $this->host->validate($host, $config, $context);
|
||||
if ($host === false) $host = null;
|
||||
|
||||
if ($this->checkBlacklist($host, $config, $context)) return false;
|
||||
|
||||
// more lenient absolute checking
|
||||
if (isset($our_host)) {
|
||||
$host_parts = array_reverse(explode('.', $host));
|
||||
// could be cached
|
||||
$our_host_parts = array_reverse(explode('.', $our_host));
|
||||
foreach ($our_host_parts as $i => $discard) {
|
||||
if (!isset($host_parts[$i])) return false;
|
||||
if ($host_parts[$i] != $our_host_parts[$i]) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// userinfo and host are validated within the regexp
|
||||
|
||||
} else {
|
||||
$port = $host = $userinfo = null;
|
||||
}
|
||||
|
||||
// back to string
|
||||
$result = $uri->toString();
|
||||
|
||||
// query and fragment are quite simple in terms of definition:
|
||||
// *( pchar / "/" / "?" ), so define their validation routines
|
||||
// when we start fixing percent encoding
|
||||
|
||||
|
||||
|
||||
// path gets to be validated against a hodge-podge of rules depending
|
||||
// on the status of authority and scheme, but it's not that important,
|
||||
// esp. since it won't be applicable to everyone
|
||||
|
||||
|
||||
|
||||
// okay, now we defer execution to the subobject for more processing
|
||||
// note that $fragment is omitted
|
||||
list($userinfo, $host, $port, $path, $query) =
|
||||
$scheme_obj->validateComponents(
|
||||
$userinfo, $host, $port, $path, $query, $config, $context
|
||||
);
|
||||
|
||||
|
||||
// reconstruct authority
|
||||
$authority = null;
|
||||
if (!is_null($userinfo) || !is_null($host) || !is_null($port)) {
|
||||
$authority = '';
|
||||
if($userinfo !== null) $authority .= $userinfo . '@';
|
||||
$authority .= $host;
|
||||
if($port !== null) $authority .= ':' . $port;
|
||||
}
|
||||
|
||||
// reconstruct the result
|
||||
$result = '';
|
||||
if ($scheme !== null) $result .= "$scheme:";
|
||||
if ($authority !== null) $result .= "//$authority";
|
||||
$result .= $path;
|
||||
if ($query !== null) $result .= "?$query";
|
||||
if ($fragment !== null) $result .= "#$fragment";
|
||||
|
||||
// munge if necessary
|
||||
$munge = $config->get('URI', 'Munge');
|
||||
if (!empty($scheme_obj->browsable) && $munge !== null) {
|
||||
if ($authority !== null) {
|
||||
$result = str_replace('%s', rawurlencode($result), $munge);
|
||||
}
|
||||
// munge entire URI if necessary
|
||||
if (
|
||||
!is_null($uri->host) && // indicator for authority
|
||||
!empty($scheme_obj->browsable) &&
|
||||
!is_null($munge = $config->get('URI', 'Munge'))
|
||||
) {
|
||||
$result = str_replace('%s', rawurlencode($result), $munge);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a host against an array blacklist
|
||||
* @param $host Host to check
|
||||
* @param $config HTMLPurifier_Config instance
|
||||
* @param $context HTMLPurifier_Context instance
|
||||
* @return bool Is spam?
|
||||
*/
|
||||
function checkBlacklist($host, &$config, &$context) {
|
||||
$blacklist = $config->get('URI', 'HostBlacklist');
|
||||
if (!empty($blacklist)) {
|
||||
foreach($blacklist as $blacklisted_host_fragment) {
|
||||
if (strpos($host, $blacklisted_host_fragment) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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
|
||||
{
|
||||
|
@@ -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);
|
||||
|
@@ -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'));
|
||||
|
||||
|
@@ -5,6 +5,7 @@ require_once 'HTMLPurifier/ConfigSchema.php';
|
||||
// member variables
|
||||
require_once 'HTMLPurifier/HTMLDefinition.php';
|
||||
require_once 'HTMLPurifier/CSSDefinition.php';
|
||||
require_once 'HTMLPurifier/URIDefinition.php';
|
||||
require_once 'HTMLPurifier/Doctype.php';
|
||||
require_once 'HTMLPurifier/DefinitionCacheFactory.php';
|
||||
|
||||
@@ -41,7 +42,7 @@ class HTMLPurifier_Config
|
||||
/**
|
||||
* HTML Purifier's version
|
||||
*/
|
||||
var $version = '2.0.1';
|
||||
var $version = '2.1.2';
|
||||
|
||||
/**
|
||||
* Two-level associative array of configuration directives
|
||||
@@ -75,6 +76,11 @@ class HTMLPurifier_Config
|
||||
*/
|
||||
var $serials = array();
|
||||
|
||||
/**
|
||||
* Serial for entire configuration object
|
||||
*/
|
||||
var $serial;
|
||||
|
||||
/**
|
||||
* @param $definition HTMLPurifier_ConfigSchema that defines what directives
|
||||
* are allowed.
|
||||
@@ -98,7 +104,6 @@ class HTMLPurifier_Config
|
||||
$ret = HTMLPurifier_Config::createDefault();
|
||||
if (is_string($config)) $ret->loadIni($config);
|
||||
elseif (is_array($config)) $ret->loadArray($config);
|
||||
if (isset($revision)) $ret->revision = $revision;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
@@ -165,6 +170,17 @@ class HTMLPurifier_Config
|
||||
return $this->serials[$namespace];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a md5 signature for the entire configuration object
|
||||
* that uniquely identifies that particular configuration
|
||||
*/
|
||||
function getSerial() {
|
||||
if (empty($this->serial)) {
|
||||
$this->serial = md5(serialize($this->getAll()));
|
||||
}
|
||||
return $this->serial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all directives, organized by namespace
|
||||
*/
|
||||
@@ -295,6 +311,8 @@ class HTMLPurifier_Config
|
||||
$this->definitions[$type] = new HTMLPurifier_HTMLDefinition();
|
||||
} elseif ($type == 'CSS') {
|
||||
$this->definitions[$type] = new HTMLPurifier_CSSDefinition();
|
||||
} elseif ($type == 'URI') {
|
||||
$this->definitions[$type] = new HTMLPurifier_URIDefinition();
|
||||
} else {
|
||||
trigger_error("Definition of $type type not supported");
|
||||
$false = false;
|
||||
@@ -393,6 +411,26 @@ class HTMLPurifier_Config
|
||||
* @static
|
||||
*/
|
||||
static function loadArrayFromForm($array, $index, $allowed = true, $mq_fix = true) {
|
||||
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix);
|
||||
$config = HTMLPurifier_Config::create($ret);
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
|
||||
* @note Same parameters as loadArrayFromForm
|
||||
*/
|
||||
function mergeArrayFromForm($array, $index, $allowed = true, $mq_fix = true) {
|
||||
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix);
|
||||
$this->loadArray($ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an array from a form into something usable for the more
|
||||
* strict parts of HTMLPurifier_Config
|
||||
* @static
|
||||
*/
|
||||
static function prepareArrayFromForm($array, $index, $allowed = true, $mq_fix = true) {
|
||||
$array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
|
||||
$mq = get_magic_quotes_gpc() && $mq_fix;
|
||||
|
||||
@@ -409,9 +447,7 @@ class HTMLPurifier_Config
|
||||
$value = $mq ? stripslashes($array[$skey]) : $array[$skey];
|
||||
$ret[$ns][$directive] = $value;
|
||||
}
|
||||
|
||||
$config = HTMLPurifier_Config::create($ret);
|
||||
return $config;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -6,6 +6,8 @@ require_once 'HTMLPurifier/ConfigDef/Namespace.php';
|
||||
require_once 'HTMLPurifier/ConfigDef/Directive.php';
|
||||
require_once 'HTMLPurifier/ConfigDef/DirectiveAlias.php';
|
||||
|
||||
if (!defined('HTMLPURIFIER_SCHEMA_STRICT')) define('HTMLPURIFIER_SCHEMA_STRICT', false);
|
||||
|
||||
/**
|
||||
* Configuration definition, defines directives and their defaults.
|
||||
* @note If you update this, please update Printer_ConfigForm
|
||||
@@ -49,6 +51,8 @@ class HTMLPurifier_ConfigSchema {
|
||||
var $types = array(
|
||||
'string' => 'String',
|
||||
'istring' => 'Case-insensitive string',
|
||||
'text' => 'Text',
|
||||
'itext' => 'Case-insensitive text',
|
||||
'int' => 'Integer',
|
||||
'float' => 'Float',
|
||||
'bool' => 'Boolean',
|
||||
@@ -100,27 +104,30 @@ class HTMLPurifier_ConfigSchema {
|
||||
* HTMLPurifier_DirectiveDef::$type for allowed values
|
||||
* @param $description Description of directive for documentation
|
||||
*/
|
||||
static function define(
|
||||
$namespace, $name, $default, $type,
|
||||
$description
|
||||
) {
|
||||
static function define($namespace, $name, $default, $type, $description) {
|
||||
$def =& HTMLPurifier_ConfigSchema::instance();
|
||||
if (!isset($def->info[$namespace])) {
|
||||
trigger_error('Cannot define directive for undefined namespace',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!ctype_alnum($name)) {
|
||||
trigger_error('Directive name must be alphanumeric',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (empty($description)) {
|
||||
trigger_error('Description must be non-empty',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
|
||||
// basic sanity checks
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT) {
|
||||
if (!isset($def->info[$namespace])) {
|
||||
trigger_error('Cannot define directive for undefined namespace',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!ctype_alnum($name)) {
|
||||
trigger_error('Directive name must be alphanumeric',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (empty($description)) {
|
||||
trigger_error('Description must be non-empty',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($def->info[$namespace][$name])) {
|
||||
// already defined
|
||||
if (
|
||||
$def->info[$namespace][$name]->type !== $type ||
|
||||
$def->defaults[$namespace][$name] !== $default
|
||||
@@ -129,29 +136,35 @@ class HTMLPurifier_ConfigSchema {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// process modifiers
|
||||
// needs defining
|
||||
|
||||
// process modifiers (OPTIMIZE!)
|
||||
$type_values = explode('/', $type, 2);
|
||||
$type = $type_values[0];
|
||||
$modifier = isset($type_values[1]) ? $type_values[1] : false;
|
||||
$allow_null = ($modifier === 'null');
|
||||
|
||||
if (!isset($def->types[$type])) {
|
||||
trigger_error('Invalid type for configuration directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
$default = $def->validate($default, $type, $allow_null);
|
||||
if ($def->isError($default)) {
|
||||
trigger_error('Default value does not match directive type',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT) {
|
||||
if (!isset($def->types[$type])) {
|
||||
trigger_error('Invalid type for configuration directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
$default = $def->validate($default, $type, $allow_null);
|
||||
if ($def->isError($default)) {
|
||||
trigger_error('Default value does not match directive type',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$def->info[$namespace][$name] =
|
||||
new HTMLPurifier_ConfigDef_Directive();
|
||||
$def->info[$namespace][$name]->type = $type;
|
||||
$def->info[$namespace][$name]->allow_null = $allow_null;
|
||||
$def->defaults[$namespace][$name] = $default;
|
||||
}
|
||||
if (!HTMLPURIFIER_SCHEMA_STRICT) return;
|
||||
$backtrace = debug_backtrace();
|
||||
$file = $def->mungeFilename($backtrace[0]['file']);
|
||||
$line = $backtrace[0]['line'];
|
||||
@@ -166,19 +179,21 @@ class HTMLPurifier_ConfigSchema {
|
||||
*/
|
||||
static function defineNamespace($namespace, $description) {
|
||||
$def =& HTMLPurifier_ConfigSchema::instance();
|
||||
if (isset($def->info[$namespace])) {
|
||||
trigger_error('Cannot redefine namespace', E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!ctype_alnum($namespace)) {
|
||||
trigger_error('Namespace name must be alphanumeric',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (empty($description)) {
|
||||
trigger_error('Description must be non-empty',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT) {
|
||||
if (isset($def->info[$namespace])) {
|
||||
trigger_error('Cannot redefine namespace', E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!ctype_alnum($namespace)) {
|
||||
trigger_error('Namespace name must be alphanumeric',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (empty($description)) {
|
||||
trigger_error('Description must be non-empty',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$def->info[$namespace] = array();
|
||||
$def->info_namespace[$namespace] = new HTMLPurifier_ConfigDef_Namespace();
|
||||
@@ -199,23 +214,25 @@ class HTMLPurifier_ConfigSchema {
|
||||
*/
|
||||
static function defineValueAliases($namespace, $name, $aliases) {
|
||||
$def =& HTMLPurifier_ConfigSchema::instance();
|
||||
if (!isset($def->info[$namespace][$name])) {
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT && !isset($def->info[$namespace][$name])) {
|
||||
trigger_error('Cannot set value alias for non-existant directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
foreach ($aliases as $alias => $real) {
|
||||
if (!$def->info[$namespace][$name] !== true &&
|
||||
!isset($def->info[$namespace][$name]->allowed[$real])
|
||||
) {
|
||||
trigger_error('Cannot define alias to value that is not allowed',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (isset($def->info[$namespace][$name]->allowed[$alias])) {
|
||||
trigger_error('Cannot define alias over allowed value',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT) {
|
||||
if (!$def->info[$namespace][$name] !== true &&
|
||||
!isset($def->info[$namespace][$name]->allowed[$real])
|
||||
) {
|
||||
trigger_error('Cannot define alias to value that is not allowed',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (isset($def->info[$namespace][$name]->allowed[$alias])) {
|
||||
trigger_error('Cannot define alias over allowed value',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$def->info[$namespace][$name]->aliases[$alias] = $real;
|
||||
}
|
||||
@@ -230,14 +247,14 @@ class HTMLPurifier_ConfigSchema {
|
||||
*/
|
||||
static function defineAllowedValues($namespace, $name, $allowed_values) {
|
||||
$def =& HTMLPurifier_ConfigSchema::instance();
|
||||
if (!isset($def->info[$namespace][$name])) {
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT && !isset($def->info[$namespace][$name])) {
|
||||
trigger_error('Cannot define allowed values for undefined directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
$directive =& $def->info[$namespace][$name];
|
||||
$type = $directive->type;
|
||||
if ($type != 'string' && $type != 'istring') {
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT && $type != 'string' && $type != 'istring') {
|
||||
trigger_error('Cannot define allowed values for directive whose type is not string',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
@@ -248,8 +265,11 @@ class HTMLPurifier_ConfigSchema {
|
||||
foreach ($allowed_values as $value) {
|
||||
$directive->allowed[$value] = true;
|
||||
}
|
||||
if ($def->defaults[$namespace][$name] !== null &&
|
||||
!isset($directive->allowed[$def->defaults[$namespace][$name]])) {
|
||||
if (
|
||||
HTMLPURIFIER_SCHEMA_STRICT &&
|
||||
$def->defaults[$namespace][$name] !== null &&
|
||||
!isset($directive->allowed[$def->defaults[$namespace][$name]])
|
||||
) {
|
||||
trigger_error('Default value must be in allowed range of variables',
|
||||
E_USER_ERROR);
|
||||
$directive->allowed = true; // undo undo!
|
||||
@@ -267,30 +287,32 @@ class HTMLPurifier_ConfigSchema {
|
||||
*/
|
||||
static function defineAlias($namespace, $name, $new_namespace, $new_name) {
|
||||
$def =& HTMLPurifier_ConfigSchema::instance();
|
||||
if (!isset($def->info[$namespace])) {
|
||||
trigger_error('Cannot define directive alias in undefined namespace',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!ctype_alnum($name)) {
|
||||
trigger_error('Directive name must be alphanumeric',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (isset($def->info[$namespace][$name])) {
|
||||
trigger_error('Cannot define alias over directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!isset($def->info[$new_namespace][$new_name])) {
|
||||
trigger_error('Cannot define alias to undefined directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if ($def->info[$new_namespace][$new_name]->class == 'alias') {
|
||||
trigger_error('Cannot define alias to alias',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT) {
|
||||
if (!isset($def->info[$namespace])) {
|
||||
trigger_error('Cannot define directive alias in undefined namespace',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!ctype_alnum($name)) {
|
||||
trigger_error('Directive name must be alphanumeric',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (isset($def->info[$namespace][$name])) {
|
||||
trigger_error('Cannot define alias over directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!isset($def->info[$new_namespace][$new_name])) {
|
||||
trigger_error('Cannot define alias to undefined directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if ($def->info[$new_namespace][$new_name]->class == 'alias') {
|
||||
trigger_error('Cannot define alias to alias',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$def->info[$namespace][$name] =
|
||||
new HTMLPurifier_ConfigDef_DirectiveAlias(
|
||||
@@ -313,8 +335,10 @@ class HTMLPurifier_ConfigSchema {
|
||||
return $var;
|
||||
case 'istring':
|
||||
case 'string':
|
||||
case 'text': // no difference, just is longer/multiple line string
|
||||
case 'itext':
|
||||
if (!is_string($var)) break;
|
||||
if ($type === 'istring') $var = strtolower($var);
|
||||
if ($type === 'istring' || $type === 'itext') $var = strtolower($var);
|
||||
return $var;
|
||||
case 'int':
|
||||
if (is_string($var) && ctype_digit($var)) $var = (int) $var;
|
||||
@@ -345,9 +369,13 @@ class HTMLPurifier_ConfigSchema {
|
||||
// a single empty string item, but having an empty
|
||||
// array is more intuitive
|
||||
if ($var == '') return array();
|
||||
// simplistic string to array method that only works
|
||||
// for simple lists of tag names or alphanumeric characters
|
||||
$var = explode(',',$var);
|
||||
if (strpos($var, "\n") === false && strpos($var, "\r") === false) {
|
||||
// simplistic string to array method that only works
|
||||
// for simple lists of tag names or alphanumeric characters
|
||||
$var = explode(',',$var);
|
||||
} else {
|
||||
$var = preg_split('/(,|[\n\r]+)/', $var);
|
||||
}
|
||||
// remove spaces
|
||||
foreach ($var as $i => $j) $var[$i] = trim($j);
|
||||
if ($type === 'hash') {
|
||||
@@ -388,6 +416,7 @@ class HTMLPurifier_ConfigSchema {
|
||||
* Takes an absolute path and munges it into a more manageable relative path
|
||||
*/
|
||||
function mungeFilename($filename) {
|
||||
if (!HTMLPURIFIER_SCHEMA_STRICT) return $filename;
|
||||
$offset = strrpos($filename, 'HTMLPurifier');
|
||||
$filename = substr($filename, $offset);
|
||||
$filename = str_replace('\\', '/', $filename);
|
||||
|
@@ -5,6 +5,7 @@ require_once 'HTMLPurifier/ChildDef.php';
|
||||
require_once 'HTMLPurifier/ChildDef/Empty.php';
|
||||
require_once 'HTMLPurifier/ChildDef/Required.php';
|
||||
require_once 'HTMLPurifier/ChildDef/Optional.php';
|
||||
require_once 'HTMLPurifier/ChildDef/Custom.php';
|
||||
|
||||
// NOT UNIT TESTED!!!
|
||||
|
||||
|
@@ -99,7 +99,7 @@ class HTMLPurifier_DefinitionCache_Serializer extends
|
||||
*/
|
||||
function generateBaseDirectoryPath($config) {
|
||||
$base = $config->get('Cache', 'SerializerPath');
|
||||
$base = is_null($base) ? dirname(__FILE__) . '/Serializer' : $base;
|
||||
$base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;
|
||||
return $base;
|
||||
}
|
||||
|
||||
|
@@ -19,7 +19,7 @@ class HTMLPurifier_EntityLookup {
|
||||
*/
|
||||
function setup($file = false) {
|
||||
if (!$file) {
|
||||
$file = dirname(__FILE__) . '/EntityLookup/entities.ser';
|
||||
$file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser';
|
||||
}
|
||||
$this->table = unserialize(file_get_contents($file));
|
||||
}
|
||||
|
@@ -110,12 +110,13 @@ HTMLPurifier_ConfigSchema::define(
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'HTML', 'Allowed', null, 'string/null', '
|
||||
'HTML', 'Allowed', null, 'itext/null', '
|
||||
<p>
|
||||
This is a convenience directive that rolls the functionality of
|
||||
%HTML.AllowedElements and %HTML.AllowedAttributes into one directive.
|
||||
Specify elements and attributes that are allowed using:
|
||||
<code>element1[attr1|attr2],element2...</code>.
|
||||
<code>element1[attr1|attr2],element2...</code>. You can also use
|
||||
newlines instead of commas to separate elements.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Warning</strong>:
|
||||
@@ -329,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);
|
||||
}
|
||||
|
||||
@@ -339,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);
|
||||
}
|
||||
@@ -426,8 +427,9 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
|
||||
$elements = array();
|
||||
$attributes = array();
|
||||
|
||||
$chunks = explode(',', $list);
|
||||
$chunks = preg_split('/(,|[\n\r]+)/', $list);
|
||||
foreach ($chunks as $chunk) {
|
||||
if (empty($chunk)) continue;
|
||||
// remove TinyMCE element control characters
|
||||
if (!strpos($chunk, '[')) {
|
||||
$element = $chunk;
|
||||
|
47
library/HTMLPurifier/HTMLModule/Object.php
Normal file
47
library/HTMLPurifier/HTMLModule/Object.php
Normal 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'
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
28
library/HTMLPurifier/HTMLModule/Ruby.php
Normal file
28
library/HTMLPurifier/HTMLModule/Ruby.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/HTMLModule.php';
|
||||
|
||||
/**
|
||||
* XHTML 1.1 Ruby Annotation Module, defines elements that indicate
|
||||
* short runs of text alongside base text for annotation or pronounciation.
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
|
||||
var $name = 'Ruby';
|
||||
|
||||
function HTMLPurifier_HTMLModule_Ruby() {
|
||||
$this->addElement('ruby', true, 'Inline',
|
||||
'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))',
|
||||
'Common');
|
||||
$this->addElement('rbc', true, false, 'Required: rb', 'Common');
|
||||
$this->addElement('rtc', true, false, 'Required: rt', 'Common');
|
||||
$rb =& $this->addElement('rb', true, false, 'Inline', 'Common');
|
||||
$rb->excludes = array('ruby' => true);
|
||||
$rt =& $this->addElement('rt', true, false, 'Inline', 'Common', array('rbspan' => 'Number'));
|
||||
$rt->excludes = array('ruby' => true);
|
||||
$this->addElement('rp', true, false, 'Optional: #PCDATA', 'Common');
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -28,6 +28,8 @@ require_once 'HTMLPurifier/HTMLModule/Target.php';
|
||||
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';
|
||||
@@ -171,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');
|
||||
@@ -215,8 +217,8 @@ class HTMLPurifier_HTMLModuleManager
|
||||
|
||||
$this->doctypes->register(
|
||||
'XHTML 1.1', true,
|
||||
array_merge($common, $xml),
|
||||
array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary'), // Tidy_XHTML1_1
|
||||
array_merge($common, $xml, array('Ruby')),
|
||||
array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_XHTMLStrict'), // Tidy_XHTML1_1
|
||||
array(),
|
||||
'-//W3C//DTD XHTML 1.1//EN',
|
||||
'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
|
||||
|
@@ -8,6 +8,11 @@
|
||||
class HTMLPurifier_Injector
|
||||
{
|
||||
|
||||
/**
|
||||
* Advisory name of injector, this is for friendly error messages
|
||||
*/
|
||||
var $name;
|
||||
|
||||
/**
|
||||
* Amount of tokens the injector needs to skip + 1. Because
|
||||
* the decrement is the first thing that happens, this needs to
|
||||
@@ -40,16 +45,37 @@ class HTMLPurifier_Injector
|
||||
var $inputIndex;
|
||||
|
||||
/**
|
||||
* Prepares the injector by giving it the config and context objects,
|
||||
* so that important variables can be extracted and not passed via
|
||||
* parameter constantly. Remember: always instantiate a new injector
|
||||
* when handling a set of HTML.
|
||||
* Array of elements and attributes this injector creates and therefore
|
||||
* need to be allowed by the definition. Takes form of
|
||||
* array('element' => array('attr', 'attr2'), 'element2')
|
||||
*/
|
||||
var $needed = array();
|
||||
|
||||
/**
|
||||
* Prepares the injector by giving it the config and context objects:
|
||||
* this allows references to important variables to be made within
|
||||
* the injector. This function also checks if the HTML environment
|
||||
* will work with the Injector: if p tags are not allowed, the
|
||||
* Auto-Paragraphing injector should not be enabled.
|
||||
* @param $config Instance of HTMLPurifier_Config
|
||||
* @param $context Instance of HTMLPurifier_Context
|
||||
* @return Boolean false if success, string of missing needed element/attribute if failure
|
||||
*/
|
||||
function prepare($config, &$context) {
|
||||
$this->htmlDefinition = $config->getHTMLDefinition();
|
||||
// perform $needed checks
|
||||
foreach ($this->needed as $element => $attributes) {
|
||||
if (is_int($element)) $element = $attributes;
|
||||
if (!isset($this->htmlDefinition->info[$element])) return $element;
|
||||
if (!is_array($attributes)) continue;
|
||||
foreach ($attributes as $name) {
|
||||
if (!isset($this->htmlDefinition->info[$element]->attr[$name])) return "$element.$name";
|
||||
}
|
||||
}
|
||||
$this->currentNesting =& $context->get('CurrentNesting');
|
||||
$this->inputTokens =& $context->get('InputTokens');
|
||||
$this->inputIndex =& $context->get('InputIndex');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,12 +100,12 @@ class HTMLPurifier_Injector
|
||||
/**
|
||||
* Handler that is called when a text token is processed
|
||||
*/
|
||||
function handleText(&$token, $config, &$context) {}
|
||||
function handleText(&$token) {}
|
||||
|
||||
/**
|
||||
* Handler that is called when a start token is processed
|
||||
* Handler that is called when a start or empty token is processed
|
||||
*/
|
||||
function handleStart(&$token, $config, &$context) {}
|
||||
function handleElement(&$token) {}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -15,6 +15,11 @@ HTMLPurifier_ConfigSchema::define(
|
||||
block elements in nodes that allow paragraph tags</li>
|
||||
<li>There are double newlines in paragraph tags</li>
|
||||
</ul>
|
||||
<p>
|
||||
<code>p</code> tags must be allowed for this directive to take effect.
|
||||
We do not use <code>br</code> tags for paragraphing, as that is
|
||||
semantically incorrect.
|
||||
</p>
|
||||
<p>
|
||||
This directive has been available since 2.0.1.
|
||||
</p>
|
||||
@@ -27,13 +32,16 @@ HTMLPurifier_ConfigSchema::define(
|
||||
class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
|
||||
{
|
||||
|
||||
var $name = 'AutoParagraph';
|
||||
var $needed = array('p');
|
||||
|
||||
function _pStart() {
|
||||
$par = new HTMLPurifier_Token_Start('p');
|
||||
$par->armor['MakeWellFormed_TagClosedError'] = true;
|
||||
return $par;
|
||||
}
|
||||
|
||||
function handleText(&$token, $config, &$context) {
|
||||
function handleText(&$token) {
|
||||
$text = $token->data;
|
||||
if (empty($this->currentNesting)) {
|
||||
if (!$this->allowsElement('p')) return;
|
||||
@@ -79,7 +87,7 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
|
||||
|
||||
}
|
||||
|
||||
function handleStart(&$token, $config, &$context) {
|
||||
function handleElement(&$token) {
|
||||
// check if we're inside a tag already
|
||||
if (!empty($this->currentNesting)) {
|
||||
if ($this->allowsElement('p')) {
|
||||
@@ -88,11 +96,19 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
|
||||
// this token is already paragraph, abort
|
||||
if ($token->name == 'p') return;
|
||||
|
||||
// check if this token is adjacent to the parent
|
||||
if ($this->inputTokens[$this->inputIndex - 1]->type != 'start') {
|
||||
// this token is a block level, abort
|
||||
if (!$this->_isInline($token)) return;
|
||||
|
||||
// check if this token is adjacent to the parent token
|
||||
$prev = $this->inputTokens[$this->inputIndex - 1];
|
||||
if ($prev->type != 'start') {
|
||||
// not adjacent, we can abort early
|
||||
// add lead paragraph tag if our token is inline
|
||||
if ($this->_isInline($token)) {
|
||||
// and the previous tag was an end paragraph
|
||||
if (
|
||||
$prev->name == 'p' && $prev->type == 'end' &&
|
||||
$this->_isInline($token)
|
||||
) {
|
||||
$token = array($this->_pStart(), $token);
|
||||
}
|
||||
return;
|
||||
@@ -105,8 +121,8 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
|
||||
$ok = false;
|
||||
// maintain a mini-nesting counter, this lets us bail out
|
||||
// early if possible
|
||||
$j = 2; // current nesting, is two due to parent and this start
|
||||
for ($i = $this->inputIndex + 1; isset($this->inputTokens[$i]); $i++) {
|
||||
$j = 1; // current nesting, one is due to parent (we recalculate current token)
|
||||
for ($i = $this->inputIndex; isset($this->inputTokens[$i]); $i++) {
|
||||
if ($this->inputTokens[$i]->type == 'start') $j++;
|
||||
if ($this->inputTokens[$i]->type == 'end') $j--;
|
||||
if ($this->inputTokens[$i]->type == 'text') {
|
||||
@@ -150,7 +166,14 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
|
||||
$needs_start = false;
|
||||
$needs_end = false;
|
||||
|
||||
for ($i = 0, $c = count($raw_paragraphs); $i < $c; $i++) {
|
||||
$c = count($raw_paragraphs);
|
||||
if ($c == 1) {
|
||||
// there were no double-newlines, abort quickly
|
||||
$result[] = new HTMLPurifier_Token_Text($data);
|
||||
return;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $c; $i++) {
|
||||
$par = $raw_paragraphs[$i];
|
||||
if (trim($par) !== '') {
|
||||
$paragraphs[] = $par;
|
||||
|
@@ -6,7 +6,8 @@ HTMLPurifier_ConfigSchema::define(
|
||||
'AutoFormat', 'Linkify', false, 'bool', '
|
||||
<p>
|
||||
This directive turns on linkification, auto-linking http, ftp and
|
||||
https URLs. This directive has been available since 2.0.1.
|
||||
https URLs. <code>a</code> tags with the <code>href</code> attribute
|
||||
must be allowed. This directive has been available since 2.0.1.
|
||||
</p>
|
||||
');
|
||||
|
||||
@@ -16,7 +17,10 @@ HTMLPurifier_ConfigSchema::define(
|
||||
class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector
|
||||
{
|
||||
|
||||
function handleText(&$token, $config, &$context) {
|
||||
var $name = 'Linkify';
|
||||
var $needed = array('a' => array('href'));
|
||||
|
||||
function handleText(&$token) {
|
||||
if (!$this->allowsElement('a')) return;
|
||||
|
||||
if (strpos($token->data, '://') === false) {
|
||||
|
@@ -6,8 +6,9 @@ HTMLPurifier_ConfigSchema::define(
|
||||
'AutoFormat', 'PurifierLinkify', false, 'bool', '
|
||||
<p>
|
||||
Internal auto-formatter that converts configuration directives in
|
||||
syntax <a>%Namespace.Directive</a> to links. This directive has been available
|
||||
since 2.0.1.
|
||||
syntax <a>%Namespace.Directive</a> to links. <code>a</code> tags
|
||||
with the <code>href</code> attribute must be allowed.
|
||||
This directive has been available since 2.0.1.
|
||||
</p>
|
||||
');
|
||||
|
||||
@@ -27,14 +28,16 @@ HTMLPurifier_ConfigSchema::define(
|
||||
class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector
|
||||
{
|
||||
|
||||
var $name = 'PurifierLinkify';
|
||||
var $docURL;
|
||||
var $needed = array('a' => array('href'));
|
||||
|
||||
function prepare($config, &$context) {
|
||||
parent::prepare($config, $context);
|
||||
$this->docURL = $config->get('AutoFormatParam', 'PurifierLinkifyDocURL');
|
||||
return parent::prepare($config, $context);
|
||||
}
|
||||
|
||||
function handleText(&$token, $config, &$context) {
|
||||
function handleText(&$token) {
|
||||
if (!$this->allowsElement('a')) return;
|
||||
if (strpos($token->data, '%') === false) return;
|
||||
|
||||
|
@@ -28,7 +28,7 @@ $messages = array(
|
||||
'Strategy_RemoveForeignElements: Foreign element to text' => 'Unrecognized $CurrentToken.Serialized tag converted to text',
|
||||
'Strategy_RemoveForeignElements: Foreign element removed' => 'Unrecognized $CurrentToken.Serialized tag removed',
|
||||
'Strategy_RemoveForeignElements: Comment removed' => 'Comment containing "$CurrentToken.Data" removed',
|
||||
'Strategy_RemoveForeignElements: Script removed' => 'Script removed',
|
||||
'Strategy_RemoveForeignElements: Foreign meta element removed' => 'Unrecognized $CurrentToken.Serialized meta tag and all descendants removed',
|
||||
'Strategy_RemoveForeignElements: Token removed to end' => 'Tags and text starting from $1 element where removed to end',
|
||||
|
||||
'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed',
|
||||
|
@@ -82,7 +82,7 @@ class HTMLPurifier_LanguageFactory
|
||||
*/
|
||||
function setup() {
|
||||
$this->validator = new HTMLPurifier_AttrDef_Lang();
|
||||
$this->dir = dirname(__FILE__);
|
||||
$this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -66,6 +66,16 @@ HTMLPurifier_ConfigSchema::define(
|
||||
</p>
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Core', 'AggressivelyFixLt', false, 'bool', '
|
||||
This directive enables aggressive pre-filter fixes HTML Purifier can
|
||||
perform in order to ensure that open angled-brackets do not get killed
|
||||
during parsing stage. Enabling this will result in two preg_replace_callback
|
||||
calls and one preg_replace call for every bit of HTML passed through here.
|
||||
It is not necessary and will have no effect for PHP 4.
|
||||
This directive has been available since 2.1.0.
|
||||
');
|
||||
|
||||
/**
|
||||
* Forgivingly lexes HTML (SGML-style) markup into tokens.
|
||||
*
|
||||
@@ -179,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);
|
||||
}
|
||||
|
@@ -42,15 +42,18 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
|
||||
|
||||
$html = $this->normalize($html, $config, $context);
|
||||
|
||||
// attempt to armor stray angled brackets that cannot possibly
|
||||
// form tags and thus are probably being used as emoticons
|
||||
if ($config->get('Core', 'AggressivelyFixLt')) {
|
||||
$char = '[^a-z!\/]';
|
||||
$comment = "/<!--(.*?)(-->|\z)/is";
|
||||
$html = preg_replace_callback($comment, array('HTMLPurifier_Lexer_DOMLex', 'callbackArmorCommentEntities'), $html);
|
||||
$html = preg_replace("/<($char)/i", '<\\1', $html);
|
||||
$html = preg_replace_callback($comment, array('HTMLPurifier_Lexer_DOMLex', 'callbackUndoCommentSubst'), $html); // fix comments
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -151,5 +154,41 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
|
||||
*/
|
||||
public function muteErrorHandler($errno, $errstr) {}
|
||||
|
||||
/**
|
||||
* Callback function for undoing escaping of stray angled brackets
|
||||
* in comments
|
||||
*/
|
||||
static public function callbackUndoCommentSubst($matches) {
|
||||
return '<!--' . strtr($matches[1], array('&'=>'&','<'=>'<')) . $matches[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function that entity-izes ampersands in comments so that
|
||||
* callbackUndoCommentSubst doesn't clobber them
|
||||
*/
|
||||
static public function callbackArmorCommentEntities($matches) {
|
||||
return '<!--' . str_replace('&', '&', $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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -150,6 +150,14 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
|
||||
// We are in tag and it is well formed
|
||||
// Grab the internals of the tag
|
||||
$strlen_segment = $position_next_gt - $cursor;
|
||||
|
||||
if ($strlen_segment < 1) {
|
||||
// there's nothing to process!
|
||||
$token = new HTMLPurifier_Token_Text('<');
|
||||
$cursor++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$segment = substr($html, $cursor, $strlen_segment);
|
||||
|
||||
// Check if it's a comment
|
||||
@@ -204,7 +212,8 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
|
||||
// Check leading character is alnum, if not, we may
|
||||
// have accidently grabbed an emoticon. Translate into
|
||||
// text and go our merry way
|
||||
if (!ctype_alnum($segment[0])) {
|
||||
if (!ctype_alpha($segment[0])) {
|
||||
// XML: $segment[0] !== '_' && $segment[0] !== ':'
|
||||
if ($e) $e->send(E_NOTICE, 'Lexer: Unescaped lt');
|
||||
$token = new
|
||||
HTMLPurifier_Token_Text(
|
||||
@@ -228,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);
|
||||
@@ -371,6 +380,7 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
|
||||
$value = $quoted_value;
|
||||
}
|
||||
}
|
||||
if ($value === false) $value = '';
|
||||
return array($key => $value);
|
||||
}
|
||||
|
||||
@@ -385,7 +395,6 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
|
||||
|
||||
// infinite loop protection
|
||||
$loops = 0;
|
||||
|
||||
while(true) {
|
||||
|
||||
// infinite loop protection
|
||||
@@ -399,7 +408,6 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
|
||||
}
|
||||
|
||||
$cursor += ($value = strspn($string, $this->_whitespace, $cursor));
|
||||
|
||||
// grab the key
|
||||
|
||||
$key_begin = $cursor; //we're currently at the start of the key
|
||||
@@ -435,6 +443,11 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
|
||||
$cursor++;
|
||||
$cursor += strspn($string, $this->_whitespace, $cursor);
|
||||
|
||||
if ($cursor === false) {
|
||||
$array[$key] = '';
|
||||
break;
|
||||
}
|
||||
|
||||
// we might be in front of a quote right now
|
||||
|
||||
$char = @$string[$cursor];
|
||||
@@ -452,7 +465,14 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
|
||||
$value_end = $cursor;
|
||||
}
|
||||
|
||||
// we reached a premature end
|
||||
if ($cursor === false) {
|
||||
$cursor = $size;
|
||||
$value_end = $cursor;
|
||||
}
|
||||
|
||||
$value = substr($string, $value_begin, $value_end - $value_begin);
|
||||
if ($value === false) $value = '';
|
||||
$array[$key] = $this->parseData($value);
|
||||
$cursor++;
|
||||
|
||||
|
3886
library/HTMLPurifier/Lexer/PH5P.php
Normal file
3886
library/HTMLPurifier/Lexer/PH5P.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
|
||||
.hp-config {}
|
||||
|
||||
.hp-config tbody th {text-align:right;}
|
||||
.hp-config tbody th {text-align:right; padding-right:0.5em;}
|
||||
.hp-config thead, .hp-config .namespace {background:#3C578C; color:#FFF;}
|
||||
.hp-config .namespace th {text-align:center;}
|
||||
.hp-config .verbose {display:none;}
|
||||
|
@@ -23,18 +23,55 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
|
||||
*/
|
||||
var $name;
|
||||
|
||||
/**
|
||||
* Whether or not to compress directive names, clipping them off
|
||||
* after a certain amount of letters. False to disable or integer letters
|
||||
* before clipping.
|
||||
* @protected
|
||||
*/
|
||||
var $compress = false;
|
||||
|
||||
/**
|
||||
* @param $name Form element name for directives to be stuffed into
|
||||
* @param $doc_url String documentation URL, will have fragment tagged on
|
||||
* @param $compress Integer max length before compressing a directive name, set to false to turn off
|
||||
*/
|
||||
function HTMLPurifier_Printer_ConfigForm($name, $doc_url = null) {
|
||||
function HTMLPurifier_Printer_ConfigForm(
|
||||
$name, $doc_url = null, $compress = false
|
||||
) {
|
||||
parent::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
|
||||
*/
|
||||
function setTextareaDimensions($cols = null, $rows = null) {
|
||||
if ($cols) $this->fields['default']->cols = $cols;
|
||||
if ($rows) $this->fields['default']->rows = $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 it is not accessible by webserver
|
||||
*/
|
||||
function getJavaScript() {
|
||||
return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML output for a configuration form
|
||||
* @param $config Configuration object of current form state
|
||||
@@ -63,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;
|
||||
@@ -98,11 +135,12 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
|
||||
$ret .= $this->start('a', array('href' => $url));
|
||||
}
|
||||
$attr = array('for' => "{$this->name}:$ns.$directive");
|
||||
|
||||
// crop directive name if it's too long
|
||||
if (strlen($directive) < 14) {
|
||||
if (!$this->compress || (strlen($directive) < $this->compress)) {
|
||||
$directive_disp = $directive;
|
||||
} else {
|
||||
$directive_disp = substr($directive, 0, 12) . '...';
|
||||
$directive_disp = substr($directive, 0, $this->compress - 2) . '...';
|
||||
$attr['title'] = $directive;
|
||||
}
|
||||
|
||||
@@ -176,6 +214,8 @@ class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer
|
||||
* Swiss-army knife configuration form field printer
|
||||
*/
|
||||
class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer {
|
||||
var $cols = 18;
|
||||
var $rows = 5;
|
||||
function render($ns, $directive, $value, $name, $config) {
|
||||
$this->prepareGenerator($config);
|
||||
// this should probably be split up a little
|
||||
@@ -190,12 +230,12 @@ class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer {
|
||||
$value[] = $val;
|
||||
}
|
||||
case 'list':
|
||||
$value = implode(',', $value);
|
||||
$value = implode(PHP_EOL, $value);
|
||||
break;
|
||||
case 'hash':
|
||||
$nvalue = '';
|
||||
foreach ($value as $i => $v) {
|
||||
$nvalue .= "$i:$v,";
|
||||
$nvalue .= "$i:$v" . PHP_EOL;
|
||||
}
|
||||
$value = $nvalue;
|
||||
break;
|
||||
@@ -220,6 +260,15 @@ class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer {
|
||||
$ret .= $this->element('option', $val, $attr);
|
||||
}
|
||||
$ret .= $this->end('select');
|
||||
} elseif (
|
||||
$def->type == 'text' || $def->type == 'itext' ||
|
||||
$def->type == 'list' || $def->type == 'hash' || $def->type == 'lookup'
|
||||
) {
|
||||
$attr['cols'] = $this->cols;
|
||||
$attr['rows'] = $this->rows;
|
||||
$ret .= $this->start('textarea', $attr);
|
||||
$ret .= $this->text($value);
|
||||
$ret .= $this->end('textarea');
|
||||
} else {
|
||||
$attr['value'] = $value;
|
||||
$attr['type'] = 'text';
|
||||
|
@@ -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();
|
||||
|
@@ -67,7 +67,8 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
|
||||
unset($injectors['Custom']); // special case
|
||||
foreach ($injectors as $injector => $b) {
|
||||
$injector = "HTMLPurifier_Injector_$injector";
|
||||
if ($b) $this->injectors[] = new $injector;
|
||||
if (!$b) continue;
|
||||
$this->injectors[] = new $injector;
|
||||
}
|
||||
foreach ($custom_injectors as $injector) {
|
||||
if (is_string($injector)) {
|
||||
@@ -87,7 +88,11 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
|
||||
// give the injectors references to the definition and context
|
||||
// variables for performance reasons
|
||||
foreach ($this->injectors as $i => $x) {
|
||||
$this->injectors[$i]->prepare($config, $context);
|
||||
$error = $this->injectors[$i]->prepare($config, $context);
|
||||
if (!$error) continue;
|
||||
list($injector) = array_splice($this->injectors, $i, 1);
|
||||
$name = $injector->name;
|
||||
trigger_error("Cannot enable $name injector because $error is not allowed", E_USER_WARNING);
|
||||
}
|
||||
|
||||
// -- end INJECTOR --
|
||||
@@ -109,7 +114,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
|
||||
if ($token->type === 'text') {
|
||||
// injector handler code; duplicated for performance reasons
|
||||
foreach ($this->injectors as $i => $x) {
|
||||
if (!$x->skip) $x->handleText($token, $config, $context);
|
||||
if (!$x->skip) $x->handleText($token);
|
||||
if (is_array($token)) {
|
||||
$this->currentInjector = $i;
|
||||
break;
|
||||
@@ -122,26 +127,24 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
|
||||
|
||||
$info = $definition->info[$token->name]->child;
|
||||
|
||||
// quick checks:
|
||||
// test if it claims to be a start tag but is empty
|
||||
// quick tag checks: anything that's *not* an end tag
|
||||
$ok = false;
|
||||
if ($info->type == 'empty' && $token->type == 'start') {
|
||||
$result[] = new HTMLPurifier_Token_Empty($token->name, $token->attr);
|
||||
continue;
|
||||
}
|
||||
// test if it claims to be empty but really is a start tag
|
||||
if ($info->type != 'empty' && $token->type == 'empty' ) {
|
||||
$result[] = new HTMLPurifier_Token_Start($token->name, $token->attr);
|
||||
$result[] = new HTMLPurifier_Token_End($token->name);
|
||||
continue;
|
||||
}
|
||||
// automatically insert empty tags
|
||||
if ($token->type == 'empty') {
|
||||
$result[] = $token;
|
||||
continue;
|
||||
}
|
||||
|
||||
// start tags have precedence, so they get passed through...
|
||||
if ($token->type == 'start') {
|
||||
// test if it claims to be a start tag but is empty
|
||||
$token = new HTMLPurifier_Token_Empty($token->name, $token->attr);
|
||||
$ok = true;
|
||||
} elseif ($info->type != 'empty' && $token->type == 'empty' ) {
|
||||
// claims to be empty but really is a start tag
|
||||
$token = array(
|
||||
new HTMLPurifier_Token_Start($token->name, $token->attr),
|
||||
new HTMLPurifier_Token_End($token->name)
|
||||
);
|
||||
$ok = true;
|
||||
} elseif ($token->type == 'empty') {
|
||||
// real empty token
|
||||
$ok = true;
|
||||
} elseif ($token->type == 'start') {
|
||||
// start tag
|
||||
|
||||
// ...unless they also have to close their parent
|
||||
if (!empty($this->currentNesting)) {
|
||||
@@ -163,16 +166,18 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
|
||||
|
||||
$this->currentNesting[] = $parent; // undo the pop
|
||||
}
|
||||
|
||||
// injector handler code; duplicated for performance reasons
|
||||
$ok = true;
|
||||
}
|
||||
|
||||
// injector handler code; duplicated for performance reasons
|
||||
if ($ok) {
|
||||
foreach ($this->injectors as $i => $x) {
|
||||
if (!$x->skip) $x->handleStart($token, $config, $context);
|
||||
if (!$x->skip) $x->handleElement($token);
|
||||
if (is_array($token)) {
|
||||
$this->currentInjector = $i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->processToken($token, $config, $context);
|
||||
continue;
|
||||
}
|
||||
@@ -280,9 +285,11 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
|
||||
array_splice($this->inputTokens, $this->inputIndex--, 1, $token);
|
||||
|
||||
// adjust the injector skips based on the array substitution
|
||||
$offset = count($token) + 1;
|
||||
for ($i = 0; $i <= $this->currentInjector; $i++) {
|
||||
$this->injectors[$i]->skip += $offset;
|
||||
if ($this->injectors) {
|
||||
$offset = count($token) + 1;
|
||||
for ($i = 0; $i <= $this->currentInjector; $i++) {
|
||||
$this->injectors[$i]->skip += $offset;
|
||||
}
|
||||
}
|
||||
} elseif ($token) {
|
||||
// regular case
|
||||
|
@@ -8,19 +8,38 @@ require_once 'HTMLPurifier/TagTransform.php';
|
||||
require_once 'HTMLPurifier/AttrValidator.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Core', 'RemoveInvalidImg', true, 'bool',
|
||||
'This directive enables pre-emptive URI checking in <code>img</code> '.
|
||||
'tags, as the attribute validation strategy is not authorized to '.
|
||||
'remove elements from the document. This directive has been available '.
|
||||
'since 1.3.0, revert to pre-1.3.0 behavior by setting to false.'
|
||||
'Core', 'RemoveInvalidImg', true, 'bool', '
|
||||
<p>
|
||||
This directive enables pre-emptive URI checking in <code>img</code>
|
||||
tags, as the attribute validation strategy is not authorized to
|
||||
remove elements from the document. This directive has been available
|
||||
since 1.3.0, revert to pre-1.3.0 behavior by setting to false.
|
||||
</p>
|
||||
'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Core', 'RemoveScriptContents', true, 'bool', '
|
||||
'Core', 'RemoveScriptContents', null, 'bool/null', '
|
||||
<p>
|
||||
This directive enables HTML Purifier to remove not only script tags
|
||||
but all of their contents. This directive has been available since 2.0.0,
|
||||
revert to pre-2.0.0 behavior by setting to false.
|
||||
but all of their contents. This directive has been deprecated since 2.1.0,
|
||||
and when not set the value of %Core.HiddenElements will take
|
||||
precedence. This directive has been available since 2.0.0, and can be used to
|
||||
revert to pre-2.0.0 behavior by setting it to false.
|
||||
</p>
|
||||
'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Core', 'HiddenElements', array('script' => true, 'style' => true), 'lookup', '
|
||||
<p>
|
||||
This directive is a lookup array of elements which should have their
|
||||
contents removed when they are not allowed by the HTML definition.
|
||||
For example, the contents of a <code>script</code> tag are not
|
||||
normally shown in a document, so if script tags are to be removed,
|
||||
their contents should be removed to. This is opposed to a <code>b</code>
|
||||
tag, which defines some presentational changes but does not hide its
|
||||
contents.
|
||||
</p>
|
||||
'
|
||||
);
|
||||
@@ -43,7 +62,16 @@ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
|
||||
|
||||
$escape_invalid_tags = $config->get('Core', 'EscapeInvalidTags');
|
||||
$remove_invalid_img = $config->get('Core', 'RemoveInvalidImg');
|
||||
|
||||
$remove_script_contents = $config->get('Core', 'RemoveScriptContents');
|
||||
$hidden_elements = $config->get('Core', 'HiddenElements');
|
||||
|
||||
// remove script contents compatibility
|
||||
if ($remove_script_contents === true) {
|
||||
$hidden_elements['script'] = true;
|
||||
} elseif ($remove_script_contents === false && isset($hidden_elements['script'])) {
|
||||
unset($hidden_elements['script']);
|
||||
}
|
||||
|
||||
$attr_validator = new HTMLPurifier_AttrValidator();
|
||||
|
||||
@@ -107,7 +135,7 @@ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
|
||||
}
|
||||
|
||||
// CAN BE GENERICIZED
|
||||
if ($token->name == 'script' && $token->type == 'start') {
|
||||
if (isset($hidden_elements[$token->name]) && $token->type == 'start') {
|
||||
$textify_comments = $token->name;
|
||||
} elseif ($token->name === $textify_comments && $token->type == 'end') {
|
||||
$textify_comments = false;
|
||||
@@ -122,7 +150,7 @@ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
|
||||
} else {
|
||||
// check if we need to destroy all of the tag's children
|
||||
// CAN BE GENERICIZED
|
||||
if ($token->name == 'script' && $remove_script_contents) {
|
||||
if (isset($hidden_elements[$token->name])) {
|
||||
if ($token->type == 'start') {
|
||||
$remove_until = $token->name;
|
||||
} elseif ($token->type == 'empty') {
|
||||
@@ -130,7 +158,7 @@ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
|
||||
} else {
|
||||
$remove_until = false;
|
||||
}
|
||||
if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Script removed');
|
||||
if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');
|
||||
} else {
|
||||
if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
|
||||
}
|
||||
|
@@ -46,6 +46,7 @@ class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy
|
||||
}
|
||||
|
||||
$context->destroy('IDAccumulator');
|
||||
$context->destroy('CurrentToken');
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
119
library/HTMLPurifier/URI.php
Normal file
119
library/HTMLPurifier/URI.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/URIParser.php';
|
||||
require_once 'HTMLPurifier/URIFilter.php';
|
||||
|
||||
/**
|
||||
* HTML Purifier's internal representation of a URI
|
||||
*/
|
||||
class HTMLPurifier_URI
|
||||
{
|
||||
|
||||
var $scheme, $userinfo, $host, $port, $path, $query, $fragment;
|
||||
|
||||
/**
|
||||
* @note Automatically normalizes scheme and port
|
||||
*/
|
||||
function HTMLPurifier_URI($scheme, $userinfo, $host, $port, $path, $query, $fragment) {
|
||||
$this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
|
||||
$this->userinfo = $userinfo;
|
||||
$this->host = $host;
|
||||
$this->port = is_null($port) ? $port : (int) $port;
|
||||
$this->path = $path;
|
||||
$this->query = $query;
|
||||
$this->fragment = $fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a scheme object corresponding to the URI's scheme/default
|
||||
* @param $config Instance of HTMLPurifier_Config
|
||||
* @param $context Instance of HTMLPurifier_Context
|
||||
* @return Scheme object appropriate for validating this URI
|
||||
*/
|
||||
function getSchemeObj($config, &$context) {
|
||||
$registry =& HTMLPurifier_URISchemeRegistry::instance();
|
||||
if ($this->scheme !== null) {
|
||||
$scheme_obj = $registry->getScheme($this->scheme, $config, $context);
|
||||
if (!$scheme_obj) return false; // invalid scheme, clean it out
|
||||
} else {
|
||||
// no scheme: retrieve the default one
|
||||
$def = $config->getDefinition('URI');
|
||||
$scheme_obj = $registry->getScheme($def->defaultScheme, $config, $context);
|
||||
if (!$scheme_obj) {
|
||||
// something funky happened to the default scheme object
|
||||
trigger_error(
|
||||
'Default scheme object "' . $def->defaultScheme . '" was not readable',
|
||||
E_USER_WARNING
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return $scheme_obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic validation method applicable for all schemes
|
||||
* @param $config Instance of HTMLPurifier_Config
|
||||
* @param $context Instance of HTMLPurifier_Context
|
||||
* @return True if validation/filtering succeeds, false if failure
|
||||
*/
|
||||
function validate($config, &$context) {
|
||||
|
||||
// validate host
|
||||
if (!is_null($this->host)) {
|
||||
$host_def = new HTMLPurifier_AttrDef_URI_Host();
|
||||
$this->host = $host_def->validate($this->host, $config, $context);
|
||||
if ($this->host === false) $this->host = null;
|
||||
}
|
||||
|
||||
// validate port
|
||||
if (!is_null($this->port)) {
|
||||
if ($this->port < 1 || $this->port > 65535) $this->port = null;
|
||||
}
|
||||
|
||||
// query and fragment are quite simple in terms of definition:
|
||||
// *( pchar / "/" / "?" ), so define their validation routines
|
||||
// when we start fixing percent encoding
|
||||
|
||||
// path gets to be validated against a hodge-podge of rules depending
|
||||
// on the status of authority and scheme, but it's not that important,
|
||||
// esp. since it won't be applicable to everyone
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert URI back to string
|
||||
* @return String URI appropriate for output
|
||||
*/
|
||||
function toString() {
|
||||
// reconstruct authority
|
||||
$authority = null;
|
||||
if (!is_null($this->host)) {
|
||||
$authority = '';
|
||||
if(!is_null($this->userinfo)) $authority .= $this->userinfo . '@';
|
||||
$authority .= $this->host;
|
||||
if(!is_null($this->port)) $authority .= ':' . $this->port;
|
||||
}
|
||||
|
||||
// reconstruct the result
|
||||
$result = '';
|
||||
if (!is_null($this->scheme)) $result .= $this->scheme . ':';
|
||||
if (!is_null($authority)) $result .= '//' . $authority;
|
||||
$result .= $this->path;
|
||||
if (!is_null($this->query)) $result .= '?' . $this->query;
|
||||
if (!is_null($this->fragment)) $result .= '#' . $this->fragment;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the URI object
|
||||
*/
|
||||
function copy() {
|
||||
return unserialize(serialize($this));
|
||||
}
|
||||
|
||||
}
|
||||
|
145
library/HTMLPurifier/URIDefinition.php
Normal file
145
library/HTMLPurifier/URIDefinition.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/Definition.php';
|
||||
require_once 'HTMLPurifier/URIFilter.php';
|
||||
require_once 'HTMLPurifier/URIParser.php';
|
||||
|
||||
require_once 'HTMLPurifier/URIFilter/DisableExternal.php';
|
||||
require_once 'HTMLPurifier/URIFilter/DisableExternalResources.php';
|
||||
require_once 'HTMLPurifier/URIFilter/HostBlacklist.php';
|
||||
require_once 'HTMLPurifier/URIFilter/MakeAbsolute.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'DefinitionID', null, 'string/null', '
|
||||
<p>
|
||||
Unique identifier for a custom-built URI definition. If you want
|
||||
to add custom URIFilters, you must specify this value.
|
||||
This directive has been available since 2.1.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'DefinitionRev', 1, 'int', '
|
||||
<p>
|
||||
Revision identifier for your custom definition. See
|
||||
%HTML.DefinitionRev for details. This directive has been available
|
||||
since 2.1.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
// informative URI directives
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'DefaultScheme', 'http', 'string', '
|
||||
<p>
|
||||
Defines through what scheme the output will be served, in order to
|
||||
select the proper object validator when no scheme information is present.
|
||||
</p>
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'Host', null, 'string/null', '
|
||||
<p>
|
||||
Defines the domain name of the server, so we can determine whether or
|
||||
an absolute URI is from your website or not. Not strictly necessary,
|
||||
as users should be using relative URIs to reference resources on your
|
||||
website. It will, however, let you use absolute URIs to link to
|
||||
subdomains of the domain you post here: i.e. example.com will allow
|
||||
sub.example.com. However, higher up domains will still be excluded:
|
||||
if you set %URI.Host to sub.example.com, example.com will be blocked.
|
||||
<strong>Note:</strong> This directive overrides %URI.Base because
|
||||
a given page may be on a sub-domain, but you wish HTML Purifier to be
|
||||
more relaxed and allow some of the parent domains too.
|
||||
This directive has been available since 1.2.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'Base', null, 'string/null', '
|
||||
<p>
|
||||
The base URI is the URI of the document this purified HTML will be
|
||||
inserted into. This information is important if HTML Purifier needs
|
||||
to calculate absolute URIs from relative URIs, such as when %URI.MakeAbsolute
|
||||
is on. You may use a non-absolute URI for this value, but behavior
|
||||
may vary (%URI.MakeAbsolute deals nicely with both absolute and
|
||||
relative paths, but forwards-compatibility is not guaranteed).
|
||||
<strong>Warning:</strong> If set, the scheme on this URI
|
||||
overrides the one specified by %URI.DefaultScheme. This directive has
|
||||
been available since 2.1.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
|
||||
{
|
||||
|
||||
var $type = 'URI';
|
||||
var $filters = array();
|
||||
var $registeredFilters = array();
|
||||
|
||||
/**
|
||||
* HTMLPurifier_URI object of the base specified at %URI.Base
|
||||
*/
|
||||
var $base;
|
||||
|
||||
/**
|
||||
* String host to consider "home" base
|
||||
*/
|
||||
var $host;
|
||||
|
||||
/**
|
||||
* Name of default scheme based on %URI.DefaultScheme and %URI.Base
|
||||
*/
|
||||
var $defaultScheme;
|
||||
|
||||
function HTMLPurifier_URIDefinition() {
|
||||
$this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal());
|
||||
$this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());
|
||||
$this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());
|
||||
$this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());
|
||||
}
|
||||
|
||||
function registerFilter($filter) {
|
||||
$this->registeredFilters[$filter->name] = $filter;
|
||||
}
|
||||
|
||||
function addFilter($filter, $config) {
|
||||
$filter->prepare($config);
|
||||
$this->filters[$filter->name] = $filter;
|
||||
}
|
||||
|
||||
function doSetup($config) {
|
||||
$this->setupMemberVariables($config);
|
||||
$this->setupFilters($config);
|
||||
}
|
||||
|
||||
function setupFilters($config) {
|
||||
foreach ($this->registeredFilters as $name => $filter) {
|
||||
$conf = $config->get('URI', $name);
|
||||
if ($conf !== false && $conf !== null) {
|
||||
$this->addFilter($filter, $config);
|
||||
}
|
||||
}
|
||||
unset($this->registeredFilters);
|
||||
}
|
||||
|
||||
function setupMemberVariables($config) {
|
||||
$this->host = $config->get('URI', 'Host');
|
||||
$base_uri = $config->get('URI', 'Base');
|
||||
if (!is_null($base_uri)) {
|
||||
$parser = new HTMLPurifier_URIParser();
|
||||
$this->base = $parser->parse($base_uri);
|
||||
$this->defaultScheme = $this->base->scheme;
|
||||
if (is_null($this->host)) $this->host = $this->base->host;
|
||||
}
|
||||
if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI', 'DefaultScheme');
|
||||
}
|
||||
|
||||
function filter(&$uri, $config, &$context) {
|
||||
foreach ($this->filters as $name => $x) {
|
||||
$result = $this->filters[$name]->filter($uri, $config, $context);
|
||||
if (!$result) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
24
library/HTMLPurifier/URIFilter.php
Normal file
24
library/HTMLPurifier/URIFilter.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chainable filters for custom URI processing
|
||||
*/
|
||||
class HTMLPurifier_URIFilter
|
||||
{
|
||||
var $name;
|
||||
|
||||
/**
|
||||
* Performs initialization for the filter
|
||||
*/
|
||||
function prepare($config) {}
|
||||
|
||||
/**
|
||||
* Filter a URI object
|
||||
* @param &$uri Reference to URI object
|
||||
* @param $config Instance of HTMLPurifier_Config
|
||||
* @param &$context Instance of HTMLPurifier_Context
|
||||
*/
|
||||
function filter(&$uri, $config, &$context) {
|
||||
trigger_error('Cannot call abstract function', E_USER_ERROR);
|
||||
}
|
||||
}
|
34
library/HTMLPurifier/URIFilter/DisableExternal.php
Normal file
34
library/HTMLPurifier/URIFilter/DisableExternal.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/URIFilter.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'DisableExternal', false, 'bool',
|
||||
'Disables links to external websites. This is a highly effective '.
|
||||
'anti-spam and anti-pagerank-leech measure, but comes at a hefty price: no'.
|
||||
'links or images outside of your domain will be allowed. Non-linkified '.
|
||||
'URIs will still be preserved. If you want to be able to link to '.
|
||||
'subdomains or use absolute URIs, specify %URI.Host for your website. '.
|
||||
'This directive has been available since 1.2.0.'
|
||||
);
|
||||
|
||||
class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter
|
||||
{
|
||||
var $name = 'DisableExternal';
|
||||
var $ourHostParts = false;
|
||||
function prepare($config) {
|
||||
$our_host = $config->get('URI', 'Host');
|
||||
if ($our_host !== null) $this->ourHostParts = array_reverse(explode('.', $our_host));
|
||||
}
|
||||
function filter(&$uri, $config, &$context) {
|
||||
if (is_null($uri->host)) return true;
|
||||
if ($this->ourHostParts === false) return false;
|
||||
$host_parts = array_reverse(explode('.', $uri->host));
|
||||
foreach ($this->ourHostParts as $i => $x) {
|
||||
if (!isset($host_parts[$i])) return false;
|
||||
if ($host_parts[$i] != $this->ourHostParts[$i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
26
library/HTMLPurifier/URIFilter/DisableExternalResources.php
Normal file
26
library/HTMLPurifier/URIFilter/DisableExternalResources.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/URIFilter/DisableExternal.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'DisableExternalResources', false, 'bool',
|
||||
'Disables the embedding of external resources, preventing users from '.
|
||||
'embedding things like images from other hosts. This prevents '.
|
||||
'access tracking (good for email viewers), bandwidth leeching, '.
|
||||
'cross-site request forging, goatse.cx posting, and '.
|
||||
'other nasties, but also results in '.
|
||||
'a loss of end-user functionality (they can\'t directly post a pic '.
|
||||
'they posted from Flickr anymore). Use it if you don\'t have a '.
|
||||
'robust user-content moderation team. This directive has been '.
|
||||
'available since 1.3.0.'
|
||||
);
|
||||
|
||||
class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal
|
||||
{
|
||||
var $name = 'DisableExternalResources';
|
||||
function filter(&$uri, $config, &$context) {
|
||||
if (!$context->get('EmbeddedURI', true)) return true;
|
||||
return parent::filter($uri, $config, $context);
|
||||
}
|
||||
}
|
||||
|
28
library/HTMLPurifier/URIFilter/HostBlacklist.php
Normal file
28
library/HTMLPurifier/URIFilter/HostBlacklist.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/URIFilter.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'HostBlacklist', array(), 'list',
|
||||
'List of strings that are forbidden in the host of any URI. Use it to '.
|
||||
'kill domain names of spam, etc. Note that it will catch anything in '.
|
||||
'the domain, so <tt>moo.com</tt> will catch <tt>moo.com.example.com</tt>. '.
|
||||
'This directive has been available since 1.3.0.'
|
||||
);
|
||||
|
||||
class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
|
||||
{
|
||||
var $name = 'HostBlacklist';
|
||||
var $blacklist = array();
|
||||
function prepare($config) {
|
||||
$this->blacklist = $config->get('URI', 'HostBlacklist');
|
||||
}
|
||||
function filter(&$uri, $config, &$context) {
|
||||
foreach($this->blacklist as $blacklisted_host_fragment) {
|
||||
if (strpos($uri->host, $blacklisted_host_fragment) !== false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
115
library/HTMLPurifier/URIFilter/MakeAbsolute.php
Normal file
115
library/HTMLPurifier/URIFilter/MakeAbsolute.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
// does not support network paths
|
||||
|
||||
require_once 'HTMLPurifier/URIFilter.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'MakeAbsolute', false, 'bool', '
|
||||
<p>
|
||||
Converts all URIs into absolute forms. This is useful when the HTML
|
||||
being filtered assumes a specific base path, but will actually be
|
||||
viewed in a different context (and setting an alternate base URI is
|
||||
not possible). %URI.Base must be set for this directive to work.
|
||||
This directive has been available since 2.1.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter
|
||||
{
|
||||
var $name = 'MakeAbsolute';
|
||||
var $base;
|
||||
var $basePathStack = array();
|
||||
function prepare($config) {
|
||||
$def = $config->getDefinition('URI');
|
||||
$this->base = $def->base;
|
||||
if (is_null($this->base)) {
|
||||
trigger_error('URI.MakeAbsolute is being ignored due to lack of value for URI.Base configuration', E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
$this->base->fragment = null; // fragment is invalid for base URI
|
||||
$stack = explode('/', $this->base->path);
|
||||
array_pop($stack); // discard last segment
|
||||
$stack = $this->_collapseStack($stack); // do pre-parsing
|
||||
$this->basePathStack = $stack;
|
||||
}
|
||||
function filter(&$uri, $config, &$context) {
|
||||
if (is_null($this->base)) return true; // abort early
|
||||
if (
|
||||
$uri->path === '' && is_null($uri->scheme) &&
|
||||
is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)
|
||||
) {
|
||||
// reference to current document
|
||||
$uri = $this->base->copy();
|
||||
return true;
|
||||
}
|
||||
if (!is_null($uri->scheme)) {
|
||||
// absolute URI already: don't change
|
||||
if (!is_null($uri->host)) return true;
|
||||
$scheme_obj = $uri->getSchemeObj($config, $context);
|
||||
if (!$scheme_obj->hierarchical) {
|
||||
// non-hierarchal URI with explicit scheme, don't change
|
||||
return true;
|
||||
}
|
||||
// special case: had a scheme but always is hierarchical and had no authority
|
||||
}
|
||||
if (!is_null($uri->host)) {
|
||||
// network path, don't bother
|
||||
return true;
|
||||
}
|
||||
if ($uri->path === '') {
|
||||
$uri->path = $this->base->path;
|
||||
}elseif ($uri->path[0] !== '/') {
|
||||
// relative path, needs more complicated processing
|
||||
$stack = explode('/', $uri->path);
|
||||
$new_stack = array_merge($this->basePathStack, $stack);
|
||||
$new_stack = $this->_collapseStack($new_stack);
|
||||
$uri->path = implode('/', $new_stack);
|
||||
}
|
||||
// re-combine
|
||||
$uri->scheme = $this->base->scheme;
|
||||
if (is_null($uri->userinfo)) $uri->userinfo = $this->base->userinfo;
|
||||
if (is_null($uri->host)) $uri->host = $this->base->host;
|
||||
if (is_null($uri->port)) $uri->port = $this->base->port;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve dots and double-dots in a path stack
|
||||
* @private
|
||||
*/
|
||||
function _collapseStack($stack) {
|
||||
$result = array();
|
||||
for ($i = 0; isset($stack[$i]); $i++) {
|
||||
$is_folder = false;
|
||||
// absorb an internally duplicated slash
|
||||
if ($stack[$i] == '' && $i && isset($stack[$i+1])) continue;
|
||||
if ($stack[$i] == '..') {
|
||||
if (!empty($result)) {
|
||||
$segment = array_pop($result);
|
||||
if ($segment === '' && empty($result)) {
|
||||
// error case: attempted to back out too far:
|
||||
// restore the leading slash
|
||||
$result[] = '';
|
||||
} elseif ($segment === '..') {
|
||||
$result[] = '..'; // cannot remove .. with ..
|
||||
}
|
||||
} else {
|
||||
// relative path, preserve the double-dots
|
||||
$result[] = '..';
|
||||
}
|
||||
$is_folder = true;
|
||||
continue;
|
||||
}
|
||||
if ($stack[$i] == '.') {
|
||||
// silently absorb
|
||||
$is_folder = true;
|
||||
continue;
|
||||
}
|
||||
$result[] = $stack[$i];
|
||||
}
|
||||
if ($is_folder) $result[] = '';
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
62
library/HTMLPurifier/URIParser.php
Normal file
62
library/HTMLPurifier/URIParser.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/URI.php';
|
||||
|
||||
/**
|
||||
* Parses a URI into the components and fragment identifier as specified
|
||||
* by RFC 2396.
|
||||
* @todo Replace regexps with a native PHP parser
|
||||
*/
|
||||
class HTMLPurifier_URIParser
|
||||
{
|
||||
|
||||
/**
|
||||
* Parses a URI
|
||||
* @param $uri string URI to parse
|
||||
* @return HTMLPurifier_URI representation of URI
|
||||
*/
|
||||
function parse($uri) {
|
||||
$r_URI = '!'.
|
||||
'(([^:/?#<>\'"]+):)?'. // 2. Scheme
|
||||
'(//([^/?#<>\'"]*))?'. // 4. Authority
|
||||
'([^?#<>\'"]*)'. // 5. Path
|
||||
'(\?([^#<>\'"]*))?'. // 7. Query
|
||||
'(#([^<>\'"]*))?'. // 8. Fragment
|
||||
'!';
|
||||
|
||||
$matches = array();
|
||||
$result = preg_match($r_URI, $uri, $matches);
|
||||
|
||||
if (!$result) return false; // *really* invalid URI
|
||||
|
||||
// seperate out parts
|
||||
$scheme = !empty($matches[1]) ? $matches[2] : null;
|
||||
$authority = !empty($matches[3]) ? $matches[4] : null;
|
||||
$path = $matches[5]; // always present, can be empty
|
||||
$query = !empty($matches[6]) ? $matches[7] : null;
|
||||
$fragment = !empty($matches[8]) ? $matches[9] : null;
|
||||
|
||||
// further parse authority
|
||||
if ($authority !== null) {
|
||||
// ridiculously inefficient: it's a stacked regex!
|
||||
$HEXDIG = '[A-Fa-f0-9]';
|
||||
$unreserved = 'A-Za-z0-9-._~'; // make sure you wrap with []
|
||||
$sub_delims = '!$&\'()'; // needs []
|
||||
$pct_encoded = "%$HEXDIG$HEXDIG";
|
||||
$r_userinfo = "(?:[$unreserved$sub_delims:]|$pct_encoded)*";
|
||||
$r_authority = "/^(($r_userinfo)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";
|
||||
$matches = array();
|
||||
preg_match($r_authority, $authority, $matches);
|
||||
$userinfo = !empty($matches[1]) ? $matches[2] : null;
|
||||
$host = !empty($matches[3]) ? $matches[3] : '';
|
||||
$port = !empty($matches[4]) ? (int) $matches[5] : null;
|
||||
} else {
|
||||
$port = $host = $userinfo = null;
|
||||
}
|
||||
|
||||
return new HTMLPurifier_URI(
|
||||
$scheme, $userinfo, $host, $port, $path, $query, $fragment);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -19,24 +19,24 @@ class HTMLPurifier_URIScheme
|
||||
*/
|
||||
var $browsable = false;
|
||||
|
||||
/**
|
||||
* Whether or not the URI always uses <hier_part>, resolves edge cases
|
||||
* with making relative URIs absolute
|
||||
*/
|
||||
var $hierarchical = false;
|
||||
|
||||
/**
|
||||
* Validates the components of a URI
|
||||
* @note This implementation should be called by children if they define
|
||||
* a default port, as it does port processing.
|
||||
* @note Fragment is omitted as that is scheme independent
|
||||
* @param $userinfo User info found before at sign in authority
|
||||
* @param $host Hostname in authority
|
||||
* @param $port Port found after colon in authority
|
||||
* @param $path Path of URI
|
||||
* @param $query Query of URI, found after question mark
|
||||
* @param $uri Instance of HTMLPurifier_URI
|
||||
* @param $config HTMLPurifier_Config object
|
||||
* @param $context HTMLPurifier_Context object
|
||||
* @return Bool success or failure
|
||||
*/
|
||||
function validateComponents(
|
||||
$userinfo, $host, $port, $path, $query, $config, &$context
|
||||
) {
|
||||
if ($this->default_port == $port) $port = null;
|
||||
return array($userinfo, $host, $port, $path, $query);
|
||||
function validate(&$uri, $config, &$context) {
|
||||
if ($this->default_port == $uri->port) $uri->port = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,35 +9,35 @@ class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme {
|
||||
|
||||
var $default_port = 21;
|
||||
var $browsable = true; // usually
|
||||
var $hierarchical = true;
|
||||
|
||||
function validateComponents(
|
||||
$userinfo, $host, $port, $path, $query, $config, &$context
|
||||
) {
|
||||
list($userinfo, $host, $port, $path, $query) =
|
||||
parent::validateComponents(
|
||||
$userinfo, $host, $port, $path, $query, $config, $context );
|
||||
$semicolon_pos = strrpos($path, ';'); // reverse
|
||||
function validate(&$uri, $config, &$context) {
|
||||
parent::validate($uri, $config, $context);
|
||||
$uri->query = null;
|
||||
|
||||
// typecode check
|
||||
$semicolon_pos = strrpos($uri->path, ';'); // reverse
|
||||
if ($semicolon_pos !== false) {
|
||||
// typecode check
|
||||
$type = substr($path, $semicolon_pos + 1); // no semicolon
|
||||
$path = substr($path, 0, $semicolon_pos);
|
||||
$type = substr($uri->path, $semicolon_pos + 1); // no semicolon
|
||||
$uri->path = substr($uri->path, 0, $semicolon_pos);
|
||||
$type_ret = '';
|
||||
if (strpos($type, '=') !== false) {
|
||||
// figure out whether or not the declaration is correct
|
||||
list($key, $typecode) = explode('=', $type, 2);
|
||||
if ($key !== 'type') {
|
||||
// invalid key, tack it back on encoded
|
||||
$path .= '%3B' . $type;
|
||||
$uri->path .= '%3B' . $type;
|
||||
} elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') {
|
||||
$type_ret = ";type=$typecode";
|
||||
}
|
||||
} else {
|
||||
$path .= '%3B' . $type;
|
||||
$uri->path .= '%3B' . $type;
|
||||
}
|
||||
$path = str_replace(';', '%3B', $path);
|
||||
$path .= $type_ret;
|
||||
$uri->path = str_replace(';', '%3B', $uri->path);
|
||||
$uri->path .= $type_ret;
|
||||
}
|
||||
return array($userinfo, $host, $port, $path, null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,14 +9,12 @@ class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme {
|
||||
|
||||
var $default_port = 80;
|
||||
var $browsable = true;
|
||||
var $hierarchical = true;
|
||||
|
||||
function validateComponents(
|
||||
$userinfo, $host, $port, $path, $query, $config, &$context
|
||||
) {
|
||||
list($userinfo, $host, $port, $path, $query) =
|
||||
parent::validateComponents(
|
||||
$userinfo, $host, $port, $path, $query, $config, $context );
|
||||
return array(null, $host, $port, $path, $query);
|
||||
function validate(&$uri, $config, &$context) {
|
||||
parent::validate($uri, $config, $context);
|
||||
$uri->userinfo = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -15,14 +15,13 @@ class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme {
|
||||
|
||||
var $browsable = false;
|
||||
|
||||
function validateComponents(
|
||||
$userinfo, $host, $port, $path, $query, $config, &$context
|
||||
) {
|
||||
list($userinfo, $host, $port, $path, $query) =
|
||||
parent::validateComponents(
|
||||
$userinfo, $host, $port, $path, $query, $config, $context );
|
||||
function validate(&$uri, $config, &$context) {
|
||||
parent::validate($uri, $config, $context);
|
||||
$uri->userinfo = null;
|
||||
$uri->host = null;
|
||||
$uri->port = null;
|
||||
// we need to validate path against RFC 2368's addr-spec
|
||||
return array(null, null, null, $path, $query);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,14 +9,14 @@ class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme {
|
||||
|
||||
var $browsable = false;
|
||||
|
||||
function validateComponents(
|
||||
$userinfo, $host, $port, $path, $query, $config, &$context
|
||||
) {
|
||||
list($userinfo, $host, $port, $path, $query) =
|
||||
parent::validateComponents(
|
||||
$userinfo, $host, $port, $path, $query, $config, $context );
|
||||
function validate(&$uri, $config, &$context) {
|
||||
parent::validate($uri, $config, $context);
|
||||
$uri->userinfo = null;
|
||||
$uri->host = null;
|
||||
$uri->port = null;
|
||||
$uri->query = null;
|
||||
// typecode check needed on path
|
||||
return array(null, null, null, $path, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -10,13 +10,11 @@ class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme {
|
||||
var $default_port = 119;
|
||||
var $browsable = false;
|
||||
|
||||
function validateComponents(
|
||||
$userinfo, $host, $port, $path, $query, $config, &$context
|
||||
) {
|
||||
list($userinfo, $host, $port, $path, $query) =
|
||||
parent::validateComponents(
|
||||
$userinfo, $host, $port, $path, $query, $config, $context );
|
||||
return array(null, $host, $port, $path, null);
|
||||
function validate(&$uri, $config, &$context) {
|
||||
parent::validate($uri, $config, $context);
|
||||
$uri->userinfo = null;
|
||||
$uri->query = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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,11 +79,8 @@ class HTMLPurifier_URISchemeRegistry
|
||||
}
|
||||
|
||||
if (isset($this->schemes[$scheme])) return $this->schemes[$scheme];
|
||||
if (empty($this->_dir)) $this->_dir = dirname(__FILE__) . '/URIScheme/';
|
||||
|
||||
if (!isset($allowed_schemes[$scheme])) return $null;
|
||||
|
||||
@include_once $this->_dir . $scheme . '.php';
|
||||
$class = 'HTMLPurifier_URIScheme_' . $scheme;
|
||||
if (!class_exists($class)) return $null;
|
||||
$this->schemes[$scheme] = new $class();
|
||||
@@ -91,7 +88,7 @@ class HTMLPurifier_URISchemeRegistry
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
45
maintenance/PH5P.patch
Normal 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;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
107
maintenance/compat-function-file-put-contents.php
Normal file
107
maintenance/compat-function-file-put-contents.php
Normal 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);
|
||||
}
|
||||
}
|
@@ -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,6 +110,16 @@ function make_file_standalone($file) {
|
||||
*/
|
||||
function replace_includes_callback($matches) {
|
||||
$file = $matches[1];
|
||||
$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 '';
|
||||
$GLOBALS['loaded'][$file] = true;
|
||||
create_blank($file);
|
||||
@@ -180,19 +133,30 @@ echo 'Creating full file...';
|
||||
$contents = replace_includes(file_get_contents('HTMLPurifier.php'));
|
||||
$contents = str_replace(
|
||||
"define('HTMLPURIFIER_PREFIX', dirname(__FILE__));",
|
||||
"define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone');",
|
||||
"define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone');
|
||||
set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path());",
|
||||
$contents
|
||||
);
|
||||
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');
|
||||
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;
|
||||
|
56
plugins/phorum/config.default.php
Normal file
56
plugins/phorum/config.default.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
if(!defined("PHORUM")) exit;
|
||||
|
||||
// default HTML Purifier configuration settings
|
||||
$config->set('HTML', 'Allowed',
|
||||
// alphabetically sorted
|
||||
'a[href|title]
|
||||
abbr[title]
|
||||
acronym[title]
|
||||
b
|
||||
blockquote[cite]
|
||||
br
|
||||
caption
|
||||
cite
|
||||
code
|
||||
dd
|
||||
del
|
||||
dfn
|
||||
div
|
||||
dl
|
||||
dt
|
||||
em
|
||||
i
|
||||
img[src|alt|title|class]
|
||||
ins
|
||||
kbd
|
||||
li
|
||||
ol
|
||||
p
|
||||
pre
|
||||
s
|
||||
strike
|
||||
strong
|
||||
sub
|
||||
sup
|
||||
table
|
||||
tbody
|
||||
td
|
||||
tfoot
|
||||
th
|
||||
thead
|
||||
tr
|
||||
tt
|
||||
u
|
||||
ul
|
||||
var');
|
||||
$config->set('AutoFormat', 'AutoParagraph', true);
|
||||
$config->set('AutoFormat', 'Linkify', true);
|
||||
$config->set('HTML', 'Doctype', 'XHTML 1.0 Transitional');
|
||||
$config->set('Core', 'AggressivelyFixLt', true);
|
||||
$config->set('Core', 'Encoding', $GLOBALS['PHORUM']['DATA']['CHARSET']); // we'll change this eventually
|
||||
if (strtolower($GLOBALS['PHORUM']['DATA']['CHARSET']) !== 'utf-8') {
|
||||
$config->set('Core', 'EscapeNonASCIICharacters', true);
|
||||
}
|
||||
|
272
plugins/phorum/htmlpurifier.php
Normal file
272
plugins/phorum/htmlpurifier.php
Normal file
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* HTML Purifier Phorum Mod. Filter your HTML the Standards-Compliant Way!
|
||||
*
|
||||
* This Phorum mod enables users to post raw HTML into Phorum. But never
|
||||
* fear: with the help of HTML Purifier, this HTML will be beat into
|
||||
* de-XSSed and standards-compliant form, safe for general consumption.
|
||||
* It is not recommended, but possible to run this mod in parallel
|
||||
* with other formatters (in short, please DISABLE the BBcode mod).
|
||||
*
|
||||
* For help migrating from your previous markup language to pure HTML
|
||||
* please check the migrate.bbcode.php file.
|
||||
*
|
||||
* If you'd like to use this with a WYSIWYG editor, make sure that
|
||||
* editor sets $PHORUM['mod_htmlpurifier']['wysiwyg'] to true. Otherwise,
|
||||
* administrators who need to edit other people's comments may be at
|
||||
* risk for some nasty attacks.
|
||||
*
|
||||
* Tested with Phorum 5.1.22. This module will almost definitely need
|
||||
* to be upgraded when Phorum 6 rolls around.
|
||||
*/
|
||||
|
||||
// Note: Cache data is base64 encoded because Phorum insists on flinging
|
||||
// to the user and expecting it to come back unharmed, newlines and
|
||||
// all, which ain't happening. It's slower, it takes up more space, but
|
||||
// at least it won't get mutilated
|
||||
|
||||
/**
|
||||
* Purifies a data array
|
||||
*/
|
||||
function phorum_htmlpurifier_format($data)
|
||||
{
|
||||
$PHORUM = $GLOBALS["PHORUM"];
|
||||
|
||||
$purifier =& HTMLPurifier::getInstance();
|
||||
$cache_serial = $PHORUM['mod_htmlpurifier']['body_cache_serial'];
|
||||
|
||||
foreach($data as $message_id => $message){
|
||||
if(isset($message['body'])) {
|
||||
|
||||
if ($message_id) {
|
||||
// we're dealing with a real message, not a fake, so
|
||||
// there a number of shortcuts that can be taken
|
||||
|
||||
if (isset($message['meta']['htmlpurifier_light'])) {
|
||||
// format hook was called outside of Phorum's normal
|
||||
// functions, do the abridged purification
|
||||
$data[$message_id]['body'] = $purifier->purify($message['body']);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($PHORUM['args']['purge'])) {
|
||||
// purge the cache, must be below the following if
|
||||
unset($message['meta']['body_cache']);
|
||||
}
|
||||
|
||||
if (
|
||||
isset($message['meta']['body_cache']) &&
|
||||
isset($message['meta']['body_cache_serial']) &&
|
||||
$message['meta']['body_cache_serial'] == $cache_serial
|
||||
) {
|
||||
// cached version is present, bail out early
|
||||
$data[$message_id]['body'] = base64_decode($message['meta']['body_cache']);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// migration might edit this array, that's why it's defined
|
||||
// so early
|
||||
$updated_message = array();
|
||||
|
||||
// create the $body variable
|
||||
if (
|
||||
$message_id && // message must be real to migrate
|
||||
!isset($message['meta']['body_cache_serial'])
|
||||
) {
|
||||
// perform migration
|
||||
$fake_data = array();
|
||||
list($signature, $edit_message) = phorum_htmlpurifier_remove_sig_and_editmessage($message);
|
||||
$fake_data[$message_id] = $message;
|
||||
$fake_data = phorum_htmlpurifier_migrate($fake_data);
|
||||
$body = $fake_data[$message_id]['body'];
|
||||
$body = str_replace("<phorum break>", '', $body);
|
||||
$updated_message['body'] = $body; // save it in
|
||||
$body .= $signature . $edit_message; // add it back in
|
||||
} else {
|
||||
// reverse Phorum's pre-processing
|
||||
$body = $message['body'];
|
||||
// order is important
|
||||
$body = str_replace("<phorum break>\n", "\n", $body);
|
||||
$body = str_replace(array('<','>','&'), array('<','>','&'), $body);
|
||||
if (!$message_id && defined('PHORUM_CONTROL_CENTER')) {
|
||||
// we're in control.php, so it was double-escaped
|
||||
$body = str_replace(array('<','>','&', '"'), array('<','>','&','"'), $body);
|
||||
}
|
||||
}
|
||||
|
||||
$body = $purifier->purify($body);
|
||||
|
||||
// dynamically update the cache (MUST BE DONE HERE!)
|
||||
// this is inefficient because it's one db call per
|
||||
// cache miss, but once the cache is in place things are
|
||||
// a lot zippier.
|
||||
|
||||
if ($message_id) { // make sure it's not a fake id
|
||||
$updated_message['meta'] = $message['meta'];
|
||||
$updated_message['meta']['body_cache'] = base64_encode($body);
|
||||
$updated_message['meta']['body_cache_serial'] = $cache_serial;
|
||||
phorum_db_update_message($message_id, $updated_message);
|
||||
}
|
||||
|
||||
// must not get overloaded until after we cache it, otherwise
|
||||
// we'll inadvertently change the original text
|
||||
$data[$message_id]['body'] = $body;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// This is fragile code, copied from read.php:359. It will break if
|
||||
// that is changed
|
||||
|
||||
/**
|
||||
* Generates a signature based on a message array
|
||||
*/
|
||||
function phorum_htmlpurifier_generate_sig($row) {
|
||||
$phorum_sig = '';
|
||||
if(isset($row["user"]["signature"])
|
||||
&& isset($row['meta']['show_signature']) && $row['meta']['show_signature']==1){
|
||||
$phorum_sig=trim($row["user"]["signature"]);
|
||||
if(!empty($phorum_sig)){
|
||||
$phorum_sig="\n\n$phorum_sig";
|
||||
}
|
||||
}
|
||||
return $phorum_sig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an edit message based on a message array
|
||||
*/
|
||||
function phorum_htmlpurifier_generate_editmessage($row) {
|
||||
$PHORUM = $GLOBALS['PHORUM'];
|
||||
$editmessage = '';
|
||||
if(isset($row['meta']['edit_count']) && $row['meta']['edit_count'] > 0) {
|
||||
$editmessage = str_replace ("%count%", $row['meta']['edit_count'], $PHORUM["DATA"]["LANG"]["EditedMessage"]);
|
||||
$editmessage = str_replace ("%lastedit%", phorum_date($PHORUM["short_date"],$row['meta']['edit_date']), $editmessage);
|
||||
$editmessage = str_replace ("%lastuser%", $row['meta']['edit_username'], $editmessage);
|
||||
$editmessage="\n\n\n\n$editmessage";
|
||||
}
|
||||
return $editmessage;
|
||||
}
|
||||
|
||||
// End fragile code
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Removes the signature and edit message from a message
|
||||
* @param $row Message passed by reference
|
||||
*/
|
||||
function phorum_htmlpurifier_remove_sig_and_editmessage(&$row) {
|
||||
// attempt to remove the Phorum's pre-processing:
|
||||
// we must not process the signature or editmessage
|
||||
$signature = phorum_htmlpurifier_generate_sig($row);
|
||||
$editmessage = phorum_htmlpurifier_generate_editmessage($row);
|
||||
$row['body'] = strtr($row['body'], array($signature => '', $editmessage => ''));
|
||||
return array($signature, $editmessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that data is fully HTML and not from migration, invalidate
|
||||
* previous caches
|
||||
* @note This function used to generate the actual cache entries, but
|
||||
* since there's data missing that must be deferred to the first read
|
||||
*/
|
||||
function phorum_htmlpurifier_posting($message) {
|
||||
$PHORUM = $GLOBALS["PHORUM"];
|
||||
unset($message['meta']['body_cache']); // invalidate the cache
|
||||
$message['meta']['body_cache_serial'] = $PHORUM['mod_htmlpurifier']['body_cache_serial'];
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload quoting mechanism to prevent default, mail-style quote from happening
|
||||
*/
|
||||
function phorum_htmlpurifier_quote($array) {
|
||||
$PHORUM = $GLOBALS["PHORUM"];
|
||||
$purifier =& HTMLPurifier::getInstance();
|
||||
$text = $purifier->purify($array[1]);
|
||||
return "<blockquote cite=\"$array[0]\">\n$text\n</blockquote>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that our format hook is processed last. Also, loads the library.
|
||||
* @credits <http://secretsauce.phorum.org/snippets/make_bbcode_last_formatter.php.txt>
|
||||
*/
|
||||
function phorum_htmlpurifier_common() {
|
||||
|
||||
require_once(dirname(__FILE__).'/htmlpurifier/HTMLPurifier.auto.php');
|
||||
require(dirname(__FILE__).'/init-config.php');
|
||||
|
||||
$config = phorum_htmlpurifier_get_config();
|
||||
HTMLPurifier::getInstance($config);
|
||||
|
||||
// increment revision.txt if you want to invalidate the cache
|
||||
$GLOBALS['PHORUM']['mod_htmlpurifier']['body_cache_serial'] = $config->getSerial();
|
||||
|
||||
// load migration
|
||||
if (file_exists(dirname(__FILE__) . '/migrate.php')) {
|
||||
include(dirname(__FILE__) . '/migrate.php');
|
||||
} else {
|
||||
echo '<strong>Error:</strong> No migration path specified for HTML Purifier, please check
|
||||
<tt>modes/htmlpurifier/migrate.bbcode.php</tt> for instructions on
|
||||
how to migrate from your previous markup language.';
|
||||
exit;
|
||||
}
|
||||
|
||||
// see if our hooks need to be bubbled to the end
|
||||
phorum_htmlpurifier_bubble_hook('format');
|
||||
|
||||
}
|
||||
|
||||
function phorum_htmlpurifier_bubble_hook($hook) {
|
||||
global $PHORUM;
|
||||
$our_idx = null;
|
||||
$last_idx = null;
|
||||
if (!isset($PHORUM['hooks'][$hook]['mods'])) return;
|
||||
foreach ($PHORUM['hooks'][$hook]['mods'] as $idx => $mod) {
|
||||
if ($mod == 'htmlpurifier') $our_idx = $idx;
|
||||
$last_idx = $idx;
|
||||
}
|
||||
list($mod) = array_splice($PHORUM['hooks'][$hook]['mods'], $our_idx, 1);
|
||||
$PHORUM['hooks'][$hook]['mods'][] = $mod;
|
||||
list($func) = array_splice($PHORUM['hooks'][$hook]['funcs'], $our_idx, 1);
|
||||
$PHORUM['hooks'][$hook]['funcs'][] = $func;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-emptively performs purification if it looks like a WYSIWYG editor
|
||||
* is being used
|
||||
*/
|
||||
function phorum_htmlpurifier_before_editor($message) {
|
||||
if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'])) {
|
||||
if (!empty($message['body'])) {
|
||||
$body = $message['body'];
|
||||
// de-entity-ize contents
|
||||
$body = str_replace(array('<','>','&'), array('<','>','&'), $body);
|
||||
$purifier =& HTMLPurifier::getInstance();
|
||||
$body = $purifier->purify($message['body']);
|
||||
// re-entity-ize contents
|
||||
$body = htmlspecialchars($body, ENT_QUOTES, $GLOBALS['PHORUM']['DATA']['CHARSET']);
|
||||
}
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
function phorum_htmlpurifier_editor_after_subject() {
|
||||
// don't show this message if it's a WYSIWYG editor, since it will
|
||||
// then be handled automatically
|
||||
if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'])) return;
|
||||
?><tr><td colspan="2" style="padding:1em 0.3em;">
|
||||
HTML input is <strong>on</strong>. Make sure you escape all HTML and
|
||||
angled-brackets with &lt; and &gt; (you can also use CDATA
|
||||
tags, simply wrap the suspect text with
|
||||
<![CDATA[<em>text</em>]]>. Paragraphs will only be applied to
|
||||
double-spaces; single-spaces will not generate <tt><br></tt> tags.
|
||||
</td></tr><?php
|
||||
}
|
||||
|
504
plugins/phorum/htmlpurifier/LICENSE
Normal file
504
plugins/phorum/htmlpurifier/LICENSE
Normal file
@@ -0,0 +1,504 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
|
1
plugins/phorum/htmlpurifier/README
Normal file
1
plugins/phorum/htmlpurifier/README
Normal file
@@ -0,0 +1 @@
|
||||
The contents of the library/ folder should be here.
|
8
plugins/phorum/info.txt
Normal file
8
plugins/phorum/info.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
hook: format|phorum_htmlpurifier_format
|
||||
hook: quote|phorum_htmlpurifier_quote
|
||||
hook: posting_custom_action|phorum_htmlpurifier_posting
|
||||
hook: common|phorum_htmlpurifier_common
|
||||
hook: before_editor|phorum_htmlpurifier_before_editor
|
||||
hook: tpl_editor_after_subject|phorum_htmlpurifier_editor_after_subject
|
||||
title: HTML Purifier Phorum Mod
|
||||
desc: This module enables standards-compliant HTML filtering on Phorum. Please check migrate.bbcode.php before enabling this mod.
|
27
plugins/phorum/init-config.php
Normal file
27
plugins/phorum/init-config.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Initializes the appropriate configuration from either a PHP file
|
||||
* or a module configuration value
|
||||
* @return Instance of HTMLPurifier_Config
|
||||
*/
|
||||
function phorum_htmlpurifier_get_config() {
|
||||
global $PHORUM;
|
||||
$config_exists = phorum_htmlpurifier_config_file_exists();
|
||||
if ($config_exists || !isset($PHORUM['mod_htmlpurifier']['config'])) {
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
include(dirname(__FILE__) . '/config.default.php');
|
||||
if ($config_exists) {
|
||||
include(dirname(__FILE__) . '/config.php');
|
||||
}
|
||||
unset($PHORUM['mod_htmlpurifier']['config']); // unnecessary
|
||||
} else {
|
||||
$config = HTMLPurifier_Config::create($PHORUM['mod_htmlpurifier']['config']);
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
function phorum_htmlpurifier_config_file_exists() {
|
||||
return file_exists(dirname(__FILE__) . '/config.php');
|
||||
}
|
||||
|
33
plugins/phorum/install.txt
Normal file
33
plugins/phorum/install.txt
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
HTML Purifier Phorum Mod - Filter your HTML the Standards-Compliant Way!
|
||||
|
||||
This Phorum mod enables HTML posting on Phorum. Under normal circumstances,
|
||||
this would cause a huge security risk, but because we are running
|
||||
HTML through HTML Purifier, output is guaranteed to be XSS free and
|
||||
standards-compliant.
|
||||
|
||||
This mod requires HTML input, and previous markup languages need to be
|
||||
converted accordingly. Thus, it is vital that you create a 'migrate.php'
|
||||
file that works with your installation. If you're using the built-in
|
||||
BBCode formatting, simply move migrate.bbcode.php to that place; for
|
||||
other markup languages, consult said file for instructions on how
|
||||
to adapt it to your needs.
|
||||
|
||||
This module will not work if 'migrate.php' is not created, and an improperly
|
||||
made migration file may *CORRUPT* Phorum, so please take your time to
|
||||
do this correctly. It should go without saying to *BACKUP YOUR DATABASE*
|
||||
before attempting anything here.
|
||||
|
||||
This module will not automatically migrate user signatures, because this
|
||||
process may take a long time. After installing the HTML Purifier module and
|
||||
then configuring 'migrate.php', navigate to Settings and click 'Migrate
|
||||
Signatures' to migrate all user signatures.
|
||||
|
||||
The version of HTML Purifier bundled with is a custom modified 2.0.1.
|
||||
Do not attempt to replace it with a version equal to or less than
|
||||
downloaded from the HTML Purifier website: the module will combust
|
||||
spectacularly. (Greater versions, however, are okay, because the changes
|
||||
made to accomodate this module have been committed to the trunk).
|
||||
|
||||
Visit HTML Purifier at <http://htmlpurifier.org/>. May the force
|
||||
be with you.
|
28
plugins/phorum/migrate.bbcode.php
Normal file
28
plugins/phorum/migrate.bbcode.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is responsible for migrating from a specific markup language
|
||||
* like BBCode or Markdown to HTML. WARNING: THIS PROCESS IS NOT REVERSIBLE
|
||||
*
|
||||
* Copy this file to 'migrate.php' and it will automatically work for
|
||||
* BBCode; you may need to tweak this a little to get it to work for other
|
||||
* languages (usually, just replace the include name and the function name).
|
||||
*
|
||||
* If you do NOT want to have any migration performed (for instance, you
|
||||
* are installing the module on a new forum with no posts), simply remove
|
||||
* phorum_htmlpurifier_migrate() function. You still need migrate.php
|
||||
* present, otherwise the module won't work.
|
||||
*/
|
||||
|
||||
if(!defined("PHORUM")) exit;
|
||||
|
||||
require_once(dirname(__FILE__) . "/../bbcode/bbcode.php");
|
||||
|
||||
/**
|
||||
* 'format' hook style function that will be called to convert
|
||||
* legacy markup into HTML.
|
||||
*/
|
||||
function phorum_htmlpurifier_migrate($data) {
|
||||
return phorum_bb_code($data); // bbcode's 'format' hook
|
||||
}
|
||||
|
63
plugins/phorum/settings.php
Normal file
63
plugins/phorum/settings.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
// based off of BBCode's settings file
|
||||
|
||||
/**
|
||||
* HTML Purifier Phorum mod settings configuration. This provides
|
||||
* a convenient web-interface for editing the most common HTML Purifier
|
||||
* configuration directives. You can also specify custom configuration
|
||||
* by creating a 'config.php' file.
|
||||
*/
|
||||
|
||||
if(!defined("PHORUM_ADMIN")) exit;
|
||||
|
||||
// error reporting is good!
|
||||
error_reporting(E_ALL ^ E_NOTICE);
|
||||
|
||||
// load library and other paraphenalia
|
||||
require_once './include/admin/PhorumInputForm.php';
|
||||
require_once (dirname(__FILE__) . '/htmlpurifier/HTMLPurifier.auto.php');
|
||||
require_once (dirname(__FILE__) . '/init-config.php');
|
||||
require_once (dirname(__FILE__) . '/settings/migrate-sigs-form.php');
|
||||
require_once (dirname(__FILE__) . '/settings/migrate-sigs.php');
|
||||
require_once (dirname(__FILE__) . '/settings/form.php');
|
||||
require_once (dirname(__FILE__) . '/settings/save.php');
|
||||
|
||||
// define friendly configuration directives. you can expand this array
|
||||
// to get more web-definable directives
|
||||
$PHORUM['mod_htmlpurifier']['directives'] = array(
|
||||
'URI.Host', // auto-detectable
|
||||
'URI.DisableExternal',
|
||||
'URI.DisableExternalResources',
|
||||
'URI.DisableResources',
|
||||
'URI.Munge',
|
||||
'URI.HostBlacklist',
|
||||
'URI.Disable',
|
||||
'HTML.TidyLevel',
|
||||
'HTML.Doctype', // auto-detectable
|
||||
'HTML.Allowed',
|
||||
'AutoFormat',
|
||||
'-AutoFormat.Custom',
|
||||
'-AutoFormat.PurifierLinkify',
|
||||
'Output.TidyFormat',
|
||||
);
|
||||
|
||||
// lower this setting if you're getting time outs/out of memory
|
||||
$PHORUM['mod_htmlpurifier']['migrate-sigs-increment'] = 100;
|
||||
|
||||
if (isset($_POST['reset'])) {
|
||||
unset($PHORUM['mod_htmlpurifier']['config']);
|
||||
}
|
||||
|
||||
if ($offset = phorum_htmlpurifier_migrate_sigs_check()) {
|
||||
// migrate signatures
|
||||
phorum_htmlpurifier_migrate_sigs($offset);
|
||||
} elseif(!empty($_POST)){
|
||||
// save settings
|
||||
phorum_htmlpurifier_save_settings();
|
||||
}
|
||||
|
||||
phorum_htmlpurifier_show_migrate_sigs_form();
|
||||
echo '<br />';
|
||||
phorum_htmlpurifier_show_form();
|
||||
|
79
plugins/phorum/settings/form.php
Normal file
79
plugins/phorum/settings/form.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
function phorum_htmlpurifier_show_form() {
|
||||
if (phorum_htmlpurifier_config_file_exists()) {
|
||||
phorum_htmlpurifier_show_config_info();
|
||||
return;
|
||||
}
|
||||
|
||||
global $PHORUM;
|
||||
|
||||
$config = phorum_htmlpurifier_get_config();
|
||||
|
||||
$frm = new PhorumInputForm ("", "post", "Save");
|
||||
$frm->hidden("module", "modsettings");
|
||||
$frm->hidden("mod", "htmlpurifier"); // this is the directory name that the Settings file lives in
|
||||
|
||||
if (!empty($error)){
|
||||
echo "$error<br />";
|
||||
}
|
||||
|
||||
$frm->addbreak("Edit settings for the HTML Purifier module");
|
||||
|
||||
$frm->addMessage('<p>Click on directive links to read what each option does
|
||||
(links do not open in new windows).</p>
|
||||
<p>For more flexibility (for instance, you want to edit the full
|
||||
range of configuration directives), you can create a <tt>config.php</tt>
|
||||
file in your <tt>mods/htmlpurifier/</tt> directory. Doing so will,
|
||||
however, make the web configuration interface unavailable.</p>');
|
||||
|
||||
require_once 'HTMLPurifier/Printer/ConfigForm.php';
|
||||
$htmlpurifier_form = new HTMLPurifier_Printer_ConfigForm('config', 'http://htmlpurifier.org/live/configdoc/plain.html#%s');
|
||||
$htmlpurifier_form->setTextareaDimensions(23, 7); // widen a little, since we have space
|
||||
|
||||
$frm->addMessage($htmlpurifier_form->render(
|
||||
$config, $PHORUM['mod_htmlpurifier']['directives'], false));
|
||||
|
||||
$frm->addMessage("<strong>Warning: Changing HTML Purifier's configuration will invalidate
|
||||
the cache. Expect to see a flurry of database activity after you change
|
||||
any of these settings.</strong>");
|
||||
|
||||
$frm->addrow('Reset to defaults:', $frm->checkbox("reset", "1", "", false));
|
||||
|
||||
// hack to include extra styling
|
||||
echo '<style type="text/css">' . $htmlpurifier_form->getCSS() . '
|
||||
.hp-config {margin-left:auto;margin-right:auto;}
|
||||
</style>';
|
||||
$js = $htmlpurifier_form->getJavaScript();
|
||||
echo '<script type="text/javascript">'."<!--\n$js\n//-->".'</script>';
|
||||
|
||||
$frm->show();
|
||||
}
|
||||
|
||||
function phorum_htmlpurifier_show_config_info() {
|
||||
global $PHORUM;
|
||||
|
||||
// update mod_htmlpurifier for housekeeping
|
||||
phorum_htmlpurifier_commit_settings();
|
||||
|
||||
// politely tell user how to edit settings manually
|
||||
?>
|
||||
<div class="input-form-td-break">How to edit settings for HTML Purifier module</div>
|
||||
<p>
|
||||
A <tt>config.php</tt> file exists in your <tt>mods/htmlpurifier/</tt>
|
||||
directory. This file contains your custom configuration: in order to
|
||||
change it, please navigate to that file and edit it accordingly.
|
||||
</p>
|
||||
<p>
|
||||
To use the web interface, delete <tt>config.php</tt> (or rename it to
|
||||
<tt>config.php.bak</tt>).
|
||||
</p>
|
||||
<p>
|
||||
<strong>Warning: Changing HTML Purifier's configuration will invalidate
|
||||
the cache. Expect to see a flurry of database activity after you change
|
||||
any of these settings.</strong>
|
||||
</p>
|
||||
<?php
|
||||
|
||||
}
|
||||
|
21
plugins/phorum/settings/migrate-sigs-form.php
Normal file
21
plugins/phorum/settings/migrate-sigs-form.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
function phorum_htmlpurifier_show_migrate_sigs_form() {
|
||||
|
||||
$frm = new PhorumInputForm ('', "post", "Migrate");
|
||||
$frm->hidden("module", "modsettings");
|
||||
$frm->hidden("mod", "htmlpurifier");
|
||||
$frm->hidden("migrate-sigs", "1");
|
||||
$frm->addbreak("Migrate user signatures to HTML");
|
||||
$frm->addMessage('This operation will migrate your users signatures
|
||||
to HTML. <strong>This process is irreversible and must only be performed once.</strong>
|
||||
Type in yes in the confirmation field to migrate.');
|
||||
if (!file_exists(dirname(__FILE__) . '/../migrate.php')) {
|
||||
$frm->addMessage('Migration file does not exist, cannot migrate signatures.
|
||||
Please check <tt>migrate.bbcode.php</tt> on how to create an appropriate file.');
|
||||
} else {
|
||||
$frm->addrow('Confirm:', $frm->text_box("confirmation", ""));
|
||||
}
|
||||
$frm->show();
|
||||
}
|
||||
|
85
plugins/phorum/settings/migrate-sigs.php
Normal file
85
plugins/phorum/settings/migrate-sigs.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
function phorum_htmlpurifier_migrate_sigs_check() {
|
||||
global $PHORUM;
|
||||
$offset = 0;
|
||||
if (!empty($_POST['migrate-sigs'])) {
|
||||
if (!isset($_POST['confirmation']) || strtolower($_POST['confirmation']) !== 'yes') {
|
||||
echo 'Invalid confirmation code.';
|
||||
exit;
|
||||
}
|
||||
$PHORUM['mod_htmlpurifier']['migrate-sigs'] = true;
|
||||
phorum_db_update_settings(array("mod_htmlpurifier"=>$PHORUM["mod_htmlpurifier"]));
|
||||
$offset = 1;
|
||||
} elseif (!empty($_GET['migrate-sigs']) && $PHORUM['mod_htmlpurifier']['migrate-sigs']) {
|
||||
$offset = (int) $_GET['migrate-sigs'];
|
||||
}
|
||||
return $offset;
|
||||
}
|
||||
|
||||
function phorum_htmlpurifier_migrate_sigs($offset) {
|
||||
global $PHORUM;
|
||||
|
||||
if(!$offset) return; // bail out quick of $offset == 0
|
||||
|
||||
@set_time_limit(0); // attempt to let this run
|
||||
$increment = $PHORUM['mod_htmlpurifier']['migrate-sigs-increment'];
|
||||
|
||||
require_once(dirname(__FILE__) . '/../migrate.php');
|
||||
// migrate signatures
|
||||
// do this in batches so we don't run out of time/space
|
||||
$end = $offset + $increment;
|
||||
$user_ids = array();
|
||||
for ($i = $offset; $i < $end; $i++) {
|
||||
$user_ids[] = $i;
|
||||
}
|
||||
$userinfos = phorum_db_user_get_fields($user_ids, 'signature');
|
||||
foreach ($userinfos as $i => $user) {
|
||||
if (empty($user['signature'])) continue;
|
||||
$sig = $user['signature'];
|
||||
// perform standard Phorum processing on the sig
|
||||
$sig = str_replace(array("&","<",">"), array("&","<",">"), $sig);
|
||||
$sig = preg_replace("/<((http|https|ftp):\/\/[a-z0-9;\/\?:@=\&\$\-_\.\+!*'\(\),~%]+?)>/i", "$1", $sig);
|
||||
// prepare fake data to pass to migration function
|
||||
$fake_data = array(array("author"=>"", "email"=>"", "subject"=>"", 'body' => $sig));
|
||||
list($fake_message) = phorum_htmlpurifier_migrate($fake_data);
|
||||
$user['signature'] = $fake_message['body'];
|
||||
if (!phorum_user_save($user)) {
|
||||
exit('Error while saving user data');
|
||||
}
|
||||
}
|
||||
unset($userinfos); // free up memory
|
||||
|
||||
// query for highest ID in database
|
||||
$type = $PHORUM['DBCONFIG']['type'];
|
||||
if ($type == 'mysql') {
|
||||
$conn = phorum_db_mysql_connect();
|
||||
$sql = "select MAX(user_id) from {$PHORUM['user_table']}";
|
||||
$res = mysql_query($sql, $conn);
|
||||
$row = mysql_fetch_row($res);
|
||||
$top_id = (int) $row[0];
|
||||
} elseif ($type == 'mysqli') {
|
||||
$conn = phorum_db_mysqli_connect();
|
||||
$sql = "select MAX(user_id) from {$PHORUM['user_table']}";
|
||||
$res = mysqli_query($conn, $sql);
|
||||
$row = mysqli_fetch_row($res);
|
||||
$top_id = (int) $row[0];
|
||||
} else {
|
||||
exit('Unrecognized database!');
|
||||
}
|
||||
|
||||
$offset += $increment;
|
||||
if ($offset > $top_id) { // test for end condition
|
||||
echo 'Migration finished';
|
||||
$PHORUM['mod_htmlpurifier']['migrate-sigs'] = false;
|
||||
phorum_htmlpurifier_commit_settings();
|
||||
return true;
|
||||
}
|
||||
$host = $_SERVER['HTTP_HOST'];
|
||||
$uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
|
||||
$extra = 'admin.php?module=modsettings&mod=htmlpurifier&migrate-sigs=' . $offset;
|
||||
// relies on output buffering to work
|
||||
header("Location: http://$host$uri/$extra");
|
||||
exit;
|
||||
|
||||
}
|
23
plugins/phorum/settings/save.php
Normal file
23
plugins/phorum/settings/save.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
function phorum_htmlpurifier_save_settings() {
|
||||
global $PHORUM;
|
||||
if (phorum_htmlpurifier_config_file_exists()) {
|
||||
echo "Cannot update settings, <code>mods/htmlpurifier/config.php</code> already exists. To change
|
||||
settings, edit that file. To use the web form, delete that file.<br />";
|
||||
} else {
|
||||
$config = phorum_htmlpurifier_get_config();
|
||||
if (!isset($_POST['reset'])) $config->mergeArrayFromForm($_POST, 'config', $PHORUM['mod_htmlpurifier']['directives']);
|
||||
$PHORUM['mod_htmlpurifier']['config'] = $config->getAll();
|
||||
if(!phorum_htmlpurifier_commit_settings()){
|
||||
$error="Database error while updating settings.";
|
||||
} else {
|
||||
echo "Settings Updated<br />";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function phorum_htmlpurifier_commit_settings() {
|
||||
global $PHORUM;
|
||||
return phorum_db_update_settings(array("mod_htmlpurifier"=>$PHORUM["mod_htmlpurifier"]));
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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) {
|
||||
|
@@ -37,3 +37,7 @@ HTMLPurifier_ConfigSchema::defineNamespace('ReportCard', 'It is for grades.');
|
||||
HTMLPurifier_ConfigSchema::define('ReportCard', 'English', null, 'string/null', 'Grade from English class.');
|
||||
HTMLPurifier_ConfigSchema::define('ReportCard', 'Absences', 0, 'int', 'How many times missing from school?');
|
||||
|
||||
HTMLPurifier_ConfigSchema::defineNamespace('Text', 'This stuff is long, boring, and English.');
|
||||
HTMLPurifier_ConfigSchema::define('Text', 'AboutUs', 'Nothing much, but this should be decently long so that a textarea would be better', 'text', 'Who are we? What are we up to?');
|
||||
HTMLPurifier_ConfigSchema::define('Text', 'Hash', "not-case-sensitive\nstill-not-case-sensitive\nsuper-not-case-sensitive", 'itext', 'This is of limited utility, but of course it ends up being used.');
|
||||
|
||||
|
@@ -1,16 +1,20 @@
|
||||
<?php
|
||||
|
||||
// This file is necessary to run the unit tests and profiling
|
||||
// scripts.
|
||||
// ATTENTION! DO NOT EDIT THIS FILE!
|
||||
// This file is necessary to run the unit tests and profiling scripts.
|
||||
// Please copy it to 'test-settings.php' and make the necessary edits.
|
||||
|
||||
// Is PEAR available on your system? If it isn't, set to false. If PEAR
|
||||
// is not part of the default include_path, add it.
|
||||
$GLOBALS['HTMLPurifierTest']['PEAR'] = true;
|
||||
// Some of these scripts run a long time, so it is recommended that you
|
||||
// turn off the time limit
|
||||
set_time_limit(0);
|
||||
|
||||
// Turning off output buffering will prevent mysterious errors from core dumps
|
||||
@ob_end_flush();
|
||||
|
||||
// Where is SimpleTest located?
|
||||
$simpletest_location = '/path/to/simpletest/';
|
||||
|
||||
// How many times should profiling scripts iterate over the function? More runs
|
||||
// means more accurate results, but they'll take longer to perform.
|
||||
$GLOBALS['HTMLPurifierTest']['Runs'] = 2;
|
||||
|
||||
// Where is SimpleTest located?
|
||||
$simpletest_location = '/path/to/simpletest/';
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;',
|
||||
|
@@ -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')
|
||||
);
|
||||
|
||||
}
|
||||
|
@@ -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;')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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' => '')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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(
|
||||
|
@@ -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')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;')
|
||||
|
@@ -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')
|
||||
|
@@ -6,6 +6,7 @@ class HTMLPurifier_AttrTransformHarness extends HTMLPurifier_ComplexHarness
|
||||
{
|
||||
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
$this->func = 'transform';
|
||||
}
|
||||
|
||||
|
@@ -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');
|
||||
|
@@ -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>'
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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', '');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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><img />',
|
||||
array(
|
||||
'Core.EscapeInvalidChildren' => true
|
||||
)
|
||||
'Out <b>Bold text</b><img />',
|
||||
'Out <b>Bold text</b><img />'
|
||||
);
|
||||
|
||||
}
|
||||
|
@@ -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>');
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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"
|
||||
);
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user