Changeset 46894
- Timestamp:
- 12/12/2019 05:51:35 PM (5 years ago)
- Location:
- trunk
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/formatting.php
r46660 r46894 3149 3149 function wp_targeted_link_rel( $text ) { 3150 3150 // Don't run (more expensive) regex if no links with targets. 3151 if ( stripos( $text, 'target' ) !== false && stripos( $text, '<a ' ) !== false ) { 3152 if ( ! is_serialized( $text ) ) { 3153 $text = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $text ); 3151 if ( stripos( $text, 'target' ) === false || stripos( $text, '<a ' ) === false || is_serialized( $text ) ) { 3152 return $text; 3153 } 3154 3155 $script_and_style_regex = '/<(script|style).*?<\/\\1>/si'; 3156 3157 preg_match_all( $script_and_style_regex, $text, $matches ); 3158 $extra_parts = $matches[0]; 3159 $html_parts = preg_split( $script_and_style_regex, $text ); 3160 3161 foreach ( $html_parts as &$part ) { 3162 $part = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $part ); 3163 } 3164 3165 $text = ''; 3166 for ( $i = 0; $i < count( $html_parts ); $i++ ) { 3167 $text .= $html_parts[ $i ]; 3168 if ( isset( $extra_parts[ $i ] ) ) { 3169 $text .= $extra_parts[ $i ]; 3154 3170 } 3155 3171 } … … 3170 3186 */ 3171 3187 function wp_targeted_link_rel_callback( $matches ) { 3172 $link_html = $matches[1]; 3173 $rel_match = array(); 3188 $link_html = $matches[1]; 3189 $original_link_html = $link_html; 3190 3191 // Consider the html escaped if there are no unescaped quotes 3192 $is_escaped = ! preg_match( '/(^|[^\\\\])[\'"]/', $link_html ); 3193 if ( $is_escaped ) { 3194 // Replace only the quotes so that they are parsable by wp_kses_hair, leave the rest as is 3195 $link_html = preg_replace( '/\\\\([\'"])/', '$1', $link_html ); 3196 } 3197 3198 $atts = wp_kses_hair( $link_html, wp_allowed_protocols() ); 3174 3199 3175 3200 /** … … 3183 3208 $rel = apply_filters( 'wp_targeted_link_rel', 'noopener noreferrer', $link_html ); 3184 3209 3185 // Avoid additional regex if the filter removes rel values. 3186 if ( ! $rel ) { 3187 return "<a $link_html>"; 3188 } 3189 3190 // Value with delimiters, spaces around are optional. 3191 $attr_regex = '|rel\s*=\s*?(\\\\{0,1}["\'])(.*?)\\1|i'; 3192 preg_match( $attr_regex, $link_html, $rel_match ); 3193 3194 if ( empty( $rel_match[0] ) ) { 3195 // No delimiters, try with a single value and spaces, because `rel = va"lue` is totally fine... 3196 $attr_regex = '|rel\s*=(\s*)([^\s]*)|i'; 3197 preg_match( $attr_regex, $link_html, $rel_match ); 3198 } 3199 3200 if ( ! empty( $rel_match[0] ) ) { 3201 $parts = preg_split( '|\s+|', strtolower( $rel_match[2] ) ); 3202 $parts = array_map( 'esc_attr', $parts ); 3203 $needed = explode( ' ', $rel ); 3204 $parts = array_unique( array_merge( $parts, $needed ) ); 3205 $delimiter = trim( $rel_match[1] ) ? $rel_match[1] : '"'; 3206 $rel = 'rel=' . $delimiter . trim( implode( ' ', $parts ) ) . $delimiter; 3207 $link_html = str_replace( $rel_match[0], $rel, $link_html ); 3208 } elseif ( preg_match( '|target\s*=\s*?\\\\"|', $link_html ) ) { 3209 $link_html .= " rel=\\\"$rel\\\""; 3210 } elseif ( preg_match( '#(target|href)\s*=\s*?\'#', $link_html ) ) { 3211 $link_html .= " rel='$rel'"; 3212 } else { 3213 $link_html .= " rel=\"$rel\""; 3210 // Return early if no rel values to be added or if no actual target attribute 3211 if ( ! $rel || ! isset( $atts['target'] ) ) { 3212 return "<a $original_link_html>"; 3213 } 3214 3215 if ( isset( $atts['rel'] ) ) { 3216 $all_parts = preg_split( '/\s/', "{$atts['rel']['value']} $rel", -1, PREG_SPLIT_NO_EMPTY ); 3217 $rel = implode( ' ', array_unique( $all_parts ) ); 3218 } 3219 3220 $atts['rel']['whole'] = 'rel="' . esc_attr( $rel ) . '"'; 3221 $link_html = join( ' ', array_column( $atts, 'whole' ) ); 3222 3223 if ( $is_escaped ) { 3224 $link_html = preg_replace( '/[\'"]/', '\\\\$0', $link_html ); 3214 3225 } 3215 3226 -
trunk/tests/phpunit/tests/formatting/WPTargetedLinkRel.php
r46586 r46894 39 39 public function test_rel_with_single_quote_delimiter() { 40 40 $content = '<p>Links: <a href="/" rel=\'existing values\' target="_blank">Existing rel</a></p>'; 41 $expected = '<p>Links: <a href="/" rel= \'existing values noopener noreferrer\'target="_blank">Existing rel</a></p>';41 $expected = '<p>Links: <a href="/" rel="existing values noopener noreferrer" target="_blank">Existing rel</a></p>'; 42 42 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) ); 43 43 } … … 52 52 $content = '<p>Links: <a href="/" rel = existing target="_blank">Existing rel</a></p>'; 53 53 $expected = '<p>Links: <a href="/" rel="existing noopener noreferrer" target="_blank">Existing rel</a></p>'; 54 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) );55 }56 57 public function test_rel_value_spaced_and_no_delimiter_and_values_to_escape() {58 $content = '<p>Links: <a href="/" rel = existing"value target="_blank">Existing rel</a></p>';59 $expected = '<p>Links: <a href="/" rel="existing"value noopener noreferrer" target="_blank">Existing rel</a></p>';60 54 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) ); 61 55 } … … 115 109 116 110 /** 117 * Ensure correct quotes are used when relation attribute (rel) is missing.111 * Ensure the content of style and script tags are not processed 118 112 * 119 113 * @ticket 47244 120 114 */ 121 public function test_wp_targeted_link_rel_should_use_correct_quotes() { 122 $content = '<p>Links: <a href=\'\/\' target=\'_blank\'>No rel<\/a><\/p>'; 123 $expected = '<p>Links: <a href=\'\/\' target=\'_blank\' rel=\'noopener noreferrer\'>No rel<\/a><\/p>'; 124 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) ); 125 126 $content = '<p>Links: <a href=\'\/\' target=_blank>No rel<\/a><\/p>'; 127 $expected = '<p>Links: <a href=\'\/\' target=_blank rel=\'noopener noreferrer\'>No rel<\/a><\/p>'; 115 public function test_wp_targeted_link_rel_skips_style_and_scripts() { 116 $content = '<style><a href="/" target=a></style><p>Links: <script>console.log("<a href=\'/\' target=a>hi</a>");</script><script>alert(1);</script>here <a href="/" target=_blank>aq</a></p><script>console.log("<a href=\'last\' target=\'_blank\'")</script>'; 117 $expected = '<style><a href="/" target=a></style><p>Links: <script>console.log("<a href=\'/\' target=a>hi</a>");</script><script>alert(1);</script>here <a href="/" target="_blank" rel="noopener noreferrer">aq</a></p><script>console.log("<a href=\'last\' target=\'_blank\'")</script>'; 128 118 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) ); 129 119 } … … 140 130 } 141 131 132 public function test_wp_targeted_link_rel_tab_separated_values_are_split() { 133 $content = "<p>Links: <a href=\"/\" target=\"_blank\" rel=\"ugc\t\tnoopener\t\">No rel</a></p>"; 134 $expected = '<p>Links: <a href="/" target="_blank" rel="ugc noopener noreferrer">No rel</a></p>'; 135 $this->assertEquals( $expected, wp_targeted_link_rel( $content ) ); 136 } 137 142 138 }
Note: See TracChangeset
for help on using the changeset viewer.