requestHeaders[$key] = $value; } /** * The headers returned in the response * * @return array */ public function getResponseHeaders() { return $this->responseHeaders; } /** * The HTTP status response code * * @return int */ public function getResponseHttpStatusCode() { return $this->responseHttpStatusCode; } /** * Sends a request to the server * * @param string $url The endpoint to send the request to * @param string $method The request method * @param array $parameters The key value pairs to be sent in the body * * @return string Raw response from the server * * @throws \Facebook\FacebookSDKException */ public function send($url, $method = 'GET', $parameters = array()) { $this->openConnection($url, $method, $parameters); $this->tryToSendRequest(); // Need to verify the peer if ($this->curlErrorCode == 60 || $this->curlErrorCode == 77) { $this->addBundledCert(); $this->tryToSendRequest(); } if ($this->curlErrorCode) { throw new FacebookSDKException($this->curlErrorMessage, $this->curlErrorCode); } // Separate the raw headers from the raw body list($rawHeaders, $rawBody) = $this->extractResponseHeadersAndBody(); $this->responseHeaders = self::headersToArray($rawHeaders); $this->closeConnection(); return $rawBody; } /** * Opens a new curl connection * * @param string $url The endpoint to send the request to * @param string $method The request method * @param array $parameters The key value pairs to be sent in the body */ public function openConnection($url, $method = 'GET', $parameters = array()) { $options = array( CURLOPT_URL => $url, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 60, CURLOPT_RETURNTRANSFER => true, // Follow 301 redirects CURLOPT_HEADER => true, // Enable header processing ); 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(); } if (self::$disableIPv6) { $options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; } self::$facebookCurl->init(); self::$facebookCurl->setopt_array($options); } /** * Add a bundled cert to the connection */ public function addBundledCert() { self::$facebookCurl->setopt(CURLOPT_CAINFO, dirname(__FILE__) . DIRECTORY_SEPARATOR . 'fb_ca_chain_bundle.crt'); } /** * Closes an existing curl connection */ public function closeConnection() { self::$facebookCurl->close(); } /** * Try to send the request */ public function tryToSendRequest() { $this->sendRequest(); $this->curlErrorMessage = self::$facebookCurl->error(); $this->curlErrorCode = self::$facebookCurl->errno(); $this->responseHttpStatusCode = self::$facebookCurl->getinfo(CURLINFO_HTTP_CODE); } /** * Send the request and get the raw response from curl */ public function sendRequest() { $this->rawResponse = self::$facebookCurl->exec(); } /** * Compiles the request headers into a curl-friendly format * * @return array */ public function compileRequestHeaders() { $return = array(); foreach ($this->requestHeaders as $key => $value) { $return[] = $key . ': ' . $value; } return $return; } /** * Extracts the headers and the body into a two-part array * * @return array */ 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)); } /** * Converts raw header responses into an array * * @param string $rawHeaders * * @return array */ public static function headersToArray($rawHeaders) { $headers = array(); // Normalize line breaks $rawHeaders = str_replace("\r\n", "\n", $rawHeaders); // There will be multiple headers if a 301 was followed // or a proxy was followed, etc $headerCollection = explode("\n\n", trim($rawHeaders)); // We just want the last response (at the end) $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; } /** * Return proper header size * * @return integer */ private function getHeaderSize() { $headerSize = self::$facebookCurl->getinfo(CURLINFO_HEADER_SIZE); // This corrects a Curl bug where header size does not account // for additional Proxy headers. if ( self::needsCurlProxyFix() ) { // Additional way to calculate the request body size. 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; } /** * Detect versions of Curl which report incorrect header lengths when * using Proxies. * * @return boolean */ private static function needsCurlProxyFix() { $ver = self::$facebookCurl->version(); $version = $ver['version_number']; return $version < self::CURL_PROXY_QUIRK_VER; } }