implement user agent filter

A new live-only filter block for user agent strings is now available.
It features the known exact, keyword and regular expression modes.
This commit is contained in:
Stefan Kalscheuer 2020-10-19 11:52:48 +02:00
parent e5c30c2183
commit a6cc821089
4 changed files with 254 additions and 1 deletions

View File

@ -92,6 +92,7 @@ Same for IPv6 prefixes like _2001:db8:a0b:12f0::/64_.
### 1.6.0 / unreleased ###
* Minor accessibility fixes on settings page
* Introduced new user agent filter (#20)
### 1.5.2 / 03.09.2020 ###
* Minor translation updates

View File

@ -91,7 +91,10 @@ class StatifyBlacklist {
self::update_options();
// Add Filter to statify hook if enabled.
if ( 0 !== self::$options['referer']['active'] || 0 !== self::$options['target']['active'] || 0 !== self::$options['ip']['active'] ) {
if ( 0 !== self::$options['referer']['active'] ||
0 !== self::$options['target']['active'] ||
0 !== self::$options['ip']['active'] ||
0 !== self::$options['ua']['active'] ) {
add_filter( 'statify__skip_tracking', array( 'StatifyBlacklist', 'apply_blacklist_filter' ) );
}
@ -156,6 +159,11 @@ class StatifyBlacklist {
'active' => 0,
'blacklist' => array(),
),
'ua' => array(
'active' => 0,
'regexp' => 0,
'blacklist' => array(),
),
'version' => self::VERSION_MAIN,
);
}
@ -264,6 +272,59 @@ class StatifyBlacklist {
}
}
// User agent filter (since 1.6).
if ( isset( self::$options['ua']['active'] ) && 0 !== self::$options['ua']['active'] ) {
// Determine filter mode.
$mode = isset( self::$options['ua']['regexp'] ) ? intval( self::$options['ua']['regexp'] ) : 0;
// Get full user agent string.
if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
$user_agent = filter_var( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ), FILTER_SANITIZE_STRING );
if ( $user_agent ) {
switch ( $mode ) {
// Regular Expression filtering since 1.3.0.
case self::MODE_REGEX:
case self::MODE_REGEX_CI:
// Merge given regular expressions into one.
$regexp = self::regex(
array_keys( self::$options['ua']['blacklist'] ),
self::MODE_REGEX_CI === self::$options['ua']['regexp']
);
// Check filter (no return to continue filtering #12).
if ( 1 === preg_match( $regexp, $user_agent ) ) {
return true;
}
break;
// Keyword filter since 1.5.0 (#15).
case self::MODE_KEYWORD:
// Get filter.
$blacklist = self::$options['ua']['blacklist'];
foreach ( array_keys( $blacklist ) as $keyword ) {
if ( false !== strpos( strtolower( $user_agent ), strtolower( $keyword ) ) ) {
return true;
}
}
break;
// Standard exact filter.
default:
// Get filter.
$blacklist = self::$options['ua']['blacklist'];
// Check filter.
if ( isset( $blacklist[ $user_agent ] ) ) {
return true;
}
}
}
}
}
// Skip and continue (return NULL), if all filters are inactive.
return null;
}

View File

