/[drupal]/drupal/modules/filter/filter.test
ViewVC logotype

Contents of /drupal/modules/filter/filter.test

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.46 - (show annotations) (download) (as text)
Tue Oct 13 15:39:41 2009 UTC (6 weeks, 3 days ago) by dries
Branch: MAIN
CVS Tags: DRUPAL-7-0-UNSTABLE-10
Changes since 1.45: +12 -12 lines
File MIME type: text/x-php
- Patch #582378 by sun: more filter system clean-up.
1 <?php
2 // $Id: filter.test,v 1.45 2009/10/11 03:07:18 webchick Exp $
3
4 /**
5 * Tests for text format and filter CRUD operations.
6 */
7 class FilterCRUDTestCase extends DrupalWebTestCase {
8 public static function getInfo() {
9 return array(
10 'name' => 'Filter CRUD operations',
11 'description' => 'Test creation, loading, updating, deleting of text formats and filters.',
12 'group' => 'Filter',
13 );
14 }
15
16 /**
17 * Test CRUD operations for text formats and filters.
18 */
19 function testTextFormatCRUD() {
20 // Add a text format with minimum data only.
21 $format = new stdClass;
22 $format->name = 'Empty format';
23 filter_format_save($format);
24 $this->verifyTextFormat($format);
25 $this->verifyFilters($format);
26
27 // Add another text format specifying all possible properties.
28 $format = new stdClass;
29 $format->name = 'Custom format';
30 $format->filters = array(
31 'filter_url' => array(
32 'status' => 1,
33 'settings' => array(
34 'filter_url_length' => 30,
35 ),
36 ),
37 );
38 filter_format_save($format);
39 $this->verifyTextFormat($format);
40 $this->verifyFilters($format);
41
42 // Alter some text format properties and save again.
43 $format->name = 'Altered format';
44 $format->filters['filter_url']['status'] = 0;
45 $format->filters['filter_autop']['status'] = 1;
46 filter_format_save($format);
47 $this->verifyTextFormat($format);
48 $this->verifyFilters($format);
49
50 // Delete the text format.
51 filter_format_delete($format);
52 $db_format = db_query("SELECT * FROM {filter_format} WHERE format = :format", array(':format' => $format->format))->fetchObject();
53 $this->assertFalse($db_format, t('Database: Deleted text format no longer exists.'));
54 $db_filters = db_query("SELECT * FROM {filter} WHERE format = :format", array(':format' => $format->format))->fetchObject();
55 $this->assertFalse($db_filters, t('Database: Filters for deleted text format no longer exist.'));
56 $formats = filter_formats();
57 $this->assertTrue(!isset($formats[$format->format]), t('filter_formats: Deleted text format no longer exists.'));
58 }
59
60 /**
61 * Verify that a text format is properly stored.
62 */
63 function verifyTextFormat($format) {
64 $t_args = array('%format' => $format->name);
65 // Verify text format database record.
66 $db_format = db_select('filter_format', 'ff')
67 ->fields('ff')
68 ->condition('format', $format->format)
69 ->execute()
70 ->fetchObject();
71 $this->assertEqual($db_format->format, $format->format, t('Database: Proper format id for text format %format.', $t_args));
72 $this->assertEqual($db_format->name, $format->name, t('Database: Proper title for text format %format.', $t_args));
73 $this->assertEqual($db_format->cache, $format->cache, t('Database: Proper cache indicator for text format %format.', $t_args));
74 $this->assertEqual($db_format->weight, $format->weight, t('Database: Proper weight for text format %format.', $t_args));
75
76 // Verify filter_format_load().
77 $filter_format = filter_format_load($format->format);
78 $this->assertEqual($filter_format->format, $format->format, t('filter_format_load: Proper format id for text format %format.', $t_args));
79 $this->assertEqual($filter_format->name, $format->name, t('filter_format_load: Proper title for text format %format.', $t_args));
80 $this->assertEqual($filter_format->cache, $format->cache, t('filter_format_load: Proper cache indicator for text format %format.', $t_args));
81 $this->assertEqual($filter_format->weight, $format->weight, t('filter_format_load: Proper weight for text format %format.', $t_args));
82 }
83
84 /**
85 * Verify that filters are properly stored for a text format.
86 */
87 function verifyFilters($format) {
88 // Verify filter database records.
89 $filters = db_query("SELECT * FROM {filter} WHERE format = :format", array(':format' => $format->format))->fetchAllAssoc('name');
90 $format_filters = $format->filters;
91 foreach ($filters as $name => $filter) {
92 $t_args = array('%format' => $format->name, '%filter' => $name);
93 // Check whether the filter is contained in the saved $format.
94 if (isset($format_filters[$name])) {
95 // Verify that filter status is properly stored.
96 $this->assertEqual($filter->status, $format_filters[$name]['status'], t('Database: Proper status for %filter in text format %format.', $t_args));
97 // Verify that filter settings were properly stored.
98 $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));
99 }
100 // Otherwise, the stored filter must be disabled.
101 else {
102 $this->assertTrue($filter->status == 0, t('Database: Proper status for disabled %filter in text format %format.', $t_args));
103 }
104 // Verify that each filter has a module name assigned.
105 $this->assertTrue(!empty($filter->module), t('Database: Proper module name for %filter in text format %format.', $t_args));
106
107 // Remove the filter from the copy of saved $format to check whether all
108 // filters have been processed later.
109 unset($format_filters[$name]);
110 }
111 $this->assertTrue(empty($format_filters), t('Database contains values for all filters in the saved format.'));
112
113 // Verify filter_list_format().
114 $filters = filter_list_format($format->format, TRUE);
115 $format_filters = $format->filters;
116 foreach ($filters as $name => $filter) {
117 $t_args = array('%format' => $format->name, '%filter' => $name);
118 // Check whether the filter is contained in the saved $format.
119 if (isset($format_filters[$name])) {
120 // Verify that filter status is properly stored.
121 $this->assertEqual($filter->status, $format_filters[$name]['status'], t('filter_list_format: Proper status for %filter in text format %format.', $t_args));
122 // Verify that filter settings were properly stored.
123 $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));
124 }
125 // Otherwise, the stored filter must be disabled.
126 else {
127 $this->assertTrue($filter->status == 0, t('filter_list_format: Proper status for disabled %filter in text format %format.', $t_args));
128 }
129 // Verify that each filter has a module name assigned.
130 $this->assertTrue(!empty($filter->module), t('filter_list_format: Proper module name for %filter in text format %format.', $t_args));
131
132 // Remove the filter from the copy of saved $format to check whether all
133 // filters have been processed later.
134 unset($format_filters[$name]);
135 }
136 $this->assertTrue(empty($format_filters), t('filter_list_format: Loaded filters contain values for all filters in the saved format.'));
137 }
138 }
139
140 class FilterAdminTestCase extends DrupalWebTestCase {
141 public static function getInfo() {
142 return array(
143 'name' => 'Filter administration functionality',
144 'description' => 'Thoroughly test the administrative interface of the filter module.',
145 'group' => 'Filter',
146 );
147 }
148
149 function setUp() {
150 parent::setUp();
151
152 // Create users.
153 $this->admin_user = $this->drupalCreateUser(array('administer filters'));
154 $this->web_user = $this->drupalCreateUser(array('create page content', 'edit own page content'));
155 $this->drupalLogin($this->admin_user);
156 }
157
158 function testFormatAdmin() {
159 // Add text format.
160 $this->drupalGet('admin/config/content/formats');
161 $this->clickLink('Add text format');
162 $edit = array(
163 'name' => $this->randomName(),
164 );
165 $this->drupalPost(NULL, $edit, t('Save configuration'));
166
167 // Edit text format.
168 $format = $this->getFormat($edit['name']);
169 $this->drupalGet('admin/config/content/formats');
170 $this->assertRaw('admin/config/content/formats/' . $format->format);
171 $this->drupalGet('admin/config/content/formats/' . $format->format);
172 $this->drupalPost(NULL, array(), t('Save configuration'));
173
174 // Delete text format.
175 $this->drupalGet('admin/config/content/formats');
176 $this->assertRaw('admin/config/content/formats/' . $format->format . '/delete');
177 $this->drupalGet('admin/config/content/formats/' . $format->format . '/delete');
178 $this->drupalPost(NULL, array(), t('Delete'));
179
180 // Verify that deleted text format no longer exists.
181 $this->drupalGet('admin/config/content/formats/' . $format->format);
182 $this->assertResponse(404, t('Deleted text format no longer exists.'));
183 }
184
185 /**
186 * Test filter administration functionality.
187 */
188 function testFilterAdmin() {
189 // URL filter.
190 $first_filter = 'filter_url';
191 // Line filter.
192 $second_filter = 'filter_autop';
193
194 list($filtered, $full, $plain) = $this->checkFilterFormats();
195
196 // Check that the fallback format exists and cannot be deleted.
197 $this->assertTrue(!empty($plain) && $plain == filter_fallback_format(), t('The fallback format is set to plain text.'));
198 $this->drupalGet('admin/config/content/formats');
199 $this->assertNoRaw('admin/config/content/formats/' . $plain . '/delete', t('Delete link for the fallback format not found.'));
200 $this->drupalGet('admin/config/content/formats/' . $plain . '/delete');
201 $this->assertResponse(403, t('The fallback format cannot be deleted.'));
202
203 // Verify access permissions to Full HTML format.
204 $this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), t('Admin user may use Full HTML.'));
205 $this->assertFalse(filter_access(filter_format_load($full), $this->web_user), t('Web user may not use Full HTML.'));
206
207 // Add an additional tag.
208 $edit = array();
209 $edit['settings[filter_html][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <quote>';
210 $this->drupalPost('admin/config/content/formats/' . $filtered . '/configure', $edit, t('Save configuration'));
211 $this->assertText(t('The configuration options have been saved.'), t('Allowed HTML tag added.'));
212
213 $this->assertRaw(htmlentities($edit['settings[filter_html][allowed_html]']), t('Tag displayed.'));
214
215 $result = db_query('SELECT * FROM {cache_filter}')->fetchObject();
216 $this->assertFalse($result, t('Cache cleared.'));
217
218 // Reorder filters.
219 $edit = array();
220 $edit['weights[' . $second_filter . ']'] = 1;
221 $edit['weights[' . $first_filter . ']'] = 2;
222 $this->drupalPost('admin/config/content/formats/' . $filtered . '/order', $edit, t('Save configuration'));
223 $this->assertText(t('The filter ordering has been saved.'), t('Order saved successfully.'));
224
225 $result = db_query('SELECT * FROM {filter} WHERE format = :format ORDER BY weight ASC', array(':format' => $filtered));
226 $filters = array();
227 foreach ($result as $filter) {
228 if ($filter->name == $second_filter || $filter->name == $first_filter) {
229 $filters[] = $filter;
230 }
231 }
232 $this->assertTrue(($filters[0]->name == $second_filter && $filters[1]->name == $first_filter), t('Order confirmed.'));
233
234 // Add filter.
235 $edit = array();
236 $edit['name'] = $this->randomName();
237 $edit['roles[2]'] = 1;
238 $edit['filters[' . $second_filter . '][status]'] = TRUE;
239 $edit['filters[' . $first_filter . '][status]'] = TRUE;
240 $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
241 $this->assertRaw(t('Added text format %format.', array('%format' => $edit['name'])), t('New filter created.'));
242
243 $format = $this->getFormat($edit['name']);
244 $this->assertNotNull($format, t('Format found in database.'));
245
246 if ($format !== NULL) {
247 $this->assertFieldByName('roles[2]', '', t('Role found.'));
248 $this->assertFieldByName('filters[' . $second_filter . '][status]', '', t('Line break filter found.'));
249 $this->assertFieldByName('filters[' . $first_filter . '][status]', '', t('Url filter found.'));
250
251 // Delete new filter.
252 $this->drupalPost('admin/config/content/formats/' . $format->format . '/delete', array(), t('Delete'));
253 $this->assertRaw(t('Deleted text format %format.', array('%format' => $edit['name'])), t('Format successfully deleted.'));
254 }
255
256 // Allow authenticated users on full HTML.
257 $format = filter_format_load($full);
258 $edit = array();
259 $edit['roles[1]'] = 0;
260 $edit['roles[2]'] = 1;
261 $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
262 $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully updated.'));
263
264 // Switch user.
265 $this->drupalLogout();
266 $this->drupalLogin($this->web_user);
267
268 $this->drupalGet('node/add/page');
269 $this->assertRaw('<option value="' . $full . '">Full HTML</option>', t('Full HTML filter accessible.'));
270
271 // Use filtered HTML and see if it removes tags that are not allowed.
272 $body = '<em>' . $this->randomName() . '</em>';
273 $extra_text = 'text';
274 $text = $body . '<random>' . $extra_text . '</random>';
275
276 $edit = array();
277 $langcode = FIELD_LANGUAGE_NONE;
278 $edit["title[$langcode][0][value]"] = $this->randomName();
279 $edit["body[$langcode][0][value]"] = $text;
280 $edit["body[$langcode][0][value_format]"] = $filtered;
281 $this->drupalPost('node/add/page', $edit, t('Save'));
282 $this->assertRaw(t('Page %title has been created.', array('%title' => $edit["title[$langcode][0][value]"])), t('Filtered node created.'));
283
284 $node = $this->drupalGetNodeByTitle($edit["title[$langcode][0][value]"]);
285 $this->assertTrue($node, t('Node found in database.'));
286
287 $this->drupalGet('node/' . $node->nid);
288 $this->assertRaw($body . $extra_text, t('Filter removed invalid tag.'));
289
290 // Use plain text and see if it escapes all tags, whether allowed or not.
291 $edit = array();
292 $edit["body[$langcode][0][value_format]"] = $plain;
293 $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
294 $this->drupalGet('node/' . $node->nid);
295 $this->assertText(check_plain($text), t('The "Plain text" text format escapes all HTML tags.'));
296
297 // Switch user.
298 $this->drupalLogout();
299 $this->drupalLogin($this->admin_user);
300
301 // Clean up.
302 // Allowed tags.
303 $edit = array();
304 $edit['settings[filter_html][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>';
305 $this->drupalPost('admin/config/content/formats/' . $filtered . '/configure', $edit, t('Save configuration'));
306 $this->assertText(t('The configuration options have been saved.'), t('Changes reverted.'));
307
308 // Full HTML.
309 $edit = array();
310 $edit['roles[2]'] = FALSE;
311 $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
312 $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully reverted.'));
313
314 // Filter order.
315 $edit = array();
316 $edit['weights[' . $second_filter . ']'] = 2;
317 $edit['weights[' . $first_filter . ']'] = 1;
318 $this->drupalPost('admin/config/content/formats/' . $filtered . '/order', $edit, t('Save configuration'));
319 $this->assertText(t('The filter ordering has been saved.'), t('Order successfully reverted.'));
320 }
321
322 /**
323 * Query the database to get the three basic formats.
324 *
325 * @return
326 * An array containing filtered, full, and plain text format ids.
327 */
328 function checkFilterFormats() {
329 $result = db_query('SELECT format, name FROM {filter_format}');
330
331 $filtered = -1;
332 $full = -1;
333 $plain = -1;
334 foreach ($result as $format) {
335 if ($format->name == 'Filtered HTML') {
336 $filtered = $format->format;
337 }
338 elseif ($format->name == 'Full HTML') {
339 $full = $format->format;
340 }
341 elseif ($format->name == 'Plain text') {
342 $plain = $format->format;
343 }
344 }
345
346 return array($filtered, $full, $plain);
347 }
348
349 /**
350 * Retrieve a text format object by name.
351 *
352 * @param $name
353 * The name of a text format.
354 * @return
355 * A text format object.
356 */
357 function getFormat($name) {
358 return db_query("SELECT * FROM {filter_format} WHERE name = :name", array(':name' => $name))->fetchObject();
359 }
360 }
361
362 class FilterAccessTestCase extends DrupalWebTestCase {
363 protected $admin_user;
364 protected $web_user;
365 protected $allowed_format;
366 protected $disallowed_format;
367
368 public static function getInfo() {
369 return array(
370 'name' => 'Filter access functionality',
371 'description' => 'Test the filter access system.',
372 'group' => 'Filter',
373 );
374 }
375
376 function setUp() {
377 parent::setUp();
378
379 // Create two text formats and grant a regular user access to one of them.
380 $this->admin_user = $this->drupalCreateUser(array('administer filters'));
381 $this->drupalLogin($this->admin_user);
382 $formats = array();
383 for ($i = 0; $i < 2; $i++) {
384 $edit = array('name' => $this->randomName());
385 $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
386 $this->resetFilterCaches();
387 $format_id = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $edit['name']))->fetchField();
388 $formats[] = filter_format_load($format_id);
389 }
390 list($this->allowed_format, $this->disallowed_format) = $formats;
391 $this->web_user = $this->drupalCreateUser(array('create page content', filter_permission_name($this->allowed_format)));
392 }
393
394 function testFormatPermissions() {
395 // Make sure that a regular user only has access to the text format they
396 // were granted access to, as well to the fallback format.
397 $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.'));
398 $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.'));
399 $this->assertTrue(filter_access(filter_format_load(filter_fallback_format()), $this->web_user), t('A regular user has access to the fallback format.'));
400
401 // Perform similar checks as above, but now against the entire list of
402 // available formats for this user.
403 $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.'));
404 $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.'));
405 $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.'));
406
407 // Make sure that a regular user only has permission to use the format
408 // they were granted access to.
409 $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.'));
410 $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.'));
411
412 // Make sure that the allowed format appears on the node form and that
413 // the disallowed format does not.
414 $this->drupalLogin($this->web_user);
415 $this->drupalGet('node/add/page');
416 $this->assertRaw($this->formatSelectorHTML($this->allowed_format), t('The allowed text format appears as an option when adding a new node.'));
417 $this->assertNoRaw($this->formatSelectorHTML($this->disallowed_format), t('The disallowed text format does not appear as an option when adding a new node.'));
418 $this->assertRaw($this->formatSelectorHTML(filter_format_load(filter_fallback_format())), t('The fallback format appears as an option when adding a new node.'));
419 }
420
421 function testFormatRoles() {
422 // Get the role ID assigned to the regular user; it must be the maximum.
423 $rid = max(array_keys($this->web_user->roles));
424
425 // Check that this role appears in the list of roles that have access to an
426 // allowed text format, but does not appear in the list of roles that have
427 // access to a disallowed text format.
428 $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.'));
429 $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.'));
430
431 // Check that the correct text format appears in the list of formats
432 // available to that role.
433 $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.'));
434 $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.'));
435
436 // Check that the fallback format is always allowed.
437 $this->assertEqual(filter_get_roles_by_format(filter_format_load(filter_fallback_format())), user_roles(), t('All roles have access to the fallback format.'));
438 $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.'));
439 }
440
441 /**
442 * Returns the expected HTML for a particular text format selector.
443 *
444 * @param $format
445 * An object representing the text format for which to return HTML.
446 * @return
447 * The expected HTML for that text format's selector.
448 */
449 function formatSelectorHTML($format) {
450 return '<option value="' . $format->format . '">' . $format->name . '</option>';
451 }
452
453 /**
454 * Rebuild text format and permission caches in the thread running the tests.
455 */
456 protected function resetFilterCaches() {
457 filter_formats_reset();
458 $this->checkPermissions(array(), TRUE);
459 }
460 }
461
462 class FilterDefaultFormatTestCase extends DrupalWebTestCase {
463 public static function getInfo() {
464 return array(
465 'name' => 'Default text format functionality',
466 'description' => 'Test the default text formats for different users.',
467 'group' => 'Filter',
468 );
469 }
470
471 function testDefaultTextFormats() {
472 // Create two text formats, and two users. The first user has access to
473 // both formats, but the second user only has access to the second one.
474 $admin_user = $this->drupalCreateUser(array('administer filters'));
475 $this->drupalLogin($admin_user);
476 $formats = array();
477 for ($i = 0; $i < 2; $i++) {
478 $edit = array('name' => $this->randomName());
479 $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
480 $this->resetFilterCaches();
481 $format_id = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $edit['name']))->fetchField();
482 $formats[] = filter_format_load($format_id);
483 }
484 list($first_format, $second_format) = $formats;
485 $first_user = $this->drupalCreateUser(array(filter_permission_name($first_format), filter_permission_name($second_format)));
486 $second_user = $this->drupalCreateUser(array(filter_permission_name($second_format)));
487
488 // Adjust the weights so that the first and second formats (in that order)
489 // are the two lowest weighted formats available to any user.
490 $minimum_weight = db_query("SELECT MIN(weight) FROM {filter_format}")->fetchField();
491 $edit = array();
492 $edit['formats[' . $first_format->format . '][weight]'] = $minimum_weight - 2;
493 $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 1;
494 $this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
495 $this->resetFilterCaches();
496
497 // Check that each user's default format is the lowest weighted format that
498 // the user has access to.
499 $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."));
500 $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."));
501
502 // Reorder the two formats, and check that both users now have the same
503 // default.
504 $edit = array();
505 $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 3;
506 $this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
507 $this->resetFilterCaches();
508 $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.'));
509 }
510
511 /**
512 * Rebuild text format and permission caches in the thread running the tests.
513 */
514 protected function resetFilterCaches() {
515 filter_formats_reset();
516 $this->checkPermissions(array(), TRUE);
517 }
518 }
519
520 class FilterNoFormatTestCase extends DrupalWebTestCase {
521 public static function getInfo() {
522 return array(
523 'name' => 'Unassigned text format functionality',
524 'description' => 'Test the behavior of check_markup() when it is called without a text format.',
525 'group' => 'Filter',
526 );
527 }
528
529 function testCheckMarkupNoFormat() {
530 // Create some text. Include some HTML and line breaks, so we get a good
531 // test of the filtering that is applied to it.
532 $text = "<strong>" . $this->randomName(32) . "</strong>\n\n<div>" . $this->randomName(32) . "</div>";
533
534 // Make sure that when this text is run through check_markup() with no text
535 // format, it is filtered as though it is in the fallback format.
536 $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.'));
537 }
538 }
539
540 /**
541 * Unit tests for core filters.
542 */
543 class FilterUnitTestCase extends DrupalUnitTestCase {
544 public static function getInfo() {
545 return array(
546 'name' => 'Core filters',
547 'description' => 'Filter each filter individually: convert line breaks, correct broken HTML.',
548 'group' => 'Filter',
549 );
550 }
551
552 /**
553 * Test the line break filter.
554 */
555 function testLineBreakFilter() {
556 // Single line breaks should be changed to <br /> tags, while paragraphs
557 // separated with double line breaks should be enclosed with <p></p> tags.
558 $f = _filter_autop("aaa\nbbb\n\nccc");
559 $this->assertEqual(str_replace("\n", '', $f), "<p>aaa<br />bbb</p><p>ccc</p>", t('Line breaking basic case.'));
560
561 // Text within some contexts should not be processed.
562 $f = _filter_autop("<script>aaa\nbbb\n\nccc</script>");
563 $this->assertEqual($f, "<script>aaa\nbbb\n\nccc</script>", t('Line breaking -- do not break scripts.'));
564
565 $f = _filter_autop('<p><div> </div></p>');
566 $this->assertEqual(substr_count($f, '<p>'), substr_count($f, '</p>'), t('Make sure line breaking produces matching paragraph tags.'));
567
568 $f = _filter_autop('<div><p> </p></div>');
569 $this->assertEqual(substr_count($f, '<p>'), substr_count($f, '</p>'), t('Make sure line breaking produces matching paragraph tags.'));
570
571 $f = _filter_autop('<blockquote><pre>aaa</pre></blockquote>');
572 $this->assertEqual(substr_count($f, '<p>'), substr_count($f, '</p>'), t('Make sure line breaking produces matching paragraph tags.'));
573
574 $limit = max(ini_get('pcre.backtrack_limit'), ini_get('pcre.recursion_limit'));
575 $f = _filter_autop($this->randomName($limit));
576 $this->assertNotEqual($f, '', t('Make sure line breaking can process long strings.'));
577 }
578
579 /**
580 * Test limiting allowed tags, XSS prevention and adding 'nofollow' to links.
581 *
582 * XSS tests assume that script is dissallowed on default and src is allowed
583 * on default, but on* and style are dissallowed.
584 *
585 * Script injection vectors mostly adopted from http://ha.ckers.org/xss.html.
586 *
587 * Relevant CVEs:
588 * - CVE-2002-1806, ~CVE-2005-0682, ~CVE-2005-2106, CVE-2005-3973,
589 * CVE-2006-1226 (= rev. 1.112?), CVE-2008-0273, CVE-2008-3740.
590 */
591 function testHtmlFilter() {
592 // Tag stripping, different ways to work around removal of HTML tags.
593 $f = filter_xss('<script>alert(0)</script>');
594 $this->assertNoNormalized($f, 'script', t('HTML tag stripping -- simple script without special characters.'));
595
596 $f = filter_xss('<script src="http://www.example.com" />');
597 $this->assertNoNormalized($f, 'script', t('HTML tag stripping -- empty script with source.'));
598
599 $f = filter_xss('<ScRipt sRc=http://www.example.com/>');
600 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- varying case.'));
601
602 $f = filter_xss("<script\nsrc\n=\nhttp://www.example.com/\n>");
603 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- multiline tag.'));
604
605 $f = filter_xss('<script/a src=http://www.example.com/a.js></script>');
606 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- non whitespace character after tag name.'));
607
608 $f = filter_xss('<script/src=http://www.example.com/a.js></script>');
609 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no space between tag and attribute.'));
610
611 // Null between < and tag name works at least with IE6.
612 $f = filter_xss("<\0scr\0ipt>alert(0)</script>");
613 $this->assertNoNormalized($f, 'ipt', t('HTML tag stripping evasion -- breaking HTML with nulls.'));
614
615 $f = filter_xss("<scrscriptipt src=http://www.example.com/a.js>");
616 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- filter just removing "script".'));
617
618 $f = filter_xss('<<script>alert(0);//<</script>');
619 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- double opening brackets.'));
620
621 $f = filter_xss('<script src=http://www.example.com/a.js?<b>');
622 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no closing tag.'));
623
624 // DRUPAL-SA-2008-047: This doesn't seem exploitable, but the filter should
625 // work consistently.
626 $f = filter_xss('<script>>');
627 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- double closing tag.'));
628
629 $f = filter_xss('<script src=//www.example.com/.a>');
630 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no scheme or ending slash.'));
631
632 $f = filter_xss('<script src=http://www.example.com/.a');
633 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no closing bracket.'));
634
635 $f = filter_xss('<script src=http://www.example.com/ <');
636 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- opening instead of closing bracket.'));
637
638 $f = filter_xss('<nosuchtag attribute="newScriptInjectionVector">');
639 $this->assertNoNormalized($f, 'nosuchtag', t('HTML tag stripping evasion -- unknown tag.'));
640
641 $f = filter_xss('<?xml:namespace ns="urn:schemas-microsoft-com:time">');
642 $this->assertTrue(stripos($f, '<?xml') === FALSE, t('HTML tag stripping evasion -- starting with a question sign (processing instructions).'));
643
644 $f = filter_xss('<t:set attributeName="innerHTML" to="&lt;script defer&gt;alert(0)&lt;/script&gt;">');
645 $this->assertNoNormalized($f, 't:set', t('HTML tag stripping evasion -- colon in the tag name (namespaces\' tricks).'));
646
647 $f = filter_xss('<img """><script>alert(0)</script>', array('img'));
648 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- a malformed image tag.'));
649
650 $f = filter_xss('<blockquote><script>alert(0)</script></blockquote>', array('blockquote'));
651 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script in a blockqoute.'));
652
653 $f = filter_xss("<!--[if true]><script>alert(0)</script><![endif]-->");
654 $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script within a comment.'));
655
656 // Dangerous attributes removal.
657 $f = filter_xss('<p onmouseover="http://www.example.com/">', array('p'));
658 $this->assertNoNormalized($f, 'onmouseover', t('HTML filter attributes removal -- events, no evasion.'));
659
660 $f = filter_xss('<li style="list-style-image: url(javascript:alert(0))">', array('li'));
661 $this->assertNoNormalized($f, 'style', t('HTML filter attributes removal -- style, no evasion.'));
662
663 $f = filter_xss('<img onerror =alert(0)>', array('img'));
664 $this->assertNoNormalized($f, 'onerror', t('HTML filter attributes removal evasion -- spaces before equals sign.'));
665
666 $f = filter_xss('<img onabort!#$%&()*~+-_.,:;?@[/|\]^`=alert(0)>', array('img'));
667 $this->assertNoNormalized($f, 'onabort', t('HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.'));
668
669 $f = filter_xss('<img oNmediAError=alert(0)>', array('img'));
670 $this->assertNoNormalized($f, 'onmediaerror', t('HTML filter attributes removal evasion -- varying case.'));
671
672 // Works at least with IE6.
673 $f = filter_xss("<img o\0nfocus\0=alert(0)>", array('img'));
674 $this->assertNoNormalized($f, 'focus', t('HTML filter attributes removal evasion -- breaking with nulls.'));
675
676 // Only whitelisted scheme names allowed in attributes.
677 $f = filter_xss('<img src="javascript:alert(0)">', array('img'));
678 $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- no evasion.'));
679
680 $f = filter_xss('<img src=javascript:alert(0)>', array('img'));
681 $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- no quotes.'));
682
683 // A bit like CVE-2006-0070.
684 $f = filter_xss('<img src="javascript:confirm(0)">', array('img'));
685 $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- no alert ;)'));
686
687 $f = filter_xss('<img src=`javascript:alert(0)`>', array('img'));
688 $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- grave accents.'));
689
690 $f = filter_xss('<img dynsrc="javascript:alert(0)">', array('img'));
691 $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- rare attribute.'));
692
693 $f = filter_xss('<table background="javascript:alert(0)">', array('table'));
694 $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- another tag.'));
695
696 $f = filter_xss('<base href="javascript:alert(0);//">', array('base'));
697 $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- one more attribute and tag.'));
698
699 $f = filter_xss('<img src="jaVaSCriPt:alert(0)">', array('img'));
700 $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- varying case.'));
701
702 $f = filter_xss('<img src=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#48;&#41;>', array('img'));
703 $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- UTF-8 decimal encoding.'));
704
705 $f = filter_xss('<img src=&#00000106&#0000097&#00000118&#0000097&#00000115&#0000099&#00000114&#00000105&#00000112&#00000116&#0000058&#0000097&#00000108&#00000101&#00000114&#00000116&#0000040&#0000048&#0000041>', array('img'));
706 $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- long UTF-8 encoding.'));
707
708 $f = filter_xss('<img src=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x30&#x29>', array('img'));
709 $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- UTF-8 hex encoding.'));
710
711 $f = filter_xss("<img src=\"jav\tascript:alert(0)\">", array('img'));
712 $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an embedded tab.'));
713
714 $f = filter_xss('<img src="jav&#x09;ascript:alert(0)">', array('img'));
715 $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an encoded, embedded tab.'));
716
717 $f = filter_xss('<img src="jav&#x000000A;ascript:alert(0)">', array('img'));
718 $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an encoded, embedded newline.'));
719
720 // With &#xD; this test would fail, but the entity gets turned into
721 // &amp;#xD;, so it's OK.
722 $f = filter_xss('<img src="jav&#x0D;ascript:alert(0)">', array('img'));
723 $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an encoded, embedded carriage return.'));
724
725 $f = filter_xss("<img src=\"\n\n\nj\na\nva\ns\ncript:alert(0)\">", array('img'));
726 $this->assertNoNormalized($f, 'cript', t('HTML scheme clearing evasion -- broken into many lines.'));
727
728 $f = filter_xss("<img src=\"jav\0a\0\0cript:alert(0)\">", array('img'));
729 $this->assertNoNormalized($f, 'cript', t('HTML scheme clearing evasion -- embedded nulls.'));
730
731 $f = filter_xss('<img src=" &#14; javascript:alert(0)">', array('img'));
732 $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- spaces and metacharacters before scheme.'));
733
734 $f = filter_xss('<img src="vbscript:msgbox(0)">', array('img'));
735 $this->assertNoNormalized($f, 'vbscript', t('HTML scheme clearing evasion -- another scheme.'));
736
737 $f = filter_xss('<img src="nosuchscheme:notice(0)">', array('img'));
738 $this->assertNoNormalized($f, 'nosuchscheme', t('HTML scheme clearing evasion -- unknown scheme.'));
739
740 // Netscape 4.x javascript entities.
741 $f = filter_xss('<br size="&{alert(0)}">', array('br'));
742 $this->assertNoNormalized($f, 'alert', t('Netscape 4.x javascript entities.'));
743
744 // DRUPAL-SA-2008-006: Invalid UTF-8, these only work as reflected XSS with
745 // Internet Explorer 6.
746 $f = filter_xss("<p arg=\"\xe0\">\" style=\"background-image: url(javascript:alert(0));\"\xe0<p>", array('p'));
747 $this->assertNoNormalized($f, 'style', t('HTML filter -- invalid UTF-8.'));
748
749 $f = filter_xss("\xc0aaa");
750 $this->assertEqual($f, '', t('HTML filter -- overlong UTF-8 sequences.'));
751
752 $f = filter_xss("Who&#039;s Online");
753 $this->assertNormalized($f, "who's online", t('HTML filter -- html entity number'));
754
755 $f = filter_xss("Who&amp;#039;s Online");
756 $this->assertNormalized($f, "who&#039;s online", t('HTML filter -- encoded html entity number'));
757
758 $f = filter_xss("Who&amp;amp;#039; Online");
759 $this->assertNormalized($f, "who&amp;#039; online", t('HTML filter -- double encoded html entity number'));
760 }
761
762 /**
763 * Test filter settings, defaults, access restrictions and similar.
764 *
765 * @todo This is for functions like filter_filter and check_markup, whose
766 * functionality is not completely focused on filtering. Some ideas:
767 * restricting formats according to user permissions, proper cache
768 * handling, defaults -- allowed tags/attributes/protocols.
769 *
770 * @todo It is possible to add script, iframe etc. to allowed tags, but this
771 * makes HTML filter completely ineffective.
772 *
773 * @todo Class, id, name and xmlns should be added to disallowed attributes,
774 * or better a whitelist approach should be used for that too.
775 */
776 function testFilter() {
777 // Setup dummy filter object.
778 $filter = new stdClass;
779 $filter->settings = array(
780 'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>',
781 'filter_html_help' => 1,
782 'filter_html_nofollow' => 0,
783 );
784
785 // HTML filter is not able to secure some tags, these should never be
786 // allowed.
787 $f = _filter_html('<script />', $filter);
788 $this->assertNoNormalized($f, 'script', t('HTML filter should always remove script tags.'));
789
790 $f = _filter_html('<iframe />', $filter);
791 $this->assertNoNormalized($f, 'iframe', t('HTML filter should always remove iframe tags.'));
792
793 $f = _filter_html('<object />', $filter);
794 $this->assertNoNormalized($f, 'object', t('HTML filter should always remove object tags.'));
795
796 $f = _filter_html('<style />', $filter);
797 $this->assertNoNormalized($f, 'style', t('HTML filter should always remove style tags.'));
798
799 // Some tags make CSRF attacks easier, let the user take the risk herself.
800 $f = _filter_html('<img />', $filter);
801 $this->assertNoNormalized($f, 'img', t('HTML filter should remove img tags on default.'));
802
803 $f = _filter_html('<input />', $filter);
804 $this->assertNoNormalized($f, 'img', t('HTML filter should remove input tags on default.'));
805
806 // Filtering content of some attributes is infeasible, these shouldn't be
807 // allowed too.
808 $f = _filter_html('<p style="display: none;" />', $filter);
809 $this->assertNoNormalized($f, 'style', t('HTML filter should remove style attribute on default.'));
810
811 $f = _filter_html('<p onerror="alert(0);" />', $filter);
812 $this->assertNoNormalized($f, 'onerror', t('HTML filter should remove on* attributes on default.'));
813 }
814
815 /**
816 * Test the spam deterrent.
817 */
818 function testNoFollowFilter() {
819 // Setup dummy filter object.
820 $filter = new stdClass;
821 $filter->settings = array(
822 'allowed_html' => '<a>',
823 'filter_html_help' => 1,
824 'filter_html_nofollow' => 1,
825 );
826
827 // Test if the rel="nofollow" attribute is added, even if we try to prevent
828 // it.
829 $f = _filter_html('<a href="http://www.example.com/">text</a>', $filter);
830 $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent -- no evasion.'));
831
832 $f = _filter_html('<A href="http://www.example.com/">text</a>', $filter);
833 $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- capital A.'));
834
835 $f = _filter_html("<a/href=\"http://www.example.com/\">text</a>", $filter);
836 $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- non whitespace character after tag name.'));
837
838 $f = _filter_html("<\0a\0 href=\"http://www.example.com/\">text</a>", $filter);
839 $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- some nulls.'));
840
841 $f = _filter_html('<!--[if true]><a href="http://www.example.com/">text</a><![endif]-->', $filter);
842 $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- link within a comment.'));
843
844 $f = _filter_html('<a href="http://www.example.com/" rel="follow">text</a>', $filter);
845 $this->assertNoNormalized($f, 'rel="follow"', t('Spam deterrent evasion -- with rel set - rel="follow" removed.'));
846 $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- with rel set - rel="nofollow" added.'));
847 }
848
849 /**
850 * Test the loose, admin HTML filter.
851 */
852 function testAdminHtmlFilter() {
853 // DRUPAL-SA-2008-044
854 $f = filter_xss_admin('<object />');
855 $this->assertNoNormalized($f, 'object', t('Admin HTML filter -- should not allow object tag.'));
856
857 $f = filter_xss_admin('<script />');
858 $this->assertNoNormalized($f, 'script', t('Admin HTML filter -- should not allow script tag.'));
859
860 $f = filter_xss_admin('<style /><iframe /><frame /><frameset /><meta /><link /><embed /><applet /><param /><layer />');
861 $this->assertEqual($f, '', t('Admin HTML filter -- should never allow some tags.'));
862 }
863
864 /**
865 * Test the HTML escaping filter.
866 */
867 function testNoHtmlFilter() {
868 $this->_testEscapedHTML('_filter_html_escape');
869 }
870
871 /**
872 * Test that the check_plain() function escapes HTML correctly.
873 */
874 function testCheckPlain() {
875 $this->_testEscapedHTML('check_plain');
876 }
877
878 /**
879 * Test the URL filter.
880 */
881 function testUrlFilter() {
882 // Setup dummy filter object.
883 $filter = new stdClass;
884 $filter->settings = array(
885 'filter_url_length' => 496,
886 );
887
888 // Converting URLs.
889 $f = _filter_url('http://www.example.com/', $filter);
890 $this->assertEqual($f, '<a href="http://www.example.com/" title="http://www.example.com/">http://www.example.com/</a>', t('Converting URLs.'));
891
892 $f = _filter_url('http://www.example.com/?a=1&b=2', $filter);
893 $this->assertEqual($f, '<a href="http://www.example.com/?a=1&amp;b=2" title="http://www.example.com/?a=1&amp;b=2">http://www.example.com/?a=1&amp;b=2</a>', t('Converting URLs -- ampersands.'));
894
895 $f = _filter_url('ftp://user:pass@ftp.example.com/dir1/dir2', $filter);
896 $this->assertEqual($f, '<a href="ftp://user:pass@ftp.example.com/dir1/dir2" title="ftp://user:pass@ftp.example.com/dir1/dir2">ftp://user:pass@ftp.example.com/dir1/dir2</a>', t('Converting URLs -- FTP scheme.'));
897
898 // Converting domain names.
899 $f = _filter_url('www.example.com', $filter);
900 $this->assertEqual($f, '<a href="http://www.example.com" title="www.example.com">www.example.com</a>', t('Converting domain names.'));
901
902 $f = _filter_url('<li>www.example.com</li>', $filter);
903 $this->assertEqual($f, '<li><a href="http://www.example.com" title="www.example.com">www.example.com</a></li>', t('Converting domain names -- domain in a list.'));
904
905 $f = _filter_url('(www.example.com/dir?a=1&b=2#a)', $filter);
906 $this->assertEqual($f, '(<a href="http://www.example.com/dir?a=1&amp;b=2#a" title="www.example.com/dir?a=1&amp;b=2#a">www.example.com/dir?a=1&amp;b=2#a</a>)', t('Converting domain names -- domain in parentheses.'));
907
908 // Converting e-mail addresses.
909 $f = _filter_url('johndoe@example.com', $filter);
910 $this->assertEqual($f, '<a href="mailto:johndoe@example.com">johndoe@example.com</a>', t('Converting e-mail addresses.'));
911
912 $f = _filter_url('aaa@sub.tv', $filter);
913 $this->assertEqual($f, '<a href="mailto:aaa@sub.tv">aaa@sub.tv</a>', t('Converting e-mail addresses -- a short e-mail from Tuvalu.'));
914
915 // URL trimming.
916 $filter->settings['filter_url_length'] = 28;
917
918 $f = _filter_url('http://www.example.com/d/ff.ext?a=1&b=2#a1', $filter);
919 $this->assertNormalized($f, 'http://www.example.com/d/ff....', t('URL trimming.'));
920
921 // Not breaking existing links.
922 $f = _filter_url('<a href="http://www.example.com">www.example.com</a>', $filter);
923 $this->assertEqual($f, '<a href="http://www.example.com">www.example.com</a>', t('Converting URLs -- do not break existing links.'));
924
925 $f = _filter_url('<a href="foo">http://www.example.com</a>', $filter);
926 $this->assertEqual($f, '<a href="foo">http://www.example.com</a>', t('Converting URLs -- do not break existing, relative links.'));
927
928 // Addresses within some tags such as code or script should not be converted.
929 $f = _filter_url('<code>http://www.example.com</code>', $filter);
930 $this->assertEqual($f, '<code>http://www.example.com</code>', t('Converting URLs -- skip code contents.'));
931
932 $f = _filter_url('<code><em>http://www.example.com</em></code>', $filter);
933 $this->assertEqual($f, '<code><em>http://www.example.com</em></code>', t('Converting URLs -- really skip code contents.'));
934
935 $f = _filter_url('<script>http://www.example.com</script>', $filter);
936 $this->assertEqual($f, '<script>http://www.example.com</script>', t('Converting URLs -- do not process scripts.'));
937
938 // Addresses in attributes should not be converted.
939 $f = _filter_url('<p xmlns="http://www.example.com" />', $filter);
940 $this->assertEqual($f, '<p xmlns="http://www.example.com" />', t('Converting URLs -- do not convert addresses in attributes.'));
941
942 $f = _filter_url('<a title="Go to www.example.com" href="http://www.example.com">text</a>', $filter);
943 $this->assertEqual($f, '<a title="Go to www.example.com" href="http://www.example.com">text</a>', t('Converting URLs -- do not break existing links with custom title attribute.'));
944
945 // Even though a dot at the end of a URL can indicate a fully qualified
946 // domain name, such usage is rare compared to using a link at the end
947 // of a sentence, so remove the dot from the link.
948 // @todo It can also be used at the end of a filename or a query string.
949 $f = _filter_url('www.example.com.', $filter);
950 $this->assertEqual($f, '<a href="http://www.example.com" title="www.example.com">www.example.com</a>.', t('Converting URLs -- do not recognize a dot at the end of a domain name (FQDNs).'));
951
952 $f = _filter_url('http://www.example.com.', $filter);
953 $this->assertEqual($f, '<a href="http://www.example.com" title="http://www.example.com">http://www.example.com</a>.', t('Converting URLs -- do not recognize a dot at the end of an URL (FQDNs).'));
954
955 $f = _filter_url('www.example.com/index.php?a=.', $filter);
956 $this->assertEqual($f, '<a href="http://www.example.com/index.php?a=" title="www.example.com/index.php?a=">www.example.com/index.php?a=</a>.', t('Converting URLs -- do forget about a dot at the end of a query string.'));
957 }
958
959 /**
960 * Test the HTML corrector filter.
961 *
962 * @todo This test could really use some validity checking function.
963 */
964 function testHtmlCorrectorFilter() {
965 // Tag closing.
966 $f = _filter_htmlcorrector('<p>text');
967 $this->assertEqual($f, '<p>text</p>', t('HTML corrector -- tag closing at the end of input.'));
968
969 $f = _filter_htmlcorrector('<p>text<p><p>text');
970 $this->assertEqual($f, '<p>text</p><p /><p>text</p>', t('HTML corrector -- tag closing.'));
971
972 $f = _filter_htmlcorrector("<ul><li>e1<li>e2");
973 $this->assertEqual($f, "<ul><li>e1</li><li>e2</li></ul>", t('HTML corrector -- unclosed list tags.'));
974
975 $f = _filter_htmlcorrector('<div id="d">content');
976 $this->assertEqual($f, '<div id="d">content</div>', t('HTML corrector -- unclosed tag with attribute.'));
977
978 // XHTML slash for empty elements.
979 $f = _filter_htmlcorrector('<hr><br>');
980 $this->assertEqual($f, '<hr /><br />', t('HTML corrector -- XHTML closing slash.'));
981
982 $f = _filter_htmlcorrector('<P>test</P>');
983 $this->assertEqual($f, '<p>test</p>', t('HTML corrector -- Convert uppercased tags to proper lowercased ones.'));
984
985 $f = _filter_htmlcorrector('<P>test</p>');
986 $this->assertEqual($f, '<p>test</p>', t('HTML corrector -- Convert uppercased tags to proper lowercased ones.'));
987
988 $f = _filter_htmlcorrector('test<hr/>');
989 $this->assertEqual($f, 'test<hr />', t('HTML corrector -- Let proper XHTML pass thru.'));
990
991 $f = _filter_htmlcorrector('test<hr />');
992 $this->assertEqual($f, 'test<hr />', t('HTML corrector -- Let proper XHTML pass thru.'));
993
994 $f = _filter_htmlcorrector('<span class="test" />');
995 $this->assertEqual($f, '<span class="test" />', t('HTML corrector -- Let proper XHTML pass thru.'));
996
997 $f = _filter_htmlcorrector('test1<br class="test">test2');
998 $this->assertEqual($f, 'test1<br class="test" />test2', t('HTML corrector -- Automatically close single tags.'));
999
1000 $f = _filter_htmlcorrector('line1<hr>line2');
1001 $this->assertEqual($f, 'line1<hr />line2', t('HTML corrector -- Automatically close single tags.'));
1002
1003 $f = _filter_htmlcorrector('line1<HR>line2');
1004 $this->assertEqual($f, 'line1<hr />line2', t('HTML corrector -- Automatically close single tags.'));
1005
1006 $f = _filter_htmlcorrector('<img src="http://example.com/test.jpg">test</img>');
1007 $this->assertEqual($f, '<img src="http://example.com/test.jpg" />test', t('HTML corrector -- Automatically close single tags.'));
1008
1009 $f = _filter_htmlcorrector('<p>line1<br/><hr/>line2</p>');
1010 $this->assertEqual