recaptcha
recaptcha copied to clipboard
Google server response changed this morning and causes PHP Client Library Error
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.
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
CurlPostinstead ofSocketPost.
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.
https://github.com/google/recaptcha/compare/1.2.3...1.2.4