mirror of
https://github.com/ezyang/htmlpurifier.git
synced 2025-08-08 15:16:54 +02:00
Release 2.1.0, merged in 1255 to HEAD.
git-svn-id: http://htmlpurifier.org/svnroot/htmlpurifier/branches/strict@1368 48356398-32a2-884e-a903-53898d9a118a
This commit is contained in:
@@ -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,65 @@
|
||||
<?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';
|
||||
|
||||
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.'
|
||||
);
|
||||
// special case filtering 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.'
|
||||
);
|
||||
'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', '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 +67,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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.1';
|
||||
|
||||
/**
|
||||
* 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>:
|
||||
@@ -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;
|
||||
|
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,7 @@ 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';
|
||||
|
||||
// tidy modules
|
||||
require_once 'HTMLPurifier/HTMLModule/Tidy.php';
|
||||
@@ -215,8 +216,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.
|
||||
*
|
||||
|
@@ -42,6 +42,16 @@ 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 '.
|
||||
@@ -151,5 +161,21 @@ 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];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -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(
|
||||
@@ -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++;
|
||||
|
||||
|
@@ -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,52 @@ 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
|
||||
*/
|
||||
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;
|
||||
$this->fields['default'] = new HTMLPurifier_Printer_ConfigForm_default();
|
||||
$this->fields['bool'] = new HTMLPurifier_Printer_ConfigForm_bool();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 the directory it's in is not publically
|
||||
* available
|
||||
*/
|
||||
function getCSS() {
|
||||
return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.css');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves JavaScript, in case directory is not public
|
||||
*/
|
||||
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
|
||||
@@ -98,11 +132,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 +211,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 +227,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 +257,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';
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -79,12 +79,14 @@ class HTMLPurifier_URISchemeRegistry
|
||||
}
|
||||
|
||||
if (isset($this->schemes[$scheme])) return $this->schemes[$scheme];
|
||||
if (empty($this->_dir)) $this->_dir = dirname(__FILE__) . '/URIScheme/';
|
||||
if (empty($this->_dir)) $this->_dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/URIScheme/';
|
||||
|
||||
if (!isset($allowed_schemes[$scheme])) return $null;
|
||||
|
||||
@include_once $this->_dir . $scheme . '.php';
|
||||
// this bit of reflection is not very efficient, and a bit
|
||||
// hacky too
|
||||
$class = 'HTMLPurifier_URIScheme_' . $scheme;
|
||||
if (!class_exists($class)) include_once $this->_dir . $scheme . '.php';
|
||||
if (!class_exists($class)) return $null;
|
||||
$this->schemes[$scheme] = new $class();
|
||||
return $this->schemes[$scheme];
|
||||
|
Reference in New Issue
Block a user