What happens
HttpClient::makeCloudRequest calls substr() on the result of
curl_exec without checking for false, so any transport failure, a TLS
certificate problem in particular, surfaces as a type error about
substr() with no mention of the network or of TLS.
Reproduced on 5 September 2026, PHP 8.5.1 on Windows, running the TAC
lookup example from
device-detection-php
against https://cloud.51degrees.com on a machine where PHP has no
curl.cainfo set:
PHP Fatal error: Uncaught TypeError: substr(): Argument #1 ($string) must be of type string, false given in ...\vendor\51degrees\fiftyone.pipeline.cloudrequestengine\src\HttpClient.php:86
Stack trace:
#0 ...\src\HttpClient.php(86): substr()
#1 ...\src\CloudRequestEngine.php(279): fiftyone\pipeline\cloudrequestengine\HttpClient->makeCloudRequest()
#2 ...\src\CloudRequestEngine.php(119): fiftyone\pipeline\cloudrequestengine\CloudRequestEngine->getEngineProperties()
#3 ...\vendor\51degrees\fiftyone.pipeline.engines\src\Engine.php(107): fiftyone\pipeline\cloudrequestengine\CloudRequestEngine->processInternal()
...
thrown in ...\src\HttpClient.php on line 86
The actual cause, read back from the same handle:
$ php -r '$ch = curl_init("https://cloud.51degrees.com/api/v4/accessibleproperties");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$d = curl_exec($ch);
var_dump($d === false);
echo "errno=" . curl_errno($ch) . " err=" . curl_error($ch) . PHP_EOL;'
bool(true)
errno=60 err=SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)
Nothing in what the person running the example sees points at the
certificate store, so the natural next step is to go looking for a bug in
the example or in the pipeline, which is the wrong place.
Where
src/HttpClient.php,
version 4.5.7 on Packagist, lines 79 to 86:
$data = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// Get headers length
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
// Get response header and body using header length
$headerStr = substr($data, 0, $headerSize);
Suggested fix
Check curl_exec for false before using the result, and throw with
curl_errno and curl_error in the message, naming the URL that was being
requested. Close the handle on that path as well, since the current code
only reaches curl_close on success. Something of this shape:
$data = curl_exec($ch);
if ($data === false) {
$errorNumber = curl_errno($ch);
$errorText = curl_error($ch);
curl_close($ch);
throw new \Exception(
'The request to ' . $url . ' could not be completed. curl ' .
'reported error ' . $errorNumber . ': ' . $errorText
);
}
A test can force the failure without a network by pointing the client at a
host that will not resolve, and asserting the message names curl and the
URL rather than substr.
The same check is worth applying to the file_get_contents branch above
it, which is used when curl is not available. @file_get_contents also
returns false on failure and the result is passed straight to
validateResponse.
Found by
Running the cloud examples in
device-detection-php
as part of the work in
device-detection-php#73,
which fixes the example side faults but cannot fix this one, because the
file is in this repository.
Produced with AI assistance. The output above is real rather than
illustrative, and the suggested fix has not been implemented or tested, so
it needs human review.
What happens
HttpClient::makeCloudRequestcallssubstr()on the result ofcurl_execwithout checking forfalse, so any transport failure, a TLScertificate problem in particular, surfaces as a type error about
substr()with no mention of the network or of TLS.Reproduced on 5 September 2026, PHP 8.5.1 on Windows, running the TAC
lookup example from
device-detection-php
against
https://cloud.51degrees.comon a machine where PHP has nocurl.cainfoset:The actual cause, read back from the same handle:
Nothing in what the person running the example sees points at the
certificate store, so the natural next step is to go looking for a bug in
the example or in the pipeline, which is the wrong place.
Where
src/HttpClient.php,version 4.5.7 on Packagist, lines 79 to 86:
Suggested fix
Check
curl_execforfalsebefore using the result, and throw withcurl_errnoandcurl_errorin the message, naming the URL that was beingrequested. Close the handle on that path as well, since the current code
only reaches
curl_closeon success. Something of this shape:A test can force the failure without a network by pointing the client at a
host that will not resolve, and asserting the message names curl and the
URL rather than
substr.The same check is worth applying to the
file_get_contentsbranch aboveit, which is used when curl is not available.
@file_get_contentsalsoreturns
falseon failure and the result is passed straight tovalidateResponse.Found by
Running the cloud examples in
device-detection-php
as part of the work in
device-detection-php#73,
which fixes the example side faults but cannot fix this one, because the
file is in this repository.
Produced with AI assistance. The output above is real rather than
illustrative, and the suggested fix has not been implemented or tested, so
it needs human review.