diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index acc43e6..a4b048c 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -3,6 +3,7 @@ If you can't find a solution below, please open an [issue](https://github.com/se ## Table of Contents * [Viewing the Request Body](#request-body) +* [Handling SSL Errors](#ssl-errors) ## Viewing the Request Body @@ -14,3 +15,10 @@ echo $response->statusCode(); echo $response->body(); echo $response->headers(); ``` + + +## Handling SSL Errors + +If any SSL errors occur during API calls, an `InvalidRequest` will be thrown. This will provide information to help debug the issue further. + +If the issue is caused by an unrecognized certificate, it may be possible that PHP is unable to locate your system's CA bundle. An easy fix would be requiring the `composer/ca-bundle` package - this library will automatically detect and use that to locate the CA bundle, or use Mozilla's as a fallback. diff --git a/composer.json b/composer.json index bd0c9d8..0c637fa 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,9 @@ "squizlabs/php_codesniffer": "~2.0", "friendsofphp/php-cs-fixer": "^2.16" }, + "suggest": { + "composer/ca-bundle": "Including this library will ensure that a valid CA bundle is available for secure connections" + }, "autoload": { "psr-4": { "SendGrid\\": "lib/" diff --git a/lib/Client.php b/lib/Client.php index 77b092c..1d2c55c 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -369,6 +369,15 @@ private function createCurlOptions($method, $body = null, $headers = null) } $options[CURLOPT_HTTPHEADER] = $headers; + if (class_exists('\\Composer\\CaBundle\\CaBundle') && method_exists('\\Composer\\CaBundle\\CaBundle', 'getSystemCaRootBundlePath')) { + $caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(); + if (is_dir($caPathOrFile) || (is_link($caPathOrFile) && is_dir(readlink($caPathOrFile)))) { + $options[CURLOPT_CAPATH] = $caPathOrFile; + } else { + $options[CURLOPT_CAINFO] = $caPathOrFile; + } + } + return $options; } @@ -492,6 +501,8 @@ public function makeRequest($method, $url, $body = null, $headers = null, $retry * @param array $requests * * @return Response[] + * + * @throws InvalidRequest */ public function makeAllRequests(array $requests = []) { @@ -512,6 +523,11 @@ public function makeAllRequests(array $requests = []) $sleepDurations = 0; foreach ($channels as $id => $channel) { $content = curl_multi_getcontent($channel); + + if ($content === false) { + throw new InvalidRequest(curl_error($channel), curl_errno($channel)); + } + $response = $this->parseResponse($channel, $content); if ($requests[$id]['retryOnLimit'] && $response->statusCode() === self::TOO_MANY_REQUESTS_HTTP_CODE) { diff --git a/test/unit/ClientTest.php b/test/unit/ClientTest.php index 0e19748..0d829a4 100644 --- a/test/unit/ClientTest.php +++ b/test/unit/ClientTest.php @@ -33,7 +33,7 @@ public function testConstructor() $this->assertAttributeEquals([], 'path', $this->client); $this->assertAttributeEquals([], 'curlOptions', $this->client); $this->assertAttributeEquals(false, 'retryOnLimit', $this->client); - $this->assertAttributeEquals(['get', 'post', 'patch', 'put', 'delete'], 'methods', $this->client); + $this->assertAttributeEquals(['get', 'post', 'patch', 'put', 'delete'], 'methods', $this->client); } public function test_() @@ -209,6 +209,15 @@ public function testThrowExceptionOnInvalidCall() $client->get(); } + public function testMakeRequestWithUntrustedRootCert() + { + $this->expectException(InvalidRequest::class); + $this->expectExceptionMessageRegExp('/certificate/i'); + + $client = new Client('https://untrusted-root.badssl.com/'); + $client->makeRequest('GET', 'https://untrusted-root.badssl.com/'); + } + /** * @param object $obj * @param string $name