<?php
/*
 * framework-spider
 * util/HttpRequest.class.php
 * 
 * CopyRight(C)Framework-Spider Developer Team. 2010. All Right Reserved. 
 * URL         : http://sourceforge.jp/projects/frameworkspider/
 * Mail        : frameworkspider-dev@lists.sourceforge.jp
 * Auther      : Masanori Nakashima
 * Modifier    : Masanori Nakashima
 * Last Updated: 2010.06.23
 * 
 */
/**
 * ユーティリティ：HTTPリクエストを送信してレスポンスを取得するユーティリティオブジェクト
 * 
 * @package util ユーティリティパッケージ
 * @version 1.0.0
 * @copyright Copyright &copy; 2008, <m_nakashima@users.sourceforge.jp> http://sourceforge.jp/projects/shopformer/
 * @author  m.nakashima <m_nakashima@users.sourceforge.jp>
 * @since PHP 4.3
 */
class util_HttpRequest {
	/** リクエスト対象URL	*/
	var $requestUrl;
	/** リクエストプロトコル	*/
	var $requestProtocol	= 'http';
	/** リクエストホスト名	*/
	var $requestHostName;
	/** リクエストポート番号	*/
	var $requestPort		= 80;
	/** リクエストURI	*/
	var $requestUri			= '/';
	/** リクエストメソッド	*/
	var $requestMethod	= 'get';
	/** リクエストヘッダハッシュ	*/
	var $requestHeaders	= array();
	/** POSTリクエストデータハッシュ	*/
	var $postDataHash	= array();
	/** レスポンスステータスコード	*/
	var $statusCode			= 0;
	/** レスポンスステータスメッセージ	*/
	var $statusMessage		= '';
	/** レスポンスプロトコル	*/
	var $responseProtocol	= "";

	/** エラー番号	*/
	var $errorNumber;
	/** エラーメッセージ	*/
	var $errorMessage;

	/** レスポンスヘッダハッシュ	*/
	var $responseHeaders	= array();
	/** レスポンスボディ	*/
	var $responseBody		= null;

