Newer
Older
<?php

Dries Buytaert
committed
/**
* @file
* Tests for filter.module.
*/
/**
* Tests for text format and filter CRUD operations.
*/
class FilterCRUDTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Filter CRUD operations',
'description' => 'Test creation, loading, updating, deleting of text formats and filters.',
'group' => 'Filter',
);
}

Dries Buytaert
committed
function setUp() {
parent::setUp('filter_test');
}
/**
* Test CRUD operations for text formats and filters.
*/
function testTextFormatCRUD() {
// Add a text format with minimum data only.

Dries Buytaert
committed
$format = new stdClass();
$format->format = 'empty_format';
$format->name = 'Empty format';
filter_format_save($format);
$this->verifyTextFormat($format);
$this->verifyFilters($format);
// Add another text format specifying all possible properties.

Dries Buytaert
committed
$format = new stdClass();
$format->format = 'custom_format';
$format->name = 'Custom format';
$format->filters = array(
'filter_url' => array(
'status' => 1,
'settings' => array(
'filter_url_length' => 30,
),
),
);
filter_format_save($format);
$this->verifyTextFormat($format);
$this->verifyFilters($format);
// Alter some text format properties and save again.
$format->name = 'Altered format';
$format->filters['filter_url']['status'] = 0;
$format->filters['filter_autop']['status'] = 1;
filter_format_save($format);
$this->verifyTextFormat($format);
$this->verifyFilters($format);

Dries Buytaert
committed
// Add a uncacheable filter and save again.
$format->filters['filter_test_uncacheable']['status'] = 1;
filter_format_save($format);
$this->verifyTextFormat($format);
$this->verifyFilters($format);

Dries Buytaert
committed
// Disable the text format.
filter_format_disable($format);
$db_format = db_query("SELECT * FROM {filter_format} WHERE format = :format", array(':format' => $format->format))->fetchObject();

Dries Buytaert
committed
$this->assertFalse($db_format->status, t('Database: Disabled text format is marked as disabled.'));
$formats = filter_formats();

Dries Buytaert
committed
$this->assertTrue(!isset($formats[$format->format]), t('filter_formats: Disabled text format no longer exists.'));
}
/**
* Verify that a text format is properly stored.
*/
function verifyTextFormat($format) {
$t_args = array('%format' => $format->name);
// Verify text format database record.
$db_format = db_select('filter_format', 'ff')
->fields('ff')
->condition('format', $format->format)
->execute()
->fetchObject();
$this->assertEqual($db_format->format, $format->format, t('Database: Proper format id for text format %format.', $t_args));
$this->assertEqual($db_format->name, $format->name, t('Database: Proper title for text format %format.', $t_args));
$this->assertEqual($db_format->cache, $format->cache, t('Database: Proper cache indicator for text format %format.', $t_args));
$this->assertEqual($db_format->weight, $format->weight, t('Database: Proper weight for text format %format.', $t_args));
// Verify filter_format_load().
$filter_format = filter_format_load($format->format);
$this->assertEqual($filter_format->format, $format->format, t('filter_format_load: Proper format id for text format %format.', $t_args));
$this->assertEqual($filter_format->name, $format->name, t('filter_format_load: Proper title for text format %format.', $t_args));
$this->assertEqual($filter_format->cache, $format->cache, t('filter_format_load: Proper cache indicator for text format %format.', $t_args));
$this->assertEqual($filter_format->weight, $format->weight, t('filter_format_load: Proper weight for text format %format.', $t_args));

Dries Buytaert
committed
// Verify the 'cache' text format property according to enabled filters.
$filter_info = filter_get_filters();
$filters = filter_list_format($filter_format->format);
$cacheable = TRUE;
foreach ($filters as $name => $filter) {
// If this filter is not cacheable, update $cacheable accordingly, so we
// can verify $format->cache after iterating over all filters.

Dries Buytaert
committed
if ($filter->status && isset($filter_info[$name]['cache']) && !$filter_info[$name]['cache']) {

Dries Buytaert
committed
$cacheable = FALSE;

Dries Buytaert
committed
break;

Dries Buytaert
committed
}
}
$this->assertEqual($filter_format->cache, $cacheable, t('Text format contains proper cache property.'));
}
/**
* Verify that filters are properly stored for a text format.
*/
function verifyFilters($format) {
// Verify filter database records.
$filters = db_query("SELECT * FROM {filter} WHERE format = :format", array(':format' => $format->format))->fetchAllAssoc('name');
$format_filters = $format->filters;
foreach ($filters as $name => $filter) {
$t_args = array('%format' => $format->name, '%filter' => $name);

Dries Buytaert
committed

Dries Buytaert
committed
// Verify that filter status is properly stored.
$this->assertEqual($filter->status, $format_filters[$name]['status'], t('Database: Proper status for %filter in text format %format.', $t_args));

Dries Buytaert
committed

Dries Buytaert
committed
// Verify that filter settings were properly stored.
$this->assertEqual(unserialize($filter->settings), isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), t('Database: Proper filter settings for %filter in text format %format.', $t_args));

Dries Buytaert
committed
// Verify that each filter has a module name assigned.
$this->assertTrue(!empty($filter->module), t('Database: Proper module name for %filter in text format %format.', $t_args));
// Remove the filter from the copy of saved $format to check whether all
// filters have been processed later.
unset($format_filters[$name]);
}

