recaptcha icon indicating copy to clipboard operation
recaptcha copied to clipboard

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

Open Domgarth opened this issue 5 years ago • 15 comments
trafficstars

Issue description

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

$recaptcha = new \ReCaptcha\ReCaptcha(MY_KEY, new \ReCaptcha\RequestMethod\SocketPost());

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.

Domgarth avatar Mar 17 '20 13:03 Domgarth

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

LionelMarbot avatar Mar 17 '20 18:03 LionelMarbot

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

Domgarth avatar Mar 17 '20 22:03 Domgarth

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)

pedde07 avatar Mar 19 '20 12:03 pedde07

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

Domgarth avatar Mar 19 '20 13:03 Domgarth

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

pedde07 avatar Mar 19 '20 13:03 pedde07

Also fixed by switching to CurlPost instead of SocketPost.

jonnott avatar Mar 19 '20 14:03 jonnott

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);
}

Domgarth avatar Mar 19 '20 17:03 Domgarth

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!

ntanis-dev avatar Mar 22 '20 04:03 ntanis-dev

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

Domgarth avatar Mar 22 '20 07:03 Domgarth

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.

ntanis-dev avatar Mar 22 '20 07:03 ntanis-dev

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);
}

DavidsRaptor avatar Mar 25 '20 08:03 DavidsRaptor

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

Where did you make this switch?

gingram-brpc-org avatar Mar 30 '20 19:03 gingram-brpc-org

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.

rowan-m avatar Mar 31 '20 17:03 rowan-m

https://github.com/google/recaptcha/compare/1.2.3...1.2.4

rowan-m avatar Mar 31 '20 17:03 rowan-m

#383 would fix this issue i think

Vergil137 avatar Jun 25 '20 13:06 Vergil137