first commit

This commit is contained in:
OPSXCQ 2016-12-02 17:19:11 -02:00
parent 985a5c928c
commit f40a84879c
No known key found for this signature in database
GPG key ID: 9AD730FE9CDE5661
551 changed files with 72374 additions and 24 deletions

View file

@ -0,0 +1,286 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
require_once 'IDS/Caching/Interface.php';
/**
* Needed SQL:
*
#create the database
CREATE DATABASE IF NOT EXISTS `phpids` DEFAULT CHARACTER
SET utf8 COLLATE utf8_general_ci;
DROP TABLE IF EXISTS `cache`;
#now select the created datbase and create the table
CREATE TABLE `cache` (
`type` VARCHAR( 32 ) NOT null ,
`data` TEXT NOT null ,
`created` DATETIME NOT null ,
`modified` DATETIME NOT null
) ENGINE = MYISAM ;
*/
/**
* Database caching wrapper
*
* This class inhabits functionality to get and set cache via a database.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Groupup
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Database.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
* @since Version 0.4
*/
class IDS_Caching_Database implements IDS_Caching_Interface
{
/**
* Caching type
*
* @var string
*/
private $type = null;
/**
* Cache configuration
*
* @var array
*/
private $config = null;
/**
* DBH
*
* @var object
*/
private $handle = null;
/**
* Holds an instance of this class
*
* @var object
*/
private static $cachingInstance = null;
/**
* Constructor
*
* Connects to database.
*
* @param string $type caching type
* @param array $init the IDS_Init object
*
* @return void
*/
public function __construct($type, $init)
{
$this->type = $type;
$this->config = $init->config['Caching'];
$this->handle = $this->_connect();
}
/**
* Returns an instance of this class
*
* @param string $type caching type
* @param array $init the IDS_Init object
*
* @return object $this
*/
public static function getInstance($type, $init)
{
if (!self::$cachingInstance) {
self::$cachingInstance = new IDS_Caching_Database($type, $init);
}
return self::$cachingInstance;
}
/**
* Writes cache data into the database
*
* @param array $data the caching data
*
* @throws PDOException if a db error occurred
* @return object $this
*/
public function setCache(array $data)
{
$handle = $this->handle;
$rows = $handle->query('SELECT created FROM `' .
$handle->quote($this->config['table']).'`');
if (!$rows || $rows->rowCount() === 0) {
$this->_write($handle, $data);
} else {
foreach ($rows as $row) {
if ((time()-strtotime($row['created'])) >
$this->config['expiration_time']) {
$this->_write($handle, $data);
}
}
}
return $this;
}
/**
* Returns the cached data
*
* Note that this method returns false if either type or file cache is
* not set
*
* @throws PDOException if a db error occurred
* @return mixed cache data or false
*/
public function getCache()
{
try{
$handle = $this->handle;
$result = $handle->prepare('SELECT * FROM ' .
$handle->quote($this->config['table']) .
' where type=?');
$result->execute(array($this->type));
foreach ($result as $row) {
return unserialize($row['data']);
}
} catch (PDOException $e) {
die('PDOException: ' . $e->getMessage());
}
return false;
}
/**
* Connect to database and return a handle
*
* @return object dbh
* @throws PDOException if a db error occurred
*/
private function _connect()
{
// validate connection parameters
if (!$this->config['wrapper']
|| !$this->config['user']
|| !$this->config['password']
|| !$this->config['table']) {
throw new Exception('
Insufficient connection parameters'
);
}
// try to connect
try {
$handle = new PDO(
$this->config['wrapper'],
$this->config['user'],
$this->config['password']
);
} catch (PDOException $e) {
die('PDOException: ' . $e->getMessage());
}
return $handle;
}
/**
* Write the cache data to the table
*
* @param object $handle the database handle
* @param array $data the caching data
*
* @return object dbh
* @throws PDOException if a db error occurred
*/
private function _write($handle, $data)
{
try {
$handle->query('TRUNCATE ' .
$this->config['table'].'');
$statement = $handle->prepare('
INSERT INTO `' .
$this->config['table'].'` (
type,
data,
created,
modified
)
VALUES (
:type,
:data,
now(),
now()
)
');
$statement->bindParam('type',
$handle->quote($this->type));
$statement->bindParam('data', serialize($data));
if (!$statement->execute()) {
throw new PDOException($statement->errorCode());
}
} catch (PDOException $e) {
die('PDOException: ' . $e->getMessage());
}
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,94 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
/**
* Caching factory
*
* This class is used as a factory to load the correct concrete caching
* implementation.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Factory.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
* @since Version 0.4
*/
class IDS_Caching
{
/**
* Factory method
*
* @param array $init the IDS_Init object
* @param string $type the caching type
*
* @return object the caching facility
*/
public static function factory($init, $type)
{
$object = false;
$wrapper = preg_replace(
'/\W+/m',
null,
ucfirst($init->config['Caching']['caching'])
);
$class = 'IDS_Caching_' . $wrapper;
$path = dirname(__FILE__) . DIRECTORY_SEPARATOR .
$wrapper . '.php';
if (file_exists($path)) {
include_once $path;
if (class_exists($class)) {
$object = call_user_func(array($class, 'getInstance'),
$type, $init);
}
}
return $object;
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,183 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
require_once 'IDS/Caching/Interface.php';
/**
* File caching wrapper
*
* This class inhabits functionality to get and set cache via a static flatfile.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:File.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
* @since Version 0.4
*/
class IDS_Caching_File implements IDS_Caching_Interface
{
/**
* Caching type
*
* @var string
*/
private $type = null;
/**
* Cache configuration
*
* @var array
*/
private $config = null;
/**
* Path to cache file
*
* @var string
*/
private $path = null;
/**
* Holds an instance of this class
*
* @var object
*/
private static $cachingInstance = null;
/**
* Constructor
*
* @param string $type caching type
* @param array $init the IDS_Init object
*
* @return void
*/
public function __construct($type, $init)
{
$this->type = $type;
$this->config = $init->config['Caching'];
$this->path = $init->getBasePath() . $this->config['path'];
if (file_exists($this->path) && !is_writable($this->path)) {
throw new Exception('Make sure all files in ' .
htmlspecialchars($this->path, ENT_QUOTES, 'UTF-8') .
'are writeable!');
}
}
/**
* Returns an instance of this class
*
* @param string $type caching type
* @param array $init the IDS_Init object
*
* @return object $this
*/
public static function getInstance($type, $init)
{
if (!self::$cachingInstance) {
self::$cachingInstance = new IDS_Caching_File($type, $init);
}
return self::$cachingInstance;
}
/**
* Writes cache data into the file
*
* @param array $data the cache data
*
* @throws Exception if cache file couldn't be created
* @return object $this
*/
public function setCache(array $data)
{
if (!is_writable(preg_replace('/[\/][^\/]+\.[^\/]++$/', null,
$this->path))) {
throw new Exception('Temp directory ' .
htmlspecialchars($this->path, ENT_QUOTES, 'UTF-8') .
' seems not writable');
}
if ((!file_exists($this->path) || (time()-filectime($this->path)) >
$this->config['expiration_time'])) {
$handle = @fopen($this->path, 'w+');
if (!$handle) {
throw new Exception("Cache file couldn't be created");
}
fwrite($handle, serialize($data));
fclose($handle);
}
return $this;
}
/**
* Returns the cached data
*
* Note that this method returns false if either type or file cache is
* not set
*
* @return mixed cache data or false
*/
public function getCache()
{
// make sure filters are parsed again if cache expired
if (file_exists($this->path) && (time()-filectime($this->path)) <
$this->config['expiration_time']) {
$data = unserialize(file_get_contents($this->path));
return $data;
}
return false;
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,73 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
/**
* Caching wrapper interface
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @version SVN: $Id:Interface.php 517 2007-09-15 15:04:13Z mario $
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @since Version 0.4
* @link http://php-ids.org/
*/
interface IDS_Caching_Interface
{
/**
* Interface method
*
* @param array $data the cache data
*
* @return void
*/
public function setCache(array $data);
/**
* Interface method
*
* @return void
*/
public function getCache();
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,228 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
require_once 'IDS/Caching/Interface.php';
/**
* File caching wrapper
*
* This class inhabits functionality to get and set cache via memcached.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Groupoup
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Memcached.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
* @since Version 0.4
*/
class IDS_Caching_Memcached implements IDS_Caching_Interface
{
/**
* Caching type
*
* @var string
*/
private $type = null;
/**
* Cache configuration
*
* @var array
*/
private $config = null;
/**
* Path to memcache timestamp file
*
* @var string
*/
private $path = null;
/**
* Flag if the filter storage has been found in memcached
*
* @var boolean
*/
private $isCached = false;
/**
* Memcache object
*
* @var object
*/
private $memcache = null;
/**
* Holds an instance of this class
*
* @var object
*/
private static $cachingInstance = null;
/**
* Constructor
*
* @param string $type caching type
* @param array $init the IDS_Init object
*
* @throws Exception if necessary files aren't writeable
* @return void
*/
public function __construct($type, $init)
{
$this->type = $type;
$this->config = $init->config['Caching'];
$this->path = $init->getBasePath() . $this->config['path'];
$this->_connect();
if (file_exists($this->path) && !is_writable($this->path)) {
throw new Exception('Make sure all files in ' .
htmlspecialchars($this->path, ENT_QUOTES, 'UTF-8') .
' are writeable!');
}
}
/**
* Returns an instance of this class
*
* @param string $type caching type
* @param array $init the IDS_Init object
*
* @return object $this
*/
public static function getInstance($type, $init)
{
if (!self::$cachingInstance) {
self::$cachingInstance = new IDS_Caching_Memcached($type, $init);
}
return self::$cachingInstance;
}
/**
* Writes cache data
*
* @param array $data the caching data
*
* @throws Exception if necessary files aren't writeable
* @return object $this
*/
public function setCache(array $data)
{
if (!file_exists($this->path)) {
$handle = fopen($this->path, 'w');
fclose($handle);
}
if (!is_writable($this->path)) {
throw new Exception('Make sure all files in ' .
htmlspecialchars($this->path, ENT_QUOTES, 'UTF-8') .
' are writeable!');
}
if(!$this->isCached) {
$this->memcache->set(
$this->config['key_prefix'] . '.storage',
$data, false, $this->config['expiration_time']
);
}
return $this;
}
/**
* Returns the cached data
*
* Note that this method returns false if either type or file cache is
* not set
*
* @return mixed cache data or false
*/
public function getCache()
{
$data = $this->memcache->get(
$this->config['key_prefix'] .
'.storage'
);
$this->isCached = !empty($data);
return $data;
}
/**
* Connect to the memcached server
*
* @throws Exception if connection parameters are insufficient
* @return void
*/
private function _connect()
{
if ($this->config['host'] && $this->config['port']) {
// establish the memcache connection
$this->memcache = new Memcache;
$this->memcache->pconnect($this->config['host'],
$this->config['port']);
$this->path = $this->config['tmp_path'];
if(isset($init->config['General']['base_path'])
&& $init->config['General']['base_path']
&& isset($init->config['General']['use_base_path'])
&& $init->config['General']['use_base_path']) {
$this->source = $init->config['General']['base_path'] . $this->path;
}
} else {
throw new Exception('Insufficient connection parameters');
}
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,146 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
require_once 'IDS/Caching/Interface.php';
/**
* File caching wrapper
*
* This class inhabits functionality to get and set cache via session.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Session.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
* @since Version 0.4
*/
class IDS_Caching_Session implements IDS_Caching_Interface
{
/**
* Caching type
*
* @var string
*/
private $type = null;
/**
* Cache configuration
*
* @var array
*/
private $config = null;
/**
* Holds an instance of this class
*
* @var object
*/
private static $cachingInstance = null;
/**
* Constructor
*
* @param string $type caching type
* @param array $init the IDS_Init object
*
* @return void
*/
public function __construct($type, $init)
{
$this->type = $type;
$this->config = $init->config['Caching'];
}
/**
* Returns an instance of this class
*
* @param string $type caching type
* @param array $init the IDS_Init object
*
* @return object $this
*/
public static function getInstance($type, $init)
{
if (!self::$cachingInstance) {
self::$cachingInstance = new IDS_Caching_Session($type, $init);
}
return self::$cachingInstance;
}
/**
* Writes cache data into the session
*
* @param array $data the caching data
*
* @return object $this
*/
public function setCache(array $data)
{
$_SESSION['PHPIDS'][$this->type] = $data;
return $this;
}
/**
* Returns the cached data
*
* Note that this method returns false if either type or file cache is not set
*
* @return mixed cache data or false
*/
public function getCache()
{
if ($this->type && $_SESSION['PHPIDS'][$this->type]) {
return $_SESSION['PHPIDS'][$this->type];
}
return false;
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,89 @@
; PHPIDS Config.ini
; General configuration settings
; !!!DO NOT PLACE THIS FILE INSIDE THE WEB-ROOT IF DATABASE CONNECTION DATA WAS ADDED!!!
[General]
; basic settings - customize to make the PHPIDS work at all
filter_type = xml
base_path = /full/path/to/IDS/
use_base_path = false
filter_path = default_filter.xml
tmp_path = tmp
scan_keys = false
; in case you want to use a different HTMLPurifier source, specify it here
; By default, those files are used that are being shipped with PHPIDS
HTML_Purifier_Path = IDS/vendors/htmlpurifier/HTMLPurifier.auto.php
HTML_Purifier_Cache = IDS/vendors/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer
; define which fields contain html and need preparation before
; hitting the PHPIDS rules (new in PHPIDS 0.5)
html[] = __wysiwyg
; define which fields contain JSON data and should be treated as such
; for fewer false positives (new in PHPIDS 0.5.3)
json[] = __jsondata
; define which fields shouldn't be monitored (a[b]=c should be referenced via a.b)
exceptions[] = __utmz
exceptions[] = __utmc
; PHPIDS should run with PHP 5.1.2 but this is untested - set
; this value to force compatibilty with minor versions
min_php_version = 5.1.6
; If you use the PHPIDS logger you can define specific configuration here
[Logging]
; file logging
path = tmp/phpids_log.txt
; email logging
; note that enabling safemode you can prevent spam attempts,
; see documentation
recipients[] = test@test.com.invalid
subject = "PHPIDS detected an intrusion attempt!"
header = "From: <PHPIDS> info@php-ids.org"
envelope = ""
safemode = true
urlencode = true
allowed_rate = 15
; database logging
wrapper = "mysql:host=localhost;port=3306;dbname=phpids"
user = phpids_user
password = 123456
table = intrusions
; If you would like to use other methods than file caching you can configure them here
[Caching]
; caching: session|file|database|memcached|none
caching = file
expiration_time = 600
; file cache
path = tmp/default_filter.cache
; database cache
wrapper = "mysql:host=localhost;port=3306;dbname=phpids"
user = phpids_user
password = 123456
table = cache
; memcached
;host = localhost
;port = 11211
;key_prefix = PHPIDS
;tmp_path = tmp/memcache.timestamp

View file

@ -0,0 +1,721 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
/**
* PHPIDS specific utility class to convert charsets manually
*
* Note that if you make use of IDS_Converter::runAll(), existing class
* methods will be executed in the same order as they are implemented in the
* class tree!
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Converter.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
*/
class IDS_Converter
{
/**
* Runs all converter functions
*
* Note that if you make use of IDS_Converter::runAll(), existing class
* methods will be executed in the same order as they are implemented in the
* class tree!
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function runAll($value)
{
foreach (get_class_methods(__CLASS__) as $method) {
if (strpos($method, 'run') === 0) {
continue;
}
$value = self::$method($value);
}
return $value;
}
/**
* Check for comments and erases them if available
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertFromCommented($value)
{
// check for existing comments
if (preg_match('/(?:\<!-|-->|\/\*|\*\/|\/\/\W*\w+\s*$)|' .
'(?:--[^-]*-)/ms', $value)) {
$pattern = array(
'/(?:(?:<!)(?:(?:--(?:[^-]*(?:-[^-]+)*)--\s*)*)(?:>))/ms',
'/(?:(?:\/\*\/*[^\/\*]*)+\*\/)/ms',
'/(?:--[^-]*-)/ms'
);
$converted = preg_replace($pattern, ';', $value);
$value .= "\n" . $converted;
}
// deal with x509 false alerts
$value = preg_replace('/(\w+)\/\/(\w+)/m', '$1/$2', $value);
$value = preg_replace('/(\w+)\/\+(\w+)/m', '$1/$2', $value);
//make sure inline comments are detected and converted correctly
$value = preg_replace('/(<\w+)\/+(\w+=?)/m', '$1/$2', $value);
$value = preg_replace('/[^\\\:]\/\/(.*)$/m', '/**/$1', $value);
return $value;
}
/**
* Strip newlines
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertFromNewLines($value)
{
//check for inline linebreaks
$search = array('\r', '\n', '\f', '\t', '\v');
$value = str_replace($search, ';', $value);
//convert real linebreaks
return preg_replace('/(?:\n|\r|\v)/m', ' ', $value);
}
/**
* Checks for common charcode pattern and decodes them
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertFromJSCharcode($value)
{
$matches = array();
// check if value matches typical charCode pattern
if (preg_match_all('/(?:[\d+-=\/\* ]+(?:\s?,\s?[\d+-=\/\* ]+)){4,}/ms',
$value, $matches)) {
$converted = '';
$string = implode(',', $matches[0]);
$string = preg_replace('/\s/', '', $string);
$string = preg_replace('/\w+=/', '', $string);
$charcode = explode(',', $string);
foreach ($charcode as $char) {
$char = preg_replace('/\W0/s', '', $char);
if (preg_match_all('/\d*[+-\/\* ]\d+/', $char, $matches)) {
$match = preg_split('/(\W?\d+)/',
(implode('', $matches[0])),
null,
PREG_SPLIT_DELIM_CAPTURE);
if (array_sum($match) >= 20 && array_sum($match) <= 127) {
$converted .= chr(array_sum($match));
}
} elseif (!empty($char) && $char >= 20 && $char <= 127) {
$converted .= chr($char);
}
}
$value .= "\n" . $converted;
}
// check for octal charcode pattern
if (preg_match_all('/(?:(?:[\\\]+\d+[ \t]*){8,})/ims', $value, $matches)) {
$converted = '';
$charcode = explode('\\', preg_replace('/\s/', '', implode(',',
$matches[0])));
foreach ($charcode as $char) {
if (!empty($char)) {
if (octdec($char) >= 20 && octdec($char) <= 127) {
$converted .= chr(octdec($char));
}
}
}
$value .= "\n" . $converted;
}
// check for hexadecimal charcode pattern
if (preg_match_all('/(?:(?:[\\\]+\w+\s*){8,})/ims', $value, $matches)) {
$converted = '';
$charcode = explode('\\', preg_replace('/[ux]/', '', implode(',',
$matches[0])));
foreach ($charcode as $char) {
if (!empty($char)) {
if (hexdec($char) >= 20 && hexdec($char) <= 127) {
$converted .= chr(hexdec($char));
}
}
}
$value .= "\n" . $converted;
}
return $value;
}
/**
* Eliminate JS regex modifiers
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertJSRegexModifiers($value)
{
$value = preg_replace('/\/[gim]/', '/', $value);
return $value;
}
/**
* Converts from hex/dec entities
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertEntities($value)
{
$converted = null;
//deal with double encoded payload
$value = preg_replace('/&amp;/', '&', $value);
if (preg_match('/&#x?[\w]+/ms', $value)) {
$converted = preg_replace('/(&#x?[\w]{2}\d?);?/ms', '$1;', $value);
$converted = html_entity_decode($converted, ENT_QUOTES, 'UTF-8');
$value .= "\n" . str_replace(';;', ';', $converted);
}
return $value;
}
/**
* Normalize quotes
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertQuotes($value)
{
// normalize different quotes to "
$pattern = array('\'', '`', '´', '', '');
$value = str_replace($pattern, '"', $value);
return $value;
}
/**
* Converts SQLHEX to plain text
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertFromSQLHex($value)
{
$matches = array();
if(preg_match_all('/(?:0x[a-f\d]{2,}[a-f\d]*)+/im', $value, $matches)) {
foreach($matches[0] as $match) {
$converted = '';
foreach(str_split($match, 2) as $hex_index) {
if(preg_match('/[a-f\d]{2,3}/i', $hex_index)) {
$converted .= chr(hexdec($hex_index));
}
}
$value = str_replace($match, $converted, $value);
}
}
// take care of hex encoded ctrl chars
$value = preg_replace('/0x\d+/m', 1, $value);
return $value;
}
/**
* Converts basic SQL keywords and obfuscations
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertFromSQLKeywords($value)
{
$pattern = array('/(?:IS\s+null)|(LIKE\s+null)|' .
'(?:(?:^|\W)IN[+\s]*\([\s\d"]+[^()]*\))/ims');
$value = preg_replace($pattern, '"=0', $value);
$value = preg_replace('/null[,\s]/ims', ',0', $value);
$value = preg_replace('/,null/ims', ',0', $value);
$value = preg_replace('/(?:between|mod)/ims', 'or', $value);
$value = preg_replace('/(?:and\s+\d+\.?\d*)/ims', '', $value);
$value = preg_replace('/(?:\s+and\s+)/ims', ' or ', $value);
$pattern = array('/[^\w,(]NULL|\\\N|TRUE|FALSE|UTC_TIME|' .
'LOCALTIME(?:STAMP)?|CURRENT_\w+|BINARY|' .
'(?:(?:ASCII|SOUNDEX|' .
'MD5|R?LIKE)[+\s]*\([^()]+\))|(?:-+\d)/ims');
$value = preg_replace($pattern, 0, $value);
$pattern = array('/(?:NOT\s+BETWEEN)|(?:IS\s+NOT)|(?:NOT\s+IN)|' .
'(?:XOR|\WDIV\W|\WNOT\W|<>|RLIKE(?:\s+BINARY)?)|' .
'(?:REGEXP\s+BINARY)|' .
'(?:SOUNDS\s+LIKE)/ims');
$value = preg_replace($pattern, '!', $value);
$value = preg_replace('/"\s+\d/', '"', $value);
$value = preg_replace('/\/(?:\d+|null)/', null, $value);
return $value;
}
/**
* Detects nullbytes and controls chars via ord()
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertFromControlChars($value)
{
// critical ctrl values
$search = array(chr(0), chr(1), chr(2),
chr(3), chr(4), chr(5),
chr(6), chr(7), chr(8),
chr(11), chr(12), chr(14),
chr(15), chr(16), chr(17),
chr(18), chr(19));
$value = str_replace($search, '%00', $value);
$urlencoded = urlencode($value);
//take care for malicious unicode characters
$value = urldecode(preg_replace('/(?:%E(?:2|3)%8(?:0|1)%(?:A|8|9)' .
'\w|%EF%BB%BF|%EF%BF%BD)|(?:&#(?:65|8)\d{3};?)/i', null,
$urlencoded));
$value = preg_replace('/(?:&[#x]*(200|820|200|820|zwn?j|lrm|rlm)\w?;?)/i', null,
$value);
$value = preg_replace('/(?:&#(?:65|8)\d{3};?)|' .
'(?:&#(?:56|7)3\d{2};?)|' .
'(?:&#x(?:fe|20)\w{2};?)|' .
'(?:&#x(?:d[c-f])\w{2};?)/i', null,
$value);
return $value;
}
/**
* This method matches and translates base64 strings and fragments
* used in data URIs
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertFromNestedBase64($value)
{
$matches = array();
preg_match_all('/(?:^|[,&?])\s*([a-z0-9]{30,}=*)(?:\W|$)/im',
$value,
$matches);
foreach ($matches[1] as $item) {
if (isset($item) && !preg_match('/[a-f0-9]{32}/i', $item)) {
$value = str_replace($item, base64_decode($item), $value);
}
}
return $value;
}
/**
* Detects nullbytes and controls chars via ord()
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertFromOutOfRangeChars($value)
{
$values = str_split($value);
foreach ($values as $item) {
if (ord($item) >= 127) {
$value = str_replace($item, 'U', $value);
}
}
return $value;
}
/**
* Strip XML patterns
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertFromXML($value)
{
$converted = strip_tags($value);
if ($converted && ($converted != $value)) {
return $value . "\n" . $converted;
}
return $value;
}
/**
* This method converts JS unicode code points to
* regular characters
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertFromJSUnicode($value)
{
$matches = array();
preg_match_all('/\\\u[0-9a-f]{4}/ims', $value, $matches);
if (!empty($matches[0])) {
foreach ($matches[0] as $match) {
$value = str_replace($match,
chr(hexdec(substr($match, 2, 4))),
$value);
}
$value .= "\n\u0001";
}
return $value;
}
/**
* Converts relevant UTF-7 tags to UTF-8
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertFromUTF7($value)
{
if(preg_match('/\+A\w+-/m', $value)) {
if (function_exists('mb_convert_encoding')) {
if(version_compare(PHP_VERSION, '5.2.8', '<')) {
$tmp_chars = str_split($value);
$value = '';
foreach($tmp_chars as $char) {
if(ord($char) <= 127) {
$value .= $char;
}
}
}
$value .= "\n" . mb_convert_encoding($value, 'UTF-8', 'UTF-7');
} else {
//list of all critical UTF7 codepoints
$schemes = array(
'+ACI-' => '"',
'+ADw-' => '<',
'+AD4-' => '>',
'+AFs-' => '[',
'+AF0-' => ']',
'+AHs-' => '{',
'+AH0-' => '}',
'+AFw-' => '\\',
'+ADs-' => ';',
'+ACM-' => '#',
'+ACY-' => '&',
'+ACU-' => '%',
'+ACQ-' => '$',
'+AD0-' => '=',
'+AGA-' => '`',
'+ALQ-' => '"',
'+IBg-' => '"',
'+IBk-' => '"',
'+AHw-' => '|',
'+ACo-' => '*',
'+AF4-' => '^',
'+ACIAPg-' => '">',
'+ACIAPgA8-' => '">'
);
$value = str_ireplace(array_keys($schemes),
array_values($schemes), $value);
}
}
return $value;
}
/**
* Converts basic concatenations
*
* @param string $value the value to convert
*
* @static
* @return string
*/
public static function convertFromConcatenated($value)
{
//normalize remaining backslashes
if ($value != preg_replace('/(\w)\\\/', "$1", $value)) {
$value .= preg_replace('/(\w)\\\/', "$1", $value);
}
$compare = stripslashes($value);
$pattern = array('/(?:<\/\w+>\+<\w+>)/s',
'/(?:":\d+[^"[]+")/s',
'/(?:"?"\+\w+\+")/s',
'/(?:"\s*;[^"]+")|(?:";[^"]+:\s*")/s',
'/(?:"\s*(?:;|\+).{8,18}:\s*")/s',
'/(?:";\w+=)|(?:!""&&")|(?:~)/s',
'/(?:"?"\+""?\+?"?)|(?:;\w+=")|(?:"[|&]{2,})/s',
'/(?:"\s*\W+")/s',
'/(?:";\w\s*\+=\s*\w?\s*")/s',
'/(?:"[|&;]+\s*[^|&\n]*[|&]+\s*"?)/s',
'/(?:";\s*\w+\W+\w*\s*[|&]*")/s',
'/(?:"\s*"\s*\.)/s',
'/(?:\s*new\s+\w+\s*[+",])/',
'/(?:(?:^|\s+)(?:do|else)\s+)/',
'/(?:[{(]\s*new\s+\w+\s*[)}])/',
'/(?:(this|self)\.)/',
'/(?:undefined)/',
'/(?:in\s+)/');
// strip out concatenations
$converted = preg_replace($pattern, null, $compare);
//strip object traversal
$converted = preg_replace('/\w(\.\w\()/', "$1", $converted);
// normalize obfuscated method calls
$converted = preg_replace('/\)\s*\+/', ")", $converted);
//convert JS special numbers
$converted = preg_replace('/(?:\(*[.\d]e[+-]*[^a-z\W]+\)*)' .
'|(?:NaN|Infinity)\W/ms', 1, $converted);
if ($converted && ($compare != $converted)) {
$value .= "\n" . $converted;
}
return $value;
}
/**
* This method collects and decodes proprietary encoding types
*
* @param string $value the value to convert
* @param IDS_Monitor $monitor the monitor object
*
* @static
* @return string
*/
public static function convertFromProprietaryEncodings($value) {
//Xajax error reportings
$value = preg_replace('/<!\[CDATA\[(\W+)\]\]>/im', '$1', $value);
//strip false alert triggering apostrophes
$value = preg_replace('/(\w)\"(s)/m', '$1$2', $value);
//strip quotes within typical search patterns
$value = preg_replace('/^"([^"=\\!><~]+)"$/', '$1', $value);
//OpenID login tokens
$value = preg_replace('/{[\w-]{8,9}\}(?:\{[\w=]{8}\}){2}/', null, $value);
//convert Content and \sdo\s to null
$value = preg_replace('/Content|\Wdo\s/', null, $value);
//strip emoticons
$value = preg_replace(
'/(?:\s[:;]-[)\/PD]+)|(?:\s;[)PD]+)|(?:\s:[)PD]+)|-\.-|\^\^/m',
null,
$value
);
//normalize separation char repetion
$value = preg_replace('/([.+~=*_\-])\1{2,}/m', '$1', $value);
//normalize multiple single quotes
$value = preg_replace('/"{2,}/m', '"', $value);
//normalize ampersand listings
$value = preg_replace('/(\w\s)&\s(\w)/', '$1$2', $value);
//normalize JS backspace linebreaks
$value = preg_replace('/^\/|\/$|,\/\n|\/,/', null, $value);
return $value;
}
/**
* This method is the centrifuge prototype
*
* @param string $value the value to convert
* @param IDS_Monitor $monitor the monitor object
*
* @static
* @return string
*/
public static function runCentrifuge($value, IDS_Monitor $monitor = null)
{
$threshold = 3.49;
$unserialized = false;
if(preg_match('/^\w:\d+:\{/', $value)) {
$unserialized = @unserialize($value);
}
if (strlen($value) > 25 && !$unserialized) {
//strip padding
$tmp_value = preg_replace('/\s{4}/m', null, $value);
$tmp_value = preg_replace(
'/\s{4}|[\p{L}\d\+\-,.%]{8,}/m',
'aaa',
$tmp_value
);
// Check for the attack char ratio
$tmp_value = preg_replace('/([*.!?+-])\1{1,}/m', '$1', $tmp_value);
$tmp_value = preg_replace('/"[\p{L}\d\s]+"/m', null, $tmp_value);
$stripped_length = strlen(preg_replace('/[\d\s\p{L}.:,%&\/><\-)]+/m',
null, $tmp_value));
$overall_length = strlen(
preg_replace('/([\d\s\p{L}:,]{3,})+/m', 'aaa',
preg_replace('/\s{2,}/m', null, $tmp_value))
);
if ($stripped_length != 0
&& $overall_length/$stripped_length <= $threshold) {
$monitor->centrifuge['ratio'] =
$overall_length/$stripped_length;
$monitor->centrifuge['threshold'] =
$threshold;
$value .= "\n$[!!!]";
}
}
if (strlen($value) > 40) {
// Replace all non-special chars
$converted = preg_replace('/[\w\s\p{L},.!]/', null, $value);
// Split string into an array, unify and sort
$array = str_split($converted);
$array = array_unique($array);
asort($array);
// Normalize certain tokens
$schemes = array(
'~' => '+',
'^' => '+',
'|' => '+',
'*' => '+',
'%' => '+',
'&' => '+',
'/' => '+'
);
$converted = implode($array);
$converted = str_replace(array_keys($schemes),
array_values($schemes), $converted);
$converted = preg_replace('/[+-]\s*\d+/', '+', $converted);
$converted = preg_replace('/[()[\]{}]/', '(', $converted);
$converted = preg_replace('/[!?:=]/', ':', $converted);
$converted = preg_replace('/[^:(+]/', null, stripslashes($converted));
// Sort again and implode
$array = str_split($converted);
asort($array);
$converted = implode($array);
if (preg_match('/(?:\({2,}\+{2,}:{2,})|(?:\({2,}\+{2,}:+)|' .
'(?:\({3,}\++:{2,})/', $converted)) {
$monitor->centrifuge['converted'] = $converted;
return $value . "\n" . $converted;
}
}
return $value;
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,235 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
/**
* PHPIDS event object
*
* This class represents a certain event that occured while applying the filters
* to the supplied data. It aggregates a bunch of IDS_Filter implementations and
* is a assembled in IDS_Report.
*
* Note that this class implements both Countable and IteratorAggregate
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Event.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
*/
class IDS_Event implements Countable, IteratorAggregate
{
/**
* Event name
*
* @var scalar
*/
protected $name = null;
/**
* Value of the event
*
* @var scalar
*/
protected $value = null;
/**
* List of filter objects
*
* Filter objects in this array are those that matched the events value
*
* @var array
*/
protected $filters = array();
/**
* Calculated impact
*
* Total impact of the event
*
* @var integer
*/
protected $impact = 0;
/**
* Affecte tags
*
* @var array
*/
protected $tags = array();
/**
* Constructor
*
* Fills event properties
*
* @param scalar $name the event name
* @param scalar $value the event value
* @param array $filters the corresponding filters
*
* @return void
*/
public function __construct($name, $value, Array $filters)
{
if (!is_scalar($name)) {
throw new InvalidArgumentException(
'Expected $name to be a scalar,' . gettype($name) . ' given'
);
}
if (!is_scalar($value)) {
throw new InvalidArgumentException('
Expected $value to be a scalar,' . gettype($value) . ' given'
);
}
$this->name = $name;
$this->value = $value;
foreach ($filters as $filter) {
if (!$filter instanceof IDS_Filter) {
throw new InvalidArgumentException(
'Filter must be derived from IDS_Filter'
);
}
$this->filters[] = $filter;
}
}
/**
* Returns event name
*
* The name of the event usually is the key of the variable that was
* considered to be malicious
*
* @return scalar
*/
public function getName()
{
return $this->name;
}
/**
* Returns event value
*
* @return scalar
*/
public function getValue()
{
return $this->value;
}
/**
* Returns calculated impact
*
* @return integer
*/
public function getImpact()
{
if (!$this->impact) {
$this->impact = 0;
foreach ($this->filters as $filter) {
$this->impact += $filter->getImpact();
}
}
return $this->impact;
}
/**
* Returns affected tags
*
* @return array
*/
public function getTags()
{
$filters = $this->getFilters();
foreach ($filters as $filter) {
$this->tags = array_merge($this->tags,
$filter->getTags());
}
$this->tags = array_values(array_unique($this->tags));
return $this->tags;
}
/**
* Returns list of filter objects
*
* @return array
*/
public function getFilters()
{
return $this->filters;
}
/**
* Returns number of filters
*
* To implement interface Countable this returns the number of filters
* appended.
*
* @return integer
*/
public function count()
{
return count($this->getFilters());
}
/**
* IteratorAggregate iterator getter
*
* Returns an iterator to iterate over the appended filters.
*
* @return Iterator|IteratorAggregate
*/
public function getIterator()
{
return new ArrayObject($this->getFilters());
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,186 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
/**
* PHPIDS Filter object
*
* Each object of this class serves as a container for a specific filter. The
* object provides methods to get information about this particular filter and
* also to match an arbitrary string against it.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Filter.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
* @since Version 0.4
*/
class IDS_Filter
{
/**
* Filter rule
*
* @var string
*/
protected $rule;
/**
* List of tags of the filter
*
* @var array
*/
protected $tags = array();
/**
* Filter impact level
*
* @var integer
*/
protected $impact = 0;
/**
* Filter description
*
* @var string
*/
protected $description = null;
/**
* Constructor
*
* @param mixed $rule filter rule
* @param string $description filter description
* @param array $tags list of tags
* @param integer $impact filter impact level
*
* @return void
*/
public function __construct($id, $rule, $description, array $tags, $impact)
{
$this->id = $id;
$this->rule = $rule;
$this->tags = $tags;
$this->impact = $impact;
$this->description = $description;
}
/**
* Matches a string against current filter
*
* Matches given string against the filter rule the specific object of this
* class represents
*
* @param string $string the string to match
*
* @throws InvalidArgumentException if argument is no string
* @return boolean
*/
public function match($string)
{
if (!is_string($string)) {
throw new InvalidArgumentException('
Invalid argument. Expected a string, received ' . gettype($string)
);
}
return (bool) preg_match(
'/' . $this->getRule() . '/ms', strtolower($string)
);
}
/**
* Returns filter description
*
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Return list of affected tags
*
* Each filter rule is concerned with a certain kind of attack vectors.
* This method returns those affected kinds.
*
* @return array
*/
public function getTags()
{
return $this->tags;
}
/**
* Returns filter rule
*
* @return string
*/
public function getRule()
{
return $this->rule;
}
/**
* Get filter impact level
*
* @return integer
*/
public function getImpact()
{
return $this->impact;
}
/**
* Get filter ID
*
* @return integer
*/
public function getId()
{
return $this->id;
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,381 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
/**
* Filter Storage
*
* This class provides various default functions for gathering filter patterns
* to be used later on by the detection mechanism. You might extend this class
* to your requirements.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Storage.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
*/
class IDS_Filter_Storage
{
/**
* Filter source file
*
* @var string
*/
protected $source = null;
/**
* Holds caching settings
*
* @var array
*/
protected $cacheSettings = null;
/**
* Cache container
*
* @var object IDS_Caching wrapper
*/
protected $cache = null;
/**
* Filter container
*
* @var array
*/
protected $filterSet = array();
/**
* Constructor
*
* Loads filters based on provided IDS_Init settings.
*
* @param object $init IDS_Init instance
*
* @throws Exception if unsupported filter type is given
* @return void
*/
public final function __construct(IDS_Init $init)
{
if ($init->config) {
$caching = isset($init->config['Caching']['caching']) ?
$init->config['Caching']['caching'] : 'none';
$type = $init->config['General']['filter_type'];
$this->source = $init->getBasePath()
. $init->config['General']['filter_path'];
if ($caching && $caching != 'none') {
$this->cacheSettings = $init->config['Caching'];
include_once 'IDS/Caching/Factory.php';
$this->cache = IDS_Caching::factory($init, 'storage');
}
switch ($type) {
case 'xml' :
$this->getFilterFromXML();
break;
case 'json' :
$this->getFilterFromJson();
break;
default :
throw new Exception('Unsupported filter type.');
}
}
}
/**
* Sets the filter array
*
* @param array $filterSet array containing multiple IDS_Filter instances
*
* @return object $this
*/
public final function setFilterSet($filterSet)
{
foreach ($filterSet as $filter) {
$this->addFilter($filter);
}
return $this;
}
/**
* Returns registered filters
*
* @return array
*/
public final function getFilterSet()
{
return $this->filterSet;
}
/**
* Adds a filter
*
* @param object $filter IDS_Filter instance
*
* @return object $this
*/
public final function addFilter(IDS_Filter $filter)
{
$this->filterSet[] = $filter;
return $this;
}
/**
* Checks if any filters are cached
*
* @return mixed $filters cached filters or false
*/
private function _isCached()
{
$filters = false;
if ($this->cacheSettings) {
if ($this->cache) {
$filters = $this->cache->getCache();
}
}
return $filters;
}
/**
* Loads filters from XML using SimpleXML
*
* This function parses the provided source file and stores the result.
* If caching mode is enabled the result will be cached to increase
* the performance.
*
* @throws Exception if problems with fetching the XML data occur
* @return object $this
*/
public function getFilterFromXML()
{
if (extension_loaded('SimpleXML')) {
/*
* See if filters are already available in the cache
*/
$filters = $this->_isCached();
/*
* If they aren't, parse the source file
*/
if (!$filters) {
if (file_exists($this->source)) {
if (LIBXML_VERSION >= 20621) {
$filters = simplexml_load_file($this->source,
null,
LIBXML_COMPACT);
} else {
$filters = simplexml_load_file($this->source);
}
}
}
/*
* In case we still don't have any filters loaded and exception
* will be thrown
*/
if (empty($filters)) {
throw new Exception(
'XML data could not be loaded.' .
' Make sure you specified the correct path.'
);
}
/*
* Now the storage will be filled with IDS_Filter objects
*/
$data = array();
$nocache = $filters instanceof SimpleXMLElement;
$filters = $nocache ? $filters->filter : $filters;
include_once 'IDS/Filter.php';
foreach ($filters as $filter) {
$id = $nocache ? (string) $filter->id :
$filter['id'];
$rule = $nocache ? (string) $filter->rule :
$filter['rule'];
$impact = $nocache ? (string) $filter->impact :
$filter['impact'];
$tags = $nocache ? array_values((array) $filter->tags) :
$filter['tags'];
$description = $nocache ? (string) $filter->description :
$filter['description'];
$this->addFilter(new IDS_Filter($id,
$rule,
$description,
(array) $tags[0],
(int) $impact));
$data[] = array(
'id' => $id,
'rule' => $rule,
'impact' => $impact,
'tags' => $tags,
'description' => $description
);
}
/*
* If caching is enabled, the fetched data will be cached
*/
if ($this->cacheSettings) {
$this->cache->setCache($data);
}
} else {
throw new Exception(
'SimpleXML not loaded.'
);
}
return $this;
}
/**
* Loads filters from Json file using ext/Json
*
* This function parses the provided source file and stores the result.
* If caching mode is enabled the result will be cached to increase
* the performance.
*
* @throws Exception if problems with fetching the JSON data occur
* @return object $this
*/
public function getFilterFromJson()
{
if (extension_loaded('Json')) {
/*
* See if filters are already available in the cache
*/
$filters = $this->_isCached();
/*
* If they aren't, parse the source file
*/
if (!$filters) {
if (file_exists($this->source)) {
$filters = json_decode(file_get_contents($this->source));
} else {
throw new Exception(
'JSON data could not be loaded.' .
' Make sure you specified the correct path.'
);
}
}
if (!$filters) {
throw new Exception(
'JSON data could not be loaded.' .
' Make sure you specified the correct path.'
);
}
/*
* Now the storage will be filled with IDS_Filter objects
*/
$data = array();
$nocache = !is_array($filters);
$filters = $nocache ? $filters->filters->filter : $filters;
include_once 'IDS/Filter.php';
foreach ($filters as $filter) {
$id = $nocache ? (string) $filter->id :
$filter['id'];
$rule = $nocache ? (string) $filter->rule :
$filter['rule'];
$impact = $nocache ? (string) $filter->impact :
$filter['impact'];
$tags = $nocache ? array_values((array) $filter->tags) :
$filter['tags'];
$description = $nocache ? (string) $filter->description :
$filter['description'];
$this->addFilter(new IDS_Filter($id,
$rule,
$description,
(array) $tags[0],
(int) $impact));
$data[] = array(
'id' => $id,
'rule' => $rule,
'impact' => $impact,
'tags' => $tags,
'description' => $description
);
}
/*
* If caching is enabled, the fetched data will be cached
*/
if ($this->cacheSettings) {
$this->cache->setCache($data);
}
} else {
throw new Exception(
'ext/json not loaded.'
);
}
return $this;
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,232 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
/**
* Framework initiation
*
* This class is used for the purpose to initiate the framework and inhabits
* functionality to parse the needed configuration file.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Groupup
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Init.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
* @since Version 0.4
*/
class IDS_Init
{
/**
* Holds config settings
*
* @var array
*/
public $config = array();
/**
* Instance of this class depending on the supplied config file
*
* @var array
* @static
*/
private static $instances = array();
/**
* Path to the config file
*
* @var string
*/
private $configPath = null;
/**
* Constructor
*
* Includes needed classes and parses the configuration file
*
* @param string $configPath the path to the config file
*
* @return object $this
*/
private function __construct($configPath = null)
{
include_once 'IDS/Monitor.php';
include_once 'IDS/Filter/Storage.php';
if ($configPath) {
$this->setConfigPath($configPath);
$this->config = parse_ini_file($this->configPath, true);
}
}
/**
* Permitting to clone this object
*
* For the sake of correctness of a singleton pattern, this is necessary
*
* @return void
*/
public final function __clone()
{
}
/**
* Returns an instance of this class. Also a PHP version check
* is being performed to avoid compatibility problems with PHP < 5.1.6
*
* @param string $configPath the path to the config file
*
* @return object
*/
public static function init($configPath = null)
{
if (!isset(self::$instances[$configPath])) {
self::$instances[$configPath] = new IDS_Init($configPath);
}
return self::$instances[$configPath];
}
/**
* Sets the path to the configuration file
*
* @param string $path the path to the config
*
* @throws Exception if file not found
* @return void
*/
public function setConfigPath($path)
{
if (file_exists($path)) {
$this->configPath = $path;
} else {
throw new Exception(
'Configuration file could not be found at ' .
htmlspecialchars($path, ENT_QUOTES, 'UTF-8')
);
}
}
/**
* Returns path to configuration file
*
* @return string the config path
*/
public function getConfigPath()
{
return $this->configPath;
}
/**
* This method checks if a base path is given and usage is set to true.
* If all that tests succeed the base path will be returned as a string -
* else null will be returned.
*
* @return string the base path or null
*/
public function getBasePath() {
return ((isset($this->config['General']['base_path'])
&& $this->config['General']['base_path']
&& isset($this->config['General']['use_base_path'])
&& $this->config['General']['use_base_path'])
? $this->config['General']['base_path'] : null);
}
/**
* Merges new settings into the exsiting ones or overwrites them
*
* @param array $config the config array
* @param boolean $overwrite config overwrite flag
*
* @return void
*/
public function setConfig(array $config, $overwrite = false)
{
if ($overwrite) {
$this->config = $this->_mergeConfig($this->config, $config);
} else {
$this->config = $this->_mergeConfig($config, $this->config);
}
}
/**
* Merge config hashes recursivly
*
* The algorithm merges configuration arrays recursively. If an element is
* an array in both, the values will be appended. If it is a scalar in both,
* the value will be replaced.
*
* @param array $current The legacy hash
* @param array $successor The hash which values count more when in doubt
* @return array Merged hash
*/
protected function _mergeConfig($current, $successor)
{
if (is_array($current) and is_array($successor)) {
foreach ($successor as $key => $value) {
if (isset($current[$key])
and is_array($value)
and is_array($current[$key])) {
$current[$key] = $this->_mergeConfig($current[$key], $value);
} else {
$current[$key] = $successor[$key];
}
}
}
return $current;
}
/**
* Returns the config array
*
* @return array the config array
*/
public function getConfig()
{
return $this->config;
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,136 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
require_once 'IDS/Log/Interface.php';
/**
* Log Composite
*
* This class implements the composite pattern to allow to work with multiple
* logging wrappers at once.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Composite.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
*/
class IDS_Log_Composite
{
/**
* Holds registered logging wrapper
*
* @var array
*/
public $loggers = array();
/**
* Iterates through registered loggers and executes them
*
* @param object $data IDS_Report object
*
* @return void
*/
public function execute(IDS_Report $data)
{
// make sure request uri is set right on IIS
if (!isset($_SERVER['REQUEST_URI'])) {
$_SERVER['REQUEST_URI'] = substr($_SERVER['PHP_SELF'], 1);
if (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING']) {
$_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
}
}
// make sure server address is set right on IIS
if (isset($_SERVER['LOCAL_ADDR'])) {
$_SERVER['SERVER_ADDR'] = $_SERVER['LOCAL_ADDR'];
}
foreach ($this->loggers as $logger) {
$logger->execute($data);
}
}
/**
* Registers a new logging wrapper
*
* Only valid IDS_Log_Interface instances passed to this function will be
* registered
*
* @return void
*/
public function addLogger()
{
$args = func_get_args();
foreach ($args as $class) {
if (!in_array($class, $this->loggers) &&
($class instanceof IDS_Log_Interface)) {
$this->loggers[] = $class;
}
}
}
/**
* Removes a logger
*
* @param object $logger IDS_Log_Interface object
*
* @return boolean
*/
public function removeLogger(IDS_Log_Interface $logger)
{
$key = array_search($logger, $this->loggers);
if (isset($this->loggers[$key])) {
unset($this->loggers[$key]);
return true;
}
return false;
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,285 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
require_once 'IDS/Log/Interface.php';
/*
* Needed SQL:
*
CREATE DATABASE IF NOT EXISTS `phpids` DEFAULT CHARACTER
SET utf8 COLLATE utf8_general_ci;
DROP TABLE IF EXISTS `intrusions`;
CREATE TABLE IF NOT EXISTS `intrusions` (
`id` int(11) unsigned NOT null auto_increment,
`name` varchar(128) NOT null,
`value` text NOT null,
`page` varchar(255) NOT null,
`ip` varchar(15) NOT null,
`impact` int(11) unsigned NOT null,
`origin` varchar(15) NOT null,
`created` datetime NOT null,
PRIMARY KEY (`id`)
) ENGINE=MyISAM ;
*
*
*
*/
/**
* Database logging wrapper
*
* The database wrapper is designed to store reports into an sql database. It
* implements the singleton pattern and is based in PDO, supporting
* different database types.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Database.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
*/
class IDS_Log_Database implements IDS_Log_Interface
{
/**
* Database wrapper
*
* @var string
*/
private $wrapper = null;
/**
* Database user
*
* @var string
*/
private $user = null;
/**
* Database password
*
* @var string
*/
private $password = null;
/**
* Database table
*
* @var string
*/
private $table = null;
/**
* Database handle
*
* @var object PDO instance
*/
private $handle = null;
/**
* Prepared SQL statement
*
* @var string
*/
private $statement = null;
/**
* Holds current remote address
*
* @var string
*/
private $ip = 'local/unknown';
/**
* Instance container
*
* Due to the singleton pattern this class allows to initiate only one instance
* for each database wrapper.
*
* @var array
*/
private static $instances = array();
/**
* Constructor
*
* Prepares the SQL statement
*
* @param mixed $config IDS_Init instance | array
*
* @return void
*/
protected function __construct($config)
{
if ($config instanceof IDS_Init) {
$this->wrapper = $config->config['Logging']['wrapper'];
$this->user = $config->config['Logging']['user'];
$this->password = $config->config['Logging']['password'];
$this->table = $config->config['Logging']['table'];
} elseif (is_array($config)) {
$this->wrapper = $config['wrapper'];
$this->user = $config['user'];
$this->password = $config['password'];
$this->table = $config['table'];
}
// determine correct IP address
if ($_SERVER['REMOTE_ADDR'] != '127.0.0.1') {
$this->ip = $_SERVER['REMOTE_ADDR'];
} elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$this->ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
try {
$this->handle = new PDO(
$this->wrapper,
$this->user,
$this->password
);
$this->statement = $this->handle->prepare('
INSERT INTO ' . $this->table . ' (
name,
value,
page,
ip,
impact,
origin,
created
)
VALUES (
:name,
:value,
:page,
:ip,
:impact,
:origin,
now()
)
');
} catch (PDOException $e) {
die('PDOException: ' . $e->getMessage());
}
}
/**
* Returns an instance of this class
*
* This method allows the passed argument to be either an instance of IDS_Init or
* an array.
*
* @param mixed $config IDS_Init | array
*
* @return object $this
*/
public static function getInstance($config)
{
if ($config instanceof IDS_Init) {
$wrapper = $config->config['Logging']['wrapper'];
} elseif (is_array($config)) {
$wrapper = $config['wrapper'];
}
if (!isset(self::$instances[$wrapper])) {
self::$instances[$wrapper] = new IDS_Log_Database($config);
}
return self::$instances[$wrapper];
}
/**
* Permitting to clone this object
*
* For the sake of correctness of a singleton pattern, this is necessary
*
* @return void
*/
private function __clone()
{
}
/**
* Stores given data into the database
*
* @param object $data IDS_Report instance
*
* @throws Exception if db error occurred
* @return boolean
*/
public function execute(IDS_Report $data)
{
if (!isset($_SERVER['REQUEST_URI'])) {
$_SERVER['REQUEST_URI'] = substr($_SERVER['PHP_SELF'], 1);
if (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING']) {
$_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
}
}
foreach ($data as $event) {
$page = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
$ip = $this->ip;
$this->statement->bindParam('name', $event->getName());
$this->statement->bindParam('value', $event->getValue());
$this->statement->bindParam('page', $page);
$this->statement->bindParam('ip', $ip);
$this->statement->bindParam('impact', $data->getImpact());
$this->statement->bindParam('origin', $_SERVER['SERVER_ADDR']);
if (!$this->statement->execute()) {
$info = $this->statement->errorInfo();
throw new Exception(
$this->statement->errorCode() . ', ' . $info[1] . ', ' . $info[2]
);
}
}
return true;
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,401 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
require_once 'IDS/Log/Interface.php';
/**
* Email logging wrapper
*
* The Email wrapper is designed to send reports via email. It implements the
* singleton pattern.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Email.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
*/
class IDS_Log_Email implements IDS_Log_Interface
{
/**
* Recipient container
*
* @var array
*/
private $recipients = array();
/**
* Mail subject
*
* @var string
*/
private $subject = null;
/**
* Additional mail headers
*
* @var string
*/
private $headers = null;
/**
* Safemode switch
*
* Using this switch it is possible to enable safemode, which is a spam
* protection based on the alert frequency.
*
* @var boolean
*/
private $safemode = true;
/**
* Urlencode for result strings
*
* This switch is true by default. Setting it to false removes
* the 'better safe than sorry' urlencoding for the result string in
* the report mails. Enhances readability but maybe XSSes email clients.
*
* @var boolean
*/
private $urlencode = true;
/**
* Send rate
*
* If safemode is enabled, this property defines how often reports will be
* sent out. Default value is 15, which means that a mail will be sent on
* condition that the last email has not been sent earlier than 15 seconds ago.
*
* @var integer
*/
private $allowed_rate = 15;
/**
* PHPIDS temp directory
*
* When safemod is enabled, a path to a temp directory is needed to
* store some information. Default is IDS/tmp/
*
* @var string
*/
private $tmp_path = 'IDS/tmp/';
/**
* File prefix for tmp files
*
* @var string
*/
private $file_prefix = 'PHPIDS_Log_Email_';
/**
* Holds current remote address
*
* @var string
*/
private $ip = 'local/unknown';
/**
* Instance container
*
* @var array
*/
private static $instance = array();
/**
* Constructor
*
* @param mixed $config IDS_Init instance | array
*
* @return void
*/
protected function __construct($config)
{
if ($config instanceof IDS_Init) {
$this->recipients = $config->config['Logging']['recipients'];
$this->subject = $config->config['Logging']['subject'];
$this->headers = $config->config['Logging']['header'];
$this->envelope = $config->config['Logging']['envelope'];
$this->safemode = $config->config['Logging']['safemode'];
$this->urlencode = $config->config['Logging']['urlencode'];
$this->allowed_rate = $config->config['Logging']['allowed_rate'];
$this->tmp_path = $config->getBasePath()
. $config->config['General']['tmp_path'];
} elseif (is_array($config)) {
$this->recipients[] = $config['recipients'];
$this->subject = $config['subject'];
$this->additionalHeaders = $config['header'];
}
// determine correct IP address and concat them if necessary
$this->ip = $_SERVER['REMOTE_ADDR'] .
(isset($_SERVER['HTTP_X_FORWARDED_FOR']) ?
' (' . $_SERVER['HTTP_X_FORWARDED_FOR'] . ')' : '');
}
/**
* Returns an instance of this class
*
* This method allows the passed argument to be either an instance of
* IDS_Init or an array.
*
* @param mixed $config IDS_Init | array
*
* @return object $this
*/
public static function getInstance($config)
{
if (!self::$instance) {
self::$instance = new IDS_Log_Email($config);
}
return self::$instance;
}
/**
* Permitting to clone this object
*
* For the sake of correctness of a singleton pattern, this is necessary
*
* @return void
*/
private function __clone()
{
}
/**
* Detects spam attempts
*
* To avoid mail spam through this logging class this function is used
* to detect such attempts based on the alert frequency.
*
* @return boolean
*/
protected function isSpamAttempt()
{
/*
* loop through all files in the tmp directory and
* delete garbage files
*/
$dir = $this->tmp_path;
$numPrefixChars = strlen($this->file_prefix);
$files = scandir($dir);
foreach ($files as $file) {
if (is_file($dir . $file)) {
if (substr($file, 0, $numPrefixChars) == $this->file_prefix) {
$lastModified = filemtime($dir . $file);
if ((
time() - $lastModified) > 3600) {
unlink($dir . $file);
}
}
}
}
/*
* end deleting garbage files
*/
$remoteAddr = $this->ip;
$userAgent = $_SERVER['HTTP_USER_AGENT'];
$filename = $this->file_prefix . md5($remoteAddr.$userAgent) . '.tmp';
$file = $dir . DIRECTORY_SEPARATOR . $filename;
if (!file_exists($file)) {
$handle = fopen($file, 'w');
fwrite($handle, time());
fclose($handle);
return false;
}
$lastAttack = file_get_contents($file);
$difference = time() - $lastAttack;
if ($difference > $this->allowed_rate) {
unlink($file);
} else {
return true;
}
return false;
}
/**
* Prepares data
*
* Converts given data into a format that can be read in an email.
* You might edit this method to your requirements.
*
* @param mixed $data the report data
*
* @return string
*/
protected function prepareData($data)
{
$format = "The following attack has been detected by PHPIDS\n\n";
$format .= "IP: %s \n";
$format .= "Date: %s \n";
$format .= "Impact: %d \n";
$format .= "Affected tags: %s \n";
$attackedParameters = '';
foreach ($data as $event) {
$attackedParameters .= $event->getName() . '=' .
((!isset($this->urlencode) ||$this->urlencode)
? urlencode($event->getValue())
: $event->getValue()) . ", ";
}
$format .= "Affected parameters: %s \n";
$format .= "Request URI: %s \n";
$format .= "Origin: %s \n";
return sprintf($format,
$this->ip,
date('c'),
$data->getImpact(),
join(' ', $data->getTags()),
trim($attackedParameters),
urlencode($_SERVER['REQUEST_URI']),
$_SERVER['SERVER_ADDR']);
}
/**
* Sends the report to registered recipients
*
* @param object $data IDS_Report instance
*
* @throws Exception if data is no string
* @return boolean
*/
public function execute(IDS_Report $data)
{
if ($this->safemode) {
if ($this->isSpamAttempt()) {
return false;
}
}
/*
* In case the data has been modified before it might
* be necessary to convert it to string since it's pretty
* senseless to send array or object via e-mail
*/
$data = $this->prepareData($data);
if (is_string($data)) {
$data = trim($data);
// if headers are passed as array, we need to make a string of it
if (is_array($this->headers)) {
$headers = "";
foreach ($this->headers as $header) {
$headers .= $header . "\r\n";
}
} else {
$headers = $this->headers;
}
if (!empty($this->recipients)) {
if (is_array($this->recipients)) {
foreach ($this->recipients as $address) {
$this->send(
$address,
$data,
$headers,
$this->envelope
);
}
} else {
$this->send(
$this->recipients,
$data,
$headers,
$this->envelope
);
}
}
} else {
throw new Exception(
'Please make sure that data returned by
IDS_Log_Email::prepareData() is a string.'
);
}
return true;
}
/**
* Sends an email
*
* @param string $address email address
* @param string $data the report data
* @param string $headers the mail headers
* @param string $envelope the optional envelope string
*
* @return boolean
*/
protected function send($address, $data, $headers, $envelope = null)
{
if (!$envelope || strpos(ini_get('sendmail_path'),' -f') !== false) {
return mail($address,
$this->subject,
$data,
$headers);
} else {
return mail($address,
$this->subject,
$data,
$headers,
'-f' . $envelope);
}
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,229 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
require_once 'IDS/Log/Interface.php';
/**
* File logging wrapper
*
* The file wrapper is designed to store data into a flatfile. It implements the
* singleton pattern.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:File.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
*/
class IDS_Log_File implements IDS_Log_Interface
{
/**
* Path to the log file
*
* @var string
*/
private $logfile = null;
/**
* Instance container
*
* Due to the singleton pattern this class allows to initiate only one
* instance for each file.
*
* @var array
*/
private static $instances = array();
/**
* Holds current remote address
*
* @var string
*/
private $ip = 'local/unknown';
/**
* Constructor
*
* @param string $logfile path to the log file
*
* @return void
*/
protected function __construct($logfile)
{
// determine correct IP address
if ($_SERVER['REMOTE_ADDR'] != '127.0.0.1') {
$this->ip = $_SERVER['REMOTE_ADDR'];
} elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$this->ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
$this->logfile = $logfile;
}
/**
* Returns an instance of this class
*
* This method allows the passed argument to be either an instance of
* IDS_Init or a path to a log file. Due to the singleton pattern only one
* instance for each file can be initiated.
*
* @param mixed $config IDS_Init or path to a file
*
* @return object $this
*/
public static function getInstance($config)
{
if ($config instanceof IDS_Init) {
$logfile = $config->getBasePath() . $config->config['Logging']['path'];
} elseif (is_string($config)) {
$logfile = $config;
}
if (!isset(self::$instances[$logfile])) {
self::$instances[$logfile] = new IDS_Log_File($logfile);
}
return self::$instances[$logfile];
}
/**
* Permitting to clone this object
*
* For the sake of correctness of a singleton pattern, this is necessary
*
* @return void
*/
private function __clone()
{
}
/**
* Prepares data
*
* Converts given data into a format that can be stored into a file.
* You might edit this method to your requirements.
*
* @param mixed $data incoming report data
*
* @return string
*/
protected function prepareData($data)
{
$format = '"%s",%s,%d,"%s","%s","%s","%s"';
$attackedParameters = '';
foreach ($data as $event) {
$attackedParameters .= $event->getName() . '=' .
rawurlencode($event->getValue()) . ' ';
}
$dataString = sprintf($format,
$this->ip,
date('c'),
$data->getImpact(),
join(' ', $data->getTags()),
trim($attackedParameters),
urlencode($_SERVER['REQUEST_URI']),
$_SERVER['SERVER_ADDR']);
return $dataString;
}
/**
* Stores given data into a file
*
* @param object $data IDS_Report
*
* @throws Exception if the logfile isn't writeable
* @return mixed
*/
public function execute(IDS_Report $data)
{
/*
* In case the data has been modified before it might be necessary
* to convert it to string since we can't store array or object
* into a file
*/
$data = $this->prepareData($data);
if (is_string($data)) {
if (file_exists($this->logfile)) {
$data = trim($data);
if (!empty($data)) {
if (is_writable($this->logfile)) {
$handle = fopen($this->logfile, 'a');
fwrite($handle, trim($data) . "\n");
fclose($handle);
} else {
throw new Exception(
'Please make sure that ' . $this->logfile .
' is writeable.'
);
}
}
} else {
throw new Exception(
'Given file does not exist. Please make sure the
logfile is present in the given directory.'
);
}
} else {
throw new Exception(
'Please make sure that data returned by
IDS_Log_File::prepareData() is a string.'
);
}
return true;
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,65 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
/**
* Interface for logging wrappers
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @version Release: $Id:Interface.php 517 2007-09-15 15:04:13Z mario $
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
interface IDS_Log_Interface
{
/**
* Interface method
*
* @param IDS_Report $data the report data
*
* @return void
*/
public function execute(IDS_Report $data);
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,681 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
/**
* Monitoring engine
*
* This class represents the core of the frameworks attack detection mechanism
* and provides functions to scan incoming data for malicious appearing script
* fragments.
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Monitor.php 949 2008-06-28 01:26:03Z christ1an $
* @link http://php-ids.org/
*/
class IDS_Monitor
{
/**
* Tags to define what to search for
*
* Accepted values are xss, csrf, sqli, dt, id, lfi, rfe, spam, dos
*
* @var array
*/
private $tags = null;
/**
* Request array
*
* Array containing raw data to search in
*
* @var array
*/
private $request = null;
/**
* Container for filter rules
*
* Holds an instance of IDS_Filter_Storage
*
* @var object
*/
private $storage = null;
/**
* Results
*
* Holds an instance of IDS_Report which itself provides an API to
* access the detected results
*
* @var object
*/
private $report = null;
/**
* Scan keys switch
*
* Enabling this property will cause the monitor to scan both the key and
* the value of variables
*
* @var boolean
*/
public $scanKeys = false;
/**
* Exception container
*
* Using this array it is possible to define variables that must not be
* scanned. Per default, utmz google analytics parameters are permitted.
*
* @var array
*/
private $exceptions = array();
/**
* Html container
*
* Using this array it is possible to define variables that legally
* contain html and have to be prepared before hitting the rules to
* avoid too many false alerts
*
* @var array
*/
private $html = array();
/**
* JSON container
*
* Using this array it is possible to define variables that contain
* JSON data - and should be treated as such
*
* @var array
*/
private $json = array();
/**
* Holds HTMLPurifier object
*
* @var object
*/
private $htmlpurifier = NULL;
/**
* Path to HTMLPurifier source
*
* This path might be changed in case one wishes to make use of a
* different HTMLPurifier source file e.g. if already used in the
* application PHPIDS is protecting
*
* @var string
*/
private $pathToHTMLPurifier = '';
/**
* HTMLPurifier cache directory
*
* @var string
*/
private $HTMLPurifierCache = '';
/**
* This property holds the tmp JSON string from the
* _jsonDecodeValues() callback
*
* @var string
*/
private $tmpJsonString = '';
/**
* Constructor
*
* @param array $request array to scan
* @param object $init instance of IDS_Init
* @param array $tags list of tags to which filters should be applied
*
* @return void
*/
public function __construct(array $request, IDS_Init $init, array $tags = null)
{
$version = isset($init->config['General']['min_php_version'])
? $init->config['General']['min_php_version'] : '5.1.6';
if (version_compare(PHP_VERSION, $version, '<')) {
throw new Exception(
'PHP version has to be equal or higher than ' . $version . ' or
PHP version couldn\'t be determined'
);
}
if (!empty($request)) {
$this->storage = new IDS_Filter_Storage($init);
$this->request = $request;
$this->tags = $tags;
$this->scanKeys = $init->config['General']['scan_keys'];
$this->exceptions = isset($init->config['General']['exceptions'])
? $init->config['General']['exceptions'] : false;
$this->html = isset($init->config['General']['html'])
? $init->config['General']['html'] : false;
$this->json = isset($init->config['General']['json'])
? $init->config['General']['json'] : false;
if(isset($init->config['General']['HTML_Purifier_Path'])
&& isset($init->config['General']['HTML_Purifier_Cache'])) {
$this->pathToHTMLPurifier =
$init->config['General']['HTML_Purifier_Path'];
$this->HTMLPurifierCache =
$init->config['General']['HTML_Purifier_Cache'];
}
}
if (!is_writeable($init->getBasePath()
. $init->config['General']['tmp_path'])) {
throw new Exception(
'Please make sure the ' .
htmlspecialchars($init->getBasePath() .
$init->config['General']['tmp_path'], ENT_QUOTES, 'UTF-8') .
' folder is writable'
);
}
include_once 'IDS/Report.php';
$this->report = new IDS_Report;
}
/**
* Starts the scan mechanism
*
* @return object IDS_Report
*/
public function run()
{
if (!empty($this->request)) {
foreach ($this->request as $key => $value) {
$this->_iterate($key, $value);
}
}
return $this->getReport();
}
/**
* Iterates through given data and delegates it to IDS_Monitor::_detect() in
* order to check for malicious appearing fragments
*
* @param mixed $key the former array key
* @param mixed $value the former array value
*
* @return void
*/
private function _iterate($key, $value)
{
if (!is_array($value)) {
if (is_string($value)) {
if ($filter = $this->_detect($key, $value)) {
include_once 'IDS/Event.php';
$this->report->addEvent(
new IDS_Event(
$key,
$value,
$filter
)
);
}
}
} else {
foreach ($value as $subKey => $subValue) {
$this->_iterate($key . '.' . $subKey, $subValue);
}
}
}
/**
* Checks whether given value matches any of the supplied filter patterns
*
* @param mixed $key the key of the value to scan
* @param mixed $value the value to scan
*
* @return bool|array false or array of filter(s) that matched the value
*/
private function _detect($key, $value)
{
// to increase performance, only start detection if value
// isn't alphanumeric
if (!$value || !preg_match('/[^\w\s\/@!?,\.]+|(?:\.\/)|(?:@@\w+)/', $value)) {
return false;
}
// check if this field is part of the exceptions
if (is_array($this->exceptions)
&& in_array($key, $this->exceptions, true)) {
return false;
}
// check for magic quotes and remove them if necessary
if (function_exists('get_magic_quotes_gpc')
&& get_magic_quotes_gpc()) {
$value = stripslashes($value);
}
// if html monitoring is enabled for this field - then do it!
if (is_array($this->html) && in_array($key, $this->html, true)) {
list($key, $value) = $this->_purifyValues($key, $value);
}
// check if json monitoring is enabled for this field
if (is_array($this->json) && in_array($key, $this->json, true)) {
list($key, $value) = $this->_jsonDecodeValues($key, $value);
}
// use the converter
include_once 'IDS/Converter.php';
$value = IDS_Converter::runAll($value);
$value = IDS_Converter::runCentrifuge($value, $this);
// scan keys if activated via config
$key = $this->scanKeys ? IDS_Converter::runAll($key)
: $key;
$key = $this->scanKeys ? IDS_Converter::runCentrifuge($key, $this)
: $key;
$filters = array();
$filterSet = $this->storage->getFilterSet();
foreach ($filterSet as $filter) {
/*
* in case we have a tag array specified the IDS will only
* use those filters that are meant to detect any of the
* defined tags
*/
if (is_array($this->tags)) {
if (array_intersect($this->tags, $filter->getTags())) {
if ($this->_match($key, $value, $filter)) {
$filters[] = $filter;
}
}
} else {
if ($this->_match($key, $value, $filter)) {
$filters[] = $filter;
}
}
}
return empty($filters) ? false : $filters;
}
/**
* Purifies given key and value variables using HTMLPurifier
*
* This function is needed whenever there is variables for which HTML
* might be allowed like e.g. WYSIWYG post bodies. It will dectect malicious
* code fragments and leaves harmless parts untouched.
*
* @param mixed $key
* @param mixed $value
* @since 0.5
*
* @return array
*/
private function _purifyValues($key, $value) {
include_once $this->pathToHTMLPurifier;
if (!is_writeable($this->HTMLPurifierCache)) {
throw new Exception(
$this->HTMLPurifierCache . ' must be writeable');
}
if (class_exists('HTMLPurifier')) {
$config = HTMLPurifier_Config::createDefault();
$config->set('Attr', 'EnableID', true);
$config->set('Cache', 'SerializerPath', $this->HTMLPurifierCache);
$config->set('Output', 'Newline', "\n");
$this->htmlpurifier = new HTMLPurifier($config);
} else {
throw new Exception(
'HTMLPurifier class could not be found - ' .
'make sure the purifier files are valid and' .
' the path is correct'
);
}
$purified_value = $this->htmlpurifier->purify($value);
$purified_key = $this->htmlpurifier->purify($key);
$redux_value = strip_tags($value);
$redux_key = strip_tags($key);
if ($value != $purified_value || $redux_value) {
$value = $this->_diff($value, $purified_value, $redux_value);
} else {
$value = NULL;
}
if ($key != $purified_key) {
$key = $this->_diff($key, $purified_key, $redux_key);
} else {
$key = NULL;
}
return array($key, $value);
}
/**
* This method calculates the difference between the original
* and the purified markup strings.
*
* @param string $original the original markup
* @param string $purified the purified markup
* @param string $redux the string without html
* @since 0.5
*
* @return string the difference between the strings
*/
private function _diff($original, $purified, $redux)
{
/*
* deal with over-sensitive alt-attribute addition of the purifier
* and other common html formatting problems
*/
$purified = preg_replace('/\s+alt="[^"]*"/m', null, $purified);
$purified = preg_replace('/=?\s*"\s*"/m', null, $purified);
$original = preg_replace('/=?\s*"\s*"/m', null, $original);
$original = preg_replace('/\s+alt=?/m', null, $original);
// check which string is longer
$length = (strlen($original) - strlen($purified));
/*
* Calculate the difference between the original html input
* and the purified string.
*/
if ($length > 0) {
$array_2 = str_split($original);
$array_1 = str_split($purified);
} else {
$array_1 = str_split($original);
$array_2 = str_split($purified);
}
foreach ($array_2 as $key => $value) {
if ($value !== $array_1[$key]) {
$array_1 = array_reverse($array_1);
$array_1[] = $value;
$array_1 = array_reverse($array_1);
}
}
// return the diff - ready to hit the converter and the rules
$diff = trim(join('', array_reverse(
(array_slice($array_1, 0, $length)))));
// clean up spaces between tag delimiters
$diff = preg_replace('/>\s*</m', '><', $diff);
// correct over-sensitively stripped bad html elements
$diff = preg_replace('/[^<](iframe|script|embed|object' .
'|applet|base|img|style)/m', '<$1', $diff);
if ($original == $purified && !$redux) {
return null;
}
return $diff . $redux;
}
/**
* This method prepares incoming JSON data for the PHPIDS detection
* process. It utilizes _jsonConcatContents() as callback and returns a
* string version of the JSON data structures.
*
* @param mixed $key
* @param mixed $value
* @since 0.5.3
*
* @return array
*/
private function _jsonDecodeValues($key, $value) {
$tmp_key = json_decode($key);
$tmp_value = json_decode($value);
if($tmp_value && is_array($tmp_value) || is_object($tmp_value)) {
array_walk_recursive($tmp_value, array($this, '_jsonConcatContents'));
$value = $this->tmpJsonString;
}
if($tmp_key && is_array($tmp_key) || is_object($tmp_key)) {
array_walk_recursive($tmp_key, array($this, '_jsonConcatContents'));
$key = $this->tmpJsonString;
}
return array($key, $value);
}
/**
* This is the callback used in _jsonDecodeValues(). The method
* concatenates key and value and stores them in $this->tmpJsonString.
*
* @param mixed $key
* @param mixed $value
* @since 0.5.3
*
* @return void
*/
private function _jsonConcatContents($key, $value) {
$this->tmpJsonString .= $key . " " . $value . "\n";
}
/**
* Matches given value and/or key against given filter
*
* @param mixed $key the key to optionally scan
* @param mixed $value the value to scan
* @param object $filter the filter object
*
* @return boolean
*/
private function _match($key, $value, $filter)
{
if ($this->scanKeys) {
if ($filter->match($key)) {
return true;
}
}
if ($filter->match($value)) {
return true;
}
return false;
}
/**
* Sets exception array
*
* @param mixed $exceptions the thrown exceptions
*
* @return void
*/
public function setExceptions($exceptions)
{
if (!is_array($exceptions)) {
$exceptions = array($exceptions);
}
$this->exceptions = $exceptions;
}
/**
* Returns exception array
*
* @return array
*/
public function getExceptions()
{
return $this->exceptions;
}
/**
* Sets html array
*
* @param mixed $html the fields containing html
* @since 0.5
*
* @return void
*/
public function setHtml($html)
{
if (!is_array($html)) {
$html = array($html);
}
$this->html = $html;
}
/**
* Adds a value to the html array
*
* @since 0.5
*
* @return void
*/
public function addHtml($value)
{
$this->html[] = $value;
}
/**
* Returns html array
*
* @since 0.5
*
* @return array the fields that contain allowed html
*/
public function getHtml()
{
return $this->html;
}
/**
* Sets json array
*
* @param mixed $json the fields containing json
* @since 0.5.3
*
* @return void
*/
public function setJson($json)
{
if (!is_array($json)) {
$json = array($json);
}
$this->json = $json;
}
/**
* Adds a value to the json array
*
* @since 0.5.3
*
* @return void
*/
public function addJson($value)
{
$this->json[] = $value;
}
/**
* Returns json array
*
* @since 0.5.3
*
* @return array the fields that contain json
*/
public function getJson()
{
return $this->json;
}
/**
* Returns storage container
*
* @return array
*/
public function getStorage()
{
return $this->storage;
}
/**
* Returns report object providing various functions to work with
* detected results. Also the centrifuge data is being set as property
* of the report object.
*
* @return object IDS_Report
*/
public function getReport()
{
if (isset($this->centrifuge) && $this->centrifuge) {
$this->report->setCentrifuge($this->centrifuge);
}
return $this->report;
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

View file

@ -0,0 +1,341 @@
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (http://php-ids.org)
*
* PHPIDS 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, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS 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 PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* @category Security
* @package PHPIDS
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Christian Matthies <ch0012@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @link http://php-ids.org/
*/
/**
* PHPIDS report object
*
* The report objects collects a number of events and thereby presents the
* detected results. It provides a convenient API to work with the results.
*
* Note that this class implements Countable, IteratorAggregate and
* a __toString() method
*
* @category Security
* @package PHPIDS
* @author Christian Matthies <ch0012@gmail.com>
* @author Mario Heiderich <mario.heiderich@gmail.com>
* @author Lars Strojny <lars@strojny.net>
* @copyright 2007 The PHPIDS Group
* @license http://www.gnu.org/licenses/lgpl.html LGPL
* @version Release: $Id:Report.php 517 2007-09-15 15:04:13Z mario $
* @link http://php-ids.org/
*/
class IDS_Report implements Countable, IteratorAggregate
{
/**
* Event container
*
* @var array
*/
protected $events = array();
/**
* List of affected tags
*
* This list of tags is collected from the collected event objects on
* demand when IDS_Report->getTags() is called
*
* @var array
*/
protected $tags = array();
/**
* Impact level
*
* The impact level is calculated on demand by adding the results of the
* event objects on IDS_Report->getImpact()
*
* @var integer
*/
protected $impact = 0;
/**
* Centrifuge data
*
* This variable - initiated as an empty array - carries all information
* about the centrifuge data if available
*
* @var array
*/
protected $centrifuge = array();
/**
* Constructor
*
* @param array $events the events the report should include
*
* @return void
*/
public function __construct(array $events = null)
{
if ($events) {
foreach ($events as $event) {
$this->addEvent($event);
}
}
}
/**
* Adds an IDS_Event object to the report
*
* @param object $event IDS_Event
*
* @return object $this
*/
public function addEvent(IDS_Event $event)
{
$this->clear();
$this->events[$event->getName()] = $event;
return $this;
}
/**
* Get event by name
*
* In most cases an event is identified by the key of the variable that
* contained maliciously appearing content
*
* @param scalar $name the event name
*
* @throws InvalidArgumentException if argument is invalid
* @return mixed IDS_Event object or false if the event does not exist
*/
public function getEvent($name)
{
if (!is_scalar($name)) {
throw new InvalidArgumentException(
'Invalid argument type given'
);
}
if ($this->hasEvent($name)) {
return $this->events[$name];
}
return false;
}
/**
* Returns list of affected tags
*
* @return array
*/
public function getTags()
{
if (!$this->tags) {
$this->tags = array();
foreach ($this->events as $event) {
$this->tags = array_merge($this->tags,
$event->getTags());
}
$this->tags = array_values(array_unique($this->tags));
}
return $this->tags;
}
/**
* Returns total impact
*
* Each stored IDS_Event object and its IDS_Filter sub-object are called
* to calculate the overall impact level of this request
*
* @return integer
*/
public function getImpact()
{
if (!$this->impact) {
$this->impact = 0;
foreach ($this->events as $event) {
$this->impact += $event->getImpact();
}
}
return $this->impact;
}
/**
* Checks if a specific event with given name exists
*
* @param scalar $name the event name
*
* @throws InvalidArgumentException if argument is illegal
*
* @return boolean
*/
public function hasEvent($name)
{
if (!is_scalar($name)) {
throw new InvalidArgumentException('Invalid argument given');
}
return isset($this->events[$name]);
}
/**
* Returns total amount of events
*
* @return integer
*/
public function count()
{
return count($this->events);
}
/**
* Return iterator object
*
* In order to provide the possibility to directly iterate over the
* IDS_Event object the IteratorAggregate is implemented. One can easily
* use foreach() to iterate through all stored IDS_Event objects.
*
* @return Iterator
*/
public function getIterator()
{
return new ArrayObject($this->events);
}
/**
* Checks if any events are registered
*
* @return boolean
*/
public function isEmpty()
{
return empty($this->events);
}
/**
* Clears calculated/collected values
*
* @return void
*/
protected function clear()
{
$this->impact = 0;
$this->tags = array();
}
/**
* This method returns the centrifuge property or null if not
* filled with data
*
* @return array/null
*/
public function getCentrifuge()
{
return ($this->centrifuge && count($this->centrifuge) > 0)
? $this->centrifuge : null;
}
/**
* This method sets the centrifuge property
*
* @param array $centrifuge the centrifuge data
*
* @throws InvalidArgumentException if argument is illegal
*
* @return boolean true is arguments were valid
*/
public function setCentrifuge($centrifuge = array())
{
if (is_array($centrifuge) && $centrifuge) {
$this->centrifuge = $centrifuge;
return true;
}
throw new InvalidArgumentException('Invalid argument given');
}
/**
* Directly outputs all available information
*
* @return string
*/
public function __toString()
{
if (!$this->isEmpty()) {
$output = '';
$output .= 'Total impact: ' . $this->getImpact() . "<br/>\n";
$output .= 'Affected tags: ' . join(', ', $this->getTags()) .
"<br/>\n";
foreach ($this->events as $event) {
$output .= "<br/>\nVariable: " .
htmlspecialchars($event->getName()) . ' | Value: ' .
htmlspecialchars($event->getValue()) . "<br/>\n";
$output .= 'Impact: ' . $event->getImpact() . ' | Tags: ' .
join(', ', $event->getTags()) . "<br/>\n";
foreach ($event as $filter) {
$output .= 'Description: ' . $filter->getDescription() .
' | ';
$output .= 'Tags: ' . join(', ', $filter->getTags()) .
' | ';
$output .= 'ID: ' . $filter->getId() .
"<br/>\n";
}
}
$output .= '<br/>';
if ($centrifuge = $this->getCentrifuge()) {
$output .= 'Centrifuge detection data';
$output .= '<br/> Threshold: ' .
((isset($centrifuge['threshold'])&&$centrifuge['threshold']) ?
$centrifuge['threshold'] : '---');
$output .= '<br/> Ratio: ' .
((isset($centrifuge['ratio'])&&$centrifuge['ratio']) ?
$centrifuge['ratio'] : '---');
if(isset($centrifuge['converted'])) {
$output .= '<br/> Converted: ' . $centrifuge['converted'];
}
$output .= "<br/><br/>\n";
}
}
return isset($output) ? $output : '';
}
}
/**
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 expandtab
*/

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,732 @@
<filters>
<filter>
<id>1</id>
<rule><![CDATA[(?:"+.*[^-]?>)|(?:[^\w\s]\s*\/>)|(?:>")]]></rule>
<description>finds html breaking injections including whitespace attacks</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>2</id>
<rule><![CDATA[(?:"+.*[<=]\s*"[^"]+")|(?:"\w+\s*=)|(?:>\w=\/)|(?:#.+\)["\s]*>)|(?:"\s*(?:src|style|on\w+)\s*=\s*")]]></rule>
<description>finds attribute breaking injections including whitespace attacks</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>69</id>
<rule><![CDATA[(?:[\s\/"]+(?:on\w+|style)=["\w])]]></rule>
<description>finds malicious attribute injection attempts</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>6</impact>
</filter>
<filter>
<id>3</id>
<rule><![CDATA[(?:^>[\w\s]*<\/?\w{2,}>)]]></rule>
<description>finds unquoted attribute breaking injections</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>2</impact>
</filter>
<filter>
<id>4</id>
<rule><![CDATA[(?:[+\/]\s*name[\W\d]*[)+])|(?:;\W*url\s*=)|(?:[^\w\s\/?:>]\s*(?:location|referrer|name)\s*[^\/\w\s-])]]></rule>
<description>Detects url-, name-, JSON, and referrer-contained payload attacks</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>5</id>
<rule><![CDATA[(?:\W\s*hash\s*[^\w\s-])|(?:\w+=\W*[^,]*,[^\s(]\s*\()|(?:\?"[^\s"]":)|(?:(?<!\/)__[a-z]+__)|(?:(?:^|[\s)\]\}])(?:s|g)etter\s*=)]]></rule>
<description>Detects hash-contained xss payload attacks, setter usage and property overloading</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>6</id>
<rule><![CDATA[(?:with\s*\(\s*.+\s*\)\s*\w+\s*\()|(?:(?:do|while|for)\s*\([^)]*\)\s*\{)|(?:\/[\w\s]*\[\W*\w)]]></rule>
<description>Detects self contained xss via with(), common loops and regex to string conversion</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>7</id>
<rule><![CDATA[(?:\d\s*[|&]{2}\s*\w)|(?:[=(].+\?.+:)|(?:with\([^)]*\)\))|(?:\.\s*source\W)|(?:\?[^:]+:[^;]+(;|$))]]></rule>
<description>Detects JavaScript with(), ternary operators and XML predicate attacks</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>8</id>
<rule><![CDATA[(?:\([\w\s]+\([\w\s]+\)[\w\s]+\))|(?:(?<!(?:mozilla\/\d\.\d\s))\([^)[]+\[[^\]]+\][^)]*\))|(?:[^\s!][{([][^({[]+[{([][^}\])]+[}\])][\s+",\d]*[}\])])|(?:"\)?\]\W*\[)|(?:=\s*[^\s:;]+\s*[{([][^}\])]+[}\])];)]]></rule>
<description>Detects self-executing JavaScript functions</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>9</id>
<rule><![CDATA[(?:\\u00[a-f0-9]{2})|(?:\\x0*[a-f0-9]{2})|(?:\\\d{2,3})]]></rule>
<description>Detects the IE octal, hex and unicode entities</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>2</impact>
</filter>
<filter>
<id>10</id>
<rule><![CDATA[(?:(?:\/|\\)?\.\.(\/|\\)(?:\.\.)?)|(?:\w+\.exe\??\s)|(?:;\s*\w+\s*\/[\w*-]+\/)|(?:\d\.\dx\|)|(?:%(?:c0\.|af\.|5c\.))|(?:\/(?:%2e){2})]]></rule>
<description>Detects basic directory traversal</description>
<tags>
<tag>dt</tag>
<tag>id</tag>
<tag>lfi</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>11</id>
<rule><![CDATA[(?:%c0%ae\/)|(?:(?:\/|\\)+(home|conf|usr|etc|proc|opt|s?bin|local|dev|tmp|kern|[br]oot|sys|system|windows|winnt|program|%[a-z_-]{3,}%)(?:\/|\\))|(?:(?:\/|\\)+inetpub|localstart\.asp|boot\.ini)]]></rule>
<description>Detects specific directory and path traversal</description>
<tags>
<tag>dt</tag>
<tag>id</tag>
<tag>lfi</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>12</id>
<rule><![CDATA[(?:etc\/\W*passwd)]]></rule>
<description>Detects etc/passwd inclusion attempts</description>
<tags>
<tag>dt</tag>
<tag>id</tag>
<tag>lfi</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>13</id>
<rule><![CDATA[(?:%u(?:ff|00|e\d)\w\w)|(?:(?:%(?:e\w|c[^3\W]|))(?:%\w\w)(?:%\w\w)?)]]></rule>
<description>Detects halfwidth/fullwidth encoded unicode HTML breaking attempts</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>3</impact>
</filter>
<filter>
<id>14</id>
<rule><![CDATA[(?:\w+script:|@import[^\w]|;base64|base64,)|(?:\w+\s*\([\w\s]+,[\w\s]+,[\w\s]+,[\w\s]+,[\w\s]+,[\w\s]+\))]]></rule>
<description>Detects possible includes and packed functions</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>15</id>
<rule><![CDATA[([^*:\s\w,.\/?+-]\s*)?(?<![a-z]\s)(?<![a-z\/_@>\-\|])(\s*return\s*)?(?:create(?:element|attribute|textnode)|[a-z]+Events?|getelement\w+|appendchild|createrange|createcontextualfragment|removenode|parentnode|decodeuricomponent|\wettimeout|useragent)(?(1)[^\w%"]|(?:\s*[^@\s\w%",.+\-]))]]></rule>
<description>Detects JavaScript DOM/miscellaneous properties and methods</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
</tags>
<impact>6</impact>
</filter>
<filter>
<id>16</id>
<rule><![CDATA[([^*\s\w,.\/?+-]\s*)?(?<![a-mo-z]\s)(?<![a-z\/_@>\-\|])(\s*return\s*)?(?:alert|showmodaldialog|infinity|isnan|isnull|msgbox|expression|prompt|write(?:ln)?|confirm|dialog|urn|(?:un)?eval|exec|execscript|tostring|status|execute|window|unescape|navigate)(?(1)[^\w%"]|(?:\s*[^@\s\w%",.:\/+\-]))]]></rule>
<description>Detects possible includes and typical script methods</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>17</id>
<rule><![CDATA[([^*:\s\w,.\/?+-]\s*)?(?<![a-z]\s)(?<![a-z\/_@>\-\|])(\s*return\s*)?(?:hash|name|href|navigateandfind|source|pathname|close|constructor|port|protocol|assign|replace|back|forward|document|window|self|parent|frames|_?content|date|cookie|innerhtml|innertext|csstext+?|outerhtml|print|moveby|resizeto|createstylesheet|stylesheets)(?(1)[^\w%"]|(?:\s*[^@\/\s\w%",.+\-]))]]></rule>
<description>Detects JavaScript object properties and methods</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>18</id>
<rule><![CDATA[([^*:\s\w,.\/?+-]\s*)?(?<![a-z]\s)(?<![a-z\/_@>\-\|])(\s*return\s*)?(?:join|pop|push|reverse|shift|sp?lice|sort|unshift)(?(1)[^\w%"]|(?:\s*[^@\s\w%",.+\-]))]]></rule>
<description>Detects JavaScript array properties and methods</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>19</id>
<rule><![CDATA[([^*:\s\w,.\/?+-]\s*)?(?<![a-z]\s)(?<![a-z\/_@>\-\|])(\s*return\s*)?(?:atob|btoa|charat|charcodeat|charset|concat|crypto|frames|fromcharcode|indexof|lastindexof|match|navigator|toolbar|menubar|replace|regexp|slice|split|substr|substring|escape|\w+codeuri\w*)(?(1)[^\w%"]|(?:\s*[^@\s\w%",.+\-]))]]></rule>
<description>Detects JavaScript string properties and methods</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>20</id>
<rule><![CDATA[([^*:\s\w,.\/?+-]\s*)?(?<![a-z]\s)(?<![a-z_@>\-\|])(\s*return\s*)?(?:globalstorage|sessionstorage|postmessage|callee|constructor|content|domain|prototype|try|catch|top|call|apply|url|function|object|array|string|math|if|elseif|case|switch|regex|boolean|location|settimeout|setinterval|void|setexpression|namespace|while)(?(1)[^\w%"]|(?:\s*[^@\s\w%",.+\-]))]]></rule>
<description>Detects JavaScript language constructs</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>21</id>
<rule><![CDATA[(?:,\s*(?:alert|showmodaldialog|eval)\s*,)|(?::\s*eval\s*[^\s])|([^:\s\w,.\/?+-]\s*)?(?<![a-z\/_@])(\s*return\s*)?(?:(?:document\s*\.)?(?:.+\/)?(?:alert|eval|msgbox|showmodaldialog|prompt|write(?:ln)?|confirm|dialog|open))\s*(?(1)[^\w]|(?:\s*[^\s\w,.@\/+-]))|(?:java[\s\/]*\.[\s\/]*lang)|(?:\w\s*=\s*new\s+\w+)|(?:&\s*\w+\s*\)[^,])|(?:\+[\W\d]*new\s+\w+[\W\d]*\+)|(?:document\.\w)]]></rule>
<description>Detects very basic XSS probings</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
</tags>
<impact>3</impact>
</filter>
<filter>
<id>22</id>
<rule><![CDATA[(?:[^\s]\s*=\s*script)|(?:\.\s*constructor)|(?:default\s+xml\s+namespace\s*=)|(?:\/\s*\+[^+]+\s*\+\s*\/)]]></rule>
<description>Detects advanced XSS probings via Script(), RexExp, constructors and XML namespaces</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>23</id>
<rule><![CDATA[(?:\W\s*(?:location|document)\s*\W[^({[;]+[({[;])|(?:\(\w+\?[:\w]+\))|(?:\w+\s*=\s*\d+\W\w+)|(?:\]\s*\(\s*\w+)]]></rule>
<description>Detects JavaScript location/document property access and window access obfuscation</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>24</id>
<rule><![CDATA[(?:\/[\w\s]+\/\.)|(?:=\s*\/\w+\/\s*\.)|(?:(?:this|window|top|parent|frames|self|content)\[\s*[(,"]*\s*\w)|(?:,\s*new\s+\w+\s*[,;)])]]></rule>
<description>Detects basic obfuscated JavaScript script injections</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>25</id>
<rule><![CDATA[(?:\(\s*(?:this|top|window|self|parent|_?content)\s*\))|(?:src\s*=s*(?:\w+:|\/\/))|(?:\w+\[("\w+"|\w+\|\|))|(?:[\d\W]\|\|[\d\W]|\W=\w+,)|(?:\/\s*\+\s*[a-z"])|(?:=\s*\$[^([]*\()|(?:=\s*\(\s*")]]></rule>
<description>Detects obfuscated JavaScript script injections</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>26</id>
<rule><![CDATA[(?:[^:\s\w]+\s*[^\w\/](href|protocol|host|hostname|pathname|hash|port|cookie)[^\w])]]></rule>
<description>Detects JavaScript cookie stealing and redirection attempts</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>27</id>
<rule><![CDATA[(?:data:(?:.)*,)|(?:\w+\s*=\W*(?!https?)\w+:)|(jar:\w+:)]]></rule>
<description>Detects data: URL injections and common URI schemes</description>
<tags>
<tag>xss</tag>
<tag>rfe</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>28</id>
<rule><![CDATA[(?:firefoxurl:\w+\|)|(?:(?:file|res|telnet|nntp|news|mailto|chrome)\s*:\s*[%&#xu\/]+)|(wyciwyg|firefoxurl\s*:\s*\/\s*\/)]]></rule>
<description>Detects IE firefoxurl injections, cache poisoning attempts and local file inclusion/execution</description>
<tags>
<tag>xss</tag>
<tag>rfe</tag>
<tag>lfi</tag>
<tag>csrf</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>29</id>
<rule><![CDATA[(?:binding\s?=|moz-binding|behavior\s?=)|(?:[\s\/]style\s*=\s*[-\\])]]></rule>
<description>Detects bindings and behavior injections</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>rfe</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>30</id>
<rule><![CDATA[(?:\+=\s*\(\s")|(?:!+\s*[\d.,]+\w?\d*\s*\?)|(?:=\s*\[s*\])|(?:"\s*\+\s*")|(?:[^\s]\[\s*\d+\s*\]\s*[;+])|(?:"\s*[&|]+\s*")|(?:\/\s*\?\s*")|(?:\/\s*\)\s*\[)|(?:\d\?.+:\d)|(?:]\s*\[\W*\w)|(?:[^\s]\s*=\s*\/)]]></rule>
<description>Detects common XSS concatenation patterns 1/2</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>31</id>
<rule><![CDATA[(?:=\s*\d*\.\d*\?\d*\.\d*)|(?:[|&]{2,}\s*")|(?:!\d+\.\d*\?")|(?:\/:[\w.]+,)|(?:=[\d\W\s]*\[[^]]+\])|(?:\?\w+:\w+)]]></rule>
<description>Detects common XSS concatenation patterns 2/2</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>32</id>
<rule><![CDATA[(?:[^\w\s=]on(?!g\&gt;)\w+[^=_+-]*=[^$]+(?:\W|\&gt;)?)]]></rule>
<description>Detects possible event handlers</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>33</id>
<rule><![CDATA[(?:\<\w*:?\s(?:[^\>]*)t(?!rong))|(?:\<scri)|(<\w+:\w+)]]></rule>
<description>Detects obfuscated script tags and XML wrapped HTML</description>
<tags>
<tag>xss</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>34</id>
<rule><![CDATA[(?:\<\/\w+\s\w+)|(?:@(?:cc_on|set)[\s@,"=])]]></rule>
<description>Detects attributes in closing tags and conditional compilation tokens</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>35</id>
<rule><![CDATA[(?:--[^\n]*$)|(?:\<!-|-->)|(?:\/\*|\*\/)|(?:(?:[\W\d]#|--|{)$)|(?:\/{3,}.*$)|(?:<!\[\W)|(?:\]!>)]]></rule>
<description>Detects common comment types</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
</tags>
<impact>3</impact>
</filter>
<filter>
<id>36</id>
<rule><![CDATA[(?:--.*[^-]>)|(?:opera\s*\.\s*\w+\s*\()]]></rule>
<description>Detects comments to exploit firefox' faulty rendering and proprietary opera attacks</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
</tags>
<impact>3</impact>
</filter>
<filter>
<id>37</id>
<rule><![CDATA[(?:\<base\s+)|(?:<!(?:element|entity|\[CDATA))]]></rule>
<description>Detects base href injections and XML entity injections</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>38</id>
<rule><![CDATA[(?:\<[\/]?(?:[i]?frame|applet|isindex|marquee|keygen|script|audio|video|input|button|textarea|style|base|body|meta|link|object|embed|param|plaintext|xm\w+|image|im(?:g|port)))]]></rule>
<description>Detects possibly malicious html elements including some attributes</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
<tag>lfi</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>39</id>
<rule><![CDATA[(?:\\x[01FE]\w)|(?:%[01FE]\w)|(?:&#[01FE]\w)|(?:\\[01FE][0-9a-f])|(?:&#x[01FE]\w)]]></rule>
<description>Detects nullbytes and HTTP response splitting</description>
<tags>
<tag>id</tag>
<tag>rfe</tag>
<tag>xss</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>40</id>
<rule><![CDATA[(?:"\s*(?:#|--|{))|(?:\/\*!\s?\d+)|(?:ch(?:a)?r\s*\(\s*\d)|(?:(?:(n?and|x?or|not)\s+|\|\||\&\&)\s*\w+\()]]></rule>
<description>Detects MySQL comments, conditions and ch(a)r injections</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
<tag>lfi</tag>
</tags>
<impact>6</impact>
</filter>
<filter>
<id>41</id>
<rule><![CDATA[(?:\)\s*like\s*\()|(?:having\s+[\d\w\-"]+\s*[(=<>~])|(?:if\s?\([\d\w]\s*[=<>~])]]></rule>
<description>Detects conditional SQL injection attempts</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
<tag>lfi</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>42</id>
<rule><![CDATA[(?:\\x(?:23|27|3d))|(?:^.?"$)|(?:^.*\\".+(?<!\\)")|(?:(?:^["\\]*(?:[\d"]+|[^"]+"))+\s*(?:n?and|x?or|not |\|\||\&\&)\s*[\d"[+&!@(),.-])|(?:[^\w\s]\w+\s*[|-]\s*"\s*\w)|(?:@\w+\s+(and|or)\s*["\d]+)|(?:@[\w-]+\s(and|or)\s*[^\w\s])|(?:[^\w\s:]\s*\d\W+[^\w\s]\s*".)]]></rule>
<description>Detects classic SQL injection probings 1/2</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
<tag>lfi</tag>
</tags>
<impact>6</impact>
</filter>
<filter>
<id>43</id>
<rule><![CDATA[(?:"\s*\*.+(?:or|id)\W*"\d)|(?:\^")|(?:^[\w\s"-]+(?<=and\s)(?<=or\s)(?<=xor\s)(?<=nand\s)(?<=not\s)(?<=\|\|)(?<=\&\&)\w+\()|(?:"[\s\d]*[^\w\s]+\W*\d\W*.*["\d])|(?:"\s*[^\w\s?]+\s*[^\w\s]+\s*")|(?:"\s*[^\w\s]+\s*[\W\d].*(?:#|--))|(?:".*\*\s*\d)|(?:"\s*or\s[\w-]+[\s=*])|(?:[()*<>%+-][\w-]+[^\w\s]+"[^,])]]></rule>
<description>Detects classic SQL injection probings 2/2</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
<tag>lfi</tag>
</tags>
<impact>6</impact>
</filter>
<filter>
<id>44</id>
<rule><![CDATA[(?:\d"\s+"\s+\d)|(?:^admin\s*"|(\/\*)+"+\s?(?:--|#|\/\*|{)?)|(?:"\s*or[\w\s-]+\s*[+<>=(),-]\s*[\d"])|(?:"\s*[^\w\s]?=\s*")|(?:"\W*[+=]+\W*")|(?:"\s*[!=|][\d\s!=+-]+.*["(].*$)|(?:"\s*[!=|][\d\s!=]+.*\d+$)|(?:"\s*like\W+[\w"(])|(?:\sis\s*0\W)|(?:where\s[\s\w\.,-]+\s=)|(?:"[<>~]+")]]></rule>
<description>Detects basic SQL authentication bypass attempts 1/3</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
<tag>lfi</tag>
</tags>
<impact>7</impact>
</filter>
<filter>
<id>45</id>
<rule><![CDATA[(?:union\s*(?:all|distinct|[(!@]*)?\s*[([]\s*select)|(?:\w+\s+like\s+\")|(?:like\s*"\%)|(?:"\s*like\W*["\d])|(?:"\s*(?:n?and|x?or|not |\|\||\&\&)\s+[\s\w]+=\s*\w+\s*having)|(?:"\s*\*\s*\w+\W+")|(?:"\s*[^?\w\s=.,;)(]+\s*[(@]*\s*\w+\W+\w)|(?:select\s*[\[\]()\s\w\.,-]+from)]]></rule>
<description>Detects basic SQL authentication bypass attempts 2/3</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
<tag>lfi</tag>
</tags>
<impact>7</impact>
</filter>
<filter>
<id>46</id>
<rule><![CDATA[(?:(?:n?and|x?or|not |\|\||\&\&)\s+[\s\w+]+(?:regexp\s*\(|sounds\s+like\s*"|[=\d]+x))|("\s*\d\s*(?:--|#))|(?:"[%&<>^=]+\d\s*(=|or))|(?:"\W+[\w+-]+\s*=\s*\d\W+")|(?:"\s*is\s*\d.+"?\w)|(?:"\|?[\w-]{3,}[^\w\s.,]+")|(?:"\s*is\s*[\d.]+\s*\W.*")]]></rule>
<description>Detects basic SQL authentication bypass attempts 3/3</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
<tag>lfi</tag>
</tags>
<impact>7</impact>
</filter>
<filter>
<id>47</id>
<rule><![CDATA[(?:^\s*[;>"]\s*(?:union|select|create|rename|truncate|load|alter|delete|update|insert|desc))|(?:(?:select|create|rename|truncate|load|alter|delete|update|insert|desc)\s+(?:concat|char|load_file)\s?\(?)|(?:end\s*\);)|("\s+regexp\W)]]></rule>
<description>Detects concatenated basic SQL injection and SQLLFI attempts</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
<tag>lfi</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>48</id>
<rule><![CDATA[(?:\/\w+;?\s+(?:having|and|or|select))|(?:\d\s+group\s+by.+\()|(?:(?:;|#|--)\s*(?:drop|alter))|(?:(?:;|#|--)\s*(?:update|insert)\s*\w{2,})|(?:[^\w]SET\s*@\w+)|(?:(?:n?and|x?or|not |\|\||\&\&)\s+\w+[!=+]+[\s\d]*["=(])]]></rule>
<description>Detects chained SQL injection attempts 1/2</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
</tags>
<impact>6</impact>
</filter>
<filter>
<id>49</id>
<rule><![CDATA[(?:\*\/from)|(?:\+\s*\d+\s*\+\s*@)|(?:\w"\s*(?:[-+=|@]+\s*)+[\d(])|(?:coalesce\s*\(|@@\w+\s*[^\w\s])|(?:\W!+"\w)|(?:";\s*(?:if|while|begin))|(?:"[\s\d]+=\s*\d)|(?:order\s+by\s+if\w*\s*\()]]></rule>
<description>Detects chained SQL injection attempts 2/2</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
</tags>
<impact>6</impact>
</filter>
<filter>
<id>50</id>
<rule><![CDATA[(?:(select|;)\s+(?:benchmark|if|sleep)\s?\(\s?\(?\s?\w+)]]></rule>
<description>Detects SQL benchmark and sleep injection attempts including conditional queries</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
</tags>
<impact>4</impact>
</filter>
<filter>
<id>51</id>
<rule><![CDATA[(?:create\s+function\s+\w+\s+returns)|(?:;\s*(?:select|create|rename|truncate|load|alter|delete|update|insert|desc)\s*[\[(]?\w{2,})]]></rule>
<description>Detects MySQL UDF injection and other data/structure manipulation attempts</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
</tags>
<impact>6</impact>
</filter>
<filter>
<id>52</id>
<rule><![CDATA[(?:alter\s*\w+.*character\s+set\s+\w+)|(";\s*waitfor\s+time\s+")|(?:";.*:\s*goto)]]></rule>
<description>Detects MySQL charset switch and MSSQL DoS attempts</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
</tags>
<impact>6</impact>
</filter>
<filter>
<id>53</id>
<rule><![CDATA[(?:procedure\s+analyse\s*\()|(?:;\s*(declare|open)\s+[\w-]+)|(?:create\s+(procedure|function)\s*\w+\s*\(\s*\)\s*-)|(?:declare[^\w]+[@#]\s*\w+)|(exec\s*\(\s*@)]]></rule>
<description>Detects MySQL and PostgreSQL stored procedure/function injections</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
</tags>
<impact>7</impact>
</filter>
<filter>
<id>54</id>
<rule><![CDATA[(?:select\s*pg_sleep)|(?:waitfor\s*delay\s?"+\s?\d)|(?:;\s*shutdown\s*(?:;|--|#|\/\*|{))]]></rule>
<description>Detects Postgres pg_sleep injection, waitfor delay attacks and database shutdown attempts</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>55</id>
<rule><![CDATA[(?:";?\s*(?:select|union|having)\s*["(\d])|(?:\wiif\s*\()|(?:exec\s+master\.)|(?:union select @)|(?:union[\w(\s]*select)|(?:select.*\w?user\()|(?:into[\s+]+(?:dump|out)file\s*")]]></rule>
<description>Detects MSSQL code execution and information gathering attempts</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>56</id>
<rule><![CDATA[(?:merge.*using\s*\()|(execute\s*immediate\s*")|(?:\W+\d*\s+having\s+\d)|(?:match\s*[\w(),+-]+\s*against\s*\()]]></rule>
<description>Detects MATCH AGAINST, MERGE, EXECUTE IMMEDIATE and HAVING injections</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>57</id>
<rule><![CDATA[(?:select\s*\*\s*from)|((?:select|create|rename|truncate|load|alter|delete|update|insert|desc)\s*\(\s*space\s*\()]]></rule>
<description>Detects MySQL comment-/space-obfuscated injections</description>
<tags>
<tag>sqli</tag>
<tag>id</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>58</id>
<rule><![CDATA[(?:@[\w-]+\s*\()|(?:]\s*\(\s*["!]\s*\w)|(?:<[?%](?:php)?.*(?:[?%]>)?)|(?:;[\s\w|]*\$\w+\s*=)|(?:\$\w+\s*=(?:(?:\s*\$?\w+\s*[(;])|\s*".*"))|(?:;\s*\{\W*\w+\s*\()]]></rule>
<description>Detects code injection attempts 1/3</description>
<tags>
<tag>id</tag>
<tag>rfe</tag>
<tag>lfi</tag>
</tags>
<impact>7</impact>
</filter>
<filter>
<id>59</id>
<rule><![CDATA[(?:(?:[;]+|(<[?%](?:php)?)).*(?:define|eval|file_get_contents|include|require|require_once|set|shell_exec|phpinfo|system|passthru|preg_\w+|execute)\s*["(@])]]></rule>
<description>Detects code injection attempts 2/3</description>
<tags>
<tag>id</tag>
<tag>rfe</tag>
<tag>lfi</tag>
</tags>
<impact>7</impact>
</filter>
<filter>
<id>60</id>
<rule><![CDATA[(?:(?:[;]+|(<[?%](?:php)?)).*[^\w](?:echo|print|print_r|var_dump|[fp]open))|(?:;\s*rm\s+-\w+\s+)|(?:;.*{.*\$\w+\s*=)|(?:\$\w+\s*\[\]\s*=\s*)]]></rule>
<description>Detects code injection attempts 3/3</description>
<tags>
<tag>id</tag>
<tag>rfe</tag>
<tag>lfi</tag>
</tags>
<impact>7</impact>
</filter>
<filter>
<id>61</id>
<rule><![CDATA[(?:\w+]?(?<!href)(?<!src)(?<!longdesc)(?<!returnurl)=(?:https?|ftp):)|(?:\{\s*\$\s*\{)]]></rule>
<description>Detects url injections and RFE attempts</description>
<tags>
<tag>id</tag>
<tag>rfe</tag>
<tag>lfi</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>62</id>
<rule><![CDATA[(?:function[^(]*\([^)]*\))|(?:(?:delete|void|throw|instanceof|new|typeof)\W+\w+\s*[([])|([)\]]\s*\.\s*\w+\s*=)|(?:\(\s*new\s+\w+\s*\)\.)]]></rule>
<description>Detects common function declarations and special JS operators</description>
<tags>
<tag>id</tag>
<tag>rfe</tag>
<tag>lfi</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>63</id>
<rule><![CDATA[(?:[\w.-]+@[\w.-]+%(?:[01]\w)+\w+:)]]></rule>
<description>Detects common mail header injections</description>
<tags>
<tag>id</tag>
<tag>spam</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>64</id>
<rule><![CDATA[(?:\.pl\?\w+=\w?\|\w+;)|(?:\|\(\w+=\*)|(?:\*\s*\)+\s*;)]]></rule>
<description>Detects perl echo shellcode injection and LDAP vectors</description>
<tags>
<tag>lfi</tag>
<tag>rfe</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>65</id>
<rule><![CDATA[(?:(?:do|for|while)\s*\([^;]+;+\))|(?:(?:^|\W)on\w+\s*=[\w\W]*(?:on\w+|alert|eval|print|confirm|prompt))|(?:groups=\d+\(\w+\))|(?:(.)\1{128,})]]></rule>
<description>Detects basic XSS DoS attempts</description>
<tags>
<tag>rfe</tag>
<tag>dos</tag>
</tags>
<impact>5</impact>
</filter>
<filter>
<id>67</id>
<rule><![CDATA[(?:\({2,}\+{2,}:{2,})|(?:\({2,}\+{2,}:+)|(?:\({3,}\++:{2,})|(?:\$\[!!!\])]]></rule>
<description>Detects unknown attack vectors based on PHPIDS Centrifuge detection</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
<tag>id</tag>
<tag>rfe</tag>
<tag>lfi</tag>
</tags>
<impact>7</impact>
</filter>
<filter>
<id>68</id>
<rule><![CDATA[(?:[\s\/"]+[-\w\/\\\*]+\s*=.+(?:\/\s*>))]]></rule>
<description>finds attribute breaking injections including obfuscated attributes</description>
<tags>
<tag>xss</tag>
<tag>csrf</tag>
</tags>
<impact>4</impact>
</filter>
</filters>

View file

@ -0,0 +1 @@
"local/unknown",2010-03-15T23:07:01+00:00,72,"xss csrf id rfe lfi sqli","REQUEST.test=%22%3E%3Cscript%3Eeval%28window.name%29%3C%2Fscript%3E GET.test=%22%3E%3Cscript%3Eeval%28window.name%29%3C%2Fscript%3E","%2Fdvwa%2Fsecurity.php%3Ftest%3D%2522%253E%253Cscript%253Eeval%28window.name%29%253C%2Fscript%253E","127.0.0.1"

View file

@ -0,0 +1,9 @@
<?php
/**
* This is a stub include that automatically configures the include path.
*/
set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
require_once 'HTMLPurifier/Bootstrap.php';
require_once 'HTMLPurifier.autoload.php';

View file

@ -0,0 +1,19 @@
<?php
/**
* @file
* Convenience file that registers autoload handler for HTML Purifier.
*/
if (function_exists('spl_autoload_register') && function_exists('spl_autoload_unregister')) {
// We need unregister for our pre-registering functionality
HTMLPurifier_Bootstrap::registerAutoload();
if (function_exists('__autoload')) {
// Be polite and ensure that userland autoload gets retained
spl_autoload_register('__autoload');
}
} elseif (!function_exists('__autoload')) {
function __autoload($class) {
return HTMLPurifier_Bootstrap::autoload($class);
}
}

View file

@ -0,0 +1,22 @@
<?php
/**
* @file
* Defines a function wrapper for HTML Purifier for quick use.
* @note ''HTMLPurifier()'' is NOT the same as ''new HTMLPurifier()''
*/
/**
* Purify HTML.
* @param $html String HTML to purify
* @param $config Configuration to use, can be any value accepted by
* HTMLPurifier_Config::create()
*/
function HTMLPurifier($html, $config = null) {
static $purifier = false;
if (!$purifier) {
$purifier = new HTMLPurifier();
}
return $purifier->purify($html, $config);
}

View file

@ -0,0 +1,204 @@
<?php
/**
* @file
* This file was auto-generated by generate-includes.php and includes all of
* the core files required by HTML Purifier. Use this if performance is a
* primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
* FILE, changes will be overwritten the next time the script is run.
*
* @version 3.2.0
*
* @warning
* You must *not* include any other HTML Purifier files before this file,
* because 'require' not 'require_once' is used.
*
* @warning
* This file requires that the include path contains the HTML Purifier
* library directory; this is not auto-set.
*/
require 'HTMLPurifier.php';
require 'HTMLPurifier/AttrCollections.php';
require 'HTMLPurifier/AttrDef.php';
require 'HTMLPurifier/AttrTransform.php';
require 'HTMLPurifier/AttrTypes.php';
require 'HTMLPurifier/AttrValidator.php';
require 'HTMLPurifier/Bootstrap.php';
require 'HTMLPurifier/Definition.php';
require 'HTMLPurifier/CSSDefinition.php';
require 'HTMLPurifier/ChildDef.php';
require 'HTMLPurifier/Config.php';
require 'HTMLPurifier/ConfigSchema.php';
require 'HTMLPurifier/ContentSets.php';
require 'HTMLPurifier/Context.php';
require 'HTMLPurifier/DefinitionCache.php';
require 'HTMLPurifier/DefinitionCacheFactory.php';
require 'HTMLPurifier/Doctype.php';
require 'HTMLPurifier/DoctypeRegistry.php';
require 'HTMLPurifier/ElementDef.php';
require 'HTMLPurifier/Encoder.php';
require 'HTMLPurifier/EntityLookup.php';
require 'HTMLPurifier/EntityParser.php';
require 'HTMLPurifier/ErrorCollector.php';
require 'HTMLPurifier/ErrorStruct.php';
require 'HTMLPurifier/Exception.php';
require 'HTMLPurifier/Filter.php';
require 'HTMLPurifier/Generator.php';
require 'HTMLPurifier/HTMLDefinition.php';
require 'HTMLPurifier/HTMLModule.php';
require 'HTMLPurifier/HTMLModuleManager.php';
require 'HTMLPurifier/IDAccumulator.php';
require 'HTMLPurifier/Injector.php';
require 'HTMLPurifier/Language.php';
require 'HTMLPurifier/LanguageFactory.php';
require 'HTMLPurifier/Length.php';
require 'HTMLPurifier/Lexer.php';
require 'HTMLPurifier/PercentEncoder.php';
require 'HTMLPurifier/Strategy.php';
require 'HTMLPurifier/StringHash.php';
require 'HTMLPurifier/StringHashParser.php';
require 'HTMLPurifier/TagTransform.php';
require 'HTMLPurifier/Token.php';
require 'HTMLPurifier/TokenFactory.php';
require 'HTMLPurifier/URI.php';
require 'HTMLPurifier/URIDefinition.php';
require 'HTMLPurifier/URIFilter.php';
require 'HTMLPurifier/URIParser.php';
require 'HTMLPurifier/URIScheme.php';
require 'HTMLPurifier/URISchemeRegistry.php';
require 'HTMLPurifier/UnitConverter.php';
require 'HTMLPurifier/VarParser.php';
require 'HTMLPurifier/VarParserException.php';
require 'HTMLPurifier/AttrDef/CSS.php';
require 'HTMLPurifier/AttrDef/Enum.php';
require 'HTMLPurifier/AttrDef/Integer.php';
require 'HTMLPurifier/AttrDef/Lang.php';
require 'HTMLPurifier/AttrDef/Switch.php';
require 'HTMLPurifier/AttrDef/Text.php';
require 'HTMLPurifier/AttrDef/URI.php';
require 'HTMLPurifier/AttrDef/CSS/Number.php';
require 'HTMLPurifier/AttrDef/CSS/AlphaValue.php';
require 'HTMLPurifier/AttrDef/CSS/Background.php';
require 'HTMLPurifier/AttrDef/CSS/BackgroundPosition.php';
require 'HTMLPurifier/AttrDef/CSS/Border.php';
require 'HTMLPurifier/AttrDef/CSS/Color.php';
require 'HTMLPurifier/AttrDef/CSS/Composite.php';
require 'HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php';
require 'HTMLPurifier/AttrDef/CSS/Filter.php';
require 'HTMLPurifier/AttrDef/CSS/Font.php';
require 'HTMLPurifier/AttrDef/CSS/FontFamily.php';
require 'HTMLPurifier/AttrDef/CSS/ImportantDecorator.php';
require 'HTMLPurifier/AttrDef/CSS/Length.php';
require 'HTMLPurifier/AttrDef/CSS/ListStyle.php';
require 'HTMLPurifier/AttrDef/CSS/Multiple.php';
require 'HTMLPurifier/AttrDef/CSS/Percentage.php';
require 'HTMLPurifier/AttrDef/CSS/TextDecoration.php';
require 'HTMLPurifier/AttrDef/CSS/URI.php';
require 'HTMLPurifier/AttrDef/HTML/Bool.php';
require 'HTMLPurifier/AttrDef/HTML/Color.php';
require 'HTMLPurifier/AttrDef/HTML/FrameTarget.php';
require 'HTMLPurifier/AttrDef/HTML/ID.php';
require 'HTMLPurifier/AttrDef/HTML/Pixels.php';
require 'HTMLPurifier/AttrDef/HTML/Length.php';
require 'HTMLPurifier/AttrDef/HTML/LinkTypes.php';
require 'HTMLPurifier/AttrDef/HTML/MultiLength.php';
require 'HTMLPurifier/AttrDef/HTML/Nmtokens.php';
require 'HTMLPurifier/AttrDef/URI/Email.php';
require 'HTMLPurifier/AttrDef/URI/Host.php';
require 'HTMLPurifier/AttrDef/URI/IPv4.php';
require 'HTMLPurifier/AttrDef/URI/IPv6.php';
require 'HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php';
require 'HTMLPurifier/AttrTransform/Background.php';
require 'HTMLPurifier/AttrTransform/BdoDir.php';
require 'HTMLPurifier/AttrTransform/BgColor.php';
require 'HTMLPurifier/AttrTransform/BoolToCSS.php';
require 'HTMLPurifier/AttrTransform/Border.php';
require 'HTMLPurifier/AttrTransform/EnumToCSS.php';
require 'HTMLPurifier/AttrTransform/ImgRequired.php';
require 'HTMLPurifier/AttrTransform/ImgSpace.php';
require 'HTMLPurifier/AttrTransform/Input.php';
require 'HTMLPurifier/AttrTransform/Lang.php';
require 'HTMLPurifier/AttrTransform/Length.php';
require 'HTMLPurifier/AttrTransform/Name.php';
require 'HTMLPurifier/AttrTransform/SafeEmbed.php';
require 'HTMLPurifier/AttrTransform/SafeObject.php';
require 'HTMLPurifier/AttrTransform/SafeParam.php';
require 'HTMLPurifier/AttrTransform/ScriptRequired.php';
require 'HTMLPurifier/AttrTransform/Textarea.php';
require 'HTMLPurifier/ChildDef/Chameleon.php';
require 'HTMLPurifier/ChildDef/Custom.php';
require 'HTMLPurifier/ChildDef/Empty.php';
require 'HTMLPurifier/ChildDef/Required.php';
require 'HTMLPurifier/ChildDef/Optional.php';
require 'HTMLPurifier/ChildDef/StrictBlockquote.php';
require 'HTMLPurifier/ChildDef/Table.php';
require 'HTMLPurifier/DefinitionCache/Decorator.php';
require 'HTMLPurifier/DefinitionCache/Null.php';
require 'HTMLPurifier/DefinitionCache/Serializer.php';
require 'HTMLPurifier/DefinitionCache/Decorator/Cleanup.php';
require 'HTMLPurifier/DefinitionCache/Decorator/Memory.php';
require 'HTMLPurifier/HTMLModule/Bdo.php';
require 'HTMLPurifier/HTMLModule/CommonAttributes.php';
require 'HTMLPurifier/HTMLModule/Edit.php';
require 'HTMLPurifier/HTMLModule/Forms.php';
require 'HTMLPurifier/HTMLModule/Hypertext.php';
require 'HTMLPurifier/HTMLModule/Image.php';
require 'HTMLPurifier/HTMLModule/Legacy.php';
require 'HTMLPurifier/HTMLModule/List.php';
require 'HTMLPurifier/HTMLModule/Name.php';
require 'HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php';
require 'HTMLPurifier/HTMLModule/Object.php';
require 'HTMLPurifier/HTMLModule/Presentation.php';
require 'HTMLPurifier/HTMLModule/Proprietary.php';
require 'HTMLPurifier/HTMLModule/Ruby.php';
require 'HTMLPurifier/HTMLModule/SafeEmbed.php';
require 'HTMLPurifier/HTMLModule/SafeObject.php';
require 'HTMLPurifier/HTMLModule/Scripting.php';
require 'HTMLPurifier/HTMLModule/StyleAttribute.php';
require 'HTMLPurifier/HTMLModule/Tables.php';
require 'HTMLPurifier/HTMLModule/Target.php';
require 'HTMLPurifier/HTMLModule/Text.php';
require 'HTMLPurifier/HTMLModule/Tidy.php';
require 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
require 'HTMLPurifier/HTMLModule/Tidy/Name.php';
require 'HTMLPurifier/HTMLModule/Tidy/Proprietary.php';
require 'HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php';
require 'HTMLPurifier/HTMLModule/Tidy/Strict.php';
require 'HTMLPurifier/HTMLModule/Tidy/Transitional.php';
require 'HTMLPurifier/HTMLModule/Tidy/XHTML.php';
require 'HTMLPurifier/Injector/AutoParagraph.php';
require 'HTMLPurifier/Injector/DisplayLinkURI.php';
require 'HTMLPurifier/Injector/Linkify.php';
require 'HTMLPurifier/Injector/PurifierLinkify.php';
require 'HTMLPurifier/Injector/RemoveEmpty.php';
require 'HTMLPurifier/Injector/SafeObject.php';
require 'HTMLPurifier/Lexer/DOMLex.php';
require 'HTMLPurifier/Lexer/DirectLex.php';
require 'HTMLPurifier/Strategy/Composite.php';
require 'HTMLPurifier/Strategy/Core.php';
require 'HTMLPurifier/Strategy/FixNesting.php';
require 'HTMLPurifier/Strategy/MakeWellFormed.php';
require 'HTMLPurifier/Strategy/RemoveForeignElements.php';
require 'HTMLPurifier/Strategy/ValidateAttributes.php';
require 'HTMLPurifier/TagTransform/Font.php';
require 'HTMLPurifier/TagTransform/Simple.php';
require 'HTMLPurifier/Token/Comment.php';
require 'HTMLPurifier/Token/Tag.php';
require 'HTMLPurifier/Token/Empty.php';
require 'HTMLPurifier/Token/End.php';
require 'HTMLPurifier/Token/Start.php';
require 'HTMLPurifier/Token/Text.php';
require 'HTMLPurifier/URIFilter/DisableExternal.php';
require 'HTMLPurifier/URIFilter/DisableExternalResources.php';
require 'HTMLPurifier/URIFilter/HostBlacklist.php';
require 'HTMLPurifier/URIFilter/MakeAbsolute.php';
require 'HTMLPurifier/URIFilter/Munge.php';
require 'HTMLPurifier/URIScheme/ftp.php';
require 'HTMLPurifier/URIScheme/http.php';
require 'HTMLPurifier/URIScheme/https.php';
require 'HTMLPurifier/URIScheme/mailto.php';
require 'HTMLPurifier/URIScheme/news.php';
require 'HTMLPurifier/URIScheme/nntp.php';
require 'HTMLPurifier/VarParser/Flexible.php';
require 'HTMLPurifier/VarParser/Native.php';

View file

@ -0,0 +1,28 @@
<?php
/**
* @file
* Emulation layer for code that used kses(), substituting in HTML Purifier.
*/
require_once dirname(__FILE__) . '/HTMLPurifier.auto.php';
function kses($string, $allowed_html, $allowed_protocols = null) {
$config = HTMLPurifier_Config::createDefault();
$allowed_elements = array();
$allowed_attributes = array();
foreach ($allowed_html as $element => $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);
$allowed_schemes = array();
if ($allowed_protocols !== null) {
$config->set('URI', 'AllowedSchemes', $allowed_protocols);
}
$purifier = new HTMLPurifier($config);
return $purifier->purify($string);
}

View file

@ -0,0 +1,9 @@
<?php
/**
* @file
* Convenience stub file that adds HTML Purifier's library file to the path
* without any other side-effects.
*/
set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );

View file

@ -0,0 +1,234 @@
<?php
/*! @mainpage
*
* HTML Purifier is an HTML filter that will take an arbitrary snippet of
* HTML and rigorously test, validate and filter it into a version that
* is safe for output onto webpages. It achieves this by:
*
* -# Lexing (parsing into tokens) the document,
* -# Executing various strategies on the tokens:
* -# Removing all elements not in the whitelist,
* -# Making the tokens well-formed,
* -# Fixing the nesting of the nodes, and
* -# Validating attributes of the nodes; and
* -# Generating HTML from the purified tokens.
*
* However, most users will only need to interface with the HTMLPurifier
* and HTMLPurifier_Config.
*/
/*
HTML Purifier 3.2.0 - Standards Compliant HTML Filtering
Copyright (C) 2006-2008 Edward Z. Yang
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
*/
/**
* Facade that coordinates HTML Purifier's subsystems in order to purify HTML.
*
* @note There are several points in which configuration can be specified
* for HTML Purifier. The precedence of these (from lowest to
* highest) is as follows:
* -# Instance: new HTMLPurifier($config)
* -# Invocation: purify($html, $config)
* These configurations are entirely independent of each other and
* are *not* merged (this behavior may change in the future).
*
* @todo We need an easier way to inject strategies using the configuration
* object.
*/
class HTMLPurifier
{
/** Version of HTML Purifier */
public $version = '3.2.0';
/** Constant with version of HTML Purifier */
const VERSION = '3.2.0';
/** Global configuration object */
public $config;
/** Array of extra HTMLPurifier_Filter objects to run on HTML, for backwards compatibility */
private $filters = array();
/** Single instance of HTML Purifier */
private static $instance;
protected $strategy, $generator;
/**
* Resultant HTMLPurifier_Context of last run purification. Is an array
* of contexts if the last called method was purifyArray().
*/
public $context;
/**
* Initializes the purifier.
* @param $config Optional HTMLPurifier_Config object for all instances of
* the purifier, if omitted, a default configuration is
* supplied (which can be overridden on a per-use basis).
* The parameter can also be any type that
* HTMLPurifier_Config::create() supports.
*/
public function __construct($config = null) {
$this->config = HTMLPurifier_Config::create($config);
$this->strategy = new HTMLPurifier_Strategy_Core();
}
/**
* Adds a filter to process the output. First come first serve
* @param $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 $html String of HTML to purify
* @param $config HTMLPurifier_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 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;
$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 $config Optional HTMLPurifier_Config object for this operation.
* See HTMLPurifier::purify() for more details.
* @return Array of purified HTML
*/
public function purifyArray($array_of_html, $config = null) {
$context_array = array();
foreach ($array_of_html as $key => $html) {
$array_of_html[$key] = $this->purify($html, $config);
$context_array[$key] = $this->context;
}
$this->context = $context_array;
return $array_of_html;
}
/**
* Singleton for enforcing just one HTML Purifier in your system
* @param $prototype Optional prototype HTMLPurifier instance to
* overload singleton with, or HTMLPurifier_Config
* instance to configure the generated version with.
*/
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;
}
/**
* @note Backwards compatibility, see instance()
*/
public static function getInstance($prototype = null) {
return HTMLPurifier::instance($prototype);
}
}

View file

@ -0,0 +1,198 @@
<?php
/**
* @file
* This file was auto-generated by generate-includes.php and includes all of
* the core files required by HTML Purifier. This is a convenience stub that
* includes all files using dirname(__FILE__) and require_once. PLEASE DO NOT
* EDIT THIS FILE, changes will be overwritten the next time the script is run.
*
* Changes to include_path are not necessary.
*/
$__dir = dirname(__FILE__);
require_once $__dir . '/HTMLPurifier.php';
require_once $__dir . '/HTMLPurifier/AttrCollections.php';
require_once $__dir . '/HTMLPurifier/AttrDef.php';
require_once $__dir . '/HTMLPurifier/AttrTransform.php';
require_once $__dir . '/HTMLPurifier/AttrTypes.php';
require_once $__dir . '/HTMLPurifier/AttrValidator.php';
require_once $__dir . '/HTMLPurifier/Bootstrap.php';
require_once $__dir . '/HTMLPurifier/Definition.php';
require_once $__dir . '/HTMLPurifier/CSSDefinition.php';
require_once $__dir . '/HTMLPurifier/ChildDef.php';
require_once $__dir . '/HTMLPurifier/Config.php';
require_once $__dir . '/HTMLPurifier/ConfigSchema.php';
require_once $__dir . '/HTMLPurifier/ContentSets.php';
require_once $__dir . '/HTMLPurifier/Context.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache.php';
require_once $__dir . '/HTMLPurifier/DefinitionCacheFactory.php';
require_once $__dir . '/HTMLPurifier/Doctype.php';
require_once $__dir . '/HTMLPurifier/DoctypeRegistry.php';
require_once $__dir . '/HTMLPurifier/ElementDef.php';
require_once $__dir . '/HTMLPurifier/Encoder.php';
require_once $__dir . '/HTMLPurifier/EntityLookup.php';
require_once $__dir . '/HTMLPurifier/EntityParser.php';
require_once $__dir . '/HTMLPurifier/ErrorCollector.php';
require_once $__dir . '/HTMLPurifier/ErrorStruct.php';
require_once $__dir . '/HTMLPurifier/Exception.php';
require_once $__dir . '/HTMLPurifier/Filter.php';
require_once $__dir . '/HTMLPurifier/Generator.php';
require_once $__dir . '/HTMLPurifier/HTMLDefinition.php';
require_once $__dir . '/HTMLPurifier/HTMLModule.php';
require_once $__dir . '/HTMLPurifier/HTMLModuleManager.php';
require_once $__dir . '/HTMLPurifier/IDAccumulator.php';
require_once $__dir . '/HTMLPurifier/Injector.php';
require_once $__dir . '/HTMLPurifier/Language.php';
require_once $__dir . '/HTMLPurifier/LanguageFactory.php';
require_once $__dir . '/HTMLPurifier/Length.php';
require_once $__dir . '/HTMLPurifier/Lexer.php';
require_once $__dir . '/HTMLPurifier/PercentEncoder.php';
require_once $__dir . '/HTMLPurifier/Strategy.php';
require_once $__dir . '/HTMLPurifier/StringHash.php';
require_once $__dir . '/HTMLPurifier/StringHashParser.php';
require_once $__dir . '/HTMLPurifier/TagTransform.php';
require_once $__dir . '/HTMLPurifier/Token.php';
require_once $__dir . '/HTMLPurifier/TokenFactory.php';
require_once $__dir . '/HTMLPurifier/URI.php';
require_once $__dir . '/HTMLPurifier/URIDefinition.php';
require_once $__dir . '/HTMLPurifier/URIFilter.php';
require_once $__dir . '/HTMLPurifier/URIParser.php';
require_once $__dir . '/HTMLPurifier/URIScheme.php';
require_once $__dir . '/HTMLPurifier/URISchemeRegistry.php';
require_once $__dir . '/HTMLPurifier/UnitConverter.php';
require_once $__dir . '/HTMLPurifier/VarParser.php';
require_once $__dir . '/HTMLPurifier/VarParserException.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Enum.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Integer.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Lang.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Switch.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Text.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Number.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/AlphaValue.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Background.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Border.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Color.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Composite.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Filter.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Font.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/FontFamily.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Length.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ListStyle.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Multiple.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Percentage.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/TextDecoration.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/URI.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Bool.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Color.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/FrameTarget.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/ID.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Pixels.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Length.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/LinkTypes.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/MultiLength.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Nmtokens.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI/Email.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI/Host.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI/IPv4.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI/IPv6.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Background.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/BdoDir.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/BgColor.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/BoolToCSS.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Border.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/EnumToCSS.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/ImgRequired.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/ImgSpace.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Input.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Lang.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeEmbed.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Textarea.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Empty.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Required.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Optional.php';
require_once $__dir . '/HTMLPurifier/ChildDef/StrictBlockquote.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Table.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Null.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Serializer.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator/Memory.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Bdo.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/CommonAttributes.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Edit.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Forms.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Hypertext.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Image.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Legacy.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/List.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Name.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Object.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Proprietary.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Ruby.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeEmbed.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeObject.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Scripting.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/StyleAttribute.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Target.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Text.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Name.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Proprietary.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Strict.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Transitional.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/XHTML.php';
require_once $__dir . '/HTMLPurifier/Injector/AutoParagraph.php';
require_once $__dir . '/HTMLPurifier/Injector/DisplayLinkURI.php';
require_once $__dir . '/HTMLPurifier/Injector/Linkify.php';
require_once $__dir . '/HTMLPurifier/Injector/PurifierLinkify.php';
require_once $__dir . '/HTMLPurifier/Injector/RemoveEmpty.php';
require_once $__dir . '/HTMLPurifier/Injector/SafeObject.php';
require_once $__dir . '/HTMLPurifier/Lexer/DOMLex.php';
require_once $__dir . '/HTMLPurifier/Lexer/DirectLex.php';
require_once $__dir . '/HTMLPurifier/Strategy/Composite.php';
require_once $__dir . '/HTMLPurifier/Strategy/Core.php';
require_once $__dir . '/HTMLPurifier/Strategy/FixNesting.php';
require_once $__dir . '/HTMLPurifier/Strategy/MakeWellFormed.php';
require_once $__dir . '/HTMLPurifier/Strategy/RemoveForeignElements.php';
require_once $__dir . '/HTMLPurifier/Strategy/ValidateAttributes.php';
require_once $__dir . '/HTMLPurifier/TagTransform/Font.php';
require_once $__dir . '/HTMLPurifier/TagTransform/Simple.php';
require_once $__dir . '/HTMLPurifier/Token/Comment.php';
require_once $__dir . '/HTMLPurifier/Token/Tag.php';
require_once $__dir . '/HTMLPurifier/Token/Empty.php';
require_once $__dir . '/HTMLPurifier/Token/End.php';
require_once $__dir . '/HTMLPurifier/Token/Start.php';
require_once $__dir . '/HTMLPurifier/Token/Text.php';
require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternal.php';
require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternalResources.php';
require_once $__dir . '/HTMLPurifier/URIFilter/HostBlacklist.php';
require_once $__dir . '/HTMLPurifier/URIFilter/MakeAbsolute.php';
require_once $__dir . '/HTMLPurifier/URIFilter/Munge.php';
require_once $__dir . '/HTMLPurifier/URIScheme/ftp.php';
require_once $__dir . '/HTMLPurifier/URIScheme/http.php';
require_once $__dir . '/HTMLPurifier/URIScheme/https.php';
require_once $__dir . '/HTMLPurifier/URIScheme/mailto.php';
require_once $__dir . '/HTMLPurifier/URIScheme/news.php';
require_once $__dir . '/HTMLPurifier/URIScheme/nntp.php';
require_once $__dir . '/HTMLPurifier/VarParser/Flexible.php';
require_once $__dir . '/HTMLPurifier/VarParser/Native.php';

View file

@ -0,0 +1,127 @@
<?php
/**
* Defines common attribute collections that modules reference
*/
class HTMLPurifier_AttrCollections
{
/**
* Associative array of attribute collections, indexed by name
*/
public $info = array();
/**
* Performs all expansions on internal data for use by other inclusions
* It also collects all attribute collection extensions from
* modules
* @param $attr_types HTMLPurifier_AttrTypes instance
* @param $modules Hash array of HTMLPurifier_HTMLModule members
*/
public function __construct($attr_types, $modules) {
// load extensions from the modules
foreach ($modules as $module) {
foreach ($module->attr_collections as $coll_i => $coll) {
if (!isset($this->info[$coll_i])) {
$this->info[$coll_i] = array();
}
foreach ($coll as $attr_i => $attr) {
if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
// merge in includes
$this->info[$coll_i][$attr_i] = array_merge(
$this->info[$coll_i][$attr_i], $attr);
continue;
}
$this->info[$coll_i][$attr_i] = $attr;
}
}
}
// perform internal expansions and inclusions
foreach ($this->info as $name => $attr) {
// merge attribute collections that include others
$this->performInclusions($this->info[$name]);
// replace string identifiers with actual attribute objects
$this->expandIdentifiers($this->info[$name], $attr_types);
}
}
/**
* Takes a reference to an attribute associative array and performs
* all inclusions specified by the zero index.
* @param &$attr Reference to attribute array
*/
public function performInclusions(&$attr) {
if (!isset($attr[0])) return;
$merge = $attr[0];
$seen = array(); // recursion guard
// loop through all the inclusions
for ($i = 0; isset($merge[$i]); $i++) {
if (isset($seen[$merge[$i]])) continue;
$seen[$merge[$i]] = true;
// foreach attribute of the inclusion, copy it over
if (!isset($this->info[$merge[$i]])) continue;
foreach ($this->info[$merge[$i]] as $key => $value) {
if (isset($attr[$key])) continue; // also catches more inclusions
$attr[$key] = $value;
}
if (isset($this->info[$merge[$i]][0])) {
// recursion
$merge = array_merge($merge, $this->info[$merge[$i]][0]);
}
}
unset($attr[0]);
}
/**
* Expands all string identifiers in an attribute array by replacing
* them with the appropriate values inside HTMLPurifier_AttrTypes
* @param &$attr Reference to attribute array
* @param $attr_types HTMLPurifier_AttrTypes instance
*/
public function expandIdentifiers(&$attr, $attr_types) {
// because foreach will process new elements we add, make sure we
// skip duplicates
$processed = array();
foreach ($attr as $def_i => $def) {
// skip inclusions
if ($def_i === 0) continue;
if (isset($processed[$def_i])) continue;
// determine whether or not attribute is required
if ($required = (strpos($def_i, '*') !== false)) {
// rename the definition
unset($attr[$def_i]);
$def_i = trim($def_i, '*');
$attr[$def_i] = $def;
}
$processed[$def_i] = true;
// if we've already got a literal object, move on
if (is_object($def)) {
// preserve previous required
$attr[$def_i]->required = ($required || $attr[$def_i]->required);
continue;
}
if ($def === false) {
unset($attr[$def_i]);
continue;
}
if ($t = $attr_types->get($def)) {
$attr[$def_i] = $t;
$attr[$def_i]->required = $required;
} else {
unset($attr[$def_i]);
}
}
}
}

View file

@ -0,0 +1,86 @@
<?php
/**
* Base class for all validating attribute definitions.
*
* This family of classes forms the core for not only HTML attribute validation,
* but also any sort of string that needs to be validated or cleaned (which
* means CSS properties and composite definitions are defined here too).
* Besides defining (through code) what precisely makes the string valid,
* subclasses are also responsible for cleaning the code if possible.
*/
abstract class HTMLPurifier_AttrDef
{
/**
* Tells us whether or not an HTML attribute is minimized. Has no
* meaning in other contexts.
*/
public $minimized = false;
/**
* Tells us whether or not an HTML attribute is required. Has no
* meaning in other contexts
*/
public $required = false;
/**
* Validates and cleans passed string according to a definition.
*
* @param $string String to be validated and cleaned.
* @param $config Mandatory HTMLPurifier_Config object.
* @param $context Mandatory HTMLPurifier_AttrContext object.
*/
abstract public function validate($string, $config, $context);
/**
* Convenience method that parses a string as if it were CDATA.
*
* This method process a string in the manner specified at
* <http://www.w3.org/TR/html4/types.html#h-6.2> 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 construction info
* @return 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!
*/
protected function mungeRgb($string) {
return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string);
}
}

View file

@ -0,0 +1,86 @@
<?php
/**
* Validates the HTML attribute style, otherwise known as CSS.
* @note We don't implement the whole CSS specification, so it might be
* difficult to reuse this component in the context of validating
* actual stylesheet declarations.
* @note If we were really serious about validating the CSS, we would
* tokenize the styles and then parse the tokens. Obviously, we
* are not doing that. Doing that could seriously harm performance,
* but would make these components a lot more viable for a CSS
* filtering solution.
*/
class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
{
public function validate($css, $config, $context) {
$css = $this->parseCDATA($css);
$definition = $config->getCSSDefinition();
// we're going to break the spec and explode by semicolons.
// This is because semicolon rarely appears in escaped form
// Doing this is generally flaky but fast
// IT MIGHT APPEAR IN URIs, see HTMLPurifier_AttrDef_CSSURI
// for details
$declarations = explode(';', $css);
$propvalues = array();
/**
* Name of the current CSS property being validated.
*/
$property = false;
$context->register('CurrentCSSProperty', $property);
foreach ($declarations as $declaration) {
if (!$declaration) continue;
if (!strpos($declaration, ':')) continue;
list($property, $value) = explode(':', $declaration, 2);
$property = trim($property);
$value = trim($value);
$ok = false;
do {
if (isset($definition->info[$property])) {
$ok = true;
break;
}
if (ctype_lower($property)) break;
$property = strtolower($property);
if (isset($definition->info[$property])) {
$ok = true;
break;
}
} while(0);
if (!$ok) continue;
// inefficient call, since the validator will do this again
if (strtolower(trim($value)) !== 'inherit') {
// inherit works for everything (but only on the base property)
$result = $definition->info[$property]->validate(
$value, $config, $context );
} else {
$result = 'inherit';
}
if ($result === false) continue;
$propvalues[$property] = $result;
}
$context->destroy('CurrentCSSProperty');
// procedure does not write the new CSS simultaneously, so it's
// slightly inefficient, but it's the only way of getting rid of
// duplicates. Perhaps config to optimize it, but not now.
$new_declarations = '';
foreach ($propvalues as $prop => $value) {
$new_declarations .= "$prop:$value;";
}
return $new_declarations ? $new_declarations : false;
}
}

View file

@ -0,0 +1,19 @@
<?php
class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
{
public function __construct() {
parent::__construct(false); // opacity is non-negative, but we will clamp it
}
public function validate($number, $config, $context) {
$result = parent::validate($number, $config, $context);
if ($result === false) return $result;
$float = (float) $result;
if ($float < 0.0) $result = '0';
if ($float > 1.0) $result = '1';
return $result;
}
}

View file

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

View file

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

View file

@ -0,0 +1,42 @@
<?php
/**
* Validates the border property as defined by CSS.
*/
class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
{
/**
* Local copy of properties this property is shorthand for.
*/
protected $info = array();
public function __construct($config) {
$def = $config->getCSSDefinition();
$this->info['border-width'] = $def->info['border-width'];
$this->info['border-style'] = $def->info['border-style'];
$this->info['border-top-color'] = $def->info['border-top-color'];
}
public function validate($string, $config, $context) {
$string = $this->parseCDATA($string);
$string = $this->mungeRgb($string);
$bits = explode(' ', $string);
$done = array(); // segments we've finished
$ret = ''; // return value
foreach ($bits as $bit) {
foreach ($this->info as $propname => $validator) {
if (isset($done[$propname])) continue;
$r = $validator->validate($bit, $config, $context);
if ($r !== false) {
$ret .= $r . ' ';
$done[$propname] = true;
break;
}
}
}
return rtrim($ret);
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* Validates Color as defined by CSS.
*/
class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
{
public function validate($color, $config, $context) {
static $colors = null;
if ($colors === null) $colors = $config->get('Core', 'ColorKeywords');
$color = trim($color);
if ($color === '') return false;
$lower = strtolower($color);
if (isset($colors[$lower])) return $colors[$lower];
if (strpos($color, 'rgb(') !== false) {
// rgb literal handling
$length = strlen($color);
if (strpos($color, ')') !== $length - 1) return false;
$triad = substr($color, 4, $length - 4 - 1);
$parts = explode(',', $triad);
if (count($parts) !== 3) return false;
$type = false; // to ensure that they're all the same type
$new_parts = array();
foreach ($parts as $part) {
$part = trim($part);
if ($part === '') return false;
$length = strlen($part);
if ($part[$length - 1] === '%') {
// handle percents
if (!$type) {
$type = 'percentage';
} elseif ($type !== 'percentage') {
return false;
}
$num = (float) substr($part, 0, $length - 1);
if ($num < 0) $num = 0;
if ($num > 100) $num = 100;
$new_parts[] = "$num%";
} else {
// handle integers
if (!$type) {
$type = 'integer';
} elseif ($type !== 'integer') {
return false;
}
$num = (int) $part;
if ($num < 0) $num = 0;
if ($num > 255) $num = 255;
$new_parts[] = (string) $num;
}
}
$new_triad = implode(',', $new_parts);
$color = "rgb($new_triad)";
} else {
// hexadecimal handling
if ($color[0] === '#') {
$hex = substr($color, 1);
} else {
$hex = $color;
$color = '#' . $color;
}
$length = strlen($hex);
if ($length !== 3 && $length !== 6) return false;
if (!ctype_xdigit($hex)) return false;
}
return $color;
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* Allows multiple validators to attempt to validate attribute.
*
* Composite is just what it sounds like: a composite of many validators.
* This means that multiple HTMLPurifier_AttrDef objects will have a whack
* at the string. If one of them passes, that's what is returned. This is
* especially useful for CSS values, which often are a choice between
* an enumerated set of predefined values or a flexible data type.
*/
class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef
{
/**
* List of HTMLPurifier_AttrDef objects that may process strings
* @todo Make protected
*/
public $defs;
/**
* @param $defs List of HTMLPurifier_AttrDef objects
*/
public function __construct($defs) {
$this->defs = $defs;
}
public function validate($string, $config, $context) {
foreach ($this->defs as $i => $def) {
$result = $this->defs[$i]->validate($string, $config, $context);
if ($result !== false) return $result;
}
return false;
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* Decorator which enables CSS properties to be disabled for specific elements.
*/
class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
{
protected $def, $element;
/**
* @param $def Definition to wrap
* @param $element Element to deny
*/
public function __construct($def, $element) {
$this->def = $def;
$this->element = $element;
}
/**
* Checks if CurrentToken is set and equal to $this->element
*/
public function validate($string, $config, $context) {
$token = $context->get('CurrentToken', true);
if ($token && $token->name == $this->element) return false;
return $this->def->validate($string, $config, $context);
}
}

View file

@ -0,0 +1,52 @@
<?php
/**
* Microsoft's proprietary filter: CSS property
* @note Currently supports the alpha filter. In the future, this will
* probably need an extensible framework
*/
class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
{
protected $intValidator;
public function __construct() {
$this->intValidator = new HTMLPurifier_AttrDef_Integer();
}
public function validate($value, $config, $context) {
$value = $this->parseCDATA($value);
if ($value === 'none') return $value;
// if we looped this we could support multiple filters
$function_length = strcspn($value, '(');
$function = trim(substr($value, 0, $function_length));
if ($function !== 'alpha' &&
$function !== 'Alpha' &&
$function !== 'progid:DXImageTransform.Microsoft.Alpha'
) return false;
$cursor = $function_length + 1;
$parameters_length = strcspn($value, ')', $cursor);
$parameters = substr($value, $cursor, $parameters_length);
$params = explode(',', $parameters);
$ret_params = array();
$lookup = array();
foreach ($params as $param) {
list($key, $value) = explode('=', $param);
$key = trim($key);
$value = trim($value);
if (isset($lookup[$key])) continue;
if ($key !== 'opacity') continue;
$value = $this->intValidator->validate($value, $config, $context);
if ($value === false) continue;
$int = (int) $value;
if ($int > 100) $value = '100';
if ($int < 0) $value = '0';
$ret_params[] = "$key=$value";
$lookup[$key] = true;
}
$ret_parameters = implode(',', $ret_params);
$ret_function = "$function($ret_parameters)";
return $ret_function;
}
}

View file

@ -0,0 +1,148 @@
<?php
/**
* Validates shorthand CSS property font.
*/
class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
{
/**
* Local copy of component validators.
*
* @note If we moved specific CSS property definitions to their own
* classes instead of having them be assembled at run time by
* CSSDefinition, this wouldn't be necessary. We'd instantiate
* our own copies.
*/
protected $info = array();
public function __construct($config) {
$def = $config->getCSSDefinition();
$this->info['font-style'] = $def->info['font-style'];
$this->info['font-variant'] = $def->info['font-variant'];
$this->info['font-weight'] = $def->info['font-weight'];
$this->info['font-size'] = $def->info['font-size'];
$this->info['line-height'] = $def->info['line-height'];
$this->info['font-family'] = $def->info['font-family'];
}
public function validate($string, $config, $context) {
static $system_fonts = array(
'caption' => true,
'icon' => true,
'menu' => true,
'message-box' => true,
'small-caption' => true,
'status-bar' => true
);
// regular pre-processing
$string = $this->parseCDATA($string);
if ($string === '') return false;
// check if it's one of the keywords
$lowercase_string = strtolower($string);
if (isset($system_fonts[$lowercase_string])) {
return $lowercase_string;
}
$bits = explode(' ', $string); // bits to process
$stage = 0; // this indicates what we're looking for
$caught = array(); // which stage 0 properties have we caught?
$stage_1 = array('font-style', 'font-variant', 'font-weight');
$final = ''; // output
for ($i = 0, $size = count($bits); $i < $size; $i++) {
if ($bits[$i] === '') continue;
switch ($stage) {
// attempting to catch font-style, font-variant or font-weight
case 0:
foreach ($stage_1 as $validator_name) {
if (isset($caught[$validator_name])) continue;
$r = $this->info[$validator_name]->validate(
$bits[$i], $config, $context);
if ($r !== false) {
$final .= $r . ' ';
$caught[$validator_name] = true;
break;
}
}
// all three caught, continue on
if (count($caught) >= 3) $stage = 1;
if ($r !== false) break;
// attempting to catch font-size and perhaps line-height
case 1:
$found_slash = false;
if (strpos($bits[$i], '/') !== false) {
list($font_size, $line_height) =
explode('/', $bits[$i]);
if ($line_height === '') {
// ooh, there's a space after the slash!
$line_height = false;
$found_slash = true;
}
} else {
$font_size = $bits[$i];
$line_height = false;
}
$r = $this->info['font-size']->validate(
$font_size, $config, $context);
if ($r !== false) {
$final .= $r;
// attempt to catch line-height
if ($line_height === false) {
// we need to scroll forward
for ($j = $i + 1; $j < $size; $j++) {
if ($bits[$j] === '') continue;
if ($bits[$j] === '/') {
if ($found_slash) {
return false;
} else {
$found_slash = true;
continue;
}
}
$line_height = $bits[$j];
break;
}
} else {
// slash already found
$found_slash = true;
$j = $i;
}
if ($found_slash) {
$i = $j;
$r = $this->info['line-height']->validate(
$line_height, $config, $context);
if ($r !== false) {
$final .= '/' . $r;
}
}
$final .= ' ';
$stage = 2;
break;
}
return false;
// attempting to catch font-family
case 2:
$font_family =
implode(' ', array_slice($bits, $i, $size - $i));
$r = $this->info['font-family']->validate(
$font_family, $config, $context);
if ($r !== false) {
$final .= $r . ' ';
// processing completed successfully
return rtrim($final);
}
return false;
}
}
return false;
}
}

View file

@ -0,0 +1,89 @@
<?php
/**
* Validates a font family list according to CSS spec
* @todo whitelisting allowed fonts would be nice
*/
class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
{
public function validate($string, $config, $context) {
static $generic_names = array(
'serif' => true,
'sans-serif' => true,
'monospace' => true,
'fantasy' => true,
'cursive' => true
);
// assume that no font names contain commas in them
$fonts = explode(',', $string);
$final = '';
foreach($fonts as $font) {
$font = trim($font);
if ($font === '') continue;
// match a generic name
if (isset($generic_names[$font])) {
$final .= $font . ', ';
continue;
}
// match a quoted name
if ($font[0] === '"' || $font[0] === "'") {
$length = strlen($font);
if ($length <= 2) continue;
$quote = $font[0];
if ($font[$length - 1] !== $quote) continue;
$font = substr($font, 1, $length - 2);
$new_font = '';
for ($i = 0, $c = strlen($font); $i < $c; $i++) {
if ($font[$i] === '\\') {
$i++;
if ($i >= $c) {
$new_font .= '\\';
break;
}
if (ctype_xdigit($font[$i])) {
$code = $font[$i];
for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
if (!ctype_xdigit($font[$i])) break;
$code .= $font[$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;
$new_font .= $char;
if ($i < $c && trim($font[$i]) !== '') $i--;
continue;
}
if ($font[$i] === "\n") continue;
}
$new_font .= $font[$i];
}
$font = $new_font;
}
// $font is a pure representation of the font name
if (ctype_alnum($font) && $font !== '') {
// very simple font, allow it in unharmed
$final .= $font . ', ';
continue;
}
// complicated font, requires quoting
// armor single quotes and new lines
$font = str_replace("\\", "\\\\", $font);
$font = str_replace("'", "\\'", $font);
$final .= "'$font', ";
}
$final = rtrim($final, ', ');
if ($final === '') return false;
return $final;
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* Decorator which enables !important to be used in CSS values.
*/
class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
{
protected $def, $allow;
/**
* @param $def Definition to wrap
* @param $allow Whether or not to allow !important
*/
public function __construct($def, $allow = false) {
$this->def = $def;
$this->allow = $allow;
}
/**
* Intercepts and removes !important if necessary
*/
public function validate($string, $config, $context) {
// test for ! and important tokens
$string = trim($string);
$is_important = false;
// :TODO: optimization: test directly for !important and ! important
if (strlen($string) >= 9 && substr($string, -9) === 'important') {
$temp = rtrim(substr($string, 0, -9));
// use a temp, because we might want to restore important
if (strlen($temp) >= 1 && substr($temp, -1) === '!') {
$string = rtrim(substr($temp, 0, -1));
$is_important = true;
}
}
$string = $this->def->validate($string, $config, $context);
if ($this->allow && $is_important) $string .= ' !important';
return $string;
}
}

View file

@ -0,0 +1,46 @@
<?php
/**
* Represents a Length as defined by CSS.
*/
class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
{
protected $min, $max;
/**
* @param HTMLPurifier_Length $max Minimum length, or null for no bound. String is also acceptable.
* @param HTMLPurifier_Length $max Maximum length, or null for no bound. String is also acceptable.
*/
public function __construct($min = null, $max = null) {
$this->min = $min !== null ? HTMLPurifier_Length::make($min) : null;
$this->max = $max !== null ? HTMLPurifier_Length::make($max) : null;
}
public function validate($string, $config, $context) {
$string = $this->parseCDATA($string);
// Optimizations
if ($string === '') return false;
if ($string === '0') return '0';
if (strlen($string) === 1) return false;
$length = HTMLPurifier_Length::make($string);
if (!$length->isValid()) return false;
if ($this->min) {
$c = $length->compareTo($this->min);
if ($c === false) return false;
if ($c < 0) return false;
}
if ($this->max) {
$c = $length->compareTo($this->max);
if ($c === false) return false;
if ($c > 0) return false;
}
return $length->toString();
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* Validates shorthand CSS property list-style.
* @warning Does not support url tokens that have internal spaces.
*/
class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
{
/**
* Local copy of component validators.
* @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl.
*/
protected $info;
public function __construct($config) {
$def = $config->getCSSDefinition();
$this->info['list-style-type'] = $def->info['list-style-type'];
$this->info['list-style-position'] = $def->info['list-style-position'];
$this->info['list-style-image'] = $def->info['list-style-image'];
}
public function validate($string, $config, $context) {
// regular pre-processing
$string = $this->parseCDATA($string);
if ($string === '') return false;
// assumes URI doesn't have spaces in it
$bits = explode(' ', strtolower($string)); // bits to process
$caught = array();
$caught['type'] = false;
$caught['position'] = false;
$caught['image'] = false;
$i = 0; // number of catches
$none = false;
foreach ($bits as $bit) {
if ($i >= 3) return; // optimization bit
if ($bit === '') continue;
foreach ($caught as $key => $status) {
if ($status !== false) continue;
$r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
if ($r === false) continue;
if ($r === 'none') {
if ($none) continue;
else $none = true;
if ($key == 'image') continue;
}
$caught[$key] = $r;
$i++;
break;
}
}
if (!$i) return false;
$ret = array();
// construct type
if ($caught['type']) $ret[] = $caught['type'];
// construct image
if ($caught['image']) $ret[] = $caught['image'];
// construct position
if ($caught['position']) $ret[] = $caught['position'];
if (empty($ret)) return false;
return implode(' ', $ret);
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* Framework class for strings that involve multiple values.
*
* Certain CSS properties such as border-width and margin allow multiple
* lengths to be specified. This class can take a vanilla border-width
* definition and multiply it, usually into a max of four.
*
* @note Even though the CSS specification isn't clear about it, inherit
* can only be used alone: it will never manifest as part of a multi
* shorthand declaration. Thus, this class does not allow inherit.
*/
class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
{
/**
* Instance of component definition to defer validation to.
* @todo Make protected
*/
public $single;
/**
* Max number of values allowed.
* @todo Make protected
*/
public $max;
/**
* @param $single HTMLPurifier_AttrDef to multiply
* @param $max Max number of values allowed (usually four)
*/
public function __construct($single, $max = 4) {
$this->single = $single;
$this->max = $max;
}
public function validate($string, $config, $context) {
$string = $this->parseCDATA($string);
if ($string === '') return false;
$parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n
$length = count($parts);
$final = '';
for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) {
if (ctype_space($parts[$i])) continue;
$result = $this->single->validate($parts[$i], $config, $context);
if ($result !== false) {
$final .= $result . ' ';
$num++;
}
}
if ($final === '') return false;
return rtrim($final);
}
}

View file

@ -0,0 +1,68 @@
<?php
/**
* Validates a number as defined by the CSS spec.
*/
class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
{
/**
* Bool indicating whether or not only positive values allowed.
*/
protected $non_negative = false;
/**
* @param $non_negative Bool indicating whether negatives are forbidden
*/
public function __construct($non_negative = false) {
$this->non_negative = $non_negative;
}
/**
* @warning Some contexts do not pass $config, $context. These
* variables should not be used without checking HTMLPurifier_Length
*/
public function validate($number, $config, $context) {
$number = $this->parseCDATA($number);
if ($number === '') return false;
if ($number === '0') return '0';
$sign = '';
switch ($number[0]) {
case '-':
if ($this->non_negative) return false;
$sign = '-';
case '+':
$number = substr($number, 1);
}
if (ctype_digit($number)) {
$number = ltrim($number, '0');
return $number ? $sign . $number : '0';
}
// Period is the only non-numeric character allowed
if (strpos($number, '.') === false) return false;
list($left, $right) = explode('.', $number, 2);
if ($left === '' && $right === '') return false;
if ($left !== '' && !ctype_digit($left)) return false;
$left = ltrim($left, '0');
$right = rtrim($right, '0');
if ($right === '') {
return $left ? $sign . $left : '0';
} elseif (!ctype_digit($right)) {
return false;
}
return $sign . $left . '.' . $right;
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* Validates a Percentage as defined by the CSS spec.
*/
class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
{
/**
* Instance of HTMLPurifier_AttrDef_CSS_Number to defer number validation
*/
protected $number_def;
/**
* @param Bool indicating whether to forbid negative values
*/
public function __construct($non_negative = false) {
$this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
}
public function validate($string, $config, $context) {
$string = $this->parseCDATA($string);
if ($string === '') return false;
$length = strlen($string);
if ($length === 1) return false;
if ($string[$length - 1] !== '%') return false;
$number = substr($string, 0, $length - 1);
$number = $this->number_def->validate($number, $config, $context);
if ($number === false) return false;
return "$number%";
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* Validates the value for the CSS property text-decoration
* @note This class could be generalized into a version that acts sort of
* like Enum except you can compound the allowed values.
*/
class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
{
public function validate($string, $config, $context) {
static $allowed_values = array(
'line-through' => true,
'overline' => true,
'underline' => true,
);
$string = strtolower($this->parseCDATA($string));
if ($string === 'none') return $string;
$parts = explode(' ', $string);
$final = '';
foreach ($parts as $part) {
if (isset($allowed_values[$part])) {
$final .= $part . ' ';
}
}
$final = rtrim($final);
if ($final === '') return false;
return $final;
}
}

View file

@ -0,0 +1,55 @@
<?php
/**
* Validates a URI in CSS syntax, which uses url('http://example.com')
* @note While theoretically speaking a URI in a CSS document could
* be non-embedded, as of CSS2 there is no such usage so we're
* generalizing it. This may need to be changed in the future.
* @warning Since HTMLPurifier_AttrDef_CSS blindly uses semicolons as
* the separator, you cannot put a literal semicolon in
* in the URI. Try percent encoding it, in that case.
*/
class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
{
public function __construct() {
parent::__construct(true); // always embedded
}
public function validate($uri_string, $config, $context) {
// parse the URI out of the string and then pass it onto
// the parent object
$uri_string = $this->parseCDATA($uri_string);
if (strpos($uri_string, 'url(') !== 0) return false;
$uri_string = substr($uri_string, 4);
$new_length = strlen($uri_string) - 1;
if ($uri_string[$new_length] != ')') return false;
$uri = trim(substr($uri_string, 0, $new_length));
if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) {
$quote = $uri[0];
$new_length = strlen($uri) - 1;
if ($uri[$new_length] !== $quote) return false;
$uri = substr($uri, 1, $new_length - 1);
}
$keys = array( '(', ')', ',', ' ', '"', "'");
$values = array('\\(', '\\)', '\\,', '\\ ', '\\"', "\\'");
$uri = str_replace($values, $keys, $uri);
$result = parent::validate($uri, $config, $context);
if ($result === false) return false;
// escape necessary characters according to CSS spec
// except for the comma, none of these should appear in the
// URI at all
$result = str_replace($keys, $values, $result);
return "url($result)";
}
}

View file

@ -0,0 +1,64 @@
<?php
// Enum = Enumerated
/**
* Validates a keyword against a list of valid values.
* @warning The case-insensitive compare of this function uses PHP's
* built-in strtolower and ctype_lower functions, which may
* cause problems with international comparisons
*/
class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
{
/**
* Lookup table of valid values.
* @todo Make protected
*/
public $valid_values = array();
/**
* Bool indicating whether or not enumeration is case sensitive.
* @note In general this is always case insensitive.
*/
protected $case_sensitive = false; // values according to W3C spec
/**
* @param $valid_values List of valid values
* @param $case_sensitive Bool indicating whether or not case sensitive
*/
public function __construct(
$valid_values = array(), $case_sensitive = false
) {
$this->valid_values = array_flip($valid_values);
$this->case_sensitive = $case_sensitive;
}
public function validate($string, $config, $context) {
$string = trim($string);
if (!$this->case_sensitive) {
// we may want to do full case-insensitive libraries
$string = ctype_lower($string) ? $string : strtolower($string);
}
$result = isset($this->valid_values[$string]);
return $result ? $string : false;
}
/**
* @param $string In form of comma-delimited list of case-insensitive
* valid values. Example: "foo,bar,baz". Prepend "s:" to make
* case sensitive
*/
public function make($string) {
if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') {
$string = substr($string, 2);
$sensitive = true;
} else {
$sensitive = false;
}
$values = explode(',', $string);
return new HTMLPurifier_AttrDef_Enum($values, $sensitive);
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* Validates a boolean attribute
*/
class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
{
protected $name;
public $minimized = true;
public function __construct($name = false) {$this->name = $name;}
public function validate($string, $config, $context) {
if (empty($string)) return false;
return $this->name;
}
/**
* @param $string Name of attribute
*/
public function make($string) {
return new HTMLPurifier_AttrDef_HTML_Bool($string);
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
* Validates a color according to the HTML spec.
*/
class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
{
public function validate($string, $config, $context) {
static $colors = null;
if ($colors === null) $colors = $config->get('Core', 'ColorKeywords');
$string = trim($string);
if (empty($string)) return false;
if (isset($colors[$string])) return $colors[$string];
if ($string[0] === '#') $hex = substr($string, 1);
else $hex = $string;
$length = strlen($hex);
if ($length !== 3 && $length !== 6) return false;
if (!ctype_xdigit($hex)) return false;
if ($length === 3) $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
return "#$hex";
}
}

View file

@ -0,0 +1,20 @@
<?php
/**
* Special-case enum attribute definition that lazy loads allowed frame targets
*/
class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
{
public $valid_values = false; // uninitialized value
protected $case_sensitive = false;
public function __construct() {}
public function validate($string, $config, $context) {
if ($this->valid_values === false) $this->valid_values = $config->get('Attr', 'AllowedFrameTargets');
return parent::validate($string, $config, $context);
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* Validates the HTML attribute ID.
* @warning Even though this is the id processor, it
* will ignore the directive Attr:IDBlacklist, since it will only
* go according to the ID accumulator. Since the accumulator is
* automatically generated, it will have already absorbed the
* blacklist. If you're hacking around, make sure you use load()!
*/
class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
{
// ref functionality disabled, since we also have to verify
// whether or not the ID it refers to exists
public function validate($id, $config, $context) {
if (!$config->get('Attr', 'EnableID')) return false;
$id = trim($id); // trim it first
if ($id === '') return false;
$prefix = $config->get('Attr', 'IDPrefix');
if ($prefix !== '') {
$prefix .= $config->get('Attr', 'IDPrefixLocal');
// prevent re-appending the prefix
if (strpos($id, $prefix) !== 0) $id = $prefix . $id;
} elseif ($config->get('Attr', 'IDPrefixLocal') !== '') {
trigger_error('%Attr.IDPrefixLocal cannot be used unless '.
'%Attr.IDPrefix is set', E_USER_WARNING);
}
//if (!$this->ref) {
$id_accumulator =& $context->get('IDAccumulator');
if (isset($id_accumulator->ids[$id])) return false;
//}
// we purposely avoid using regex, hopefully this is faster
if (ctype_alpha($id)) {
$result = true;
} else {
if (!ctype_alpha(@$id[0])) return false;
$trim = trim( // primitive style of regexps, I suppose
$id,
'A..Za..z0..9:-._'
);
$result = ($trim === '');
}
$regexp = $config->get('Attr', 'IDBlacklistRegexp');
if ($regexp && preg_match($regexp, $id)) {
return false;
}
if (/*!$this->ref && */$result) $id_accumulator->add($id);
// if no change was made to the ID, return the result
// else, return the new id if stripping whitespace made it
// valid, or return false.
return $result ? $id : false;
}
}

View file

@ -0,0 +1,40 @@
<?php
/**
* Validates the HTML type length (not to be confused with CSS's length).
*
* This accepts integer pixels or percentages as lengths for certain
* HTML attributes.
*/
class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels
{
public function validate($string, $config, $context) {
$string = trim($string);
if ($string === '') return false;
$parent_result = parent::validate($string, $config, $context);
if ($parent_result !== false) return $parent_result;
$length = strlen($string);
$last_char = $string[$length - 1];
if ($last_char !== '%') return false;
$points = substr($string, 0, $length - 1);
if (!is_numeric($points)) return false;
$points = (int) $points;
if ($points < 0) return '0%';
if ($points > 100) return '100%';
return ((string) $points) . '%';
}
}

View file

@ -0,0 +1,52 @@
<?php
/**
* Validates a rel/rev link attribute against a directive of allowed values
* @note We cannot use Enum because link types allow multiple
* values.
* @note Assumes link types are ASCII text
*/
class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef
{
/** Name config attribute to pull. */
protected $name;
public function __construct($name) {
$configLookup = array(
'rel' => 'AllowedRel',
'rev' => 'AllowedRev'
);
if (!isset($configLookup[$name])) {
trigger_error('Unrecognized attribute name for link '.
'relationship.', E_USER_ERROR);
return;
}
$this->name = $configLookup[$name];
}
public function validate($string, $config, $context) {
$allowed = $config->get('Attr', $this->name);
if (empty($allowed)) return false;
$string = $this->parseCDATA($string);
$parts = explode(' ', $string);
// lookup to prevent duplicates
$ret_lookup = array();
foreach ($parts as $part) {
$part = strtolower(trim($part));
if (!isset($allowed[$part])) continue;
$ret_lookup[$part] = true;
}
if (empty($ret_lookup)) return false;
$string = implode(' ', array_keys($ret_lookup));
return $string;
}
}

View file

@ -0,0 +1,40 @@
<?php
/**
* Validates a MultiLength as defined by the HTML spec.
*
* A multilength is either a integer (pixel count), a percentage, or
* a relative number.
*/
class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length
{
public function validate($string, $config, $context) {
$string = trim($string);
if ($string === '') return false;
$parent_result = parent::validate($string, $config, $context);
if ($parent_result !== false) return $parent_result;
$length = strlen($string);
$last_char = $string[$length - 1];
if ($last_char !== '*') return false;
$int = substr($string, 0, $length - 1);
if ($int == '') return '*';
if (!is_numeric($int)) return false;
$int = (int) $int;
if ($int < 0) return false;
if ($int == 0) return '0';
if ($int == 1) return '*';
return ((string) $int) . '*';
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
* Validates contents based on NMTOKENS attribute type.
* @note The only current use for this is the class attribute in HTML
* @note Could have some functionality factored out into Nmtoken class
* @warning We cannot assume this class will be used only for 'class'
* attributes. Not sure how to hook in magic behavior, then.
*/
class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
{
public function validate($string, $config, $context) {
$string = trim($string);
// early abort: '' and '0' (strings that convert to false) are invalid
if (!$string) return false;
// OPTIMIZABLE!
// do the preg_match, capture all subpatterns for reformulation
// we don't support U+00A1 and up codepoints or
// escaping because I don't know how to do that with regexps
// and plus it would complicate optimization efforts (you never
// see that anyway).
$matches = array();
$pattern = '/(?:(?<=\s)|\A)'. // look behind for space or string start
'((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)'.
'(?:(?=\s)|\z)/'; // look ahead for space or string end
preg_match_all($pattern, $string, $matches);
if (empty($matches[1])) return false;
// reconstruct string
$new_string = '';
foreach ($matches[1] as $token) {
$new_string .= $token . ' ';
}
$new_string = rtrim($new_string);
return $new_string;
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
* Validates an integer representation of pixels according to the HTML spec.
*/
class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
{
protected $max;
public function __construct($max = null) {
$this->max = $max;
}
public function validate($string, $config, $context) {
$string = trim($string);
if ($string === '0') return $string;
if ($string === '') return false;
$length = strlen($string);
if (substr($string, $length - 2) == 'px') {
$string = substr($string, 0, $length - 2);
}
if (!is_numeric($string)) return false;
$int = (int) $string;
if ($int < 0) return '0';
// upper-bound value, extremely high values can
// crash operating systems, see <http://ha.ckers.org/imagecrash.html>
// WARNING, above link WILL crash you if you're using Windows
if ($this->max !== null && $int > $this->max) return (string) $this->max;
return (string) $int;
}
public function make($string) {
if ($string === '') $max = null;
else $max = (int) $string;
$class = get_class($this);
return new $class($max);
}
}

View file

@ -0,0 +1,72 @@
<?php
/**
* Validates an integer.
* @note While this class was modeled off the CSS definition, no currently
* allowed CSS uses this type. The properties that do are: widows,
* orphans, z-index, counter-increment, counter-reset. Some of the
* HTML attributes, however, find use for a non-negative version of this.
*/
class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef
{
/**
* Bool indicating whether or not negative values are allowed
*/
protected $negative = true;
/**
* Bool indicating whether or not zero is allowed
*/
protected $zero = true;
/**
* Bool indicating whether or not positive values are allowed
*/
protected $positive = true;
/**
* @param $negative Bool indicating whether or not negative values are allowed
* @param $zero Bool indicating whether or not zero is allowed
* @param $positive Bool indicating whether or not positive values are allowed
*/
public function __construct(
$negative = true, $zero = true, $positive = true
) {
$this->negative = $negative;
$this->zero = $zero;
$this->positive = $positive;
}
public function validate($integer, $config, $context) {
$integer = $this->parseCDATA($integer);
if ($integer === '') return false;
// we could possibly simply typecast it to integer, but there are
// certain fringe cases that must not return an integer.
// clip leading sign
if ( $this->negative && $integer[0] === '-' ) {
$digits = substr($integer, 1);
if ($digits === '0') $integer = '0'; // rm minus sign for zero
} elseif( $this->positive && $integer[0] === '+' ) {
$digits = $integer = substr($integer, 1); // rm unnecessary plus
} else {
$digits = $integer;
}
// test if it's numeric
if (!ctype_digit($digits)) return false;
// perform scope tests
if (!$this->zero && $integer == 0) return false;
if (!$this->positive && $integer > 0) return false;
if (!$this->negative && $integer < 0) return false;
return $integer;
}
}

View file

@ -0,0 +1,72 @@
<?php
/**
* Validates the HTML attribute lang, effectively a language code.
* @note Built according to RFC 3066, which obsoleted RFC 1766
*/
class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
{
public function validate($string, $config, $context) {
$string = trim($string);
if (!$string) return false;
$subtags = explode('-', $string);
$num_subtags = count($subtags);
if ($num_subtags == 0) return false; // sanity check
// process primary subtag : $subtags[0]
$length = strlen($subtags[0]);
switch ($length) {
case 0:
return false;
case 1:
if (! ($subtags[0] == 'x' || $subtags[0] == 'i') ) {
return false;
}
break;
case 2:
case 3:
if (! ctype_alpha($subtags[0]) ) {
return false;
} elseif (! ctype_lower($subtags[0]) ) {
$subtags[0] = strtolower($subtags[0]);
}
break;
default:
return false;
}
$new_string = $subtags[0];
if ($num_subtags == 1) return $new_string;
// process second subtag : $subtags[1]
$length = strlen($subtags[1]);
if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) {
return $new_string;
}
if (!ctype_lower($subtags[1])) $subtags[1] = strtolower($subtags[1]);
$new_string .= '-' . $subtags[1];
if ($num_subtags == 2) return $new_string;
// process all other subtags, index 2 and up
for ($i = 2; $i < $num_subtags; $i++) {
$length = strlen($subtags[$i]);
if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) {
return $new_string;
}
if (!ctype_lower($subtags[$i])) {
$subtags[$i] = strtolower($subtags[$i]);
}
$new_string .= '-' . $subtags[$i];
}
return $new_string;
}
}

View file

@ -0,0 +1,32 @@
<?php
/**
* Decorator that, depending on a token, switches between two definitions.
*/
class HTMLPurifier_AttrDef_Switch
{
protected $tag;
protected $withTag, $withoutTag;
/**
* @param string $tag Tag name to switch upon
* @param HTMLPurifier_AttrDef $with_tag Call if token matches tag
* @param HTMLPurifier_AttrDef $without_tag Call if token doesn't match, or there is no token
*/
public function __construct($tag, $with_tag, $without_tag) {
$this->tag = $tag;
$this->withTag = $with_tag;
$this->withoutTag = $without_tag;
}
public function validate($string, $config, $context) {
$token = $context->get('CurrentToken', true);
if (!$token || $token->name !== $this->tag) {
return $this->withoutTag->validate($string, $config, $context);
} else {
return $this->withTag->validate($string, $config, $context);
}
}
}

View file

@ -0,0 +1,14 @@
<?php
/**
* Validates arbitrary text according to the HTML spec.
*/
class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
{
public function validate($string, $config, $context) {
return $this->parseCDATA($string);
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* Validates a URI as defined by RFC 3986.
* @note Scheme-specific mechanics deferred to HTMLPurifier_URIScheme
*/
class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
{
protected $parser;
protected $embedsResource;
/**
* @param $embeds_resource_resource Does the URI here result in an extra HTTP request?
*/
public function __construct($embeds_resource = false) {
$this->parser = new HTMLPurifier_URIParser();
$this->embedsResource = (bool) $embeds_resource;
}
public function make($string) {
$embeds = (bool) $string;
return new HTMLPurifier_AttrDef_URI($embeds);
}
public function validate($uri, $config, $context) {
if ($config->get('URI', 'Disable')) return false;
$uri = $this->parseCDATA($uri);
// parse the URI
$uri = $this->parser->parse($uri);
if ($uri === false) return false;
// add embedded flag to context for validators
$context->register('EmbeddedURI', $this->embedsResource);
$ok = false;
do {
// generic validation
$result = $uri->validate($config, $context);
if (!$result) break;
// chained filtering
$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;
// Post chained filtering
$result = $uri_def->postFilter($uri, $config, $context);
if (!$result) break;
// survived gauntlet
$ok = true;
} while (false);
$context->destroy('EmbeddedURI');
if (!$ok) return false;
// back to string
return $uri->toString();
}
}

View file

@ -0,0 +1,15 @@
<?php
abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
{
/**
* Unpacks a mailbox into its display-name and address
*/
function unpack($string) {
// needs to be implemented
}
}
// sub-implementations

View file

@ -0,0 +1,20 @@
<?php
/**
* Primitive email validation class based on the regexp found at
* http://www.regular-expressions.info/email.html
*/
class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email
{
public function validate($string, $config, $context) {
// no support for named mailboxes i.e. "Bob <bob@example.com>"
// that needs more percent encoding to be done
if ($string == '') return false;
$string = trim($string);
$result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string);
return $result ? $string : false;
}
}

View file

@ -0,0 +1,61 @@
<?php
/**
* Validates a host according to the IPv4, IPv6 and DNS (future) specifications.
*/
class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
{
/**
* Instance of HTMLPurifier_AttrDef_URI_IPv4 sub-validator
*/
protected $ipv4;
/**
* Instance of HTMLPurifier_AttrDef_URI_IPv6 sub-validator
*/
protected $ipv6;
public function __construct() {
$this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
$this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
}
public function validate($string, $config, $context) {
$length = strlen($string);
if ($string === '') return '';
if ($length > 1 && $string[0] === '[' && $string[$length-1] === ']') {
//IPv6
$ip = substr($string, 1, $length - 2);
$valid = $this->ipv6->validate($ip, $config, $context);
if ($valid === false) return false;
return '['. $valid . ']';
}
// need to do checks on unusual encodings too
$ipv4 = $this->ipv4->validate($string, $config, $context);
if ($ipv4 !== false) return $ipv4;
// A regular domain name.
// This breaks I18N domain names, but we don't have proper IRI support,
// so force users to insert Punycode. If there's complaining we'll
// try to fix things into an international friendly form.
// The productions describing this are:
$a = '[a-z]'; // alpha
$an = '[a-z0-9]'; // alphanum
$and = '[a-z0-9-]'; // alphanum | "-"
// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
$domainlabel = "$an($and*$an)?";
// toplabel = alpha | alpha *( alphanum | "-" ) alphanum
$toplabel = "$a($and*$an)?";
// hostname = *( domainlabel "." ) toplabel [ "." ]
$match = preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string);
if (!$match) return false;
return $string;
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* Validates an IPv4 address
* @author Feyd @ forums.devnetwork.net (public domain)
*/
class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef
{
/**
* IPv4 regex, protected so that IPv6 can reuse it
*/
protected $ip4;
public function validate($aIP, $config, $context) {
if (!$this->ip4) $this->_loadRegex();
if (preg_match('#^' . $this->ip4 . '$#s', $aIP))
{
return $aIP;
}
return false;
}
/**
* Lazy load function to prevent regex from being stuffed in
* cache.
*/
protected function _loadRegex() {
$oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255
$this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})";
}
}

View file

@ -0,0 +1,98 @@
<?php
/**
* Validates an IPv6 address.
* @author Feyd @ forums.devnetwork.net (public domain)
* @note This function requires brackets to have been removed from address
* in URI.
*/
class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
{
public function validate($aIP, $config, $context) {
if (!$this->ip4) $this->_loadRegex();
$original = $aIP;
$hex = '[0-9a-fA-F]';
$blk = '(?:' . $hex . '{1,4})';
$pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128
// prefix check
if (strpos($aIP, '/') !== false)
{
if (preg_match('#' . $pre . '$#s', $aIP, $find))
{
$aIP = substr($aIP, 0, 0-strlen($find[0]));
unset($find);
}
else
{
return false;
}
}
// IPv4-compatiblity check
if (preg_match('#(?<=:'.')' . $this->ip4 . '$#s', $aIP, $find))
{
$aIP = substr($aIP, 0, 0-strlen($find[0]));
$ip = explode('.', $find[0]);
$ip = array_map('dechex', $ip);
$aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
unset($find, $ip);
}
// compression check
$aIP = explode('::', $aIP);
$c = count($aIP);
if ($c > 2)
{
return false;
}
elseif ($c == 2)
{
list($first, $second) = $aIP;
$first = explode(':', $first);
$second = explode(':', $second);
if (count($first) + count($second) > 8)
{
return false;
}
while(count($first) < 8)
{
array_push($first, '0');
}
array_splice($first, 8 - count($second), 8, $second);
$aIP = $first;
unset($first,$second);
}
else
{
$aIP = explode(':', $aIP[0]);
}
$c = count($aIP);
if ($c != 8)
{
return false;
}
// All the pieces should be 16-bit hex strings. Are they?
foreach ($aIP as $piece)
{
if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece)))
{
return false;
}
}
return $original;
}
}

View file

@ -0,0 +1,55 @@
<?php
/**
* Processes an entire attribute array for corrections needing multiple values.
*
* Occasionally, a certain attribute will need to be removed and popped onto
* another value. Instead of creating a complex return syntax for
* HTMLPurifier_AttrDef, we just pass the whole attribute array to a
* specialized object and have that do the special work. That is the
* family of HTMLPurifier_AttrTransform.
*
* An attribute transformation can be assigned to run before or after
* HTMLPurifier_AttrDef validation. See HTMLPurifier_HTMLDefinition for
* more details.
*/
abstract class HTMLPurifier_AttrTransform
{
/**
* Abstract: makes changes to the attributes dependent on multiple values.
*
* @param $attr Assoc array of attributes, usually from
* HTMLPurifier_Token_Tag::$attr
* @param $config Mandatory HTMLPurifier_Config object.
* @param $context Mandatory HTMLPurifier_Context object
* @returns Processed attribute array.
*/
abstract public function transform($attr, $config, $context);
/**
* Prepends CSS properties to the style attribute, creating the
* attribute if it doesn't exist.
* @param $attr Attribute array to process (passed by reference)
* @param $css CSS to prepend
*/
public function prependCSS(&$attr, $css) {
$attr['style'] = isset($attr['style']) ? $attr['style'] : '';
$attr['style'] = $css . $attr['style'];
}
/**
* Retrieves and removes an attribute
* @param $attr Attribute array to process (passed by reference)
* @param $key Key of attribute to confiscate
*/
public function confiscateAttr(&$attr, $key) {
if (!isset($attr[$key])) return null;
$value = $attr[$key];
unset($attr[$key]);
return $value;
}
}

View file

@ -0,0 +1,22 @@
<?php
/**
* Pre-transform that changes proprietary background attribute to CSS.
*/
class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform {
public function transform($attr, $config, $context) {
if (!isset($attr['background'])) return $attr;
$background = $this->confiscateAttr($attr, 'background');
// some validation should happen here
$this->prependCSS($attr, "background-image:url($background);");
return $attr;
}
}

View file

@ -0,0 +1,18 @@
<?php
// this MUST be placed in post, as it assumes that any value in dir is valid
/**
* Post-trasnform that ensures that bdo tags have the dir attribute set.
*/
class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform
{
public function transform($attr, $config, $context) {
if (isset($attr['dir'])) return $attr;
$attr['dir'] = $config->get('Attr', 'DefaultTextDir');
return $attr;
}
}

View file

@ -0,0 +1,22 @@
<?php
/**
* Pre-transform that changes deprecated bgcolor attribute to CSS.
*/
class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform {
public function transform($attr, $config, $context) {
if (!isset($attr['bgcolor'])) return $attr;
$bgcolor = $this->confiscateAttr($attr, 'bgcolor');
// some validation should happen here
$this->prependCSS($attr, "background-color:$bgcolor;");
return $attr;
}
}

View file

@ -0,0 +1,35 @@
<?php
/**
* Pre-transform that changes converts a boolean attribute to fixed CSS
*/
class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform {
/**
* Name of boolean attribute that is trigger
*/
protected $attr;
/**
* CSS declarations to add to style, needs trailing semicolon
*/
protected $css;
/**
* @param $attr string attribute name to convert from
* @param $css string CSS declarations to add to style (needs semicolon)
*/
public function __construct($attr, $css) {
$this->attr = $attr;
$this->css = $css;
}
public function transform($attr, $config, $context) {
if (!isset($attr[$this->attr])) return $attr;
unset($attr[$this->attr]);
$this->prependCSS($attr, $this->css);
return $attr;
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* Pre-transform that changes deprecated border attribute to CSS.
*/
class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform {
public function transform($attr, $config, $context) {
if (!isset($attr['border'])) return $attr;
$border_width = $this->confiscateAttr($attr, 'border');
// some validation should happen here
$this->prependCSS($attr, "border:{$border_width}px solid;");
return $attr;
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* Generic pre-transform that converts an attribute with a fixed number of
* values (enumerated) to CSS.
*/
class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform {
/**
* Name of attribute to transform from
*/
protected $attr;
/**
* Lookup array of attribute values to CSS
*/
protected $enumToCSS = array();
/**
* Case sensitivity of the matching
* @warning Currently can only be guaranteed to work with ASCII
* values.
*/
protected $caseSensitive = false;
/**
* @param $attr String attribute name to transform from
* @param $enumToCSS Lookup array of attribute values to CSS
* @param $case_sensitive Boolean case sensitivity indicator, default false
*/
public function __construct($attr, $enum_to_css, $case_sensitive = false) {
$this->attr = $attr;
$this->enumToCSS = $enum_to_css;
$this->caseSensitive = (bool) $case_sensitive;
}
public function transform($attr, $config, $context) {
if (!isset($attr[$this->attr])) return $attr;
$value = trim($attr[$this->attr]);
unset($attr[$this->attr]);
if (!$this->caseSensitive) $value = strtolower($value);
if (!isset($this->enumToCSS[$value])) {
return $attr;
}
$this->prependCSS($attr, $this->enumToCSS[$value]);
return $attr;
}
}

View file

@ -0,0 +1,41 @@
<?php
// must be called POST validation
/**
* Transform that supplies default values for the src and alt attributes
* in img tags, as well as prevents the img tag from being removed
* because of a missing alt tag. This needs to be registered as both
* a pre and post attribute transform.
*/
class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
{
public function transform($attr, $config, $context) {
$src = true;
if (!isset($attr['src'])) {
if ($config->get('Core', 'RemoveInvalidImg')) return $attr;
$attr['src'] = $config->get('Attr', 'DefaultInvalidImage');
$src = false;
}
if (!isset($attr['alt'])) {
if ($src) {
$alt = $config->get('Attr', 'DefaultImageAlt');
if ($alt === null) {
$attr['alt'] = basename($attr['src']);
} else {
$attr['alt'] = $alt;
}
} else {
$attr['alt'] = $config->get('Attr', 'DefaultInvalidImageAlt');
}
}
return $attr;
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
* Pre-transform that changes deprecated hspace and vspace attributes to CSS
*/
class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform {
protected $attr;
protected $css = array(
'hspace' => array('left', 'right'),
'vspace' => array('top', 'bottom')
);
public function __construct($attr) {
$this->attr = $attr;
if (!isset($this->css[$attr])) {
trigger_error(htmlspecialchars($attr) . ' is not valid space attribute');
}
}
public function transform($attr, $config, $context) {
if (!isset($attr[$this->attr])) return $attr;
$width = $this->confiscateAttr($attr, $this->attr);
// some validation could happen here
if (!isset($this->css[$this->attr])) return $attr;
$style = '';
foreach ($this->css[$this->attr] as $suffix) {
$property = "margin-$suffix";
$style .= "$property:{$width}px;";
}
$this->prependCSS($attr, $style);
return $attr;
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* Performs miscellaneous cross attribute validation and filtering for
* input elements. This is meant to be a post-transform.
*/
class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform {
protected $pixels;
public function __construct() {
$this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels();
}
public function transform($attr, $config, $context) {
if (!isset($attr['type'])) $t = 'text';
else $t = strtolower($attr['type']);
if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') {
unset($attr['checked']);
}
if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') {
unset($attr['maxlength']);
}
if (isset($attr['size']) && $t !== 'text' && $t !== 'password') {
$result = $this->pixels->validate($attr['size'], $config, $context);
if ($result === false) unset($attr['size']);
else $attr['size'] = $result;
}
if (isset($attr['src']) && $t !== 'image') {
unset($attr['src']);
}
if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) {
$attr['value'] = '';
}
return $attr;
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* Post-transform that copies lang's value to xml:lang (and vice-versa)
* @note Theoretically speaking, this could be a pre-transform, but putting
* post is more efficient.
*/
class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform
{
public function transform($attr, $config, $context) {
$lang = isset($attr['lang']) ? $attr['lang'] : false;
$xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false;
if ($lang !== false && $xml_lang === false) {
$attr['xml:lang'] = $lang;
} elseif ($xml_lang !== false) {
$attr['lang'] = $xml_lang;
}
return $attr;
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* Class for handling width/height length attribute transformations to CSS
*/
class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
{
protected $name;
protected $cssName;
public function __construct($name, $css_name = null) {
$this->name = $name;
$this->cssName = $css_name ? $css_name : $name;
}
public function transform($attr, $config, $context) {
if (!isset($attr[$this->name])) return $attr;
$length = $this->confiscateAttr($attr, $this->name);
if(ctype_digit($length)) $length .= 'px';
$this->prependCSS($attr, $this->cssName . ":$length;");
return $attr;
}
}

View file

@ -0,0 +1,18 @@
<?php
/**
* Pre-transform that changes deprecated name attribute to ID if necessary
*/
class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
{
public function transform($attr, $config, $context) {
if (!isset($attr['name'])) return $attr;
$id = $this->confiscateAttr($attr, 'name');
if ( isset($attr['id'])) return $attr;
$attr['id'] = $id;
return $attr;
}
}

View file

@ -0,0 +1,13 @@
<?php
class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform
{
public $name = "SafeEmbed";
public function transform($attr, $config, $context) {
$attr['allowscriptaccess'] = 'never';
$attr['allownetworking'] = 'internal';
$attr['type'] = 'application/x-shockwave-flash';
return $attr;
}
}

View file

@ -0,0 +1,14 @@
<?php
/**
* Writes default type for all objects. Currently only supports flash.
*/
class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
{
public $name = "SafeObject";
function transform($attr, $config, $context) {
if (!isset($attr['type'])) $attr['type'] = 'application/x-shockwave-flash';
return $attr;
}
}

View file

@ -0,0 +1,48 @@
<?php
/**
* Validates name/value pairs in param tags to be used in safe objects. This
* will only allow name values it recognizes, and pre-fill certain attributes
* with required values.
*
* @note
* This class only supports Flash. In the future, Quicktime support
* may be added.
*
* @warning
* This class expects an injector to add the necessary parameters tags.
*/
class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
{
public $name = "SafeParam";
private $uri;
public function __construct() {
$this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
}
public function transform($attr, $config, $context) {
// If we add support for other objects, we'll need to alter the
// transforms.
switch ($attr['name']) {
// application/x-shockwave-flash
// Keep this synchronized with Injector/SafeObject.php
case 'allowScriptAccess':
$attr['value'] = 'never';
break;
case 'allowNetworking':
$attr['value'] = 'internal';
break;
case 'wmode':
$attr['value'] = 'window';
break;
case 'movie':
$attr['value'] = $this->uri->validate($attr['value'], $config, $context);
break;
// add other cases to support other param name/value pairs
default:
$attr['name'] = $attr['value'] = null;
}
return $attr;
}
}

View file

@ -0,0 +1,14 @@
<?php
/**
* Implements required attribute stipulation for <script>
*/
class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform
{
public function transform($attr, $config, $context) {
if (!isset($attr['type'])) {
$attr['type'] = 'text/javascript';
}
return $attr;
}
}

View file

@ -0,0 +1,16 @@
<?php
/**
* Sets height/width defaults for <textarea>
*/
class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform
{
public function transform($attr, $config, $context) {
// Calculated from Firefox
if (!isset($attr['cols'])) $attr['cols'] = '22';
if (!isset($attr['rows'])) $attr['rows'] = '3';
return $attr;
}
}

View file

@ -0,0 +1,74 @@
<?php
/**
* Provides lookup array of attribute types to HTMLPurifier_AttrDef objects
*/
class HTMLPurifier_AttrTypes
{
/**
* Lookup array of attribute string identifiers to concrete implementations
*/
protected $info = array();
/**
* Constructs the info array, supplying default implementations for attribute
* types.
*/
public function __construct() {
// pseudo-types, must be instantiated via shorthand
$this->info['Enum'] = new HTMLPurifier_AttrDef_Enum();
$this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool();
$this->info['CDATA'] = new HTMLPurifier_AttrDef_Text();
$this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID();
$this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length();
$this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength();
$this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens();
$this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels();
$this->info['Text'] = new HTMLPurifier_AttrDef_Text();
$this->info['URI'] = new HTMLPurifier_AttrDef_URI();
$this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang();
$this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color();
// unimplemented aliases
$this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
$this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text();
$this->info['Charsets'] = new HTMLPurifier_AttrDef_Text();
$this->info['Character'] = new HTMLPurifier_AttrDef_Text();
// number is really a positive integer (one or more digits)
// FIXME: ^^ not always, see start and value of list items
$this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true);
}
/**
* Retrieves a type
* @param $type String type name
* @return Object AttrDef for type
*/
public function get($type) {
// determine if there is any extra info tacked on
if (strpos($type, '#') !== false) list($type, $string) = explode('#', $type, 2);
else $string = '';
if (!isset($this->info[$type])) {
trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
return;
}
return $this->info[$type]->make($string);
}
/**
* Sets a new implementation for a type
* @param $type String type name
* @param $impl Object AttrDef for type
*/
public function set($type, $impl) {
$this->info[$type] = $impl;
}
}

View file

@ -0,0 +1,161 @@
<?php
/**
* Validates the attributes of a token. Doesn't manage required attributes
* very well. The only reason we factored this out was because RemoveForeignElements
* also needed it besides ValidateAttributes.
*/
class HTMLPurifier_AttrValidator
{
/**
* Validates the attributes of a token, returning a modified token
* that has valid tokens
* @param $token Reference to token to validate. We require a reference
* because the operation this class performs on the token are
* not atomic, so the context CurrentToken to be updated
* throughout
* @param $config Instance of HTMLPurifier_Config
* @param $context Instance of HTMLPurifier_Context
*/
public function validateToken(&$token, &$config, $context) {
$definition = $config->getHTMLDefinition();
$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 $token;
// 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. <p align="right"> to <p style="text-align:right;">
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');
}
}

View file

@ -0,0 +1,96 @@
<?php
// constants are slow, so we use as few as possible
if (!defined('HTMLPURIFIER_PREFIX')) {
define('HTMLPURIFIER_PREFIX', realpath(dirname(__FILE__) . '/..'));
}
// accomodations for versions earlier than 5.0.2
// borrowed from PHP_Compat, LGPL licensed, by Aidan Lister <aidan@php.net>
if (!defined('PHP_EOL')) {
switch (strtoupper(substr(PHP_OS, 0, 3))) {
case 'WIN':
define('PHP_EOL', "\r\n");
break;
case 'DAR':
define('PHP_EOL', "\r");
break;
default:
define('PHP_EOL', "\n");
}
}
/**
* Bootstrap class that contains meta-functionality for HTML Purifier such as
* the autoload function.
*
* @note
* This class may be used without any other files from HTML Purifier.
*/
class HTMLPurifier_Bootstrap
{
/**
* Autoload function for HTML Purifier
* @param $class Class to load
*/
public static function autoload($class) {
$file = HTMLPurifier_Bootstrap::getPath($class);
if (!$file) return false;
require HTMLPURIFIER_PREFIX . '/' . $file;
return true;
}
/**
* Returns the path for a specific class.
*/
public static function getPath($class) {
if (strncmp('HTMLPurifier', $class, 12) !== 0) return false;
// Custom implementations
if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
$code = str_replace('_', '-', substr($class, 22));
$file = 'HTMLPurifier/Language/classes/' . $code . '.php';
} else {
$file = str_replace('_', '/', $class) . '.php';
}
if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) return false;
return $file;
}
/**
* "Pre-registers" our autoloader on the SPL stack.
*/
public static function registerAutoload() {
$autoload = array('HTMLPurifier_Bootstrap', 'autoload');
if ( ($funcs = spl_autoload_functions()) === false ) {
spl_autoload_register($autoload);
} elseif (function_exists('spl_autoload_unregister')) {
$compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
version_compare(PHP_VERSION, '5.1.0', '>=');
foreach ($funcs as $func) {
if (is_array($func)) {
// :TRICKY: There are some compatibility issues and some
// places where we need to error out
$reflector = new ReflectionMethod($func[0], $func[1]);
if (!$reflector->isStatic()) {
throw new Exception('
HTML Purifier autoloader registrar is not compatible
with non-static object methods due to PHP Bug #44144;
Please do not use HTMLPurifier.autoload.php (or any
file that includes this file); instead, place the code:
spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
after your own autoloaders.
');
}
// Suprisingly, spl_autoload_register supports the
// Class::staticMethod callback format, although call_user_func doesn't
if ($compat) $func = implode('::', $func);
}
spl_autoload_unregister($func);
}
spl_autoload_register($autoload);
foreach ($funcs as $func) spl_autoload_register($func);
}
}
}

View file

@ -0,0 +1,290 @@
<?php
/**
* Defines allowed CSS attributes and what their values are.
* @see HTMLPurifier_HTMLDefinition
*/
class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
{
public $type = 'CSS';
/**
* Assoc array of attribute name to definition object.
*/
public $info = array();
/**
* Constructs the info array. The meat of this class.
*/
protected function doSetup($config) {
$this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
array('left', 'right', 'center', 'justify'), false);
$border_style =
$this->info['border-bottom-style'] =
$this->info['border-right-style'] =
$this->info['border-left-style'] =
$this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
array('none', 'hidden', 'dotted', 'dashed', 'solid', 'double',
'groove', 'ridge', 'inset', 'outset'), false);
$this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
$this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
array('none', 'left', 'right', 'both'), false);
$this->info['float'] = new HTMLPurifier_AttrDef_Enum(
array('none', 'left', 'right'), false);
$this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
array('normal', 'italic', 'oblique'), false);
$this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
array('normal', 'small-caps'), false);
$uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_Enum(array('none')),
new HTMLPurifier_AttrDef_CSS_URI()
)
);
$this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
array('inside', 'outside'), false);
$this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
array('disc', 'circle', 'square', 'decimal', 'lower-roman',
'upper-roman', 'lower-alpha', 'upper-alpha', 'none'), false);
$this->info['list-style-image'] = $uri_or_none;
$this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
$this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
array('capitalize', 'uppercase', 'lowercase', 'none'), false);
$this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
$this->info['background-image'] = $uri_or_none;
$this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
);
$this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
array('scroll', 'fixed')
);
$this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
$border_color =
$this->info['border-top-color'] =
$this->info['border-bottom-color'] =
$this->info['border-left-color'] =
$this->info['border-right-color'] =
$this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_Enum(array('transparent')),
new HTMLPurifier_AttrDef_CSS_Color()
));
$this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
$this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
$border_width =
$this->info['border-top-width'] =
$this->info['border-bottom-width'] =
$this->info['border-left-width'] =
$this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
));
$this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
$this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_Enum(array('normal')),
new HTMLPurifier_AttrDef_CSS_Length()
));
$this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_Enum(array('normal')),
new HTMLPurifier_AttrDef_CSS_Length()
));
$this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_Enum(array('xx-small', 'x-small',
'small', 'medium', 'large', 'x-large', 'xx-large',
'larger', 'smaller')),
new HTMLPurifier_AttrDef_CSS_Percentage(),
new HTMLPurifier_AttrDef_CSS_Length()
));
$this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_Enum(array('normal')),
new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
new HTMLPurifier_AttrDef_CSS_Length('0'),
new HTMLPurifier_AttrDef_CSS_Percentage(true)
));
$margin =
$this->info['margin-top'] =
$this->info['margin-bottom'] =
$this->info['margin-left'] =
$this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_CSS_Length(),
new HTMLPurifier_AttrDef_CSS_Percentage(),
new HTMLPurifier_AttrDef_Enum(array('auto'))
));
$this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
// non-negative
$padding =
$this->info['padding-top'] =
$this->info['padding-bottom'] =
$this->info['padding-left'] =
$this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_CSS_Length('0'),
new HTMLPurifier_AttrDef_CSS_Percentage(true)
));
$this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
$this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_CSS_Length(),
new HTMLPurifier_AttrDef_CSS_Percentage()
));
$trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_CSS_Length('0'),
new HTMLPurifier_AttrDef_CSS_Percentage(true),
new HTMLPurifier_AttrDef_Enum(array('auto'))
));
$max = $config->get('CSS', 'MaxImgLength');
$this->info['width'] =
$this->info['height'] =
$max === null ?
$trusted_wh :
new HTMLPurifier_AttrDef_Switch('img',
// For img tags:
new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_CSS_Length('0', $max),
new HTMLPurifier_AttrDef_Enum(array('auto'))
)),
// For everyone else:
$trusted_wh
);
$this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
$this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
// this could use specialized code
$this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
array('normal', 'bold', 'bolder', 'lighter', '100', '200', '300',
'400', '500', '600', '700', '800', '900'), false);
// MUST be called after other font properties, as it references
// a CSSDefinition object
$this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
// same here
$this->info['border'] =
$this->info['border-bottom'] =
$this->info['border-top'] =
$this->info['border-left'] =
$this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
$this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(array(
'collapse', 'separate'));
$this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(array(
'top', 'bottom'));
$this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(array(
'auto', 'fixed'));
$this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_Enum(array('baseline', 'sub', 'super',
'top', 'text-top', 'middle', 'bottom', 'text-bottom')),
new HTMLPurifier_AttrDef_CSS_Length(),
new HTMLPurifier_AttrDef_CSS_Percentage()
));
$this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
// partial support
$this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(array('nowrap'));
if ($config->get('CSS', 'Proprietary')) {
$this->doSetupProprietary($config);
}
if ($config->get('CSS', 'AllowTricky')) {
$this->doSetupTricky($config);
}
$allow_important = $config->get('CSS', 'AllowImportant');
// wrap all attr-defs with decorator that handles !important
foreach ($this->info as $k => $v) {
$this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
}
$this->setupConfigStuff($config);
}
protected function doSetupProprietary($config) {
// Internet Explorer only scrollbar colors
$this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
$this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
$this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
$this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
$this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
$this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
// technically not proprietary, but CSS3, and no one supports it
$this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
$this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
$this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
// only opacity, for now
$this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
}
protected function doSetupTricky($config) {
$this->info['display'] = new HTMLPurifier_AttrDef_Enum(array(
'inline', 'block', 'list-item', 'run-in', 'compact',
'marker', 'table', 'inline-table', 'table-row-group',
'table-header-group', 'table-footer-group', 'table-row',
'table-column-group', 'table-column', 'table-cell', 'table-caption', 'none'
));
$this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(array(
'visible', 'hidden', 'collapse'
));
}
/**
* Performs extra config-based processing. Based off of
* HTMLPurifier_HTMLDefinition.
* @todo Refactor duplicate elements into common class (probably using
* composition, not inheritance).
*/
protected function setupConfigStuff($config) {
// setup allowed elements
$support = "(for information on implementing this, see the ".
"support forums) ";
$allowed_attributes = $config->get('CSS', 'AllowedProperties');
if ($allowed_attributes !== null) {
foreach ($this->info as $name => $d) {
if(!isset($allowed_attributes[$name])) unset($this->info[$name]);
unset($allowed_attributes[$name]);
}
// emit errors
foreach ($allowed_attributes as $name => $d) {
// :TODO: Is this htmlspecialchars() call really necessary?
$name = htmlspecialchars($name);
trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
}
}
}
}

View file

@ -0,0 +1,48 @@
<?php
/**
* Defines allowed child nodes and validates tokens against it.
*/
abstract class HTMLPurifier_ChildDef
{
/**
* Type of child definition, usually right-most part of class name lowercase.
* Used occasionally in terms of context.
*/
public $type;
/**
* Bool that indicates whether or not an empty array of children is okay
*
* This is necessary for redundant checking when changes affecting
* a child node may cause a parent node to now be disallowed.
*/
public $allow_empty;
/**
* Lookup array of all elements that this definition could possibly allow
*/
public $elements = array();
/**
* Get lookup of tag names that should not close this element automatically.
* All other elements will do so.
*/
public function getNonAutoCloseElements($config) {
return $this->elements;
}
/**
* Validates nodes according to definition and returns modification.
*
* @param $tokens_of_children Array of HTMLPurifier_Token
* @param $config HTMLPurifier_Config object
* @param $context HTMLPurifier_Context object
* @return bool true to leave nodes as is
* @return bool false to remove parent node
* @return array of replacement child tokens
*/
abstract public function validateChildren($tokens_of_children, $config, $context);
}

View file

@ -0,0 +1,47 @@
<?php
/**
* Definition that uses different definitions depending on context.
*
* The del and ins tags are notable because they allow different types of
* elements depending on whether or not they're in a block or inline context.
* Chameleon allows this behavior to happen by using two different
* definitions depending on context. While this somewhat generalized,
* it is specifically intended for those two tags.
*/
class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef
{
/**
* Instance of the definition object to use when inline. Usually stricter.
*/
public $inline;
/**
* Instance of the definition object to use when block.
*/
public $block;
public $type = 'chameleon';
/**
* @param $inline List of elements to allow when inline.
* @param $block List of elements to allow when block.
*/
public function __construct($inline, $block) {
$this->inline = new HTMLPurifier_ChildDef_Optional($inline);
$this->block = new HTMLPurifier_ChildDef_Optional($block);
$this->elements = $this->block->elements;
}
public function validateChildren($tokens_of_children, $config, $context) {
if ($context->get('IsInline') === false) {
return $this->block->validateChildren(
$tokens_of_children, $config, $context);
} else {
return $this->inline->validateChildren(
$tokens_of_children, $config, $context);
}
}
}

View file

@ -0,0 +1,89 @@
<?php
/**
* Custom validation class, accepts DTD child definitions
*
* @warning Currently this class is an all or nothing proposition, that is,
* it will only give a bool return value.
*/
class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef
{
public $type = 'custom';
public $allow_empty = false;
/**
* Allowed child pattern as defined by the DTD
*/
public $dtd_regex;
/**
* PCRE regex derived from $dtd_regex
* @private
*/
private $_pcre_regex;
/**
* @param $dtd_regex Allowed child pattern from the DTD
*/
public function __construct($dtd_regex) {
$this->dtd_regex = $dtd_regex;
$this->_compileRegex();
}
/**
* Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex)
*/
protected function _compileRegex() {
$raw = str_replace(' ', '', $this->dtd_regex);
if ($raw{0} != '(') {
$raw = "($raw)";
}
$el = '[#a-zA-Z0-9_.-]+';
$reg = $raw;
// COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M
// DOING! Seriously: if there's problems, please report them.
// collect all elements into the $elements array
preg_match_all("/$el/", $reg, $matches);
foreach ($matches[0] as $match) {
$this->elements[$match] = true;
}
// setup all elements as parentheticals with leading commas
$reg = preg_replace("/$el/", '(,\\0)', $reg);
// remove commas when they were not solicited
$reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg);
// remove all non-paranthetical commas: they are handled by first regex
$reg = preg_replace("/,\(/", '(', $reg);
$this->_pcre_regex = $reg;
}
public function validateChildren($tokens_of_children, $config, $context) {
$list_of_children = '';
$nesting = 0; // depth into the nest
foreach ($tokens_of_children as $token) {
if (!empty($token->is_whitespace)) continue;
$is_child = ($nesting == 0); // direct
if ($token instanceof HTMLPurifier_Token_Start) {
$nesting++;
} elseif ($token instanceof HTMLPurifier_Token_End) {
$nesting--;
}
if ($is_child) {
$list_of_children .= $token->name . ',';
}
}
// add leading comma to deal with stray comma declarations
$list_of_children = ',' . rtrim($list_of_children, ',');
$okay =
preg_match(
'/^,?'.$this->_pcre_regex.'$/',
$list_of_children
);
return (bool) $okay;
}
}

View file

@ -0,0 +1,19 @@
<?php
/**
* Definition that disallows all elements.
* @warning validateChildren() in this class is actually never called, because
* empty elements are corrected in HTMLPurifier_Strategy_MakeWellFormed
* before child definitions are parsed in earnest by
* HTMLPurifier_Strategy_FixNesting.
*/
class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef
{
public $allow_empty = true;
public $type = 'empty';
public function __construct() {}
public function validateChildren($tokens_of_children, $config, $context) {
return array();
}
}

View file

@ -0,0 +1,23 @@
<?php
/**
* Definition that allows a set of elements, and allows no children.
* @note This is a hack to reuse code from HTMLPurifier_ChildDef_Required,
* really, one shouldn't inherit from the other. Only altered behavior
* is to overload a returned false with an array. Thus, it will never
* return false.
*/
class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required
{
public $allow_empty = true;
public $type = 'optional';
public function validateChildren($tokens_of_children, $config, $context) {
$result = parent::validateChildren($tokens_of_children, $config, $context);
if ($result === false) {
if (empty($tokens_of_children)) return true;
else return array();
}
return $result;
}
}

View file

@ -0,0 +1,106 @@
<?php
/**
* Definition that allows a set of elements, but disallows empty children.
*/
class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
{
/**
* Lookup table of allowed elements.
* @public
*/
public $elements = array();
/**
* @param $elements List of allowed element names (lowercase).
*/
public function __construct($elements) {
if (is_string($elements)) {
$elements = str_replace(' ', '', $elements);
$elements = explode('|', $elements);
}
$keys = array_keys($elements);
if ($keys == array_keys($keys)) {
$elements = array_flip($elements);
foreach ($elements as $i => $x) {
$elements[$i] = true;
if (empty($i)) unset($elements[$i]); // remove blank
}
}
$this->elements = $elements;
}
public $allow_empty = false;
public $type = 'required';
public function validateChildren($tokens_of_children, $config, $context) {
// if there are no tokens, delete parent node
if (empty($tokens_of_children)) return false;
// the new set of children
$result = array();
// current depth into the nest
$nesting = 0;
// whether or not we're deleting a node
$is_deleting = false;
// whether or not parsed character data is allowed
// this controls whether or not we silently drop a tag
// or generate escaped HTML from it
$pcdata_allowed = isset($this->elements['#PCDATA']);
// a little sanity check to make sure it's not ALL whitespace
$all_whitespace = true;
// some configuration
$escape_invalid_children = $config->get('Core', 'EscapeInvalidChildren');
// generator
$gen = new HTMLPurifier_Generator($config, $context);
foreach ($tokens_of_children as $token) {
if (!empty($token->is_whitespace)) {
$result[] = $token;
continue;
}
$all_whitespace = false; // phew, we're not talking about whitespace
$is_child = ($nesting == 0);
if ($token instanceof HTMLPurifier_Token_Start) {
$nesting++;
} elseif ($token instanceof HTMLPurifier_Token_End) {
$nesting--;
}
if ($is_child) {
$is_deleting = false;
if (!isset($this->elements[$token->name])) {
$is_deleting = true;
if ($pcdata_allowed && $token instanceof HTMLPurifier_Token_Text) {
$result[] = $token;
} elseif ($pcdata_allowed && $escape_invalid_children) {
$result[] = new HTMLPurifier_Token_Text(
$gen->generateFromToken($token)
);
}
continue;
}
}
if (!$is_deleting || ($pcdata_allowed && $token instanceof HTMLPurifier_Token_Text)) {
$result[] = $token;
} elseif ($pcdata_allowed && $escape_invalid_children) {
$result[] =
new HTMLPurifier_Token_Text(
$gen->generateFromToken($token)
);
} else {
// drop silently
}
}
if (empty($result)) return false;
if ($all_whitespace) return false;
if ($tokens_of_children == $result) return true;
return $result;
}
}

View file

@ -0,0 +1,87 @@
<?php
/**
* Takes the contents of blockquote when in strict and reformats for validation.
*/
class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required
{
protected $real_elements;
protected $fake_elements;
public $allow_empty = true;
public $type = 'strictblockquote';
protected $init = false;
/**
* @note We don't want MakeWellFormed to auto-close inline elements since
* they might be allowed.
*/
public function getNonAutoCloseElements($config) {
$this->init($config);
return $this->fake_elements;
}
public function validateChildren($tokens_of_children, $config, $context) {
$this->init($config);
// trick the parent class into thinking it allows more
$this->elements = $this->fake_elements;
$result = parent::validateChildren($tokens_of_children, $config, $context);
$this->elements = $this->real_elements;
if ($result === false) return array();
if ($result === true) $result = $tokens_of_children;
$def = $config->getHTMLDefinition();
$block_wrap_start = new HTMLPurifier_Token_Start($def->info_block_wrapper);
$block_wrap_end = new HTMLPurifier_Token_End( $def->info_block_wrapper);
$is_inline = false;
$depth = 0;
$ret = array();
// assuming that there are no comment tokens
foreach ($result as $i => $token) {
$token = $result[$i];
// ifs are nested for readability
if (!$is_inline) {
if (!$depth) {
if (
($token instanceof HTMLPurifier_Token_Text && !$token->is_whitespace) ||
(!$token instanceof HTMLPurifier_Token_Text && !isset($this->elements[$token->name]))
) {
$is_inline = true;
$ret[] = $block_wrap_start;
}
}
} else {
if (!$depth) {
// starting tokens have been inline text / empty
if ($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) {
if (isset($this->elements[$token->name])) {
// ended
$ret[] = $block_wrap_end;
$is_inline = false;
}
}
}
}
$ret[] = $token;
if ($token instanceof HTMLPurifier_Token_Start) $depth++;
if ($token instanceof HTMLPurifier_Token_End) $depth--;
}
if ($is_inline) $ret[] = $block_wrap_end;
return $ret;
}
private function init($config) {
if (!$this->init) {
$def = $config->getHTMLDefinition();
// allow all inline elements
$this->real_elements = $this->elements;
$this->fake_elements = $def->info_content_sets['Flow'];
$this->fake_elements['#PCDATA'] = true;
$this->init = true;
}
}
}

Some files were not shown because too many files have changed in this diff Show more