@ -68,6 +68,11 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
'active' => 0,
'blacklist' => array(),
),
'ua' => array(
'active' => 0,
'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array(),
),
'version' => StatifyBlacklist::VERSION_MAIN,
);
@ -130,6 +135,11 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
'active' => 0,
'blacklist' => array(),
),
'ua' => array(
'active' => 0,
'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array(),
),
'version' => StatifyBlacklist::VERSION_MAIN,
);
@ -187,6 +197,11 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
'active' => 0,
'blacklist' => array(),
),
'ua' => array(
'active' => 0,
'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array(),
),
'version' => StatifyBlacklist::VERSION_MAIN,
);
@ -426,6 +441,11 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
'2001:db8:a0b:12f0::1',
),
),
'ua' => array(
'active' => 0,
'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array(),
),
'version' => StatifyBlacklist::VERSION_MAIN,
);
@ -498,6 +518,11 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
'active' => 0,
'blacklist' => array(),
),
'ua' => array(
'active' => 0,
'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array(),
),
'version' => StatifyBlacklist::VERSION_MAIN,
);
@ -544,6 +569,69 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
// TODO: Test target regex filter.
/**
* Test user agent filter (#20).
*
* @return void
*/
public function test_ua_filter() {
// Prepare Options: 2 filtered IPs, disabled.
StatifyBlacklist::$options = array(
'referer' => array(
'active' => 0,
'cron' => 0,
'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array(),
),
'target' => array(
'active' => 0,
'cron' => 0,
'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array(),
),
'ip' => array(
'active' => 0,
'blacklist' => array(),
),
'ua' => array(
'active' => 0,
'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array(
'TestBot/1.23' => 0,
),
),
'version' => StatifyBlacklist::VERSION_MAIN,
);
// No multisite.
StatifyBlacklist::$multisite = false;
// Set matching user agent.
$_SERVER['HTTP_USER_AGENT'] = 'TestBot/1.23';
$this->assertNull( StatifyBlacklist::apply_blacklist_filter() );
// Activate filter.
StatifyBlacklist::$options['ua']['active'] = 1;
$this->assertTrue( StatifyBlacklist::apply_blacklist_filter() );
// Non-matching addresses.
$_SERVER['HTTP_USER_AGENT'] = 'Another Browser 4.5.6 (Linux)';
$this->assertNull( StatifyBlacklist::apply_blacklist_filter() );
$_SERVER['HTTP_USER_AGENT'] = 'TestBot/2.34';
$this->assertNull( StatifyBlacklist::apply_blacklist_filter() );
// Keyword matching.
StatifyBlacklist::$options['ua']['blacklist'] = array( 'TestBot' => 0 );
StatifyBlacklist::$options['ua']['regexp'] = StatifyBlacklist::MODE_KEYWORD;
$this->assertTrue( StatifyBlacklist::apply_blacklist_filter() );
// RegEx.
StatifyBlacklist::$options['ua']['blacklist'] = array( 'T[a-z]+B[a-z]+' => 0 );
StatifyBlacklist::$options['ua']['regexp'] = StatifyBlacklist::MODE_REGEX;
$this->assertTrue( StatifyBlacklist::apply_blacklist_filter() );
StatifyBlacklist::$options['ua']['blacklist'] = array( 't[a-z]+' => 0 );
$this->assertNull( StatifyBlacklist::apply_blacklist_filter() );
StatifyBlacklist::$options['ua']['regexp'] = StatifyBlacklist::MODE_REGEX_CI;
$this->assertTrue( StatifyBlacklist::apply_blacklist_filter() );
}
/**
* Test combined filters.
*
@ -576,6 +664,11 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase {
'192.0.2.123',
),
),
'ua' => array(
'active' => 0,
'regexp' => StatifyBlacklist::MODE_NORMAL,
'blacklist' => array(),
),
'version' => StatifyBlacklist::VERSION_MAIN,
);

View File

@ -88,6 +88,26 @@ if ( ! empty( $_POST['statifyblacklist'] ) ) {
);
}
// TODO: Extract user agent array.
if ( isset( $_POST['statifyblacklist']['ua']['blacklist'] ) ) {
$ua_string = sanitize_textarea_field( wp_unslash( $_POST['statifyblacklist']['ua']['blacklist'] ) );
}
if ( empty( trim( $ua_string ) ) ) {
$ua = array();
} else {
$ua = array_filter(
array_map(
function ( $a ) {
return trim( $a );
},
explode( "\r\n", str_replace( '\\\\', '\\', $ua_string ) )
),
function ( $a ) {
return ! empty( $a );
}
);
}
// Update options (data will be sanitized).
$statifyblacklist_update_result = StatifyBlacklist_Admin::update_options(
array(
@ -114,6 +134,13 @@ if ( ! empty( $_POST['statifyblacklist'] ) ) {
? (int) $_POST['statifyblacklist']['ip']['active'] : 0,
'blacklist' => $ip,
),
'ua' => array(
'active' => isset( $_POST['statifyblacklist']['ua']['active'] )
? (int) $_POST['statifyblacklist']['ua']['active'] : 0,
'regexp' => isset( $_POST['statifyblacklist']['ua']['regexp'] )
? (int) $_POST['statifyblacklist']['ua']['regexp'] : 0,
'blacklist' => $ua,
),
'version' => StatifyBlacklist::VERSION_MAIN,
)
);
@ -374,6 +401,77 @@ if ( ! empty( $_POST['statifyblacklist'] ) ) {
</tbody>
</table>
<h2><?php esc_html_e( 'User agent filter', 'statify-blacklist' ); ?></h2>
<table class="form-table">
<tbody>
<tr>
<th scope="row">
<label for="statify-blacklist_active_ua">
<?php esc_html_e( 'Activate live filter', 'statify-blacklist' ); ?>
</label>
</th>
<td>
<input type="checkbox" name="statifyblacklist[ua][active]" id="statify-blacklist_active_ua"
value="1" <?php checked( StatifyBlacklist::$options['ua']['active'], 1 ); ?>>
<p class="description">
<?php esc_html_e( 'Filter at time of tracking, before anything is stored', 'statify-blacklist' ); ?>
<br>
<?php esc_html_e( 'Cron execution is not possible for user agent filter, because the user agent is stored.', 'statify-blacklist' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="statify-blacklist_ua_regexp"><?php esc_html_e( 'Matching method', 'statify-blacklist' ); ?></label>
</th>
<td>
<select name="statifyblacklist[ua][regexp]" id="statify-blacklist_ua_regexp">
<option value="<?php print esc_attr( StatifyBlacklist::MODE_NORMAL ); ?>" <?php selected( StatifyBlacklist::$options['ua']['regexp'], StatifyBlacklist::MODE_NORMAL ); ?>>
<?php esc_html_e( 'Exact', 'statify-blacklist' ); ?>
</option>
<option value="<?php print esc_attr( StatifyBlacklist::MODE_KEYWORD ); ?>" <?php selected( StatifyBlacklist::$options['ua']['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['ua']['regexp'], StatifyBlacklist::MODE_REGEX ); ?>>
<?php esc_html_e( 'RegEx case-sensitive', 'statify-blacklist' ); ?>
</option>
<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX_CI ); ?>" <?php selected( StatifyBlacklist::$options['ua']['regexp'], StatifyBlacklist::MODE_REGEX_CI ); ?>>
<?php esc_html_e( 'RegEx case-insensitive', 'statify-blacklist' ); ?>
</option>
</select>
<p class="description">
<?php esc_html_e( 'Exact', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match only given user agents', 'statify-blacklist' ); ?>
<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 user agent by regular expression', 'statify-blacklist' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="statify-blacklist_ua"><?php esc_html_e( 'User agent filter', 'statify-blacklist' ); ?></label>:
</th>
<td>
<textarea cols="40" rows="5" name="statifyblacklist[ua][blacklist]" id="statify-blacklist_ua"><?php
if ( empty( $statifyblacklist_update_result['ua'] ) ) {
print esc_html( implode( "\r\n", StatifyBlacklist::$options['ua']['blacklist'] ) );
} else {
print esc_html( implode( "\r\n", $statifyblacklist_update_result['ua']['sanitized'] ) );
}
?></textarea>
<p class="description">
<?php esc_html_e( 'Add one user agent string per line, e.g.', 'statify-blacklist' ); ?>
MyBot/1.23
</p>
</td>
</tr>
</tbody>
</table>
<p class="submit">
<input class="button-primary" type="submit" name="submit" value="<?php esc_html_e( 'Save Changes', 'statify-blacklist' ); ?>">
<hr>