Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions inc/cleantalk-common.php
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,146 @@ function apbct_exclusions_check__url()
return false;
}

/**
* Split Protection mode (Lite) pages list into patterns.
*
* @param string $urls_setting
*
* @return string[]
*/
function apbct_protection_mode__parse_url_patterns($urls_setting)
{
if ( $urls_setting === '' ) {
return array();
}

if ( strpos($urls_setting, "\r\n") !== false ) {
$patterns = explode("\r\n", $urls_setting);
} elseif ( strpos($urls_setting, "\n") !== false ) {
$patterns = explode("\n", $urls_setting);
} else {
$patterns = explode(',', $urls_setting);
}

$result = array();
foreach ( $patterns as $pattern ) {
$pattern = trim($pattern);
if ( $pattern !== '' ) {
$result[] = $pattern;
}
}

return $result;
}

/**
* Build a regexp from a user pattern with a safe delimiter.
*
* @param string $pattern
*
* @return string
*/
function apbct_protection_mode__build_regexp($pattern)
{
$delimiter = '#';

return $delimiter . str_replace($delimiter, '\\' . $delimiter, $pattern) . $delimiter;
}

/**
* Whether the pattern compiles as a regular expression (same delimiter as runtime).
*
* @param string $pattern
*
* @return bool
*/
function apbct_protection_mode__is_regexp_compilable($pattern)
{
return @preg_match(apbct_protection_mode__build_regexp($pattern), '') !== false;
}

/**
* Whether the pattern looks like an intentional regular expression.
*
* @param string $pattern
*
* @return bool
*/
function apbct_protection_mode__looks_like_regexp($pattern)
{
return (bool) preg_match('/[.*+?\[\](){}|^$\\\\]/', $pattern);
}

/**
* Return the first invalid regexp-like pattern, or null if all are ok.
*
* @param string $urls_setting
*
* @return string|null
*/
function apbct_protection_mode__get_invalid_regexp_pattern($urls_setting)
{
foreach ( apbct_protection_mode__parse_url_patterns($urls_setting) as $pattern ) {
if ( apbct_protection_mode__looks_like_regexp($pattern) && ! apbct_protection_mode__is_regexp_compilable($pattern) ) {
return $pattern;
}
}

return null;
}

/**
* Match a single Protection mode pattern against a URL haystack.
* Substring first; regexp only when the pattern looks like a regular expression.
*
* @param string $pattern
* @param string $url_haystack
*
* @return bool
*/
function apbct_protection_mode__pattern_matches($pattern, $url_haystack)
{
if ( stripos($url_haystack, $pattern) !== false ) {
return true;
}

if ( ! apbct_protection_mode__looks_like_regexp($pattern) ) {
return false;
}

return @preg_match(apbct_protection_mode__build_regexp($pattern), $url_haystack) === 1;
}

/**
* Whether public JS/CSS assets are allowed on the current page.
* Full mode — always. Lite mode — only if REQUEST_URI matches listed pages (substring or regexp).
*
* @return bool
*/
function apbct_is_assets_allowed_on_current_page()
{
global $apbct;

// Full mode (0) or unset
if ( empty($apbct->settings['data__protection_mode']) ) {
return true;
}

if ( empty($apbct->settings['data__protection_mode__urls']) ) {
return false;
}

$url_haystack = TT::toString(Server::getString('REQUEST_URI'));

foreach ( apbct_protection_mode__parse_url_patterns($apbct->settings['data__protection_mode__urls']) as $pattern ) {
if ( apbct_protection_mode__pattern_matches($pattern, $url_haystack) ) {
return true;
}
}

return false;
}

