diff --git a/inc/config.php b/inc/config.php
index eff1c596..87959d62 100644
--- a/inc/config.php
+++ b/inc/config.php
@@ -1210,6 +1210,8 @@
$config['error']['captcha'] = _('You seem to have mistyped the verification.');
$config['error']['flag_undefined'] = _('The flag %s is undefined, your PHP version is too old!');
$config['error']['flag_wrongtype'] = _('defined_flags_accumulate(): The flag %s is of the wrong type!');
+ $config['error']['remote_io_error'] = _('IO error while interacting with a remote service.');
+ $config['error']['local_io_error'] = _('IO error while interacting with a local resource or service.');
// Moderator errors
diff --git a/inc/service/captcha-queries.php b/inc/service/captcha-queries.php
new file mode 100644
index 00000000..8f9342dc
--- /dev/null
+++ b/inc/service/captcha-queries.php
@@ -0,0 +1,102 @@
+http = $http;
+ $this->secret = $secret;
+ $this->endpoint = $endpoint;
+ }
+
+ /**
+ * Checks if the user at the remote ip passed the captcha.
+ *
+ * @param string $response User provided response.
+ * @param string $remote_ip User ip.
+ * @return bool Returns true if the user passed the captcha.
+ * @throws RuntimeException|JsonException Throws on IO errors or if it fails to decode the answer.
+ */
+ public function verify(string $response, string $remote_ip): bool {
+ $data = array(
+ 'secret' => $this->secret,
+ 'response' => $response,
+ 'remoteip' => $remote_ip
+ );
+
+ $ret = $this->http->requestGet($this->endpoint, $data);
+ $resp = json_decode($ret, true, 16, JSON_THROW_ON_ERROR);
+
+ return isset($resp['success']) && $resp['success'];
+ }
+}
+
+class NativeCaptchaQuery {
+ private HttpDriver $http;
+ private string $domain;
+ private string $provider_check;
+
+
+ /**
+ * @param HttpDriver $http The http client.
+ * @param string $domain The server's domain.
+ * @param string $provider_check Path to the endpoint.
+ */
+ function __construct(HttpDriver $http, string $domain, string $provider_check) {
+ $this->http = $http;
+ $this->domain = $domain;
+ $this->provider_check = $provider_check;
+ }
+
+ /**
+ * Checks if the user at the remote ip passed the native vichan captcha.
+ *
+ * @param string $extra Extra http parameters.
+ * @param string $user_text Remote user's text input.
+ * @param string $user_cookie Remote user cookie.
+ * @return bool Returns true if the user passed the check.
+ * @throws RuntimeException Throws on IO errors.
+ */
+ public function verify(string $extra, string $user_text, string $user_cookie): bool {
+ $data = array(
+ 'mode' => 'check',
+ 'text' => $user_text,
+ 'extra' => $extra,
+ 'cookie' => $user_cookie
+ );
+
+ $ret = $this->http->requestGet($this->domain . '/' . $this->provider_check, $data);
+ return $ret === '1';
+ }
+}
diff --git a/post.php b/post.php
index dc6e444f..ae1e9001 100644
--- a/post.php
+++ b/post.php
@@ -7,6 +7,7 @@ require_once 'inc/bootstrap.php';
use Vichan\AppContext;
use Vichan\Driver\HttpDriver;
+use Vichan\Service\{RemoteCaptchaQuery, NativeCaptchaQuery};
/**
* Utility functions
@@ -144,6 +145,7 @@ function ocr_image(array $config, string $img_path): string {
return trim($ret);
}
+
/**
* Method handling functions
*/
@@ -447,22 +449,32 @@ if (isset($_POST['delete'])) {
if (count($report) > $config['report_limit'])
error($config['error']['toomanyreports']);
- if ($config['report_captcha'] && !isset($_POST['captcha_text'], $_POST['captcha_cookie'])) {
- error($config['error']['bot']);
- }
if ($config['report_captcha']) {
- $ch = curl_init($config['domain'].'/'.$config['captcha']['provider_check'] . "?" . http_build_query([
- 'mode' => 'check',
- 'text' => $_POST['captcha_text'],
- 'extra' => $config['captcha']['extra'],
- 'cookie' => $_POST['captcha_cookie']
- ]));
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- $resp = curl_exec($ch);
+ if (!isset($_POST['captcha_text'], $_POST['captcha_cookie'])) {
+ error($config['error']['bot']);
+ }
- if ($resp !== '1') {
- error($config['error']['captcha']);
+ try {
+ $query = new NativeCaptchaQuery(
+ $context->getHttpDriver(),
+ $config['domain'],
+ $config['captcha']['provider_check']
+ );
+ $success = $query->verify(
+ $config['captcha']['extra'],
+ $_POST['captcha_text'],
+ $_POST['captcha_cookie']
+ );
+
+ if (!$success) {
+ error($config['error']['captcha']);
+ }
+ } catch (RuntimeException $e) {
+ if ($config['syslog']) {
+ _syslog(LOG_ERR, "Native captcha IO exception: {$e->getMessage()}");
+ }
+ error($config['error']['local_io_error']);
}
}
@@ -552,62 +564,60 @@ if (isset($_POST['delete'])) {
// Check if banned
checkBan($board['uri']);
- // Check for CAPTCHA right after opening the board so the "return" link is in there
- if ($config['recaptcha']) {
- if (!isset($_POST['g-recaptcha-response']))
- error($config['error']['bot']);
+ // Check for CAPTCHA right after opening the board so the "return" link is in there.
+ try {
+ // With our custom captcha provider
+ if ($config['captcha']['enabled'] || ($post['op'] && $config['new_thread_capt'])) {
+ $query = new NativeCaptchaQuery($context->getHttpDriver(), $config['domain'], $config['captcha']['provider_check']);
+ $success = $query->verify($config['captcha']['extra'], $_POST['captcha_text'], $_POST['captcha_cookie']);
- // Check what reCAPTCHA has to say...
- $resp = json_decode(file_get_contents(sprintf('https://www.recaptcha.net/recaptcha/api/siteverify?secret=%s&response=%s&remoteip=%s',
- $config['recaptcha_private'],
- urlencode($_POST['g-recaptcha-response']),
- $_SERVER['REMOTE_ADDR'])), true);
-
- if (!$resp['success']) {
- error($config['error']['captcha']);
+ if (!$success) {
+ error(
+ $config['error']['captcha']
+ . ''
+ );
+ }
}
- }
- // hCaptcha
- if ($config['hcaptcha']) {
- if (!isset($_POST['h-captcha-response'])) {
- error($config['error']['bot']);
+ // Remote 3rd party captchas.
+ else {
+ // recaptcha
+ if ($config['recaptcha']) {
+ if (!isset($_POST['g-recaptcha-response'])) {
+ error($config['error']['bot']);
+ }
+ $response = $_POST['g-recaptcha-response'];
+ $query = RemoteCaptchaQuery::with_recaptcha($context->getHttpDriver(), $config['recaptcha_private']);
+ }
+ // hCaptcha
+ elseif ($config['hcaptcha']) {
+ if (!isset($_POST['h-captcha-response'])) {
+ error($config['error']['bot']);
+ }
+ $response = $_POST['g-recaptcha-response'];
+ $query = RemoteCaptchaQuery::with_hcaptcha($context->getHttpDriver(), $config['hcaptcha_private']);
+ }
+
+ $success = $query->verify($response, $_SERVER['REMOTE_ADDR']);
+ if (!$success) {
+ error($config['error']['captcha']);
+ }
}
-
- $data = array(
- 'secret' => $config['hcaptcha_private'],
- 'response' => $_POST['h-captcha-response'],
- 'remoteip' => $_SERVER['REMOTE_ADDR']
- );
-
- $hcaptchaverify = curl_init();
- curl_setopt($hcaptchaverify, CURLOPT_URL, "https://hcaptcha.com/siteverify");
- curl_setopt($hcaptchaverify, CURLOPT_POST, true);
- curl_setopt($hcaptchaverify, CURLOPT_POSTFIELDS, http_build_query($data));
- curl_setopt($hcaptchaverify, CURLOPT_RETURNTRANSFER, true);
- $hcaptcharesponse = curl_exec($hcaptchaverify);
-
- $resp = json_decode($hcaptcharesponse, true); // Decoding $hcaptcharesponse instead of $response
-
- if (!$resp['success']) {
- error($config['error']['captcha']);
+ } catch (RuntimeException $e) {
+ if ($config['syslog']) {
+ _syslog(LOG_ERR, "Captcha IO exception: {$e->getMessage()}");
}
+ error($config['error']['remote_io_error']);
+ } catch (JsonException $e) {
+ if ($config['syslog']) {
+ _syslog(LOG_ERR, "Bad JSON reply to captcha: {$e->getMessage()}");
+ }
+ error($config['error']['remote_io_error']);
}
- // Same, but now with our custom captcha provider
- if (($config['captcha']['enabled']) || (($post['op']) && ($config['new_thread_capt'])) ) {
- $ch = curl_init($config['domain'].'/'.$config['captcha']['provider_check'] . "?" . http_build_query([
- 'mode' => 'check',
- 'text' => $_POST['captcha_text'],
- 'extra' => $config['captcha']['extra'],
- 'cookie' => $_POST['captcha_cookie']
- ]));
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- $resp = curl_exec($ch);
- if ($resp !== '1') {
- error($config['error']['captcha'] .
- '');
- }
- }
if (!(($post['op'] && $_POST['post'] == $config['button_newtopic']) ||
(!$post['op'] && $_POST['post'] == $config['button_reply'])))