- Timestamp:
- 01/13/2026 01:11:23 PM (6 months ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php
r60706 r61477 445 445 * Ensures that updates with potentially-compromising values aren't accepted. 446 446 * 447 * For example, a modifiable text update should be allowed which would break 448 * the structure of the containing element, such as in a script or comment. 447 * For example, a modifiable text update that would change the structure of the HTML 448 * document is not allowed, like attempting to set `-->` within a comment or `</script>` 449 * within a text/plain SCRIPT tag. 449 450 * 450 451 * @ticket 61617 452 * @ticket 62797 451 453 * 452 454 * @dataProvider data_unallowed_modifiable_text_updates … … 455 457 * @param string $invalid_update Update containing possibly-compromising text. 456 458 */ 457 public function test_rejects_ updates_with_unallowed_substrings( string $html_with_nonempty_modifiable_text, string $invalid_update ) {459 public function test_rejects_dangerous_updates( string $html_with_nonempty_modifiable_text, string $invalid_update ) { 458 460 $processor = new WP_HTML_Tag_Processor( $html_with_nonempty_modifiable_text ); 459 461 … … 467 469 $this->assertFalse( 468 470 $processor->set_modifiable_text( $invalid_update ), 469 'Should have reject possibly-compromising modifiable text update.'471 'Should have rejected possibly-compromising modifiable text update.' 470 472 ); 471 473 … … 487 489 public static function data_unallowed_modifiable_text_updates() { 488 490 return array( 489 'Comment with -->' => array( '<!-- this is a comment -->', 'Comments end in -->' ), 490 'Comment with --!>' => array( '<!-- this is a comment -->', 'Invalid but legitimate comments end in --!>' ), 491 'SCRIPT with </script>' => array( '<script>Replace me</script>', 'Just a </script>' ), 492 'SCRIPT with </script attributes>' => array( '<script>Replace me</script>', 'before</script id=sneak>after' ), 493 'SCRIPT with "<script " opener' => array( '<script>Replace me</script>', '<!--<script ' ), 491 'Comment with -->' => array( '<!-- this is a comment -->', 'Comments end in -->' ), 492 'Comment with --!>' => array( '<!-- this is a comment -->', 'Invalid but legitimate comments end in --!>' ), 493 'Non-JS SCRIPT with <script>' => array( '<script type="text/html">Replace me</script>', '<!-- Just a <script>' ), 494 'Non-JS SCRIPT with </script>' => array( '<script type="text/plain">Replace me</script>', 'Just a </script>' ), 495 'Non-JS SCRIPT with <script attributes>' => array( '<script language="text">Replace me</script>', '<!-- <script sneaky>after' ), 496 'Non-JS SCRIPT with </script attributes>' => array( '<script language="text">Replace me</script>', 'before</script sneaky>after' ), 497 ); 498 } 499 500 /** 501 * Ensures that JavaScript script tag contents are safely updated. 502 * 503 * @ticket 62797 504 * 505 * @dataProvider data_script_tag_text_updates 506 * 507 * @param string $html HTML containing a SCRIPT tag to be modified. 508 * @param string $update Update containing possibly-compromising text. 509 * @param string $expected Expected result. 510 */ 511 public function test_safely_updates_script_tag_contents( string $html, string $update, string $expected ) { 512 $processor = new WP_HTML_Tag_Processor( $html ); 513 $this->assertTrue( $processor->next_tag( 'SCRIPT' ) ); 514 $this->assertTrue( $processor->set_modifiable_text( $update ) ); 515 $this->assertSame( $expected, $processor->get_updated_html() ); 516 } 517 518 /** 519 * Data provider. 520 * 521 * @return array[] 522 */ 523 public static function data_script_tag_text_updates(): array { 524 return array( 525 'Simple update' => array( '<script></script>', '{}', '<script>{}</script>' ), 526 'Needs no replacement' => array( '<script></script>', '<!--<scriptish>', '<script><!--<scriptish></script>' ), 527 'var script;1<script>0' => array( '<script></script>', 'var script;1<script>0', '<script>var script;1<\u0073cript>0</script>' ), 528 '1</script>/' => array( '<script></script>', '1</script>/', '<script>1</\u0073cript>/</script>' ), 529 'var SCRIPT;1<SCRIPT>0' => array( '<script></script>', 'var SCRIPT;1<SCRIPT>0', '<script>var SCRIPT;1<\u0053CRIPT>0</script>' ), 530 '1</SCRIPT>/' => array( '<script></script>', '1</SCRIPT>/', '<script>1</\u0053CRIPT>/</script>' ), 531 '"</script>"' => array( '<script></script>', '"</script>"', '<script>"</\u0073cript>"</script>' ), 532 '"</ScRiPt>"' => array( '<script></script>', '"</ScRiPt>"', '<script>"</\u0053cRiPt>"</script>' ), 533 'Tricky script open tag with \r' => array( '<script></script>', "<!-- <script\r>", "<script><!-- <\\u0073cript\r></script>" ), 534 'Tricky script open tag with \r\n' => array( '<script></script>', "<!-- <script\r\n>", "<script><!-- <\\u0073cript\r\n></script>" ), 535 'Tricky script close tag with \r' => array( '<script></script>', "// </script\r>", "<script>// </\\u0073cript\r></script>" ), 536 'Tricky script close tag with \r\n' => array( '<script></script>', "// </script\r\n>", "<script>// </\\u0073cript\r\n></script>" ), 537 'Module tag' => array( '<script type="module"></script>', '"<script>"', '<script type="module">"<\u0073cript>"</script>' ), 538 'Tag with type' => array( '<script type="text/javascript"></script>', '"<script>"', '<script type="text/javascript">"<\u0073cript>"</script>' ), 539 'Tag with language' => array( '<script language="javascript"></script>', '"<script>"', '<script language="javascript">"<\u0073cript>"</script>' ), 540 'Non-JS script, save HTML-like content' => array( '<script type="text/html"></script>', '<h1>This & that</h1>', '<script type="text/html"><h1>This & that</h1></script>' ), 541 ); 542 } 543 544 /** 545 * @ticket 64419 546 */ 547 public function test_complex_javascript_and_json_auto_escaping() { 548 $processor = new WP_HTML_Tag_Processor( "<script></script>\n<script></script>\n<hr>" ); 549 $processor->next_tag( 'SCRIPT' ); 550 $processor->set_attribute( 'type', 'importmap' ); 551 $importmap_data = array( 552 'imports' => array( 553 '</SCRIPT>\\<!--\\<script>' => './script', 554 ), 555 ); 556 557 $importmap = json_encode( 558 $importmap_data, 559 JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_LINE_TERMINATORS 560 ); 561 562 $processor->set_modifiable_text( "\n{$importmap}\n" ); 563 $decoded_importmap = json_decode( $processor->get_modifiable_text(), true ); 564 $this->assertSame( JSON_ERROR_NONE, json_last_error(), 'JSON failed to decode correctly.' ); 565 $this->assertEquals( $importmap_data, $decoded_importmap ); 566 $processor->next_tag( 'SCRIPT' ); 567 $processor->set_attribute( 'type', 'module' ); 568 $javascript = <<<'JS' 569 import '</SCRIPT>\\<!--\\<script>'; 570 JS; 571 $processor->set_modifiable_text( "\n{$javascript}\n" ); 572 573 $expected = <<<'HTML' 574 <script type="importmap"> 575 {"imports":{"</\u0053CRIPT>\\<!--\\<\u0073cript>":"./script"}} 576 </script> 577 <script type="module"> 578 import '</\u0053CRIPT>\\<!--\\<\u0073cript>'; 579 </script> 580 <hr> 581 HTML; 582 583 $updated_html = $processor->get_updated_html(); 584 $this->assertEqualHTML( $expected, $updated_html ); 585 586 // Reprocess to ensure JSON survives HTML round-trip: 587 $processor = new WP_HTML_Tag_Processor( $updated_html ); 588 $processor->next_tag( 'SCRIPT' ); 589 $this->assertSame( 'importmap', $processor->get_attribute( 'type' ) ); 590 $importmap_json = $processor->get_modifiable_text(); 591 $decoded_importmap = json_decode( $importmap_json, true ); 592 $this->assertSame( JSON_ERROR_NONE, json_last_error(), 'Importmap JSON failed to decode.' ); 593 $this->assertEquals( 594 $importmap_data, 595 $decoded_importmap, 596 'JSON was not equal after re-processing updated HTML.' 597 ); 598 } 599 600 /** 601 * @ticket 64419 602 */ 603 public function test_json_auto_escaping() { 604 // This is not a typical JSON encoding or escaping, but it is valid. 605 $json_text = '"Escaped BS: \\\\; Escaped BS+LT: \\\\<; Unescaped LT: <; Script closer: </script>"'; 606 $expected_decoded_json = 'Escaped BS: \\; Escaped BS+LT: \\<; Unescaped LT: <; Script closer: </script>'; 607 $decoded_json = json_decode( $json_text ); 608 $this->assertSame( JSON_ERROR_NONE, json_last_error(), 'JSON failed to decode.' ); 609 $this->assertSame( 610 $expected_decoded_json, 611 $decoded_json, 612 'Decoded JSON did not match expected value.' 613 ); 614 615 $processor = new WP_HTML_Tag_Processor( '<script type="application/json"></script>' ); 616 $processor->next_tag( 'SCRIPT' ); 617 618 $processor->set_modifiable_text( "\n{$json_text}\n" ); 619 620 $expected = <<<'HTML' 621 <script type="application/json"> 622 "Escaped BS: \\; Escaped BS+LT: \\<; Unescaped LT: <; Script closer: </\u0073cript>" 623 </script> 624 HTML; 625 626 $updated_html = $processor->get_updated_html(); 627 $this->assertEqualHTML( $expected, $updated_html ); 628 629 // Reprocess to ensure JSON value survives HTML round-trip: 630 $processor = new WP_HTML_Tag_Processor( $updated_html ); 631 $processor->next_tag( 'SCRIPT' ); 632 $decoded_json_from_html = json_decode( $processor->get_modifiable_text(), true ); 633 $this->assertSame( JSON_ERROR_NONE, json_last_error(), 'JSON failed to decode.' ); 634 $this->assertEquals( 635 $expected_decoded_json, 636 $decoded_json_from_html 494 637 ); 495 638 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)