Recaptcha: Google server response changed this morning and causes PHP Client Library Error

Created on 17 Mar 2020  路  15Comments  路  Source: google/recaptcha

Issue description

Between 8.00 and 8.30 CET this morning the response from the google reCAPTCHA servers changed

$recaptcha = new ReCaptchaReCaptcha(MY_KEY, new ReCaptchaRequestMethodSocketPost());

The google servers now return, the json is invalid
see below.

Has anybody else had this problem?

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 17 Mar 2020 11:39:47 GMT
Expires: Tue, 17 Mar 2020 11:39:47 GMT
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
X-XSS-Protection: 1; mode=block
Server: GSE
Alt-Svc: quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,h3-T050=":443"; ma=2592000
Accept-Ranges: none
Vary: Accept-Encoding
Connection: close
Transfer-Encoding: chunked
5a
{
"success": true,
"challenge_ts": "2020-03-17T11:36:43Z",
"hostname": "127.0.0.1"
}
0

This breaks the function public static function fromJson($json) in the lib

Environment
We have seen the problem on 4 systems of different configurations.

bug php

All 15 comments

We are experiencing the exact same issue since today. We "fixed" this by using CurlPost instead of SocketPost.

Rowan, Is the google server json response valid?
5a
{
"success": true,
"challenge_ts": "2020-03-17T11:36:43Z",
"hostname": "127.0.0.1"
}
0

Same Problem here.
/ReCaptcha/ReCaptcha.php:96:string '69
{
"success": true,
"challenge_ts": "2020-03-19T11:48:43Z",
"hostname": "dummy.host.name.com"
}
0
' (length=113)

Did you see the fix from Lionel?
Just change SocketPost to CurlPost

Yes. The fix worked fine.
I just wanted to confirm the problem.

Also fixed by switching to CurlPost instead of SocketPost.

We also fixed the SocketPost by patching the fromJson() in the lib
It's not a proper fix but it works
It may be needed by some as the CurlPost needs a certificate

/**
 * Build the response from the expected JSON returned by the service.
 *
 * @param string $json
 * @return \ReCaptcha\Response
 */
public static function fromJson($json)
{
    $responseData = json_decode($json, true);

    if (!$responseData) {

        $hostname = isset($responseData['hostname']) ? $responseData['hostname'] : null;

        if (strpos($json, 'success') !== false && strpos($json, 'true') !== false) {
            return new Response(true, array(), $hostname);
        }

        return new Response(false, array('invalid-json'));
    }

    $hostname = isset($responseData['hostname']) ? $responseData['hostname'] : null;

    if (isset($responseData['success']) && $responseData['success'] == true) {
        return new Response(true, array(), $hostname);
    }

    if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) {
        return new Response(false, $responseData['error-codes'], $hostname);
    }

    return new Response(false, array(), $hostname);
}

The issue is that recently Google servers started to serve chunk-encoded HTTP responses. Until this is fixed in the library, if you can't switch to CurlPost what you need to do is edit SocketPost and decode the response as a potential HTTP chunk-encoded. You can do so either by using a custom function (plenty of examples around the web) or more conveniently if you are using / can use PECL, the pecl-http package includes an http_chunked_decode function. If you end up with a clean solution, feel free to PR it as well. Hope that helps!

Thanks for the explanation.
I wondered why switching to CurlPost fixed the problem.

CurlPost does not experience the problem because cURL handles and parses the chunk-encoded HTTP internally, while SocketPost just uses a socket for a raw HTTP request with minimal parsing involved.

We fixed it that way:

/**
 * Build the response from the expected JSON returned by the service.
 *
 * @param string $json
 * @return \ReCaptcha\Response
 */
public static function fromJson($json)
{
    $responseData = false;
    if (self::isJsonValid($json)) {
        $responseData = json_decode($json, true);
    } else {
        $json_pattern = '/\{(?:[^{}]|(?R))*\}/xs';
        preg_match_all($json_pattern, $json, $matches);
        $filtered_json = trim(preg_replace('/\s+/', ' ', $matches[0][0]));
        if (self::isJsonValid($filtered_json)) {
            $responseData = json_decode($filtered_json, true);
        }
    }

    if (!$responseData) {
        return new Response(false, array(ReCaptcha::E_INVALID_JSON));
    }

    $hostname = isset($responseData['hostname']) ? $responseData['hostname'] : null;
    $challengeTs = isset($responseData['challenge_ts']) ? $responseData['challenge_ts'] : null;
    $apkPackageName = isset($responseData['apk_package_name']) ? $responseData['apk_package_name'] : null;
    $score = isset($responseData['score']) ? floatval($responseData['score']) : null;
    $action = isset($responseData['action']) ? $responseData['action'] : null;

    if (isset($responseData['success']) && $responseData['success'] == true) {
        return new Response(true, array(), $hostname, $challengeTs, $apkPackageName, $score, $action);
    }

    if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) {
        return new Response(false, $responseData['error-codes'], $hostname, $challengeTs, $apkPackageName, $score, $action);
    }

    return new Response(false, array(ReCaptcha::E_UNKNOWN_ERROR), $hostname, $challengeTs, $apkPackageName, $score, $action);
}

/**
 * Checking for JSON validity.
 *
 * @param string $string
 * @return bool
 */
public static function isJsonValid($string) {
    json_decode($string);
    return (json_last_error() == JSON_ERROR_NONE);
}

We are experiencing the exact same issue since today. We "fixed" this by using CurlPost instead of SocketPost.

Where did you make this switch?

I've released a super quick fix which is just requesting HTTP 1.0 instead of HTTP 1.1 and as a result - no chunking in the response.

That's a bit dirty though, so I'll update later on to actually handle a chunked response properly.

#383 would fix this issue i think

Was this page helpful?
0 / 5 - 0 ratings

Related issues

renorram picture renorram  路  5Comments

Chaython picture Chaython  路  5Comments

crobinson42 picture crobinson42  路  4Comments

duesee picture duesee  路  3Comments

apriebe picture apriebe  路  7Comments