Dries Buytaert
committed
// Verify that all filters have been processed.
$this->assertTrue(empty($format_filters), t('Database contains values for all filters in the saved format.'));
// Verify filter_list_format().

Dries Buytaert
committed
$filters = filter_list_format($format->format);
$format_filters = $format->filters;
foreach ($filters as $name => $filter) {
$t_args = array('%format' => $format->name, '%filter' => $name);

Dries Buytaert
committed

Dries Buytaert
committed
// Verify that filter status is properly stored.
$this->assertEqual($filter->status, $format_filters[$name]['status'], t('filter_list_format: Proper status for %filter in text format %format.', $t_args));

Dries Buytaert
committed

Dries Buytaert
committed
// Verify that filter settings were properly stored.
$this->assertEqual($filter->settings, isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), t('filter_list_format: Proper filter settings for %filter in text format %format.', $t_args));

Dries Buytaert
committed
// Verify that each filter has a module name assigned.
$this->assertTrue(!empty($filter->module), t('filter_list_format: Proper module name for %filter in text format %format.', $t_args));
// Remove the filter from the copy of saved $format to check whether all
// filters have been processed later.
unset($format_filters[$name]);
}

Dries Buytaert
committed
// Verify that all filters have been processed.
$this->assertTrue(empty($format_filters), t('filter_list_format: Loaded filters contain values for all filters in the saved format.'));
}
}
class FilterAdminTestCase extends DrupalWebTestCase {

Angie Byron
committed
public static function getInfo() {
return array(
'name' => 'Filter administration functionality',
'description' => 'Thoroughly test the administrative interface of the filter module.',
'group' => 'Filter',
);
}

Dries Buytaert
committed
function setUp() {
parent::setUp();
// Create users.

Angie Byron
committed
$filtered_html_format = filter_format_load('filtered_html');
$full_html_format = filter_format_load('full_html');

Dries Buytaert
committed
$this->admin_user = $this->drupalCreateUser(array(
'administer filters',

Dries Buytaert
committed
filter_permission_name($filtered_html_format),
filter_permission_name($full_html_format),

Dries Buytaert
committed
));

Dries Buytaert
committed
$this->web_user = $this->drupalCreateUser(array('create page content', 'edit own page content'));
$this->drupalLogin($this->admin_user);
}
function testFormatAdmin() {
// Add text format.
$this->drupalGet('admin/config/content/formats');
$this->clickLink('Add text format');

Angie Byron
committed
$format_id = drupal_strtolower($this->randomName());
$name = $this->randomName();

Dries Buytaert
committed
$edit = array(

Angie Byron
committed
'format' => $format_id,
'name' => $name,

Dries Buytaert
committed
);
$this->drupalPost(NULL, $edit, t('Save configuration'));

Angie Byron
committed
// Verify default weight of the text format.
$this->drupalGet('admin/config/content/formats');
$this->assertFieldByName("formats[$format_id][weight]", 0, t('Text format weight was saved.'));
// Change the weight of the text format.
$edit = array(
"formats[$format_id][weight]" => 5,
);
$this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
$this->assertFieldByName("formats[$format_id][weight]", 5, t('Text format weight was saved.'));

Dries Buytaert
committed
// Edit text format.
$this->drupalGet('admin/config/content/formats');

Angie Byron
committed
$this->assertLinkByHref('admin/config/content/formats/' . $format_id);
$this->drupalGet('admin/config/content/formats/' . $format_id);

Dries Buytaert
committed
$this->drupalPost(NULL, array(), t('Save configuration'));

Angie Byron
committed
// Verify that the custom weight of the text format has been retained.

Dries Buytaert
committed
$this->drupalGet('admin/config/content/formats');

Angie Byron
committed
$this->assertFieldByName("formats[$format_id][weight]", 5, t('Text format weight was retained.'));
// Disable text format.

Angie Byron
committed
$this->assertLinkByHref('admin/config/content/formats/' . $format_id . '/disable');
$this->drupalGet('admin/config/content/formats/' . $format_id . '/disable');

Dries Buytaert
committed
$this->drupalPost(NULL, array(), t('Disable'));

Dries Buytaert
committed

Dries Buytaert
committed
// Verify that disabled text format no longer exists.

Angie Byron
committed
$this->drupalGet('admin/config/content/formats/' . $format_id);

Dries Buytaert
committed
$this->assertResponse(404, t('Disabled text format no longer exists.'));

Angie Byron
committed
// Attempt to create a format of the same machine name as the disabled
// format but with a different human readable name.
$edit = array(

Angie Byron
committed
'format' => $format_id,

Angie Byron
committed
'name' => 'New format',
);
$this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
$this->assertText('The machine-readable name is already in use. It must be unique.');
// Attempt to create a format of the same human readable name as the
// disabled format but with a different machine name.
$edit = array(
'format' => 'new_format',

Angie Byron
committed
'name' => $name,

Angie Byron
committed
);
$this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));

Angie Byron
committed
$this->assertRaw(t('Text format names must be unique. A format named %name already exists.', array(
'%name' => $name,
)));