	/**
	 * コンストラクタ
	 */
	function util_HttpRequest( $url = null, $method='get', $headerParms=array() ){
		$this->requestHeaders	= array();
		$this->open( $url, $method, $headerParms );
	}
	/**
	 * 指定URLへの接続を作成準備します
	 */
	function open( $url = null, $method='get', $headerParms=null ) {
		// メソッドの設定
		if( 'post' == strtolower($method) ) {
			$this->requestMethod	= 'post';
		} else {
			$this->requestMethod	= 'get';
		}
		// URLの設定
		$this->requestUrl	= $url;
		// リクエストヘッダの設定
		if( is_array($headerParms) ) {
			$this->requestHeaders	= array();
			foreach($headerParms as $key=>$val ) {
				if( strlen($key)>0 && strlen($val) > 0 ) {
					$this->requestHeaders[$key]	= $val;
				}
			}
		}
		// POST情報をクリア
		$this->clearPostData();
	}
	/**
	 * リクエストを送信して結果を取得します
	 */
	function send( $contents=null, $timeout=10 ) {
		// URLの分割
		$urlElementArray	= explode('/',$this->requestUrl );
		if( count($urlElementArray) == 0 ) {
			$this->errorMessage	= 'Target URL is invalid!';
			return false;
		}
		if( strtolower($urlElementArray[0]) != 'http:'
			&& strtolower($urlElementArray[0]) != 'https:' ) {
			// httpまたはhttpsから始まらないならデフォルトでhttpを追加する
			array_unshift($urlElementArray,'');
			array_unshift($urlElementArray,'http:');
		}
		$this->requestProtocol	= str_replace(':','',trim(array_shift($urlElementArray)));
		array_shift($urlElementArray);
		$fqdn					= array_shift($urlElementArray);
		$this->requestUri		= '/'.implode('/',$urlElementArray);
		$this->requestHostName	= $fqdn;
		$this->requestPort		= 80;
		// ホスト名整理
		if( strpos( $this->requestHostName, ':' ) !== false ) {
			list( $this->requestHostName, $this->requestPort )	= explode(':',$this->requestHostName);
		} else if( strtolower($this->requestProtocol) == 'https' ) {
			$this->requestPort	= 443;
		}
		// ホスト存在確認
		$address	= gethostbyname($this->requestHostName);
		if( preg_match('/^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}/',$address) == 0 ) {
			$this->errorMessage	= 'Target Host is not found!';
			return false;
		}
		// 接続先の判断：IPアドレスで接続する
		$connectTarget			= $address;
		if( strtolower($this->requestProtocol) == 'https' ) {
			$connectTarget	= 'ssl://'.$address;
		}
		
		// PEAR HTTP_Requestの存在確認
		@include_once('HTTP/Request.php');
		if( class_exists('HTTP_Request') ){
			// PEARが存在するなら優先
			$httpRequestOptions = array(
				// タイムアウトの秒数指定
				'timeout' => $timeout,
				//"allowRedirects" => true, // リダイレクトの許可設定(true/false)
				//"maxRedirects" => 3, // リダイレクトの最大回数
			);
			// httpリクエストオブジェクト作成
			$httpRequest	= new HTTP_Request($this->requestUrl, $httpRequestOptions );
			if( 'post' == strtolower($this->requestMethod) ) {
				$httpRequest->setMethod(HTTP_REQUEST_METHOD_POST);
			} else {
				$httpRequest->setMethod(HTTP_REQUEST_METHOD_GET);
			}
			// headerパラメータ
			if( is_array($this->requestHeaders) && count($this->requestHeaders) > 0 ) {
				foreach( $this->requestHeaders as $key => $val ) {
					$httpRequest->addHeader($key,$val);
				}
			}
			// bodyコンテンツ
			$contentArray	= explode('&',$contents);
			foreach( $contentArray as $content ) {
				if( false !== strpos($contents,'=') ) {
					list( $key, $val ) = explode('=',$content);
					$this->postDataHash[urldecode($key)]	= urldecode($val);
				}
			}
			if( is_array($this->postDataHash) && count($this->postDataHash) > 0 ) {
				$httpRequest->addHeader('Content-Type','application/x-www-form-urlencoded');
				foreach( $this->postDataHash as $key => $val ) {
					$httpRequest->addPostData($key, $val);
				}
			}
			// リクエスト実行
			$httpResponse = $httpRequest->sendRequest();
			if (!PEAR::isError($httpResponse)) {
				// 応答内容の解析
				$this->responseProtocol	= $this->requestProtocol;
				if( method_exists($httpRequest,'getResponseCode') ) {
					$this->statusCode		= $httpRequest->getResponseCode();
				}
				if( method_exists($httpRequest,'getResponseReason') ) {
					$this->statusMessage	= $httpRequest->getResponseReason();
				}
				if( method_exists($httpRequest,'getResponseHeader') ) {
					$this->responseHeaders	= $httpRequest->getResponseHeader();
				}
				if( method_exists($httpRequest,'getResponseBody') ) {
					$this->responseBody		= $httpRequest->getResponseBody();
				}
				return true;
			} else {
				// リクエストエラー
				$this->errorMessage	= $httpResponse->getMessage();
				return false;
			}
		} else {
			// PEARが存在しない場合
			if( strtolower($this->requestProtocol) == 'https' ) {
				// SSL接続の場合
				if( function_exists('get_loaded_extensions') && in_array('openssl', get_loaded_extensions())) {
				} else {
					// openssl拡張モジュールがないならエラー
					$this->errorMessage	= 'Requre PEAR:HTTP_Request or openssl extension!';
					return false;
				}
			}
			// リクエスト文字列作成
			$requestStrings	= $this->_createRequestStrings( $contents );
			// ソケット通信
			if( $socket = @fsockopen( $connectTarget, $this->requestPort, $this->errorNumber, $this->errorMessage, $timeout ) ){
				if( @socket_set_timeout( $socket, $timeout ) ) {
					// リクエスト送信
					fwrite( $socket, $requestStrings );
					// レスポンス文字列受信
					$responseText	= '';
					while (!feof($socket)) {
						$responseText .= fgets($socket, 128);
					}
					$this->_convertResponse( $responseText );
				} else {
					$this->errorMessage	= 'Can\'t set connection timeout!';
					return false;
				}
				@fclose($socket);
			} else {
				return false;
			}
			return true;
		}
	}
	/**
	 * リクエスト用文字列を作成して取得します
	 */
	function _createRequestStrings( $contents ) {
		$method			= 'GET';
		$requestStrings	= '';
		if( 'post' == $this->requestMethod ) {
			$method	= 'POST';
		}
		$hostName	= $this->requestHostName;
		$uri		= $this->requestUri;
		$requestStrings	= "{$method} {$uri} HTTP/1.1\r\n"
			."Accept: */*\r\n"
			."Host: {$hostName}\r\n";
		foreach( $this->requestHeaders as $key => $value ) {
			if( strlen(trim($key)) > 0 && strlen(trim($value)) > 0 ) {
				$requestStrings	.= $key . ": " . $value . "\r\n";
			}
		}
		$requestStrings	.= "Connection: close\r\n";
		
		// POSTリクエストデータがあるなら文字列に追加
		if( $this->requestMethod == 'post' && is_array($this->postDataHash) ) {
			$addContentsArray	= array();
			foreach( $this->postDataHash as $key => $value ) {
				if( strlen($key) > 0 ) {
					array_push($addContentsArray, urlencode($key).'='.urlencode($value) );
				}
			}
			if( count($addContentsArray) > 0 ) {
				$str	= implode('&',$addContentsArray);
				if( strlen($contents) == 0 ) {
					$contents	= $str;
				} else if( preg_match('/\\&$/',$contents) == 0 ) {
					$contents	.= '&'.$str;
				} else {
					$contents	.= $str;
				}
			}
		}
		
		if( strlen($contents) > 0 ) {
			$requestStrings	.= "Content-Type: application/x-www-form-urlencoded\r\n";
			$requestStrings	.= "Content-Length: ".strlen($contents)."\r\n\r\n";
			$requestStrings	.= $contents."\r\n\r\n";
		} else {
			$requestStrings	.= "\r\n";
		}
		$this->requestStrings	= $requestStrings;
		return $requestStrings;
	}
	/**
	 * レスポンス文字列をヘッダとボディに分割します
	 */
	function _convertResponse( $responseText ) {
		// レスポンスヘッダ文字列(最初のCRLFCRLFまで)を取得
		$globalHeaderStr	= substr( $responseText, 0, strpos($responseText,"\r\n\r\n"));
		$globalHeaders		= explode("\r\n",$globalHeaderStr);
		// レスポンスステータスを確認
		$responseStatus		= array_shift($globalHeaders);
		$responseStatusElms	= explode(" ",$responseStatus);
		$this->responseProtocol	= array_shift($responseStatusElms);
		$this->statusCode		= array_shift($responseStatusElms);
		$this->statusMessage	= implode(' ',$responseStatusElms);
		// ヘッダをハッシュに格納
		foreach($globalHeaders as $line){
			if( strlen($line) > 0 ) {
				$headers	= explode(":",$line);
				if( count($headers) > 0 ) {
					$key	= array_shift($headers);
					$val	= implode(':',$headers);
					$this->responseHeaders[trim($key)] = trim($val);
				}
			}
		}
		// ボディの解析
		$transferEncoding	= null;
		foreach( $this->responseHeaders as $key => $val ) {
			if( 'transfer-encoding' == strtolower($key) ) {
				$transferEncoding	= strtolower($val);
				break;
			}
		}
		$lines					= explode("\r\n\r\n",$responseText);
		$globalHeaderStr		= array_shift($lines);
		$responseBody			= implode("\r\n\r\n",$lines);
		if( 'chunked' == $transferEncoding ) {
			// body部をCRLFで分割
			$realBody	= '';
			$elmArray	= explode("\r\n",$responseBody);
			$isData		= false;
			$lineData	= '';
			$dataBytes	= 0;
			foreach( $elmArray as $elm ) {
				if( $isData ) {
					$lineData	.= $elm;
					if( strlen($lineData) == $dataBytes ) {
						// チャンクサイズにマッチするならボディに接続してチャンク終了
						$realBody	.= $lineData;
						$isData		= false;
						$lineData	= '';
						$dataBytes	= 0;
					} else {
						// チャンクサイズにマッチしないならCRLF付加し次の行に接続
						$lineData	.= "\r\n";
					}
				} else {
					$dataBytes	= hexdec(trim($elm));
					$isData		= true;
				}
			}
			$this->responseBody	= $realBody;
		} else {
			$this->responseBody	= $responseBody;
		}
	}
	/**
	 * POSTで送信するデータを追加・設定します。keyが同じ場合は値を上書きします
	 * @param $key POSTするキー文字列
	 * @param $value POSTする値文字列
	 */
	function addPostData( $key, $value ) {
		if( strlen($key)>0 ) {
			$this->postDataHash[$key]	= $value;
		}
	}
	/**
	 * 現在設定されるPOSTデータをキーを指定して取得します。
	 * @param $key POSTするキー文字列
	 */
	function getPostData( $key ) {
		if( isset( $this->postDataHash[$key] ) ) {
			return $this->postDataHash[$key];
		} else {
			return null;
		}
	}
	/**
	 * 現在設定されているPOSTデータをクリアします
	 */
	function clearPostData() {
		$this->postDataHash	= array();
	}
	/**
	 * 要求リクエストのレスポンスボディをそのまま取得します
	 */
	function getDirectResponseBody($url,$headerParms=null) {
		if( !is_array($headerParms) ) {
			$headerParms	= array();
		}
		if( !array_key_exists('User-Agent',$headerParms)
			&& !array_key_exists('user-agent',$headerParms) ) {
			$headerParms['User-Agent']	= $_SERVER['HTTP_USER_AGENT'];
		}
		$this->open( $url, 'get', $headerParms );
		if( $this->send() ) {
			return $this->responseBody;
		}
		return $this->errorMessage;
	}
}
?>