PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.8.0
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.8.0
5.8.0 5.8.1 5.7.0 5.6.2 5.6.3 5.6.1 5.6.0 5.5.0 5.4.0 5.3.2 5.3.1 5.1.6 5.1.5 trunk 2.1.5 2.11 2.12 2.13 2.15 3.0.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.51 All 41 releases
double-opt-in / src / Spam / SubmissionTrap.php

SubmissionTrap.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 5.8.0, at src/Spam/SubmissionTrap.php

90 lines 2.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Honeypot and minimum fill time for double opt-in forms.
4 *
5 * A double opt-in form is a tool for mail bombing: a bot enters someone
6 * else's address and the site sends that person a confirmation mail. The
7 * rate limit caps the damage; these two traps stop most bots before any
8 * mail is sent, without a captcha. They also protect the reputation of the
9 * site's sender domain.
10 *
11 * Both fields start with an underscore, so CF7 keeps them out of the posted
12 * data — they never reach the stored opt-in or a mail. A submission without
13 * the fields (cached HTML from before the update, custom markup) passes: a
14 * trap that blocked real visitors would be worse than none.
15 *
16 * @package Forge12\DoubleOptIn\Spam
17 * @since 5.8.0
18 */
19
20 declare( strict_types=1 );
21
22 namespace Forge12\DoubleOptIn\Spam;
23
24 if ( ! defined( 'ABSPATH' ) ) {
25 exit;
26 }
27
28 final class SubmissionTrap {
29
30 public const HONEYPOT = '_f12_doi_hp';
31
32 public const STAMP = '_f12_doi_ts';
33
34 public const DEFAULT_MIN_SECONDS = 2;
35
36 /**
37 * The hidden fields, rendered into the form.
38 */
39 public static function markup( int $now ): string {
40 return '<div class="f12-doi-hp" aria-hidden="true" style="position:absolute!important;left:-10000px!important;top:auto!important;width:1px!important;height:1px!important;overflow:hidden!important;">'
41 . '<label>' . esc_html__( 'Leave this field empty', 'double-opt-in' )
42 . ' <input type="text" name="' . esc_attr( self::HONEYPOT ) . '" value="" tabindex="-1" autocomplete="off" /></label>'
43 . '</div>'
44 . '<input type="hidden" name="' . esc_attr( self::STAMP ) . '" value="' . esc_attr( self::stamp( $now ) ) . '" />';
45 }
46
47 /**
48 * `<time>.<signature>` — the signature keeps a bot from sending a
49 * made-up, old enough time.
50 */
51 public static function stamp( int $time ): string {
52 return $time . '.' . self::sign( $time );
53 }
54
55 /**
56 * Why a submission is a bot, or '' when it passes.
57 *
58 * @param array<string, mixed> $post Raw request fields.
59 * @param int $now Current time.
60 * @param int $minSeconds Minimum time between render and submit.
61 *
62 * @return string '' | 'honeypot' | 'stamp_invalid' | 'too_fast'
63 */
64 public static function check( array $post, int $now, int $minSeconds ): string {
65 if ( isset( $post[ self::HONEYPOT ] ) && ( ! is_string( $post[ self::HONEYPOT ] ) || trim( $post[ self::HONEYPOT ] ) !== '' ) ) {
66 return 'honeypot';
67 }
68
69 if ( ! isset( $post[ self::STAMP ] ) ) {
70 return '';
71 }
72
73 $stamp = is_string( $post[ self::STAMP ] ) ? $post[ self::STAMP ] : '';
74 if ( ! preg_match( '/^(\d{9,11})\.([a-f0-9]{16})$/', $stamp, $m ) ) {
75 return 'stamp_invalid';
76 }
77
78 $time = (int) $m[1];
79 if ( ! hash_equals( self::sign( $time ), $m[2] ) || $time > $now + 60 ) {
80 return 'stamp_invalid';
81 }
82
83 return $now - $time < $minSeconds ? 'too_fast' : '';
84 }
85
86 private static function sign( int $time ): string {
87 return substr( wp_hash( 'f12_doi_submission_trap|' . $time ), 0, 16 );
88 }
89 }
90