Dries Buytaert
committed
}
/**
* Test filter administration functionality.
*/
function testFilterAdmin() {

Dries Buytaert
committed
$first_filter = 'filter_url';

Dries Buytaert
committed
$second_filter = 'filter_autop';

Angie Byron
committed
$filtered = 'filtered_html';
$full = 'full_html';
$plain = 'plain_text';

Dries Buytaert
committed
// Check that the fallback format exists and cannot be disabled.

Angie Byron
committed
$this->assertTrue($plain == filter_fallback_format(), t('The fallback format is set to plain text.'));
$this->drupalGet('admin/config/content/formats');

Dries Buytaert
committed
$this->assertNoRaw('admin/config/content/formats/' . $plain . '/disable', t('Disable link for the fallback format not found.'));
$this->drupalGet('admin/config/content/formats/' . $plain . '/disable');
$this->assertResponse(403, t('The fallback format cannot be disabled.'));

Dries Buytaert
committed
// Verify access permissions to Full HTML format.
$this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), t('Admin user may use Full HTML.'));
$this->assertFalse(filter_access(filter_format_load($full), $this->web_user), t('Web user may not use Full HTML.'));
// Add an additional tag.
$edit = array();

Dries Buytaert
committed
$edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <quote>';
$this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
$this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], t('Allowed HTML tag added.'));

Dries Buytaert
committed
$result = db_query('SELECT * FROM {cache_filter}')->fetchObject();
$this->assertFalse($result, t('Cache cleared.'));

Dries Buytaert
committed
$elements = $this->xpath('//select[@name=:first]/following::select[@name=:second]', array(
':first' => 'filters[' . $first_filter . '][weight]',
':second' => 'filters[' . $second_filter . '][weight]',
));
$this->assertTrue(!empty($elements), t('Order confirmed in admin interface.'));

Dries Buytaert
committed
// Reorder filters.
$edit = array();

Dries Buytaert
committed
$edit['filters[' . $second_filter . '][weight]'] = 1;
$edit['filters[' . $first_filter . '][weight]'] = 2;
$this->drupalPost(NULL, $edit, t('Save configuration'));
$this->assertFieldByName('filters[' . $second_filter . '][weight]', 1, t('Order saved successfully.'));
$this->assertFieldByName('filters[' . $first_filter . '][weight]', 2, t('Order saved successfully.'));

Dries Buytaert
committed
$elements = $this->xpath('//select[@name=:first]/following::select[@name=:second]', array(
':first' => 'filters[' . $second_filter . '][weight]',
':second' => 'filters[' . $first_filter . '][weight]',
));
$this->assertTrue(!empty($elements), t('Reorder confirmed in admin interface.'));

Dries Buytaert
committed

Dries Buytaert
committed
$result = db_query('SELECT * FROM {filter} WHERE format = :format ORDER BY weight ASC', array(':format' => $filtered));
$filters = array();

Dries Buytaert
committed
foreach ($result as $filter) {

Dries Buytaert
committed
if ($filter->name == $second_filter || $filter->name == $first_filter) {
$filters[] = $filter;
}
}
$this->assertTrue(($filters[0]->name == $second_filter && $filters[1]->name == $first_filter), t('Order confirmed in database.'));
// Add format.
$edit = array();
$edit['format'] = drupal_strtolower($this->randomName());
$edit['name'] = $this->randomName();

Dries Buytaert
committed
$edit['roles[2]'] = 1;

Dries Buytaert
committed
$edit['filters[' . $second_filter . '][status]'] = TRUE;
$edit['filters[' . $first_filter . '][status]'] = TRUE;
$this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
$this->assertRaw(t('Added text format %format.', array('%format' => $edit['name'])), t('New filter created.'));

Angie Byron
committed
drupal_static_reset('filter_formats');
$format = filter_format_load($edit['format']);
$this->assertNotNull($format, t('Format found in database.'));
$this->assertFieldByName('roles[2]', '', t('Role found.'));
$this->assertFieldByName('filters[' . $second_filter . '][status]', '', t('Line break filter found.'));
$this->assertFieldByName('filters[' . $first_filter . '][status]', '', t('Url filter found.'));

Dries Buytaert
committed
// Disable new filter.
$this->drupalPost('admin/config/content/formats/' . $format->format . '/disable', array(), t('Disable'));
$this->assertRaw(t('Disabled text format %format.', array('%format' => $edit['name'])), t('Format successfully disabled.'));
// Allow authenticated users on full HTML.
$format = filter_format_load($full);
$edit = array();

Dries Buytaert
committed
$edit['roles[1]'] = 0;
$edit['roles[2]'] = 1;
$this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
$this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully updated.'));
// Switch user.
$this->drupalLogout();

Dries Buytaert
committed
$this->drupalLogin($this->web_user);
$this->drupalGet('node/add/page');
$this->assertRaw('<option value="' . $full . '">Full HTML</option>', t('Full HTML filter accessible.'));
// Use filtered HTML and see if it removes tags that are not allowed.
$body = '<em>' . $this->randomName() . '</em>';
$extra_text = 'text';
$text = $body . '<random>' . $extra_text . '</random>';
$edit = array();
$langcode = LANGUAGE_NONE;
$edit["title"] = $this->randomName();
$edit["body[$langcode][0][value]"] = $text;
$edit["body[$langcode][0][format]"] = $filtered;
$this->drupalPost('node/add/page', $edit, t('Save'));
$this->assertRaw(t('Basic page %title has been created.', array('%title' => $edit["title"])), t('Filtered node created.'));
$node = $this->drupalGetNodeByTitle($edit["title"]);
$this->assertTrue($node, t('Node found in database.'));
$this->drupalGet('node/' . $node->nid);
$this->assertRaw($body . $extra_text, t('Filter removed invalid tag.'));
// Use plain text and see if it escapes all tags, whether allowed or not.
$edit = array();
$edit["body[$langcode][0][format]"] = $plain;
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->drupalGet('node/' . $node->nid);
$this->assertText(check_plain($text), t('The "Plain text" text format escapes all HTML tags.'));
// Switch user.
$this->drupalLogout();

