From b7c3b5187326b70292081d6411b275c28406d47c Mon Sep 17 00:00:00 2001 From: Stefan Kalscheuer Date: Tue, 12 Mar 2019 19:37:22 +0100 Subject: [PATCH] implement keyword filter for referer blacklist (closes #15) In addition to the pre-existing normal and regular expression filters a keyword mode is added. This filter matches if the referer string contains a given keyword (case insensitive). --- README.md | 2 + inc/class-statifyblacklist-admin.php | 24 +++++- inc/class-statifyblacklist.php | 105 ++++++++++++++++++++------- test/statifyblacklist-test.php | 71 +++++++++++++++--- views/settings-page.php | 19 +++-- 5 files changed, 174 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index b913aee..3dc84e4 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,8 @@ Because of this, an IP blacklist can only be applied while processing the reques * Minimum required WordPress version is 4.7 * Removed `load_plugin_textdomain()` and `Domain Path` header * Added automatic compatibility check for WP and PHP version (#17) +* Added keyword filter mode for referer blacklist (#15) +* Layout adjustments on settings page ### 1.4.4 / 19.05.2018 ### * Fix live filter chain when regular expressions are active (#12) diff --git a/inc/class-statifyblacklist-admin.php b/inc/class-statifyblacklist-admin.php index 5fe6832..35f78d8 100644 --- a/inc/class-statifyblacklist-admin.php +++ b/inc/class-statifyblacklist-admin.php @@ -20,9 +20,6 @@ if ( ! defined( 'ABSPATH' ) ) { * @since 1.0.0 */ class StatifyBlacklist_Admin extends StatifyBlacklist { - const MODE_NORMAL = 0; - const MODE_REGEX = 1; - const MODE_REGEX_CI = 2; /** * Initialize admin-only components of the plugin. @@ -96,7 +93,7 @@ class StatifyBlacklist_Admin extends StatifyBlacklist { 'sanitized' => $sanitized_referer, 'diff' => array_diff( $given_referer, $sanitized_referer ), ], - 'target' => [ + 'target' => [ 'sanitized' => $sanitized_target, 'diff' => array_diff( $given_target, $sanitized_target ), ], @@ -334,4 +331,23 @@ class StatifyBlacklist_Admin extends StatifyBlacklist { } ); } + + /** + * Validate regular expressions, i.e. remove duplicates and empty values and validate others. + * + * @since 1.5.0 #13 + * + * @param array $expressions Given pre-sanitized array of regular expressions. + * + * @return array Array of invalid expressions. + */ + private static function sanitize_regex( $expressions ) { + return array_filter( + $expressions, + function ( $re ) { + // Check of preg_match() fails (warnings suppressed). + return false === @preg_match( $re, null ); + } + ); + } } diff --git a/inc/class-statifyblacklist.php b/inc/class-statifyblacklist.php index faf8df3..59b071e 100644 --- a/inc/class-statifyblacklist.php +++ b/inc/class-statifyblacklist.php @@ -28,6 +28,35 @@ class StatifyBlacklist { */ const VERSION_MAIN = 1.4; + /** + * Operation mode "normal". + * + * @var integer MODE_NORMAL + */ + const MODE_NORMAL = 0; + + /** + * Operation mode "regular expression". + * + * @var integer MODE_REGEX + */ + const MODE_REGEX = 1; + + /** + * Operation mode "regular expression case insensitive". + * + * @var integer MODE_REGEX_CI + */ + const MODE_REGEX_CI = 2; + + /** + * Operation mode "keyword". + * + * @since 1.5.0 + * @var integer MODE_KEYWORD + */ + const MODE_KEYWORD = 3; + /** * Plugin options. * @@ -137,35 +166,57 @@ class StatifyBlacklist { public static function apply_blacklist_filter() { // Referer blacklist. if ( isset( self::$_options['referer']['active'] ) && 0 !== self::$_options['referer']['active'] ) { - // Regular Expression filtering since 1.3.0. - if ( isset( self::$_options['referer']['regexp'] ) && self::$_options['referer']['regexp'] > 0 ) { - // Get full referer string. - $referer = wp_get_raw_referer(); - if ( ! $referer ) { - $referer = ''; - } - // Merge given regular expressions into one. - $regexp = '/' . implode( '|', array_keys( self::$_options['referer']['blacklist'] ) ) . '/'; - if ( 2 === self::$_options['referer']['regexp'] ) { - $regexp .= 'i'; - } + // Determine filter mode. + $mode = isset( self::$_options['referer']['regexp'] ) ? intval( self::$_options['referer']['regexp'] ) : 0; - // Check blacklist (no return to continue filtering #12). - if ( 1 === preg_match( $regexp, $referer ) ) { - return true; - } - } else { - // Extract relevant domain parts. - $referer = wp_parse_url( wp_get_raw_referer() ); - $referer = strtolower( ( isset( $referer['host'] ) ? $referer['host'] : '' ) ); + // Get full referer string. + $referer = wp_get_raw_referer(); + if ( ! $referer ) { + $referer = ''; + } - // Get blacklist. - $blacklist = self::$_options['referer']['blacklist']; + switch ( $mode ) { - // Check blacklist. - if ( isset( $blacklist[ $referer ] ) ) { - return true; - } + // Regular Expression filtering since 1.3.0. + case self::MODE_REGEX: + case self::MODE_REGEX_CI: + // Merge given regular expressions into one. + $regexp = '/' . implode( '|', array_keys( self::$_options['referer']['blacklist'] ) ) . '/'; + if ( self::MODE_REGEX_CI === self::$_options['referer']['regexp'] ) { + $regexp .= 'i'; + } + + // Check blacklist (no return to continue filtering #12). + if ( 1 === preg_match( $regexp, $referer ) ) { + return true; + } + break; + + // Keyword filter since 1.5.0 (#15). + case self::MODE_KEYWORD: + // Get blacklist. + $blacklist = self::$_options['referer']['blacklist']; + + foreach ( array_keys( $blacklist ) as $keyword ) { + if ( false !== strpos( strtolower( $referer ), strtolower( $keyword ) ) ) { + return true; + } + } + break; + + // Standard domain filter. + default: + // Extract relevant domain parts. + $referer = wp_parse_url( $referer ); + $referer = strtolower( ( isset( $referer['host'] ) ? $referer['host'] : '' ) ); + + // Get blacklist. + $blacklist = self::$_options['referer']['blacklist']; + + // Check blacklist. + if ( isset( $blacklist[ $referer ] ) ) { + return true; + } } } @@ -324,6 +375,6 @@ class StatifyBlacklist { } return ( 0 === substr_compare( sprintf( '%032b', ip2long( $ip ) ), sprintf( '%032b', ip2long( $base ) ), 0, $mask ) ); - } // End if(). + } } } diff --git a/test/statifyblacklist-test.php b/test/statifyblacklist-test.php index 29a7b43..e91d378 100644 --- a/test/statifyblacklist-test.php +++ b/test/statifyblacklist-test.php @@ -151,7 +151,7 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase { // Matching both. $_SERVER['HTTP_REFERER'] = 'http://example.net/test/me'; $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); - // Mathinc with wrong case. + // Matching with wrong case. $_SERVER['HTTP_REFERER'] = 'http://eXaMpLe.NeT/tEsT/mE'; $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); @@ -160,6 +160,59 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase { $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); } + /** + * Test referer filter using keywords. + * + * @return void + */ + public function test_referer_keyword_filter() { + // Prepare Options: 2 regular expressions. + StatifyBlacklist::$_options = array( + 'referer' => array( + 'active' => 1, + 'cron' => 0, + 'regexp' => StatifyBlacklist::MODE_KEYWORD, + 'blacklist' => array( + 'example' => 0, + 'test' => 1, + ), + ), + 'target' => array( + 'active' => 0, + 'cron' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, + 'blacklist' => array(), + ), + 'ip' => array( + 'active' => 0, + 'blacklist' => array(), + ), + 'version' => StatifyBlacklist::VERSION_MAIN, + ); + + // No multisite. + StatifyBlacklist::$multisite = false; + + // No referer. + unset( $_SERVER['HTTP_REFERER'] ); + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + // Non-blacklisted referer. + $_SERVER['HTTP_REFERER'] = 'http://not.evil'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + // Blacklisted referer. + $_SERVER['HTTP_REFERER'] = 'http://example.com'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + // Blacklisted referer with path. + $_SERVER['HTTP_REFERER'] = 'http://foobar.net/test/me'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + // Matching both. + $_SERVER['HTTP_REFERER'] = 'http://example.net/test/me'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + // Matching with wrong case. + $_SERVER['HTTP_REFERER'] = 'http://eXaMpLe.NeT/tEsT/mE'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + } + /** * Test the upgrade methodology for configuration options. * @@ -366,13 +419,13 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase { 'referer' => array( 'active' => 0, 'cron' => 0, - 'regexp' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, 'blacklist' => array(), ), 'target' => array( 'active' => 0, 'cron' => 0, - 'regexp' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, 'blacklist' => array(), ), 'ip' => array( @@ -438,13 +491,13 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase { 'referer' => array( 'active' => 0, 'cron' => 0, - 'regexp' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, 'blacklist' => array(), ), 'target' => array( 'active' => 0, 'cron' => 0, - 'regexp' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, 'blacklist' => array( '/excluded/page/' => 0, '/?page_id=3' => 1, @@ -513,7 +566,7 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase { 'referer' => array( 'active' => 1, 'cron' => 0, - 'regexp' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, 'blacklist' => array( 'example.com' => 0, ), @@ -521,7 +574,7 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase { 'target' => array( 'active' => 1, 'cron' => 0, - 'regexp' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, 'blacklist' => array( '/excluded/page/' => 0 ), @@ -561,9 +614,9 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase { $_SERVER['REMOTE_ADDR'] = '192.0.2.234'; // Same for RegExp filters. - StatifyBlacklist::$_options['referer']['regexp'] = 1; + StatifyBlacklist::$_options['referer']['regexp'] = StatifyBlacklist::MODE_REGEX; StatifyBlacklist::$_options['referer']['blacklist'] = array( 'example\.com' => 0 ); - StatifyBlacklist::$_options['target']['regexp'] = 1; + StatifyBlacklist::$_options['target']['regexp'] = StatifyBlacklist::MODE_REGEX; StatifyBlacklist::$_options['target']['blacklist'] = array( '\/excluded\/.*' => 0 ); $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); diff --git a/views/settings-page.php b/views/settings-page.php index 42dcdb4..b4a69bb 100755 --- a/views/settings-page.php +++ b/views/settings-page.php @@ -194,13 +194,16 @@ if ( ! empty( $_POST['statifyblacklist'] ) ) { @@ -208,6 +211,8 @@ if ( ! empty( $_POST['statifyblacklist'] ) ) {

-
+ - +
-

@@ -272,14 +277,14 @@ if ( ! empty( $_POST['statifyblacklist'] ) ) { - + - -