Index: modules/filter/filter.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.api.php,v
retrieving revision 1.13
diff -u -p -r1.13 filter.api.php
--- modules/filter/filter.api.php	27 Aug 2009 21:18:19 -0000	1.13
+++ modules/filter/filter.api.php	12 Sep 2009 20:41:13 -0000
@@ -40,6 +40,10 @@
  *     content before the actual filtering happens.
  *   - 'process callback': The name the function that performs the actual
  *     filtering.
+ * - When a text format is checked to determine if it is configured securely,
+ *   the following properties may be used:
+ *   - 'security callback': The name of a function that determines whether the
+ *     filter allows new exploits or prevents existing ones.
  *
  * Filtering is a two-step process. First, the content is 'prepared' by calling
  * the 'prepare callback' function for every filter. The purpose of the 'prepare
@@ -64,6 +68,14 @@
  * format is an entire filter setup: which filters to enable, in what order
  * and with what settings.
  *
+ * Different filters can introduce or remove security vulnerabilities in the
+ * text formats that contain them, depending on how they are configured.
+ * Filters that have implications for security should therefore use the
+ * 'security callback' function to provide information about whether they are
+ * configured securely. The filtering system will collect this information and
+ * use it to automatically determine whether the text format as a whole is
+ * secure.
+ *
  * Filters that require settings should provide the form controls to configure
  * the settings in a form builder function, specified in 'settings callback'.
  * The filter system stores the settings in the database per text format.
@@ -122,6 +134,56 @@
  * - $format: The format object of the text to be filtered.
  * - $long: Boolean whether to return long or short filter guidelines.
  *
+ * 'security callback' is invoked with the following parameters:
+ * - $filter: An object representing the filter within the text format that is
+ *   being checked. The most important property is $filter->settings, which
+ *   contains an array of settings for this filter as it is configured within
+ *   the current text format. The security callback function should inspect
+ *   these settings if they are relevant for determining when the filter is
+ *   configured securely.
+ * - &$warnings: An array which the security callback function should fill with
+ *   warning messages in one of two cases: (1) If the filter opens up a new
+ *   exploit in a previously safe text format, or (2) If the filter could have
+ *   specifically prevented an exploit if it had been configured in a different
+ *   way. The warning messages should be keyed by the type of output in which
+ *   the potential vulnerability exists. For example, use $warnings['html'][]
+ *   for an exploit that occurs when content that passes through the text
+ *   format is output as HTML (e.g., cross-site scripting exploits). This is
+ *   the most common type of output, but others are possible; for example, the
+ *   PHP module uses $warnings['php'][] to indicate that the PHP filter
+ *   contains an inherent vulnerability due to the fact that content which
+ *   passes through it is executed as PHP code.
+ *
+ * The return value of the 'security callback' function is an optional array
+ * representing the effect of the filter on each type of output. The constant
+ * FILTER_EXPLOIT_PREVENTS should be used if the filter prevents security
+ * exploits by sanitizing previously-unsafe content; for example, a filter that
+ * escapes HTML would return array('html' => FILTER_EXPLOIT_PREVENTS). The
+ * constant FILTER_EXPLOIT_ALLOWS should be used if the filter allows a new
+ * security exploit, even if the content passed in to the filter was previously
+ * sanitized. For example, a filter that evaluates PHP code would return
+ * array('php' => FILTER_EXPLOIT_ALLOWS). If the security callback function
+ * does not include a particular type of output in the returned array, this is
+ * taken to mean that the filter does not affect the security of this type of
+ * output one way or the other; unsanitized content passed in to the filter
+ * remains unsanitized after the filter is applied, and sanitized content
+ * passed in to the filter similarly remains sanitized.
+ *
+ * @code
+ *   function mymodule_check_security($filter, &$warnings) {
+ *     if (empty($filter->settings['remove_unsafe_html'])) {
+ *       $warnings['html'][] = t('The "mymodule" filter is configured to allow unsafe HTML. Either select the "remove unsafe HTML" option in the configuration settings for this filter, or add another filter after it that separately removes unsafe HTML.');
+ *     }
+ *     else {
+ *       // The "remove unsafe HTML" configuration option was selected, so this
+ *       // filter prevents HTML exploits.
+ *       return array('html' => FILTER_EXPLOIT_PREVENTS);
+ *     }
+ *   }
+ * @endcode
+ *
+ * @see filter_format_is_secure()
+ *
  * For performance reasons content is only filtered once; the result is stored
  * in the cache table and retrieved from the cache the next time the same piece
  * of content is displayed. If a filter's output is dynamic, it can override the