Dries Buytaert
committed
$this->drupalLogin($this->admin_user);
// Clean up.
// Allowed tags.
$edit = array();

Dries Buytaert
committed
$edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>';
$this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
$this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], t('Changes reverted.'));
// Full HTML.
$edit = array();
$edit['roles[2]'] = FALSE;
$this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
$this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully reverted.'));
$this->assertFieldByName('roles[2]', $edit['roles[2]'], t('Changes reverted.'));
// Filter order.
$edit = array();

Dries Buytaert
committed
$edit['filters[' . $second_filter . '][weight]'] = 2;
$edit['filters[' . $first_filter . '][weight]'] = 1;
$this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
$this->assertFieldByName('filters[' . $second_filter . '][weight]', $edit['filters[' . $second_filter . '][weight]'], t('Changes reverted.'));
$this->assertFieldByName('filters[' . $first_filter . '][weight]', $edit['filters[' . $first_filter . '][weight]'], t('Changes reverted.'));
}
}

Dries Buytaert
committed
class FilterFormatAccessTestCase extends DrupalWebTestCase {
protected $admin_user;

Angie Byron
committed
protected $filter_admin_user;
protected $web_user;
protected $allowed_format;
protected $disallowed_format;
public static function getInfo() {
return array(

Dries Buytaert
committed
'name' => 'Filter format access',
'description' => 'Tests access to text formats.',
'group' => 'Filter',
);
}
function setUp() {
parent::setUp();

Angie Byron
committed
// Create a user who can administer text formats, but does not have
// specific permission to use any of them.
$this->filter_admin_user = $this->drupalCreateUser(array(

Dries Buytaert
committed
'administer filters',
'create page content',
'edit any page content',
));

Angie Byron
committed
// Create two text formats.
$this->drupalLogin($this->filter_admin_user);
$formats = array();
for ($i = 0; $i < 2; $i++) {
$edit = array(
'format' => drupal_strtolower($this->randomName()),
'name' => $this->randomName(),
);
$this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
$this->resetFilterCaches();

Angie Byron
committed
$formats[] = filter_format_load($edit['format']);
}
list($this->allowed_format, $this->disallowed_format) = $formats;

Angie Byron
committed
$this->drupalLogout();

Dries Buytaert
committed

Angie Byron
committed
// Create a regular user with access to one of the formats.

Dries Buytaert
committed
$this->web_user = $this->drupalCreateUser(array(
'create page content',

Angie Byron
committed
'edit any page content',

Dries Buytaert
committed
filter_permission_name($this->allowed_format),
));

Angie Byron
committed
// Create an administrative user who has access to use both formats.
$this->admin_user = $this->drupalCreateUser(array(
'administer filters',
'create page content',
'edit any page content',
filter_permission_name($this->allowed_format),
filter_permission_name($this->disallowed_format),
));
}
function testFormatPermissions() {
// Make sure that a regular user only has access to the text format they
// were granted access to, as well to the fallback format.
$this->assertTrue(filter_access($this->allowed_format, $this->web_user), t('A regular user has access to a text format they were granted access to.'));
$this->assertFalse(filter_access($this->disallowed_format, $this->web_user), t('A regular user does not have access to a text format they were not granted access to.'));
$this->assertTrue(filter_access(filter_format_load(filter_fallback_format()), $this->web_user), t('A regular user has access to the fallback format.'));
// Perform similar checks as above, but now against the entire list of
// available formats for this user.
$this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_formats($this->web_user))), t('The allowed format appears in the list of available formats for a regular user.'));
$this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_formats($this->web_user))), t('The disallowed format does not appear in the list of available formats for a regular user.'));
$this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_formats($this->web_user))), t('The fallback format appears in the list of available formats for a regular user.'));
// Make sure that a regular user only has permission to use the format
// they were granted access to.
$this->assertTrue(user_access(filter_permission_name($this->allowed_format), $this->web_user), t('A regular user has permission to use the allowed text format.'));
$this->assertFalse(user_access(filter_permission_name($this->disallowed_format), $this->web_user), t('A regular user does not have permission to use the disallowed text format.'));
// Make sure that the allowed format appears on the node form and that
// the disallowed format does not.
$this->drupalLogin($this->web_user);
$this->drupalGet('node/add/page');

