debugMode = kUtil::constOn('DBG_CURL'); } /** * Reset connection settings (not results) after connection was closed * * @access protected */ protected function _resetSettings() { $this->timeout = 90; $this->followLocation = false; $this->requestMethod = self::REQUEST_METHOD_GET; $this->requestData = ''; $this->requestHeaders = Array (); $this->responseHeaders = Array (); $this->options = Array (); } /** * Resets information in last* properties. * * @return void */ protected function resetLastInfo() { $this->lastErrorCode = 0; $this->lastErrorMsg = ''; $this->lastHTTPCode = 0; $this->lastRedirectCount = 0; } /** * Sets CURL options (adds to options set before) * * @param Array $options_hash * @access public */ public function setOptions($options_hash) { $this->options = kUtil::array_merge_recursive($this->options, $options_hash); } /** * Combines user-defined and default options before setting them to CURL * * @access protected */ protected function prepareOptions() { $default_options = Array ( // customizable options CURLOPT_TIMEOUT => $this->timeout, // hardcoded options CURLOPT_RETURNTRANSFER => 1, CURLOPT_REFERER => PROTOCOL.SERVER_NAME, CURLOPT_MAXREDIRS => 5, // don't verify SSL certificates CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, // Prevents CURL from adding "Expect: 100-continue" header for POST requests. CURLOPT_HTTPHEADER => Array ('Expect:'), ); if ( isset($_SERVER['HTTP_USER_AGENT']) ) { $default_options[CURLOPT_USERAGENT] = $_SERVER['HTTP_USER_AGENT']; } if ($this->requestHeaders) { $default_options[CURLOPT_HTTPHEADER] = $this->prepareHeaders(); } // if we have post data, then POST else use GET method instead if ($this->requestMethod == self::REQUEST_METHOD_POST) { $default_options[CURLOPT_POST] = 1; $default_options[CURLOPT_POSTFIELDS] = $this->requestData; } $default_options[CURLOPT_HEADERFUNCTION] = array(&$this, 'ParseHeader'); $user_options = $this->options; // backup options, that user set directly $this->setOptions($default_options); $this->setOptions($user_options); $this->applyOptions(); } /** * Sets prepared options to CURL * * @access protected */ protected function applyOptions() { foreach ($this->options as $option_name => $option_value) { curl_setopt($this->connectionID, $option_name, $option_value); } } /** * Parses headers from CURL request * * @param resource $ch * @param string $header * @return int * @access protected */ protected function ParseHeader(&$ch, $header) { $trimmed_header = rtrim($header); if ( $trimmed_header ) { $this->responseHeaders[] = $trimmed_header; } return strlen($header); } /** * Sets request data for next query * * @param mixed $data Array or string */ public function SetRequestData($data) { if ( is_array($data) ) { $data = http_build_query($data); } $this->requestData = $data; } /** * Sets request data for next query and switches request method to POST * * @param mixed $data Array or string * @access public */ public function SetPostData($data) { $this->requestMethod = self::REQUEST_METHOD_POST; $this->SetRequestData($data); } /** * Sets request method to be used in next request * * @param int $request_method * * @throws InvalidArgumentException When invalid request method given. */ public function SetRequestMethod($request_method) { if ($request_method != self::REQUEST_METHOD_GET && $request_method != self::REQUEST_METHOD_POST) { throw new InvalidArgumentException('Method "' . __METHOD__ . '": Invalid $request_method parameter value'); return ; } $this->requestMethod = $request_method; } /** * Sets headers to be sent along with next query * * @param Array $headers * @access public */ public function SetHeaders($headers) { $this->requestHeaders = array_merge($this->requestHeaders, $headers); } /** * Returns compiled header to be used by curl * * @return Array * @access protected */ protected function prepareHeaders() { $ret = Array (); foreach ($this->requestHeaders as $header_name => $header_value) { $ret[] = is_numeric($header_name) ? $header_value : $header_name . ': ' . $header_value; } return $ret; } /** * Performs CURL request and returns it's result * * @param string $url * @param bool $close_connection * @param bool $log_status * @param string $log_message * @return string * @access public */ public function Send($url, $close_connection = true, $log_status = NULL, $log_message = '') { if ( isset($log_status) ) { // override debug mode setting $this->debugMode = $log_status; } $request_url = $url; if ( $this->requestMethod == self::REQUEST_METHOD_GET && $this->requestData ) { $request_url .= (strpos($request_url, '?') !== false ? '&' : '?') . $this->requestData; } $this->connectionID = curl_init($request_url); if ( $this->debugMode ) { // collect page data $page_data = Array (); if ( $_GET ) { $page_data[] = '_GET:' . "\n" . print_r($_GET, true); } if ( $_POST ) { $page_data[] = '_POST:' . "\n" . print_r($_POST, true); } if ( $_COOKIE ) { $page_data[] = '_COOKIE:' . "\n" . print_r($_COOKIE, true); } // create log record $fields_hash = Array ( 'Message' => $log_message, 'PageUrl' => $_SERVER['REQUEST_URI'], 'RequestUrl' => $url, 'PortalUserId' => $this->Application->RecallVar('user_id'), 'SessionKey' => $this->Application->GetSID(), 'IsAdmin' => $this->Application->isAdminUser ? 1 : 0, 'PageData' => implode("\n", $page_data), 'RequestData' => $this->requestData, 'RequestDate' => adodb_mktime(), ); $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'CurlLog'); $this->logId = $this->Conn->getInsertID(); } $this->prepareOptions(); $this->lastResponse = $this->_sendRequest(); $this->Finalize($close_connection); return $this->lastResponse; } /** * Reads data from remote url * * @return string * @access protected */ protected function _sendRequest() { $this->resetLastInfo(); curl_setopt($this->connectionID, CURLOPT_RETURNTRANSFER, true); if ( $this->followLocation ) { if ( $this->followLocationLimited() ) { return $this->_followLocationManually(); } else { // no restrictions - let curl do automatic redirects curl_setopt($this->connectionID, CURLOPT_FOLLOWLOCATION, true); } } return curl_exec($this->connectionID); } /** * Fixes curl inability to automatically follow location when safe_mode/open_basedir restriction in effect * * @return string * @access protected */ protected function _followLocationManually() { curl_setopt($this->connectionID, CURLOPT_HEADER, true); $data = curl_exec($this->connectionID); $http_code = $this->getInfo(CURLINFO_HTTP_CODE); if ( $http_code == 301 || $http_code == 302 ) { // safe more or open_basedir restriction - do redirects manually list ($header) = explode("\r\n\r\n", $data, 2); preg_match('/(Location:|URI:)(.*?)\n/', $header, $regs); $url = trim(array_pop($regs)); $url_parsed = parse_url($url); if ( $this->lastRedirectCount == $this->options[CURLOPT_MAXREDIRS] ) { return $this->setError(CURLE_TOO_MANY_REDIRECTS, 'Maximum (' . $this->options[CURLOPT_MAXREDIRS] . ') redirects followed'); } if ( isset($url_parsed) ) { curl_setopt($this->connectionID, CURLOPT_URL, $url); $this->lastRedirectCount++; return $this->_followLocationManually(); } } list(, $body) = explode("\r\n\r\n", $data, 2); return $body; } /** * Sets error manually. * * @param integer $code Code. * @param string $message Message. * * @return boolean */ protected function setError($code, $message) { $this->lastErrorCode = $code; $this->lastErrorMsg = $message; return false; } /** * Returns various info about request made * * @param int $info_type * @return mixed * * @see http://www.php.net/manual/ru/function.curl-getinfo.php * @access public */ public function getInfo($info_type) { if ( $info_type == CURLINFO_REDIRECT_COUNT && $this->followLocationLimited() ) { return $this->lastRedirectCount; } return curl_getinfo($this->connectionID, $info_type); } /** * Returns response headers. * * @return array */ public function getResponseHeaders() { return $this->responseHeaders; } /** * Detects, that follow location can't be done automatically by curl due safe_mode/open_basedir restrictions * * @return bool * @access protected */ protected function followLocationLimited() { return (defined('SAFE_MODE') && SAFE_MODE) || ini_get('open_basedir'); } /** * Finalizes curl request and saves some data from curl before closing connection * * @param bool $close_connection * @return void * @access public */ public function Finalize($close_connection = true) { if ( $this->lastErrorCode == 0 ) { // error not set manually -> get it from curl $this->lastErrorCode = curl_errno($this->connectionID); $this->lastErrorMsg = curl_error($this->connectionID); } $this->lastHTTPCode = $this->getInfo(CURLINFO_HTTP_CODE); if ( $close_connection ) { $this->CloseConnection(); } $this->_resetSettings(); } /** * Closes connection to server * * @access public */ public function CloseConnection() { curl_close($this->connectionID); if ( $this->debugMode ) { $fields_hash = Array ( 'ResponseData' => $this->lastResponse, 'ResponseDate' => adodb_mktime(), 'ResponseHttpCode' => $this->lastHTTPCode, 'CurlError' => $this->lastErrorCode != 0 ? '#' . $this->lastErrorCode . ' (' . $this->lastErrorMsg . ')' : '', ); $this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'CurlLog', 'LogId = ' . $this->logId); } // restore debug mode setting $this->debugMode = kUtil::constOn('DBG_CURL'); } /** * Checks, that last curl request was successful * * @return bool * @access public */ public function isGoodResponseCode() { if ( $this->lastErrorCode != 0 ) { return false; } return ($this->lastHTTPCode == 200) || ($this->lastHTTPCode >= 300 && $this->lastHTTPCode < 310); } }