Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 231
0.00% covered (danger)
0.00%
0 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 2
get_available_languages
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
VaultPress_Hotfixes
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 12
1406
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 protect_rest_type_juggling
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 protect_youtube_embeds
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 safe_embed_handler_youtube
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 disable_jetpack_oembed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_pagenum_link
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 r20493_make_url_clickable_cb
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 r20493_split_str_by_whitespace
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 r18346_sanitize_admin_email
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 r18346_sanitize_lang
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 protect_aioseo_ajax
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
56
 protect_woocommerce_paypal_object_injection
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
72
VaultPress_kses
0.00% covered (danger)
0.00%
0 / 158
0.00% covered (danger)
0.00%
0 / 9
3422
0.00% covered (danger)
0.00%
0 / 1
 wp_kses
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 wp_kses_split
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 _vp_kses_split_callback
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 wp_kses_split2
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
72
 wp_kses_attr
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
182
 wp_kses_hair
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 1
462
 wp_kses_bad_protocol
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 wp_kses_bad_protocol_once
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 wp_kses_bad_protocol_once2
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2// don't call the file directly
3defined( 'ABSPATH' ) || die( 0 );
4
5class VaultPress_Hotfixes {
6    function __construct() {
7
8        add_filter( 'option_new_admin_email', array( $this, 'r18346_sanitize_admin_email' ) );
9
10        add_filter( 'get_pagenum_link', array( $this, 'get_pagenum_link' ) );
11
12        // Protect All-in-one SEO from non-authorized users making changes, and script injection attacks.
13        add_action( 'wp_ajax_aioseop_ajax_save_meta', array( $this, 'protect_aioseo_ajax' ), 1 );
14
15        // Protect WooCommerce from object injection via PayPal IPN notifications. Affects 2.0.20 -> 2.3.10
16        add_action( 'init', array( $this , 'protect_woocommerce_paypal_object_injection' ), 1 );
17    }
18
19    function protect_rest_type_juggling( $replace, $server, $request ) {
20        if ( isset( $request['id'] ) ) {
21            $request['id'] = intval( $request['id'] );
22        }
23
24        return $replace;
25    }
26
27    function protect_youtube_embeds() {
28        if ( ! apply_filters( 'load_default_embeds', true ) ) {
29            return;
30        }
31
32        wp_embed_unregister_handler( 'youtube_embed_url' );
33        wp_embed_register_handler( 'youtube_embed_url', '#https?://(www.)?youtube\.com/(?:v|embed)/([^/]+)#i', array( $this, 'safe_embed_handler_youtube' ), 9 );
34    }
35
36    function safe_embed_handler_youtube( $matches, $attr, $url, $rawattr ) {
37        $matches[2] = urlencode( $matches[2] );
38        return( wp_embed_handler_youtube( $matches, $attr, $url, $rawattr ) );
39    }
40
41    function disable_jetpack_oembed( $enabled ) {
42        return false;
43    }
44
45    function get_pagenum_link( $url ) {
46        return esc_url_raw( $url );
47    }
48
49    function r20493_make_url_clickable_cb($matches) {
50        $url = $matches[2];
51
52        if ( ')' == $matches[3] && strpos( $url, '(' ) ) {
53            // If the trailing character is a closing parethesis, and the URL has an opening parenthesis in it, add the closing parenthesis to the URL.
54            // Then we can let the parenthesis balancer do its thing below.
55            $url .= $matches[3];
56            $suffix = '';
57        } else {
58            $suffix = $matches[3];
59        }
60
61        // Include parentheses in the URL only if paired
62        while ( substr_count( $url, '(' ) < substr_count( $url, ')' ) ) {
63            $suffix = strrchr( $url, ')' ) . $suffix;
64            $url = substr( $url, 0, strrpos( $url, ')' ) );
65        }
66
67        $url = esc_url($url);
68        if ( empty($url) )
69            return $matches[0];
70
71        return $matches[1] . "<a href=\"$url\" rel=\"nofollow\">$url</a>" . $suffix;
72    }
73
74    function r20493_split_str_by_whitespace( $string, $goal ) {
75        $chunks = array();
76
77        $string_nullspace = strtr( $string, "\r\n\t\v\f ", "\000\000\000\000\000\000" );
78
79        while ( $goal < strlen( $string_nullspace ) ) {
80            $pos = strrpos( substr( $string_nullspace, 0, $goal + 1 ), "\000" );
81
82            if ( false === $pos ) {
83                $pos = strpos( $string_nullspace, "\000", $goal + 1 );
84                if ( false === $pos ) {
85                    break;
86                }
87            }
88
89            $chunks[] = substr( $string, 0, $pos + 1 );
90            $string = substr( $string, $pos + 1 );
91            $string_nullspace = substr( $string_nullspace, $pos + 1 );
92        }
93
94        if ( $string ) {
95            $chunks[] = $string;
96        }
97
98        return $chunks;
99    }
100
101    function r18346_sanitize_admin_email( $value ) {
102        return sanitize_email( $value ); // Is it enough ?
103    }
104
105    function r18346_sanitize_lang( $value ) {
106        $allowed = apply_filters( 'available_languages', get_available_languages() ); // add a filter to unit test
107        if ( !empty( $value ) && !in_array( $value, $allowed ) )
108            return false;
109        else
110            return $value;
111    }
112
113    // Protect All-in-one SEO AJAX calls from script injection and changes without privileges. Affects versions <= 2.1.5
114    function protect_aioseo_ajax() {
115        if ( defined( 'AIOSEOP_VERSION' ) && version_compare( AIOSEOP_VERSION, '2.1.5', '>' ) )
116            return;
117
118        if ( ! isset( $_POST['post_id'] ) || ! isset( $_POST['target_meta'] ) )
119            die( 0 );
120
121        // Ensure the current user has permission to write to the post.
122        if ( ! current_user_can( 'edit_post', intval( $_POST['post_id'] ) ) )
123            die( 0 );
124
125        // Limit the fields that can be written to
126        if ( ! in_array( $_POST['target_meta'], array( 'title', 'description', 'keywords' ) ) )
127            die( 0 );
128
129        // Strip tags from the metadata value.
130        $_POST['new_meta'] = strip_tags( $_POST['new_meta'] );
131    }
132
133    // Protect WooCommerce 2.0.20 - 2.3.10 from PayPal IPN object injection attack.
134    function protect_woocommerce_paypal_object_injection() {
135        global $woocommerce;
136        if ( ! isset( $woocommerce ) )
137            return;
138
139        $wc_version = $woocommerce->version;
140        if ( version_compare( $wc_version, '2.0.20', '<' ) || version_compare( $wc_version, '2.3.11', '>=' ) )
141            return;
142
143        if ( isset( $_REQUEST['paypalListener'] ) ) {
144            $check_fields = array( 'custom', 'cm' );
145            foreach ( $check_fields as $field ) {
146                if ( isset( $_REQUEST[ $field ] ) && preg_match( '/[CO]:\+?[0-9]+:/', $_REQUEST[ $field ] ) ) {
147                    die( 0 );
148                }
149            }
150        }
151    }
152}
153
154class VaultPress_kses {
155    static function wp_kses($string, $allowed_html, $allowed_protocols = array ()) {
156        $string = wp_kses_no_null($string);
157        $string = wp_kses_js_entities($string);
158        $string = wp_kses_normalize_entities($string);
159        return VaultPress_kses::wp_kses_split($string, $allowed_html, $allowed_protocols);
160    }
161
162    static function wp_kses_split($string, $allowed_html, $allowed_protocols) {
163        global $pass_allowed_html, $pass_allowed_protocols;
164        $pass_allowed_html = $allowed_html;
165        $pass_allowed_protocols = $allowed_protocols;
166        return preg_replace_callback( '%(<!--.*?(-->|$))|(<[^>]*(>|$)|>)%', 'VaultPress_kses::_vp_kses_split_callback', $string );
167    }
168
169    static function _vp_kses_split_callback( $match ) {
170        global $pass_allowed_html, $pass_allowed_protocols;
171        return VaultPress_kses::wp_kses_split2( $match[0], $pass_allowed_html, $pass_allowed_protocols );
172    }
173
174    static function wp_kses_split2($string, $allowed_html, $allowed_protocols) {
175        $string = wp_kses_stripslashes($string);
176
177        if (substr($string, 0, 1) != '<')
178            return '&gt;';
179        # It matched a ">" character
180
181        if ( '<!--' == substr( $string, 0, 4 ) ) {
182            $string = str_replace( array('<!--', '-->'), '', $string );
183            while ( $string != ($newstring = VaultPress_kses::wp_kses($string, $allowed_html, $allowed_protocols)) )
184                $string = $newstring;
185            if ( $string == '' )
186                return '';
187            // prevent multiple dashes in comments
188            $string = preg_replace('/--+/', '-', $string);
189            // prevent three dashes closing a comment
190            $string = preg_replace('/-$/', '', $string);
191            return "<!--{$string}-->";
192        }
193        # Allow HTML comments
194
195        if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $string, $matches))
196            return '';
197        # It's seriously malformed
198
199        $slash = trim($matches[1]);
200        $elem = $matches[2];
201        $attrlist = $matches[3];
202
203        if ( ! isset($allowed_html[strtolower($elem)]) )
204            return '';
205        # They are using a not allowed HTML element
206
207        if ($slash != '')
208            return "</$elem>";
209        # No attributes are allowed for closing elements
210
211        return VaultPress_kses::wp_kses_attr( $elem, $attrlist, $allowed_html, $allowed_protocols );
212    }
213
214    static function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) {
215        # Is there a closing XHTML slash at the end of the attributes?
216
217        $xhtml_slash = '';
218        if (preg_match('%\s*/\s*$%', $attr))
219            $xhtml_slash = ' /';
220
221        # Are any attributes allowed at all for this element?
222        if ( ! isset($allowed_html[strtolower($element)]) || count($allowed_html[strtolower($element)]) == 0 )
223            return "<$element$xhtml_slash>";
224
225        # Split it
226        $attrarr = VaultPress_kses::wp_kses_hair($attr, $allowed_protocols);
227
228        # Go through $attrarr, and save the allowed attributes for this element
229        # in $attr2
230        $attr2 = '';
231
232        $allowed_attr = $allowed_html[strtolower($element)];
233        foreach ($attrarr as $arreach) {
234            if ( ! isset( $allowed_attr[strtolower($arreach['name'])] ) )
235                continue; # the attribute is not allowed
236
237            $current = $allowed_attr[strtolower($arreach['name'])];
238            if ( $current == '' )
239                continue; # the attribute is not allowed
240
241            if ( strtolower( $arreach['name'] ) == 'style' ) {
242                $orig_value = $arreach['value'];
243                $value = safecss_filter_attr( $orig_value );
244
245                if ( empty( $value ) )
246                    continue;
247
248                $arreach['value'] = $value;
249                $arreach['whole'] = str_replace( $orig_value, $value, $arreach['whole'] );
250            }
251
252            if ( ! is_array($current) ) {
253                $attr2 .= ' '.$arreach['whole'];
254            # there are no checks
255
256            } else {
257                # there are some checks
258                $ok = true;
259                foreach ($current as $currkey => $currval) {
260                    if ( ! wp_kses_check_attr_val($arreach['value'], $arreach['vless'], $currkey, $currval) ) {
261                        $ok = false;
262                        break;
263                    }
264                }
265
266                if ( $ok )
267                    $attr2 .= ' '.$arreach['whole']; # it passed them
268            } # if !is_array($current)
269        } # foreach
270
271        # Remove any "<" or ">" characters
272        $attr2 = preg_replace('/[<>]/', '', $attr2);
273
274        return "<$element$attr2$xhtml_slash>";
275    }
276
277    static function wp_kses_hair($attr, $allowed_protocols) {
278        $attrarr = array ();
279        $mode = 0;
280        $attrname = '';
281        $uris = array('xmlns', 'profile', 'href', 'src', 'cite', 'classid', 'codebase', 'data', 'usemap', 'longdesc', 'action');
282
283        # Loop through the whole attribute list
284
285        while (strlen($attr) != 0) {
286            $working = 0; # Was the last operation successful?
287
288            switch ($mode) {
289                case 0 : # attribute name, href for instance
290
291                    if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
292                        $attrname = $match[1];
293                        $working = $mode = 1;
294                        $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
295                    }
296
297                    break;
298
299                case 1 : # equals sign or valueless ("selected")
300
301                    if (preg_match('/^\s*=\s*/', $attr)) # equals sign
302                        {
303                        $working = 1;
304                        $mode = 2;
305                        $attr = preg_replace('/^\s*=\s*/', '', $attr);
306                        break;
307                    }
308
309                    if (preg_match('/^\s+/', $attr)) # valueless
310                        {
311                        $working = 1;
312                        $mode = 0;
313                        if(false === array_key_exists($attrname, $attrarr)) {
314                            $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y');
315                        }
316                        $attr = preg_replace('/^\s+/', '', $attr);
317                    }
318
319                    break;
320
321                case 2 : # attribute value, a URL after href= for instance
322
323                    if (preg_match('%^"([^"]*)"(\s+|/?$)%', $attr, $match))
324                        # "value"
325                        {
326                        $thisval = $match[1];
327                        if ( in_array(strtolower($attrname), $uris) )
328                            $thisval = VaultPress_kses::wp_kses_bad_protocol($thisval, $allowed_protocols);
329
330                        if(false === array_key_exists($attrname, $attrarr)) {
331                            $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n');
332                        }
333                        $working = 1;
334                        $mode = 0;
335                        $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
336                        break;
337                    }
338
339                    if (preg_match("%^'([^']*)'(\s+|/?$)%", $attr, $match))
340                        # 'value'
341                        {
342                        $thisval = $match[1];
343                        if ( in_array(strtolower($attrname), $uris) )
344                            $thisval = VaultPress_kses::wp_kses_bad_protocol($thisval, $allowed_protocols);
345
346                        if(false === array_key_exists($attrname, $attrarr)) {
347                            $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname='$thisval'", 'vless' => 'n');
348                        }
349                        $working = 1;
350                        $mode = 0;
351                        $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
352                        break;
353                    }
354
355                    if (preg_match("%^([^\s\"']+)(\s+|/?$)%", $attr, $match))
356                        # value
357                        {
358                        $thisval = $match[1];
359                        if ( in_array(strtolower($attrname), $uris) )
360                            $thisval = VaultPress_kses::wp_kses_bad_protocol($thisval, $allowed_protocols);
361
362                        if(false === array_key_exists($attrname, $attrarr)) {
363                            $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n');
364                        }
365                        # We add quotes to conform to W3C's HTML spec.
366                        $working = 1;
367                        $mode = 0;
368                        $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
369                    }
370
371                    break;
372            } # switch
373
374            if ($working == 0) # not well formed, remove and try again
375            {
376                $attr = wp_kses_html_error($attr);
377                $mode = 0;
378            }
379        } # while
380
381        if ($mode == 1 && false === array_key_exists($attrname, $attrarr))
382            # special case, for when the attribute list ends with a valueless
383            # attribute like "selected"
384            $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y');
385
386        return $attrarr;
387    }
388
389    static function wp_kses_bad_protocol($string, $allowed_protocols) {
390        $string = wp_kses_no_null($string);
391        $iterations = 0;
392
393        do {
394            $original_string = $string;
395            $string = VaultPress_kses::wp_kses_bad_protocol_once($string, $allowed_protocols);
396        } while ( $original_string != $string && ++$iterations < 6 );
397
398        if ( $original_string != $string )
399            return '';
400
401        return $string;
402    }
403
404    static function wp_kses_bad_protocol_once($string, $allowed_protocols, $count = 1) {
405        $string2 = preg_split( '/:|&#0*58;|&#x0*3a;/i', $string, 2 );
406        if ( isset($string2[1]) && ! preg_match('%/\?%', $string2[0]) ) {
407            $string = trim( $string2[1] );
408            $protocol = VaultPress_kses::wp_kses_bad_protocol_once2( $string2[0], $allowed_protocols );
409            if ( 'feed:' == $protocol ) {
410                if ( $count > 2 )
411                    return '';
412                $string = VaultPress_kses::wp_kses_bad_protocol_once( $string, $allowed_protocols, ++$count );
413                if ( empty( $string ) )
414                    return $string;
415            }
416            $string = $protocol . $string;
417        }
418
419        return $string;
420    }
421
422    static function wp_kses_bad_protocol_once2( $string, $allowed_protocols ) {
423        $string2 = wp_kses_decode_entities($string);
424        $string2 = preg_replace('/\s/', '', $string2);
425        $string2 = wp_kses_no_null($string2);
426        $string2 = strtolower($string2);
427
428        $allowed = false;
429        foreach ( (array) $allowed_protocols as $one_protocol ) {
430            if ( strtolower( $one_protocol ) == $string2 ) {
431                $allowed = true;
432                break;
433            }
434        }
435
436        if ($allowed)
437            return "$string2:";
438        else
439            return '';
440    }
441}
442
443if ( !function_exists( 'get_available_languages' ) ) {
444    function get_available_languages( $dir = null ) {
445        $languages = array();
446        foreach( glob( ( is_null( $dir) ? WP_LANG_DIR : $dir ) . '/*.mo' ) as $lang_file )
447            if ( false === strpos( $lang_file, 'continents-cities' ) )
448                $languages[] = basename($lang_file, '.mo');
449        return $languages;
450    }
451}