Angie Byron
committed
$langcode = LANGUAGE_NONE;
$elements = $this->xpath('//select[@name=:name]/option', array(
':name' => "body[$langcode][0][format]",
':option' => $this->allowed_format->format,
));
$options = array();
foreach ($elements as $element) {
$options[(string) $element['value']] = $element;
}
$this->assertTrue(isset($options[$this->allowed_format->format]), t('The allowed text format appears as an option when adding a new node.'));
$this->assertFalse(isset($options[$this->disallowed_format->format]), t('The disallowed text format does not appear as an option when adding a new node.'));
$this->assertTrue(isset($options[filter_fallback_format()]), t('The fallback format appears as an option when adding a new node.'));
}
function testFormatRoles() {
// Get the role ID assigned to the regular user; it must be the maximum.
$rid = max(array_keys($this->web_user->roles));
// Check that this role appears in the list of roles that have access to an
// allowed text format, but does not appear in the list of roles that have
// access to a disallowed text format.
$this->assertTrue(in_array($rid, array_keys(filter_get_roles_by_format($this->allowed_format))), t('A role which has access to a text format appears in the list of roles that have access to that format.'));
$this->assertFalse(in_array($rid, array_keys(filter_get_roles_by_format($this->disallowed_format))), t('A role which does not have access to a text format does not appear in the list of roles that have access to that format.'));
// Check that the correct text format appears in the list of formats
// available to that role.
$this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role has access to appears in the list of formats available to that role.'));
$this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role does not have access to does not appear in the list of formats available to that role.'));
// Check that the fallback format is always allowed.
$this->assertEqual(filter_get_roles_by_format(filter_format_load(filter_fallback_format())), user_roles(), t('All roles have access to the fallback format.'));
$this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_get_formats_by_role($rid))), t('The fallback format appears in the list of allowed formats for any role.'));
}

Dries Buytaert
committed
/**
* Test editing a page using a disallowed text format.
*

Angie Byron
committed
* Verifies that regular users and administrators are able to edit a page,
* but not allowed to change the fields which use an inaccessible text
* format. Also verifies that fields which use a text format that does not
* exist can be edited by administrators only, but that the administrator is
* forced to choose a new format before saving the page.

Dries Buytaert
committed
*/
function testFormatWidgetPermissions() {
$langcode = LANGUAGE_NONE;
$title_key = "title";
$body_value_key = "body[$langcode][0][value]";
$body_format_key = "body[$langcode][0][format]";
// Create node to edit.
$this->drupalLogin($this->admin_user);
$edit = array();
$edit['title'] = $this->randomName(8);
$edit[$body_value_key] = $this->randomName(16);

Angie Byron
committed
$edit[$body_format_key] = $this->disallowed_format->format;

Dries Buytaert
committed
$this->drupalPost('node/add/page', $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit['title']);
// Try to edit with a less privileged user.

Angie Byron
committed
$this->drupalLogin($this->web_user);

Dries Buytaert
committed
$this->drupalGet('node/' . $node->nid);
$this->clickLink(t('Edit'));
// Verify that body field is read-only and contains replacement value.
$this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.'));

Dries Buytaert
committed
// Verify that title can be changed, but preview displays original body.
$new_edit = array();
$new_edit['title'] = $this->randomName(8);
$this->drupalPost(NULL, $new_edit, t('Preview'));
$this->assertText($edit[$body_value_key], t('Old body found in preview.'));

Dries Buytaert
committed
// Save and verify that only the title was changed.
$this->drupalPost(NULL, $new_edit, t('Save'));
$this->assertNoText($edit['title'], t('Old title not found.'));
$this->assertText($new_edit['title'], t('New title found.'));
$this->assertText($edit[$body_value_key], t('Old body found.'));

Dries Buytaert
committed

Angie Byron
committed
// Check that even an administrator with "administer filters" permission
// cannot edit the body field if they do not have specific permission to
// use its stored format. (This must be disallowed so that the
// administrator is never forced to switch the text format to something
// else.)
$this->drupalLogin($this->filter_admin_user);
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.'));
// Disable the text format used above.
filter_format_disable($this->disallowed_format);

Dries Buytaert
committed
$this->resetFilterCaches();

Angie Byron
committed
// Log back in as the less privileged user and verify that the body field
// is still disabled, since the less privileged user should not be able to
// edit content that does not have an assigned format.
$this->drupalLogin($this->web_user);
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.'));
// Log back in as the filter administrator and verify that the body field
// can be edited.
$this->drupalLogin($this->filter_admin_user);

Dries Buytaert
committed
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertNoFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", NULL, t('Text format access denied message not found.'));
$this->assertFieldByXPath("//select[@name='$body_format_key']", NULL, t('Text format selector found.'));

Angie Byron
committed
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
// Verify that trying to save the node without selecting a new text format
// produces an error message, and does not result in the node being saved.
$old_title = $new_edit['title'];
$new_title = $this->randomName(8);
$edit = array('title' => $new_title);
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertText(t('!name field is required.', array('!name' => t('Text format'))), t('Error message is displayed.'));
$this->drupalGet('node/' . $node->nid);
$this->assertText($old_title, t('Old title found.'));
$this->assertNoText($new_title, t('New title not found.'));
// Now select a new text format and make sure the node can be saved.
$edit[$body_format_key] = filter_fallback_format();
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertUrl('node/' . $node->nid);
$this->assertText($new_title, t('New title found.'));
$this->assertNoText($old_title, t('Old title not found.'));
// Switch the text format to a new one, then disable that format and all
// other formats on the site (leaving only the fallback format).
$this->drupalLogin($this->admin_user);
$edit = array($body_format_key => $this->allowed_format->format);
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertUrl('node/' . $node->nid);
foreach (filter_formats() as $format) {
if ($format->format != filter_fallback_format()) {
filter_format_disable($format);
}
}
// Since there is now only one available text format, the widget for
// selecting a text format would normally not display when the content is
// edited. However, we need to verify that the filter administrator still
// is forced to make a conscious choice to reassign the text to a different
// format.
$this->drupalLogin($this->filter_admin_user);
$old_title = $new_title;
$new_title = $this->randomName(8);
$edit = array('title' => $new_title);
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertText(t('!name field is required.', array('!name' => t('Text format'))), t('Error message is displayed.'));
$this->drupalGet('node/' . $node->nid);
$this->assertText($old_title, t('Old title found.'));
$this->assertNoText($new_title, t('New title not found.'));
$edit[$body_format_key] = filter_fallback_format();
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertUrl('node/' . $node->nid);
$this->assertText($new_title, t('New title found.'));
$this->assertNoText($old_title, t('Old title not found.'));

Dries Buytaert
committed
}
/**
* Rebuild text format and permission caches in the thread running the tests.
*/
protected function resetFilterCaches() {
filter_formats_reset();
$this->checkPermissions(array(), TRUE);
}
}
class FilterDefaultFormatTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Default text format functionality',
'description' => 'Test the default text formats for different users.',
'group' => 'Filter',
);
}

