Index: branches/5.2.x/core/units/helpers/curl_helper.php
===================================================================
--- branches/5.2.x/core/units/helpers/curl_helper.php	(revision 16750)
+++ branches/5.2.x/core/units/helpers/curl_helper.php	(revision 16751)
@@ -1,573 +1,586 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class kCurlHelper extends kHelper {
 
 		const REQUEST_METHOD_GET = 1;
 
 		const REQUEST_METHOD_POST = 2;
 
 		/**
 		 * ID of database record of currently active curl request
 		 *
 		 * @var int
 		 * @access protected
 		 */
 		protected $logId = 0;
 
 		/**
 		 * Connection to host
 		 *
 		 * @var resource
 		 * @access protected
 		 */
 		protected $connectionID = NULL;
 
 		/**
 		 * Response waiting timeout in seconds
 		 *
 		 * @var int
 		 * @access public
 		 */
 		public $timeout = 90;
 
 		/**
 		 * Follow to url, if redirect received instead of document (only works when open_basedir and safe mode is off)
 		 *
 		 * @var bool
 		 * @access public
 		 */
 		public $followLocation = false;
 
 		/**
 		 * Last response received by Curl
 		 *
 		 * @var string
 		 * @access public
 		 */
 		public $lastResponse = '';
 
 		/**
 		 * Last error code
 		 *
 		 * @var int
 		 * @access public
 		 */
 		public $lastErrorCode = 0;
 
 		/**
 		 * Last error message
 		 *
 		 * @var string
 		 * @access public
 		 */
 		public $lastErrorMsg = '';
 
 		/**
 		 * Most recent HTTP response code received
 		 *
 		 * @var int
 		 * @access public
 		 */
 		public $lastHTTPCode = 0;
 
 		/**
 		 * Count of intermediate redirects performed to get actual content
 		 *
 		 * @var int
 		 * @access protected
 		 */
 		protected $lastRedirectCount = 0;
 
 		/**
 		 * Default request method
 		 *
 		 * @var int
 		 * @access protected
 		 */
 		protected $requestMethod = self::REQUEST_METHOD_GET;
 
 		/**
 		 * Data to be sent using curl
 		 *
 		 * @var string
 		 * @access protected
 		 */
 		protected $requestData = '';
 
 		/**
 		 * Request headers (associative array)
 		 *
 		 * @var Array
 		 * @access protected
 		 */
 		protected $requestHeaders = Array ();
 
 		/**
 		 * Response headers
 		 *
 		 * @var Array
 		 * @access protected
 		 */
 		protected $responseHeaders = Array ();
 
 		/**
 		 * CURL options
 		 *
 		 * @var Array
 		 * @access protected
 		 */
 		protected $options = Array ();
 
 		/**
 		 * Indicates debug mode status
 		 *
 		 * @var bool
 		 * @access public
 		 */
 		public $debugMode = false;
 
 		/**
 		 * Creates an instance of kCurlHelper class
 		 */
 		public function __construct()
 		{
 			parent::__construct();
 
 			$this->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');
+			$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)
 		{
-			$this->responseHeaders[] = $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->responseHeaders = Array ();
-
 			$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);
 		}
-	}
\ No newline at end of file
+	}