<?php
/*
* Paste $v3.1 2025/08/16 https://github.com/boxlabss/PASTE
* demo: https://paste.boxlabs.uk/
*
* https://phpaste.sourceforge.io/
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License in LICENCE for more details.
*/
// includes/recaptcha.php
declare(strict_types=1);
// pull secrets and knobs
$RECAPTCHA_V3_SECRET = trim($_SESSION['recaptcha_secretkey'] ??
'');
$RECAPTCHA_V2_SECRET = trim($_SESSION['recaptcha_secretkey'] ??
'');
$RECAPTCHA_MIN_SCORE = 0.8;
$RECAPTCHA_MAX_AGE = 120;
$RECAPTCHA_HOST = parse_url((string
)($_SESSION['baseurl'] ??
''), PHP_URL_HOST
) ?
: ($_SERVER['SERVER_NAME'] ??
'');
// low-level HTTP call
function recaptcha_siteverify
(array $payload): ?
array {
'http' => [
'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'timeout' => 8,
]
]);
$resp = @file_get_contents('https://www.google.com/recaptcha/api/siteverify', false, $ctx);
if ($resp === false) return null;
}
// accept exact host or subdomain suffix
function host_matches(string $reported, string $expected): bool {
if ($reported === $expected) return true;
}
// v3 verify
function verify_recaptcha_v3
(string
$token, string
$expectedAction): array {
global $RECAPTCHA_V3_SECRET, $RECAPTCHA_MIN_SCORE, $RECAPTCHA_MAX_AGE, $RECAPTCHA_HOST;
if ($token === '' || $RECAPTCHA_V3_SECRET === '') return ['ok' => false, 'why' => 'missing'];
$data = recaptcha_siteverify([
'secret' => $RECAPTCHA_V3_SECRET,
'response' => $token,
'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
]);
if (!$data || empty($data['success'])) {
return ['ok' => false, 'why' => ($data['error-codes'][0] ?? 'not_success')];
}
if (!empty($data['action']) && $expectedAction !== '' && $data['action'] !== $expectedAction) {
return ['ok' => false, 'why' => 'bad_action'];
}
if (!empty($data['hostname']) && $RECAPTCHA_HOST && !host_matches
($data['hostname'], $RECAPTCHA_HOST)) {
return ['ok' => false, 'why' => 'bad_host'];
}
if (!empty($data['challenge_ts'])) {
if ($age > $RECAPTCHA_MAX_AGE) return ['ok' => false, 'why' => 'stale'];
}
$score = (float)($data['score'] ?? 0.0);
if ($score < $RECAPTCHA_MIN_SCORE) return ['ok' => false, 'why' => 'low_score', 'score' => $score];
return ['ok' => true, 'score' => $score];
}
// v2 verify (checkbox)
function verify_recaptcha_v2
(string
$token): array {
global $RECAPTCHA_V2_SECRET;
if ($token === '' || $RECAPTCHA_V2_SECRET === '') return ['ok' => false, 'why' => 'missing'];
$data = recaptcha_siteverify([
'secret' => $RECAPTCHA_V2_SECRET,
'response' => $token,
'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
]);
if (!$data || empty($data['success'])) return ['ok' => false, 'why' => ($data['error-codes'][0] ??
'not_success')];
return ['ok' => true];
}
// single gate used by controllers
function require_human(string $expectedAction): void {
$cap_e = $_SESSION['cap_e'] ?? 'off';
$mode = $_SESSION['mode'] ?? 'Normal';
$ver = $_SESSION['recaptcha_version'] ?? 'v2';
$token = trim((string
)($_POST['g-recaptcha-response'] ??
''));
// only enforce when admin enabled reCAPTCHA
if ($cap_e !== 'on' || $mode !== 'reCAPTCHA') return;
if ($ver === 'v3') {
$res = verify_recaptcha_v3($token, $expectedAction);
if (!$res['ok']) {
error_log('reCAPTCHA v3 failed: ' . ($res['why'] ??
'unknown') . (isset($res['score']) ?
(' score=' . $res['score']) : ''));
die('reCAPTCHA failed.');
}
return;
}
$res = verify_recaptcha_v2($token);
if (!$res['ok']) {
error_log('reCAPTCHA v2 failed: ' . ($res['why'] ??
'unknown'));
die('reCAPTCHA failed.');
}
}