Angie Byron
committed
function testDefaultTextFormats() {
// Create two text formats, and two users. The first user has access to
// both formats, but the second user only has access to the second one.
$admin_user = $this->drupalCreateUser(array('administer filters'));
$this->drupalLogin($admin_user);
$formats = array();
for ($i = 0; $i < 2; $i++) {
$edit = array(
'format' => drupal_strtolower($this->randomName()),
'name' => $this->randomName(),
);
$this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
$this->resetFilterCaches();

Angie Byron
committed
$formats[] = filter_format_load($edit['format']);
}
list($first_format, $second_format) = $formats;
$first_user = $this->drupalCreateUser(array(filter_permission_name($first_format), filter_permission_name($second_format)));
$second_user = $this->drupalCreateUser(array(filter_permission_name($second_format)));
// Adjust the weights so that the first and second formats (in that order)
// are the two lowest weighted formats available to any user.
$minimum_weight = db_query("SELECT MIN(weight) FROM {filter_format}")->fetchField();
$edit = array();
$edit['formats[' . $first_format->format . '][weight]'] = $minimum_weight - 2;
$edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 1;
$this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
$this->resetFilterCaches();
// Check that each user's default format is the lowest weighted format that
// the user has access to.
$this->assertEqual(filter_default_format($first_user), $first_format->format, t("The first user's default format is the lowest weighted format that the user has access to."));
$this->assertEqual(filter_default_format($second_user), $second_format->format, t("The second user's default format is the lowest weighted format that the user has access to, and is different than the first user's."));
// Reorder the two formats, and check that both users now have the same
// default.
$edit = array();
$edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 3;
$this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
$this->resetFilterCaches();
$this->assertEqual(filter_default_format($first_user), filter_default_format($second_user), t('After the formats are reordered, both users have the same default format.'));
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
}
/**
* Rebuild text format and permission caches in the thread running the tests.
*/
protected function resetFilterCaches() {
filter_formats_reset();
$this->checkPermissions(array(), TRUE);
}
}
class FilterNoFormatTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Unassigned text format functionality',
'description' => 'Test the behavior of check_markup() when it is called without a text format.',
'group' => 'Filter',
);
}
function testCheckMarkupNoFormat() {
// Create some text. Include some HTML and line breaks, so we get a good
// test of the filtering that is applied to it.
$text = "<strong>" . $this->randomName(32) . "</strong>\n\n<div>" . $this->randomName(32) . "</div>";
// Make sure that when this text is run through check_markup() with no text
// format, it is filtered as though it is in the fallback format.
$this->assertEqual(check_markup($text), check_markup($text, filter_fallback_format()), t('Text with no format is filtered the same as text in the fallback format.'));
}
}

