diff --git a/README.md b/README.md index 44b8cb8..d07f084 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ This plugin adds customizable blacklist to Statify to allow blocking of referer #### Referer Blacklist #### Add a list of domains (for simplicity only second-level, e.g. _example.com_ which blocks _everything.example.com_). +#### IP Blacklist #### +Add a list of IP addresses or subnets (e.g. _192.0.2.123_, _198.51.100.0/24_, _2001:db8:a0b:12f0::/64_). + #### CleanUp Database #### Filters can be applied to data stored in database after modifying filter rules or for one-time clean-up. @@ -42,7 +45,7 @@ The plugin is capable of handling multisite installations. Nothing. By default all blacklists are empty and disabled. They can and have to be filled by the blog administrator. A default blacklist is not provided, as the plugin itself is totally neutral. If you want to filter out referer spam, -visitors from search engines or just "false" referers from 301 redirects only depends on you. +visitors from search engines, just "false" referers from 301 redirects or you own IP address used for testing only depends on you. ### Does the filter effect user experience? ### No. It only prevent's _Statify_ from tracking, nothing more or less. @@ -59,12 +62,19 @@ Yes, it it. Just select if you want to filter using regular expressions case sen Note, that regular expression matching is significantly slower than the plain domain filter. Hence it is only recommended for asynchronous cron or manual execution and not for live filtering. +### Why is IP filtering only available as live filter? ### +As you might know, Statify does not store any personal information, including IP addresses in the database. +Because of this, an IP blacklist can only be applied while processing the request and not afterwards. + ## Screenshots ## 1. Statify Blacklist settings page ## Changelog ## +### 1.4.0 / work in progress ### +* IP blacklist implemented (#7) + ### 1.3.1 / 09.12.2016 ### * Continue filtering if no filter applies (#6) diff --git a/inc/statifyblacklist.class.php b/inc/statifyblacklist.class.php index aa18526..197d846 100644 --- a/inc/statifyblacklist.class.php +++ b/inc/statifyblacklist.class.php @@ -10,6 +10,9 @@ defined( 'ABSPATH' ) OR exit; * @version 1.4.0~dev */ class StatifyBlacklist { + + const VERSION_MAIN = 1.4; + /** * Plugin options * @@ -99,12 +102,26 @@ class StatifyBlacklist { public static function update_options( $options = null ) { self::$_options = wp_parse_args( get_option( 'statify-blacklist' ), - array( - 'active_referer' => 0, - 'cron_referer' => 0, - 'referer' => array(), - 'referer_regexp' => 0 - ) + self::defaultOptions() + ); + } + + /** + * Create default plugin configuration. + * + * @since 1.4.0 + * + * @return array the options array + */ + protected static function defaultOptions() { + return array( + 'active_referer' => 0, + 'cron_referer' => 0, + 'referer' => array(), + 'referer_regexp' => 0, + 'active_ip' => 0, + 'ip' => array(), + 'version' => self::VERSION_MAIN ); } @@ -114,34 +131,150 @@ class StatifyBlacklist { * @return TRUE if referer matches blacklist. * * @since 1.0.0 - * @changed 1.3.1 + * @changed 1.4.0 */ public static function apply_blacklist_filter() { - /* Skip if blacklist is inactive */ - if ( self::$_options['active_referer'] != 1 ) { - return NULL; + /* Referer blacklist */ + if ( isset( self::$_options['active_referer'] ) && self::$_options['active_referer'] != 0 ) { + /* Regular Expression filtering since 1.3.0 */ + if ( isset( self::$_options['referer_regexp'] ) && self::$_options['referer_regexp'] > 0 ) { + /* Get full referer string */ + $referer = ( isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : '' ); + /* Merge given regular expressions into one */ + $regexp = '/' . implode( "|", array_keys( self::$_options['referer'] ) ) . '/'; + if ( self::$_options['referer_regexp'] == 2 ) { + $regexp .= 'i'; + } + + /* Check blacklist (return NULL to continue filtering) */ + + return ( preg_match( $regexp, $referer ) === 1 ) ? true : null; + } else { + /* Extract relevant domain parts */ + $referer = strtolower( ( isset( $_SERVER['HTTP_REFERER'] ) ? parse_url( $_SERVER['HTTP_REFERER'], PHP_URL_HOST ) : '' ) ); + + /* Get blacklist */ + $blacklist = self::$_options['referer']; + + /* Check blacklist */ + if ( isset( $blacklist[ $referer ] ) ) { + return true; + } + } } - /* Regular Expression filtering since 1.3.0 */ - if ( isset(self::$_options['referer_regexp']) && self::$_options['referer_regexp'] > 0 ) { - /* Get full referer string */ - $referer = ( isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : '' ); - /* Merge given regular expressions into one */ - $regexp = '/' . implode( "|", array_keys( self::$_options['referer'] ) ) . '/'; - if ( self::$_options['referer_regexp'] == 2 ) { - $regexp .= 'i'; + /* IP blacklist (since 1.4.0) */ + if ( isset ( self::$_options['active_ip'] ) && self::$_options['active_ip'] != 0 ) { + if ( ( $ip = self::getIP() ) !== false ) { + foreach ( self::$_options['ip'] as $net ) { + if ( self::cidrMatch( $ip, $net ) ) { + return true; + } + } } - /* Check blacklist (return NULL to continue filtering) */ - return (preg_match( $regexp, $referer) === 1) ? true : NULL; - } else { - /* Extract relevant domain parts */ - $referer = strtolower( ( isset( $_SERVER['HTTP_REFERER'] ) ? parse_url( $_SERVER['HTTP_REFERER'], PHP_URL_HOST ) : '' ) ); + } - /* Get blacklist */ - $blacklist = self::$_options['referer']; + /* Skip and continue (return NULL), if all blacklists are inactive */ - /* Check blacklist (return NULL to continue filtering) */ - return isset($blacklist[ $referer]) ? true : NULL; + return null; + } + + /** + * Helper method to determine the client's IP address. + * If a proxy is used, the X-Real-IP or X-Forwarded-For header is checked, otherwise the default remote address. + * For performance reasons only the most common flags are checked. This might be even reduce by user configuration. + * Maybe some community feedback will ease the decision on that. + * + * @return string|bool the client's IP address or FALSE, if none could be determined + */ + private static function getIP() { + foreach ( + array( +// 'HTTP_CLIENT_IP', + 'HTTP_X_REAL_IP', + 'HTTP_X_FORWARDED_FOR', +// 'HTTP_X_FORWARDED', +// 'HTTP_X_CLUSTER_CLIENT_IP', +// 'HTTP_FORWARDED_FOR', +// 'HTTP_FORWARDED', + 'REMOTE_ADDR' + ) as $k + ) { + if ( isset( $_SERVER[ $k ] ) ) { + foreach ( explode( ',', $_SERVER[ $k ] ) as $ip ) { + if ( filter_var( $ip, FILTER_VALIDATE_IP ) !== false ) { + return $ip; + } + } + } + } + + return false; + } + + /** + * Helper function to check if an IP address matches a given subnet. + * + * @param $ip string IP address to check + * @param $net string IP address or subnet in CIDR notation + * + * @return bool TRUE, if the given IP addresses matches the given subnet + */ + private static function cidrMatch( $ip, $net ) { + if ( substr_count( $net, ':' ) > 1 ) { /* Check for IPv6 */ + if ( ! ( ( extension_loaded( 'sockets' ) && defined( 'AF_INET6' ) ) || @inet_pton( '::1' ) ) ) { + return false; + } + + if ( false !== strpos( $net, '/' ) ) { /* Parse CIDR subnet */ + list( $base, $mask ) = explode( '/', $net, 2 ); + + if ( $mask < 1 || $mask > 128 ) { + return false; + } + } else { + $base = $net; + $mask = 128; + } + + $bytesAddr = unpack( 'n*', @inet_pton( $base ) ); + $bytesTest = unpack( 'n*', @inet_pton( $ip ) ); + + if ( ! $bytesAddr || ! $bytesTest ) { + return false; + } + + for ( $i = 1, $ceil = ceil( $mask / 16 ); $i <= $ceil; ++ $i ) { + $left = $mask - 16 * ( $i - 1 ); + $left = ( $left <= 16 ) ? $left : 16; + $maskB = ~( 0xffff >> $left ) & 0xffff; + if ( ( $bytesAddr[ $i ] & $maskB ) != ( $bytesTest[ $i ] & $maskB ) ) { + return false; + } + } + + return true; + } else { /* Check for IPv4 */ + if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) { + return false; + } + + if ( false !== strpos( $net, '/' ) ) { /* Parse CIDR subnet */ + list( $base, $mask ) = explode( '/', $net, 2 ); + + if ( $mask === '0' ) { + return filter_var( $base, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ); + } + + if ( $mask < 0 || $mask > 32 ) { + return false; + } + } else { /* Use single address */ + $base = $net; + $mask = 32; + } + + return 0 === substr_compare( sprintf( '%032b', ip2long( $ip ) ), sprintf( '%032b', ip2long( $base ) ), 0, $mask ); } } } diff --git a/inc/statifyblacklist_admin.class.php b/inc/statifyblacklist_admin.class.php index 6705d54..bd9236c 100644 --- a/inc/statifyblacklist_admin.class.php +++ b/inc/statifyblacklist_admin.class.php @@ -14,22 +14,30 @@ class StatifyBlacklist_Admin extends StatifyBlacklist { * Update options * * @param $options array New options to save + * * @return mixed array of sanitized array on errors, FALSE if there were none * @since 1.1.1 - * @changed 1.3.0 + * @changed 1.4.0 */ public static function update_options( $options = null ) { if ( isset( $options ) && current_user_can( 'manage_options' ) ) { /* Sanitize URLs and remove empty inputs */ $givenReferer = $options['referer']; - if ($options['referer_regexp'] == 0) + if ( $options['referer_regexp'] == 0 ) { $sanitizedReferer = self::sanitizeURLs( $givenReferer ); - else + } else { $sanitizedReferer = $givenReferer; + } + + /* Sanitize IPs and Subnets and remove empty inputs */ + $givenIP = $options['ip']; + $sanitizedIP = self::sanitizeIPs( $givenIP ); /* Abort on errors */ - if ( ! empty( array_diff( $givenReferer, $sanitizedReferer ) ) ) { - return $sanitizedReferer; + if ( ! empty( array_diff( array_keys( $givenReferer ), array_keys( $sanitizedReferer ) ) ) ) { + return array( 'referer' => $sanitizedReferer ); + } elseif ( ! empty( array_diff( $givenIP, $sanitizedIP ) ) ) { + return array( 'ip' => array_diff( $givenIP, $sanitizedIP ) ); } /* Update database on success */ @@ -175,4 +183,22 @@ class StatifyBlacklist_Admin extends StatifyBlacklist { ) ); } + + /** + * Sanitize IP addresses with optional CIDR notation and remove empty results + * + * @param $ips array given array of URLs + * + * @return array sanitized array + * + * @since 1.4.0 + */ + private static function sanitizeIPs( $ips ) { + return array_filter( $ips, function ( $ip ) { + return preg_match('/^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])'. + '(\/([0-9]|[1-2][0-9]|3[0-2]))?$/', $ip) || + preg_match('/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))'. + '(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/', $ip); + } ); + } } diff --git a/inc/statifyblacklist_system.class.php b/inc/statifyblacklist_system.class.php index 88700e8..4fd443b 100644 --- a/inc/statifyblacklist_system.class.php +++ b/inc/statifyblacklist_system.class.php @@ -11,8 +11,6 @@ defined( 'ABSPATH' ) OR exit; */ class StatifyBlacklist_System extends StatifyBlacklist { - const VERSION_MAIN = 1.3; - /** * Plugin install handler. * @@ -49,23 +47,6 @@ class StatifyBlacklist_System extends StatifyBlacklist { } } - /** - * Create default plugin configuration. - * - * @since 1.4.0 - * - * @return array the options array - */ - private static function defaultOptions() { - return array( - 'activate-referer' => 0, - 'cron_referer' => 0, - 'referer' => array(), - 'referer_regexp' => 0, - 'version' => self::VERSION_MAIN - ); - } - /** * Plugin uninstall handler. @@ -100,7 +81,7 @@ class StatifyBlacklist_System extends StatifyBlacklist { * Upgrade plugin options. * * @since 1.2.0 - * @changed 1.3.0 + * @changed 1.4.0 */ public static function upgrade() { self::update_options(); @@ -116,13 +97,11 @@ class StatifyBlacklist_System extends StatifyBlacklist { } } - /* Check if version is set (not before 1.3.0) */ - if ( ! isset( self::$_options['version'] ) ) { - $options = self::$_options; - /* Set version */ + /* Version not set (pre 1.3.0) or older than current major release */ + if ( ! isset( self::$_options['version'] ) || self::$_options['version'] < self::VERSION_MAIN ) { + /* Merge default options with current config, assuming only additive changes */ + $options = array_merge( self::defaultOptions(), self::$_options ); $options['version'] = self::VERSION_MAIN; - /* Add regular expression option (as of 1.3) */ - $options['referer_regexp'] = 0; if ( ( is_multisite() && array_key_exists( STATIFYBLACKLIST_BASE, (array) get_site_option( 'active_sitewide_plugins' ) ) ) ) { update_site_option( 'statify-blacklist', $options ); } else { diff --git a/test/StatifyBlacklistTest.php b/test/StatifyBlacklistTest.php index a80d893..a8c1150 100644 --- a/test/StatifyBlacklistTest.php +++ b/test/StatifyBlacklistTest.php @@ -2,6 +2,8 @@ const ABSPATH = false; require_once( '../inc/statifyblacklist.class.php' ); +require_once( '../inc/statifyblacklist_system.class.php' ); +require_once( '../inc/statifyblacklist_admin.class.php' ); /** * Class StatifyBlacklistTest @@ -12,7 +14,10 @@ require_once( '../inc/statifyblacklist.class.php' ); */ class StatifyBlacklistTest extends PHPUnit_Framework_TestCase { - public function testFilter() { + /** + * Test simple referer filter. + */ + public function testRefererFilter() { /* Prepare Options: 2 blacklisted domains, disabled */ StatifyBlacklist::$_options = array( 'active_referer' => 0, @@ -21,7 +26,9 @@ class StatifyBlacklistTest extends PHPUnit_Framework_TestCase { 'example.com' => 0, 'example.net' => 1 ), - 'version' => 1.3 + 'active_ip' => 0, + 'ip' => array(), + 'version' => StatifyBlacklist::VERSION_MAIN ); /* No multisite */ @@ -56,7 +63,10 @@ class StatifyBlacklistTest extends PHPUnit_Framework_TestCase { $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); } - public function testRegexFilter() { + /** + * Test referer filter using regular expressions. + */ + public function testRefererRegexFilter() { /* Prepare Options: 2 regular expressions */ StatifyBlacklist::$_options = array( 'active_referer' => 1, @@ -96,4 +106,243 @@ class StatifyBlacklistTest extends PHPUnit_Framework_TestCase { $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); } -} \ No newline at end of file + /** + * Test the upgrade methodology for configuration options. + */ + public function testUpgrade() { + /* Create configuration of version 1.3 */ + $options13 = array( + 'active_referer' => 1, + 'cron_referer' => 0, + 'referer' => array( + 'example.net' => 0, + 'example.com' => 1 + ), + 'referer_regexp' => 0, + 'version' => 1.3 + ); + + /* Set options in mock */ + update_option( 'statify-blacklist', $options13 ); + + /* Execute upgrade */ + StatifyBlacklist_System::upgrade(); + + /* Retrieve updated options */ + $optionsUpdated = get_option( 'statify-blacklist' ); + + /* Verify size against default options (no junk left) */ + $this->assertEquals( 7, sizeof( $optionsUpdated ) ); + + /* Verify that original attributes are unchanged */ + $this->assertEquals( $options13['active_referer'], $optionsUpdated['active_referer'] ); + $this->assertEquals( $options13['cron_referer'], $optionsUpdated['cron_referer'] ); + $this->assertEquals( $options13['referer'], $optionsUpdated['referer'] ); + $this->assertEquals( $options13['referer_regexp'], $optionsUpdated['referer_regexp'] ); + + /* Verify that new attributes are present in config */ + $this->assertEquals( 0, $optionsUpdated['active_ip'] ); + $this->assertEquals( array(), $optionsUpdated['ip'] ); + + /* Verify that version number has changed to current release */ + $this->assertEquals( StatifyBlacklist::VERSION_MAIN, $optionsUpdated['version'] ); + } + + /** + * Test CIDR address matching for IP filter (#7) + */ + public function testCidrMatch() { + /* IPv4 tests */ + $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( '127.0.0.1', '127.0.0.1' ) ) ); + $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( '127.0.0.1', '127.0.0.1/32' ) ) ); + $this->assertFalse( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( + '127.0.0.1', + '127.0.0.1/33' + ) ) ); + $this->assertFalse( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( + '127.0.0.1', + '127.0.0.1/-1' + ) ) ); + $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( + '192.0.2.123', + '192.0.2.0/24' + ) ) ); + $this->assertFalse( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( + '192.0.3.123', + '192.0.2.0/24' + ) ) ); + $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( + '192.0.2.123', + '192.0.2.120/29' + ) ) ); + $this->assertFalse( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( + '192.0.2.128', + '192.0.2.120/29' + ) ) ); + $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( '10.11.12.13', '10.0.0.0/8' ) ) ); + $this->assertFalse( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( + '10.11.12.345', + '10.0.0.0/8' + ) ) ); + + /* IPv6 tests */ + $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( '::1', '::1' ) ) ); + $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( '::1', '::1/128' ) ) ); + $this->assertFalse( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( '::1', '::1/129' ) ) ); + $this->assertFalse( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( '::1', '::1/-1' ) ) ); + $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( + '2001:db8:a0b:12f0:1:2:3:4', + '2001:db8:a0b:12f0::1/64 ' + ) ) ); + $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( + '2001:db8:a0b:12f0::123:456', + '2001:db8:a0b:12f0::1/96 ' + ) ) ); + $this->assertFalse( invokeStatic( StatifyBlacklist::class, 'cidrMatch', array( + '2001:db8:a0b:12f0::1:132:465', + '2001:db8:a0b:12f0::1/96 ' + ) ) ); + } + + /** + * Test sanitization of IP addresses + */ + public function testSanitizeIPs() { + /* IPv4 tests */ + $valid = array( '192.0.2.123', '192.0.2.123/32', '192.0.2.0/24', '192.0.2.128/25' ); + $invalid = array( '12.34.56.789', '192.0.2.123/33', '192.0.2.123/-1' ); + $result = invokeStatic( StatifyBlacklist_Admin::class, 'sanitizeIPs', array( array_merge( $valid, $invalid ) ) ); + $this->assertNotFalse( $result ); + $this->assertInternalType( 'array', $result ); + $this->assertEquals( $valid, $result ); + + /* IPv6 tests */ + $valid = array( + '2001:db8:a0b:12f0::', + '2001:db8:a0b:12f0::1', + '2001:db8:a0b:12f0::1/128', + '2001:db8:a0b:12f0::/64' + ); + $invalid = array( + '2001:db8:a0b:12f0::x', + '2001:db8:a0b:12f0:::', + '2001:fffff:a0b:12f0::1', + '2001:db8:a0b:12f0::/129', + '1:2:3:4:5:6:7:8:9' + ); + $result = invokeStatic( StatifyBlacklist_Admin::class, 'sanitizeIPs', array( array_merge( $valid, $invalid ) ) ); + $this->assertNotFalse( $result ); + $this->assertInternalType( 'array', $result ); + $this->assertEquals( $valid, $result ); + } + + /** + * Test IP filter (#7). + */ + public function testIPFilter() { + /* Prepare Options: 2 blacklisted IPs, disabled */ + StatifyBlacklist::$_options = array( + 'active_referer' => 0, + 'cron_referer' => 0, + 'referer' => array(), + 'active_ip' => 0, + 'ip' => array( + '192.0.2.123', + '2001:db8:a0b:12f0::1' + ), + 'version' => StatifyBlacklist::VERSION_MAIN + ); + + /* No multisite */ + StatifyBlacklist::$multisite = false; + + /* Set matching IP */ + $_SERVER['REMOTE_ADDR'] = '192.0.2.123'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + /* Activate filter */ + StatifyBlacklist::$_options['active_ip'] = 1; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + /* Try matching v6 address */ + $_SERVER['REMOTE_ADDR'] = '2001:db8:a0b:12f0::1'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + /* Non-matching addresses */ + $_SERVER['REMOTE_ADDR'] = '192.0.2.234'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['REMOTE_ADDR'] = '2001:db8:a0b:12f0::2'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + /* Subnet matching */ + StatifyBlacklist::$_options['ip'] = array( + '192.0.2.0/25', + '2001:db8:a0b:12f0::/96' + ); + $_SERVER['REMOTE_ADDR'] = '192.0.2.123'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['REMOTE_ADDR'] = '192.0.2.234'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['REMOTE_ADDR'] = '2001:db8:a0b:12f0::5'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['REMOTE_ADDR'] = '2001:db8:a0b:12f0:0:1111::1'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + + /* Filter using proxy header */ + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['HTTP_X_FORWARDED_FOR'] = '192.0.2.123'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['HTTP_X_REAL_IP'] = '2001:db8:a0b:12f0:0:1111::1'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['HTTP_X_REAL_IP'] = '2001:db8:a0b:12f0:0::1'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + } +} + +/** + * Helper for testing inaccessible static methods + */ +function invokeStatic( $class, $methodName, $parameters = array() ) { + $reflection = new \ReflectionClass( $class ); + $method = $reflection->getMethod( $methodName ); + $method->setAccessible( true ); + + return $method->invokeArgs( null, $parameters ); +} + + +/* Some mocked functions */ + +$mock_options = array(); +$mock_multisite = false; + + +function is_multisite() { + global $mock_multisite; + + return $mock_multisite; +} + +function wp_parse_args( $args, $defaults = '' ) { + if ( is_object( $args ) ) { + $r = get_object_vars( $args ); + } elseif ( is_array( $args ) ) { + $r =& $args; + } else { + parse_str( $args, $r ); + } + + if ( is_array( $defaults ) ) { + return array_merge( $defaults, $r ); + } + + return $r; +} + +function get_option( $option, $default = false ) { + global $mock_options; + + return isset( $mock_options[ $option ] ) ? $mock_options[ $option ] : $default; +} + +function update_option( $option, $value, $autoload = null ) { + global $mock_options; + $mock_options[ $option ] = $value; +} diff --git a/views/settings_page.php b/views/settings_page.php index 293fd0b..b1c1d67 100755 --- a/views/settings_page.php +++ b/views/settings_page.php @@ -24,21 +24,35 @@ if ( ! empty( $_POST['statifyblacklist'] ) ) { $referer = explode( "\r\n", $_POST['statifyblacklist']['referer'] ); } + /* Extract IP array */ + if ( empty( trim( $_POST['statifyblacklist']['ip'] ) ) ) { + $ip = array(); + } else { + $ip = explode( "\r\n", $_POST['statifyblacklist']['ip'] ); + } + /* Update options (data will be sanitized) */ $statifyBlacklistUpdateResult = StatifyBlacklist_Admin::update_options( array( 'active_referer' => (int) @$_POST['statifyblacklist']['active_referer'], 'cron_referer' => (int) @$_POST['statifyblacklist']['cron_referer'], 'referer' => array_flip( $referer ), - 'referer_regexp' => (int) @$_POST['statifyblacklist']['referer_regexp'] + 'referer_regexp' => (int) @$_POST['statifyblacklist']['referer_regexp'], + 'active_ip' => (int) @$_POST['statifyblacklist']['active_ip'], + 'ip' => $ip, + 'version' => StatifyBlacklist::VERSION_MAIN ) ); /* Generate messages */ if ( $statifyBlacklistUpdateResult !== false ) { - $statifyBlacklistPostWarning = 'Some URLs are invalid and have been sanitized. Settings have not been saved yet.'; + if ( array_key_exists( 'referer', $statifyBlacklistUpdateResult ) ) { + $statifyBlacklistPostWarning = __( 'Some URLs are invalid and have been sanitized.', 'statify-blacklist' ); + } elseif ( array_key_exists( 'ip', $statifyBlacklistUpdateResult ) ) { + $statifyBlacklistPostWarning = sprintf( __( 'Some IPs are invalid : %s', 'statify-blacklist' ), implode( ', ', $statifyBlacklistUpdateResult['ip'] ) ); + } } else { - $statifyBlacklistPostSuccess = 'Settings updated successfully.'; + $statifyBlacklistPostSuccess = __('Settings updated successfully.', 'statify-blacklist'); } } } @@ -49,73 +63,113 @@ if ( ! empty( $_POST['statifyblacklist'] ) ) {
'; - esc_html_e( 'Statify plugin is not active.', 'statify-blacklist' ); + esc_html( 'Statify plugin is not active.'); print '
'; } if ( isset( $statifyBlacklistPostWarning ) ) { - print ''; - esc_html_e( $statifyBlacklistPostWarning ); + print '
' .
+ esc_html( $statifyBlacklistPostWarning );
+ print '
';
+ esc_html_e('Settings have not been saved yet.', 'statify-blacklist');
print '
'; - esc_html_e( $statifyBlacklistPostSuccess ); - print '
'. + esc_html( $statifyBlacklistPostSuccess ). + '