PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.24
MxChat – AI Chatbot & Content Generation for WordPress v3.2.24
3.2.25 3.2.24 3.2.23 3.2.22 3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 All 156 releases
← All changes | includes/class-mxchat-user.php +98 -2 3.2.2 → 3.2.24 View file →
@@ -12,9 +12,11 @@
12 12 //error_log('Current User: ' . print_r($current_user, true));
13 13 return $current_user->user_login; // Use username as identifier
14 14 } else {
15 15 //error_log('User is not logged in. Using IP as identifier.');
16 - return sanitize_text_field($_SERVER['REMOTE_ADDR']); // Use IP address as fallback
16 + // Same resolver the rate limiter keys on, so the transcript
17 + // identifier and the rate-limit counter name the same address.
18 + return self::mxchat_get_client_ip();
17 19 }
18 20 }
19 21
20 22 // Function to get user email
@@ -24,6 +26,100 @@
24 26 return $current_user->user_email;
25 27 }
26 28 return null; // No email if not logged in
27 29 }
30 +
31 + /**
32 + * The visitor's address, from a source the visitor cannot choose.
33 + *
34 + * REMOTE_ADDR is the answer. Client-IP and X-Forwarded-For are set by
35 + * whoever sends the request, so a counter keyed on them can be reset
36 + * with a header. The one exception is Cloudflare: when the connection
37 + * really comes from a Cloudflare edge (REMOTE_ADDR inside the ranges
38 + * Cloudflare publishes), CF-Connecting-IP is the visitor. Hosts behind
39 + * some other proxy can supply their own resolver with the
40 + * `mxchat_client_ip` filter; whatever comes back must still be a valid
41 + * address or REMOTE_ADDR is used.
42 + *
43 + * @return string A validated IP address, or 'unknown' when there is none.
44 + */
45 + public static function mxchat_get_client_ip() {
46 + $remote = isset($_SERVER['REMOTE_ADDR']) ? trim((string) wp_unslash($_SERVER['REMOTE_ADDR'])) : '';
47 + $remote = filter_var($remote, FILTER_VALIDATE_IP) ? $remote : '';
48 +
49 + $ip = $remote;
50 + if ($remote !== '' && !empty($_SERVER['HTTP_CF_CONNECTING_IP']) && self::mxchat_ip_is_cloudflare($remote)) {
51 + $candidate = trim((string) wp_unslash($_SERVER['HTTP_CF_CONNECTING_IP']));
52 + if (filter_var($candidate, FILTER_VALIDATE_IP)) {
53 + $ip = $candidate;
54 + }
55 + }
56 +
57 + /**
58 + * Override the resolved visitor address, e.g. for a host behind a
59 + * trusted proxy that is not Cloudflare. Return a valid IP.
60 + *
61 + * @param string $ip The address MxChat resolved.
62 + * @param string $remote REMOTE_ADDR as received (validated, may be '').
63 + */
64 + $filtered = apply_filters('mxchat_client_ip', $ip, $remote);
65 + if (is_string($filtered) && filter_var(trim($filtered), FILTER_VALIDATE_IP)) {
66 + $ip = trim($filtered);
67 + }
68 +
69 + return $ip !== '' ? $ip : 'unknown';
70 + }
71 +
72 + /**
73 + * Cloudflare's published edge ranges (cloudflare.com/ips-v4, /ips-v6),
74 + * as of 2026-09-16. Refresh with the plugin.
75 + */
76 + public static function mxchat_cloudflare_ranges() {
77 + return array(
78 + '173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22',
79 + '141.101.64.0/18', '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20',
80 + '197.234.240.0/22', '198.41.128.0/17', '162.158.0.0/15', '104.16.0.0/13',
81 + '104.24.0.0/14', '172.64.0.0/13', '131.0.72.0/22',
82 + '2400:cb00::/32', '2606:4700::/32', '2803:f800::/32', '2405:b500::/32',
83 + '2405:8100::/32', '2a06:98c0::/29', '2c0f:f248::/32',
84 + );
85 + }
86 +
87 + public static function mxchat_ip_is_cloudflare($ip) {
88 + foreach (self::mxchat_cloudflare_ranges() as $cidr) {
89 + if (self::mxchat_ip_in_cidr($ip, $cidr)) {
90 + return true;
91 + }
92 + }
93 + return false;
94 + }
95 +
96 + /**
97 + * Does $ip fall inside $cidr? Works for IPv4 and IPv6; families must match.
98 + */
99 + public static function mxchat_ip_in_cidr($ip, $cidr) {
100 + if (strpos($cidr, '/') === false) {
101 + return false;
102 + }
103 + list($subnet, $bits) = explode('/', $cidr, 2);
104 + $ip_bin = @inet_pton($ip);
105 + $subnet_bin = @inet_pton($subnet);
106 + if ($ip_bin === false || $subnet_bin === false || strlen($ip_bin) !== strlen($subnet_bin)) {
107 + return false;
108 + }
109 + $bits = (int) $bits;
110 + $max = strlen($ip_bin) * 8;
111 + if ($bits < 0 || $bits > $max) {
112 + return false;
113 + }
114 + $full_bytes = intdiv($bits, 8);
115 + if ($full_bytes > 0 && substr($ip_bin, 0, $full_bytes) !== substr($subnet_bin, 0, $full_bytes)) {
116 + return false;
117 + }
118 + $rest = $bits % 8;
119 + if ($rest === 0) {
120 + return true;
121 + }
122 + $mask = (0xFF << (8 - $rest)) & 0xFF;
123 + return ((ord($ip_bin[$full_bytes]) & $mask) === (ord($subnet_bin[$full_bytes]) & $mask));
124 + }
28 125 }
29 -