Changeset 46898
- Timestamp:
- 12/12/2019 06:08:36 PM (5 years ago)
- Location:
- branches/5.3
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/5.3/src/wp-includes/formatting.php
r46564 r46898 3147 3147 function wp_targeted_link_rel( $text ) { 3148 3148 // Don't run (more expensive) regex if no links with targets. 3149 if ( stripos( $text, 'target' ) !== false && stripos( $text, '<a ' ) !== false ) { 3150 if ( ! is_serialized( $text ) ) { 3151 $text = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $text ); 3149 if ( stripos( $text, 'target' ) === false || stripos( $text, '<a ' ) === false || is_serialized( $text ) ) { 3150 return $text; 3151 } 3152 3153 $script_and_style_regex = '/<(script|style).*?<\/\\1>/si'; 3154 3155 preg_match_all( $script_and_style_regex, $text, $matches ); 3156 $extra_parts = $matches[0]; 3157 $html_parts = preg_split( $script_and_style_regex, $text ); 3158 3159 foreach ( $html_parts as &$part ) { 3160 $part = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $part ); 3161 } 3162 3163 $text = ''; 3164 for ( $i = 0; $i < count( $html_parts ); $i++ ) { 3165 $text .= $html_parts[ $i ]; 3166 if ( isset( $extra_parts[ $i ] ) ) { 3167 $text .= $extra_parts[ $i ]; 3152 3168 } 3153 3169 } … … 3168 3184 */ 3169 3185 function wp_targeted_link_rel_callback( $matches ) { 3170 $link_html = $matches[1]; 3171 $rel_match = array(); 3186 $link_html = $matches[1]; 3187 $original_link_html = $link_html; 3188 3189 // Consider the html escaped if there are no unescaped quotes 3190 $is_escaped = ! preg_match( '/(^|[^\\\\])[\'"]/', $link_html ); 3191 if ( $is_escaped ) { 3192 // Replace only the quotes so that they are parsable by wp_kses_hair, leave the rest as is 3193 $link_html = preg_replace( '/\\\\([\'"])/', '$1', $link_html ); 3194 } 3195 3196 $atts = wp_kses_hair( $link_html, wp_allowed_protocols() ); 3172 3197 3173 3198 /** … … 3181 3206 $rel = apply_filters( 'wp_targeted_link_rel', 'noopener noreferrer', $link_html ); 3182 3207 3183 // Avoid additional regex if the filter removes rel values. 3184 if ( ! $rel ) { 3185 return "<a $link_html>"; 3186 } 3187 3188 // Value with delimiters, spaces around are optional. 3189 $attr_regex = '|rel\s*=\s*?(\\\\{0,1}["\'])(.*?)\\1|i'; 3190 preg_match( $attr_regex, $link_html, $rel_match ); 3191 3192 if ( empty( $rel_match[0] ) ) { 3193 // No delimiters, try with a single value and spaces, because `rel = va"lue` is totally fine... 3194 $attr_regex = '|rel\s*=(\s*)([^\s]*)|i'; 3195 preg_match( $attr_regex, $link_html, $rel_match ); 3196 } 3197 3198 if ( ! empty( $rel_match[0] ) ) { 3199 $parts = preg_split( '|\s+|', strtolower( $rel_match[2] ) ); 3200 $parts = array_map( 'esc_attr', $parts ); 3201 $needed = explode( ' ', $rel ); 3202 $parts = array_unique( array_merge( $parts, $needed ) ); 3203 $delimiter = trim( $rel_match[1] ) ? $rel_match[1] : '"'; 3204 $rel = 'rel=' . $delimiter . trim( implode( ' ', $parts ) ) . $delimiter; 3205 $link_html = str_replace( $rel_match[0], $rel, $link_html ); 3206 } elseif ( preg_match( '|target\s*=\s*?\\\\"|', $link_html ) ) { 3207 $link_html .= " rel=\\\"$rel\\\""; 3208 } elseif ( preg_match( '#(target|href)\s*=\s*?\'#', $link_html ) ) { 3209 $link_html .= " rel='$rel'"; 3210 } else { 3211 $link_html .= " rel=\"$rel\""; 3208 // Return early if no rel values to be added or if no actual target attribute 3209 if ( ! $rel || ! isset( $atts['target'] ) ) { 3210 return "<a $original_link_html>"; 3211 } 3212 3213 if ( isset( $atts['rel'] ) ) { 3214 $all_parts = preg_split( '/\s/', "{$atts['rel']['value']} $rel", -1, PREG_SPLIT_NO_EMPTY ); 3215 $rel = implode( ' ', array_unique( $all_parts ) ); 3216 } 3217 3218 $atts['rel']['whole'] = 'rel="' . esc_attr( $rel ) . '"'; 3219 $link_html = join( ' ', array_column( $atts, 'whole' ) ); 3220 3221 if ( $is_escaped ) { 3222 $link_html = preg_replace( '/[\'"]/', '\\\\$0', $link_html ); 3212 3223 } 3213 3224 -
branches/5.3/tests/phpunit/tests/formatting/WPTargetedLinkRel.php
r45409 r46898 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.