/**
* Check POST array for the exclusion form signs. Listen for array keys or for value in case if key is "action".
* @param array $form_data The POST array or another filtered array of form data.
Expand Down
10 changes: 7 additions & 3 deletions inc/cleantalk-public.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function apbct_init()
}

// Localize data
if ( ! apbct_exclusions_check__url() ) {
if ( ! apbct_exclusions_check__url() && apbct_is_assets_allowed_on_current_page() ) {
if (defined('CLEANTALK_PLACE_PUBLIC_JS_SCRIPTS_IN_FOOTER') && CLEANTALK_PLACE_PUBLIC_JS_SCRIPTS_IN_FOOTER) {
add_action('wp_footer', array(LocalizeHandler::class, 'handle'), 1);
add_action('login_footer', array(LocalizeHandler::class, 'handle'), 1);
Expand Down Expand Up @@ -1138,6 +1138,10 @@ function apbct_login__scripts()
{
global $apbct;

if ( ! apbct_is_assets_allowed_on_current_page() ) {
return;
}

apbct_enqueue_and_localize_public_scripts();

$apbct->public_script_loaded = true;
Expand Down Expand Up @@ -1205,7 +1209,7 @@ function ct_enqueue_scripts_public($_hook)
{
global $current_user, $apbct;

if ( apbct_exclusions_check__url() || apbct_is_amp_request() ) {
if ( apbct_exclusions_check__url() || apbct_is_amp_request() || ! apbct_is_assets_allowed_on_current_page() ) {
return;
}

Expand All @@ -1232,7 +1236,7 @@ function ct_enqueue_styles_public()
{
global $apbct, $current_user;

if ( apbct_exclusions_check__url() ) {
if ( apbct_exclusions_check__url() || ! apbct_is_assets_allowed_on_current_page() ) {
return;
}

Expand Down
58 changes: 58 additions & 0 deletions inc/cleantalk-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,28 @@ function apbct_settings__set_fields()
'title' => __('Data Processing', 'cleantalk-spam-protect'),
'section' => 'hidden_section',
'fields' => array(
'data__protection_mode' => array(
'title' => __('Protection mode', 'cleantalk-spam-protect'),
'description' => __(
'Full — load Anti-Spam scripts and styles on all pages. Lite — load them only on the pages listed below. Regular expressions are allowed.',
'cleantalk-spam-protect'
),
'options' => array(
array('val' => 0, 'label' => __('Full', 'cleantalk-spam-protect'), 'childrens_enable' => 0,),
array('val' => 1, 'label' => __('Lite', 'cleantalk-spam-protect'), 'childrens_enable' => 1,),
),
'childrens' => array('data__protection_mode__urls'),
),
'data__protection_mode__urls' => array(
'type' => 'textarea',
'title' => __('Pages to protect (Lite mode)', 'cleantalk-spam-protect'),
'description' => __(
'List pages where Anti-Spam assets should be loaded. One value per line or comma-separated. Plain URL parts and regular expressions are allowed. Example: /contact, /checkout, /wp-login\\.php',
'cleantalk-spam-protect'
),
'parent' => 'data__protection_mode',
'class' => 'apbct_settings-field_wrapper--sub',
),
'data__protect_logged_in' => array(
'title' => __("Protect logged in Users", 'cleantalk-spam-protect'),
'description' => __(
Expand Down Expand Up @@ -1795,6 +1817,7 @@ function apbct_settings__error__output($return = false)
'settings_validate' => 'Validate Settings',
'exclusions_urls' => 'URL Exclusions',
'exclusions_fields' => 'Field Exclusions',
'protection_mode_urls' => 'Pages to protect (Lite mode)',

// Unknown
'unknown' => __('Unknown error type: ', 'cleantalk-spam-protect'),
Expand Down Expand Up @@ -2369,6 +2392,7 @@ function apbct_settings__validate($incoming_settings)
'data__email_decoder_obfuscation_mode',
'data__email_decoder_obfuscation_custom_text',
'data__email_decoder_buffer',
'data__protection_mode__urls',
);
$incoming_settings = apbct_settings__keep_settings_state_values(
$incoming_settings,
Expand Down Expand Up @@ -2448,6 +2472,40 @@ function apbct_settings__validate($incoming_settings)
} // Make HTML code inactive
}

// Sanitize / validate Protection mode pages (Lite) — plain URL parts and regexps both allowed
$raw_protection_mode_urls = isset($incoming_settings['data__protection_mode__urls'])
? $incoming_settings['data__protection_mode__urls']
: '';
$result = apbct_settings__sanitize__exclusions($raw_protection_mode_urls, false);
if ( ! is_string($result) ) {
$incoming_settings['data__protection_mode__urls'] = '';
$apbct->errorAdd(
'protection_mode_urls',
'is not valid: "' . $raw_protection_mode_urls . '"',
'settings_validate'
);
} else {
$incoming_settings['data__protection_mode__urls'] = $result;
$invalid_pattern = apbct_protection_mode__get_invalid_regexp_pattern($result);
if ( $invalid_pattern !== null ) {
$apbct->errorAdd(
'protection_mode_urls',
'contains invalid regular expression: "' . $invalid_pattern . '"',
'settings_validate'
);
} else {
$apbct->errorDelete('protection_mode_urls', true, 'settings_validate');
}
}
Comment thread
svfcode marked this conversation as resolved.

// Lite without pages would disable assets everywhere — fall back to Full
if (
! empty($incoming_settings['data__protection_mode']) &&
empty($incoming_settings['data__protection_mode__urls'])
) {
$incoming_settings['data__protection_mode'] = 0;
}

// Validate Exclusions
// URLs
$is_exclusions_url_like = apbct_settings__sanitize__exclusions(
Expand Down
Loading
Loading