File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 254254add_filter ( 'wp_get_custom_css ' , 'wp_replace_insecure_home_url ' );
255255
256256// RSS filters.
257- add_filter ( ' the_title_rss ' , ' strip_tags ' );
257+
258258add_filter ( 'the_title_rss ' , 'ent2ncr ' , 8 );
259- add_filter ( 'the_title_rss ' , 'esc_html ' );
259+ add_filter ( 'the_title_rss ' , 'wp_encode_bare_lt ' , 9 );
260+ add_filter ( 'the_title_rss ' , 'strip_tags ' , 10 );
261+ add_filter ( 'the_title_rss ' , 'esc_html ' , 11 );
260262add_filter ( 'the_content_rss ' , 'ent2ncr ' , 8 );
261263add_filter ( 'the_content_feed ' , 'wp_staticize_emoji ' );
262264add_filter ( 'the_content_feed ' , '_oembed_filter_feed_content ' );
Original file line number Diff line number Diff line change @@ -5509,6 +5509,29 @@ function normalize_whitespace( $str ) {
55095509 return $ str ;
55105510}
55115511
5512+ /**
5513+ * Encodes bare `<` characters (those not starting an HTML tag) as `<`.
5514+ *
5515+ * @since x.x.x
5516+ *
5517+ * @param string $text The text to encode.
5518+ * @return string Text with bare `<` characters encoded.
5519+ */
5520+ function wp_encode_bare_lt ( $ text ) {
5521+ /*
5522+ * A valid HTML tag begins with `<` followed by:
5523+ * - a letter (opening tag, e.g. `<strong>`)
5524+ * - `/` (closing tag, e.g. `</strong>`)
5525+ * - `!` (comment or doctype, e.g. `<!-- … -->`)
5526+ * - `?` (processing instruction, e.g. `<?xml … ?>`)
5527+ *
5528+ * Any `<` not followed by one of these is a bare less-than sign and is
5529+ * encoded as `<` so that strip_tags() does not silently discard it.
5530+ */
5531+ $ encoded = preg_replace ( '#<(?![a-zA-Z/!?])# ' , '< ' , $ text );
5532+ return null !== $ encoded ? $ encoded : $ text ;
5533+ }
5534+
55125535/**
55135536 * Properly strips all HTML tags including 'script' and 'style'.
55145537 *
Original file line number Diff line number Diff line change @@ -294,4 +294,30 @@ public function test_atom_enclosure_with_extended_url_length_type_parsing() {
294294 }
295295 }
296296 }
297+
298+ /**
299+ * Tests that a bare `<` in a post title is encoded inside the Atom feed CDATA block, not stripped.
300+ *
301+ * Atom wraps `the_title_rss()` output in `<![CDATA[…]]>`. This test verifies
302+ * the encoded `<` character survives into that block by writing the raw title
303+ * directly to the database, bypassing kses sanitization on insert.
304+ *
305+ * @ticket 9993
306+ *
307+ * @covers ::get_the_title_rss
308+ */
309+ public function test_atom_title_encodes_bare_lt_in_cdata () {
310+ global $ wpdb ;
311+
312+ $ post_id = self ::factory ()->post ->create ();
313+
314+ $ wpdb ->update ( $ wpdb ->posts , array ( 'post_title ' => '& > test < ' ), array ( 'ID ' => $ post_id ) );
315+ clean_post_cache ( $ post_id );
316+
317+ $ this ->go_to ( '/?feed=atom&p= ' . $ post_id );
318+ $ feed = $ this ->do_atom ();
319+
320+ $ this ->assertStringContainsString ( 'test < ' , $ feed , 'Bare `<` was stripped from the Atom feed title CDATA block. ' );
321+ $ this ->assertStringNotContainsString ( 'test ]]> ' , $ feed , 'Bare `<` must not be stripped (leaving title truncated before `]]>`). ' );
322+ }
297323}
Original file line number Diff line number Diff line change @@ -629,4 +629,42 @@ function ( $headers ) use ( $today ) {
629629
630630 $ this ->go_to ( '/?feed=rss2&withcomments=1 ' );
631631 }
632+
633+ /**
634+ * Tests that the `the_title_rss` filter encodes bare `<` characters rather than stripping them.
635+ *
636+ * @ticket 9993
637+ * @dataProvider data_title_rss_encodes_special_characters
638+ *
639+ * @covers ::get_the_title_rss
640+ */
641+ public function test_title_rss_encodes_special_characters ( $ title , $ expected ) {
642+ $ this ->assertSame ( $ expected , apply_filters ( 'the_title_rss ' , $ title ) );
643+ }
644+
645+ /**
646+ * Data provider for test_title_rss_encodes_special_characters().
647+ *
648+ * @return array[]
649+ */
650+ public static function data_title_rss_encodes_special_characters () {
651+ return array (
652+ 'bare less-than at end is encoded not stripped ' => array (
653+ '& > test < ' ,
654+ '& > test < ' ,
655+ ),
656+ 'bare less-than in middle is encoded ' => array (
657+ 'a < b ' ,
658+ 'a < b ' ,
659+ ),
660+ 'html tags are still stripped by strip_tags ' => array (
661+ '<strong>bold</strong> ' ,
662+ 'bold ' ,
663+ ),
664+ 'plain text passes through unchanged ' => array (
665+ 'Hello World ' ,
666+ 'Hello World ' ,
667+ ),
668+ );
669+ }
632670}
You can’t perform that action at this time.
0 commit comments