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).
This commit is contained in:
Stefan Kalscheuer 2019-03-12 19:37:22 +01:00
parent 0822537f0e
commit b7c3b51873
5 changed files with 174 additions and 47 deletions

View File

@ -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 * Minimum required WordPress version is 4.7
* Removed `load_plugin_textdomain()` and `Domain Path` header * Removed `load_plugin_textdomain()` and `Domain Path` header
* Added automatic compatibility check for WP and PHP version (#17) * 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 ### ### 1.4.4 / 19.05.2018 ###
* Fix live filter chain when regular expressions are active (#12) * Fix live filter chain when regular expressions are active (#12)

View File

@ -20,9 +20,6 @@ if ( ! defined( 'ABSPATH' ) ) {
* @since 1.0.0 * @since 1.0.0
*/ */
class StatifyBlacklist_Admin extends StatifyBlacklist { 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. * Initialize admin-only components of the plugin.
@ -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 );
}
);
}
} }

View File

@ -28,6 +28,35 @@ class StatifyBlacklist {
*/ */
const VERSION_MAIN = 1.4; 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. * Plugin options.
* *
@ -137,16 +166,23 @@ class StatifyBlacklist {
public static function apply_blacklist_filter() { public static function apply_blacklist_filter() {
// Referer blacklist. // Referer blacklist.
if ( isset( self::$_options['referer']['active'] ) && 0 !== self::$_options['referer']['active'] ) { if ( isset( self::$_options['referer']['active'] ) && 0 !== self::$_options['referer']['active'] ) {
// Regular Expression filtering since 1.3.0. // Determine filter mode.
if ( isset( self::$_options['referer']['regexp'] ) && self::$_options['referer']['regexp'] > 0 ) { $mode = isset( self::$_options['referer']['regexp'] ) ? intval( self::$_options['referer']['regexp'] ) : 0;
// Get full referer string. // Get full referer string.
$referer = wp_get_raw_referer(); $referer = wp_get_raw_referer();
if ( ! $referer ) { if ( ! $referer ) {
$referer = ''; $referer = '';
} }
switch ( $mode ) {
// Regular Expression filtering since 1.3.0.
case self::MODE_REGEX:
case self::MODE_REGEX_CI:
// Merge given regular expressions into one. // Merge given regular expressions into one.
$regexp = '/' . implode( '|', array_keys( self::$_options['referer']['blacklist'] ) ) . '/'; $regexp = '/' . implode( '|', array_keys( self::$_options['referer']['blacklist'] ) ) . '/';
if ( 2 === self::$_options['referer']['regexp'] ) { if ( self::MODE_REGEX_CI === self::$_options['referer']['regexp'] ) {
$regexp .= 'i'; $regexp .= 'i';
} }
@ -154,9 +190,24 @@ class StatifyBlacklist {
if ( 1 === preg_match( $regexp, $referer ) ) { if ( 1 === preg_match( $regexp, $referer ) ) {
return true; return true;
} }
} else { 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. // Extract relevant domain parts.
$referer = wp_parse_url( wp_get_raw_referer() ); $referer = wp_parse_url( $referer );
$referer = strtolower( ( isset( $referer['host'] ) ? $referer['host'] : '' ) ); $referer = strtolower( ( isset( $referer['host'] ) ? $referer['host'] : '' ) );
// Get blacklist. // Get blacklist.
@ -324,6 +375,6 @@ class StatifyBlacklist {
} }
return ( 0 === substr_compare( sprintf( '%032b', ip2long( $ip ) ), sprintf( '%032b', ip2long( $base ) ), 0, $mask ) ); return ( 0 === substr_compare( sprintf( '%032b', ip2long( $ip ) ), sprintf( '%032b', ip2long( $base ) ), 0, $mask ) );
} // End if(). }
} }
} }

View File

@ -151,7 +151,7 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
// Matching both. // Matching both.
$_SERVER['HTTP_REFERER'] = 'http://example.net/test/me'; $_SERVER['HTTP_REFERER'] = 'http://example.net/test/me';
$this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() );
// Mathinc with wrong case. // Matching with wrong case.
$_SERVER['HTTP_REFERER'] = 'http://eXaMpLe.NeT/tEsT/mE'; $_SERVER['HTTP_REFERER'] = 'http://eXaMpLe.NeT/tEsT/mE';
$this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); $this->assertNull( StatifyBlacklist::apply_blacklist_filter() );
@ -160,6 +160,59 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
$this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); $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. * Test the upgrade methodology for configuration options.
* *
@ -366,13 +419,13 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
'referer' => array( 'referer' => array(
'active' => 0, 'active' => 0,
'cron' => 0, 'cron' => 0,
'regexp' => 0, 'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array(), 'blacklist' => array(),
), ),
'target' => array( 'target' => array(
'active' => 0, 'active' => 0,
'cron' => 0, 'cron' => 0,
'regexp' => 0, 'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array(), 'blacklist' => array(),
), ),
'ip' => array( 'ip' => array(
@ -438,13 +491,13 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
'referer' => array( 'referer' => array(
'active' => 0, 'active' => 0,
'cron' => 0, 'cron' => 0,
'regexp' => 0, 'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array(), 'blacklist' => array(),
), ),
'target' => array( 'target' => array(
'active' => 0, 'active' => 0,
'cron' => 0, 'cron' => 0,
'regexp' => 0, 'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array( 'blacklist' => array(
'/excluded/page/' => 0, '/excluded/page/' => 0,
'/?page_id=3' => 1, '/?page_id=3' => 1,
@ -513,7 +566,7 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
'referer' => array( 'referer' => array(
'active' => 1, 'active' => 1,
'cron' => 0, 'cron' => 0,
'regexp' => 0, 'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array( 'blacklist' => array(
'example.com' => 0, 'example.com' => 0,
), ),
@ -521,7 +574,7 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
'target' => array( 'target' => array(
'active' => 1, 'active' => 1,
'cron' => 0, 'cron' => 0,
'regexp' => 0, 'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array( 'blacklist' => array(
'/excluded/page/' => 0 '/excluded/page/' => 0
), ),
@ -561,9 +614,9 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
$_SERVER['REMOTE_ADDR'] = '192.0.2.234'; $_SERVER['REMOTE_ADDR'] = '192.0.2.234';
// Same for RegExp filters. // 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['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 ); StatifyBlacklist::$_options['target']['blacklist'] = array( '\/excluded\/.*' => 0 );
$this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); $this->assertNull( StatifyBlacklist::apply_blacklist_filter() );

View File

@ -194,13 +194,16 @@ if ( ! empty( $_POST['statifyblacklist'] ) ) {
</th> </th>
<td> <td>
<select name="statifyblacklist[referer][regexp]" id="statify-blacklist_referer_regexp"> <select name="statifyblacklist[referer][regexp]" id="statify-blacklist_referer_regexp">
<option value="0" <?php selected( StatifyBlacklist::$_options['referer']['regexp'], 0 ); ?>> <option value="<?php print esc_attr( StatifyBlacklist::MODE_NORMAL ); ?>" <?php selected( StatifyBlacklist::$_options['referer']['regexp'], StatifyBlacklist::MODE_NORMAL ); ?>>
<?php esc_html_e( 'Domain', 'statify-blacklist' ); ?> <?php esc_html_e( 'Domain', 'statify-blacklist' ); ?>
</option> </option>
<option value="1" <?php selected( StatifyBlacklist::$_options['referer']['regexp'], 1 ); ?>> <option value="<?php print esc_attr( StatifyBlacklist::MODE_KEYWORD ); ?>" <?php selected( StatifyBlacklist::$_options['referer']['regexp'], StatifyBlacklist::MODE_KEYWORD ); ?>>
<?php esc_html_e( 'Keyword', 'statify-blacklist' ); ?>
</option>
<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX ); ?>" <?php selected( StatifyBlacklist::$_options['referer']['regexp'], StatifyBlacklist::MODE_REGEX ); ?>>
<?php esc_html_e( 'RegEx case-sensitive', 'statify-blacklist' ); ?> <?php esc_html_e( 'RegEx case-sensitive', 'statify-blacklist' ); ?>
</option> </option>
<option value="2" <?php selected( StatifyBlacklist::$_options['referer']['regexp'], 2 ); ?>> <option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX_CI ); ?>" <?php selected( StatifyBlacklist::$_options['referer']['regexp'], StatifyBlacklist::MODE_REGEX_CI ); ?>>
<?php esc_html_e( 'RegEx case-insensitive', 'statify-blacklist' ); ?> <?php esc_html_e( 'RegEx case-insensitive', 'statify-blacklist' ); ?>
</option> </option>
</select> </select>
@ -208,6 +211,8 @@ if ( ! empty( $_POST['statifyblacklist'] ) ) {
<p class="description"> <p class="description">
<?php esc_html_e( 'Domain', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match given domain including subdomains', 'statify-blacklist' ); ?> <?php esc_html_e( 'Domain', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match given domain including subdomains', 'statify-blacklist' ); ?>
<br> <br>
<?php esc_html_e( 'Keyword', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match every referer that contains one of the keywords', 'statify-blacklist' ); ?>
<br>
<?php esc_html_e( 'RegEx', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match referer by regular expression', 'statify-blacklist' ); ?> <?php esc_html_e( 'RegEx', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match referer by regular expression', 'statify-blacklist' ); ?>
</p> </p>
</td> </td>
@ -272,14 +277,14 @@ if ( ! empty( $_POST['statifyblacklist'] ) ) {
</label> </label>
</th> </th>
<td> <td>
<select name="statifyblacklist[referer][regexp]" id="statify-blacklist_referer_regexp"> <select name="statifyblacklist[target][regexp]" id="statify-blacklist_referer_regexp">
<option value="0" <?php selected( StatifyBlacklist::$_options['target']['regexp'], 0 ); ?>> <option value="<?php print esc_attr( StatifyBlacklist::MODE_NORMAL ); ?>" <?php selected( StatifyBlacklist::$_options['target']['regexp'], StatifyBlacklist::MODE_NORMAL ); ?>>
<?php esc_html_e( 'Exact', 'statify-blacklist' ); ?> <?php esc_html_e( 'Exact', 'statify-blacklist' ); ?>
</option> </option>
<option value="1" <?php selected( StatifyBlacklist::$_options['target']['regexp'], 1 ); ?>> <option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX ); ?>" <?php selected( StatifyBlacklist::$_options['target']['regexp'], StatifyBlacklist::MODE_REGEX ); ?>>
<?php esc_html_e( 'RegEx case-sensitive', 'statify-blacklist' ); ?> <?php esc_html_e( 'RegEx case-sensitive', 'statify-blacklist' ); ?>
</option> </option>
<option value="2" <?php selected( StatifyBlacklist::$_options['target']['regexp'], 2 ); ?>> <option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX_CI ); ?>" <?php selected( StatifyBlacklist::$_options['target']['regexp'], StatifyBlacklist::MODE_REGEX_CI ); ?>>
<?php esc_html_e( 'RegEx case-insensitive', 'statify-blacklist' ); ?> <?php esc_html_e( 'RegEx case-insensitive', 'statify-blacklist' ); ?>
</option> </option>
</select> </select>