<?php
 namespace Facebook\HttpClients; use Facebook\FacebookSDKException; class FacebookCurlHttpClient implements FacebookHttpable { protected $requestHeaders = array(); protected $responseHeaders = array(); protected $responseHttpStatusCode = 0; protected $curlErrorMessage = ''; protected $curlErrorCode = 0; protected $rawResponse; protected static $facebookCurl; const CURL_PROXY_QUIRK_VER = 0x071E00; const CONNECTION_ESTABLISHED = "HTTP/1.0 200 Connection established\r\n\r\n"; public function __construct(?FacebookCurl $facebookCurl = null) { self::$facebookCurl = $facebookCurl ?: new FacebookCurl(); } public function addRequestHeader($key, $value) { $this->requestHeaders[$key] = $value; } public function getResponseHeaders() { return $this->responseHeaders; } public function getResponseHttpStatusCode() { return $this->responseHttpStatusCode; } public function send($url, $method = 'GET', $parameters = array()) { $this->openConnection($url, $method, $parameters); $this->tryToSendRequest(); if ($this->curlErrorCode == 60 || $this->curlErrorCode == 77) { $this->addBundledCert(); $this->tryToSendRequest(); } if ($this->curlErrorCode) { throw new FacebookSDKException($this->curlErrorMessage, $this->curlErrorCode); } list($rawHeaders, $rawBody) = $this->extractResponseHeadersAndBody(); $this->responseHeaders = self::headersToArray($rawHeaders); $this->closeConnection(); return $rawBody; } public function openConnection($url, $method = 'GET', $parameters = array()) { $options = array( CURLOPT_URL => $url, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 60, CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, ); if ($method !== "GET") { $options[CURLOPT_POSTFIELDS] = $parameters; } if ($method === 'DELETE' || $method === 'PUT') { $options[CURLOPT_CUSTOMREQUEST] = $method; } if (!empty($this->requestHeaders)) { $options[CURLOPT_HTTPHEADER] = $this->compileRequestHeaders(); } self::$facebookCurl->init(); self::$facebookCurl->setopt_array($options); } public function addBundledCert() { self::$facebookCurl->setopt(CURLOPT_CAINFO, dirname(__FILE__) . DIRECTORY_SEPARATOR . 'fb_ca_chain_bundle.crt'); } public function closeConnection() { self::$facebookCurl->close(); } public function tryToSendRequest() { $this->sendRequest(); $this->curlErrorMessage = self::$facebookCurl->error(); $this->curlErrorCode = self::$facebookCurl->errno(); $this->responseHttpStatusCode = self::$facebookCurl->getinfo(CURLINFO_HTTP_CODE); } public function sendRequest() { $this->rawResponse = self::$facebookCurl->exec(); } public function compileRequestHeaders() { $return = array(); foreach ($this->requestHeaders as $key => $value) { $return[] = $key . ': ' . $value; } return $return; } public function extractResponseHeadersAndBody() { $headerSize = self::getHeaderSize(); $rawHeaders = mb_substr($this->rawResponse, 0, $headerSize); $rawBody = mb_substr($this->rawResponse, $headerSize); return array(trim($rawHeaders), trim($rawBody)); } public static function headersToArray($rawHeaders) { $headers = array(); $rawHeaders = str_replace("\r\n", "\n", $rawHeaders); $headerCollection = explode("\n\n", trim($rawHeaders)); $rawHeader = array_pop($headerCollection); $headerComponents = explode("\n", $rawHeader); foreach ($headerComponents as $line) { if (strpos($line, ': ') === false) { $headers['http_code'] = $line; } else { list ($key, $value) = explode(': ', $line); $headers[$key] = $value; } } return $headers; } private function getHeaderSize() { $headerSize = self::$facebookCurl->getinfo(CURLINFO_HEADER_SIZE); if ( self::needsCurlProxyFix() ) { if (preg_match('/Content-Length: (\d+)/', $this->rawResponse, $m)) { $headerSize = mb_strlen($this->rawResponse) - $m[1]; } elseif (stripos($this->rawResponse, self::CONNECTION_ESTABLISHED) !== false) { $headerSize += mb_strlen(self::CONNECTION_ESTABLISHED); } } return $headerSize; } private static function needsCurlProxyFix() { $ver = self::$facebookCurl->version(); $version = $ver['version_number']; return $version < self::CURL_PROXY_QUIRK_VER; } } 