@@ -145,6 +207,9 @@
  *     of the filtering.
  *   - 'process callback': (required) The callback function to call in the
  *     'process' step of the filtering.
+ *   - 'security callback': A callback function that determines whether the
+ *     filter is configured to allow new security exploits or prevent existing
+ *     ones.
  *   - 'settings callback': A callback function that provides form controls
  *     for the filter's settings. Each filter should apply either the default
  *     settings or the configured settings contained in $filter->settings. The
@@ -166,6 +231,7 @@ function hook_filter_info() {
     'title' => t('Limit allowed HTML tags'),
     'description' => t('Allows you to restrict the HTML tags the user can use. It will also remove harmful content such as JavaScript events, JavaScript URLs and CSS styles from those tags that are not removed.'),
     'process callback' => '_filter_html',
+    'security callback' => '_filter_html_check_security',
     'settings callback' => '_filter_html_settings',
     'default settings' => array(
       'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>',
Index: modules/filter/filter.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v
retrieving revision 1.289
diff -u -p -r1.289 filter.module
--- modules/filter/filter.module	12 Sep 2009 06:09:45 -0000	1.289
+++ modules/filter/filter.module	12 Sep 2009 20:41:13 -0000
@@ -15,6 +15,18 @@
 define('FILTER_FORMAT_DEFAULT', 0);
 
 /**
+ * Status which indicates that a filter prevents security exploits. This should
+ * be used by filters that sanitize previously-unsafe content.
+ */
+define('FILTER_EXPLOIT_PREVENTS', 1);
+
+/**
+ * Status which indicates that a filter allows a new security exploit, even if
+ * the content passed in to the filter was previously sanitized.
+ */
+define('FILTER_EXPLOIT_ALLOWS', 2);
+
+/**
  * Implement hook_help().
  */
 function filter_help($path, $arg) {
@@ -187,8 +199,14 @@ function filter_format_save($format) {
   $filters = $format->filters;
   foreach ($filters as $name => $filter) {
     $fields = array();
-    // Add new filters to the bottom.
-    $fields['weight'] = isset($current[$name]->weight) ? $current[$name]->weight : 10;
+    // If a specific weight was requested, use that.
+    if (isset($filter['weight'])) {
+      $fields['weight'] = $filter['weight'];
+    }
+    // Otherwise add new filters to the bottom.
+    else {
+      $fields['weight'] = isset($current[$name]->weight) ? $current[$name]->weight : 10;
+    }
     $fields['status'] = $filter['status'];
     // Only update settings if there are any.
     if (!empty($filter['settings'])) {
@@ -537,6 +555,82 @@ function filter_list_format($format, $in
 }
 
 /**
+ * Determines if a text format is configured securely.
+ *
+ * @param $format
+ *   The ID of the format to check.
+ * @param $warnings
+ *   An array which will be filled with warning messages if the format is
+ *   insecure, explaining the reasons why it is insecure. The keys of the
+ *   array represent types of output in which a security vulnerability exists,
+ *   and the values are arrays of messages describing the vulnerability for
+ *   that output type. Possible keys include 'html' (if content that passes
+ *   through the text format will be insecure when output as HTML) and 'php'
+ *   (if content that passes through the text format will be executed as PHP
+ *   code).
+ * @return
+ *   TRUE if the format is configured securely, FALSE otherwise.
+ */
+function filter_format_is_secure($format, &$warnings) {
+  // Start with a blank slate of warning messages.
+  $warnings = array();
+
+  // All text formats start off as insecure, because unfiltered HTML is by
+  // definition not safe.
+  $format_status = array('html' => FILTER_EXPLOIT_ALLOWS);
+
+  // Go through each filter and execute its security callback function,
+  // modifying the overall state of the text format as appropriate.
+  $filters = filter_list_format($format);
+  $filter_info = filter_get_filters();
+  foreach ($filters as $name => $filter) {
+    if (isset($filter_info[$name]['security callback']) && function_exists($filter_info[$name]['security callback'])) {
+      // Check if the callback function returned any security status or
+      // warnings associated with this filter.
+      $filter_warnings = array();
+      $callback_function = $filter_info[$name]['security callback'];
+      $filter_status = $callback_function($filter, $filter_warnings);
+      if (!empty($filter_status) || !empty($filter_warnings)) {
+        // Merge in the results of the callback function, so that if the filter
+        // either explicitly allows or explicitly prevents exploits for a
+        // particular type of output, that result becomes the new, temporary
+        // status for the text format as a whole.
+        if (!empty($filter_status)) {
+          $format_status = array_merge($format_status, $filter_status);
+        }
+        // Merge in any warnings that this filter added for each type of
+        // output.
+        if (!empty($filter_warnings)) {
+          $warnings = array_merge_recursive($warnings, $filter_warnings);
+        }
+        // Clear out all warning messages except those for output types where
+        // exploits are currently allowed.
+        foreach (array_keys($warnings) as $type) {
+          if (!isset($format_status[$type]) || $format_status[$type] == FILTER_EXPLOIT_PREVENTS) {
+            unset($warnings[$type]);
+          }
+        }
+      }
+    }
+  }
+
+  // If the text format allows HTML exploits but no warning messages have been
+  // added, use a default message that is appropriate for unfiltered HTML. This
+  // covers the case where no HTML filter has been applied at all.
+  if ($format_status['html'] == FILTER_EXPLOIT_ALLOWS && empty($warnings['html'])) {
+    $warnings['html'][] = t('No filter has been added to sanitize raw HTML.');
+  }
+
+  // Return FALSE (the format is insecure) if exploits of any kind are allowed.
+  foreach ($format_status as $status) {
+    if ($status != FILTER_EXPLOIT_PREVENTS) {
+      return FALSE;
+    }
+  }
+  return TRUE;
+}
+
+/**
  * @name Filtering functions
  * @{
  * Modules which need to have content filtered can use these functions to
@@ -768,6 +862,18 @@ function filter_dom_serialize($dom_docum
 }
 
 /**
+ * Splits a string containing HTML tags into an array of tag names.
+ *
+ * @param $tag_string
+ *   A string containing HTML tags; for example, '<a> <em> <strong>'.
+ * @return
+ *   An array of tag names; for example, array('a', 'em', 'strong').
+ */
+function _filter_split_tags($tag_string) {
+  return preg_split('/\s+|<|>/', $tag_string, -1, PREG_SPLIT_NO_EMPTY);
+}
+
+/**
  * Format a link to the more extensive filter tips.
  *
  * @ingroup themeable
@@ -800,6 +906,7 @@ function filter_filter_info() {
     'title' => t('Limit allowed HTML tags'),
     'description' => t('Allows you to restrict the HTML tags the user can use. It will also remove harmful content such as JavaScript events, JavaScript URLs and CSS styles from those tags that are not removed.'),
     'process callback' => '_filter_html',
+    'security callback' => '_filter_html_check_security',
     'settings callback' => '_filter_html_settings',
     'default settings' => array(
       'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>',
@@ -833,6 +940,7 @@ function filter_filter_info() {
     'title' => t('Escape all HTML'),
     'description' => t('Escapes all HTML tags, so they will be visible instead of being effective.'),
     'process callback' => '_filter_html_escape',
+    'security callback' => '_filter_html_escape_check_security',
     'tips callback' => '_filter_html_escape_tips',
   );
   return $filters;
@@ -866,10 +974,30 @@ function _filter_html_settings(&$form_st
 }
 
 /**
+ * Security callback for the HTML filter.
+ */
+function _filter_html_check_security($filter, &$warnings) {
+  $allowed_tags = _filter_split_tags($filter->settings['allowed_html']);
+  $dangerous_tags = array('iframe', 'object', 'embed', 'script', 'style'); // Obviously we can expand this later!
+  $tags = array_intersect($dangerous_tags, $allowed_tags);
+  if (empty($tags)) {
+    // If the filter does not allow any dangerous tags, that means it is
+    // configured to prevent HTML exploits.
+    return array('html' => FILTER_EXPLOIT_PREVENTS);
+  }
+  else {
+    // Otherwise, generate a useful warning for the case where this filter is
+    // being used in an already insecure text format, to explain how it could
+    // have been made safe.
+    $warnings['html'][] = t('The HTML filter is configured to allow the following dangerous tags: %tags', array('%tags' => implode(', ', $tags)));
+  }
+}
+
+/**
  * HTML filter. Provides filtering of input into accepted HTML.
  */
 function _filter_html($text, $filter) {
-  $allowed_tags = preg_split('/\s+|<|>/', $filter->settings['allowed_html'], -1, PREG_SPLIT_NO_EMPTY);
+  $allowed_tags = _filter_split_tags($filter->settings['allowed_html']);
   $text = filter_xss($text, $allowed_tags);
 
   if ($filter->settings['filter_html_nofollow']) {
@@ -1032,6 +1160,15 @@ function _filter_html_escape($text) {
 }
 
 /**
+ * Security callback for the HTML escaping filter.
+ */
+function _filter_html_escape_check_security($filter, &$warnings) {
+  // Because this filter escapes all HTML, it prevents any HTML-related
+  // exploits from occurring.
+  return array('html' => FILTER_EXPLOIT_PREVENTS);
+}
+
+/**
  * @} End of "Standard filters".
  */
 
Index: modules/filter/filter.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.test,v
retrieving revision 1.41
diff -u -p -r1.41 filter.test
--- modules/filter/filter.test	12 Sep 2009 06:09:45 -0000	1.41
+++ modules/filter/filter.test	12 Sep 2009 20:41:13 -0000
@@ -1,7 +1,41 @@
 <?php
 // $Id: filter.test,v 1.41 2009/09/12 06:09:45 dries Exp $
 
-class FilterAdminTestCase extends DrupalWebTestCase {
+class FilterTestCase extends DrupalWebTestCase {
+  /**
+   * Helper method for creating a new text format.
+   *
+   * @param $filters
+   *   An ordered array of filters to enable for the text format.
+   * @param $filter_settings
+   *   An array of optional filter settings, keyed by the filter they will be
+   *   applied to. 
+   * @return
+   *   The new text format object.
+   */
+  function createTextFormat($filters = array(), $filter_settings = array()) {
+    $format = new stdClass();
+    $format->name = $this->randomName();
+    $format->roles = array();
+    $format->filters = array();
+    $weight = 0;
+    foreach ($filters as $filter) {
+      // Enable each filter, set its weight, and add any requested settings.
+      $format->filters[$filter] = array(
+        'status' => 1,
+        'weight' => $weight,
+      );
+      if (isset($filter_settings[$filter])) {
+        $format->filters[$filter]['settings'] = $filter_settings[$filter];
+      }
+      $weight++;
+    }
+    filter_format_save($format);
+    return $format;
+  }
+}
+
+class FilterAdminTestCase extends FilterTestCase {
   public static function getInfo() {
     return array(
       'name' => 'Filter administration functionality',
@@ -191,7 +225,7 @@ class FilterAdminTestCase extends Drupal
 /**
  * Unit tests for core filters.
  */
-class FilterUnitTestCase extends DrupalWebTestCase {
+class FilterUnitTestCase extends FilterTestCase {
   public static function getInfo() {
     return array(
       'name' => 'Core filters',
@@ -769,9 +803,83 @@ class FilterUnitTestCase extends DrupalW
 }
 
 /**
+ * Tests for the text format security API.
+ */
+class FilterFormatSecurityTestCase extends FilterTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Text format security',
+      'description' => 'Test the API for determining whether text formats are secure.',
+      'group' => 'Filter',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('filter_test');
+  }
+
+  /**
+   * Test that text formats are labeled secure or insecure, as appropriate.
+   */
+  function testFormatSecurity() {
+    // Test a text format with no filters; it should be labeled insecure.
+    $format = $this->createTextFormat();
+    $this->assertFalse(filter_format_is_secure($format->format, $warnings), t('A text format with no filters is labeled insecure.'));
+    $this->assertEqual(count($warnings['html']), 1, t('A text format with no filters has an associated warning message about insecure HTML.'));
+
+    // Use the HTML filter with default settings; the text format should be
+    // labeled secure.
+    $format = $this->createTextFormat(array('filter_html'));
+    $this->assertTrue(filter_format_is_secure($format->format, $warnings), t('A text format with the default HTML filter is labeled secure.'));
+    $this->assertEqual(count($warnings), 0, t('A text format with the default HTML filter has no associated warning messages.'));
+
+    // Configure the HTML filter to allow an unsafe tag; the text format should
+    // be labeled insecure.
+    $filter_html_unsafe_settings = array('allowed_html' => '<a> <em> <script> <strong>');
+    $format = $this->createTextFormat(array('filter_html'), array('filter_html' => $filter_html_unsafe_settings));
+    $this->assertFalse(filter_format_is_secure($format->format, $warnings), t('A text format that allows script tags is labeled insecure.'));
+    $this->assertEqual(count($warnings['html']), 1, t('A text format that allows script tags has an associated warning message about insecure HTML.'));
+
+    // Still configure the HTML filter to allow an unsafe tag, but sanitize the
+    // input beforehand by escaping all HTML; the text format should be labeled
+    // secure.
+    $format = $this->createTextFormat(array('filter_html_escape', 'filter_html'), array('filter_html' => $filter_html_unsafe_settings));
+    $this->assertTrue(filter_format_is_secure($format->format, $warnings), t('A text format that escapes HTML before incompletely filtering it is labeled secure.'));
+    $this->assertEqual(count($warnings), 0, t('A text format that escapes HTML before incompletely filtering it has no associated warning messages.'));
+
+    // Do the same thing as above, but escape HTML afterwards instead; the text
+    // format should also be labeled secure.
+    $format = $this->createTextFormat(array('filter_html', 'filter_html_escape'), array('filter_html' => $filter_html_unsafe_settings));
+    $this->assertTrue(filter_format_is_secure($format->format, $warnings), t('A text format that escapes HTML after incompletely filtering it is labeled secure.'));
+    $this->assertEqual(count($warnings), 0, t('A text format that escapes HTML after incompletely filtering it has no associated warning messages.'));
+
+    // Escape all HTML, but after doing so, apply a filter that creates new
+    // HTML-related security vulnerabilities even in previously sanitized
+    // content; the text format should be labeled insecure.
+    $format = $this->createTextFormat(array('filter_html_escape', 'filter_test_create_html_exploit'));
+    $this->assertFalse(filter_format_is_secure($format->format, $warnings), t('A text format that escapes HTML but then allows a new HTML-related security exploit afterwards is labeled insecure.'));
+    $this->assertEqual(count($warnings['html']), 1, t('A text format that escapes HTML but then allows a new HTML-related security exploit afterwards has an associated warning message about insecure HTML.'));
+
+    // Do the same thing as above, but now apply the HTML filter at the end,
+    // and once again configure this filter to allow an unsafe tag; the text
+    // format should be labeled insecure, and warning messages from both of the
+    // last two filters should be present.
+    $format = $this->createTextFormat(array('filter_html_escape', 'filter_test_create_html_exploit', 'filter_html'), array('filter_html' => $filter_html_unsafe_settings));
+    $this->assertFalse(filter_format_is_secure($format->format, $warnings), t('A text format that incompletely filters HTML after allowing a new HTML-related security exploit is labeled insecure.'));
+    $this->assertEqual(count($warnings['html']), 2, t('A text format that incompletely filters HTML after allowing a new HTML-related security exploit has two associated warning messages about insecure HTML.'));
+
+    // Do the same thing as above, but now configure the final HTML filter
+    // using the default settings; the text format should be labeled secure.
+    $format = $this->createTextFormat(array('filter_html_escape', 'filter_test_create_html_exploit', 'filter_html'));
+    $this->assertTrue(filter_format_is_secure($format->format, $warnings), t('A text format that correctly filters HTML after allowing a new HTML-related security exploit is labeled secure.'));
+    $this->assertEqual(count($warnings), 0, t('A text format that correctly filters HTML after allowing a new HTML-related security exploit has no associated warning messages.'));
+  }
+}
+
+/**
  * Tests for filter hook invocation.
  */
-class FilterHooksTestCase extends DrupalWebTestCase {
+class FilterHooksTestCase extends FilterTestCase {
   public static function getInfo() {
     return array(
       'name' => 'Filter format hooks',
Index: modules/php/php.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/php/php.module,v
retrieving revision 1.20
diff -u -p -r1.20 php.module
--- modules/php/php.module	27 Aug 2009 21:18:19 -0000	1.20
+++ modules/php/php.module	12 Sep 2009 20:41:13 -0000
@@ -127,9 +127,18 @@ function php_filter_info() {
     'title' => t('PHP evaluator'),
     'description' => t('Executes a piece of PHP code. The usage of this filter should be restricted to administrators only!'),
     'process callback' => 'php_eval',
+    'security callback' => '_php_check_security',
     'tips callback' => '_php_filter_tips',
     'cache' => FALSE,
   );
   return $filters;
 }
 
+/**
+ * Security callback for the PHP filter.
+ */
+function _php_check_security($filter, &$warnings) {
+  // Any format using this filter is insecure.
+  $warnings['php'][] = t('The PHP filter allows users to execute arbitrary PHP code on your site.');
+  return array('php' => FILTER_EXPLOIT_ALLOWS);
+}
Index: modules/php/php.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/php/php.test,v
retrieving revision 1.16
diff -u -p -r1.16 php.test
--- modules/php/php.test	28 Aug 2009 16:23:04 -0000	1.16
+++ modules/php/php.test	12 Sep 2009 20:41:13 -0000
@@ -4,7 +4,7 @@
 /**
  * Base PHP test case class.
  */
-class PHPTestCase extends DrupalWebTestCase {
+class PHPTestCase extends FilterTestCase {
   function setUp() {
     parent::setUp('php');
 
@@ -101,3 +101,36 @@ class PHPAccessTestCase extends PHPTestC
     $this->assertNoFieldByName('body_format', '3', t('Format not available.'));
   }
 }
+
+/**
+ * Test that text formats with the PHP filter are always labeled as insecure.
+ */
+class PHPFilterSecurityTestCase extends PHPTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'PHP filter security',
+      'description' => 'Test that text formats containing the PHP filter are always labeled as insecure.',
+      'group' => 'PHP',
+    );
+  }
+
+  /**
+   * Test that the PHP filter is correctly labeled as insecure.
+   */
+  function testPHPFilterSecurity() {
+    // Test a text format with the PHP filter only; it should be labeled
+    // insecure for both HTML and PHP exploits.
+    $format = $this->createTextFormat(array('php_code'));
+    $this->assertFalse(filter_format_is_secure($format->format, $warnings), t('A text format with the PHP filter is labeled insecure.'));
+    $this->assertEqual(count($warnings['php']), 1, t('A text format with the PHP filter has an associated warning message about PHP code execution.'));
+    $this->assertEqual(count($warnings['html']), 1, t('A text format with the PHP filter has an associated warning message about insecure HTML.'));
+
+    // Create a new format that is the same as the previous one, but escapes
+    // all HTML afterwards; the text format should still be labeled insecure
+    // for PHP exploits, but not for HTML exploits.
+    $format = $this->createTextFormat(array('php_code', 'filter_html_escape'));
+    $this->assertFalse(filter_format_is_secure($format->format, $warnings), t('A text format with the PHP filter that also escapes HTML is still labeled insecure.'));
+    $this->assertEqual(count($warnings['php']), 1, t('A text format with the PHP filter that also escapes HTML still has an associated warning message about PHP code execution.'));
+    $this->assertTrue(empty($warnings['html']), t('A text format with the PHP filter that also escapes HTML has no associated warning messages about insecure HTML.'));
+  }
+}
Index: modules/simpletest/tests/filter_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/filter_test.module,v
retrieving revision 1.2
diff -u -p -r1.2 filter_test.module
--- modules/simpletest/tests/filter_test.module	27 Aug 2009 21:18:20 -0000	1.2
+++ modules/simpletest/tests/filter_test.module	12 Sep 2009 20:41:13 -0000
@@ -7,6 +7,39 @@
  */
 
 /**
+ * Implement hook_filter_info().
+ */
+function filter_test_filter_info() {
+  $filters['filter_test_create_html_exploit'] = array(
+    'title' => t('Create an HTML exploit'),
+    'description' => t('This filter intentionally creates a new HTML-related security vulnerability, for the purpose of testing the text format security API.'),
+    'process callback' => '_filter_test_create_html_exploit',
+    'security callback' => '_filter_test_create_html_exploit_check_security',
+  );
+  return $filters;
+}
+
+/**
+ * Filters text in such a way that a new HTML exploit is intentionally created.
+ *
+ * This filter is used for testing the text format security API, and it is an
+ * example of a filter that creates a new HTML-related security exploit by
+ * allowing safe text, such as "[script]", to be converted to an unsafe
+ * "<script>" HTML tag.
+ */
+function _filter_test_create_html_exploit($text) {
+  return str_replace(array('[', ']'), array('<', '>'), $text);
+}
+
+/**
+ * Security callback for the test HTML exploit filter.
+ */
+function _filter_test_create_html_exploit_check_security($filter, &$warnings) {
+  $warnings['html'][] = t('The HTML exploit filter deliberately creates a new HTML-related security vulnerability, even if the content passed in to the filter was previously sanitized.');
+  return array('html' => FILTER_EXPLOIT_ALLOWS);
+}
+
+/**
  * Implement hook_filter_format_insert().
  */
 function filter_test_filter_format_insert($format) {