Dries Buytaert
committed
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
/**
* Security tests for missing/vanished text formats or filters.
*/
class FilterSecurityTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Security',
'description' => 'Test the behavior of check_markup() when a filter or text format vanishes.',
'group' => 'Filter',
);
}
function setUp() {
parent::setUp('php', 'filter_test');
$this->admin_user = $this->drupalCreateUser(array('administer modules', 'administer filters', 'administer site configuration'));
$this->drupalLogin($this->admin_user);
}
/**
* Test that filtered content is emptied when an actively used filter module is disabled.
*/
function testDisableFilterModule() {
// Create a new node.
$node = $this->drupalCreateNode(array('promote' => 1));
$body_raw = $node->body[LANGUAGE_NONE][0]['value'];
$format_id = $node->body[LANGUAGE_NONE][0]['format'];
$this->drupalGet('node/' . $node->nid);
$this->assertText($body_raw, t('Node body found.'));
// Enable the filter_test_replace filter.
$edit = array(
'filters[filter_test_replace][status]' => 1,
);
$this->drupalPost('admin/config/content/formats/' . $format_id, $edit, t('Save configuration'));
// Verify that filter_test_replace filter replaced the content.
$this->drupalGet('node/' . $node->nid);
$this->assertNoText($body_raw, t('Node body not found.'));
$this->assertText('Filter: Testing filter', t('Testing filter output found.'));

Dries Buytaert
committed
// Disable the text format entirely.
$this->drupalPost('admin/config/content/formats/' . $format_id . '/disable', array(), t('Disable'));

Dries Buytaert
committed
// Verify that the content is empty, because the text format does not exist.
$this->drupalGet('node/' . $node->nid);
$this->assertNoText($body_raw, t('Node body not found.'));
}
}
/**
* Unit tests for core filters.
*/
class FilterUnitTestCase extends DrupalUnitTestCase {

Angie Byron
committed
public static function getInfo() {
return array(

Dries Buytaert
committed
'name' => 'Filter module filters',
'description' => 'Tests Filter module filters individually.',
'group' => 'Filter',
);
}
/**
* Test the line break filter.
*/
function testLineBreakFilter() {

Dries Buytaert
committed
// Setup dummy filter object.
$filter = new stdClass();

Dries Buytaert
committed
$filter->callback = '_filter_autop';

Dries Buytaert
committed

Dries Buytaert
committed
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
// Since the line break filter naturally needs plenty of newlines in test
// strings and expectations, we're using "\n" instead of regular newlines
// here.
$tests = array(
// Single line breaks should be changed to <br /> tags, while paragraphs
// separated with double line breaks should be enclosed with <p></p> tags.
"aaa\nbbb\n\nccc" => array(
"<p>aaa<br />\nbbb</p>\n<p>ccc</p>" => TRUE,
),
// Skip contents of certain block tags entirely.
"<script>aaa\nbbb\n\nccc</script>
<style>aaa\nbbb\n\nccc</style>
<pre>aaa\nbbb\n\nccc</pre>
<object>aaa\nbbb\n\nccc</object>
<iframe>aaa\nbbb\n\nccc</iframe>
" => array(
"<script>aaa\nbbb\n\nccc</script>" => TRUE,
"<style>aaa\nbbb\n\nccc</style>" => TRUE,
"<pre>aaa\nbbb\n\nccc</pre>" => TRUE,
"<object>aaa\nbbb\n\nccc</object>" => TRUE,
"<iframe>aaa\nbbb\n\nccc</iframe>" => TRUE,
),
// Skip comments entirely.
"One. <!-- comment --> Two.\n<!--\nThree.\n-->\n" => array(
'<!-- comment -->' => TRUE,
"<!--\nThree.\n-->" => TRUE,
),
// Resulting HTML should produce matching paragraph tags.
'<p><div> </div></p>' => array(
"<p>\n<div> </div>\n</p>" => TRUE,
),
'<div><p> </p></div>' => array(
"<div>\n</div>" => TRUE,
),
'<blockquote><pre>aaa</pre></blockquote>' => array(
"<blockquote><pre>aaa</pre></blockquote>" => TRUE,
),

Dries Buytaert
committed
"<pre>aaa\nbbb\nccc</pre>\nddd\neee" => array(
"<pre>aaa\nbbb\nccc</pre>" => TRUE,
"<p>ddd<br />\neee</p>" => TRUE,
),
// Comments remain unchanged and subsequent lines/paragraphs are
// transformed normally.
"aaa<!--comment-->\n\nbbb\n\nccc\n\nddd<!--comment\nwith linebreak-->\n\neee\n\nfff" => array(
"<p>aaa</p>\n<!--comment--><p>\nbbb</p>\n<p>ccc</p>\n<p>ddd</p>" => TRUE,
"<!--comment\nwith linebreak--><p>\neee</p>\n<p>fff</p>" => TRUE,
),
// Check that a comment in a PRE will result that the text after
// the comment, but still in PRE, is not transformed.
"<pre>aaa\nbbb<!-- comment -->\n\nccc</pre>\nddd" => array(
"<pre>aaa\nbbb<!-- comment -->\n\nccc</pre>" => TRUE,
),
// Bug 810824, paragraphs were appearing around iframe tags.
"<iframe>aaa</iframe>\n\n" => array(
"<p><iframe>aaa</iframe></p>" => FALSE,
),

Dries Buytaert
committed
);
$this->assertFilteredString($filter, $tests);

Dries Buytaert
committed

Dries Buytaert
committed
// Very long string hitting PCRE limits.

Dries Buytaert
committed
$limit = max(ini_get('pcre.backtrack_limit'), ini_get('pcre.recursion_limit'));

Dries Buytaert
committed
$source = $this->randomName($limit);
$result = _filter_autop($source);
$success = $this->assertEqual($result, '<p>' . $source . "</p>\n", t('Line break filter can process very long strings.'));
if (!$success) {
$this->verbose("\n" . $source . "\n<hr />\n" . $result);
}
}
/**

Dries Buytaert
committed
* Tests limiting allowed tags and XSS prevention.

Dries Buytaert
committed
* XSS tests assume that script is disallowed by default and src is allowed
* by default, but on* and style attributes are disallowed.
*
* Script injection vectors mostly adopted from http://ha.ckers.org/xss.html.
*
* Relevant CVEs:
* - CVE-2002-1806, ~CVE-2005-0682, ~CVE-2005-2106, CVE-2005-3973,
* CVE-2006-1226 (= rev. 1.112?), CVE-2008-0273, CVE-2008-3740.

Dries Buytaert
committed
function testFilterXSS() {
// Tag stripping, different ways to work around removal of HTML tags.
$f = filter_xss('<script>alert(0)</script>');
$this->assertNoNormalized($f, 'script', t('HTML tag stripping -- simple script without special characters.'));
$f = filter_xss('<script src="http://www.example.com" />');
$this->assertNoNormalized($f, 'script', t('HTML tag stripping -- empty script with source.'));
$f = filter_xss('<ScRipt sRc=http://www.example.com/>');
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- varying case.'));
$f = filter_xss("<script\nsrc\n=\nhttp://www.example.com/\n>");
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- multiline tag.'));
$f = filter_xss('<script/a src=http://www.example.com/a.js></script>');
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- non whitespace character after tag name.'));
$f = filter_xss('<script/src=http://www.example.com/a.js></script>');
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no space between tag and attribute.'));
// Null between < and tag name works at least with IE6.
$f = filter_xss("<\0scr\0ipt>alert(0)</script>");
$this->assertNoNormalized($f, 'ipt', t('HTML tag stripping evasion -- breaking HTML with nulls.'));
$f = filter_xss("<scrscriptipt src=http://www.example.com/a.js>");
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- filter just removing "script".'));
$f = filter_xss('<<script>alert(0);//<</script>');
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- double opening brackets.'));
$f = filter_xss('<script src=http://www.example.com/a.js?<b>');
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no closing tag.'));
// DRUPAL-SA-2008-047: This doesn't seem exploitable, but the filter should
// work consistently.
$f = filter_xss('<script>>');
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- double closing tag.'));
$f = filter_xss('<script src=//www.example.com/.a>');
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no scheme or ending slash.'));
$f = filter_xss('<script src=http://www.example.com/.a');
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no closing bracket.'));
$f = filter_xss('<script src=http://www.example.com/ <');
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- opening instead of closing bracket.'));
$f = filter_xss('<nosuchtag attribute="newScriptInjectionVector">');
$this->assertNoNormalized($f, 'nosuchtag', t('HTML tag stripping evasion -- unknown tag.'));
$f = filter_xss('<?xml:namespace ns="urn:schemas-microsoft-com:time">');
$this->assertTrue(stripos($f, '<?xml') === FALSE, t('HTML tag stripping evasion -- starting with a question sign (processing instructions).'));
$f = filter_xss('<t:set attributeName="innerHTML" to="<script defer>alert(0)</script>">');
$this->assertNoNormalized($f, 't:set', t('HTML tag stripping evasion -- colon in the tag name (namespaces\' tricks).'));
$f = filter_xss('<img """><script>alert(0)</script>', array('img'));
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- a malformed image tag.'));
$f = filter_xss('<blockquote><script>alert(0)</script></blockquote>', array('blockquote'));
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script in a blockqoute.'));
$f = filter_xss("<!--[if true]><script>alert(0)</script><![endif]-->");
$this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script within a comment.'));
// Dangerous attributes removal.
$f = filter_xss('<p onmouseover="http://www.example.com/">', array('p'));
$this->assertNoNormalized($f, 'onmouseover', t('HTML filter attributes removal -- events, no evasion.'));
$f = filter_xss('<li style="list-style-image: url(javascript:alert(0))">', array('li'));
$this->assertNoNormalized($f, 'style', t('HTML filter attributes removal -- style, no evasion.'));
$f = filter_xss('<img onerror =alert(0)>', array('img'));
$this->assertNoNormalized($f, 'onerror', t('HTML filter attributes removal evasion -- spaces before equals sign.'));
$f = filter_xss('<img onabort!#$%&()*~+-_.,:;?@[/|\]^`=alert(0)>', array('img'));
$this->assertNoNormalized($f, 'onabort', t('HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.'));
$f = filter_xss('<img oNmediAError=alert(0)>', array('img'));
$this->assertNoNormalized($f, 'onmediaerror', t('HTML filter attributes removal evasion -- varying case.'));
// Works at least with IE6.
$f = filter_xss("<img o\0nfocus\0=alert(0)>", array('img'));
$this->assertNoNormalized($f, 'focus', t('HTML filter attributes removal evasion -- breaking with nulls.'));
// Only whitelisted scheme names allowed in attributes.
$f = filter_xss('<img src="javascript:alert(0)">', array('img'));
$this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- no evasion.'));
$f = filter_xss('<img src=javascript:alert(0)>', array('img'));
$this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- no quotes.'));
// A bit like CVE-2006-0070.
$f = filter_xss('<img src="javascript:confirm(0)">', array('img'));
$this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- no alert ;)'));
$f = filter_xss('<img src=`javascript:alert(0)`>', array('img'));
$this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- grave accents.'));
$f = filter_xss('<img dynsrc="javascript:alert(0)">', array('img'));
$this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- rare attribute.'));
$f = filter_xss('<table background="javascript:alert(0)">', array('table'));
$this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- another tag.'));
$f = filter_xss('<base href="javascript:alert(0);//">', array('base'));
$this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- one more attribute and tag.'));
$f = filter_xss('<img src="jaVaSCriPt:alert(0)">', array('img'));
$this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- varying case.'));
$f = filter_xss('<img src=javascript:alert(0)>', array('img'));
$this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- UTF-8 decimal encoding.'));
$f = filter_xss('<img src=javascript:alert(0)>', array('img'));
$this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- long UTF-8 encoding.'));
$f = filter_xss('<img src=javascript:alert(0)>', array('img'));
$this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- UTF-8 hex encoding.'));
$f = filter_xss("<img src=\"jav\tascript:alert(0)\">", array('img'));
$this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an embedded tab.'));