diff --git a/lib/Cleantalk/Antispam/Integrations/CleantalkPreprocessComment.php b/lib/Cleantalk/Antispam/Integrations/CleantalkPreprocessComment.php index 6fcfba0df..a1d94cf69 100644 --- a/lib/Cleantalk/Antispam/Integrations/CleantalkPreprocessComment.php +++ b/lib/Cleantalk/Antispam/Integrations/CleantalkPreprocessComment.php @@ -483,8 +483,7 @@ private function doSkipReason($current_user, $ct_comment_done) apbct_is_user_enable() === false || $this->apbct->settings['forms__comments_test'] == 0 || $ct_comment_done || - (isset($_SERVER['HTTP_REFERER']) && stripos($_SERVER['HTTP_REFERER'], 'page=wysija_campaigns&action=editTemplate') !== false) || - (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/wp-admin/') !== false) + is_admin() ) { return __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__; } diff --git a/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EmailEncoderShortCode.php b/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EmailEncoderShortCode.php index 7ff272094..3f9731a76 100644 --- a/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EmailEncoderShortCode.php +++ b/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EmailEncoderShortCode.php @@ -15,6 +15,66 @@ class EmailEncoderShortCode extends \Cleantalk\ApbctWP\ShortCode */ protected $public_name; + /** + * @var string Wrapper template overridden by child classes as the placeholder source. + */ + protected $exclusion_wrapper = ''; + + // Placeholder nonce for ensuring unique placeholders per render. + protected $placeholder_nonce = ''; + + /** + * Build a placeholder for the given counter using the child's $exclusion_wrapper as a template. + * Lazy-initialises the per-render nonce so replacements survive isolated render passes. + * + * @param int $counter + * + * @return string + */ + protected function buildPlaceholder($counter) + { + if ($this->placeholder_nonce === '') { + $this->placeholder_nonce = $this->generatePlaceholderNonce(); + } + + $wrapper = (string)$this->exclusion_wrapper; + $placeholder = preg_replace( + '/EE\_\d+/', + 'EE_' . (string)$counter . '_' . $this->placeholder_nonce, + $wrapper + ); + + return is_null($placeholder) ? $wrapper : $placeholder; + } + + /** + * Rotates the placeholder nonce to ensure unique placeholders for subsequent renders. + * @return void + */ + protected function rotatePlaceholderNonce() + { + $this->placeholder_nonce = $this->generatePlaceholderNonce(); + } + + /** + * Generates a new high-entropy nonce for placeholders. + * @return string + */ + protected function generatePlaceholderNonce() + { + if (function_exists('random_bytes')) { + try { + return bin2hex(random_bytes(16)); + } catch (\Exception $e) { + // fall through to WP fallback + } + } + if (function_exists('wp_generate_password')) { + return strtolower(wp_generate_password(32, false)); + } + return substr(hash('sha256', uniqid((string)mt_rand(), true)), 0, 32); + } + /** * Process only this encoder's shortcode tags in the content. * diff --git a/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EncodeContentSC.php b/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EncodeContentSC.php index fb65b16d2..3d9741610 100644 --- a/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EncodeContentSC.php +++ b/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/EncodeContentSC.php @@ -115,10 +115,7 @@ public function changeContentBeforeEncoderModify($content) // Extract shortcode content to protect it from email encoding, supports sc attributes(!) $shortcode_exist_pattern = sprintf('/(\[%s(?:\s[^\]]*)?\])([\s\S]*?)(\[\/%s\])/s', $this->public_name, $this->public_name); $content = preg_replace_callback($shortcode_exist_pattern, function ($matches) { - $placeholder = preg_replace('/EE\_\d+/', 'EE_' . (string)$this->shortcode_counter++, $this->exclusion_wrapper); - if (is_null($placeholder)) { - $placeholder = $this->exclusion_wrapper; - } + $placeholder = $this->buildPlaceholder($this->shortcode_counter++); if (isset($matches[1], $matches[2], $matches[3])) { $prefix = $matches[1]; $entity = $matches[2]; @@ -166,5 +163,6 @@ public function resetShortcodeReplacements() { $this->shortcode_replacements = array(); $this->shortcode_counter = 0; + $this->rotatePlaceholderNonce(); } } diff --git a/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/ExcludedEncodeContentSC.php b/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/ExcludedEncodeContentSC.php index 01b59208e..a0a64980a 100644 --- a/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/ExcludedEncodeContentSC.php +++ b/lib/Cleantalk/ApbctWP/ContactsEncoder/Shortcodes/ExcludedEncodeContentSC.php @@ -275,10 +275,7 @@ protected function shouldDeferPlaceholderRestore() */ protected function createPlaceholder($content) { - $placeholder = preg_replace('/EE\_\d+/', 'EE_' . (string)$this->shortcode_counter++, $this->exclusion_wrapper); - if (is_null($placeholder)) { - $placeholder = $this->exclusion_wrapper; - } + $placeholder = $this->buildPlaceholder($this->shortcode_counter++); $this->shortcode_replacements[$placeholder] = $content; return $placeholder; @@ -291,6 +288,7 @@ public function resetShortcodeReplacements() { $this->shortcode_replacements = array(); $this->shortcode_counter = 0; + $this->rotatePlaceholderNonce(); } /** diff --git a/tests/ApbctWP/ContactsEncoder/TestContactsEncoderShortCodeEncode.php b/tests/ApbctWP/ContactsEncoder/TestContactsEncoderShortCodeEncode.php index dfa5a44f5..55e37e167 100644 --- a/tests/ApbctWP/ContactsEncoder/TestContactsEncoderShortCodeEncode.php +++ b/tests/ApbctWP/ContactsEncoder/TestContactsEncoderShortCodeEncode.php @@ -102,8 +102,8 @@ public function testChangeContentBeforeEncoderModifyReplacesShortcodesWithPlaceh $content = 'Some content with [apbct_encode_data]Test content[/apbct_encode_data]'; $result = $this->shortcode->changeContentBeforeEncoderModify($content); - $this->assertStringContainsString('%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', $result); - $this->assertArrayHasKey('%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', $this->shortcode->shortcode_replacements); + $this->assertRegExp('/%%APBCT_SHORT_CODE_INCLUDE_EE_0_[a-f0-9]+%%/', $result); + $this->assertCount(1, $this->shortcode->shortcode_replacements); } public function testChangeContentBeforeEncoderModifyUsesPlaceholdersWhenDecoderCookieSet() @@ -113,8 +113,8 @@ public function testChangeContentBeforeEncoderModifyUsesPlaceholdersWhenDecoderC $content = 'Some content with [apbct_encode_data]Test content[/apbct_encode_data]'; $result = $this->shortcode->changeContentBeforeEncoderModify($content); - $this->assertStringContainsString('%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', $result); - $this->assertArrayHasKey('%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', $this->shortcode->shortcode_replacements); + $this->assertRegExp('/%%APBCT_SHORT_CODE_INCLUDE_EE_0_[a-f0-9]+%%/', $result); + $this->assertCount(1, $this->shortcode->shortcode_replacements); } public function testChangeContentBeforeEncoderModifyUsesPlaceholdersWhenGlobalEmailEncodingDisabled() @@ -128,7 +128,7 @@ public function testChangeContentBeforeEncoderModifyUsesPlaceholdersWhenGlobalEm $content = '
[apbct_encode_data]Test content[/apbct_encode_data]
'; $result = $shortcode->changeContentBeforeEncoderModify($content); - $this->assertStringContainsString('%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', $result); + $this->assertRegExp('/%%APBCT_SHORT_CODE_INCLUDE_EE_0_[a-f0-9]+%%/', $result); $this->assertStringContainsString('', $result); } @@ -174,8 +174,8 @@ public function testShortcodeOutsideHtmlIsProcessed() $result = $this->shortcode->changeContentBeforeEncoderModify($content); - $this->assertStringContainsString( - '%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', + $this->assertRegExp( + '/%%APBCT_SHORT_CODE_INCLUDE_EE_0_[a-f0-9]+%%/', $result ); @@ -191,8 +191,10 @@ public function testMultipleShortcodesAreHandled() $result = $this->shortcode->changeContentBeforeEncoderModify($content); - $this->assertStringContainsString('%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', $result); - $this->assertStringContainsString('%%APBCT_SHORT_CODE_INCLUDE_EE_1%%', $result); + // both placeholders in the same render pass must share the same nonce + preg_match('/%%APBCT_SHORT_CODE_INCLUDE_EE_0_([a-f0-9]+)%%/', $result, $matches); + $this->assertNotEmpty($matches); + $this->assertStringContainsString('%%APBCT_SHORT_CODE_INCLUDE_EE_1_' . $matches[1] . '%%', $result); } public function testHtmlAttributeBreakPayloadDoesNotExplode() @@ -234,8 +236,8 @@ public function testShortcodeWithAttributesIsProcessed() $result = $this->shortcode->changeContentBeforeEncoderModify($content); - $this->assertStringContainsString( - '%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', + $this->assertRegExp( + '/%%APBCT_SHORT_CODE_INCLUDE_EE_0_[a-f0-9]+%%/', $result ); @@ -271,7 +273,7 @@ public function testPlaceholderNeverAppearsInsideHtmlAttribute() $result = $this->shortcode->changeContentBeforeEncoderModify($content); - $this->assertStringNotContainsString('%%APBCT_SHORT_CODE_INCLUDE_EE_0%%', $result); + $this->assertNotRegExp('/%%APBCT_SHORT_CODE_INCLUDE_EE_0(_[a-f0-9]+)?%%/', $result); } public function testCallbackEscapesReplacingText()