8889841chtmlpurifier/README.md000064400000002367150437375620010561 0ustar00HTML Purifier [![Build Status](https://github.com/ezyang/htmlpurifier/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/ezyang/htmlpurifier/actions/workflows/ci.yml) ============= HTML Purifier is an HTML filtering solution that uses a unique combination of robust whitelists and aggressive parsing to ensure that not only are XSS attacks thwarted, but the resulting HTML is standards compliant. HTML Purifier is oriented towards richly formatted documents from untrusted sources that require CSS and a full tag-set. This library can be configured to accept a more restrictive set of tags, but it won't be as efficient as more bare-bones parsers. It will, however, do the job right, which may be more important. Places to go: * See INSTALL for a quick installation guide * See docs/ for developer-oriented documentation, code examples and an in-depth installation guide. * See WYSIWYG for information on editors like TinyMCE and FCKeditor HTML Purifier can be found on the web at: [http://htmlpurifier.org/](http://htmlpurifier.org/) ## Installation Package available on [Composer](https://packagist.org/packages/ezyang/htmlpurifier). If you're using Composer to manage dependencies, you can use $ composer require ezyang/htmlpurifier htmlpurifier/library/HTMLPurifier.func.php000064400000001100150437375620014643 0ustar00purify($html, $config); } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/Printer.php000064400000013411150437375620015344 0ustar00getAll(); $context = new HTMLPurifier_Context(); $this->generator = new HTMLPurifier_Generator($config, $context); } /** * Main function that renders object or aspect of that object * @note Parameters vary depending on printer */ // function render() {} /** * Returns a start tag * @param string $tag Tag name * @param array $attr Attribute array * @return string */ protected function start($tag, $attr = array()) { return $this->generator->generateFromToken( new HTMLPurifier_Token_Start($tag, $attr ? $attr : array()) ); } /** * Returns an end tag * @param string $tag Tag name * @return string */ protected function end($tag) { return $this->generator->generateFromToken( new HTMLPurifier_Token_End($tag) ); } /** * Prints a complete element with content inside * @param string $tag Tag name * @param string $contents Element contents * @param array $attr Tag attributes * @param bool $escape whether or not to escape contents * @return string */ protected function element($tag, $contents, $attr = array(), $escape = true) { return $this->start($tag, $attr) . ($escape ? $this->escape($contents) : $contents) . $this->end($tag); } /** * @param string $tag * @param array $attr * @return string */ protected function elementEmpty($tag, $attr = array()) { return $this->generator->generateFromToken( new HTMLPurifier_Token_Empty($tag, $attr) ); } /** * @param string $text * @return string */ protected function text($text) { return $this->generator->generateFromToken( new HTMLPurifier_Token_Text($text) ); } /** * Prints a simple key/value row in a table. * @param string $name Key * @param mixed $value Value * @return string */ protected function row($name, $value) { if (is_bool($value)) { $value = $value ? 'On' : 'Off'; } return $this->start('tr') . "\n" . $this->element('th', $name) . "\n" . $this->element('td', $value) . "\n" . $this->end('tr'); } /** * Escapes a string for HTML output. * @param string $string String to escape * @return string */ protected function escape($string) { $string = HTMLPurifier_Encoder::cleanUTF8($string); $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8'); return $string; } /** * Takes a list of strings and turns them into a single list * @param string[] $array List of strings * @param bool $polite Bool whether or not to add an end before the last * @return string */ protected function listify($array, $polite = false) { if (empty($array)) { return 'None'; } $ret = ''; $i = count($array); foreach ($array as $value) { $i--; $ret .= $value; if ($i > 0 && !($polite && $i == 1)) { $ret .= ', '; } if ($polite && $i == 1) { $ret .= 'and '; } } return $ret; } /** * Retrieves the class of an object without prefixes, as well as metadata * @param object $obj Object to determine class of * @param string $sec_prefix Further prefix to remove * @return string */ protected function getClass($obj, $sec_prefix = '') { static $five = null; if ($five === null) { $five = version_compare(PHP_VERSION, '5', '>='); } $prefix = 'HTMLPurifier_' . $sec_prefix; if (!$five) { $prefix = strtolower($prefix); } $class = str_replace($prefix, '', get_class($obj)); $lclass = strtolower($class); $class .= '('; switch ($lclass) { case 'enum': $values = array(); foreach ($obj->valid_values as $value => $bool) { $values[] = $value; } $class .= implode(', ', $values); break; case 'css_composite': $values = array(); foreach ($obj->defs as $def) { $values[] = $this->getClass($def, $sec_prefix); } $class .= implode(', ', $values); break; case 'css_multiple': $class .= $this->getClass($obj->single, $sec_prefix) . ', '; $class .= $obj->max; break; case 'css_denyelementdecorator': $class .= $this->getClass($obj->def, $sec_prefix) . ', '; $class .= $obj->element; break; case 'css_importantdecorator': $class .= $this->getClass($obj->def, $sec_prefix); if ($obj->allow) { $class .= ', !important'; } break; } $class .= ')'; return $class; } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/URIFilter.php000064400000004475150437375620015540 0ustar00checkDefType($def)) { return; } $file = $this->generateFilePath($config); if (file_exists($file)) { return false; } if (!$this->_prepareDir($config)) { return false; } return $this->_write($file, serialize($def), $config); } /** * @param HTMLPurifier_Definition $def * @param HTMLPurifier_Config $config * @return int|bool */ public function set($def, $config) { if (!$this->checkDefType($def)) { return; } $file = $this->generateFilePath($config); if (!$this->_prepareDir($config)) { return false; } return $this->_write($file, serialize($def), $config); } /** * @param HTMLPurifier_Definition $def * @param HTMLPurifier_Config $config * @return int|bool */ public function replace($def, $config) { if (!$this->checkDefType($def)) { return; } $file = $this->generateFilePath($config); if (!file_exists($file)) { return false; } if (!$this->_prepareDir($config)) { return false; } return $this->_write($file, serialize($def), $config); } /** * @param HTMLPurifier_Config $config * @return bool|HTMLPurifier_Config */ public function get($config) { $file = $this->generateFilePath($config); if (!file_exists($file)) { return false; } return unserialize(file_get_contents($file)); } /** * @param HTMLPurifier_Config $config * @return bool */ public function remove($config) { $file = $this->generateFilePath($config); if (!file_exists($file)) { return false; } return unlink($file); } /** * @param HTMLPurifier_Config $config * @return bool */ public function flush($config) { if (!$this->_prepareDir($config)) { return false; } $dir = $this->generateDirectoryPath($config); $dh = opendir($dir); // Apparently, on some versions of PHP, readdir will return // an empty string if you pass an invalid argument to readdir. // So you need this test. See #49. if (false === $dh) { return false; } while (false !== ($filename = readdir($dh))) { if (empty($filename)) { continue; } if ($filename[0] === '.') { continue; } unlink($dir . '/' . $filename); } closedir($dh); return true; } /** * @param HTMLPurifier_Config $config * @return bool */ public function cleanup($config) { if (!$this->_prepareDir($config)) { return false; } $dir = $this->generateDirectoryPath($config); $dh = opendir($dir); // See #49 (and above). if (false === $dh) { return false; } while (false !== ($filename = readdir($dh))) { if (empty($filename)) { continue; } if ($filename[0] === '.') { continue; } $key = substr($filename, 0, strlen($filename) - 4); if ($this->isOld($key, $config)) { unlink($dir . '/' . $filename); } } closedir($dh); return true; } /** * Generates the file path to the serial file corresponding to * the configuration and definition name * @param HTMLPurifier_Config $config * @return string * @todo Make protected */ public function generateFilePath($config) { $key = $this->generateKey($config); return $this->generateDirectoryPath($config) . '/' . $key . '.ser'; } /** * Generates the path to the directory contain this cache's serial files * @param HTMLPurifier_Config $config * @return string * @note No trailing slash * @todo Make protected */ public function generateDirectoryPath($config) { $base = $this->generateBaseDirectoryPath($config); return $base . '/' . $this->type; } /** * Generates path to base directory that contains all definition type * serials * @param HTMLPurifier_Config $config * @return mixed|string * @todo Make protected */ public function generateBaseDirectoryPath($config) { $base = $config->get('Cache.SerializerPath'); $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base; return $base; } /** * Convenience wrapper function for file_put_contents * @param string $file File name to write to * @param string $data Data to write into file * @param HTMLPurifier_Config $config * @return int|bool Number of bytes written if success, or false if failure. */ private function _write($file, $data, $config) { $result = file_put_contents($file, $data); if ($result !== false) { // set permissions of the new file (no execute) $chmod = $config->get('Cache.SerializerPermissions'); if ($chmod !== null) { chmod($file, $chmod & 0666); } } return $result; } /** * Prepares the directory that this type stores the serials in * @param HTMLPurifier_Config $config * @return bool True if successful */ private function _prepareDir($config) { $directory = $this->generateDirectoryPath($config); $chmod = $config->get('Cache.SerializerPermissions'); if ($chmod === null) { if (!@mkdir($directory) && !is_dir($directory)) { trigger_error( 'Could not create directory ' . $directory . '', E_USER_WARNING ); return false; } return true; } if (!is_dir($directory)) { $base = $this->generateBaseDirectoryPath($config); if (!is_dir($base)) { trigger_error( 'Base directory ' . $base . ' does not exist, please create or change using %Cache.SerializerPath', E_USER_WARNING ); return false; } elseif (!$this->_testPermissions($base, $chmod)) { return false; } if (!@mkdir($directory, $chmod) && !is_dir($directory)) { trigger_error( 'Could not create directory ' . $directory . '', E_USER_WARNING ); return false; } if (!$this->_testPermissions($directory, $chmod)) { return false; } } elseif (!$this->_testPermissions($directory, $chmod)) { return false; } return true; } /** * Tests permissions on a directory and throws out friendly * error messages and attempts to chmod it itself if possible * @param string $dir Directory path * @param int $chmod Permissions * @return bool True if directory is writable */ private function _testPermissions($dir, $chmod) { // early abort, if it is writable, everything is hunky-dory if (is_writable($dir)) { return true; } if (!is_dir($dir)) { // generally, you'll want to handle this beforehand // so a more specific error message can be given trigger_error( 'Directory ' . $dir . ' does not exist', E_USER_WARNING ); return false; } if (function_exists('posix_getuid') && $chmod !== null) { // POSIX system, we can give more specific advice if (fileowner($dir) === posix_getuid()) { // we can chmod it ourselves $chmod = $chmod | 0700; if (chmod($dir, $chmod)) { return true; } } elseif (filegroup($dir) === posix_getgid()) { $chmod = $chmod | 0070; } else { // PHP's probably running as nobody, so we'll // need to give global permissions $chmod = $chmod | 0777; } trigger_error( 'Directory ' . $dir . ' not writable, ' . 'please chmod to ' . decoct($chmod), E_USER_WARNING ); } else { // generic error message trigger_error( 'Directory ' . $dir . ' not writable, ' . 'please alter file permissions', E_USER_WARNING ); } return false; } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php000064400000004012150437375620022124 0ustar00definitions[$this->generateKey($config)] = $def; } return $status; } /** * @param HTMLPurifier_Definition $def * @param HTMLPurifier_Config $config * @return mixed */ public function set($def, $config) { $status = parent::set($def, $config); if ($status) { $this->definitions[$this->generateKey($config)] = $def; } return $status; } /** * @param HTMLPurifier_Definition $def * @param HTMLPurifier_Config $config * @return mixed */ public function replace($def, $config) { $status = parent::replace($def, $config); if ($status) { $this->definitions[$this->generateKey($config)] = $def; } return $status; } /** * @param HTMLPurifier_Config $config * @return mixed */ public function get($config) { $key = $this->generateKey($config); if (isset($this->definitions[$key])) { return $this->definitions[$key]; } $this->definitions[$key] = parent::get($config); return $this->definitions[$key]; } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php000064400000003243150437375620022250 0ustar00copy(); // reference is necessary for mocks in PHP 4 $decorator->cache =& $cache; $decorator->type = $cache->type; return $decorator; } /** * Cross-compatible clone substitute * @return HTMLPurifier_DefinitionCache_Decorator */ public function copy() { return new HTMLPurifier_DefinitionCache_Decorator(); } /** * @param HTMLPurifier_Definition $def * @param HTMLPurifier_Config $config * @return mixed */ public function add($def, $config) { return $this->cache->add($def, $config); } /** * @param HTMLPurifier_Definition $def * @param HTMLPurifier_Config $config * @return mixed */ public function set($def, $config) { return $this->cache->set($def, $config); } /** * @param HTMLPurifier_Definition $def * @param HTMLPurifier_Config $config * @return mixed */ public function replace($def, $config) { return $this->cache->replace($def, $config); } /** * @param HTMLPurifier_Config $config * @return mixed */ public function get($config) { return $this->cache->get($config); } /** * @param HTMLPurifier_Config $config * @return mixed */ public function remove($config) { return $this->cache->remove($config); } /** * @param HTMLPurifier_Config $config * @return mixed */ public function flush($config) { return $this->cache->flush($config); } /** * @param HTMLPurifier_Config $config * @return mixed */ public function cleanup($config) { return $this->cache->cleanup($config); } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README000064400000000140150437375620021210 0ustar00This is a dummy file to prevent Git from ignoring this empty directory. vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/HTMLModule.php000064400000023723150437375620015642 0ustar00info, since the object's data is only info, * with extra behavior associated with it. * @type array */ public $attr_collections = array(); /** * Associative array of deprecated tag name to HTMLPurifier_TagTransform. * @type array */ public $info_tag_transform = array(); /** * List of HTMLPurifier_AttrTransform to be performed before validation. * @type array */ public $info_attr_transform_pre = array(); /** * List of HTMLPurifier_AttrTransform to be performed after validation. * @type array */ public $info_attr_transform_post = array(); /** * List of HTMLPurifier_Injector to be performed during well-formedness fixing. * An injector will only be invoked if all of it's pre-requisites are met; * if an injector fails setup, there will be no error; it will simply be * silently disabled. * @type array */ public $info_injector = array(); /** * Boolean flag that indicates whether or not getChildDef is implemented. * For optimization reasons: may save a call to a function. Be sure * to set it if you do implement getChildDef(), otherwise it will have * no effect! * @type bool */ public $defines_child_def = false; /** * Boolean flag whether or not this module is safe. If it is not safe, all * of its members are unsafe. Modules are safe by default (this might be * slightly dangerous, but it doesn't make much sense to force HTML Purifier, * which is based off of safe HTML, to explicitly say, "This is safe," even * though there are modules which are "unsafe") * * @type bool * @note Previously, safety could be applied at an element level granularity. * We've removed this ability, so in order to add "unsafe" elements * or attributes, a dedicated module with this property set to false * must be used. */ public $safe = true; /** * Retrieves a proper HTMLPurifier_ChildDef subclass based on * content_model and content_model_type member variables of * the HTMLPurifier_ElementDef class. There is a similar function * in HTMLPurifier_HTMLDefinition. * @param HTMLPurifier_ElementDef $def * @return HTMLPurifier_ChildDef subclass */ public function getChildDef($def) { return false; } // -- Convenience ----------------------------------------------------- /** * Convenience function that sets up a new element * @param string $element Name of element to add * @param string|bool $type What content set should element be registered to? * Set as false to skip this step. * @param string|HTMLPurifier_ChildDef $contents Allowed children in form of: * "$content_model_type: $content_model" * @param array|string $attr_includes What attribute collections to register to * element? * @param array $attr What unique attributes does the element define? * @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters. * @return HTMLPurifier_ElementDef Created element definition object, so you * can set advanced parameters */ public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array()) { $this->elements[] = $element; // parse content_model list($content_model_type, $content_model) = $this->parseContents($contents); // merge in attribute inclusions $this->mergeInAttrIncludes($attr, $attr_includes); // add element to content sets if ($type) { $this->addElementToContentSet($element, $type); } // create element $this->info[$element] = HTMLPurifier_ElementDef::create( $content_model, $content_model_type, $attr ); // literal object $contents means direct child manipulation if (!is_string($contents)) { $this->info[$element]->child = $contents; } return $this->info[$element]; } /** * Convenience function that creates a totally blank, non-standalone * element. * @param string $element Name of element to create * @return HTMLPurifier_ElementDef Created element */ public function addBlankElement($element) { if (!isset($this->info[$element])) { $this->elements[] = $element; $this->info[$element] = new HTMLPurifier_ElementDef(); $this->info[$element]->standalone = false; } else { trigger_error("Definition for $element already exists in module, cannot redefine"); } return $this->info[$element]; } /** * Convenience function that registers an element to a content set * @param string $element Element to register * @param string $type Name content set (warning: case sensitive, usually upper-case * first letter) */ public function addElementToContentSet($element, $type) { if (!isset($this->content_sets[$type])) { $this->content_sets[$type] = ''; } else { $this->content_sets[$type] .= ' | '; } $this->content_sets[$type] .= $element; } /** * Convenience function that transforms single-string contents * into separate content model and content model type * @param string $contents Allowed children in form of: * "$content_model_type: $content_model" * @return array * @note If contents is an object, an array of two nulls will be * returned, and the callee needs to take the original $contents * and use it directly. */ public function parseContents($contents) { if (!is_string($contents)) { return array(null, null); } // defer switch ($contents) { // check for shorthand content model forms case 'Empty': return array('empty', ''); case 'Inline': return array('optional', 'Inline | #PCDATA'); case 'Flow': return array('optional', 'Flow | #PCDATA'); } list($content_model_type, $content_model) = explode(':', $contents); $content_model_type = strtolower(trim($content_model_type)); $content_model = trim($content_model); return array($content_model_type, $content_model); } /** * Convenience function that merges a list of attribute includes into * an attribute array. * @param array $attr Reference to attr array to modify * @param array $attr_includes Array of includes / string include to merge in */ public function mergeInAttrIncludes(&$attr, $attr_includes) { if (!is_array($attr_includes)) { if (empty($attr_includes)) { $attr_includes = array(); } else { $attr_includes = array($attr_includes); } } $attr[0] = $attr_includes; } /** * Convenience function that generates a lookup table with boolean * true as value. * @param string $list List of values to turn into a lookup * @note You can also pass an arbitrary number of arguments in * place of the regular argument * @return array array equivalent of list */ public function makeLookup($list) { $args = func_get_args(); if (is_string($list)) { $list = $args; } $ret = array(); foreach ($list as $value) { if (is_null($value)) { continue; } $ret[$value] = true; } return $ret; } /** * Lazy load construction of the module after determining whether * or not it's needed, and also when a finalized configuration object * is available. * @param HTMLPurifier_Config $config */ public function setup($config) { } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/Filter.php000064400000003131150437375620015144 0ustar00preFilter, * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter, * 1->postFilter. * * @note Methods are not declared abstract as it is perfectly legitimate * for an implementation not to want anything to happen on a step */ class HTMLPurifier_Filter { /** * Name of the filter for identification purposes. * @type string */ public $name; /** * Pre-processor function, handles HTML before HTML Purifier * @param string $html * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return string */ public function preFilter($html, $config, $context) { return $html; } /** * Post-processor function, handles HTML after HTML Purifier * @param string $html * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return string */ public function postFilter($html, $config, $context) { return $html; } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/AttrDef.php000064400000012113150437375620015250 0ustar00 by removing * leading and trailing whitespace, ignoring line feeds, and replacing * carriage returns and tabs with spaces. While most useful for HTML * attributes specified as CDATA, it can also be applied to most CSS * values. * * @note This method is not entirely standards compliant, as trim() removes * more types of whitespace than specified in the spec. In practice, * this is rarely a problem, as those extra characters usually have * already been removed by HTMLPurifier_Encoder. * * @warning This processing is inconsistent with XML's whitespace handling * as specified by section 3.3.3 and referenced XHTML 1.0 section * 4.7. However, note that we are NOT necessarily * parsing XML, thus, this behavior may still be correct. We * assume that newlines have been normalized. */ public function parseCDATA($string) { $string = trim($string); $string = str_replace(array("\n", "\t", "\r"), ' ', $string); return $string; } /** * Factory method for creating this class from a string. * @param string $string String construction info * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string */ public function make($string) { // default implementation, return a flyweight of this object. // If $string has an effect on the returned object (i.e. you // need to overload this method), it is best // to clone or instantiate new copies. (Instantiation is safer.) return $this; } /** * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work * properly. THIS IS A HACK! * @param string $string a CSS colour definition * @return string */ protected function mungeRgb($string) { $p = '\s*(\d+(\.\d+)?([%]?))\s*'; if (preg_match('/(rgba|hsla)\(/', $string)) { return preg_replace('/(rgba|hsla)\('.$p.','.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8,\11)', $string); } return preg_replace('/(rgb|hsl)\('.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8)', $string); } /** * Parses a possibly escaped CSS string and returns the "pure" * version of it. */ protected function expandCSSEscape($string) { // flexibly parse it $ret = ''; for ($i = 0, $c = strlen($string); $i < $c; $i++) { if ($string[$i] === '\\') { $i++; if ($i >= $c) { $ret .= '\\'; break; } if (ctype_xdigit($string[$i])) { $code = $string[$i]; for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) { if (!ctype_xdigit($string[$i])) { break; } $code .= $string[$i]; } // We have to be extremely careful when adding // new characters, to make sure we're not breaking // the encoding. $char = HTMLPurifier_Encoder::unichr(hexdec($code)); if (HTMLPurifier_Encoder::cleanUTF8($char) === '') { continue; } $ret .= $char; if ($i < $c && trim($string[$i]) !== '') { $i--; } continue; } if ($string[$i] === "\n") { continue; } } $ret .= $string[$i]; } return $ret; } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/StringHashParser.php000064400000007071150437375620017155 0ustar00 'DefaultKeyValue', * 'KEY' => 'Value', * 'KEY2' => 'Value2', * 'MULTILINE-KEY' => "Multiline\nvalue.\n", * ) * * We use this as an easy to use file-format for configuration schema * files, but the class itself is usage agnostic. * * You can use ---- to forcibly terminate parsing of a single string-hash; * this marker is used in multi string-hashes to delimit boundaries. */ class HTMLPurifier_StringHashParser { /** * @type string */ public $default = 'ID'; /** * Parses a file that contains a single string-hash. * @param string $file * @return array */ public function parseFile($file) { if (!file_exists($file)) { return false; } $fh = fopen($file, 'r'); if (!$fh) { return false; } $ret = $this->parseHandle($fh); fclose($fh); return $ret; } /** * Parses a file that contains multiple string-hashes delimited by '----' * @param string $file * @return array */ public function parseMultiFile($file) { if (!file_exists($file)) { return false; } $ret = array(); $fh = fopen($file, 'r'); if (!$fh) { return false; } while (!feof($fh)) { $ret[] = $this->parseHandle($fh); } fclose($fh); return $ret; } /** * Internal parser that acepts a file handle. * @note While it's possible to simulate in-memory parsing by using * custom stream wrappers, if such a use-case arises we should * factor out the file handle into its own class. * @param resource $fh File handle with pointer at start of valid string-hash * block. * @return array */ protected function parseHandle($fh) { $state = false; $single = false; $ret = array(); do { $line = fgets($fh); if ($line === false) { break; } $line = rtrim($line, "\n\r"); if (!$state && $line === '') { continue; } if ($line === '----') { break; } if (strncmp('--#', $line, 3) === 0) { // Comment continue; } elseif (strncmp('--', $line, 2) === 0) { // Multiline declaration $state = trim($line, '- '); if (!isset($ret[$state])) { $ret[$state] = ''; } continue; } elseif (!$state) { $single = true; if (strpos($line, ':') !== false) { // Single-line declaration list($state, $line) = explode(':', $line, 2); $line = trim($line); } else { // Use default declaration $state = $this->default; } } if ($single) { $ret[$state] = $line; $single = false; $state = false; } else { $ret[$state] .= "$line\n"; } } while (!feof($fh)); return $ret; } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php000064400000050022150437375620016662 0ustar00get('HTML.Trusted')) { $html = preg_replace_callback( '#(]*>)(\s*[^<].+?)()#si', array($this, 'scriptCallback'), $html ); } $html = $this->normalize($html, $config, $context); $cursor = 0; // our location in the text $inside_tag = false; // whether or not we're parsing the inside of a tag $array = array(); // result array // This is also treated to mean maintain *column* numbers too $maintain_line_numbers = $config->get('Core.MaintainLineNumbers'); if ($maintain_line_numbers === null) { // automatically determine line numbering by checking // if error collection is on $maintain_line_numbers = $config->get('Core.CollectErrors'); } if ($maintain_line_numbers) { $current_line = 1; $current_col = 0; $length = strlen($html); } else { $current_line = false; $current_col = false; $length = false; } $context->register('CurrentLine', $current_line); $context->register('CurrentCol', $current_col); $nl = "\n"; // how often to manually recalculate. This will ALWAYS be right, // but it's pretty wasteful. Set to 0 to turn off $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval'); $e = false; if ($config->get('Core.CollectErrors')) { $e =& $context->get('ErrorCollector'); } // for testing synchronization $loops = 0; while (++$loops) { // $cursor is either at the start of a token, or inside of // a tag (i.e. there was a < immediately before it), as indicated // by $inside_tag if ($maintain_line_numbers) { // $rcursor, however, is always at the start of a token. $rcursor = $cursor - (int)$inside_tag; // Column number is cheap, so we calculate it every round. // We're interested at the *end* of the newline string, so // we need to add strlen($nl) == 1 to $nl_pos before subtracting it // from our "rcursor" position. $nl_pos = strrpos($html, $nl, $rcursor - $length); $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1); // recalculate lines if ($synchronize_interval && // synchronization is on $cursor > 0 && // cursor is further than zero $loops % $synchronize_interval === 0) { // time to synchronize! $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor); } } $position_next_lt = strpos($html, '<', $cursor); $position_next_gt = strpos($html, '>', $cursor); // triggers on "asdf" but not "asdf " // special case to set up context if ($position_next_lt === $cursor) { $inside_tag = true; $cursor++; } if (!$inside_tag && $position_next_lt !== false) { // We are not inside tag and there still is another tag to parse $token = new HTMLPurifier_Token_Text( $this->parseText( substr( $html, $cursor, $position_next_lt - $cursor ), $config ) ); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor); } $array[] = $token; $cursor = $position_next_lt + 1; $inside_tag = true; continue; } elseif (!$inside_tag) { // We are not inside tag but there are no more tags // If we're already at the end, break if ($cursor === strlen($html)) { break; } // Create Text of rest of string $token = new HTMLPurifier_Token_Text( $this->parseText( substr( $html, $cursor ), $config ) ); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); } $array[] = $token; break; } elseif ($inside_tag && $position_next_gt !== false) { // 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); if ($segment === false) { // somehow, we attempted to access beyond the end of // the string, defense-in-depth, reported by Nate Abele break; } // Check if it's a comment if (substr($segment, 0, 3) === '!--') { // re-determine segment length, looking for --> $position_comment_end = strpos($html, '-->', $cursor); if ($position_comment_end === false) { // uh oh, we have a comment that extends to // infinity. Can't be helped: set comment // end position to end of string if ($e) { $e->send(E_WARNING, 'Lexer: Unclosed comment'); } $position_comment_end = strlen($html); $end = true; } else { $end = false; } $strlen_segment = $position_comment_end - $cursor; $segment = substr($html, $cursor, $strlen_segment); $token = new HTMLPurifier_Token_Comment( substr( $segment, 3, $strlen_segment - 3 ) ); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment); } $array[] = $token; $cursor = $end ? $position_comment_end : $position_comment_end + 3; $inside_tag = false; continue; } // Check if it's an end tag $is_end_tag = (strpos($segment, '/') === 0); if ($is_end_tag) { $type = substr($segment, 1); $token = new HTMLPurifier_Token_End($type); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); } $array[] = $token; $inside_tag = false; $cursor = $position_next_gt + 1; continue; } // Check leading character is alnum, if not, we may // have accidently grabbed an emoticon. Translate into // text and go our merry way if (!ctype_alpha($segment[0])) { // XML: $segment[0] !== '_' && $segment[0] !== ':' if ($e) { $e->send(E_NOTICE, 'Lexer: Unescaped lt'); } $token = new HTMLPurifier_Token_Text('<'); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); } $array[] = $token; $inside_tag = false; continue; } // Check if it is explicitly self closing, if so, remove // trailing slash. Remember, we could have a tag like
, so // any later token processing scripts must convert improperly // classified EmptyTags from StartTags. $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1); if ($is_self_closing) { $strlen_segment--; $segment = substr($segment, 0, $strlen_segment); } // Check if there are any attributes $position_first_space = strcspn($segment, $this->_whitespace); if ($position_first_space >= $strlen_segment) { if ($is_self_closing) { $token = new HTMLPurifier_Token_Empty($segment); } else { $token = new HTMLPurifier_Token_Start($segment); } if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); } $array[] = $token; $inside_tag = false; $cursor = $position_next_gt + 1; continue; } // Grab out all the data $type = substr($segment, 0, $position_first_space); $attribute_string = trim( substr( $segment, $position_first_space ) ); if ($attribute_string) { $attr = $this->parseAttributeString( $attribute_string, $config, $context ); } else { $attr = array(); } if ($is_self_closing) { $token = new HTMLPurifier_Token_Empty($type, $attr); } else { $token = new HTMLPurifier_Token_Start($type, $attr); } if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); } $array[] = $token; $cursor = $position_next_gt + 1; $inside_tag = false; continue; } else { // inside tag, but there's no ending > sign if ($e) { $e->send(E_WARNING, 'Lexer: Missing gt'); } $token = new HTMLPurifier_Token_Text( '<' . $this->parseText( substr($html, $cursor), $config ) ); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); } // no cursor scroll? Hmm... $array[] = $token; break; } break; } $context->destroy('CurrentLine'); $context->destroy('CurrentCol'); return $array; } /** * PHP 5.0.x compatible substr_count that implements offset and length * @param string $haystack * @param string $needle * @param int $offset * @param int $length * @return int */ protected function substrCount($haystack, $needle, $offset, $length) { static $oldVersion; if ($oldVersion === null) { $oldVersion = version_compare(PHP_VERSION, '5.1', '<'); } if ($oldVersion) { $haystack = substr($haystack, $offset, $length); return substr_count($haystack, $needle); } else { return substr_count($haystack, $needle, $offset, $length); } } /** * Takes the inside of an HTML tag and makes an assoc array of attributes. * * @param string $string Inside of tag excluding name. * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return array Assoc array of attributes. */ public function parseAttributeString($string, $config, $context) { $string = (string)$string; // quick typecast if ($string == '') { return array(); } // no attributes $e = false; if ($config->get('Core.CollectErrors')) { $e =& $context->get('ErrorCollector'); } // let's see if we can abort as quickly as possible // one equal sign, no spaces => one attribute $num_equal = substr_count($string, '='); $has_space = strpos($string, ' '); if ($num_equal === 0 && !$has_space) { // bool attribute return array($string => $string); } elseif ($num_equal === 1 && !$has_space) { // only one attribute list($key, $quoted_value) = explode('=', $string); $quoted_value = trim($quoted_value); if (!$key) { if ($e) { $e->send(E_ERROR, 'Lexer: Missing attribute key'); } return array(); } if (!$quoted_value) { return array($key => ''); } $first_char = @$quoted_value[0]; $last_char = @$quoted_value[strlen($quoted_value) - 1]; $same_quote = ($first_char == $last_char); $open_quote = ($first_char == '"' || $first_char == "'"); if ($same_quote && $open_quote) { // well behaved $value = substr($quoted_value, 1, strlen($quoted_value) - 2); } else { // not well behaved if ($open_quote) { if ($e) { $e->send(E_ERROR, 'Lexer: Missing end quote'); } $value = substr($quoted_value, 1); } else { $value = $quoted_value; } } if ($value === false) { $value = ''; } return array($key => $this->parseAttr($value, $config)); } // setup loop environment $array = array(); // return assoc array of attributes $cursor = 0; // current position in string (moves forward) $size = strlen($string); // size of the string (stays the same) // if we have unquoted attributes, the parser expects a terminating // space, so let's guarantee that there's always a terminating space. $string .= ' '; $old_cursor = -1; while ($cursor < $size) { if ($old_cursor >= $cursor) { throw new Exception("Infinite loop detected"); } $old_cursor = $cursor; $cursor += ($value = strspn($string, $this->_whitespace, $cursor)); // grab the key $key_begin = $cursor; //we're currently at the start of the key // scroll past all characters that are the key (not whitespace or =) $cursor += strcspn($string, $this->_whitespace . '=', $cursor); $key_end = $cursor; // now at the end of the key $key = substr($string, $key_begin, $key_end - $key_begin); if (!$key) { if ($e) { $e->send(E_ERROR, 'Lexer: Missing attribute key'); } $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop continue; // empty key } // scroll past all whitespace $cursor += strspn($string, $this->_whitespace, $cursor); if ($cursor >= $size) { $array[$key] = $key; break; } // if the next character is an equal sign, we've got a regular // pair, otherwise, it's a bool attribute $first_char = @$string[$cursor]; if ($first_char == '=') { // key="value" $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]; if ($char == '"' || $char == "'") { // it's quoted, end bound is $char $cursor++; $value_begin = $cursor; $cursor = strpos($string, $char, $cursor); $value_end = $cursor; } else { // it's not quoted, end bound is whitespace $value_begin = $cursor; $cursor += strcspn($string, $this->_whitespace, $cursor); $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->parseAttr($value, $config); $cursor++; } else { // boolattr if ($key !== '') { $array[$key] = $key; } else { // purely theoretical if ($e) { $e->send(E_ERROR, 'Lexer: Missing attribute key'); } } } } return $array; } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php000064400000030257150437375620016077 0ustar00factory = new HTMLPurifier_TokenFactory(); } /** * @param string $html * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return HTMLPurifier_Token[] */ public function tokenizeHTML($html, $config, $context) { $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($this, 'callbackArmorCommentEntities'), $html); do { $old = $html; $html = preg_replace("/<($char)/i", '<\\1', $html); } while ($html !== $old); $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments } // preprocess html, essential for UTF-8 $html = $this->wrapHTML($html, $config, $context); $doc = new DOMDocument(); $doc->encoding = 'UTF-8'; // theoretically, the above has this covered $options = 0; if ($config->get('Core.AllowParseManyTags') && defined('LIBXML_PARSEHUGE')) { $options |= LIBXML_PARSEHUGE; } set_error_handler(array($this, 'muteErrorHandler')); // loadHTML() fails on PHP 5.3 when second parameter is given if ($options) { $doc->loadHTML($html, $options); } else { $doc->loadHTML($html); } restore_error_handler(); $body = $doc->getElementsByTagName('html')->item(0)-> // getElementsByTagName('body')->item(0); // $div = $body->getElementsByTagName('div')->item(0); //
$tokens = array(); $this->tokenizeDOM($div, $tokens, $config); // If the div has a sibling, that means we tripped across // a premature
tag. So remove the div we parsed, // and then tokenize the rest of body. We can't tokenize // the sibling directly as we'll lose the tags in that case. if ($div->nextSibling) { $body->removeChild($div); $this->tokenizeDOM($body, $tokens, $config); } return $tokens; } /** * Iterative function that tokenizes a node, putting it into an accumulator. * To iterate is human, to recurse divine - L. Peter Deutsch * @param DOMNode $node DOMNode to be tokenized. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. * @return HTMLPurifier_Token of node appended to previously passed tokens. */ protected function tokenizeDOM($node, &$tokens, $config) { $level = 0; $nodes = array($level => new HTMLPurifier_Queue(array($node))); $closingNodes = array(); do { while (!$nodes[$level]->isEmpty()) { $node = $nodes[$level]->shift(); // FIFO $collect = $level > 0 ? true : false; $needEndingTag = $this->createStartNode($node, $tokens, $collect, $config); if ($needEndingTag) { $closingNodes[$level][] = $node; } if ($node->childNodes && $node->childNodes->length) { $level++; $nodes[$level] = new HTMLPurifier_Queue(); foreach ($node->childNodes as $childNode) { $nodes[$level]->push($childNode); } } } $level--; if ($level && isset($closingNodes[$level])) { while ($node = array_pop($closingNodes[$level])) { $this->createEndNode($node, $tokens); } } } while ($level > 0); } /** * Portably retrieve the tag name of a node; deals with older versions * of libxml like 2.7.6 * @param DOMNode $node */ protected function getTagName($node) { if (isset($node->tagName)) { return $node->tagName; } else if (isset($node->nodeName)) { return $node->nodeName; } else if (isset($node->localName)) { return $node->localName; } return null; } /** * Portably retrieve the data of a node; deals with older versions * of libxml like 2.7.6 * @param DOMNode $node */ protected function getData($node) { if (isset($node->data)) { return $node->data; } else if (isset($node->nodeValue)) { return $node->nodeValue; } else if (isset($node->textContent)) { return $node->textContent; } return null; } /** * @param DOMNode $node DOMNode to be tokenized. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. * @param bool $collect Says whether or start and close are collected, set to * false at first recursion because it's the implicit DIV * tag you're dealing with. * @return bool if the token needs an endtoken * @todo data and tagName properties don't seem to exist in DOMNode? */ protected function createStartNode($node, &$tokens, $collect, $config) { // intercept non element nodes. WE MUST catch all of them, // but we're not getting the character reference nodes because // those should have been preprocessed if ($node->nodeType === XML_TEXT_NODE) { $data = $this->getData($node); // Handle variable data property if ($data !== null) { $tokens[] = $this->factory->createText($data); } return false; } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) { // undo libxml's special treatment of #i', '', $html); } return $html; } /** * Takes a string of HTML (fragment or document) and returns the content * @todo Consider making protected */ public function extractBody($html) { $matches = array(); $result = preg_match('|(.*?)]*>(.*)|is', $html, $matches); if ($result) { // Make sure it's not in a comment $comment_start = strrpos($matches[1], ''); if ($comment_start === false || ($comment_end !== false && $comment_end > $comment_start)) { return $matches[2]; } } return $html; } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/ChildDef.php000064400000003027150437375620015365 0ustar00elements; } /** * Validates nodes according to definition and returns modification. * * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node * @param HTMLPurifier_Config $config HTMLPurifier_Config object * @param HTMLPurifier_Context $context HTMLPurifier_Context object * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children */ abstract public function validateChildren($children, $config, $context); } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/URIParser.php000064400000004366150437375620015546 0ustar00percentEncoder = new HTMLPurifier_PercentEncoder(); } /** * Parses a URI. * @param $uri string URI to parse * @return HTMLPurifier_URI representation of URI. This representation has * not been validated yet and may not conform to RFC. */ public function parse($uri) { $uri = $this->percentEncoder->normalize($uri); // Regexp is as per Appendix B. // Note that ["<>] are an addition to the RFC's recommended // characters, because they represent external delimeters. $r_URI = '!'. '(([a-zA-Z0-9\.\+\-]+):)?'. // 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) { $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\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); } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/AttrValidator.php000064400000014654150437375620016513 0ustar00getHTMLDefinition(); $e =& $context->get('ErrorCollector', true); // initialize IDAccumulator if necessary $ok =& $context->get('IDAccumulator', true); if (!$ok) { $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); $context->register('IDAccumulator', $id_accumulator); } // initialize CurrentToken if necessary $current_token =& $context->get('CurrentToken', true); if (!$current_token) { $context->register('CurrentToken', $token); } if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty ) { return; } // create alias to global definition array, see also $defs // DEFINITION CALL $d_defs = $definition->info_global_attr; // don't update token until the very end, to ensure an atomic update $attr = $token->attr; // do global transformations (pre) // nothing currently utilizes this foreach ($definition->info_attr_transform_pre as $transform) { $attr = $transform->transform($o = $attr, $config, $context); if ($e) { if ($attr != $o) { $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); } } } // do local transformations only applicable to this element (pre) // ex.

to

foreach ($definition->info[$token->name]->attr_transform_pre as $transform) { $attr = $transform->transform($o = $attr, $config, $context); if ($e) { if ($attr != $o) { $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); } } } // create alias to this element's attribute definition array, see // also $d_defs (global attribute definition array) // DEFINITION CALL $defs = $definition->info[$token->name]->attr; $attr_key = false; $context->register('CurrentAttr', $attr_key); // iterate through all the attribute keypairs // Watch out for name collisions: $key has previously been used foreach ($attr as $attr_key => $value) { // call the definition if (isset($defs[$attr_key])) { // there is a local definition defined if ($defs[$attr_key] === false) { // We've explicitly been told not to allow this element. // This is usually when there's a global definition // that must be overridden. // Theoretically speaking, we could have a // AttrDef_DenyAll, but this is faster! $result = false; } else { // validate according to the element's definition $result = $defs[$attr_key]->validate( $value, $config, $context ); } } elseif (isset($d_defs[$attr_key])) { // there is a global definition defined, validate according // to the global definition $result = $d_defs[$attr_key]->validate( $value, $config, $context ); } else { // system never heard of the attribute? DELETE! $result = false; } // put the results into effect if ($result === false || $result === null) { // this is a generic error message that should replaced // with more specific ones when possible if ($e) { $e->send(E_ERROR, 'AttrValidator: Attribute removed'); } // remove the attribute unset($attr[$attr_key]); } elseif (is_string($result)) { // generally, if a substitution is happening, there // was some sort of implicit correction going on. We'll // delegate it to the attribute classes to say exactly what. // simple substitution $attr[$attr_key] = $result; } else { // nothing happens } // we'd also want slightly more complicated substitution // involving an array as the return value, // although we're not sure how colliding attributes would // resolve (certain ones would be completely overriden, // others would prepend themselves). } $context->destroy('CurrentAttr'); // post transforms // global (error reporting untested) foreach ($definition->info_attr_transform_post as $transform) { $attr = $transform->transform($o = $attr, $config, $context); if ($e) { if ($attr != $o) { $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); } } } // local (error reporting untested) foreach ($definition->info[$token->name]->attr_transform_post as $transform) { $attr = $transform->transform($o = $attr, $config, $context); if ($e) { if ($attr != $o) { $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); } } } $token->attr = $attr; // destroy CurrentToken if we made it ourselves if (!$current_token) { $context->destroy('CurrentToken'); } } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/Language/messages/en.php000064400000007720150437375620017663 0ustar00 'HTML Purifier', // for unit testing purposes 'LanguageFactoryTest: Pizza' => 'Pizza', 'LanguageTest: List' => '$1', 'LanguageTest: Hash' => '$1.Keys; $1.Values', 'Item separator' => ', ', 'Item separator last' => ' and ', // non-Harvard style 'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.', 'ErrorCollector: At line' => ' at line $line', 'ErrorCollector: Incidental errors' => 'Incidental errors', 'Lexer: Unclosed comment' => 'Unclosed comment', 'Lexer: Unescaped lt' => 'Unescaped less-than sign (<) should be <', 'Lexer: Missing gt' => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped', 'Lexer: Missing attribute key' => 'Attribute declaration has no key', 'Lexer: Missing end quote' => 'Attribute declaration has no end quote', 'Lexer: Extracted body' => 'Removed document metadata tags', 'Strategy_RemoveForeignElements: Tag transform' => '<$1> element transformed into $CurrentToken.Serialized', 'Strategy_RemoveForeignElements: Missing required attribute' => '$CurrentToken.Compact element missing required attribute $1', '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: 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_RemoveForeignElements: Trailing hyphen in comment removed' => 'Trailing hyphen(s) in comment removed', 'Strategy_RemoveForeignElements: Hyphens in comment collapsed' => 'Double hyphens in comments are not allowed, and were collapsed into single hyphens', 'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed', 'Strategy_MakeWellFormed: Unnecessary end tag to text' => 'Unnecessary $CurrentToken.Serialized tag converted to text', 'Strategy_MakeWellFormed: Tag auto closed' => '$1.Compact started on line $1.Line auto-closed by $CurrentToken.Compact', 'Strategy_MakeWellFormed: Tag carryover' => '$1.Compact started on line $1.Line auto-continued into $CurrentToken.Compact', 'Strategy_MakeWellFormed: Stray end tag removed' => 'Stray $CurrentToken.Serialized tag removed', 'Strategy_MakeWellFormed: Stray end tag to text' => 'Stray $CurrentToken.Serialized tag converted to text', 'Strategy_MakeWellFormed: Tag closed by element end' => '$1.Compact tag started on line $1.Line closed by end of $CurrentToken.Serialized', 'Strategy_MakeWellFormed: Tag closed by document end' => '$1.Compact tag started on line $1.Line closed by end of document', 'Strategy_FixNesting: Node removed' => '$CurrentToken.Compact node removed', 'Strategy_FixNesting: Node excluded' => '$CurrentToken.Compact node removed due to descendant exclusion by ancestor element', 'Strategy_FixNesting: Node reorganized' => 'Contents of $CurrentToken.Compact node reorganized to enforce its content model', 'Strategy_FixNesting: Node contents removed' => 'Contents of $CurrentToken.Compact node removed', 'AttrValidator: Attributes transformed' => 'Attributes on $CurrentToken.Compact transformed from $1.Keys to $2.Keys', 'AttrValidator: Attribute removed' => '$CurrentAttr.Name attribute on $CurrentToken.Compact removed', ); $errorNames = array( E_ERROR => 'Error', E_WARNING => 'Warning', E_NOTICE => 'Notice' ); // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier/AttrTransform.php000064400000003705150437375620016534 0ustar00 $attributes) { $allowed_elements[$element] = true; foreach ($attributes as $attribute => $x) { $allowed_attributes["$element.$attribute"] = true; } } $config->set('HTML.AllowedElements', $allowed_elements); $config->set('HTML.AllowedAttributes', $allowed_attributes); if ($allowed_protocols !== null) { $config->set('URI.AllowedSchemes', $allowed_protocols); } $purifier = new HTMLPurifier($config); return $purifier->purify($string); } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier.includes.php000064400000024424150437375620015534 0ustar00config = HTMLPurifier_Config::create($config); $this->strategy = new HTMLPurifier_Strategy_Core(); } /** * Adds a filter to process the output. First come first serve * * @param HTMLPurifier_Filter $filter HTMLPurifier_Filter object */ public function addFilter($filter) { trigger_error( 'HTMLPurifier->addFilter() is deprecated, use configuration directives' . ' in the Filter namespace or Filter.Custom', E_USER_WARNING ); $this->filters[] = $filter; } /** * Filters an HTML snippet/document to be XSS-free and standards-compliant. * * @param string $html String of HTML to purify * @param HTMLPurifier_Config $config Config object for this operation, * if omitted, defaults to the config object specified during this * object's construction. The parameter can also be any type * that HTMLPurifier_Config::create() supports. * * @return string Purified HTML */ public function purify($html, $config = null) { // :TODO: make the config merge in, instead of replace $config = $config ? HTMLPurifier_Config::create($config) : $this->config; // implementation is partially environment dependant, partially // configuration dependant $lexer = HTMLPurifier_Lexer::create($config); $context = new HTMLPurifier_Context(); // setup HTML generator $this->generator = new HTMLPurifier_Generator($config, $context); $context->register('Generator', $this->generator); // set up global context variables if ($config->get('Core.CollectErrors')) { // may get moved out if other facilities use it $language_factory = HTMLPurifier_LanguageFactory::instance(); $language = $language_factory->create($config, $context); $context->register('Locale', $language); $error_collector = new HTMLPurifier_ErrorCollector($context); $context->register('ErrorCollector', $error_collector); } // setup id_accumulator context, necessary due to the fact that // AttrValidator can be called from many places $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); $context->register('IDAccumulator', $id_accumulator); $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context); // setup filters $filter_flags = $config->getBatch('Filter'); $custom_filters = $filter_flags['Custom']; unset($filter_flags['Custom']); $filters = array(); foreach ($filter_flags as $filter => $flag) { if (!$flag) { continue; } if (strpos($filter, '.') !== false) { continue; } $class = "HTMLPurifier_Filter_$filter"; $filters[] = new $class; } foreach ($custom_filters as $filter) { // maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat $filters[] = $filter; } $filters = array_merge($filters, $this->filters); // maybe prepare(), but later for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) { $html = $filters[$i]->preFilter($html, $config, $context); } // purified HTML $html = $this->generator->generateFromTokens( // list of tokens $this->strategy->execute( // list of un-purified tokens $lexer->tokenizeHTML( // un-purified HTML $html, $config, $context ), $config, $context ) ); for ($i = $filter_size - 1; $i >= 0; $i--) { $html = $filters[$i]->postFilter($html, $config, $context); } $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context); $this->context =& $context; return $html; } /** * Filters an array of HTML snippets * * @param string[] $array_of_html Array of html snippets * @param HTMLPurifier_Config $config Optional config object for this operation. * See HTMLPurifier::purify() for more details. * * @return string[] Array of purified HTML */ public function purifyArray($array_of_html, $config = null) { $context_array = array(); $array = array(); foreach($array_of_html as $key=>$value){ if (is_array($value)) { $array[$key] = $this->purifyArray($value, $config); } else { $array[$key] = $this->purify($value, $config); } $context_array[$key] = $this->context; } $this->context = $context_array; return $array; } /** * Singleton for enforcing just one HTML Purifier in your system * * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype * HTMLPurifier instance to overload singleton with, * or HTMLPurifier_Config instance to configure the * generated version with. * * @return HTMLPurifier */ public static function instance($prototype = null) { if (!self::$instance || $prototype) { if ($prototype instanceof HTMLPurifier) { self::$instance = $prototype; } elseif ($prototype) { self::$instance = new HTMLPurifier($prototype); } else { self::$instance = new HTMLPurifier(); } } return self::$instance; } /** * Singleton for enforcing just one HTML Purifier in your system * * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype * HTMLPurifier instance to overload singleton with, * or HTMLPurifier_Config instance to configure the * generated version with. * * @return HTMLPurifier * @note Backwards compatibility, see instance() */ public static function getInstance($prototype = null) { return HTMLPurifier::instance($prototype); } } // vim: et sw=4 sts=4 htmlpurifier/library/HTMLPurifier.path.php000064400000000353150437375620014655 0ustar00=5.2" }, "autoload": { "psr-0": { "HTMLPurifier": "library/" }, "files": ["library/HTMLPurifier.composer.php"], "exclude-from-classmap": [ "/library/HTMLPurifier/Language/" ] } } htmlpurifier/CREDITS000064400000000525150437375620010314 0ustar00 CREDITS Almost everything written by Edward Z. Yang (Ambush Commander). Lots of thanks to the DevNetwork Community for their help (see docs/ref-devnetwork.html for more details), Feyd especially (namely IPv6 and optimization). Thanks to RSnake for letting me package his fantastic XSS cheatsheet for a smoketest. vim: et sw=4 sts=4 htmlpurifier/LICENSE000064400000063530150437375620010306 0ustar00 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! vim: et sw=4 sts=4 htmlpurifier/VERSION000064400000000006150437375620010336 0ustar004.14.0