├── Rakefile ├── CHANGELOG.md ├── .gitattributes ├── phpunit.xml ├── .travis.yml ├── CONTRIBUTING.md ├── lib └── PayPalHttp │ ├── IOException.php │ ├── Environment.php │ ├── Injector.php │ ├── Serializer │ ├── FormPart.php │ ├── Text.php │ ├── Json.php │ ├── Form.php │ └── Multipart.php │ ├── HttpException.php │ ├── HttpResponse.php │ ├── Serializer.php │ ├── HttpRequest.php │ ├── Curl.php │ ├── Encoder.php │ └── HttpClient.php ├── composer.json ├── .gitignore ├── LICENSE └── README.md /Rakefile: -------------------------------------------------------------------------------- 1 | spec = Gem::Specification.find_by_name 'releasinator' 2 | load "#{spec.gem_dir}/lib/tasks/releasinator.rake" 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.1 2 | * Fix Case Sensitivity of Content Type for deserialization process 3 | 4 | ## 1.0.0 5 | - First release 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | tests/ export-ignore 2 | .idea/ export-ignore 3 | .github/ export-ignore 4 | .releasinator.rb export-ignore 5 | Gemfile export-ignore 6 | Gemfile.lock export-ignore 7 | 8 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ./tests/unit 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: php 3 | php: 4 | - 5.6 5 | - 7.0 6 | - 7.1 7 | - hhvm 8 | matrix: 9 | allow_failures: 10 | - php: hhvm 11 | fast_finish: true 12 | before_script: 13 | - composer self-update 14 | - composer install --dev 15 | script: 16 | - vendor/bin/phpunit 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute to the PayPal PHP HttpClient 2 | 3 | ### *Pull requests are welcome!* 4 | 5 | General Guidelines 6 | ------------------ 7 | 8 | * **Code style.** Please follow local code style. Ask if you're unsure. 9 | * **No warnings.** All generated code must compile without warnings. 10 | 11 | -------------------------------------------------------------------------------- /lib/PayPalHttp/IOException.php: -------------------------------------------------------------------------------- 1 | value = $value; 13 | $this->headers = array_merge([], $headers); 14 | } 15 | 16 | public function getValue() 17 | { 18 | return $this->value; 19 | } 20 | 21 | public function getHeaders() 22 | { 23 | return $this->headers; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paypal/paypalhttp", 3 | "type": "library", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "PayPal", 8 | "homepage": "https://github.com/paypal/paypalhttp_php/contributors" 9 | } 10 | ], 11 | "require": { 12 | "ext-curl": "*" 13 | }, 14 | "require-dev": { 15 | "phpunit/phpunit": "^5.7", 16 | "wiremock-php/wiremock-php": "1.43.2" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "PayPalHttp\\": "lib/PayPalHttp" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/PayPalHttp/HttpException.php: -------------------------------------------------------------------------------- 1 | statusCode = $statusCode; 26 | $this->headers = $headers; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/PayPalHttp/HttpResponse.php: -------------------------------------------------------------------------------- 1 | statusCode = $statusCode; 31 | $this->headers = $headers; 32 | $this->result = $body; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /vendor/ 3 | composer.phar 4 | composer.lock 5 | 6 | # User-specific stuff: 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/dictionaries 10 | 11 | .idea/codeStyles/Project.xml 12 | .idea/codeStyles/codeStyleConfig.xml 13 | .idea/* 14 | 15 | # Sensitive or high-churn files: 16 | .idea/**/dataSources/ 17 | .idea/**/dataSources.ids 18 | .idea/**/dataSources.xml 19 | .idea/**/dataSources.local.xml 20 | .idea/**/sqlDataSources.xml 21 | .idea/**/dynamic.xml 22 | .idea/**/uiDesigner.xml 23 | 24 | ## File-based project format: 25 | *.iws 26 | .idea/*.iml 27 | .idea/vcs.xml 28 | .idea/php.xml 29 | .idea/php-test-framework.xml 30 | .idea/modules.xml 31 | __files/* 32 | mappings/* 33 | -------------------------------------------------------------------------------- /lib/PayPalHttp/Serializer.php: -------------------------------------------------------------------------------- 1 | path = $path; 38 | $this->verb = $verb; 39 | $this->body = NULL; 40 | $this->headers = []; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/PayPalHttp/Serializer/Text.php: -------------------------------------------------------------------------------- 1 | body; 25 | if (is_string($body)) { 26 | return $body; 27 | } 28 | if (is_array($body)) { 29 | return json_encode($body); 30 | } 31 | return implode(" ", $body); 32 | } 33 | 34 | public function decode($data) 35 | { 36 | return $data; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/PayPalHttp/Serializer/Json.php: -------------------------------------------------------------------------------- 1 | body; 25 | if (is_string($body)) { 26 | return $body; 27 | } 28 | if (is_array($body)) { 29 | return json_encode($body); 30 | } 31 | throw new \Exception("Cannot serialize data. Unknown type"); 32 | } 33 | 34 | public function decode($data) 35 | { 36 | return json_decode($data); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2021 PayPal, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /lib/PayPalHttp/Curl.php: -------------------------------------------------------------------------------- 1 | curl = $curl; 24 | } 25 | 26 | public function setOpt($option, $value) 27 | { 28 | curl_setopt($this->curl, $option, $value); 29 | return $this; 30 | } 31 | 32 | public function close() 33 | { 34 | curl_close($this->curl); 35 | return $this; 36 | } 37 | 38 | public function exec() 39 | { 40 | return curl_exec($this->curl); 41 | } 42 | 43 | public function errNo() 44 | { 45 | return curl_errno($this->curl); 46 | } 47 | 48 | public function getInfo($option) 49 | { 50 | return curl_getinfo($this->curl, $option); 51 | } 52 | 53 | public function error() 54 | { 55 | return curl_error($this->curl); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/PayPalHttp/Serializer/Form.php: -------------------------------------------------------------------------------- 1 | body) || !$this->isAssociative($request->body)) 25 | { 26 | throw new \Exception("HttpRequest body must be an associative array when Content-Type is: " . $request->headers["Content-Type"]); 27 | } 28 | 29 | return http_build_query($request->body); 30 | } 31 | 32 | /** 33 | * @param $body 34 | * @return mixed 35 | * @throws \Exception as multipart does not support deserialization. 36 | */ 37 | public function decode($body) 38 | { 39 | throw new \Exception("CurlSupported does not support deserialization"); 40 | } 41 | 42 | private function isAssociative(array $array) 43 | { 44 | return array_values($array) !== $array; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecation Notice: 2 | This SDK is deprecated; you can continue to use it, but no new features or support requests will be accepted. An integration with [the new Server SDK](https://github.com/paypal/PayPal-Server-SDKs) is recommended. Review the [docs](https://developer.paypal.com/serversdk/http/getting-started/how-to-get-started/) for details. 3 | 4 | ## PayPal HttpClient 5 | 6 | PayPalHttp is a generic HTTP Client. 7 | 8 | In it's simplest form, an [`HttpClient`](lib/PayPalHttp/HttpClient.php) exposes an `execute` method which takes an [HTTP request](lib/PayPalHttp/HttpRequest.php), executes it against the domain described in an [Environment](lib/PayPalHttp/Environment.php), and returns an [HTTP response](lib/PayPalHttp/HttpResponse.php). 9 | 10 | ### Environment 11 | 12 | An [`Environment`](./lib/PayPalHttp/environment.rb) describes a domain that hosts a REST API, against which an `HttpClient` will make requests. `Environment` is a simple interface that wraps one method, `baseUrl`. 13 | 14 | ```php 15 | $env = new Environment('https://example.com'); 16 | ``` 17 | 18 | ### Requests 19 | 20 | HTTP requests contain all the information needed to make an HTTP request against the REST API. Specifically, one request describes a path, a verb, any path/query/form parameters, headers, attached files for upload, and body data. 21 | 22 | ### Responses 23 | 24 | HTTP responses contain information returned by a server in response to a request as described above. They are simple objects which contain a status code, headers, and any data returned by the server. 25 | 26 | ```php 27 | $request = new HttpRequest("/path", "GET"); 28 | $request->body[] = "some data"; 29 | 30 | $response = $client->execute($req); 31 | 32 | $statusCode = $response->statusCode; 33 | $headers = $response->headers; 34 | $data = $response->result; 35 | ``` 36 | 37 | ### Injectors 38 | 39 | Injectors are blocks that can be used for executing arbitrary pre-flight logic, such as modifying a request or logging data. Injectors are attached to an `HttpClient` using the `addInjector` method. 40 | 41 | The `HttpClient` executes its injectors in a first-in, first-out order, before each request. 42 | 43 | ```php 44 | class LogInjector implements Injector 45 | { 46 | public function inject($httpRequest) 47 | { 48 | // Do some logging here 49 | } 50 | } 51 | 52 | $logInjector = new LogInjector(); 53 | $client = new HttpClient($environment); 54 | $client->addInjector($logInjector); 55 | ... 56 | ``` 57 | 58 | ### Error Handling 59 | 60 | `HttpClient#execute` may throw an `Exception` if something went wrong during the course of execution. If the server returned a non-200 response, [IOException](lib/PayPalHttp/IOException.php) will be thrown, that will contain a status code and headers you can use for debugging. 61 | 62 | ```php 63 | try 64 | { 65 | $client->execute($req); 66 | } 67 | catch (HttpException $e) 68 | { 69 | $statusCode = $e->response->statusCode; 70 | $headers = $e->response->headers; 71 | $body = $e->response->result; 72 | } 73 | ``` 74 | 75 | ## License 76 | PayPalHttp-PHP is open source and available under the MIT license. See the [LICENSE](./LICENSE) file for more information. 77 | 78 | ## Contributing 79 | Pull requests and issues are welcome. Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for more details. 80 | -------------------------------------------------------------------------------- /lib/PayPalHttp/Encoder.php: -------------------------------------------------------------------------------- 1 | serializers[] = new Json(); 23 | $this->serializers[] = new Text(); 24 | $this->serializers[] = new Multipart(); 25 | $this->serializers[] = new Form(); 26 | } 27 | 28 | 29 | 30 | public function serializeRequest(HttpRequest $request) 31 | { 32 | if (!array_key_exists('content-type', $request->headers)) { 33 | $message = "HttpRequest does not have Content-Type header set"; 34 | echo $message; 35 | throw new \Exception($message); 36 | } 37 | 38 | $contentType = $request->headers['content-type']; 39 | /** @var Serializer $serializer */ 40 | $serializer = $this->serializer($contentType); 41 | 42 | if (is_null($serializer)) { 43 | $message = sprintf("Unable to serialize request with Content-Type: %s. Supported encodings are: %s", $contentType, implode(", ", $this->supportedEncodings())); 44 | echo $message; 45 | throw new \Exception($message); 46 | } 47 | 48 | if (!(is_string($request->body) || is_array($request->body))) { 49 | $message = "Body must be either string or array"; 50 | echo $message; 51 | throw new \Exception($message); 52 | } 53 | 54 | $serialized = $serializer->encode($request); 55 | 56 | if (array_key_exists("content-encoding", $request->headers) && $request->headers["content-encoding"] === "gzip") { 57 | $serialized = gzencode($serialized); 58 | } 59 | return $serialized; 60 | } 61 | 62 | 63 | public function deserializeResponse($responseBody, $headers) 64 | { 65 | 66 | if (!array_key_exists('content-type', $headers)) { 67 | $message = "HTTP response does not have Content-Type header set"; 68 | echo $message; 69 | throw new \Exception($message); 70 | } 71 | 72 | $contentType = $headers['content-type']; 73 | $contentType = strtolower($contentType); 74 | /** @var Serializer $serializer */ 75 | $serializer = $this->serializer($contentType); 76 | 77 | if (is_null($serializer)) { 78 | throw new \Exception(sprintf("Unable to deserialize response with Content-Type: %s. Supported encodings are: %s", $contentType, implode(", ", $this->supportedEncodings()))); 79 | } 80 | 81 | if (array_key_exists("content-encoding", $headers) && $headers["content-encoding"] === "gzip") { 82 | $responseBody = gzdecode($responseBody); 83 | } 84 | 85 | return $serializer->decode($responseBody); 86 | } 87 | 88 | private function serializer($contentType) 89 | { 90 | /** @var Serializer $serializer */ 91 | foreach ($this->serializers as $serializer) { 92 | try { 93 | if (preg_match($serializer->contentType(), $contentType) == 1) { 94 | return $serializer; 95 | } 96 | } catch (\Exception $ex) { 97 | $message = sprintf("Error while checking content type of %s: %s", get_class($serializer), $ex->getMessage()); 98 | echo $message; 99 | throw new \Exception($message, $ex->getCode(), $ex); 100 | } 101 | } 102 | 103 | return NULL; 104 | } 105 | 106 | private function supportedEncodings() 107 | { 108 | $values = []; 109 | /** @var Serializer $serializer */ 110 | foreach ($this->serializers as $serializer) { 111 | $values[] = $serializer->contentType(); 112 | } 113 | return $values; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/PayPalHttp/Serializer/Multipart.php: -------------------------------------------------------------------------------- 1 | body) || !$this->isAssociative($request->body)) 29 | { 30 | throw new \Exception("HttpRequest body must be an associative array when Content-Type is: " . $request->headers["content-type"]); 31 | } 32 | $boundary = "---------------------" . md5(mt_rand() . microtime()); 33 | $contentTypeHeader = $request->headers["content-type"]; 34 | $request->headers["content-type"] = "{$contentTypeHeader}; boundary={$boundary}"; 35 | 36 | $value_params = []; 37 | $file_params = []; 38 | 39 | $disallow = ["\0", "\"", "\r", "\n"]; 40 | 41 | $body = []; 42 | 43 | foreach ($request->body as $k => $v) { 44 | $k = str_replace($disallow, "_", $k); 45 | if (is_resource($v)) { 46 | $file_params[] = $this->prepareFilePart($k, $v, $boundary); 47 | } else if ($v instanceof FormPart) { 48 | $value_params[] = $this->prepareFormPart($k, $v, $boundary); 49 | } else { 50 | $value_params[] = $this->prepareFormField($k, $v, $boundary); 51 | } 52 | } 53 | 54 | $body = array_merge($value_params, $file_params); 55 | 56 | // add boundary for each parameters 57 | array_walk($body, function (&$part) use ($boundary) { 58 | $part = "--{$boundary}" . self::LINEFEED . "{$part}"; 59 | }); 60 | 61 | // add final boundary 62 | $body[] = "--{$boundary}--"; 63 | $body[] = ""; 64 | 65 | return implode(self::LINEFEED, $body); 66 | } 67 | 68 | public function decode($data) 69 | { 70 | throw new \Exception("Multipart does not support deserialization"); 71 | } 72 | 73 | private function isAssociative(array $array) 74 | { 75 | return array_values($array) !== $array; 76 | } 77 | 78 | private function prepareFormField($partName, $value, $boundary) 79 | { 80 | return implode(self::LINEFEED, [ 81 | "Content-Disposition: form-data; name=\"{$partName}\"", 82 | "", 83 | filter_var($value), 84 | ]); 85 | } 86 | 87 | private function prepareFilePart($partName, $file, $boundary) 88 | { 89 | $fileInfo = new finfo(FILEINFO_MIME_TYPE); 90 | $filePath = stream_get_meta_data($file)['uri']; 91 | $data = file_get_contents($filePath); 92 | $mimeType = $fileInfo->buffer($data); 93 | 94 | $splitFilePath = explode(DIRECTORY_SEPARATOR, $filePath); 95 | $filePath = end($splitFilePath); 96 | $disallow = ["\0", "\"", "\r", "\n"]; 97 | $filePath = str_replace($disallow, "_", $filePath); 98 | return implode(self::LINEFEED, [ 99 | "Content-Disposition: form-data; name=\"{$partName}\"; filename=\"{$filePath}\"", 100 | "Content-Type: {$mimeType}", 101 | "", 102 | $data, 103 | ]); 104 | } 105 | 106 | private function prepareFormPart($partName, $formPart, $boundary) 107 | { 108 | $contentDisposition = "Content-Disposition: form-data; name=\"{$partName}\""; 109 | 110 | $partHeaders = $formPart->getHeaders(); 111 | $formattedheaders = array_change_key_case($partHeaders); 112 | if (array_key_exists("content-type", $formattedheaders)) { 113 | if ($formattedheaders["content-type"] === "application/json") { 114 | $contentDisposition .= "; filename=\"{$partName}.json\""; 115 | } 116 | $tempRequest = new HttpRequest('/', 'POST'); 117 | $tempRequest->headers = $formattedheaders; 118 | $tempRequest->body = $formPart->getValue(); 119 | $encoder = new Encoder(); 120 | $partValue = $encoder->serializeRequest($tempRequest); 121 | } else { 122 | $partValue = $formPart->getValue(); 123 | } 124 | 125 | $finalPartHeaders = []; 126 | foreach ($partHeaders as $k => $v) { 127 | $finalPartHeaders[] = "{$k}: {$v}"; 128 | } 129 | 130 | $body = array_merge([$contentDisposition], $finalPartHeaders, [""], [$partValue]); 131 | 132 | return implode(self::LINEFEED, $body); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/PayPalHttp/HttpClient.php: -------------------------------------------------------------------------------- 1 | environment = $environment; 37 | $this->encoder = new Encoder(); 38 | $this->curlCls = Curl::class; 39 | } 40 | 41 | /** 42 | * Injectors are blocks that can be used for executing arbitrary pre-flight logic, such as modifying a request or logging data. 43 | * Executed in first-in first-out order. 44 | * 45 | * @param Injector $inj 46 | */ 47 | public function addInjector(Injector $inj) 48 | { 49 | $this->injectors[] = $inj; 50 | } 51 | 52 | /** 53 | * The method that takes an HTTP request, serializes the request, makes a call to given environment, and deserialize response 54 | * 55 | * @param HttpRequest $httpRequest 56 | * @return HttpResponse 57 | * 58 | * @throws HttpException 59 | * @throws IOException 60 | */ 61 | public function execute(HttpRequest $httpRequest) 62 | { 63 | $requestCpy = clone $httpRequest; 64 | $curl = new Curl(); 65 | 66 | foreach ($this->injectors as $inj) { 67 | $inj->inject($requestCpy); 68 | } 69 | 70 | $url = $this->environment->baseUrl() . $requestCpy->path; 71 | $formattedHeaders = $this->prepareHeaders($requestCpy->headers); 72 | if (!array_key_exists("user-agent", $formattedHeaders)) { 73 | $requestCpy->headers["user-agent"] = $this->userAgent(); 74 | } 75 | 76 | $body = ""; 77 | if (!is_null($requestCpy->body)) { 78 | $rawHeaders = $requestCpy->headers; 79 | $requestCpy->headers = $formattedHeaders; 80 | $body = $this->encoder->serializeRequest($requestCpy); 81 | $requestCpy->headers = $this->mapHeaders($rawHeaders,$requestCpy->headers); 82 | } 83 | 84 | $curl->setOpt(CURLOPT_URL, $url); 85 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, $requestCpy->verb); 86 | $curl->setOpt(CURLOPT_HTTPHEADER, $this->serializeHeaders($requestCpy->headers)); 87 | $curl->setOpt(CURLOPT_RETURNTRANSFER, 1); 88 | $curl->setOpt(CURLOPT_HEADER, 0); 89 | 90 | if (!is_null($requestCpy->body)) { 91 | $curl->setOpt(CURLOPT_POSTFIELDS, $body); 92 | } 93 | 94 | if (strpos($this->environment->baseUrl(), "https://") === 0) { 95 | $curl->setOpt(CURLOPT_SSL_VERIFYPEER, true); 96 | $curl->setOpt(CURLOPT_SSL_VERIFYHOST, 2); 97 | } 98 | 99 | if ($caCertPath = $this->getCACertFilePath()) { 100 | $curl->setOpt(CURLOPT_CAINFO, $caCertPath); 101 | } 102 | 103 | $response = $this->parseResponse($curl); 104 | $curl->close(); 105 | 106 | return $response; 107 | } 108 | 109 | /** 110 | * Returns an array representing headers with their keys 111 | * to be lower case 112 | * @param $headers 113 | * @return array 114 | */ 115 | public function prepareHeaders($headers){ 116 | $preparedHeaders = array_change_key_case($headers); 117 | if (array_key_exists("content-type", $preparedHeaders)) { 118 | $preparedHeaders["content-type"] = strtolower($preparedHeaders["content-type"]); 119 | } 120 | return $preparedHeaders; 121 | } 122 | 123 | /** 124 | * Returns an array representing headers with their key in 125 | * original cases and updated values 126 | * @param $rawHeaders 127 | * @param $formattedHeaders 128 | * @return array 129 | */ 130 | public function mapHeaders($rawHeaders, $formattedHeaders){ 131 | $rawHeadersKey = array_keys($rawHeaders); 132 | foreach ($rawHeadersKey as $array_key) { 133 | if(array_key_exists(strtolower($array_key), $formattedHeaders)){ 134 | $rawHeaders[$array_key] = $formattedHeaders[strtolower($array_key)]; 135 | } 136 | } 137 | return $rawHeaders; 138 | } 139 | 140 | /** 141 | * Returns default user-agent 142 | * 143 | * @return string 144 | */ 145 | public function userAgent() 146 | { 147 | return "PayPalHttp-PHP HTTP/1.1"; 148 | } 149 | 150 | /** 151 | * Return the filepath to your custom CA Cert if needed. 152 | * @return string 153 | */ 154 | protected function getCACertFilePath() 155 | { 156 | return null; 157 | } 158 | 159 | protected function setCurl(Curl $curl) 160 | { 161 | $this->curl = $curl; 162 | } 163 | 164 | protected function setEncoder(Encoder $encoder) 165 | { 166 | $this->encoder = $encoder; 167 | } 168 | 169 | private function serializeHeaders($headers) 170 | { 171 | $headerArray = []; 172 | if ($headers) { 173 | foreach ($headers as $key => $val) { 174 | $headerArray[] = $key . ": " . $val; 175 | } 176 | } 177 | 178 | return $headerArray; 179 | } 180 | 181 | private function parseResponse($curl) 182 | { 183 | $headers = []; 184 | $curl->setOpt(CURLOPT_HEADERFUNCTION, 185 | function($curl, $header) use (&$headers) 186 | { 187 | $len = strlen($header); 188 | 189 | $k = ""; 190 | $v = ""; 191 | 192 | $this->deserializeHeader($header, $k, $v); 193 | $headers[$k] = $v; 194 | 195 | return $len; 196 | }); 197 | 198 | $responseData = $curl->exec(); 199 | $statusCode = $curl->getInfo(CURLINFO_HTTP_CODE); 200 | $errorCode = $curl->errNo(); 201 | $error = $curl->error(); 202 | 203 | if ($errorCode > 0) { 204 | throw new IOException($error, $errorCode); 205 | } 206 | 207 | $body = $responseData; 208 | 209 | if ($statusCode >= 200 && $statusCode < 300) { 210 | $responseBody = NULL; 211 | 212 | if (!empty($body)) { 213 | $responseBody = $this->encoder->deserializeResponse($body, $this->prepareHeaders($headers)); 214 | } 215 | 216 | return new HttpResponse( 217 | $errorCode === 0 ? $statusCode : $errorCode, 218 | $responseBody, 219 | $headers 220 | ); 221 | } else { 222 | throw new HttpException($body, $statusCode, $headers); 223 | } 224 | } 225 | 226 | private function deserializeHeader($header, &$key, &$value) 227 | { 228 | if (strlen($header) > 0) { 229 | if (empty($header) || strpos($header, ':') === false) { 230 | return NULL; 231 | } 232 | 233 | list($k, $v) = explode(":", $header); 234 | $key = trim($k); 235 | $value = trim($v); 236 | } 237 | } 238 | } 239 | --------------------------------------------------------------------------------