<?php
/*
 * framework-spider
 * spider/HttpSession.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
 * 
 */
require_once(dirname(dirname(__FILE__))
.DIRECTORY_SEPARATOR.'util'
.DIRECTORY_SEPARATOR.'CharUtility.class.php');
/**
 * クライアントとの通信情報を保持するHTTPセッションコンテナクラスです
 * 
 * @package spider
 * @version 1.2.00
 * @copyright Copyright(c)2010, Masanori Nakashima <m_nakashima@users.sourceforge.jp>
 * @author Masanori Nakashima <m_nakashima@users.sourceforge.jp>
 * @access public
 */
class spider_HttpSession {
	var $valueHash		= array();
	var $classNameHash	= array();
	/**
	 * コンストラクタ
	 * @access public
	 */
	function spider_HttpSession(){
		$this->valueHash		= array();
		$this->classNameHash	= array();
	}
	/**
	 * セッションに値を設定します
	 * @param string $key セッションに登録するキー文字列
	 * @param mixed $value セッションに登録する値変数
	 * @return void
	 * @access public
	 */
	function setValue($key,$value) {
		$this->valueHash[$key]	= $value;
		if(is_object($value)){
			$this->classNameHash[$key]	= get_class($value);
		}
	}
	/**
	 * セッションに登録済みの値をキーを指定して取得します
	 * @param string $key セッションに登録するキー文字列
	 * @return mixed 値
	 * @access public
	 */
	function getValue($key) {
		if( isset($this->valueHash[$key]) ) {
			return $this->valueHash[$key];
		} else {
			return false;
		}
	}
	/**
	 * セッションに登録済みの値をキーを指定して削除します
	 * @param string $key セッションに登録するキー文字列
	 * @return void
	 * @access public
	 */
	function removeValue($key) {
		unset($this->valueHash[$key]);
		if( isset($this->classNameHash[$key]) ) {
			unset($this->classNameHash[$key]);
		}
	}
	
