<?php

/**
 * This file is part of SimpleSAMLphp. See the file COPYING in the
 * root of the distribution for licence information.
 *
 * This file defines a session handler which uses the default php
 * session handler for storage.
 *
 * @author Olav Morken, UNINETT AS. <andreas.solberg@uninett.no>
 * @package simpleSAMLphp
 */
class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler {

	/* This variable contains the session cookie name. */
	protected $cookie_name;


	/* Initialize the PHP session handling. This constructor is protected
	 * because it should only be called from
	 * SimpleSAML_SessionHandler::createSessionHandler(...).
	 */
	protected function __construct() {

		/* Call the parent constructor in case it should become
		 * necessary in the future.
		 */
		parent::__construct();

		/* Initialize the php session handling.
		 *
		 * If session_id() returns a blank string, then we need
		 * to call session start. Otherwise the session is already
		 * started, and we should avoid calling session_start().
		 */
		if(session_id() === '') {
			$config = SimpleSAML_Configuration::getInstance();

			$params = $this->getCookieParams();

			$version = explode('.', PHP_VERSION);
			if ((int)$version[0] === 5 && (int)$version[1] < 2) {
				session_set_cookie_params($params['lifetime'], $params['path'], $params['domain'], $params['secure']);
			} else {
				session_set_cookie_params($params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']);
			}

			$this->cookie_name = $config->getString('session.phpsession.cookiename', NULL);
			if (!empty($this->cookie_name)) {
				session_name($this->cookie_name);
			} else {
				$this->cookie_name = session_name();
			}

			$savepath = $config->getString('session.phpsession.savepath', NULL);
			if(!empty($savepath)) {
				session_save_path($savepath);
			}
		}
	}


	/**
	 * Create and set new session id.
	 *
	 * @return string  The new session id.
	 */
	public function newSessionId() {
		$session_cookie_params = session_get_cookie_params();

		if ($session_cookie_params['secure'] && !SimpleSAML_Utilities::isHTTPS()) {
			throw new SimpleSAML_Error_Exception('Session start with secure cookie not allowed on http.');
		}

		if (headers_sent()) {
			throw new SimpleSAML_Error_Exception('Cannot create new session - headers already sent.');
		}

		/* Generate new (secure) session id. */
		$sessionId = SimpleSAML_Utilities::stringToHex(SimpleSAML_Utilities::generateRandomBytes(16));
		SimpleSAML_Session::createSession($sessionId);

		if (session_id() !== '') {
			/* Session already started, close it. */
			session_write_close();
		}

		session_id($sessionId);
		session_start();

		return session_id();
	}


	/**
	 * Retrieve the session id of saved in the session cookie.
	 *
	 * @return string  The session id saved in the cookie.
	 */
	public function getCookieSessionId() {
		if(session_id() === '') {
			if(!self::hasSessionCookie()) {
				return self::newSessionId();
			}

			$session_cookie_params = session_get_cookie_params();

			if ($session_cookie_params['secure'] && !SimpleSAML_Utilities::isHTTPS()) {
				throw new SimpleSAML_Error_Exception('Session start with secure cookie not allowed on http.');
			}

			session_start();
		}

		return session_id();
	}


	/**
	 * Retrieve the session cookie name.
	 *
	 * @return string  The session cookie name.
	 */
	public function getSessionCookieName() {

		return $this->cookie_name;
	}


	/**
	 * Save the current session to the PHP session array.
	 *
	 * @param SimpleSAML_Session $session  The session object we should save.
	 */
	public function saveSession(SimpleSAML_Session $session) {

		$_SESSION['SimpleSAMLphp_SESSION'] = serialize($session);
	}


	/**
	 * Load the session from the PHP session array.
	 *
	 * @param string|NULL $sessionId  The ID of the session we should load, or NULL to use the default.
	 * @return SimpleSAML_Session|NULL  The session object, or NULL if it doesn't exist.
	 */
	public function loadSession($sessionId = NULL) {
		assert('is_string($sessionId) || is_null($sessionId)');

		if ($sessionId !== NULL) {
			if (session_id() === '') {
				/* session not initiated with getCookieSessionId(), start session without setting cookie */
				$ret = ini_set('session.use_cookies', '0');
				if ($ret === FALSE) {
					throw new SimpleSAML_Error_Exception('Disabling PHP option session.use_cookies failed.');
				}

				session_id($sessionId);
				session_start();
			} elseif ($sessionId !== session_id()) {
				throw new SimpleSAML_Error_Exception('Cannot load PHP session with a specific ID.');
			}
		} elseif (session_id() === '') {
			$sessionId = self::getCookieSessionId();
		}

		if (!isset($_SESSION['SimpleSAMLphp_SESSION'])) {
			return NULL;
		}

		$session = $_SESSION['SimpleSAMLphp_SESSION'];
		assert('is_string($session)');

		$session = unserialize($session);
		assert('$session instanceof SimpleSAML_Session');

		return $session;
	}


	/**
	 * Check whether the session cookie is set.
	 *
	 * This function will only return FALSE if is is certain that the cookie isn't set.
	 *
	 * @return bool  TRUE if it was set, FALSE if not.
	 */
	public function hasSessionCookie() {

		return array_key_exists($this->cookie_name, $_COOKIE);
	}


	/**
	 * Get the cookie parameters that should be used for session cookies.
	 *
	 * This function contains some adjustments from the default to provide backwards-compatibility.
	 *
	 * @return array
	 * @link http://www.php.net/manual/en/function.session-get-cookie-params.php
	 */
	public function getCookieParams() {

		$config = SimpleSAML_Configuration::getInstance();

		$ret = parent::getCookieParams();

		if ($config->hasValue('session.phpsession.limitedpath') && $config->hasValue('session.cookie.path')) {
			throw new SimpleSAML_Error_Exception('You cannot set both the session.phpsession.limitedpath and session.cookie.path options.');
		} elseif ($config->hasValue('session.phpsession.limitedpath')) {
			$ret['path'] = $config->getBoolean('session.phpsession.limitedpath', FALSE) ? '/' . $config->getBaseURL() : '/';
		}

		$ret['httponly'] = $config->getBoolean('session.phpsession.httponly', FALSE);

		return $ret;
	}

}