	//
	// 開始関連
	//
	/**
	 * spiderにおけるセッションを開始します
	 * セッションIDの重複を避けるため、生成したIDのセッションが存在するかどうか
	 * 確認してからセッションIDを発行します
	 * 新規セッション発行時にロックをかけるため通常のPHPセッションスタートする場合よりも
	 * 多少のオーバーヘッドがあります。
	 * @param spider_HttpRequest &$request spider_HttpRequestクラスインスタンス参照
	 * @return boolean 新規セッションを開始出来たらtrue
	 */
	function start( & $request ) {
		if( !defined('SPIDER_USE_SPIDER_SESSION_ID')
			|| SPIDER_USE_SPIDER_SESSION_ID === false
			|| strlen(SPIDER_USE_SPIDER_SESSION_ID) == 0 ) {
			// spiderセッション機構を利用しない場合はシステム任せにセッション開始
			session_set_cookie_params(60*60,SPIDER_URI_BASE);
			if( @session_start() ) {
				$sessionId			= session_id();
				$sessionName		= session_name();
				$getSessionParam	= strip_tags($sessionName.'='.$sessionId);
				$request->setAttribute('spider.session.id',$sessionId);
				$request->setAttribute('spider.session.name',$sessionName);
				$request->setAttribute('spider.session.getparam',$getSessionParam);
				return true;
			} else {
				return false;
			}
		}
		// セッション名を確認
		session_name(SPIDER_USE_SPIDER_SESSION_ID);
		// セッション情報保存フォルダのコントロール
		$getSettingSavePath	= true;
		if( defined('SPIDER_SESSION_SAVE_PATH') ) {
			if( SPIDER_SESSION_SAVE_PATH === true ) {
				$savePath	= DIR_PATH_TMP.'/session';
				if( !file_exists($savePath) ) {
					if( @mkdir($savePath, SPIDER_PERMITTION_DATA_FOLDER) ) {
						@chmod($savePath, SPIDER_PERMITTION_DATA_FOLDER);
					}
				}
				session_save_path($savePath);
				$getSettingSavePath	= false;
			} else if(strlen(SPIDER_SESSION_SAVE_PATH)
				&& file_exists(SPIDER_SESSION_SAVE_PATH)
				&& is_dir(SPIDER_SESSION_SAVE_PATH)){
				// 定義されたフォルダを優先
				session_save_path(SPIDER_SESSION_SAVE_PATH);
				$getSettingSavePath	= false;
			}
		}
		if( $getSettingSavePath ) {
			$savePath	= session_save_path();
			if( is_null($savePath)
				|| strlen(trim($savePath)) == 0
				|| !file_exists($savePath) ) {
				$savePath	= ini_get('session.save_path');
			}
			if( is_null($savePath)
				|| strlen(trim($savePath)) == 0
				|| !file_exists($savePath) ) {
				if( isset($_ENV['TEMP']) ) {
					$savePath	= $_ENV['TEMP'];
				}
			}
			if( !is_null($savePath)
				&& strlen(trim($savePath)) > 0
				&& file_exists($savePath)
				&& is_dir($savePath) ) {
				session_save_path($savePath);
			}
		}
		session_cache_expire(60);
		session_set_cookie_params(60*60,SPIDER_URI_BASE);
		
		$sessionId		= null;
		$remoteHost		= spider_HttpRequest::getRemoteHostString();
		$cookieString	= null;
	
		// POSTパラメータでセッションIDが送信されている場合
		if( isset($_POST[SPIDER_USE_SPIDER_SESSION_ID]) && preg_match('/^[0-9a-zA-Z]{16,32}$/',$_POST[SPIDER_USE_SPIDER_SESSION_ID]) > 0 ) {
			$sessionId	= $_POST[SPIDER_USE_SPIDER_SESSION_ID];
			if( spider_HttpSession::startValidate( $request, $sessionId ) ) {
				setcookie( SPIDER_USE_SPIDER_SESSION_ID, $sessionId, time()+60*60, SPIDER_URI_BASE );
				return true;
			}
		}
		// GETパラメータでセッションIDが送信されている場合
		if( isset($_GET[SPIDER_USE_SPIDER_SESSION_ID]) && preg_match('/^[0-9a-zA-Z]{16,32}$/',$_GET[SPIDER_USE_SPIDER_SESSION_ID]) > 0 ) {
			$sessionId	= $_GET[SPIDER_USE_SPIDER_SESSION_ID];
			if( spider_HttpSession::startValidate( $request, $sessionId ) ) {
				setcookie( SPIDER_USE_SPIDER_SESSION_ID, $sessionId, time()+60*60, SPIDER_URI_BASE );
				return true;
			}
		}
		// クッキーからセッション復元を試みる
		if( isset($_SERVER['HTTP_COOKIE']) ) {
			$cookieString	= $_SERVER['HTTP_COOKIE'];
		}
		$cookieParams	= explode('; ',$cookieString);
		foreach( $cookieParams as $param ) {
			$param	= trim($param);
			if( strlen($param) > 0 ) {
				list( $ckey, $cval )	= explode('=',$param);
				if( SPIDER_USE_SPIDER_SESSION_ID == $ckey ) {
					// セッション名のキーが見つかったら開始を試みる
					if( preg_match('/^[0-9a-zA-Z]{16,32}$/',$cval) > 0 ) {
						if( spider_HttpSession::startValidate( $request, $cval ) ) {
							// 正しいセッションIDなら
							return true;
						}
					}
				}
			}
		}
		// セッションを開始できていないなら新規に開始
		return spider_HttpSession::create( $request );
	}
	/**
	 * spiderにおけるセッションを独自IDで生成します
	 * セッションIDの重複を避けるため、生成したIDのセッションが存在するかどうか
	 * 確認してからセッションIDを発行します
	 * 新規セッション発行時にロックをかけるため通常のPHPセッションスタートする場合よりも
	 * 多少のオーバーヘッドがあります。
	 * @param spider_HttpRequest &$request spider_HttpRequestクラスインスタンス参照
	 * @return boolean 新規セッションを作成出来たらtrue
	 */
	function create( & $request ) {
		session_name(SPIDER_USE_SPIDER_SESSION_ID);
		$userAgent		= $request->getUserAgentString();
		$remoteHost		= spider_HttpRequest::getRemoteHostString();
		$cookieString	= null;
		if( isset($_SERVER['HTTP_COOKIE']) ) {
			$cookieString	= $_SERVER['HTTP_COOKIE'];
		}
		if( spider_HttpSession::processLock() ) {
			$sessionId	= spider_HttpSession::createSessionId();
			$savePath	= session_save_path();
			if( !is_null($savePath) && strlen($savePath) > 0
				&& file_exists($savePath) && is_dir($savePath ) ) {
				// セッション保存フォルダを取得できた場合は重複チェック
				$saveFilePath	= $savePath.'/sess_'.$sessionId;
				while( file_exists($saveFilePath) ) {
					$sessionId		= spider_HttpSession::createSessionId();
					$saveFilePath	= session_save_path().'/sess_'.$sessionId;
				}
			}
			session_id($sessionId);
			session_start();
			$_SESSION['spider.session.initialized']	= serialize(true);
			$_SESSION['spider.session.userAgent']	= serialize($userAgent);
			$_SESSION['spider.session.remoteHost']	= serialize($remoteHost);
			spider_HttpSession::processUnlock();
			// リモートホスト維持確認
			$validHostname	= true;
			$getSessionParam	= '';
			if( !is_null($remoteHost) && strlen($remoteHost) > 0 ) {
				// HostnameLookupが有効な環境の場合
				$agentClass	= $request->agentClass;
				$permitHostArray	= array();
				if( isset($GLOBALS['SPIDER_USER_AGENT_REMOTE_SESSION_PERMIT_HOSTS'][$agentClass]) ) {
					$permitHostArray	= $GLOBALS['SPIDER_USER_AGENT_REMOTE_SESSION_PERMIT_HOSTS'][$agentClass];
				}
				if( is_array($permitHostArray) && count($permitHostArray) > 0 ) {
					// セッション維持リモートホスト名の正規表現が指定されている場合
					// 前回アクセスのリモートホストに関係なく配列要素にマッチがあればOK
					$validHostname	= false;
					foreach( $permitHostArray as $hostRegx ) {
						if( preg_match($hostRegx,$remoteHost) > 0 ) {
							$validHostname	= true;
							$getSessionParam	= SPIDER_USE_SPIDER_SESSION_ID.'='.$sessionId;
							break;
						}
					}
				}
			}
			// GETパラメータで利用可能なセッションIDを保存
			$request->setAttribute('spider.session.getparam',$getSessionParam);
			$request->setAttribute('spider.session.id',$sessionId);
			$sessionName	= SPIDER_USE_SPIDER_SESSION_ID;
			$request->setAttribute('spider.session.name',$sessionName);
			// GETパラメータにセッションIDが指定されたアクセスされていた場合は
			// 新しいセッションIDを付加してリダイレクトする
			if( $validHostname && isset($_GET[SPIDER_USE_SPIDER_SESSION_ID]) ) {
				$request->redirectTo(SPIDER_URL_REQUEST);
			} else {
				// 許可ホスト名でない場合はセッションが切れてもよいのでリダイレクトしない
			}
			return true;
		} else {
			return false;
		}
	}
	/**
	 * 指定されたセッションIDの妥当性を検査して問題なければそのままセッションを開始します
	 * @param spider_HttpRequest &$request spider_HttpRequestクラスインスタンス参照
	 * @param string $sessionId セッションID文字列
	 * @return boolean セッションを開始出来たらtrue
	 */
	function startValidate( & $request, $sessionId ) {
		$userAgent		= $request->getUserAgentString();
		$remoteHost		= spider_HttpRequest::getRemoteHostString();
		// セッションファイルが実際にあるか確認する
		$savePath	= session_save_path().'/sess_'.$sessionId;
		if( !file_exists($savePath) ) {
			// 指定セッションは存在しない
			return false;
		}
		// 指定セッションIDでセッションを開始してみる
		session_id($sessionId);
		session_start();
		$spiderSessionInitialized	= false;
		$spiderSessionUserAgent		= null;
		if( isset($_SESSION['spider.session.initialized']) ) {
			$spiderSessionInitialized	= unserialize($_SESSION['spider.session.initialized']);
		}
		if( isset($_SESSION['spider.session.userAgent']) ) {
			$spiderSessionUserAgent	= unserialize($_SESSION['spider.session.userAgent']);
		}
		if( isset($_SESSION['spider.session.remoteHost']) ) {
			$spiderSessionRemoteHost= unserialize($_SESSION['spider.session.remoteHost']);
		}
		// 必要に応じてアクセス元ホストの照合
		$validHostname	= true;
		$getSessionParam	= '';
		if( !is_null($remoteHost) && strlen($remoteHost) > 0 ) {
			// HostnameLookupが有効な環境の場合
			$agentClass	= $request->agentClass;
			$permitHostArray	= array();
			if( isset($GLOBALS['SPIDER_USER_AGENT_REMOTE_SESSION_PERMIT_HOSTS'][$agentClass]) ) {
				$permitHostArray	= $GLOBALS['SPIDER_USER_AGENT_REMOTE_SESSION_PERMIT_HOSTS'][$agentClass];
			}
			if( is_array($permitHostArray) && count($permitHostArray) > 0 ) {
				// セッション維持リモートホスト名の正規表現が指定されている場合
				// 前回アクセスのリモートホストに関係なく配列要素にマッチがあればOK
				$validHostname	= false;
				foreach( $permitHostArray as $hostRegx ) {
					if( preg_match($hostRegx,$remoteHost) > 0 ) {
						$validHostname		= true;
						$getSessionParam	= SPIDER_USE_SPIDER_SESSION_ID.'='.$sessionId;
						break;
					}
				}
			} else {
				// セッション維持リモートホストが指定されていないケースの場合
				// 前回のアクセスと一致を要求
				if( $remoteHost != $spiderSessionRemoteHost ) {
					$validHostname	= false;
				}
			}
		} else {
			// HostnameLookupが有効でないなら確認できないのでしない
			$validHostname	= true;
		}
	
		if( $spiderSessionInitialized && $validHostname && $spiderSessionUserAgent == $userAgent ) {
			// 初期化されていてかつユーザーエージェントが同じならセッション開始に問題なし
			// 最終アクセスのリモートホストを記録
			$_SESSION['spider.session.userAgent']	= serialize($userAgent);
			$_SESSION['spider.session.remoteHost']	= serialize($remoteHost);
			$sessionName	= SPIDER_USE_SPIDER_SESSION_ID;
			$request->setAttribute('spider.session.id',$sessionId);
			$request->setAttribute('spider.session.name',$sessionName);
			$request->setAttribute('spider.session.getparam',$getSessionParam);
			return true;
		} else {
			// 初期化されていないかユーザーエージェントが違うならセッションIDが古いか不正
			// 一度セッションを閉じる
			session_write_close();
			// 正しくないセッションクッキーを削除する
			setcookie( SPIDER_USE_SPIDER_SESSION_ID, '', time()-42000, SPIDER_URI_BASE );
			// 正しくないパラメータは一度削除する
			$nullStr	= '';
			$request->setAttribute('spider.session.id',$nullStr);
			$request->setAttribute('spider.session.name',$nullStr);
			$request->setAttribute('spider.session.getparam',$nullStr);
			return false;
		}
	}
	/**
	 * 現在のセッションのセッションＩＤを変更します
	 * @param spider_HttpRequest &$request spider_HttpRequestクラスインスタンス参照
	 * @return boolean セッションIDを変更出来たらtrue
	 */
	function regenerateId( & $request ) {
		if( !defined('SPIDER_USE_SPIDER_SESSION_ID')
			|| SPIDER_USE_SPIDER_SESSION_ID === false
			|| strlen(SPIDER_USE_SPIDER_SESSION_ID) == 0 ) {
			// spiderセッション機構を利用しない場合はシステム任せにセッションIDを再生成
			session_set_cookie_params(60*60,SPIDER_URI_BASE);
			if( @session_regenerate_id() ) {
				$sessionId			= session_id();
				$sessionName		= session_name();
				$getSessionParam	= strip_tags($sessionName.'='.$sessionId);
				$request->setAttribute('spider.session.id',$sessionId);
				$request->setAttribute('spider.session.name',$sessionName);
				$request->setAttribute('spider.session.getparam',$getSessionParam);
				return true;
			} else {
				return false;
			}
		}
		// 現在のセッションの情報を取得
		$oldSessionHash	= array();
		foreach( $_SESSION as $key => $val ) {
			$oldSessionHash[$key]	= unserialize($val);
		}
		$remoteHost		= spider_HttpRequest::getRemoteHostString();
		$cookieString	= null;
		// 一度セッションを全て破棄する
		$sessionId	= session_id();
		$_SESSION = array();
		setcookie( SPIDER_USE_SPIDER_SESSION_ID, '', time()-42000, SPIDER_URI_BASE );
		@session_destroy();
		// 違うセッションIDを発行して新規にセッションを作成する
		session_write_close();
		// ファイルを削除する
		$savePath	= session_save_path().'/sess_'.$sessionId;
		@unlink($savePath);
		if( spider_HttpSession::create( $request ) ) {
			// 新しいセッションに古いセッション情報を登録
			foreach( $oldSessionHash as $key => $val ) {
				$_SESSION[$key]	= serialize($val);
			}
			return true;
		} else {
			return false;
		}
	}
	//
	//
	//
	/**
	 * 新しいセッションID文字列をランダムに生成します
	 * @return string 新規セッションID
	 * @access private
	 */
	function createSessionId() {
		$mictime				= microtime();
		list( $msec, $sec )		= explode(' ', $mictime );
		$microtimestamp			= $sec . str_replace('0.','',$msec );
		$unique_num				=
			util_CharUtility::get_rundom_key(2).substr($microtimestamp,0,2)
			.util_CharUtility::get_rundom_key(2).substr($microtimestamp,2,2)
			.util_CharUtility::get_rundom_key(2).substr($microtimestamp,4,2)
			.util_CharUtility::get_rundom_key(2).substr($microtimestamp,6,2)
			.util_CharUtility::get_rundom_key(2).substr($microtimestamp,8,2)
			.util_CharUtility::get_rundom_key(2).substr($microtimestamp,10,2)
			.util_CharUtility::get_rundom_key(2).substr($microtimestamp,12,2)
			.util_CharUtility::get_rundom_key(2).substr($microtimestamp,14,2);
		return $unique_num;
	}
	/**
	 * フォルダ作成ベースの処理ロック
	 * @access private
	 */
	function processLock(){
		// 現在ロックがかかっているか確認する
		$lock_path	= DIR_PATH_LOCK.'/spider.orgsession.mainlock';
		if( spider_HttpSession::processWait() ) {
			// ロックがかかっていないならロックディレクトリ作成
			@mkdir( $lock_path, SPIDER_PERMITTION_DATA_FOLDER );
			@chmod( $lock_path, SPIDER_PERMITTION_DATA_FOLDER );
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * 指定名のロックを解除します。
	 * @access private
	 */
	function processUnlock(){
		$lock_path	= DIR_PATH_LOCK.'/spider.orgsession.mainlock';
		if( is_dir( $lock_path ) ){
			rmdir( $lock_path );
		}
		return true;
	}
	/**
	 * ロックが解除されるまで待機します
	 * @access private
	 */
	function processWait() {
		$lock_path	= DIR_PATH_LOCK.'/spider.orgsession.mainlock';
		$loop_count	= 0;
		while( file_exists($lock_path) ) {
			$loop_count++;
			if( $loop_count > 300 ){
				return false;
			} else if ( !is_dir( $lock_path ) ) {
				return true;
			} else if ( filemtime($lock_path) + 30 < time() ){
				spider_HttpSession::processUnlock();
				return true;
			} else {
				usleep(500);
				continue;
			}
		}
		return true;
	}
}
/**
 * 下位互換のためのクラス宣言
 * @deprecated 1.2.00 - 2010/11/10
 */
class spider_Session extends spider_HttpSession {}
?>